- Give a points field 2 dimensions, only the first indexed (
setDimensions(2, 1, 4)), and add 4 documents (indexed, non-indexed): (30, 5), (10, 5), (20, 5), (40, 9). They flush as one leaf. - To compress the leaf, the writer sorts it by the dimension with fewer distinct values in the first differing byte, non-indexed ones included: here the non-indexed one, 2 against 3. So the indexed values are stored as 30, 10, 20, 40.
- You expect
newSetQuery(f, 10, 30)to find 2 documents. On the same fieldnewExactQuery(f, 10)finds 1 andnewRangeQuery(f, 10, 30)finds 3, both correct. - Instead it finds 1. The set query's pointer only moves forward: seeing 30 first, it moves from 10 to 30, so doc 1 (value 10) is missed.
newSetQuery(f, 10)finds 0. In a separate 200,000-document run (random values, 200 random set queries), 189,088 of 207,884 expected hits (91.0%) were missed. - With one index dimension there is no error with or without
-ea, andCheckIndexreports the index clean. Two segments answer 2; afterforceMerge(1)the same query finds 1. With 2 of 3 dimensions indexed, a multi-dimensional set query (core has no public factory for one) found 0 instead of 2; under-eaan assertion fires instead.
Narrow: it needs a custom field type, setDimensions(n > 1, 1, bytes) (one index dimension, more in total), a set query (newSetQuery, or JoinUtil with ScoreMode.None) and data that makes a leaf sort by a non-indexed dimension; the default codec and IndexWriterConfig suffice. No Lucene-provided field has that shape; a partial code search of Elasticsearch, OpenSearch and Solr found none.
Mechanism, terms and numbers716 words
A points field can store more dimensions than it indexes ("selective indexing", Lucene 7.6.0). For such a field with one index dimension, the BKD writer may order a leaf block by a non-indexed dimension, while PointInSetQuery assumes the indexed values arrive in increasing order. The set query then silently skips matches: in a 200,000-document run it missed 91% of the hits, with no error, and CheckIndex stays clean. Every release since 7.6.0 is affected; it needs a custom field type.
- A points field stores one or more fixed-width numbers per document and is indexed in a BKD tree: inner nodes split the values into ranges, and the values end up in leaf blocks of up to 512 points by default (1024 up to 8.5.x).
- Selective indexing (LUCENE-8496, 7.6.0):
FieldType.setDimensions(dimensionCount, indexDimensionCount, dimensionNumBytes). Only the firstindexDimensionCountdimensions build the tree; the others are only stored next to each value in the leaf blocks. In the code,numDataDimsmeans all dimensions. - A set query matches documents whose value is any of the given values:
IntPoint.newSetQuery("f", 10, 30)and the same method on LongPoint, FloatPoint, DoublePoint, BinaryPoint, InetAddressPoint and the sandbox HalfFloatPoint and BigIntegerPoint all build a one-dimensionalPointInSetQuery.
- The writer. To compress a leaf block,
BKDWritersorts the leaf by one dimension: at the first byte where the values differ, it picks the dimension with the fewest distinct values of that byte, among all dimensions, non-indexed ones included (it must be strictly fewer; a tie keeps dimension 0). So a leaf can be sorted by a non-indexed dimension, and then its indexed values are not in increasing order. This is designed: the feature commit widened these loops to all dimensions, and in review the author replied that leaves "require even the non indexed dimensions be sorted for compression". - The query. For any field with one index dimension,
PointInSetQueryusesMergePointVisitor. It walks the sorted query values and the indexed values together, like a merge, and its pointer only moves forward: when it sees an indexed value larger than the current query value, it moves past that query value for good. If a smaller indexed value comes later in the same leaf, that document is silently missed (mainPointInSetQuery.java:326-340).
Field f = (indexed value, non-indexed value), four documents in one leaf: doc 0 = (30, 5), doc 1 = (10, 5), doc 2 = (20, 5), doc 3 = (40, 9). Each number is 4 bytes and only the last byte differs. On flush the writer counts that byte from the leaf's second point: indexed {0A, 14, 28} = 3 distinct, non-indexed {05, 09} = 2, so the leaf is sorted by the non-indexed dimension. The visit order is 30, 10, 20, 40.
newSetQuery(f, 10, 30) with query values [10, 30]: it sees 30, moves its pointer from 10 to 30 and matches doc 0; it sees 10, but the pointer is at 30, so doc 1 is missed. Result 1 hit, expected 2. newSetQuery(f, 10) returns 0 (expected 1), while newExactQuery(f, 10) returns 1 and newRangeQuery(f, 10, 30) returns 3, both correct. Three documents are enough; two cannot trigger it.
No exception and no assertion fires, with or without -ea. CheckIndex says the index is clean by design: its order check runs only when the total dimension count is 1, and its comment says leaves with more dimensions "are sorted by the dimension with the lowest cardinality to improve block compression".
For a field like setDimensions(3, 2, 4) queried with a multi-dimensional PointInSetQuery (core has no public factory for it), SinglePointVisitor compares the whole stored value (12 bytes) with the 2-index-dimension query point (8 bytes), and these never compare equal. Leaf blocks whose index values all equal the query point are accepted whole without a compare, so it misses the matches in mixed leaf blocks: 2 x (1,2,i) + 2 x (3,4,i) gives 0 instead of 2, and 600 + 600 gives 512 instead of 600. Under -ea, the length assertion in SinglePointVisitor.visit (main :414) fires instead, but only when such a mixed leaf is visited.
Exposure and likelihood337 words
- Silently wrong results. Missing hits only, no false positives, and CheckIndex is clean. With one index dimension nothing fails under
-eaor-da; the multi-index-dimension case trips an assertion only under-ea. On the same fieldnewSetQuery(f, v)can return 0 whilenewExactQuery(f, v)returns 1. - At scale. 200,000 documents, indexed values 0-4999, a 4-valued non-indexed dimension, one segment, 200 random set queries of 1 to 50 values: all 200 wrong, 18,796 of 207,884 expected hits returned, 189,088 (91.0%) missed. A plain one-dimensional copy of the same values is exact. A fully default
IndexWriterConfigwithFSDirectoryand four commits also fails (100 of 100 queries wrong). - A merge can make correct results wrong. Written as two segments of two documents each, both leaves are sorted by the indexed value (in the first the non-indexed values are equal, so that dimension is not counted; in the second the counts tie 1 to 1) and the query answers 2. After
forceMerge(1)the merged leaf counts 4 versus 2, is sorted by the non-indexed value, and answers 1 instead of 2 (on 7.6.0 and on 8.1.0 and later; 7.7.x and 8.0.0 answer 2 here). - When it triggers. In a 50,000-document sweep: never with unique sequential indexed values; with unique random indexed values and a 2-valued or timestamp-like non-indexed dimension, 97-98% of hits missed; with 1000 distinct indexed values and a 2- or 4-valued or timestamp-like one, 90-92%; with 100 distinct and a 2-valued or timestamp-like one, about 2-2.5%. A 64-valued, random or index-equal non-indexed dimension missed nothing.
- Who is exposed: narrow. It needs a custom field type,
setDimensions(n > 1, 1, bytes), throughField(name, byte[], type),BinaryPoint's Expert constructor or a custom field class, plus a set query (the core and sandboxnewSetQueryfactories, orJoinUtilwithScoreMode.None). No Lucene-provided field has that shape, and a partial code search of Elasticsearch, OpenSearch and Solr found none. The default codec and a defaultIndexWriterConfigare enough otherwise. - Old. Every release since 7.6.0 (2018).
The test shows that IntPoint.newSetQuery("f", 10, 30) misses a match on a points field with 2 dimensions of which only the first is indexed (FieldType.setDimensions(2, 1, 4)), 3 docs (30,5) (10,9) (20,9) in one segment (TestPointInSetQuerySelectiveIndexing#testSetQueryOnOneIndexDimensionOfTwo).
package org.apache.lucene.search;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.tests.util.TestUtil;
import org.apache.lucene.util.NumericUtils;
public class TestPointInSetQuerySelectiveIndexing extends LuceneTestCase {
public void testSetQueryOnOneIndexDimensionOfTwo() throws Exception {
FieldType type = new FieldType();
type.setDimensions(2, 1, Integer.BYTES);
type.freeze();
try (Directory dir = new ByteBuffersDirectory()) {
IndexWriterConfig iwc = new IndexWriterConfig().setCodec(TestUtil.getDefaultCodec());
try (IndexWriter w = new IndexWriter(dir, iwc)) {
for (int[] point : new int[][] {{30, 5}, {10, 9}, {20, 9}}) {
byte[] packed = new byte[2 * Integer.BYTES];
NumericUtils.intToSortableBytes(point[0], packed, 0);
NumericUtils.intToSortableBytes(point[1], packed, Integer.BYTES);
Document doc = new Document();
doc.add(new Field("f", packed, type));
w.addDocument(doc);
}
}
try (DirectoryReader reader = DirectoryReader.open(dir)) {
// expected 2 hits (indexed values 30 and 10), actual 1: the doc with value 10 is missed
assertEquals(2, new IndexSearcher(reader).count(IntPoint.newSetQuery("f", 10, 30)));
}
}
}
}
# in a copy of this finding's repro folder (repro/LFV-006/, which holds src/)
# Lucene checkout at the tested commit, built with: ./gradlew --no-daemon assemble (JDK 25)
LUCENE=/path/to/lucene; V=12.0.0-SNAPSHOT
CP=$LUCENE/lucene/core/build/libs/lucene-core-$V.jar
CP=$CP:$LUCENE/lucene/codecs/build/libs/lucene-codecs-$V.jar
CP=$CP:$LUCENE/lucene/test-framework/build/libs/lucene-test-framework-$V.jar
CP=$CP:randomizedtesting-runner-2.9.2.jar:junit-4.13.2.jar:hamcrest-3.0.jar # test-framework dependencies (Maven Central)
CP=$CP:junit-platform-console-standalone-6.1.3.jar # JUnit Platform console launcher (Maven Central)
javac -d classes -cp "$CP" $(find src -name '*.java')
# run 1: assertions on, seed DEADBEEF
java -ea -esa --add-modules jdk.incubator.vector --enable-native-access=ALL-UNNAMED -Dtests.seed=DEADBEEF \
-cp "classes:$CP" org.junit.platform.console.ConsoleLauncher \
execute --select-class org.apache.lucene.search.TestPointInSetQuerySelectiveIndexing
- Expected vs actual
- Expected count 2 (indexed values 30 and 10); actual
expected:<2> but was:<1>: the leaf is sorted by the non-indexed dimension, so the forward-onlyMergePointVisitorskips 10. Fails the same way with -ea and -da, and no exception is thrown.
More files, environment, full output34 words
| lucene | Lucene main e1faeba08bde (2026-11-02) |
|---|---|
| jdk | 25.0.3+9-LTS |
# Lucene main 645d4fe14a20, assertions on (-ea), seed DEADBEEF: 1 test(s) run, 1 failed
=> java.lang.AssertionError: expected:<2> but was:<1>
org.junit.Assert.fail(Assert.java:89)
org.junit.Assert.failNotEquals(Assert.java:835)
org.junit.Assert.assertEquals(Assert.java:647)
org.junit.Assert.assertEquals(Assert.java:633)
org.apache.lucene.search.TestPointInSetQuerySelectiveIndexing.testSetQueryOnOneIndexDimensionOfTwo(TestPointInSetQuerySelectiveIndexing.java:36)PointInSetQueryaccepts these fields, yet the feature commit made the join's sibling query reject them (IAE): likely an oversight.setDimensions(int, int, int)is public and not experimental, tested at codec level but not with set queries;BinaryPoint's own set query also finds 1 of 2.- Precedent: #8920, the same visitor fed unsorted 1-D values, was labelled a bug and fixed.
The full argument181 words
PointInSetQueryaccepts these fields. It checksvalues.getNumIndexDimensions() != numDims(main:168), so a one-index-dimension field passes. The feature commit changed that check as part of a ~70-file split ofgetNumDimensions; nothing in the issue or the PR discusses set queries.- In the same commit the sibling query was given the opposite check.
PointInSetIncludingScoreQuery(join), which has its own forward-only merge visitor, throws anIllegalArgumentExceptionfor such fields. That points to an oversight, not a design. - "Wrong factory" does not hold.
BinaryPoint's public Expert constructorBinaryPoint(String, byte[], IndexableFieldType)accepts a(2,1,1)type, andBinaryPoint.newSetQueryon it returns 1 instead of 2 (and 0 instead of 1 for one value), whilenewExactQueryreturns 1. - The configuration is supported and tested.
FieldType.setDimensions(int, int, int)is public and not experimental; its validation allows1 <= indexDimensionCount <= dimensionCount, andTestBKDandBasePointsFormatTestCaserandomize the index dimensions in1..numDims. Set queries on it are not tested. - Precedent. #8920 (LUCENE-7869, "MemoryIndex should sort 1d points"), an unsorted 1-D source feeding
MergePointVisitor, was labelledtype:bugand fixed in 6.6.1, 6.7 and 7.0.
The devil's-advocate check163 words
The intended-behaviour checker tried to show it is not a bug. What holds: the writer's leaf order is designed and documented; the IntersectVisitor#visit javadoc's "In the 1D case, values are visited in increasing order" was written in 2016, before selective indexing, and Lucene's other code reads "1D" as total dimensions = 1; JoinUtil documents its "to" field as IntPoint, LongPoint, FloatPoint or DoublePoint, so that route is outside its contract. What is weak: PointInSetQuery is @lucene.experimental, which means the API may change, not that wrong results are allowed; and the configuration is tested at the codec level.
Verdict: not intended; a bug. The writer behaves as designed and the query was never adapted to it. The maintainers may still prefer to reject such queries with an IAE (as the sibling query does) rather than make them work; either way the silent wrong answer is a defect. Not a security issue: wrong results on a Lucene-written index, so a public issue is right.
Recommended Query side: use the merge visitor only for truly one-dimensional fields, and compare only the index-dimension prefix
+9/-5 lines in PointInSetQuery.java; both parts are needed. It repairs segments already on disk with no format change; plain one-dimensional fields are unchanged, and on these fields a set query walks the tree once per value (not measured). On main 174 points and BKD tests and 2,829 core search and document tests pass; the full core suite was not run.
All options and trade-offs4 options ยท 478 words
The checks validated the two-part query-side fix and re-ran the writer-side branch; the single-walk visitor and the reject option come from the brief and were not run.
Query side: use the merge visitor only for truly one-dimensional fields, and compare only the index-dimension prefix (validated by the checks) (recommended)
Branch 5b-bkd-selective-indexing-fix (f340c1790e; -fix-106 5622253100 for branch_10_6), patch fix-query-side-main.patch, +9/-5 lines in PointInSetQuery.java: (1) use MergePointVisitor only if numDims == 1 && values.getNumDimensions() == 1, otherwise the existing SinglePointVisitor path; (2) SinglePointVisitor.visit compares only the index-dimension prefix, Arrays.equals(packedValue, 0, pointBytes.length, pointBytes, 0, pointBytes.length), and its assertion becomes >=.
Trade-offs
- Both parts are needed: part 1 alone sends these fields to the unfixed
SinglePointVisitor(0 hits under-da, an AssertionError under-ea); part 2 alone fixes only the multi-index-dimension case. - On these fields set queries then walk the tree once per query value instead of once in total (not measured). Plain one-dimensional fields are unchanged.
- It repairs segments already on disk; no index format change. The full core suite was not run.
- Related, separate root cause:
MemoryIndex.getNumIndexDimensions()returns the total dimension count; fix it with or after this fix, because fixing it alone turns an IAE into a silent miss.
What the checks validated
- main: TestPointQueries,
org.apache.lucene.util.bkd.*, TestPointValues and TestLucene90PointsFormat, seed DEADBEEF,-eaand-da: 174 tests, 0 failures; branch_10_6: 175 tests, 0 failures; the 3 new tests pass with two seeds. - More on the main fix:
:lucene:join:test104 tests,:lucene:memory:test51, sandboxdocument.*23, coresearch.*+document.*2,829, all passing. - On an index written and force-merged by the 10.5.2 jar: stock code 77 of 100 queries wrong, with the fix 0 of 100. The demo is correct everywhere, the scale run included.
Branch 5b-bkd-selective-indexing-fix-writer (43d7991778), patch fix-writer-side-main.patch: force sortedDim = 0 when numIndexDims == 1 and dimension 0 is not constant in the leaf, close to what LUCENE-8562 did on the merge path in 7.7/8.0.
Trade-offs
The two one-index-dimension tests pass, but the 3-dimension / 2-index-dimension test still fails; it does not repair existing segments; it may cost some leaf compression (not measured); SimpleText has its own BKD writer and stays wrong under it.
Keep one tree walk: inner nodes are still split on the index dimension, so compare() can keep advancing past query values below the cell; inside a leaf, look each value up among the remaining query values (binary search) instead of consuming them.
Trade-offs
Keeps the single walk and fixes old segments, but still needs part 2 for multi-index-dimension fields. Not built.
Throw an IAE for set queries on fields with non-indexed dimensions, as PointInSetIncludingScoreQuery already does.
Trade-offs
Simpler, but it changes behaviour for queries that sometimes work today.
A Dafny proof (of a hand-written model) that the set query's merge visitor finds exactly the matches needed leaf values to arrive sorted. A bounded-exhaustive check of that order on real BKD code covered 664,200 tiny trees with 2 dimensions, 1 indexed: 19,940 failures, all "1-D values out of order"; other shapes had 0. The task never mentioned selective indexing.
The method in full284 words
Method: a Dafny proof whose precondition aimed a bounded-exhaustive check of the real BKD code (the study's "semi-formal" tier). Run 3, experiment "bkd-points-proof" (2026-10-22, main 7cecf79523).
- The task text named the area (points and BKD query evaluation, including
PointInSetQueryandBKDWriter) and the techniques (Dafny proofs, and bounded-exhaustive checking of the real BKDWriter/BKDReader with tiny configurations, "numDims 1-2"). It did not mention selective indexing or index versus non-indexed dimensions. - Dafny, on a hand-written model:
MergePointVisitor.dfyproves that the merge visitor plusintersectcollect exactly the matching values, given a preconditionWF: non-empty nodes, sorted values, values inside the node bounds (17 verified, 0 errors). A mutant that drops "sorted" fails with 4 errors. - Bounded-exhaustive check of the real code: a Java harness on the real
BKDWriter/BKDReader(-ea) checkedWFon every tree it built, reading "1D" asnumIndexDims == 1, the same readingPointInSetQueryuses. Family2d1i(2 dimensions / 1 index dimension, 1 byte per dimension, 3 values per dimension, up to 4 points, leaves of 1-3 points; flush, heap and offline paths; format versions 9 and 10; 5 docID layouts): 664,200 trees, 16,106,500 queries, 25.6 s, 19,940 failures, all "1-D values out of order". Every other family,3d2iincluded, had 0. The AI tool added the2d1iand3d2ifamilies itself. - What makes it a bug is the wrong hit counts: the proof is about a model, and the check on the real code is exhaustive only within tiny bounds. A separate AI verifier, given the candidate, wrote its own tests with different data and found the
SinglePointVisitorandMemoryIndexparts while reading the code; a follow-up foldedSinglePointVisitorinto this bug.
No prior report found in GitHub issues and PRs (39+ queries), the migrated JIRA issues or the dev@, java-user@ and issues@ lists, searched 2026-10-23. Related, not duplicates: the feature (#9542), the 1-D order contract (#8115) and the precedent #8920.
Searches5 searches
No report of this bug was found. The related items are the feature itself, the 1-D order contract, the MemoryIndex precedent and the merge-path leaf-sort changes.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues and PRs (apache/lucene) | 2026-10-23 | Checkers, 39+ queries, open and closed: MergePointVisitor, SinglePointVisitor, "selective indexing", numIndexDims, indexDimensionCount, sortedDim, "data dimensions" PointInSetQuery, newSetQuery wrong hits, writeField1Dim, and all ~46 plain "PointInSetQuery" hits | no report; the newest issue was #16900 (the hunt's own) |
| Migrated JIRA issues (in the GitHub search) | 2026-10-23 | the same searches | related, not duplicates: #9542 (LUCENE-8496, the feature), #8115 (LUCENE-7059, the 1-D order contract), #8920 (LUCENE-7869, the precedent), #9608 (LUCENE-8562) and #9719 (LUCENE-8673), the merge-path leaf sort |
| Mailing lists (lists.apache.org) | 2026-10-23 | dev@, java-user@ and issues@ | no report |
| Open PRs | 2026-10-23 | PRs touching PointInSetQuery.java and the BKD code | #14352 adds another MergePointVisitor-based visitor in the same branch; #16847, #13988 and #709 touch the file; #12528 writes sortedDim into leaves (same blind spot if the sorted dimension is non-indexed) |
| Downstream code (partial) | 2026-10-23 | GitHub code search of Elasticsearch, OpenSearch and Solr default branches for 3-argument setDimensions | no (n, 1) field; one (7, 4) shape field |
Introduced in 7.6.0 (2018) by LUCENE-8496 (#9542), selective indexing, which kept the leaf sort over all dimensions; the set query's forward-only cursor dates from 2016. All 65 releases from 7.6.0 to 10.5.2, branch_10_6 and main are affected; 7.7.x and 8.0.0 only in the flush case.
Versions tested7 rows
Introduced by LUCENE-8496 "Explore selective dimension indexing in BKDReader/Writer" (#9542, Nick Knize, closed 2018-10-18, fix versions 7.6 and 8.0). On master, 1118299c338 (2018-10-08) is the feature: it changed the PointInSetQuery check to index dimensions, kept the leaf sort over all dimensions and added the CheckIndex comment; 4cfa876d9d2 (2018-10-10) sent flush through the same N-dimensional leaf sort. Both first released in 8.0.0. On branch_7x a first backport was reverted and re-applied as 804afbfd47c (committed 2018-10-18), which shipped in 7.6.0. MergePointVisitor's forward-only cursor dates from PointInSetQuery's creation in 2016 and was correct before selective indexing existed.
| Version or commit | Verdict | Result |
|---|---|---|
main b83101cdd2, branch_10_6 d04ee73177 | affected | JUnit 3/3 fail on 4 seeds with -ea and -da; the demo and the 200k run fail the same way |
| 10.5.2 | affected | demo identical; 200k: 200/200 wrong, 189,088 missed |
| 8.1.0 to 10.5.1 (every 9.x and 10.x) | affected | flush 1/2, merge 1/2, multi-dimension 0/2 |
| 7.7.0-7.7.3, 8.0.0 | flush case only | flush 1/2; the merge example gives 2, because LUCENE-8562 sorted merge-built leaves by index dimensions there; LUCENE-8673 (8.1.0) went back to all dimensions |
| 7.6.0 | affected | flush and merge; 200k: 200/200 wrong, 187,265 missed (1024-point leaves then) |
| 7.5.0 | not affected | the feature is absent: setDimensions(int, int, int) does not exist |
fix f340c1790e / 5622253100 | not affected | every count correct, the scale run included |
Independent AI checkers, a lens each, and a claim re-check that re-ran the disputed claims checked brief v1; their corrections (which versions show the merge case, the multi-index-dimension symptom, a leaf-size claim, one command) are in brief v2. The study's verifier reproduced the bug with its own data.
Checks and verdicts288 words
Independent AI checkers each took a lens on brief v1, and a claim re-check re-ran the disputed claims; brief v2 applies the result.
| Check | Verdict | What it found |
|---|---|---|
| exec-current | confirmed | 8-run JUnit matrix per branch (3/3 fail each); independent repros with LongPoint and DoublePoint, FSDirectory.open and a separate JVM per step; showed both fix parts are needed and that the fix repairs old segments; checked JoinUtil, the sandbox factories and SimpleText. |
| exec-versions | correct in substance, one error | All 65 releases 7.6.0-10.5.2 affected; one factual error about which versions show the merge symptom (7.7.x and 8.0.0 get it right) and an overstatement in the AI summary. |
| code-history | holds, with errors | Citations, root cause, PR facts and fix results hold; the multi-index-dimension symptom is layout-dependent, not "matches nothing"; ran the join, memory, sandbox and search suites on the fix. |
| intended-behavior | not intended; a bug | The writer is designed, the query was never adapted; the BinaryPoint Expert constructor defeats the wrong-factory objection; a 50k-document sweep of when it triggers. |
| dups-policy | no existing report or fix | 39 GitHub queries plus the mailing lists; checked the open PRs and the AI-policy wording. |
| Claim re-check | status ready | Its own program on three jars (3 docs suffice, the BinaryPoint case, the multi-dimension layouts). Verdicts: one claim refuted (leaf sizes 1-4, really 1-3), the 8.0.0 row wrong as worded, an order of events not supported by the file times, and one command bug. |
| study verifier | new bug | BKD points proof verifier reproduced it with its own data and, reading the code next to it, found the SinglePointVisitor and MemoryIndex defects; follow-up 4 folded the SinglePointVisitor symptom into this bug and kept MemoryIndex separate. |
The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.
- PNG 196 KB
Finding record, Lucene and upstream sources, repro files and diagram notes494 words
- Registry title
- PointInSetQuery silently misses most matches on selectively indexed points (fewer index dimensions than data dimensions)
- Area
- util / BKD points
- Priority
- Medium, rank 79: Raised from the bottom of Medium: PointInSetQuery deterministically and silently misses about 91% of matches on selectively indexed points in every release since 7.6, but only custom field types have that shape (no Lucene, Elasticsearch, OpenSearch or Solr field), so it stays mid-Medium.
- Impact class
- silent wrong results
- Impact note
- Anyone using the opt-in selective-indexing FieldType (setDimensions with fewer index than data dimensions) with PointInSetQuery/newSetQuery; default codec and SimpleText; every release since 7.6.0; no exception or assertion.
- Summary
- You index a point field with more data dimensions than index dimensions (FieldType.setDimensions(d, 1, bytes), selective indexing) and run a PointInSetQuery (newSetQuery) on it. You expect every document whose indexed value is in the set to match. Instead BKDWriter sorts leaves by a data dimension and the query's forward-only visitor skips matches: 189,088 of 207,884 expected hits (91%) were missed in the repro, and a 3-dim/2-index-dim field returned 0 hits instead of 2.
- Introduced
- LUCENE-8496: master 1118299c338 and 4cfa876d9d2 (first released in 8.0.0); branch_7x re-applied backport 804afbfd47c, which shipped in 7.6.0 (2018-10-08 (1118299c338); 2018-10-10 (4cfa876d9d2); 2018-10-18 (804afbfd47c committed on branch_7x))
- First affected version
- 7.6.0
- Affected versions
- all 65 lucene-core releases 7.6.0-10.5.2 [exec], branch_10_6 and main [exec]; 7.7.0-8.0.0 only the flush case (merge-built leaves sorted correctly there)
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed in the earlier formal verification study by independent AI checks and a rerun on Lucene main; not yet triaged by a person.
- Found by
- semi-formal: Dafny, exhaustive check
- Upstream and prior reports
- none found (2026-10-23: 39 gh queries plus all 46 PointInSetQuery hits); precedent #8920 (LUCENE-7869); contract origin #8115 (LUCENE-7059)
- Lucene commit tested
- main
b83101cdd2and branch_10_6d04ee73177(2026-10-23; JUnit on 4 seeds, each with-eaand-da), all 65 lucene-core releases on Maven Central from 7.6.0 to 10.5.2 (sha1-checked), 7.5.0 (feature absent), and fix branchesf340c1790eand5622253100. The public-list rerun reproduced both repros on mainb83101cdd2; the hunt's mainea57360912edadds only #16883, which narrows LFV-004 alone, andSelIdxDemorun on a jar built fromea57360912ed(2026-10-25,-daand-ea) prints the same flush, merge and 3-dimension results.
- TestPointInSetQuerySelectiveIndexing.java (JUnit test):
repro/LFV-006/src/org/apache/lucene/search/TestPointInSetQuerySelectiveIndexing.java(40 lines)
A structural picture of one leaf block as four document cards, the bytes of each value, and the sorted query list with a forward-only pointer, plus the merge case. The PNG is the closing card.
Each item was checked against the brief and the code before it was applied.
assets/LFV-006/bug.png(196 KB)
