A point field can store more values than it indexes; MemoryIndex mixes up the two counts.
- A point field stores N numbers per value, its dimensions.
FieldType.setDimensions(N, M, bytes)builds the index on the first M only and keeps the rest alongside (selective indexing, since 7.6.0). Every built-in point type exceptShapeFieldindexes all N. PointValuesreports both counts:getNumDimensions()(stored) andgetNumIndexDimensions()(indexed). A point query must have exactly as many dimensions as the field indexes, or it throwsIllegalArgumentException.MemoryIndexis a one-document, in-memory index for testing a document against many queries, for example insideMonitor.
FieldInfo says 1 index dimension, its PointValues says 2, so the 1-dimension query is rejected; an IndexWriter index says 1 and matches. Select the still to open it at full size. Generated with AI, checked against the code.- Define a point type that stores two ints per value but indexes only the first:
type.setDimensions(2, 1, Integer.BYTES). Add the point (10, 5) to aMemoryIndex. - Search it with
IntPoint.newExactQuery("f", 10), a 1-dimension query on the indexed value. You expect a match: anIndexWriterindex with the same document returns count 1. - Instead
searchthrowsIllegalArgumentException: field="f" was indexed with numIndexDimensions=2 but this query has numDims=1, with assertions on and off. - Why: MemoryIndex's
PointValues.getNumIndexDimensions()returns the total count, 2. Its body is a copy ofgetNumDimensions()right above it; the field's ownFieldInfocorrectly says 1. Point queries must match the index count, so they reject the legal query. - Every exact, range and set query on the indexed dimension fails, in every release from 7.6.0 (2018) to 10.6.0 and on main. Through
Monitor(a one-document batch) the stored query is reported as an error and the document does not match. Built-in point fields and shape queries are not affected.
Only applications that define their own selectively indexed point type (setDimensions(n, m < n, bytes)) and query it in a MemoryIndex: directly, through MemoryIndex.fromDocument, or through Monitor (one-document batches). Then every exact, range and set query on its indexed dimensions fails, on any data. Built-in point types other than ShapeField index all dimensions, and shape queries never check the count.
Mechanism, terms and numbers488 words
FieldType.setDimensions(dimensionCount, indexDimensionCount, bytes) lets a point field store more dimensions than it indexes (selective indexing, LUCENE-8496, 7.6.0). PointValues reports the two counts separately, and PointRangeQuery, PointInSetQuery and the sandbox MultiRangeQuery require a query to have exactly getNumIndexDimensions() dimensions. MemoryIndex stores both counts correctly in its FieldInfo, but its PointValues returns the total count from getNumIndexDimensions() too (MemoryIndex.java:2194-2197, a copy of getNumDimensions() at :2189-2192). So a MemoryIndex holding the point (10, 5) of a 2-dimension, 1-index-dimension field rejects IntPoint.newExactQuery("f", 10) with IllegalArgumentException, while an IndexWriter index of the same document answers it. Monitor, which matches a single document through a MemoryIndex, reports the stored query as an error and no match. Every release from 7.6.0 to 10.6.0 and main is affected; built-in types are not, because they index all their dimensions (or, for shapes, are queried by shape queries that never read the count). The fix is one line, but on its own it turns some set queries, on documents that hold several values in the field, from an exception into a silent miss because of the separate PointInSetQuery bug LFV-006, so LFV-006's query-side fix should land first or together with it.
- Data dimensions (
getNumDimensions(),FieldInfo.getPointDimensionCount()): how many numbers each point value stores. - Index dimensions (
getNumIndexDimensions(),FieldInfo.getPointIndexDimensionCount()): how many of them, from the first, the index is built on, and how many dimensions a point query on the field must have. - Selectively indexed field: a field with fewer index dimensions than data dimensions, made with
FieldType.setDimensions(n, m < n, bytes)(FieldType.java:309-310, "Enables points indexing with selectable dimension indexing"). The two-argumentsetDimensions(n, bytes)that the other built-in point types use passesnfor both (:305-306). The only built-in selectively indexed type isShapeField(7 dimensions, 4 indexed,ShapeField.java:52).
MemoryIndexstores both counts in the field'sFieldInfo(createFieldInfo,MemoryIndex.java:775-776;setPointDimensions,:745-750; theFieldInforebuilt for doc values keeps both,:904-905).- Its
PointValuesreturns the data count from both methods:getNumDimensions()at:2189-2192andgetNumIndexDimensions()at:2194-2197bothreturn info.fieldInfo.getPointDimensionCount();. PointRangeQuery.rewritechecks every leaf (PointRangeQuery.java:544-549) and throws unlessgetNumIndexDimensions()equals the query's dimension count (:621-633).PointInSetQuery(PointInSetQuery.java:168-174) and the sandboxMultiRangeQuery(MultiRangeQuery.java:299-306) have the same check. A query built for the field's real index count is rejected.- Shape queries never read
getNumIndexDimensions(), soShapeFieldin aMemoryIndexis not affected by this check. (Its reported count is wrong too on stock code, 7 instead of 4, but aLatLonShapebox query gives the same hit and miss with and without the fix; executed.)
Field type setDimensions(2, 1, Integer.BYTES), one point (10, 5), query IntPoint.newExactQuery("f", 10). MemoryIndex: FieldInfo.getPointIndexDimensionCount()=1, PointValues.getNumDimensions()=2, PointValues.getNumIndexDimensions()=2, and the query throws. IndexWriter index of the same document: getNumDimensions()=2, getNumIndexDimensions()=1, count 1.
Exposure and likelihood290 words
- Who is hit. Code that puts a custom selectively indexed point field into a
MemoryIndex:MemoryIndex.addField,MemoryIndex.fromDocument(MemoryIndex.java:531-564), orMonitor, whose single-document batch is aMemoryIndex(DocumentBatch.java:101-112). Only one-document batches are hit: a batch of several documents is built with anIndexWriterover aByteBuffersDirectory(DocumentBatch.java:55-62,:66-92), which reports the right count. Typical uses are matching one incoming document against many stored queries and testing a document against a query before indexing it. - Opt-in. The other built-in point types index all their dimensions;
ShapeFieldis selectively indexed but its queries never read the count. Such a custom type is legal and works withIndexWriter. - What happens. Every
PointRangeQuery(exact or range),PointInSetQueryand sandboxMultiRangeQuerywith the field's real index dimension count throwsIllegalArgumentException, deterministically, with assertions on and off. One point is enough. - Through
Monitorit becomes a missing match.Monitorcatches the exception and reports it per query (Monitor.java:294-302). Executed: a storedIntPoint.newExactQuery("f", 10)and a document holding (10, 5) givematchCount=0 errors={q1=java.lang.IllegalArgumentException: field="f" was indexed with numIndexDimensions=2 but this query has numDims=1}, with assertions on and off; with the fixmatchCount=1. An application that does not readgetErrors()sees no match. - The reverse (executed): a query with as many dimensions as the field stores passes the check on a
MemoryIndexand is evaluated against the full stored value:IntPoint.newRangeQuery("f", new int[] {10, 5}, new int[] {10, 5})matches and{10, 6}does not, while anIndexWriterindex rejects both (numIndexDimensions=1 but this query has numDims=2). Not behaviour to rely on: with the fix, aMemoryIndexrejects them too. - How likely. Rare: no prior public report found since 7.6.0 (2018).
One JUnit test, one method, public API only: one point (10, 5) of a 2-dimension, 1-index-dimension field in a MemoryIndex, queried with IntPoint.newExactQuery("f", 10). Deterministic; fails with assertions on and off. It catches the exception only to name it in the failure.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.index.memory;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestMemoryIndexSelectiveIndexing extends LuceneTestCase {
public void testExactQueryOnOneOfTwoDimensions() {
FieldType type = new FieldType();
type.setDimensions(2, 1, Integer.BYTES);
MemoryIndex index = new MemoryIndex();
index.addField(new Field("f", IntPoint.pack(10, 5), type), null);
try {
assertTrue(index.search(IntPoint.newExactQuery("f", 10)) > 0);
} catch (IllegalArgumentException e) {
// expected: a match on the 1 indexed dimension; actual: getNumIndexDimensions() returns 2
throw new AssertionError("expected f:10 to match, but got " + e, e);
}
}
}
cp TestMemoryIndexSelectiveIndexing.java $LUCENE/lucene/memory/src/test/org/apache/lucene/index/memory/
cd $LUCENE && ./gradlew :lucene:memory:test --tests TestMemoryIndexSelectiveIndexing -Ptests.seed=DEADBEEF -Ptests.asserts=true
# assertions off: -Ptests.asserts=false instead (same result)
- Expected
IntPoint.newExactQuery("f", 10)matches the point (10, 5), as it does on anIndexWriterindex (count 1).- Observed
IllegalArgumentException: field="f" was indexed with numIndexDimensions=2 but this query has numDims=1fromPointRangeQuery.rewrite, with assertions on and off.
More files, environment, full output271 words
The test sits in org.apache.lucene.index.memory next to the module's tests but uses no package-private API. It uses no randomness, and no seed sensitivity was observed.
As the regression test: the same test, for example as a new method of TestMemoryIndex; the try/catch can go (assertTrue(index.search(IntPoint.newExactQuery("f", 10)) > 0);). It fails on stock main and passes with the fix, with assertions on and off (gradle); :lucene:memory:check -x test passes with the fix and the test in place.
Not shown by this test, executed elsewhere: set queries (on stock code they throw an IllegalArgumentException too, with PointInSetQuery's own wording was indexed with numIndexDims=2 but this query has numIndexDims=1), Monitor (an error entry and no match), and the release jars 7.6.0 to 10.6.0.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); the same failure on every release from 7.6.0 to 10.6.0; 7.5.0 has no selective indexing |
|---|---|
| jdk | OpenJDK 25.0.3 |
| os | Linux, aarch64 |
cp TestMemoryIndexSelectiveIndexing.java $LUCENE/lucene/memory/src/test/org/apache/lucene/index/memory/
cd $LUCENE && ./gradlew :lucene:memory:test --tests TestMemoryIndexSelectiveIndexing -Ptests.seed=DEADBEEF -Ptests.asserts=true
# assertions off: -Ptests.asserts=false instead (same result)
# gradle, main 645d4fe14a20, -Ptests.seed=DEADBEEF -Ptests.asserts=true (the same with =false); excerpt: the SeedInfo frame and the org.apache.lucene.core@11.0.0-SNAPSHOT/ module prefixes are left out
TestMemoryIndexSelectiveIndexing > testExactQueryOnOneOfTwoDimensions FAILED
java.lang.AssertionError: expected f:10 to match, but got java.lang.IllegalArgumentException: field="f" was indexed with numIndexDimensions=2 but this query has numDims=1
at org.apache.lucene.index.memory.TestMemoryIndexSelectiveIndexing.testExactQueryOnOneOfTwoDimensions(TestMemoryIndexSelectiveIndexing.java:35)
Caused by:
java.lang.IllegalArgumentException: field="f" was indexed with numIndexDimensions=2 but this query has numDims=1
at org.apache.lucene.search.PointRangeQuery.checkValidPointValues(PointRangeQuery.java:632)
at org.apache.lucene.search.PointRangeQuery.rewrite(PointRangeQuery.java:548)
at org.apache.lucene.search.IndexSearcher.rewrite(IndexSearcher.java:873)
at org.apache.lucene.search.IndexSearcher.rewrite(IndexSearcher.java:884)
at org.apache.lucene.search.IndexSearcher.search(IndexSearcher.java:753)
at org.apache.lucene.index.memory.MemoryIndex.search(MemoryIndex.java:1115)
at org.apache.lucene.index.memory.TestMemoryIndexSelectiveIndexing.testExactQueryOnOneOfTwoDimensions(TestMemoryIndexSelectiveIndexing.java:32)
# releases 7.6.0 to 10.6.0: the same message; the check sits in the weight's scorerSupplier up to 10.2.2 and in rewrite from 10.3.0PointValuesdocumentsgetNumIndexDimensions()as "how many dimensions are used for the index"; the field declared 1, and MemoryIndex's ownFieldInfoholds 1.- An
IndexWriterindex of the same document reports 1 index dimension, and the query matches. - The body is a copy of
getNumDimensions()from the commit that added selective indexing; no MemoryIndex test uses such a field.
The full argument175 words
- Contract.
PointValues.java:483-487:getNumDimensions()"Returns how many dimensions are represented in the values",getNumIndexDimensions()"Returns how many dimensions are used for the index".FieldType.setDimensions(int, int, int): "Enables points indexing with selectable dimension indexing" (FieldType.java:309-310);(2, 1, 4)passes its validation (:323-329). - MemoryIndex promises arbitrary queries.
createSearcher(): "a searcher that can be used to execute arbitrary Lucene queries" (MemoryIndex.java:1074);search(Query): "an arbitrary Lucene query to run against this index" (:1104). No javadoc, CHANGES or MIGRATE entry excludes selectively indexed points. - Parity.
IndexWriterreports 1 index dimension and count 1 for the same input; the independent reviewer's 3-data / 2-indexLongPointcontrol onIndexWritermatches too. - History. LUCENE-8496 (
1118299c338, 2018-10-08) split MemoryIndex'sgetNumDimensions()intogetNumDataDimensions()and a newgetNumIndexDimensions()with the same body, in the commit that also passedpointIndexDimensionCount()into MemoryIndex'sFieldInfoand made the point queries checkgetNumIndexDimensions(). Two later edits (LUCENE-9149 rename, 2020; #16387, 2026) kept the value. - No test.
lucene/memory/src/testonly callssetDimensions(1, 4)(TestMemoryIndex.java:673,:699).
The devil's-advocate check95 words
The independent reviewer's strongest case: PointValues, PointRangeQuery and PointInSetQuery are @lucene.experimental, MemoryIndex has a simplified single-document point tree, and its tests never use unequal counts, so selective indexing might have been meant only for BKD-backed readers.
Why it fails: no such limitation is documented; MemoryIndex promises arbitrary queries; the introducing commit deliberately stored the index count in MemoryIndex's FieldInfo; IndexWriter handles the same input; and the two methods have different documented meanings, so returning the same value from both cannot be the design. The reviewer rated the argument weak. Verdict: a defect.
Recommended A. Return the index dimension count, landed with LFV-006's PointInSetQuery fix
Return info.fieldInfo.getPointIndexDimensionCount() from getNumIndexDimensions() (one line in MemoryIndex.java), and land it after, or together with, the query-side fix of the separate PointInSetQuery bug LFV-006: with the one line alone, some set queries on documents that hold several values in the field stop throwing and silently miss instead.
@Override
public int getNumIndexDimensions() {
- return info.fieldInfo.getPointDimensionCount();
+ return info.fieldInfo.getPointIndexDimensionCount();
}All options and trade-offs3 options ยท 660 words
Both A and A with LFV-006's query-side fix were run through gradle in a scratch worktree of 645d4fe14a20. The MemoryIndex line is the whole change for this issue; the coupling is the order of landing.
A. Return the index dimension count, landed with LFV-006's PointInSetQuery fix (validated by the checks) (recommended)
One line in MemoryIndexPointValues.getNumIndexDimensions() (MemoryIndex.java:2196). Land it after, or together with, LFV-006's query-side fix in PointInSetQuery (merge visitor only when numDims == 1 && values.getNumDimensions() == 1; SinglePointVisitor compares only the index-dimension prefix).
@Override
public int getNumIndexDimensions() {
- return info.fieldInfo.getPointDimensionCount();
+ return info.fieldInfo.getPointIndexDimensionCount();
}Trade-offs
- The MemoryIndex change is safe for every built-in type: the other built-in point types index all their dimensions, so their index count equals their data count;
ShapeFieldis selectively indexed (7 dimensions, 4 indexed), but shape queries never read the count: aLatLonShapebox query in aMemoryIndexgives the same results with and without the fix (executed). - Why the pairing: with the one line alone, some set queries, on documents that hold several values in the field, go from an exception to a silent miss; single-valued documents, and values added in increasing order of the indexed dimension, still match (executed). 2 dimensions / 1 indexed, values (30, 5) then (10, 5),
IntPoint.newSetQuery("f", 10): no match, with assertions on and off. MemoryIndex sorts values only when the field has 1 dimension in total (MemoryIndex.java:1332-1338), andPointInSetQuery's 1-dimension merge visitor needs increasing indexed values (PointInSetQuery.java:203,:282). 3 dimensions / 2 indexed, values (1, 2, 100) and (3, 4, 300), a 2-dimension set query for (1, 2): no match with assertions off, and Lucene's ownassert packedValue.length == pointBytes.length(PointInSetQuery.java:414) with assertions on. Both are LFV-006's root cause, reached through MemoryIndex. - The tested exact and range queries are correct with the one line alone: the repro, 1-dimension exact and range queries on the 2/1 field (through
addFieldandfromDocument), a sandboxMultiRangeQuery, and the independent reviewer's 3/2 range query.PointRangeQuerycompares only the firstnumDimsdimensions of a value or cell (PointRangeQuery.java:134-147,:594-618). - Behaviour change: with the fix, a query with as many dimensions as the field stores (2 on the 2/1 field) throws on a
MemoryIndex, as it already does on anIndexWriterindex; stock code matched it against the full stored value (executed).
What the checks validated
- Gradle in a scratch worktree of
645d4fe14a20(2026-11-01): the repro fails on stock main and passes with the one line, each with-Ptests.asserts=trueand=false. - With the one line:
:lucene:memory:test4 classes with the repro, 52 tests, 0 failures;:lucene:monitor:test32 classes, 181 tests, 0 failures, 1 skipped;:lucene:memory:check -x test(google-java-format, ecj lint, forbidden APIs) passes with the test in place. - With the one line plus LFV-006's query-side fix: the repro and both set-query checks pass with assertions on and off;
:lucene:memory:test5 classes, 54 tests, 0 failures; coreTestPointQueries56 tests, 0 failures, 2 skipped. - Earlier, the same line: the repro with the fix shadowed in (assertions on and off); the independent reviewer's 4-test class 4/0 and upstream
TestMemoryIndex30/0; theMonitorcheckmatchCount=1.
The same diff, landed before LFV-006 is fixed.
Trade-offs
Fixes exact and range queries, but turns some set queries, on documents that hold several values in the field, from a loud exception into a silent miss (executed above). Not recommended unless LFV-006's fix lands in the same release.
Also sort point values when the field has one index dimension (MemoryIndex.java:1334 tests the total count today), keeping the per-dimension min/max loop.
Trade-offs
Would make 1-index-dimension set queries in a MemoryIndex correct without the core change, but not multi-index-dimension set queries (SinglePointVisitor), and nothing for on-disk indexes, so LFV-006 is still needed. Only worth it if LFV-006's fix is delayed. Not built.
Downstream services that match documents against stored queries in a MemoryIndex hit it only with a custom selectively indexed point type, as an exception or, through Monitor, as a per-query error and a missing match.
Code reading, not a formal method. While verifying the PointInSetQuery selective-indexing bug (LFV-006), a verifier noticed that MemoryIndex's getNumIndexDimensions() has the body of getNumDimensions(). A follow-up executed it with its own tests and a fix matrix; a one-test repro, an independent reviewer from another model family (OpenAI GPT-5.6) and a sweep over every release from 7.5.0 to 10.6.0 confirmed it.
The method in full131 words
Method: non-formal (a side observation from code reading). In the 2026-10-22 study, the verifier of the selective-indexing PointInSetQuery bug (LFV-006) read MemoryIndex because it sorts points only for 1-dimensional fields, and noted that getNumIndexDimensions() returns getPointDimensionCount(). A follow-up executed it with its own tests (testS2MemoryIndexExact, testS2MemoryIndexSet) and a fix matrix that showed that with the fix alone its set query, on a document holding (30, 5) then (10, 5), becomes a silent miss.
The hunt then cut it to the one-test repro, re-ran it on main 645d4fe14a20 and on 10.5.2, 10.6.0 and 9.12.3 with controls, had an independent reviewer from another model family (OpenAI GPT-5.6) try to refute it with its own 3-data / 2-index-dimension test (verdict real, high confidence), and ran the history sweep over 7.5.0-10.6.0.
No prior report found in GitHub issues, PRs and commits (all 409 open PRs' changed files included), the JIRA archive, the Lucene mailing lists, downstream projects or the web (103 queries, 2026-11-01). Not fixed upstream as of 2026-11-01.
Searches4 searches
Closest items, none a report of this bug: #9542 (LUCENE-8496, the introducing change; its comments and the review of apache/lucene-solr#451 never mention MemoryIndex); #8920 (LUCENE-7869 "MemoryIndex should sort 1d points", the 1-D-only sort behind the silent-miss caveat); #8149 (LUCENE-7093, points in MemoryIndex, before selective indexing); #10189 (LUCENE-9149, renamed the getter in the buggy line); #16387 (the last edit of the method, value unchanged); #9567 (LUCENE-8521, shape encoding on selective indexing); #8730 (LUCENE-7679, a different MemoryIndex FieldType bug). Eight open PRs touch lucene/memory/src/java; none changes a dimension line. The related LFV-006 is another finding of this hunt, not an upstream report. Limits: GitHub search returns at most 50 results per query; mailing-list search returns snippets only.
| Where | When | How | Found |
|---|---|---|---|
| apache/lucene issues, PRs and commits (incl. the migrated JIRA issues) | 2026-11-01 | a scripted prior-report search (40 GitHub and 3 commit queries) plus manual GitHub searches, e.g. MemoryIndex getNumIndexDimensions, MemoryIndex numIndexDimensions, MemoryIndex selectively indexed, MemoryIndex setDimensions, MemoryIndex PointRangeQuery, "was indexed with numIndexDimensions", getNumIndexDimensions | no report; only unrelated items |
| Open PRs | 2026-11-01 | changed files of all 409 open apache/lucene PRs | 8 touch lucene/memory/src/java; none changes a dimension line in MemoryIndex.java |
| JIRA archive, mailing lists, downstream, web | 2026-11-01 | 4 JQL queries; 20 queries over dev@, java-user@, issues@ and commits@; 8 downstream queries; 6 web queries | only notifications, unrelated threads and how-to pages |
| Earlier searches | 2026-10-22, 2026-11-01 | the study's follow-up and an earlier search (8 GitHub queries) | no report |
Introduced in 7.6.0 by LUCENE-8496 (1118299c338, 2018-10-08; branch_7x backport 804afbfd47c), the change that added selective indexing; the line kept its value through two later edits. Every release from 7.6.0 to 10.6.0 and main fails; 7.5.0 cannot express the trigger.
Versions tested5 rows
Introduced by 1118299c338 "LUCENE-8496: Selective indexing - modify BKDReader/BKDWriter to allow users to select a fewer number of dimensions to be used for creating the index ..." (Nicholas Knize, 2018-10-08; issue LUCENE-8496, now #9542; PR apache/lucene-solr#451, landed as direct commits). On branch_7x: 804afbfd47c (2018-10-18) with the same MemoryIndex change; an earlier backport 5b7739a081a (committed 2018-10-10) was reverted the same day by ac11c9e5b17, before any release. CHANGES.txt lists LUCENE-8496 under 7.6.0 New Features.
Lineage of the line: c1dfeb8ef85 (LUCENE-7093, 2016, correct then) -> 1118299c338 (bug) -> 206a70e7b79 (LUCENE-9149, 2020, rename, same value) -> 0c57b5ae240 (#16387, 2026, drops throws IOException, same value).
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | minimal repro fails with assertions on and off (gradle and standalone); passes with the fix |
| 10.6.0, 10.5.2, 9.12.3 | affected | minimal repro fails (release jars; 10.5.2 with assertions on and off) |
| 7.6.0 to 10.6.0 (66 releases) | first affected 7.6.0 | history probe fails on every release; the fully indexed control passes |
| 7.5.0 | no API | the trigger cannot be written: no setDimensions(int, int, int) (compile error) |
upstream main f815e679bf6c, branch_11x, branch_10_6 | read | the same line |
Confirmed with high confidence: a re-verification, an independent reviewer from another model family (OpenAI GPT-5.6) with its own test, and an 80-run history sweep all reproduced it; everyone agrees on the root cause and the one-line fix.
Checks and verdicts313 words
The impact is minor and agreed: a loud exception on a legal query (a missing match through Monitor), only for custom selectively indexed point types.
| Check | Verdict | What it found |
|---|---|---|
| study follow-up | real, low (exception) | Own tests and a fix matrix: the exact and set queries throw on stock code; the fix alone makes its set query (a document holding (30, 5) then (10, 5)) a silent miss; with LFV-006's query-side fix both match. |
| re-verification | real | Minimal repro fails on main with assertions on and off and on 10.5.2, 10.6.0 and 9.12.3; FieldInfo says 1 while PointValues says 2; the IndexWriter control matches; the fix passes. |
| cross-model review, refute lens (OpenAI GPT-5.6) | real, high | Own 3-data / 2-index LongPoint test fails 2 of 4 with assertions on and off (wrong metadata, then the exception); 2/2 MemoryIndex and 3/2 IndexWriter controls pass; the fix passes 4/4 and upstream TestMemoryIndex 30/0. Intended-behaviour argument: weak. |
| history sweep | 7.6.0 to 10.6.0 and main | 80 runs: every release from 7.6.0 fails; 7.5.0 cannot express the trigger; the fully indexed control passes everywhere it ran. |
| brief validation | fix validated | Gradle on a scratch worktree: the repro fails on stock main and passes with the fix; memory and monitor modules pass; the set-query caveat reproduced and resolved with LFV-006's fix; Monitor reports matchCount=0 and the exception on stock code, matchCount=1 with the fix. |
| fact checks of this doc (independent; cross-model, OpenAI GPT-5.6) | bug confirmed; wording fixed | Re-ran the repro on main and 10.6.0 (assertions on and off, standalone and gradle) and probed the set-query scope, the reverse case, the sandbox MultiRangeQuery and ShapeField. Both confirm the bug, the mechanism and the fix; the wording corrections they asked for (set-query scope, a stack-trace line, the shown diff, ShapeField, the scope of the history sweep) are applied. |
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 395 KB
Finding record, Lucene and upstream sources, repro files and diagram notes811 words
- Registry title
- MemoryIndex reports the total number of point dimensions as the number of index dimensions
- Area
- memory / MemoryIndex
- Priority
- Low, rank 1137: MemoryIndex reports total point dimensions as index dimensions, so point queries on custom selectively indexed fields throw IllegalArgumentException, loud with a trivial fix and a tiny population.
- Impact class
- crash
- Impact note
- Only applications that define their own selectively indexed point type (FieldType.setDimensions(n, m < n, bytes)) and query it in a MemoryIndex, directly, through MemoryIndex.fromDocument or through Monitor's one-document batches: every exact, range and set point query on its indexed dimensions throws IllegalArgumentException (through Monitor a per-query error and no match), with -ea and -da and on any data; built-in point types are not affected (ShapeField's shape queries never read the count); every release from 7.6.0 to 10.6.0 and main [exec].
- Summary
- You add a selectively indexed point field (for example 2 dimensions, 1 index dimension) to a MemoryIndex and run a point query on it. You expect the query to work as on a normal index. Instead getNumIndexDimensions() returns the total dimension count and the query throws IllegalArgumentException.
- Introduced
- 1118299c338 (LUCENE-8496 'Selective indexing - modify BKDReader/BKDWriter to allow users to select a fewer number of dimensions to be used for creating the index', Nicholas Knize, 2018-10-08; issue now apache/lucene#9542, PR apache/lucene-solr#451 landed as direct commits; branch_7x backport 804afbfd47c, 2018-10-18, first released in 7.6.0): it split MemoryIndex's getNumDimensions() into getNumDataDimensions() and a new getNumIndexDimensions() with the same body, in the commit that made the point queries check getNumIndexDimensions() [read: the line's git history, c1dfeb8ef85 (correct) -> 1118299c338 (bug) -> 206a70e7b79 and 0c57b5ae240 (same value); exec: 7.5.0 cannot express the trigger, 7.6.0 fails] (2018-10-08 (1118299c338 on master; branch_7x backport 804afbfd47c 2018-10-18))
- First affected version
- 7.6.0 [exec] (7.5.0 has no setDimensions(int, int, int), so the trigger cannot be written [exec: compile error])
- Affected versions
- every release from 7.6.0 to 10.6.0 (66 releases) and main [exec: a main() port of the minimal repro fails on each, the fully indexed control passing; the JUnit repro fails on main 645d4fe14a20 (-ea and -da) and on 9.1.0, 9.12.3, 10.5.2 and 10.6.0]; not fixed upstream: upstream main f815e679bf6c (2026-11-01), branch_11x and branch_10_6 have the same line [read]; unaffected: 7.5.0 and earlier (no selective indexing, the trigger cannot be written) [exec on 7.5.0]
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: a cross-model review that agreed and a prior-report search; not yet triaged by a person.
- Found by
- non-formal: code reading
- Upstream and prior reports
- none found (2026-11-01, read-only: 103 queries, among them a scripted prior-report search over GitHub issues and PRs, commits, downstream repositories, the dev@, java-user@, issues@ and commits@ lists, the JIRA archive and the web, manual GitHub searches and the changed files of all 409 open apache/lucene PRs; earlier read-only GitHub searches 2026-10-22); closest items, none a report of this bug: #9542 (LUCENE-8496, the introducing change), #8920 (LUCENE-7869, MemoryIndex sorts 1-D points), #10189 (LUCENE-9149, renamed the getter in the buggy line), #16387 (the last edit of the method, value unchanged); not fixed upstream
- Lucene commit tested
- main
645d4fe14a20: the minimal repro through gradle with-Ptests.asserts=trueand=false(stock and with the fix),:lucene:memory:test,:lucene:monitor:testand:lucene:memory:check -x testwith the fix, the set-query checks with the fix alone and with LFV-006's query-side fix, and coreTestPointQueries; aMonitorcheck run standalone. Earlier steps: main645d4fe14a20,ea57360912ed(independent reviewer) anda9779ef3c193(refresh), amain()port of the repro (same input and assertion) on the release jars 7.5.0 (cannot express the trigger) and every release from 7.6.0 to 10.6.0 (66 releases, all fail), and the JUnit test itself on 9.1.0, 9.12.3, 10.5.2 and 10.6.0. Fact checks re-ran the repro on main and 10.6.0 and probed the set-query scope, the reverse case, the sandboxMultiRangeQueryandShapeField.MemoryIndex.javais unchanged on upstream mainf815e679bf6c(2026-11-01, read).
- Finding record
a Lucene clone tag releases/lucene/10.5.2: lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:2121-2122
- The finding in 30 seconds
lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:745-750, :775-776, :904-905, :2189-2197 (at 645d4fe14a20)lucene/core/src/java/org/apache/lucene/document/FieldType.java:305-310; ShapeField.java:52 (at 645d4fe14a20)lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java:544-549, :621-633; PointInSetQuery.java:168-174; lucene/sandbox/src/java/org/apache/lucene/sandbox/search/MultiRangeQuery.java:299-306 (at 645d4fe14a20)
- Who is affected
lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:531-564; lucene/monitor/src/java/org/apache/lucene/monitor/DocumentBatch.java:101-112; Monitor.java:294-302 (at 645d4fe14a20)lucene/monitor/src/java/org/apache/lucene/monitor/DocumentBatch.java:55-62, :66-92 (multi-document batches use IndexWriter; at 645d4fe14a20)
- Why it looks unintended
lucene/core/src/java/org/apache/lucene/index/PointValues.java:102, :483-487; FieldType.java:309-329; PointRangeQuery.java:52; PointInSetQuery.java:51 (at 645d4fe14a20)lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:1074, :1104; lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java:673, :699 (at 645d4fe14a20)
- Suggested fix
lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:1332-1338, :2194-2197; lucene/core/src/java/org/apache/lucene/search/PointInSetQuery.java:203, :282-340, :413-428 (at 645d4fe14a20)lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java:134-147, :594-618 (at 645d4fe14a20)
- TestMemoryIndexSelectiveIndexing.java (JUnit, one test):
repro/LFV-080/src/org/apache/lucene/index/memory/TestMemoryIndexSelectiveIndexing.java(38 lines)
Made in parallel with this doc from the code at 645d4fe14a20 and the repro's input and output.
assets/LFV-080/bug.png(395 KB)
