Skip to content

Commit 6c1f666

Browse files
committed
Add test coverage for GCD transform edge cases
- Variable cardinality: exercises the per-doc fallback loop when denseFixedCardinality == -1 - Between-multiples bounds: exercises transformGcdBounds null early-exit when query range falls between GCD multiples - Randomized test: add variable cardinality and between-multiples variations
1 parent 03a21e1 commit 6c1f666

1 file changed

Lines changed: 98 additions & 1 deletion

File tree

lucene/core/src/test/org/apache/lucene/search/TestSkipBlockRangeIteratorIntoBitSet.java

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,97 @@ public void testSortedNumericGcdNegativeValuesRangeIntoBitSet() throws Exception
827827
doTestSortedNumericGcdRangeIntoBitSet(true, 3, 7, -1_000_000_000L);
828828
}
829829

830+
/**
831+
* Tests rangeIntoBitSet on GCD-encoded sorted numeric with variable cardinality. Exercises the
832+
* per-doc fallback loop (rawValues != null, denseFixedCardinality == -1) rather than the
833+
* fixed-cardinality scalar fast path.
834+
*/
835+
public void testSortedNumericGcdVariableCardinalityRangeIntoBitSet() throws Exception {
836+
int numDocs = 4096 * 2;
837+
long gcd = 100;
838+
long offset = 500_000L;
839+
try (Directory dir = newDirectory()) {
840+
IndexWriterConfig iwc = new IndexWriterConfig().setCodec(new Lucene104Codec());
841+
try (IndexWriter w = new IndexWriter(dir, iwc)) {
842+
for (int docID = 0; docID < numDocs; docID++) {
843+
Document doc = new Document();
844+
long base = ((docID * 13L) % 100) * gcd + offset;
845+
int cardinality = (docID % 3) + 1;
846+
for (int i = 0; i < cardinality; i++) {
847+
doc.add(SortedNumericDocValuesField.indexedField("sn", base + i * gcd));
848+
}
849+
w.addDocument(doc);
850+
}
851+
w.forceMerge(1);
852+
}
853+
854+
try (DirectoryReader reader = DirectoryReader.open(dir)) {
855+
LeafReaderContext ctx = reader.leaves().get(0);
856+
long queryMin = 20 * gcd + offset;
857+
long queryMax = 40 * gcd + offset;
858+
859+
FixedBitSet expected = new FixedBitSet(numDocs);
860+
var expectedValues = ctx.reader().getSortedNumericDocValues("sn");
861+
for (int docID = 0; docID < numDocs; docID++) {
862+
if (expectedValues.advanceExact(docID)) {
863+
for (int i = 0, count = expectedValues.docValueCount(); i < count; i++) {
864+
long value = expectedValues.nextValue();
865+
if (value >= queryMin) {
866+
if (value <= queryMax) {
867+
expected.set(docID);
868+
}
869+
break;
870+
}
871+
}
872+
}
873+
}
874+
875+
FixedBitSet actual = new FixedBitSet(numDocs);
876+
ctx.reader()
877+
.getSortedNumericDocValues("sn")
878+
.rangeIntoBitSet(0, numDocs, queryMin, queryMax, actual, 0);
879+
assertEquals("GCD sorted numeric variable cardinality", expected, actual);
880+
}
881+
}
882+
}
883+
884+
/**
885+
* Tests that rangeIntoBitSet correctly handles query bounds that fall between GCD multiples,
886+
* exercising the {@code transformGcdBounds} null early-exit (no raw value can match).
887+
*/
888+
public void testSortedNumericGcdBoundsBetweenMultiplesRangeIntoBitSet() throws Exception {
889+
int numDocs = 4096 * 2;
890+
long gcd = 1000;
891+
long offset = 500_000L;
892+
try (Directory dir = newDirectory()) {
893+
IndexWriterConfig iwc = new IndexWriterConfig().setCodec(new Lucene104Codec());
894+
try (IndexWriter w = new IndexWriter(dir, iwc)) {
895+
for (int docID = 0; docID < numDocs; docID++) {
896+
Document doc = new Document();
897+
long base = ((docID * 13L) % 100) * gcd + offset;
898+
for (int i = 0; i < 3; i++) {
899+
doc.add(SortedNumericDocValuesField.indexedField("sn", base + i * gcd));
900+
}
901+
w.addDocument(doc);
902+
}
903+
w.forceMerge(1);
904+
}
905+
906+
try (DirectoryReader reader = DirectoryReader.open(dir)) {
907+
LeafReaderContext ctx = reader.leaves().get(0);
908+
long queryMin = 20 * gcd + offset + 1;
909+
long queryMax = 21 * gcd + offset - 1;
910+
911+
FixedBitSet actual = new FixedBitSet(numDocs);
912+
ctx.reader()
913+
.getSortedNumericDocValues("sn")
914+
.rangeIntoBitSet(0, numDocs, queryMin, queryMax, actual, 0);
915+
assertEquals(
916+
"GCD bounds between multiples should match zero docs", 0, actual.cardinality());
917+
}
918+
}
919+
}
920+
830921
private void doTestSortedNumericGcdRangeIntoBitSet(
831922
boolean dense, int cardinality, long gcd, long offset) throws Exception {
832923
int numDocs = 4096 * 2;
@@ -891,6 +982,7 @@ public void testSortedNumericGcdRangeIntoBitSetRandomized() throws Exception {
891982
Random rng = random();
892983
for (int iter = 0; iter < 10; iter++) {
893984
boolean dense = rng.nextBoolean();
985+
boolean variableCardinality = rng.nextBoolean();
894986
int cardinality = rng.nextInt(1, 6);
895987
long gcd = rng.nextLong(2, 1000);
896988
long offset = rng.nextLong(0, 1_000_000_000L);
@@ -903,7 +995,8 @@ public void testSortedNumericGcdRangeIntoBitSetRandomized() throws Exception {
903995
Document doc = new Document();
904996
if (dense || docID % 3 != 0) {
905997
long base = rng.nextLong(0, 100) * gcd + offset;
906-
for (int i = 0; i < cardinality; i++) {
998+
int docCardinality = variableCardinality ? rng.nextInt(1, 6) : cardinality;
999+
for (int i = 0; i < docCardinality; i++) {
9071000
doc.add(SortedNumericDocValuesField.indexedField("sn", base + i * gcd));
9081001
}
9091002
}
@@ -916,6 +1009,10 @@ public void testSortedNumericGcdRangeIntoBitSetRandomized() throws Exception {
9161009
LeafReaderContext ctx = reader.leaves().get(0);
9171010
long queryMin = rng.nextLong(0, 50) * gcd + offset;
9181011
long queryMax = rng.nextLong(50, 100) * gcd + offset;
1012+
if (rng.nextBoolean()) {
1013+
queryMin += rng.nextLong(1, gcd);
1014+
queryMax -= rng.nextLong(1, gcd);
1015+
}
9191016

9201017
FixedBitSet expected = new FixedBitSet(numDocs);
9211018
var expectedValues = ctx.reader().getSortedNumericDocValues("sn");

0 commit comments

Comments
 (0)