2020package org .elasticsearch .search .query ;
2121
2222import org .apache .lucene .index .IndexReader ;
23+ import org .apache .lucene .index .LeafReaderContext ;
2324import org .apache .lucene .queries .MinDocQuery ;
2425import org .apache .lucene .queries .SearchAfterSortedDocQuery ;
2526import org .apache .lucene .search .BooleanClause ;
3637import org .apache .lucene .search .TopDocs ;
3738import org .apache .lucene .util .Counter ;
3839import org .elasticsearch .action .search .SearchTask ;
39- import org .elasticsearch .common .Nullable ;
4040import org .elasticsearch .common .lucene .Lucene ;
4141import org .elasticsearch .common .settings .Settings ;
4242import org .elasticsearch .common .util .concurrent .EsThreadPoolExecutor ;
6161import java .util .function .Consumer ;
6262
6363import static org .elasticsearch .search .query .QueryCollectorContext .createCancellableCollectorContext ;
64- import static org .elasticsearch .search .query .QueryCollectorContext .createEarlySortingTerminationCollectorContext ;
6564import static org .elasticsearch .search .query .QueryCollectorContext .createEarlyTerminationCollectorContext ;
6665import static org .elasticsearch .search .query .QueryCollectorContext .createFilteredCollectorContext ;
6766import static org .elasticsearch .search .query .QueryCollectorContext .createMinScoreCollectorContext ;
@@ -104,10 +103,8 @@ public void execute(SearchContext searchContext) throws QueryPhaseExecutionExcep
104103 // request, preProcess is called on the DFS phase phase, this is why we pre-process them
105104 // here to make sure it happens during the QUERY phase
106105 aggregationPhase .preProcess (searchContext );
107- Sort indexSort = searchContext .mapperService ().getIndexSettings ().getIndexSortConfig ()
108- .buildIndexSort (searchContext .mapperService ()::fullName , searchContext ::getForField );
109106 final ContextIndexSearcher searcher = searchContext .searcher ();
110- boolean rescore = execute (searchContext , searchContext .searcher (), searcher ::setCheckCancelled , indexSort );
107+ boolean rescore = execute (searchContext , searchContext .searcher (), searcher ::setCheckCancelled );
111108
112109 if (rescore ) { // only if we do a regular search
113110 rescorePhase .execute (searchContext );
@@ -127,11 +124,12 @@ public void execute(SearchContext searchContext) throws QueryPhaseExecutionExcep
127124 * wire everything (mapperService, etc.)
128125 * @return whether the rescoring phase should be executed
129126 */
130- static boolean execute (SearchContext searchContext , final IndexSearcher searcher ,
131- Consumer <Runnable > checkCancellationSetter , @ Nullable Sort indexSort ) throws QueryPhaseExecutionException {
127+ static boolean execute (SearchContext searchContext ,
128+ final IndexSearcher searcher ,
129+ Consumer <Runnable > checkCancellationSetter ) throws QueryPhaseExecutionException {
130+ final IndexReader reader = searcher .getIndexReader ();
132131 QuerySearchResult queryResult = searchContext .queryResult ();
133132 queryResult .searchTimedOut (false );
134-
135133 try {
136134 queryResult .from (searchContext .from ());
137135 queryResult .size (searchContext .size ());
@@ -161,7 +159,7 @@ static boolean execute(SearchContext searchContext, final IndexSearcher searcher
161159 // ... and stop collecting after ${size} matches
162160 searchContext .terminateAfter (searchContext .size ());
163161 searchContext .trackTotalHits (false );
164- } else if (canEarlyTerminate (indexSort , searchContext )) {
162+ } else if (canEarlyTerminate (reader , searchContext . sort () )) {
165163 // now this gets interesting: since the search sort is a prefix of the index sort, we can directly
166164 // skip to the desired doc
167165 if (after != null ) {
@@ -177,10 +175,14 @@ static boolean execute(SearchContext searchContext, final IndexSearcher searcher
177175 }
178176
179177 final LinkedList <QueryCollectorContext > collectors = new LinkedList <>();
178+ // whether the chain contains a collector that filters documents
179+ boolean hasFilterCollector = false ;
180180 if (searchContext .parsedPostFilter () != null ) {
181181 // add post filters before aggregations
182182 // it will only be applied to top hits
183183 collectors .add (createFilteredCollectorContext (searcher , searchContext .parsedPostFilter ().query ()));
184+ // this collector can filter documents during the collection
185+ hasFilterCollector = true ;
184186 }
185187 if (searchContext .queryCollectors ().isEmpty () == false ) {
186188 // plug in additional collectors, like aggregations
@@ -189,10 +191,14 @@ static boolean execute(SearchContext searchContext, final IndexSearcher searcher
189191 if (searchContext .minimumScore () != null ) {
190192 // apply the minimum score after multi collector so we filter aggs as well
191193 collectors .add (createMinScoreCollectorContext (searchContext .minimumScore ()));
194+ // this collector can filter documents during the collection
195+ hasFilterCollector = true ;
192196 }
193197 if (searchContext .terminateAfter () != SearchContext .DEFAULT_TERMINATE_AFTER ) {
194198 // apply terminate after after all filters collectors
195199 collectors .add (createEarlyTerminationCollectorContext (searchContext .terminateAfter ()));
200+ // this collector can filter documents during the collection
201+ hasFilterCollector = true ;
196202 }
197203
198204 boolean timeoutSet = scrollContext == null && searchContext .timeout () != null &&
@@ -240,21 +246,9 @@ static boolean execute(SearchContext searchContext, final IndexSearcher searcher
240246 // searchContext.lowLevelCancellation()
241247 collectors .add (createCancellableCollectorContext (searchContext .getTask ()::isCancelled ));
242248
243- final IndexReader reader = searcher .getIndexReader ();
244249 final boolean doProfile = searchContext .getProfilers () != null ;
245250 // create the top docs collector last when the other collectors are known
246- final TopDocsCollectorContext topDocsFactory = createTopDocsCollectorContext (searchContext , reader ,
247- collectors .stream ().anyMatch (QueryCollectorContext ::shouldCollect ));
248- final boolean shouldCollect = topDocsFactory .shouldCollect ();
249-
250- if (topDocsFactory .numHits () > 0 &&
251- (scrollContext == null || scrollContext .totalHits != -1 ) &&
252- canEarlyTerminate (indexSort , searchContext )) {
253- // top docs collection can be early terminated based on index sort
254- // add the collector context first so we don't early terminate aggs but only top docs
255- collectors .addFirst (createEarlySortingTerminationCollectorContext (reader , searchContext .query (), indexSort ,
256- topDocsFactory .numHits (), searchContext .trackTotalHits (), shouldCollect ));
257- }
251+ final TopDocsCollectorContext topDocsFactory = createTopDocsCollectorContext (searchContext , reader , hasFilterCollector );
258252 // add the top docs collector, the first collector context in the chain
259253 collectors .addFirst (topDocsFactory );
260254
@@ -268,9 +262,7 @@ static boolean execute(SearchContext searchContext, final IndexSearcher searcher
268262 }
269263
270264 try {
271- if (shouldCollect ) {
272- searcher .search (query , queryCollector );
273- }
265+ searcher .search (query , queryCollector );
274266 } catch (TimeExceededException e ) {
275267 assert timeoutSet : "TimeExceededException thrown even though timeout wasn't set" ;
276268 queryResult .searchTimedOut (true );
@@ -280,7 +272,7 @@ static boolean execute(SearchContext searchContext, final IndexSearcher searcher
280272
281273 final QuerySearchResult result = searchContext .queryResult ();
282274 for (QueryCollectorContext ctx : collectors ) {
283- ctx .postProcess (result , shouldCollect );
275+ ctx .postProcess (result );
284276 }
285277 EsThreadPoolExecutor executor = (EsThreadPoolExecutor )
286278 searchContext .indexShard ().getThreadPool ().executor (ThreadPool .Names .SEARCH );
@@ -317,13 +309,21 @@ static boolean returnsDocsInOrder(Query query, SortAndFormats sf) {
317309 }
318310
319311 /**
320- * Returns true if the provided <code>searchContext</code> can early terminate based on <code>indexSort</code>
321- * @param indexSort The index sort specification
322- * @param context The search context for the request
323- */
324- static boolean canEarlyTerminate (Sort indexSort , SearchContext context ) {
325- final Sort sort = context .sort () == null ? Sort .RELEVANCE : context .sort ().sort ;
326- return indexSort != null && EarlyTerminatingSortingCollector .canEarlyTerminate (sort , indexSort );
312+ * Returns whether collection within the provided <code>reader</code> can be early-terminated if it sorts
313+ * with <code>sortAndFormats</code>.
314+ **/
315+ static boolean canEarlyTerminate (IndexReader reader , SortAndFormats sortAndFormats ) {
316+ if (sortAndFormats == null || sortAndFormats .sort == null ) {
317+ return false ;
318+ }
319+ final Sort sort = sortAndFormats .sort ;
320+ for (LeafReaderContext ctx : reader .leaves ()) {
321+ Sort indexSort = ctx .reader ().getMetaData ().getSort ();
322+ if (indexSort == null || EarlyTerminatingSortingCollector .canEarlyTerminate (sort , indexSort ) == false ) {
323+ return false ;
324+ }
325+ }
326+ return true ;
327327 }
328328
329329 private static class TimeExceededException extends RuntimeException {}
0 commit comments