Skip to content

Commit ae6484f

Browse files
committed
Correct TermOrdValComparator competitive iterator intoBitSet implementation (#14523)
Similar to @ChrisHegarty 's change for the count fix, this will move up the max check to before the into bit set. It seems like we could be calling intobitset erroneously on some edge cases. Additionally, TermOrdValComparator's competitive iterator will ensure that the docsWithField iterator (used when not all docs have a given field), is at least the baseline of the scoring window when doing intoBitSet. closes: #14517
1 parent 8405699 commit ae6484f

File tree

4 files changed

+94
-3
lines changed

4 files changed

+94
-3
lines changed

lucene/CHANGES.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ Bug Fixes
99
---------------------
1010

1111
* GITHUB#14522: Fix DISIDocIdStream::count so that it does not try to count beyond max.
12-
(Chris Hegarty}
12+
(Chris Hegarty)
13+
14+
* GITHUB#14523: Correct TermOrdValComparator competitive iterator so that it forces sparse
15+
field iteration to be at least scoring window baseline when doing intoBitSet. (Ben Trent, Adrien Grand)
1316

1417
======================= Lucene 10.2.0 =======================
1518

lucene/core/src/java/org/apache/lucene/search/DocIdSetIterator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ protected final int slowAdvance(int target) throws IOException {
263263
* @lucene.internal
264264
*/
265265
public void intoBitSet(int upTo, FixedBitSet bitSet, int offset) throws IOException {
266-
assert offset <= docID();
266+
assert offset <= docID() : "offset=" + offset + " docID()=" + docID() + " upTo=" + upTo;
267267
for (int doc = docID(); doc < upTo; doc = nextDoc()) {
268268
bitSet.set(doc - offset);
269269
}

lucene/core/src/java/org/apache/lucene/search/comparators/TermOrdValComparator.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -529,17 +529,21 @@ public int advance(int target) throws IOException {
529529

530530
@Override
531531
public void intoBitSet(int upTo, FixedBitSet bitSet, int offset) throws IOException {
532+
upTo = Math.min(upTo, maxDoc);
532533
if (upTo <= doc) {
533534
return;
534535
}
535536
// Optimize the case when intersecting the competitive iterator is expensive, which is when it
536537
// hasn't nailed down a disjunction of competitive terms yet.
537538
if (disjunction == null) {
538539
if (docsWithField != null) {
540+
// we need to be absolutely sure that the iterator is at least at offset
541+
if (docsWithField.docID() < offset) {
542+
docsWithField.advance(offset);
543+
}
539544
docsWithField.intoBitSet(upTo, bitSet, offset);
540545
doc = docsWithField.docID();
541546
} else {
542-
upTo = Math.min(upTo, maxDoc);
543547
bitSet.set(doc - offset, upTo - offset);
544548
doc = upTo;
545549
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.lucene.search.comparators;
18+
19+
import java.io.IOException;
20+
import org.apache.lucene.document.Document;
21+
import org.apache.lucene.document.Field;
22+
import org.apache.lucene.document.KeywordField;
23+
import org.apache.lucene.document.StringField;
24+
import org.apache.lucene.index.DirectoryReader;
25+
import org.apache.lucene.index.IndexWriter;
26+
import org.apache.lucene.index.IndexWriterConfig;
27+
import org.apache.lucene.index.LeafReaderContext;
28+
import org.apache.lucene.index.Term;
29+
import org.apache.lucene.search.BulkScorer;
30+
import org.apache.lucene.search.Collector;
31+
import org.apache.lucene.search.IndexSearcher;
32+
import org.apache.lucene.search.LeafCollector;
33+
import org.apache.lucene.search.Query;
34+
import org.apache.lucene.search.ScoreMode;
35+
import org.apache.lucene.search.Sort;
36+
import org.apache.lucene.search.SortField;
37+
import org.apache.lucene.search.SortedSetSelector;
38+
import org.apache.lucene.search.TermQuery;
39+
import org.apache.lucene.search.TopFieldCollectorManager;
40+
import org.apache.lucene.search.Weight;
41+
import org.apache.lucene.store.ByteBuffersDirectory;
42+
import org.apache.lucene.store.Directory;
43+
import org.apache.lucene.tests.util.LuceneTestCase;
44+
45+
public class TestTermOrdValComparator extends LuceneTestCase {
46+
47+
public void testIntoBitSetBugIssue14517() throws IOException {
48+
final int maxDoc = 5_000;
49+
try (Directory dir = new ByteBuffersDirectory()) {
50+
try (IndexWriter w = new IndexWriter(dir, new IndexWriterConfig())) {
51+
// high max doc to have a high number of unique values so that the competitive iterator is
52+
// initialized with `docsWithField` rather than specific (< 1024) terms
53+
for (int i = 0; i < maxDoc; ++i) {
54+
Document doc = new Document();
55+
// make the field to be sparse, so that the iterator is initialized with `docsWithField`
56+
if (i % 2 == 0) {
57+
doc.add(new StringField("field", "value", Field.Store.NO));
58+
doc.add(new KeywordField("sort", Integer.toString(i), Field.Store.NO));
59+
}
60+
w.addDocument(doc);
61+
}
62+
w.forceMerge(1);
63+
}
64+
try (DirectoryReader reader = DirectoryReader.open(dir)) {
65+
LeafReaderContext context = reader.leaves().get(0);
66+
IndexSearcher searcher = new IndexSearcher(reader);
67+
Query query = new TermQuery(new Term("field", "value"));
68+
Weight weight =
69+
searcher.createWeight(query, ScoreMode.COMPLETE_NO_SCORES, RANDOM_MULTIPLIER);
70+
SortField sortField = KeywordField.newSortField("sort", false, SortedSetSelector.Type.MIN);
71+
sortField.setMissingValue(SortField.STRING_LAST);
72+
Sort sort = new Sort(sortField);
73+
Collector collector = new TopFieldCollectorManager(sort, 10, 10).newCollector();
74+
LeafCollector leafCollector = collector.getLeafCollector(context);
75+
BulkScorer bulkScorer = weight.bulkScorer(context);
76+
// split on this specific doc ID so that the current doc of the competitive iterator
77+
// and the current doc of `docsWithField` are out of sync,
78+
// because the competitive iterator was just updated.
79+
bulkScorer.score(leafCollector, null, 0, 22);
80+
bulkScorer.score(leafCollector, null, 22, maxDoc);
81+
}
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)