Skip to content

Commit d1d5f36

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 c2c502f commit d1d5f36

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
@@ -897,6 +897,97 @@ public void testSortedNumericGcdNegativeValuesRangeIntoBitSet() throws Exception
897897
doTestSortedNumericGcdRangeIntoBitSet(true, 3, 7, -1_000_000_000L);
898898
}
899899

900+
/**
901+
* Tests rangeIntoBitSet on GCD-encoded sorted numeric with variable cardinality. Exercises the
902+
* per-doc fallback loop (rawValues != null, denseFixedCardinality == -1) rather than the
903+
* fixed-cardinality scalar fast path.
904+
*/
905+
public void testSortedNumericGcdVariableCardinalityRangeIntoBitSet() throws Exception {
906+
int numDocs = 4096 * 2;
907+
long gcd = 100;
908+
long offset = 500_000L;
909+
try (Directory dir = newDirectory()) {
910+
IndexWriterConfig iwc = new IndexWriterConfig().setCodec(new Lucene104Codec());
911+
try (IndexWriter w = new IndexWriter(dir, iwc)) {
912+
for (int docID = 0; docID < numDocs; docID++) {
913+
Document doc = new Document();
914+
long base = ((docID * 13L) % 100) * gcd + offset;
915+
int cardinality = (docID % 3) + 1;
916+
for (int i = 0; i < cardinality; i++) {
917+
doc.add(SortedNumericDocValuesField.indexedField("sn", base + i * gcd));
918+
}
919+
w.addDocument(doc);
920+
}
921+
w.forceMerge(1);
922+
}
923+
924+
try (DirectoryReader reader = DirectoryReader.open(dir)) {
925+
LeafReaderContext ctx = reader.leaves().get(0);
926+
long queryMin = 20 * gcd + offset;
927+
long queryMax = 40 * gcd + offset;
928+
929+
FixedBitSet expected = new FixedBitSet(numDocs);
930+
var expectedValues = ctx.reader().getSortedNumericDocValues("sn");
931+
for (int docID = 0; docID < numDocs; docID++) {
932+
if (expectedValues.advanceExact(docID)) {
933+
for (int i = 0, count = expectedValues.docValueCount(); i < count; i++) {
934+
long value = expectedValues.nextValue();
935+
if (value >= queryMin) {
936+
if (value <= queryMax) {
937+
expected.set(docID);
938+
}
939+
break;
940+
}
941+
}
942+
}
943+
}
944+
945+
FixedBitSet actual = new FixedBitSet(numDocs);
946+
ctx.reader()
947+
.getSortedNumericDocValues("sn")
948+
.rangeIntoBitSet(0, numDocs, queryMin, queryMax, actual, 0);
949+
assertEquals("GCD sorted numeric variable cardinality", expected, actual);
950+
}
951+
}
952+
}
953+
954+
/**
955+
* Tests that rangeIntoBitSet correctly handles query bounds that fall between GCD multiples,
956+
* exercising the {@code transformGcdBounds} null early-exit (no raw value can match).
957+
*/
958+
public void testSortedNumericGcdBoundsBetweenMultiplesRangeIntoBitSet() throws Exception {
959+
int numDocs = 4096 * 2;
960+
long gcd = 1000;
961+
long offset = 500_000L;
962+
try (Directory dir = newDirectory()) {
963+
IndexWriterConfig iwc = new IndexWriterConfig().setCodec(new Lucene104Codec());
964+
try (IndexWriter w = new IndexWriter(dir, iwc)) {
965+
for (int docID = 0; docID < numDocs; docID++) {
966+
Document doc = new Document();
967+
long base = ((docID * 13L) % 100) * gcd + offset;
968+
for (int i = 0; i < 3; i++) {
969+
doc.add(SortedNumericDocValuesField.indexedField("sn", base + i * gcd));
970+
}
971+
w.addDocument(doc);
972+
}
973+
w.forceMerge(1);
974+
}
975+
976+
try (DirectoryReader reader = DirectoryReader.open(dir)) {
977+
LeafReaderContext ctx = reader.leaves().get(0);
978+
long queryMin = 20 * gcd + offset + 1;
979+
long queryMax = 21 * gcd + offset - 1;
980+
981+
FixedBitSet actual = new FixedBitSet(numDocs);
982+
ctx.reader()
983+
.getSortedNumericDocValues("sn")
984+
.rangeIntoBitSet(0, numDocs, queryMin, queryMax, actual, 0);
985+
assertEquals(
986+
"GCD bounds between multiples should match zero docs", 0, actual.cardinality());
987+
}
988+
}
989+
}
990+
900991
private void doTestSortedNumericGcdRangeIntoBitSet(
901992
boolean dense, int cardinality, long gcd, long offset) throws Exception {
902993
int numDocs = 4096 * 2;
@@ -961,6 +1052,7 @@ public void testSortedNumericGcdRangeIntoBitSetRandomized() throws Exception {
9611052
Random rng = random();
9621053
for (int iter = 0; iter < 10; iter++) {
9631054
boolean dense = rng.nextBoolean();
1055+
boolean variableCardinality = rng.nextBoolean();
9641056
int cardinality = rng.nextInt(1, 6);
9651057
long gcd = rng.nextLong(2, 1000);
9661058
long offset = rng.nextLong(0, 1_000_000_000L);
@@ -973,7 +1065,8 @@ public void testSortedNumericGcdRangeIntoBitSetRandomized() throws Exception {
9731065
Document doc = new Document();
9741066
if (dense || docID % 3 != 0) {
9751067
long base = rng.nextLong(0, 100) * gcd + offset;
976-
for (int i = 0; i < cardinality; i++) {
1068+
int docCardinality = variableCardinality ? rng.nextInt(1, 6) : cardinality;
1069+
for (int i = 0; i < docCardinality; i++) {
9771070
doc.add(SortedNumericDocValuesField.indexedField("sn", base + i * gcd));
9781071
}
9791072
}
@@ -986,6 +1079,10 @@ public void testSortedNumericGcdRangeIntoBitSetRandomized() throws Exception {
9861079
LeafReaderContext ctx = reader.leaves().get(0);
9871080
long queryMin = rng.nextLong(0, 50) * gcd + offset;
9881081
long queryMax = rng.nextLong(50, 100) * gcd + offset;
1082+
if (rng.nextBoolean()) {
1083+
queryMin += rng.nextLong(1, gcd);
1084+
queryMax -= rng.nextLong(1, gcd);
1085+
}
9891086

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

0 commit comments

Comments
 (0)