@Test
  public void testMergeDay() {
    Result<SearchResultValue> r1 =
        new Result<SearchResultValue>(
            currTime,
            new SearchResultValue(ImmutableList.<SearchHit>of(new SearchHit("blah", "foo"))));

    Result<SearchResultValue> r2 =
        new Result<SearchResultValue>(
            currTime,
            new SearchResultValue(ImmutableList.<SearchHit>of(new SearchHit("blah2", "foo2"))));

    Result<SearchResultValue> expected =
        new Result<SearchResultValue>(
            new DateTime(QueryGranularity.DAY.truncate(currTime.getMillis())),
            new SearchResultValue(
                ImmutableList.<SearchHit>of(
                    new SearchHit("blah", "foo"), new SearchHit("blah2", "foo2"))));

    Result<SearchResultValue> actual =
        new SearchBinaryFn(
                new LexicographicSearchSortSpec(), QueryGranularity.DAY, Integer.MAX_VALUE)
            .apply(r1, r2);
    Assert.assertEquals(expected.getTimestamp(), actual.getTimestamp());
    assertSearchMergeResult(expected.getValue(), actual.getValue());
  }
  @Test
  public void testStrlenMerge() {
    Result<SearchResultValue> r1 =
        new Result<SearchResultValue>(
            currTime,
            new SearchResultValue(
                ImmutableList.<SearchHit>of(new SearchHit("blah", "thisislong"))));

    Result<SearchResultValue> r2 =
        new Result<SearchResultValue>(
            currTime,
            new SearchResultValue(ImmutableList.<SearchHit>of(new SearchHit("blah", "short"))));

    Result<SearchResultValue> expected =
        new Result<SearchResultValue>(
            currTime,
            new SearchResultValue(
                ImmutableList.<SearchHit>of(
                    new SearchHit("blah", "short"), new SearchHit("blah", "thisislong"))));

    Result<SearchResultValue> actual =
        new SearchBinaryFn(new StrlenSearchSortSpec(), QueryGranularity.ALL, Integer.MAX_VALUE)
            .apply(r1, r2);
    Assert.assertEquals(expected.getTimestamp(), actual.getTimestamp());
    assertSearchMergeResult(expected.getValue(), actual.getValue());
  }
  private static void verify(
      Iterable<Result<SelectResultValue>> expectedResults,
      Iterable<Result<SelectResultValue>> actualResults) {
    Iterator<Result<SelectResultValue>> expectedIter = expectedResults.iterator();
    Iterator<Result<SelectResultValue>> actualIter = actualResults.iterator();

    while (expectedIter.hasNext()) {
      Result<SelectResultValue> expected = expectedIter.next();
      Result<SelectResultValue> actual = actualIter.next();

      Assert.assertEquals(expected.getTimestamp(), actual.getTimestamp());

      for (Map.Entry<String, Integer> entry :
          expected.getValue().getPagingIdentifiers().entrySet()) {
        Assert.assertEquals(
            entry.getValue(), actual.getValue().getPagingIdentifiers().get(entry.getKey()));
      }

      Iterator<EventHolder> expectedEvts = expected.getValue().getEvents().iterator();
      Iterator<EventHolder> actualEvts = actual.getValue().getEvents().iterator();

      while (expectedEvts.hasNext()) {
        EventHolder exHolder = expectedEvts.next();
        EventHolder acHolder = actualEvts.next();

        Assert.assertEquals(exHolder.getTimestamp(), acHolder.getTimestamp());
        Assert.assertEquals(exHolder.getOffset(), acHolder.getOffset());

        for (Map.Entry<String, Object> ex : exHolder.getEvent().entrySet()) {
          Object actVal = acHolder.getEvent().get(ex.getKey());

          // work around for current II limitations
          if (acHolder.getEvent().get(ex.getKey()) instanceof Double) {
            actVal = ((Double) actVal).floatValue();
          }
          Assert.assertEquals(ex.getValue(), actVal);
        }
      }

      if (actualEvts.hasNext()) {
        throw new ISE("This event iterator should be exhausted!");
      }
    }

    if (actualIter.hasNext()) {
      throw new ISE("This iterator should be exhausted!");
    }
  }
  @Test
  public void testMergeLimit() {
    Result<SearchResultValue> r1 =
        new Result<SearchResultValue>(
            currTime,
            new SearchResultValue(ImmutableList.<SearchHit>of(new SearchHit("blah", "foo"))));

    Result<SearchResultValue> r2 =
        new Result<SearchResultValue>(
            currTime,
            new SearchResultValue(ImmutableList.<SearchHit>of(new SearchHit("blah2", "foo2"))));
    Result<SearchResultValue> expected = r1;
    Result<SearchResultValue> actual =
        new SearchBinaryFn(new LexicographicSearchSortSpec(), QueryGranularity.ALL, 1)
            .apply(r1, r2);
    Assert.assertEquals(expected.getTimestamp(), actual.getTimestamp());
    assertSearchMergeResult(expected.getValue(), actual.getValue());
  }
  public Iterable<Result<TimeBoundaryResultValue>> mergeResults(
      List<Result<TimeBoundaryResultValue>> results) {
    if (results == null || results.isEmpty()) {
      return Lists.newArrayList();
    }

    DateTime min = new DateTime(JodaUtils.MAX_INSTANT);
    DateTime max = new DateTime(JodaUtils.MIN_INSTANT);
    for (Result<TimeBoundaryResultValue> result : results) {
      TimeBoundaryResultValue val = result.getValue();

      DateTime currMinTime = val.getMinTime();
      if (currMinTime != null && currMinTime.isBefore(min)) {
        min = currMinTime;
      }
      DateTime currMaxTime = val.getMaxTime();
      if (currMaxTime != null && currMaxTime.isAfter(max)) {
        max = currMaxTime;
      }
    }

    final DateTime ts;
    final DateTime minTime;
    final DateTime maxTime;

    if (isMinTime()) {
      ts = min;
      minTime = min;
      maxTime = null;
    } else if (isMaxTime()) {
      ts = max;
      minTime = null;
      maxTime = max;
    } else {
      ts = min;
      minTime = min;
      maxTime = max;
    }

    return buildResult(ts, minTime, maxTime);
  }