You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ro...@apache.org on 2026/06/02 08:29:24 UTC

(lucene) branch branch_10x updated: Add count() to SortedSetDocValuesRangeQuery (#16109)

This is an automated email from the ASF dual-hosted git repository.

romseygeek 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 19285170d28 Add count() to SortedSetDocValuesRangeQuery (#16109)
19285170d28 is described below

commit 19285170d2889dcff219f8674205426ecb1cf252
Author: Prithvi S <pr...@gmail.com>
AuthorDate: Tue Jun 2 13:53:21 2026 +0530

    Add count() to SortedSetDocValuesRangeQuery (#16109)
    
    Ports the count() implementation of SortedNumericDocValuesRangeQuery
    to SortedSetDocValuesRangeQuery when a skipper is available.
---
 lucene/CHANGES.txt                                 |   2 +
 .../document/SortedSetDocValuesRangeQuery.java     |  25 +++
 .../apache/lucene/search/TestDocValuesQueries.java | 187 ++++++++++++++++++++-
 3 files changed, 213 insertions(+), 1 deletion(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index b80980dfb38..8b228af7433 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -203,6 +203,8 @@ Optimizations
 
 * GITHUB#16147: Optimize column batch parent field with bulk fill. (Tim Brooks)
 
+* GITHUB#16109: Add count() to SortedSetDocValuesRangeQuery. (Prithvi S)
+
 * GITHUB#15991: Load dense filters into bitset in MaxScoreBulkScorer. (Prithvi S)
 
 * GITHUB#16142: Optimize disjunction #docIDRunEnd to skip runs of excluded documents. (Costin Leau)
diff --git a/lucene/core/src/java/org/apache/lucene/document/SortedSetDocValuesRangeQuery.java b/lucene/core/src/java/org/apache/lucene/document/SortedSetDocValuesRangeQuery.java
index 4cc497aca15..f22fd33f68f 100644
--- a/lucene/core/src/java/org/apache/lucene/document/SortedSetDocValuesRangeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/SortedSetDocValuesRangeQuery.java
@@ -166,6 +166,31 @@ final class SortedSetDocValuesRangeQuery extends Query {
         };
       }
 
+      @Override
+      public int count(LeafReaderContext context) throws IOException {
+        if (context.reader().getFieldInfos().fieldInfo(field) == null) {
+          return 0;
+        }
+        SortedSetDocValues values = DocValues.getSortedSet(context.reader(), field);
+        final long minOrd = minOrd(values);
+        final long maxOrd = maxOrd(values);
+        if (minOrd > maxOrd) {
+          return 0;
+        }
+        final DocValuesSkipper skipper = context.reader().getDocValuesSkipper(field);
+        if (skipper != null) {
+          if (minOrd > skipper.maxValue() || maxOrd < skipper.minValue()) {
+            return 0;
+          }
+          if (skipper.docCount() == context.reader().maxDoc()
+              && skipper.minValue() >= minOrd
+              && skipper.maxValue() <= maxOrd) {
+            return context.reader().numDocs();
+          }
+        }
+        return -1;
+      }
+
       private ScorerSupplier getScorerSupplierFromDensePrimarySort(
           SortedDocValues singleton,
           SortedSetDocValues values,
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 4de335e0056..0fcaceb0937 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestDocValuesQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestDocValuesQueries.java
@@ -26,6 +26,8 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
+import java.util.Random;
 import java.util.Set;
 import java.util.function.IntFunction;
 import org.apache.lucene.codecs.Codec;
@@ -663,7 +665,190 @@ public class TestDocValuesQueries extends LuceneTestCase {
     assertEquals(expectedCount, w.count(searcher.reader.leaves().getFirst()));
   }
 
-  public void testSortedNumericDocValuesRangeQueryRewrites() throws Exception {
+  public void testSortedSetDocValuesRangeQueryCount() throws Exception {
+    try (Directory dir = newDirectory();
+        RandomIndexWriter iw = new RandomIndexWriter(random(), dir)) {
+      for (int i = 0; i < 100; i++) {
+        String val = String.format(Locale.ROOT, "%03d", i);
+        Document doc = new Document();
+        doc.add(SortedSetDocValuesField.indexedField("with_index", newBytesRef(val)));
+        doc.add(new SortedSetDocValuesField("without_index", newBytesRef(val)));
+        if (i != 55) {
+          doc.add(SortedSetDocValuesField.indexedField("sparse", newBytesRef(val)));
+        }
+        iw.addDocument(doc);
+      }
+      iw.commit();
+      iw.forceMerge(1);
+
+      try (IndexReader reader = iw.getReader()) {
+        IndexSearcher searcher = new IndexSearcher(reader);
+
+        // Nonexistent field
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "nonexistent", newBytesRef("000"), newBytesRef("099"), true, true),
+            0);
+
+        // Below all values: range is entirely before "000"
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("!"), newBytesRef("/"), true, true),
+            0);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "without_index", newBytesRef("!"), newBytesRef("/"), true, true),
+            0);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "sparse", newBytesRef("!"), newBytesRef("/"), true, true),
+            0);
+
+        // Match all values: "000" through "099"
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("000"), newBytesRef("099"), true, true),
+            100);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "without_index", newBytesRef("000"), newBytesRef("099"), true, true),
+            -1);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "sparse", newBytesRef("000"), newBytesRef("099"), true, true),
+            -1);
+
+        // Partial match: "050" through "060" = 11 values
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("050"), newBytesRef("060"), true, true),
+            -1);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "without_index", newBytesRef("050"), newBytesRef("060"), true, true),
+            -1);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "sparse", newBytesRef("050"), newBytesRef("060"), true, true),
+            -1);
+
+        // Partial match with bounds not in the index: "0501" falls between "050" and "051"
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("0501"), newBytesRef("100"), true, true),
+            -1);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "without_index", newBytesRef("0501"), newBytesRef("100"), true, true),
+            -1);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "sparse", newBytesRef("0501"), newBytesRef("100"), true, true),
+            -1);
+
+        // Non-indexed bounds that cover all values: "//" < "000" and "1000" > "099"
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("//"), newBytesRef("1000"), true, true),
+            100);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "without_index", newBytesRef("//"), newBytesRef("1000"), true, true),
+            -1);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "sparse", newBytesRef("//"), newBytesRef("1000"), true, true),
+            -1);
+
+        // Non-indexed bounds that match nothing: "0991" through "0999" are above all values
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("0991"), newBytesRef("0999"), true, true),
+            0);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "without_index", newBytesRef("0991"), newBytesRef("0999"), true, true),
+            0);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "sparse", newBytesRef("0991"), newBytesRef("0999"), true, true),
+            0);
+
+        // Above all values: range is entirely after "099"
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("100"), newBytesRef("199"), true, true),
+            0);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "without_index", newBytesRef("100"), newBytesRef("199"), true, true),
+            0);
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "sparse", newBytesRef("100"), newBytesRef("199"), true, true),
+            0);
+      }
+
+      // Delete docs with values "020" through "030" (11 docs)
+      iw.deleteDocuments(
+          SortedSetDocValuesField.newSlowRangeQuery(
+              "with_index", newBytesRef("020"), newBytesRef("030"), true, true));
+      iw.commit();
+
+      try (IndexReader reader = iw.getReader()) {
+        IndexSearcher searcher = new IndexSearcher(reader);
+
+        // Below all values still matches nothing
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("!"), newBytesRef("/"), true, true),
+            0);
+        // All values minus 11 deleted = 89
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("000"), newBytesRef("099"), true, true),
+            89);
+        // Partial match still can't be counted cheaply
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("050"), newBytesRef("060"), true, true),
+            -1);
+        // Above all values still matches nothing
+        assertCount(
+            searcher,
+            SortedSetDocValuesField.newSlowRangeQuery(
+                "with_index", newBytesRef("100"), newBytesRef("199"), true, true),
+            0);
+      }
+    }
+  }
+
+  public void testSortedNumericDocValuesRangeQueryRewrites(Random random) throws Exception {
     try (Directory dir = newDirectory();
         RandomIndexWriter iw = new RandomIndexWriter(random(), dir)) {
       for (int i = 0; i < 100; i++) {