You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by iv...@apache.org on 2026/05/19 13:16:53 UTC
(lucene) branch branch_10x updated: Use SortedSkipperScorerSupplier in SortedNumericDocValuesRangeQuery when applicable (#16085)
This is an automated email from the ASF dual-hosted git repository.
iverase pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/lucene.git
The following commit(s) were added to refs/heads/branch_10x by this push:
new ef272d01304 Use SortedSkipperScorerSupplier in SortedNumericDocValuesRangeQuery when applicable (#16085)
ef272d01304 is described below
commit ef272d01304e2cc40aee97e9f664fb123699531b
Author: Ignacio Vera <iv...@apache.org>
AuthorDate: Tue May 19 14:15:24 2026 +0100
Use SortedSkipperScorerSupplier in SortedNumericDocValuesRangeQuery when applicable (#16085)
---
lucene/CHANGES.txt | 5 +-
.../document/SortedNumericDocValuesRangeQuery.java | 94 +++++++++-------------
.../apache/lucene/search/TestDocValuesQueries.java | 43 ++++++++--
3 files changed, 75 insertions(+), 67 deletions(-)
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 0d86687d833..5026bdb839a 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -121,8 +121,9 @@ Optimizations
* GITHUB#16001: IndexSearcher.count() was calling query.rewrite twice, a regression since v9.10 (David Smiley)
-* GITHUB#16061, GITHUB#16070: Improve cost estimation in SortedSetDocValuesRangeQuery when using DocValuesSkipper
- and the field is dense and is the primary sort of the index to reduce the number of doc values visited. (Ignacio Vera)
+* GITHUB#16061, GITHUB#16070, GITHUB#16085: Improve cost estimation in SortedSetDocValuesRangeQuery and
+ SortedNumericDocValuesRangeQuery when using DocValuesSkipper, the field is dense and is the primary sort of
+ the index to reduce the number of doc values visited. (Ignacio Vera)
* GITHUB#16048: Defer Sorter.DocMap packing until after flush (Tim Brooks)
diff --git a/lucene/core/src/java/org/apache/lucene/document/SortedNumericDocValuesRangeQuery.java b/lucene/core/src/java/org/apache/lucene/document/SortedNumericDocValuesRangeQuery.java
index 64ceaa43c96..37f628f4cc1 100644
--- a/lucene/core/src/java/org/apache/lucene/document/SortedNumericDocValuesRangeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/SortedNumericDocValuesRangeQuery.java
@@ -41,6 +41,7 @@ import org.apache.lucene.search.QueryVisitor;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.ScorerSupplier;
import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.Weight;
@@ -137,14 +138,11 @@ final class SortedNumericDocValuesRangeQuery extends NumericDocValuesRangeQuery
final NumericDocValues singleton = DocValues.unwrapSingleton(values);
final DocValuesSkipper skipper = context.reader().getDocValuesSkipper(field);
+ final SortField primarySortField;
if (singleton != null) {
- if (skipper != null) {
- final DocIdSetIterator psIterator =
- getDocIdSetIteratorOrNullForPrimarySort(context.reader(), singleton, skipper);
- if (psIterator != null) {
- return ConstantScoreScorerSupplier.fromIterator(
- psIterator, score(), scoreMode, maxDoc);
- }
+ if (skipper != null
+ && (primarySortField = densePrimarySort(context.reader(), skipper)) != null) {
+ return getScorerSupplierFromDensePrimarySort(singleton, skipper, primarySortField);
}
return ConstantScoreScorerSupplier.fromIterator(
TwoPhaseIterator.asDocIdSetIterator(
@@ -190,12 +188,40 @@ final class SortedNumericDocValuesRangeQuery extends NumericDocValuesRangeQuery
}
return -1;
}
+
+ private ScorerSupplier getScorerSupplierFromDensePrimarySort(
+ NumericDocValues singleton, DocValuesSkipper skipper, SortField sortField) {
+ return new SortedSkipperScorerSupplier(skipper, sortField, score(), scoreMode) {
+
+ @Override
+ protected long getLowerValue() {
+ return lowerValue;
+ }
+
+ @Override
+ protected long getUpperValue() {
+ return upperValue;
+ }
+
+ @Override
+ protected int nextDoc(int startDocId, LongPredicate predicate) throws IOException {
+ int doc = singleton.docID();
+ if (startDocId > doc) {
+ doc = singleton.advance(startDocId);
+ }
+ for (; doc < DocIdSetIterator.NO_MORE_DOCS; doc = singleton.nextDoc()) {
+ if (predicate.test(singleton.longValue())) {
+ break;
+ }
+ }
+ return doc;
+ }
+ };
+ }
};
}
- private DocIdSetIterator getDocIdSetIteratorOrNullForPrimarySort(
- LeafReader reader, NumericDocValues numericDocValues, DocValuesSkipper skipper)
- throws IOException {
+ private SortField densePrimarySort(LeafReader reader, DocValuesSkipper skipper) {
if (skipper.docCount() != reader.maxDoc()) {
return null;
}
@@ -205,52 +231,6 @@ final class SortedNumericDocValuesRangeQuery extends NumericDocValuesRangeQuery
|| indexSort.getSort()[0].getField().equals(field) == false) {
return null;
}
-
- final int minDocID;
- final int maxDocID;
- if (indexSort.getSort()[0].getReverse()) {
- if (skipper.maxValue() <= upperValue) {
- minDocID = 0;
- } else {
- skipper.advance(Long.MIN_VALUE, upperValue);
- minDocID = nextDoc(skipper.minDocID(0), numericDocValues, l -> l <= upperValue);
- }
- if (skipper.minValue() >= lowerValue) {
- maxDocID = skipper.docCount();
- } else {
- skipper.advance(Long.MIN_VALUE, lowerValue);
- maxDocID = nextDoc(skipper.minDocID(0), numericDocValues, l -> l < lowerValue);
- }
- } else {
- if (skipper.minValue() >= lowerValue) {
- minDocID = 0;
- } else {
- skipper.advance(lowerValue, Long.MAX_VALUE);
- minDocID = nextDoc(skipper.minDocID(0), numericDocValues, l -> l >= lowerValue);
- }
- if (skipper.maxValue() <= upperValue) {
- maxDocID = skipper.docCount();
- } else {
- skipper.advance(upperValue, Long.MAX_VALUE);
- maxDocID = nextDoc(skipper.minDocID(0), numericDocValues, l -> l > upperValue);
- }
- }
- return minDocID == maxDocID
- ? DocIdSetIterator.empty()
- : DocIdSetIterator.range(minDocID, maxDocID);
- }
-
- private static int nextDoc(int startDoc, NumericDocValues docValues, LongPredicate predicate)
- throws IOException {
- int doc = docValues.docID();
- if (startDoc > doc) {
- doc = docValues.advance(startDoc);
- }
- for (; doc < DocIdSetIterator.NO_MORE_DOCS; doc = docValues.nextDoc()) {
- if (predicate.test(docValues.longValue())) {
- break;
- }
- }
- return doc;
+ return indexSort.getSort()[0];
}
}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestDocValuesQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestDocValuesQueries.java
index 8abec49e26a..9997a9cc15c 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestDocValuesQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestDocValuesQueries.java
@@ -27,6 +27,7 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.IntFunction;
import org.apache.lucene.codecs.Codec;
import org.apache.lucene.codecs.lucene90.Lucene90DocValuesFormat;
import org.apache.lucene.document.Document;
@@ -40,6 +41,7 @@ import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
@@ -47,7 +49,6 @@ import org.apache.lucene.tests.index.RandomIndexWriter;
import org.apache.lucene.tests.search.QueryUtils;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.tests.util.TestUtil;
-import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.NumericUtils;
@@ -761,11 +762,29 @@ public class TestDocValuesQueries extends LuceneTestCase {
}
public void testPrimarySortDenseSortedDocValuesExactMatch() throws IOException {
+ doTestPrimarySortDenseExactMatch(
+ SortField.Type.STRING,
+ i -> SortedDocValuesField.indexedField("dv", newBytesRef(i + "")),
+ i ->
+ SortedDocValuesField.newSlowRangeQuery(
+ "dv", newBytesRef(i + ""), newBytesRef(i + ""), true, true));
+ }
+
+ public void testPrimarySortDenseNumericDocValuesExactMatch() throws IOException {
+ doTestPrimarySortDenseExactMatch(
+ SortField.Type.LONG,
+ i -> NumericDocValuesField.indexedField("dv", i),
+ i -> NumericDocValuesField.newSlowRangeQuery("dv", i, i));
+ }
+
+ public void doTestPrimarySortDenseExactMatch(
+ SortField.Type type, IntFunction<IndexableField> fields, IntFunction<Query> queries)
+ throws IOException {
+ boolean deletes = random().nextBoolean();
Directory dir = newDirectory();
int skipIntervalSize = random().nextInt(4, 4096);
IndexWriterConfig config = new IndexWriterConfig().setCodec(getCodec(skipIntervalSize));
- config.setIndexSort(
- new Sort(new SortField("dv", SortField.Type.STRING, random().nextBoolean())));
+ config.setIndexSort(new Sort(new SortField("dv", type, random().nextBoolean())));
int numBlocks = random().nextInt(4, 16);
int[] sizes = new int[numBlocks];
for (int i = 0; i < numBlocks; i++) {
@@ -773,24 +792,32 @@ public class TestDocValuesQueries extends LuceneTestCase {
}
RandomIndexWriter iw = new RandomIndexWriter(random(), dir, config);
for (int i = 0; i < numBlocks; i++) {
- BytesRef bytesRef = new BytesRef("" + i);
for (int j = 0; j < sizes[i]; j++) {
Document doc = new Document();
- doc.add(SortedDocValuesField.indexedField("dv", bytesRef));
+ IndexableField dv = fields.apply(i);
+ doc.add(dv);
iw.addDocument(doc);
+ if (deletes && random().nextInt(10) == 0) {
+ doc = new Document();
+ doc.add(dv);
+ doc.add(new StringField("id", "to_delete", Field.Store.NO));
+ iw.addDocument(doc);
+ }
}
}
iw.commit();
iw.forceMerge(1);
+ if (deletes) {
+ iw.deleteDocuments(new TermQuery(new Term("id", "to_delete")));
+ }
+
final IndexReader reader = iw.getReader();
final IndexSearcher searcher = newSearcher(reader, false);
iw.close();
for (int i = 0; i < numBlocks; i++) {
- final Query q =
- SortedDocValuesField.newSlowRangeQuery(
- "dv", new BytesRef("" + i), new BytesRef("" + i), true, true);
+ final Query q = queries.apply(i);
assertEquals(sizes[i], searcher.count(q));
assertEquals(sizes[i], searcher.search(q, 1000).totalHits.value());
// check cost