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++) {