diff --git a/server/src/main/java/org/opensearch/search/SearchService.java b/server/src/main/java/org/opensearch/search/SearchService.java index a02f9601eb093..2aa7e899242c7 100644 --- a/server/src/main/java/org/opensearch/search/SearchService.java +++ b/server/src/main/java/org/opensearch/search/SearchService.java @@ -1558,17 +1558,28 @@ private CanMatchResponse canMatch(ShardSearchRequest request, boolean checkRefre canMatch = aliasFilterCanMatch; } final FieldDoc searchAfterFieldDoc = getSearchAfterFieldDoc(request, context); - canMatch = canMatch && canMatchSearchAfter(searchAfterFieldDoc, minMax, sortBuilder); + canMatch = canMatch && canMatchSearchAfter(searchAfterFieldDoc, minMax, sortBuilder, request.source()); return new CanMatchResponse(canMatch || hasRefreshPending, minMax); } } } - public static boolean canMatchSearchAfter(FieldDoc searchAfter, MinAndMax minMax, FieldSortBuilder primarySortField) { + public static boolean canMatchSearchAfter( + FieldDoc searchAfter, + MinAndMax minMax, + FieldSortBuilder primarySortField, + SearchSourceBuilder source + ) { // Check for sort.missing == null, since in case of missing values sort queries, if segment/shard's min/max // is out of search_after range, it still should be printed and hence we should not skip segment/shard. - if (searchAfter != null && minMax != null && primarySortField != null && primarySortField.missing() == null) { + // Skipping search on shard/segment entirely can cause mismatch on total_tracking_hits, hence skip only if + // track_total_hits is false. + if (searchAfter != null + && minMax != null + && primarySortField != null + && primarySortField.missing() == null + && isTrackTotalHitsDisabled(source)) { final Object searchAfterPrimary = searchAfter.fields[0]; if (primarySortField.order() == SortOrder.DESC) { if (minMax.compareMin(searchAfterPrimary) > 0) { @@ -1597,6 +1608,10 @@ private static FieldDoc getSearchAfterFieldDoc(ShardSearchRequest request, Query return null; } + private static boolean isTrackTotalHitsDisabled(SearchSourceBuilder source) { + return source.trackTotalHitsUpTo() != null && source.trackTotalHitsUpTo() == SearchContext.TRACK_TOTAL_HITS_DISABLED; + } + /** * Returns true iff the given search source builder can be early terminated by rewriting to a match none query. Or in other words * if the execution of the search request can be early terminated without executing it. This is for instance not possible if diff --git a/server/src/main/java/org/opensearch/search/internal/ContextIndexSearcher.java b/server/src/main/java/org/opensearch/search/internal/ContextIndexSearcher.java index 6c3d2bb278bd0..aba6c0766b2cb 100644 --- a/server/src/main/java/org/opensearch/search/internal/ContextIndexSearcher.java +++ b/server/src/main/java/org/opensearch/search/internal/ContextIndexSearcher.java @@ -511,7 +511,12 @@ private boolean canMatchSearchAfter(LeafReaderContext ctx) throws IOException { ctx, primarySortField ); - return SearchService.canMatchSearchAfter(searchContext.searchAfter(), minMax, primarySortField); + return SearchService.canMatchSearchAfter( + searchContext.searchAfter(), + minMax, + primarySortField, + searchContext.request().source() + ); } } return true; diff --git a/server/src/test/java/org/opensearch/search/SearchServiceTests.java b/server/src/test/java/org/opensearch/search/SearchServiceTests.java index dc0eb62b9f0e5..a902424518cc9 100644 --- a/server/src/test/java/org/opensearch/search/SearchServiceTests.java +++ b/server/src/test/java/org/opensearch/search/SearchServiceTests.java @@ -1752,11 +1752,13 @@ public void validatePitStats(String index, long expectedPitCurrent, long expecte * Expected result is canMatch = false */ public void testCanMatchSearchAfterAscGreaterThanMax() throws IOException { + SearchSourceBuilder source = new SearchSourceBuilder(); + source.trackTotalHitsUpTo(SearchContext.TRACK_TOTAL_HITS_DISABLED); FieldDoc searchAfter = new FieldDoc(0, 0, new Long[] { 10L }); MinAndMax minMax = new MinAndMax(0L, 9L); FieldSortBuilder primarySort = new FieldSortBuilder("test"); primarySort.order(SortOrder.ASC); - assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort), false); + assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort, source), false); } /** @@ -1765,11 +1767,13 @@ public void testCanMatchSearchAfterAscGreaterThanMax() throws IOException { * Expected result is canMatch = true */ public void testCanMatchSearchAfterAscLessThanMax() throws IOException { + SearchSourceBuilder source = new SearchSourceBuilder(); + source.trackTotalHitsUpTo(SearchContext.TRACK_TOTAL_HITS_DISABLED); FieldDoc searchAfter = new FieldDoc(0, 0, new Long[] { 7L }); MinAndMax minMax = new MinAndMax(0L, 9L); FieldSortBuilder primarySort = new FieldSortBuilder("test"); primarySort.order(SortOrder.ASC); - assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort), true); + assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort, source), true); } /** @@ -1778,11 +1782,13 @@ public void testCanMatchSearchAfterAscLessThanMax() throws IOException { * Expected result is canMatch = true */ public void testCanMatchSearchAfterAscEqualMax() throws IOException { + SearchSourceBuilder source = new SearchSourceBuilder(); + source.trackTotalHitsUpTo(SearchContext.TRACK_TOTAL_HITS_DISABLED); FieldDoc searchAfter = new FieldDoc(0, 0, new Long[] { 9L }); MinAndMax minMax = new MinAndMax(0L, 9L); FieldSortBuilder primarySort = new FieldSortBuilder("test"); primarySort.order(SortOrder.ASC); - assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort), true); + assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort, source), true); } /** @@ -1791,11 +1797,13 @@ public void testCanMatchSearchAfterAscEqualMax() throws IOException { * Expected result is canMatch = true */ public void testCanMatchSearchAfterDescGreaterThanMin() throws IOException { + SearchSourceBuilder source = new SearchSourceBuilder(); + source.trackTotalHitsUpTo(SearchContext.TRACK_TOTAL_HITS_DISABLED); FieldDoc searchAfter = new FieldDoc(0, 0, new Long[] { 10L }); MinAndMax minMax = new MinAndMax(0L, 9L); FieldSortBuilder primarySort = new FieldSortBuilder("test"); primarySort.order(SortOrder.DESC); - assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort), true); + assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort, source), true); } /** @@ -1804,11 +1812,13 @@ public void testCanMatchSearchAfterDescGreaterThanMin() throws IOException { * Expected result is canMatch = false */ public void testCanMatchSearchAfterDescLessThanMin() throws IOException { + SearchSourceBuilder source = new SearchSourceBuilder(); + source.trackTotalHitsUpTo(SearchContext.TRACK_TOTAL_HITS_DISABLED); FieldDoc searchAfter = new FieldDoc(0, 0, new Long[] { -1L }); MinAndMax minMax = new MinAndMax(0L, 9L); FieldSortBuilder primarySort = new FieldSortBuilder("test"); primarySort.order(SortOrder.DESC); - assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort), false); + assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort, source), false); } /** @@ -1817,11 +1827,13 @@ public void testCanMatchSearchAfterDescLessThanMin() throws IOException { * Expected result is canMatch = true */ public void testCanMatchSearchAfterDescEqualMin() throws IOException { + SearchSourceBuilder source = new SearchSourceBuilder(); + source.trackTotalHitsUpTo(SearchContext.TRACK_TOTAL_HITS_DISABLED); FieldDoc searchAfter = new FieldDoc(0, 0, new Long[] { 0L }); MinAndMax minMax = new MinAndMax(0L, 9L); FieldSortBuilder primarySort = new FieldSortBuilder("test"); primarySort.order(SortOrder.DESC); - assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort), true); + assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort, source), true); } /** @@ -1830,14 +1842,32 @@ public void testCanMatchSearchAfterDescEqualMin() throws IOException { * Expected result is canMatch = true */ public void testCanMatchSearchAfterWithMissing() throws IOException { + SearchSourceBuilder source = new SearchSourceBuilder(); + source.trackTotalHitsUpTo(SearchContext.TRACK_TOTAL_HITS_DISABLED); FieldDoc searchAfter = new FieldDoc(0, 0, new Long[] { -1L }); MinAndMax minMax = new MinAndMax(0L, 9L); FieldSortBuilder primarySort = new FieldSortBuilder("test"); primarySort.order(SortOrder.DESC); // Should be false without missing values - assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort), false); + assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort, source), false); primarySort.missing("_last"); // Should be true with missing values - assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort), true); + assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort, source), true); + } + + /** + * Test for DESC order search_after query with track_total_hits=true. + * Min = 0L, Max = 9L, search_after = -1L + * With above min/max and search_after, it should not match, but since + * track_total_hits = true, + * Expected result is canMatch = true + */ + public void testCanMatchSearchAfterDescLessThanMin() throws IOException { + SearchSourceBuilder source = new SearchSourceBuilder(); + FieldDoc searchAfter = new FieldDoc(0, 0, new Long[] { -1L }); + MinAndMax minMax = new MinAndMax(0L, 9L); + FieldSortBuilder primarySort = new FieldSortBuilder("test"); + primarySort.order(SortOrder.DESC); + assertEquals(SearchService.canMatchSearchAfter(searchAfter, minMax, primarySort, source), false); } }