1212import org .apache .lucene .search .CollectionTerminatedException ;
1313import org .apache .lucene .search .Collector ;
1414import org .apache .lucene .search .DocIdSetIterator ;
15+ import org .apache .lucene .search .FilterLeafCollector ;
1516import org .apache .lucene .search .FilterScorable ;
1617import org .apache .lucene .search .LeafCollector ;
1718import org .apache .lucene .search .Scorable ;
@@ -118,7 +119,7 @@ private boolean isDocWithinMinScore(Scorable scorer) throws IOException {
118119 return minScore == null || scorer .score () >= minScore ;
119120 }
120121
121- private boolean doesDocMatchPostFilter (int doc , Bits postFilterBits ) {
122+ private static boolean doesDocMatchPostFilter (int doc , Bits postFilterBits ) {
122123 return postFilterBits == null || postFilterBits .get (doc );
123124 }
124125
@@ -143,31 +144,36 @@ public LeafCollector getLeafCollector(LeafReaderContext context) throws IOExcept
143144 Bits postFilterBits = getPostFilterBits (context );
144145
145146 if (aggsCollector == null ) {
146- LeafCollector topDocsLeafCollector ;
147+ LeafCollector tdlc = null ;
147148 try {
148- topDocsLeafCollector = topDocsCollector .getLeafCollector (context );
149+ tdlc = topDocsCollector .getLeafCollector (context );
149150 } catch (@ SuppressWarnings ("unused" ) CollectionTerminatedException e ) {
150151 // TODO we keep on collecting although we have nothing to collect (there is no top docs nor aggs leaf collector).
151152 // The reason is only to set the early terminated flag to the QueryResult like some tests expect. This needs fixing.
152153 if (terminateAfter == 0 ) {
153154 throw e ;
154155 }
155- topDocsLeafCollector = null ;
156+ }
157+ final LeafCollector topDocsLeafCollector = tdlc ;
158+ if (postFilterBits == null && terminateAfter == 0 && minScore == null ) {
159+ // no need to wrap if we just need to collect unfiltered docs through leaf collector
160+ // aggs collector was not originally provided so the overall score mode is that of the top docs collector
161+ return topDocsLeafCollector ;
156162 }
157163 return new TopDocsLeafCollector (postFilterBits , topDocsLeafCollector );
158164 }
159165
160- LeafCollector topDocsLeafCollector ;
166+ LeafCollector tdlc = null ;
161167 try {
162- topDocsLeafCollector = topDocsCollector .getLeafCollector (context );
168+ tdlc = topDocsCollector .getLeafCollector (context );
163169 } catch (@ SuppressWarnings ("unused" ) CollectionTerminatedException e ) {
164170 // top docs collector does not need this segment, but the aggs collector does.
165- topDocsLeafCollector = null ;
166171 }
172+ final LeafCollector topDocsLeafCollector = tdlc ;
167173
168- LeafCollector aggsLeafCollector ;
174+ LeafCollector alf = null ;
169175 try {
170- aggsLeafCollector = aggsCollector .getLeafCollector (context );
176+ alf = aggsCollector .getLeafCollector (context );
171177 } catch (@ SuppressWarnings ("unused" ) CollectionTerminatedException e ) {
172178 // aggs collector does not need this segment, but the top docs collector may.
173179 if (topDocsLeafCollector == null ) {
@@ -177,11 +183,41 @@ public LeafCollector getLeafCollector(LeafReaderContext context) throws IOExcept
177183 throw e ;
178184 }
179185 }
180- aggsLeafCollector = null ;
181186 }
182- // say that the aggs collector early terminates while the top docs collector does not, we still want to wrap in the same way
187+ final LeafCollector aggsLeafCollector = alf ;
188+
189+ if (topDocsLeafCollector == null && terminateAfter == 0 && minScore == null ) {
190+ // top docs collector early terminated, we can avoid wrapping as long as we don't need to apply terminate_after and min_score.
191+ // post_filter does not matter because it's not applied to aggs collection anyways. terminate_after matters only until we
192+ // address the different TODOs around needless collection to honour terminate_after after early termination.
193+ // aggs don't support skipping low scoring hits, so we can rely on setMinCompetitiveScore being a no-op already.
194+ return aggsLeafCollector ;
195+ }
196+
197+ // if that the aggs collector early terminates while the top docs collector does not, we still need to wrap the leaf collector
183198 // to enforce that setMinCompetitiveScore is a no-op. Otherwise we may allow the top docs collector to skip non competitive
184- // hits despite the score mode of the Collector did not allow it.
199+ // hits despite the score mode of the Collector did not allow it (because aggs don't support TOP_SCORES).
200+ if (aggsLeafCollector == null && postFilterBits == null && terminateAfter == 0 && minScore == null ) {
201+ // special case for early terminated aggs
202+ return new FilterLeafCollector (topDocsLeafCollector ) {
203+ @ Override
204+ public void setScorer (Scorable scorer ) throws IOException {
205+ super .setScorer (new FilterScorable (scorer ) {
206+ @ Override
207+ public void setMinCompetitiveScore (float minScore ) {
208+ // Ignore calls to setMinCompetitiveScore. The top docs collector may try to skip low
209+ // scoring hits, but the overall score_mode won't allow it because an aggs collector
210+ // was originally provided which never supports TOP_SCORES is not supported for aggs
211+ }
212+ });
213+ }
214+
215+ @ Override
216+ public DocIdSetIterator competitiveIterator () throws IOException {
217+ return topDocsLeafCollector .competitiveIterator ();
218+ }
219+ };
220+ }
185221 return new CompositeLeafCollector (postFilterBits , topDocsLeafCollector , aggsLeafCollector );
186222 }
187223
@@ -259,7 +295,7 @@ private class CompositeLeafCollector implements LeafCollector {
259295
260296 @ Override
261297 public void setScorer (Scorable scorer ) throws IOException {
262- if (cacheScores ) {
298+ if (cacheScores && topDocsLeafCollector != null && aggsLeafCollector != null ) {
263299 scorer = ScoreCachingWrappingScorer .wrap (scorer );
264300 }
265301 scorer = new FilterScorable (scorer ) {
@@ -300,9 +336,9 @@ public void collect(int doc) throws IOException {
300336 }
301337 }
302338 }
303- // min_score is applied to aggs as well as top hits
304- if ( isDocWithinMinScore ( scorer )) {
305- if (aggsLeafCollector != null ) {
339+ if ( aggsLeafCollector != null ) {
340+ // min_score is applied to aggs as well as top hits
341+ if (isDocWithinMinScore ( scorer ) ) {
306342 try {
307343 aggsLeafCollector .collect (doc );
308344 } catch (@ SuppressWarnings ("unused" ) CollectionTerminatedException e ) {
0 commit comments