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