serhiy-bzhezytskyy opened a new pull request, #16434:
URL: https://github.com/apache/lucene/pull/16434

   Fixes #14399.
   
   `TopFieldCollector` decided whether the search sort is a prefix of the index 
sort by inspecting only the **first** segment, then cached that decision for 
the whole search:
   
   ```java
   // as all segments are sorted in the same way, enough to check only the 1st 
segment for indexSort
   if (searchSortPartOfIndexSort == null) {
     final Sort indexSort = context.reader().getMetaData().sort();
     searchSortPartOfIndexSort = canEarlyTerminate(sort, indexSort);
     ...
   }
   ```
   
   The premise holds for a single index — `IndexWriter` enforces one index sort 
— but not for a `MultiReader`, which can combine indexes sorted differently. 
The cached decision is then wrong for later segments, and early termination 
drops documents that should have ranked first.
   
   **This is a wrong-results bug, not only a missed optimisation.** The added 
test builds a `MultiReader` over two indexes sorted in *opposite* directions 
(`ndv` ascending and `ndv` descending) and searches with `ndv` ascending. The 
competitive document sits at the end of the second segment's docid order, so 
the decision cached from the first segment early-terminates the second one and 
discards it. Without the fix:
   
   ```
   AssertionError: the smallest value (1, trailing docid of leaf B) must win 
expected:<1> but was:<50>
   ```
   
   ### The fix
   
   Decide it per segment, inside `TopFieldLeafCollector`, rather than caching 
across leaves.
   
   That change requires one adjustment: the old code called `disableSkipping()` 
on the shared `FieldComparator`, which is the wrong scope once the decision is 
per segment. `disableSkipping()` is now a default method on 
`LeafFieldComparator` and is called on that segment's leaf comparators; 
`NumericComparator` and `TermOrdValComparator` override it to drop their 
competitive iterator. This is close to what @jpountz suggested on the issue, 
except that the method moves to `LeafFieldComparator` rather than being removed 
from `FieldComparator` — the leaf is the object whose lifetime matches the 
decision.
   
   ### Verification
   
   - The new test fails on unmodified `main` with the assertion above, and 
passes with the fix. Checked both ways rather than only the passing direction.
   - `TestTopFieldCollector*`, `TestSortOptimization`, `TestSearchAfter` and 
the `comparators` package pass.
   - `./gradlew check -x test` is green.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to