The adapters that let a ValueSource stand in for a DoubleValuesSource or LongValuesSource skip the source's per-query setup, so sources that need that setup fail.
- A
ValueSource(the older function API, used byFunctionQuery) runs in two steps:createWeight(context, searcher)once per query, where it may compute per-query numbers and store them in acontextmap, thengetValues(context, segment)for each segment, which reads them back. - A
DoubleValuesSource(the newer API, used byFunctionScoreQuery,boostByValueand sorts) has no such step: the caller callsrewrite(searcher)once, thengetValuesper segment.asDoubleValuesSource()andasLongValuesSource()wrap aValueSourceas one; this page calls them the bridge. ttf(field, term)(TotalTermFreqValueSource) is one number per query: how often the term occurs in the whole index. It computes that number increateWeight.
FunctionQuery calls createWeight, which stores ttf = 2 in the context, and scores 2.0; the bridge skips that call, the context has no entry for ttf, and getValues returns null: NullPointerException. Right: the code and the contract. Select the still to open it at full size. Generated with AI, checked against the code.- Index one document whose
textis "test test", sottf(text, test), the term's total frequency in the index, is 2. Boost a match-all query by it:FunctionScoreQuery.boostByValue(new MatchAllDocsQuery(), ttf.asDoubleValuesSource()). - You expect 1 hit with score 2: match-all scores 1, times 2.
new FunctionQuery(ttf)on the same index scores 2.0. - You get
NullPointerException: ... "this.val$fv" is nullatValueSource.java:210, with assertions on and off. - Why: a
ValueSourceruns in two steps,createWeightonce per query (ttf counts the term there and stores the 2 in a context map), thengetValuesper segment, which reads it back. The bridge builds a fresh map for each segment and calls onlygetValues, so ttf finds nothing and returnsnull. - The same happens for sttf, functions that contain ttf or sttf, any custom
ValueSourcethat keeps state fromcreateWeight, explain, andasLongValuesSource()sorts. Fields, constants, the score andmaxdoc()work.
Code that passes a ValueSource keeping per-query state in createWeight through the bridge: in-tree ttf and sttf (@lucene.internal) or functions containing them, or a custom source that follows the createWeight javadoc. Default configuration, any index size, every release run since 6.4.0; the query fails loudly at its first matching document. Fields, constants, the score and maxdoc() work.
Mechanism, terms and numbers644 words
ValueSource#asDoubleValuesSource() and #asLongValuesSource() wrap a ValueSource so it can be used where Lucene expects a DoubleValuesSource or LongValuesSource, for example in FunctionScoreQuery.boostByValue, the documented replacement for the removed BoostedQuery, or in a sort. A ValueSource is used in two steps: createWeight(context, searcher) once per query, where it "can optionally store weight info in the context", then getValues(context, segment), which gets "the context that was previously passed to createWeight()". The wrappers build a fresh context for every segment and call getValues without ever calling createWeight, although their rewrite(searcher) receives the searcher that call needs. TotalTermFreqValueSource (ttf) and SumTotalTermFreqValueSource (sttf) compute their number in createWeight and return context.get(this) from getValues, so through the wrappers they return null and the search throws NullPointerException; a custom ValueSource written the way the javadoc describes finds its state missing too. One document is enough, with a default IndexWriterConfig and a plain IndexSearcher, assertions on or off. No version of the bridges has called createWeight since they appeared in 6.4.0 (2017), and every release run, 6.4.0 to 10.6.0, fails, as does main; the boostByValue form used here exists from 7.3.0, and older releases fail through a sort or new FunctionScoreQuery.
asDoubleValuesSource()returnsnew WrappedDoubleValuesSource(this, null)(ValueSource.java:183-186).FunctionScoreQuery.createWeightrewrites its source with the searcher (FunctionScoreQuery.java:121);boostByValuewraps the source inMultiplicativeBoostValuesSource(:84-86), whoserewriterewrites the bridge (:315-317). The bridge'srewriteonly returns a new wrapper that holds the searcher (ValueSource.java:243-246). NocreateWeight.- Per segment,
FunctionScoreWeight.scorerSuppliercallsgetValues(FunctionScoreQuery.java:239). The bridge buildsnewContext(searcher), a fresh map that holds only"searcher"(ValueSource.java:200), adds"scorer"(:203) and callsin.getValues(context, ctx)(:205), with noin.createWeight(context, searcher)before it. TotalTermFreqValueSource.getValuesisreturn (FunctionValues) context.get(this);(TotalTermFreqValueSource.java:59-62); only itscreateWeight(:65-81) sums the term's frequency over all segments and stores that entry. So the bridge'sfvisnull.SumTotalTermFreqValueSourceis the same (:51-54,:57-74).- Scoring the document goes through
FunctionScoreQuery.java:244-245and:299andDoubleValues.java:79tofv.doubleVal(scorer.docId)(ValueSource.java:210):NullPointerException. asLongValuesSource()has the same shape (:126-133; the NPE comes fromfv.exists(doc),:144), and so has the bridge'sexplain(:235-240).
The native consumers do it right: FunctionQuery.FunctionWeight (FunctionQuery.java:70-71), FunctionRangeQuery (FunctionRangeQuery.java:148-149) and ValueSourceSortField.rewrite (ValueSource.java:379-382) each build one context and call createWeight(context, searcher) before any getValues.
- Minimal repro, one document "test test": expected score 2; actual
NullPointerException("this.val$fv" is null). On the same indexnew FunctionQuery(ttf)scores 2.0 andboostByValueoverIndexReaderFunctions.totalTermFreqscores 2.0. - Two documents ("test test x", "test y"): ttf is 3 and sttf is 5 through
FunctionQuery; through the bridge,FunctionScoreQuerywith ttf,boostByValuewith sttf and a sort onttf.asLongValuesSource().getSortField(false)all throw. - A custom source that stores
numDocsincreateWeight: 2 throughFunctionQuery; throughFunctionScoreQueryand through anasLongValuesSource()sort itsgetValuessees a context itscreateWeightnever filled (0 calls). ProductFloatFunction(const 2, ttf): 6 throughFunctionQuery(on the devil's advocate's 3-document index, where ttf is 3),NullPointerExceptionthroughboostByValue;FunctionQuerymakes 1createWeightcall, the bridge 0.searcher.explainthrough the bridge throws too.
- Stateless sources (fields, constants, arithmetic on them), the score, and statistics that need only the searcher or the top-level reader work through the bridge:
maxdoc()anddocfreq()read the"searcher"entry thatnewContextadds, andnumdocs()reads the top-level reader. QueryValueSourceandScaleFloatFunctiondo not crash, because they compute their state lazily when it is missing; that they redo the work per segment through the bridge is code reading only.- A custom source that silently falls back to a default when its state is missing would give wrong scores instead of an exception; that variant was not tested.
Exposure and likelihood278 words
- Who is hit. Code that passes such a source through
asDoubleValuesSource()intoFunctionScoreQuery(scoring,boostByValue, explain), or throughasLongValuesSource()(orasDoubleValuesSource()) into a sort. In-tree:TotalTermFreqValueSource(ttf),SumTotalTermFreqValueSource(sttf) and any function containing them. Outside the tree: any customValueSourcethat stores state increateWeight, as its javadoc describes. - On the documented migration path.
MIGRATE.md:1377-1381saysBoostedQuery"may be replaced by calls toFunctionScoreQuery.boostByValue()".BoostedQuerycalledcreateWeighton itsValueSource;boostByValuereaches aValueSourceonly throughasDoubleValuesSource(). - Default vs opt-in. A default
new IndexWriterConfig()and a plainIndexSearcher; the trigger is the opt-in combination of a stateful source with the bridge. Under Lucene's test framework the codec is randomized (the gradle runs usedAsserting(Lucene104)); the codec plays no part, because ttf's term lookup lives in thecreateWeightthat is never called, so the bridge throws before ttf reads anything. - Scale. One document is enough. At any index size a search throws as soon as it evaluates the source for a matching document, with assertions on and off; a query that matches nothing in a segment never reaches the bridge there (
FunctionScoreQuery.java:236-237; explain of a non-match returns at:177-178). The failure is loud, only that query fails, and nothing is written to the index. - Downstream (read, not run). Apache Solr builds
ttf()andsttf()from these classes, and its boost parsers wrap the boost function withasDoubleValuesSource()throughboostByValue. That end-to-end path was not run. - How likely. Rare: ttf and sttf are per-term constants, seldom useful as a per-document boost or sort key, custom stateful sources are uncommon, and the bug has gone unreported since 6.4.0 (2017).
One JUnit class for lucene/queries/src/test/org/apache/lucene/queries/function/, one method, public classes only (TotalTermFreqValueSource is marked @lucene.internal): one document, default IndexWriterConfig, plain IndexSearcher. It fails with assertions on and off, and it is also the suggested regression test (run ./gradlew tidy before committing it: two lines are over 100 characters).
/*
* 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.queries.function;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queries.function.valuesource.TotalTermFreqValueSource;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.util.BytesRef;
public class TestValueSourceBridgeTotalTermFreq extends LuceneTestCase {
public void testBoostByTotalTermFreq() throws Exception {
try (Directory dir = new ByteBuffersDirectory()) {
try (IndexWriter w = new IndexWriter(dir, new IndexWriterConfig())) {
Document doc = new Document();
doc.add(new TextField("text", "test test", Field.Store.NO));
w.addDocument(doc);
}
try (DirectoryReader reader = DirectoryReader.open(dir)) {
ValueSource ttf = new TotalTermFreqValueSource("text", "test", "text", new BytesRef("test"));
Query q = FunctionScoreQuery.boostByValue(new MatchAllDocsQuery(), ttf.asDoubleValuesSource());
TopDocs hits;
try {
hits = new IndexSearcher(reader).search(q, 1);
} catch (NullPointerException e) {
throw new AssertionError("expected score 2 (ttf, as FunctionQuery gives), got " + e, e);
}
assertEquals(2f, hits.scoreDocs[0].score, 0f);
}
}
}
}
cp $REPRO/TestValueSourceBridgeTotalTermFreq.java $LUCENE/lucene/queries/src/test/org/apache/lucene/queries/function/
cd $LUCENE && ./gradlew :lucene:queries:test --tests org.apache.lucene.queries.function.TestValueSourceBridgeTotalTermFreq -Ptests.seed=DEADBEEF
# assertions off: add -Ptests.asserts=false (same failure)
- Expected
- 1 hit with score 2 (
MatchAllDocsQueryscores 1 and ttf is 2;new FunctionQuery(ttf)scores 2.0 on the same index). - Observed
NullPointerException: "this.val$fv" is null atValueSource.java:210, with assertions on and off.
More files, environment, full output250 words
The test indexes one document with TextField("text", "test test") in a ByteBuffersDirectory, wraps new TotalTermFreqValueSource("text", "test", "text", new BytesRef("test")) with asDoubleValuesSource(), and runs new IndexSearcher(reader).search(FunctionScoreQuery.boostByValue(new MatchAllDocsQuery(), ...), 1). Deterministic, single thread; the test framework's randomized codec plays no part (the bridge throws before ttf reads anything). TotalTermFreqValueSource is a public class but is marked @lucene.internal, so it is not a supported public API; the custom-source variant (a ValueSource that stores its state in createWeight, executed by the finder and the independent reproducer) uses only supported extension points and can replace or accompany this test in an issue. The NullPointerException is caught only to restate expected versus actual in the message; the cause is kept. It imports org.apache.lucene.tests.*, so it compiles on 9.1.0 and later; the study ran raw-Map ports of its own tests on 6.4.0 to 9.0.0. Through gradle, checkGoogleJavaFormat asks to wrap lines 45 and 46 (101 and 103 characters); ./gradlew tidy does it.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); also fails on the 10.6.0, 10.5.2, 9.12.3 and 9.1.0 release jars; 6.3.0 and older: no such API |
|---|---|
| jdk | OpenJDK 25.0.3 |
| os | Linux aarch64 |
TestValueSourceBridgeTotalTermFreq > testBoostByTotalTermFreq FAILED
java.lang.AssertionError: expected score 2 (ttf, as FunctionQuery gives), got java.lang.NullPointerException: Cannot invoke "org.apache.lucene.queries.function.FunctionValues.doubleVal(int)" because "this.val$fv" is null
at __randomizedtesting.SeedInfo.seed([DEADBEEF:B63616D72850F488]:0)
at org.apache.lucene.queries.function.TestValueSourceBridgeTotalTermFreq.testBoostByTotalTermFreq(TestValueSourceBridgeTotalTermFreq.java:51)
Caused by:
java.lang.NullPointerException: Cannot invoke "org.apache.lucene.queries.function.FunctionValues.doubleVal(int)" because "this.val$fv" is null
at org.apache.lucene.queries.function.ValueSource$WrappedDoubleValuesSource$1.doubleValue(ValueSource.java:210)
at org.apache.lucene.core@11.0.0-SNAPSHOT/org.apache.lucene.search.DoubleValues$1.doubleValue(DoubleValues.java:79)
at org.apache.lucene.queries.function.FunctionScoreQuery$MultiplicativeBoostValuesSource$1.doubleValue(FunctionScoreQuery.java:299)
at org.apache.lucene.queries.function.FunctionScoreQuery$FunctionScoreWeight$1.score(FunctionScoreQuery.java:245)
...
at org.apache.lucene.queries.function.TestValueSourceBridgeTotalTermFreq.testBoostByTotalTermFreq(TestValueSourceBridgeTotalTermFreq.java:49)
# the same with -Ptests.asserts=false; with the recommended fix the test passesgetValuesgets "the context that was previously passed to createWeight()" (ValueSource.java:44), and the bridge promises to "Expose this ValueSource", unrestricted.FunctionQuery,FunctionRangeQuery,getSortFieldand the removedBoostedQueryall callcreateWeightfirst;boostByValue, the documented replacement, takes aValueSourceonly through this bridge.- A custom source written as the
createWeightjavadoc says fails through public API alone.
The full argument271 words
- The contract.
getValues"Gets the values for this reader and the context that was previously passed to createWeight()" (ValueSource.java:43-47);createWeight: sources "can optionally store weight info in the context. The context object will be passed to getValues() where this info can be retrieved" (:65-69). The bridge is itself a caller ofin.getValuesand breaks that precondition; its javadoc promises to "Expose this ValueSource" (:111,:183) with no restriction. - The bridge has what it needs.
DoubleValuesSource's class javadoc tells clients to callrewrite(IndexSearcher)against the top-level searcher beforegetValues(DoubleValuesSource.java:34-36);FunctionScoreQuerydoes, and the bridge keeps the searcher. Core'sDoubleValuesSource.fromQuerydoes its per-query setup inrewrite, building itsWeightthere (:776-780). - Every other consumer calls it:
FunctionQuery.java:71,FunctionRangeQuery.java:149,ValueSource.java:381, and the removedBoostedQuery(boostVal.createWeight(fcontext,searcher);, 7.2.0BoostedQuery.java:74). LUCENE-8099 removedBoostedQueryandMIGRATE.md:1377-1381maps it toboostByValue(), which reaches aValueSourceonly throughasDoubleValuesSource(); none of the 35 comments on #9147 discusses losing the call. - Tests. Upstream tests call
createWeightbeforegetValues(TestValueSources.java:697-701,:935-940) and use the bridge only with sources that need nocreateWeight; no test asserts that the bridge skips it. - History. The bridges were born without the call (LUCENE-7610, 2017), and no later change says it is skipped on purpose: not LUCENE-7736 (which gave the bridge the searcher), LUCENE-8726, LUCENE-10252 or #15406. In LUCENE-5335 (#6399, 2013) Yonik Seeley gave state keyed by
thisas an intended use of the context: 'SumTotalTermFreqValueSource is one example of where "this" is very conveniently used as a key'.
The devil's-advocate check258 words
The strongest case for "works as designed": the DoubleValuesSource protocol has no createWeight step; ttf and sttf are @lucene.internal, and LUCENE-7736 (#8787) added IndexReaderFunctions.totalTermFreq and sumTotalTermFreq so that such statistics work in FunctionScoreQuery; the facet code the bridge replaced also skipped createWeight; QueryValueSource and ScaleFloatFunction cope with a skipped createWeight, so robust sources should too; the bridge's javadoc is one line; and open issue #16820 proposes deprecating ValueSource in Lucene 11.
Why it fails: the replacement argument covers ttf and sttf only, not a custom source that follows the createWeight javadoc and fails through public API alone; nothing limits the bridges to sources that ignore createWeight, while the getValues javadoc makes it a precondition; LUCENE-7736 put the searcher into the bridge's context, so sources that read it (such as maxdoc()) work through the bridge, and it neither added nor ruled out createWeight-dependent sources (reading it as a sign that the bridge is meant to serve them is an inference); every native consumer and the removed BoostedQuery call it; two lazy fallbacks are defensive code, not a relaxed contract; and #16820 is a proposal, while 6.4.0 to 10.6.0 are affected. No reviewer accepted the intended case: the cross-model reviews rated it weak, the devil's advocate is highly confident it is not intended, and the independent reproducer and the adjudication found the call promised by the javadoc. Verdict: not intended, a defect. Expect "use IndexReaderFunctions" and "ValueSource is being deprecated" as replies; the custom-source case and the BoostedQuery migration answer both.
Recommended B: call createWeight once in rewrite, copy the context per segment (validated)
Call in.createWeight(context, searcher) once in the bridges' rewrite(searcher), keep that context, and give each segment its own copy in getValues and explain (+20/-11 lines in ValueSource.java). The repro then scores 2 and the queries module's 512 upstream tests run with 0 failures (5 skipped); the bridge makes one createWeight call per query, like FunctionQuery.
All options and trade-offs4 options · 1,191 words
Option B was applied in a scratch worktree of 645d4fe14a20 and run with gradle (the worktree was removed after the runs). Option A, the study's sketch, was validated with a standalone runner by shadowing the patched class. Options C and D were not built.
In both wrappers, rewrite(searcher) builds newContext(searcher), calls in.createWeight(context, searcher) and keeps the filled map; getValues and explain start from new IdentityHashMap<>(weightContext) instead of newContext(searcher). An un-rewritten wrapper keeps a newContext(null) map, whose only entry is "searcher" -> null, the same as today's per-segment context. WrappedLongValuesSource's now unused searcher field is replaced; WrappedDoubleValuesSource keeps its package-private searcher field, which TestValueSources checks is null before rewrite.
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
index 8f31c9af02c..d7f3dcc547f 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
@@ -110,22 +110,23 @@ public abstract class ValueSource {
/** Expose this ValueSource as a LongValuesSource */
public LongValuesSource asLongValuesSource() {
- return new WrappedLongValuesSource(this, null);
+ return new WrappedLongValuesSource(this, newContext(null));
}
private static class WrappedLongValuesSource extends LongValuesSource {
private final ValueSource in;
- private final IndexSearcher searcher;
+ // filled once per query by in.createWeight() in rewrite(), then only copied
+ private final Map<Object, Object> weightContext;
- private WrappedLongValuesSource(ValueSource in, IndexSearcher searcher) {
+ private WrappedLongValuesSource(ValueSource in, Map<Object, Object> weightContext) {
this.in = in;
- this.searcher = searcher;
+ this.weightContext = weightContext;
}
@Override
public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
- Map<Object, Object> context = newContext(searcher);
+ Map<Object, Object> context = new IdentityHashMap<>(weightContext);
var scorer = new ScorableView(scores);
context.put("scorer", scorer);
@@ -176,28 +177,34 @@ public abstract class ValueSource {
@Override
public LongValuesSource rewrite(IndexSearcher searcher) throws IOException {
- return new WrappedLongValuesSource(in, searcher);
+ Map<Object, Object> context = newContext(searcher);
+ in.createWeight(context, searcher);
+ return new WrappedLongValuesSource(in, context);
}
}
/** Expose this ValueSource as a DoubleValuesSource */
public DoubleValuesSource asDoubleValuesSource() {
- return new WrappedDoubleValuesSource(this, null);
+ return new WrappedDoubleValuesSource(this, null, newContext(null));
}
static class WrappedDoubleValuesSource extends DoubleValuesSource {
final ValueSource in;
final IndexSearcher searcher;
+ // filled once per query by in.createWeight() in rewrite(), then only copied
+ private final Map<Object, Object> weightContext;
- private WrappedDoubleValuesSource(ValueSource in, IndexSearcher searcher) {
+ private WrappedDoubleValuesSource(
+ ValueSource in, IndexSearcher searcher, Map<Object, Object> weightContext) {
this.in = in;
this.searcher = searcher;
+ this.weightContext = weightContext;
}
@Override
public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
- Map<Object, Object> context = newContext(searcher);
+ Map<Object, Object> context = new IdentityHashMap<>(weightContext);
var scorer = new ScorableView(scores);
context.put("scorer", scorer);
@@ -234,7 +241,7 @@ public abstract class ValueSource {
@Override
public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation)
throws IOException {
- Map<Object, Object> context = newContext(searcher);
+ Map<Object, Object> context = new IdentityHashMap<>(weightContext);
context.put("scorer", new ScorableView(docId, scoreExplanation.getValue().floatValue()));
FunctionValues fv = in.getValues(context, ctx);
return fv.explain(docId);
@@ -242,7 +249,9 @@ public abstract class ValueSource {
@Override
public DoubleValuesSource rewrite(IndexSearcher searcher) throws IOException {
- return new WrappedDoubleValuesSource(in, searcher);
+ Map<Object, Object> context = newContext(searcher);
+ in.createWeight(context, searcher);
+ return new WrappedDoubleValuesSource(in, searcher, context);
}
@OverrideTrade-offs
- One
createWeightper query, likeFunctionQuery. Each segment gets its own copy of the map, sogetValuesmay add entries ("scorer", lazily built state) without writing to a shared map: the bridge itself writes the shared map only duringrewrite, which avoids the one-context-shared-by-concurrent-slices problem of the native consumers for entries added ingetValues. The copy is shallow: objects thatcreateWeightstores (for exampleQueryValueSource'sWeight) are shared across segments, as inFunctionQuery, and a source that keeps a reference to thecreateWeightmap and writes to it later still shares it. Whether the context must be read-only aftercreateWeight, copied deeper, or documented as holding shareable values is a maintainer decision. - A caller that calls
getValueswithoutrewritebehaves as today, so stateful sources still fail there. TheDoubleValuesSourcejavadoc requiresrewritefirst (DoubleValuesSource.java:34-36); consumers that skip it are separate bugs with fixes in those consumers. rewritenow does the per-query work (ttf and sttf loop over all segments once). Its javadoc says "Implementations should assume that this will only be called once" (DoubleValuesSource.java:94), andfromQueryalready builds itsWeightthere (:776-780).- That work, and any exception a custom
createWeightthrows, now happens whenFunctionScoreQuery.createWeightrewrites the source (FunctionScoreQuery.java:121), before it is known whether the inner query matches anything; today the bridge is reached only for segments where the inner query has a scorer (:236-239). - The rewritten wrapper's context holds the searcher; the rewritten wrapper held it before too (LUCENE-8726).
- By code reading only:
QueryValueSourcewould find itsWeightin the context instead of rebuilding it once per segment. No benchmark was run.
What the checks validated
- Gradle, stock
645d4fe14a20, seed DEADBEEF: the repro fails with assertions on and off. - Gradle with B:
:lucene:queries:testwith the repro and the study's three test classes added (the finder's 5 tests, the reproducer's 7, the devil's advocate's 6): 531 tests, 0 failures, 5 skipped, of which 512 are upstream tests.ecjLintMainpasses;checkGoogleJavaFormatflags only the added test files, notValueSource.java, so that gradle invocation (run with--continue) endsBUILD FAILEDwhile its test task isSUCCESS. The whole module ran with assertions on only. - With
-Ptests.asserts=false: the repro, the three study classes andTestValueSources,TestFunctionScoreQuery,TestFunctionScoreExplanations,TestFunctionQuerySortandTestKnnVectorSimilarityFunctions: 95 tests (76 upstream), 0 failures, 1 skipped. - The devil's advocate's call-counting probe: with B the bridge makes 1
createWeightcall for 2 segments, asFunctionQuerydoes (stock: 0; option A: 2).
In each of the three methods, take the rewrite-time searcher (or, if the wrapper was never rewritten, a new top-level IndexSearcher), build the context from it and call in.createWeight(context, s) before in.getValues.
IndexSearcher s = searcher != null ? searcher : new IndexSearcher(ReaderUtil.getTopLevelContext(ctx));
Map<Object, Object> context = newContext(s);
in.createWeight(context, s);Trade-offs
- Simplest, and also covers callers that skip
rewrite; that fallback builds a newIndexSearcherper segment and deserves its own review. - Repeats the per-query work per segment: 2
createWeightcalls for 2 segments (executed), and ttf and sttf loop over all segments insidecreateWeight, so O(segments^2) term lookups per query (code reading).
What the checks validated
- With the patched class shadowed: the repro passes with assertions on and off; the finder's 5, the reproducer's 7 and the devil's advocate's 6 tests pass; 76 upstream function tests (
TestValueSources,TestFunctionScoreQuery,TestFunctionScoreExplanations,TestKnnVectorSimilarityFunctions,TestFunctionQuerySort) pass stock and with the fix. Not run through gradle.
Compute the statistic in getValues when the context has no entry, as ScaleFloatFunction and QueryValueSource do.
Trade-offs
Fixes the in-tree trigger only: a custom source that follows the createWeight javadoc stays broken, and the statistic is recomputed per segment.
Say in the bridge javadoc that sources needing createWeight are not supported.
Trade-offs
Contradicts the getValues javadoc and helps no released user; a maintainer who prefers deprecating ValueSource (#16820) might still choose it.
The failure is a loud exception for that one query; a search server whose query language puts ttf() or sttf() inside a boost, as Apache Solr's boost parsers allow (read, not run), would answer such requests with an error.
A bounded-exhaustive check of the createWeight contract over the queries module: 22 composites times 8 consumer entry points (176 checks), with probe children that flag getValues on a context their createWeight never saw. Under both bridges every composite's children broke it; under the native consumers only RangeMapFloatFunction (LFV-886) and the three vector similarity functions did.
The method in full264 words
Method: semi-formal (bounded-exhaustive translation validation of a documented contract on the stock jar, then an executed public-API test).
- A hunt unit sweeping public APIs that no earlier hunt had named (core and queries) took the
ValueSource#createWeightjavadoc as a contract: every consumer must callcreateWeighton a context before callinggetValueswith it, and composites must propagate it. - An AI finder wrote a checker that, by reflection, enumerates the 46 concrete
ValueSourceclasses of lucene-queries, builds the 22 composites (including anonymous subclasses of the public abstract composite bases) over strict probe children, and drives each through 8 consumer entry points: 176 checks. A probe child fails whengetValuesreceives a context itscreateWeightnever saw. - Under
FunctionQuery,FunctionRangeQueryandValueSource#getSortField(4 entry points) only the children ofRangeMapFloatFunction's target and default sources (the sibling bug LFV-886) and of the three vector similarity functions (which do not overridecreateWeight) sawgetValueswithoutcreateWeight: 8 violations per entry point. UnderasDoubleValuesSource()andasLongValuesSource()(4 entry points) every composite's children did: 40 per entry point. With both fixes shadowed (this one and LFV-886's), every entry point, the bridges included, is left with the same 6 violations, the vector similarity functions' children, which this page does not analyse. - The AI tool then reduced the signal by hand to a JUnit with public API only (ttf, sttf, a sort and a contract-following custom source), and a later step reduced that to the one-method repro. An earlier hunt unit had noted the ttf
NullPointerExceptionas an unreported lead without filing it, because ttf is@lucene.internal.
No prior report found (2026-10-28, re-checked 2026-10-30) in GitHub issues, PRs and commits incl. the JIRA archive, the Lucene lists, downstream trackers and the web, nor in the file lists of all 410 open PRs. The closest items are other bridge defects (#9772, #11288); #16820 proposes deprecating ValueSource.
Searches5 searches
No report of this bug was found, and upstream main still skips the call. The closest items are other defects of the same bridge or design context, not reports of this one.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues, PRs, commits (apache/lucene incl. the migrated JIRA archive; downstream repositories) | 2026-10-28 | two automated passes of 89 and 81 queries (identifiers, symptoms, consumers) and 34 manual GitHub searches, including created and updated windows for the last 48 h; the finder's, verifiers' and the adjudication's own searches | no report; the closest are #9772 (LUCENE-8726, the bridge leaked the searcher), #11288 (LUCENE-10252, eager score), #9786 (LUCENE-8741), #15406, #15328 |
| Related items (read in full) | 2026-10-28 | bodies and comments of the closest threads | #9147 (LUCENE-8099: the BoostedQuery removal; 35 comments, none about losing createWeight), #8787 (LUCENE-7736, IndexReaderFunctions), #6399 (LUCENE-5335, context design), #8661 (LUCENE-7610, introduced the bridge); none mentions this bug |
| Open PRs | 2026-10-28 | changed-file lists of all 410 open apache/lucene PRs | only #14775 (Sort#inverse) touches ValueSource.java, not the bridge |
| Mailing lists, ASF JIRA (LUCENE, SOLR), web | 2026-10-28 | dev@, java-user@, issues@, commits@ and the Solr lists; 24 JQL queries; 17 web queries | only other bridge defects (SOLR-12407, SOLR-13126, SOLR-13303, SOLR-13315) and a newer downstream path through the bridge (SOLR-17951, read only) |
| Re-check for this page | 2026-10-30 | last commit on ValueSource.java on upstream main; GitHub searches for asDoubleValuesSource, asLongValuesSource, TotalTermFreqValueSource, createWeight ValueSource, FunctionScoreQuery NullPointerException; issues and PRs created since 2026-10-28 | unchanged (#15406, 2025-12-02, is still the last commit on the file; upstream main 716d6f216f52); nothing new |
Introduced with the bridges by LUCENE-7610 (713b65d1dcc, 2017-01-06), first released in 6.4.0; no version ever called createWeight. It fails on every release run from 6.4.0 to 10.6.0 and on main; 6.3.0 has no such API. Not fixed upstream.
Versions tested7 rows
LUCENE-7610 "Deprecate ValueSource methods in facets module" (Alan Woodward, commits 713b65d1dcc on master and a238610bab1 on branch_6x, author date 2017-01-06; a patch on the issue, now #8661) added asDoubleValuesSource() and asLongValuesSource() without a createWeight call; CHANGES lists it under 6.4.0. Later changes reshaped the bridge without adding the call: FunctionScoreQuery (LUCENE-7623, 6.5.0), the bridge's explain (LUCENE-7741, 7.0.0), the searcher in the context (LUCENE-7736, 7.2.0), LUCENE-8099, which deprecated BoostedQuery in 7.3.0, added FunctionScoreQuery.boostByValue as its replacement (42154387d4f, 2018-01-03, first released in 7.3.0) and removed BoostedQuery in 8.0.0 (c27099b4d15, 2017-12-14), a new wrapper per rewrite (LUCENE-8726, 8.1.0 and 7.7.2), newContext(searcher) and a lazy score (LUCENE-10252, 9.1.0), and round-trip conversion (#15406, 10.4.0). Not a regression: a scan of all 77 tagged releases from 6.4.0 to 10.5.2 in the history clone (it has no tags for 8.8.2 to 8.11.4 or 10.6.0; 8.11.4 and 10.6.0 were run) finds no createWeight call in the bridge. The omission is older than the bridge: the facet module's ValueSource-based APIs (LUCENE-5297/5298, 4.6.0, 2013) already called getValues without createWeight, and on 6.3.0 ttf through LongRangeFacetCounts fails the same way (executed); LUCENE-7610 moved that pattern into the bridges and routed the facet methods through them.
The rows ran either the minimal repro (9.1.0 and later), the finder's 5-test class, or raw-Map ports of the same public-API tests for 6.4.0 to 9.0.0; every bridge test failed and every control (FunctionQuery, ValueSource#getSortField) passed. OpenJDK 25.0.3, Linux aarch64; release jars SHA-1-checked.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 (unreleased 11.0) | affected | NullPointerException, -ea and -da, gradle and standalone |
10.6.0 (release jars, built from 4ae946e5a9b9) | affected | NullPointerException, -ea and -da (minimal repro) |
| 10.5.2, 9.12.3, 9.1.0 | affected | NullPointerException (minimal repro; the finder's class fails 4 of 5 on 10.5.2 and 9.12.3 with -ea and -da) |
10.4.0, 10.0.0, branch_10_6 4ae946e5a9b | affected | the finder's class fails 4 of 5 |
| 9.0.0, 8.11.4, 8.1.0, 7.7.3, 7.2.0, 6.6.6, 6.5.0 | affected | raw-Map ports fail 6 of 8 (sort and score) |
| 6.4.0 (first release with the bridge) | affected | raw-Map port fails 4 of 6, -ea and -da (sort only; FunctionScoreQuery arrived in 6.5.0) |
| 6.3.0 | n/a | does not compile: no asDoubleValuesSource() |
The finder, an independent reproducer, an intended-behaviour devil's advocate, an adjudication, the registration check, the repro minimizer and this page's gradle runs all reproduce it, and the fixes pass. Cross-model reviews (OpenAI GPT-5.6) could run nothing: some said real, the others "not real" for procedure alone; separate re-runs failed as claimed.
Checks and verdicts375 words
Every executing check ran on the stock jars and voted real. The only dissent is procedural: one review that could not run any code voted not real at low confidence, while rating the intended-behaviour case weak; a re-run of the repro for it failed as claimed.
| Check | Verdict | What it found |
|---|---|---|
| Finder (finder) | candidate | 176-check contract sweep: every composite's children see getValues without createWeight under both bridges; its JUnit fails 4 of 5 with -ea and -da; with the fix 5 of 5 pass. |
| Cross-model review, refute lens (OpenAI GPT-5.6) | not real, low (procedural) | It could not run any code, so it did not execute the repro; it rated the intended-behaviour argument weak. A re-run of the repro for it: 4 of 5 fail. |
| Independent reproducer | real | Own JUnit (3 documents, 2 segments) fails 5 of 7 on main -ea/-da, 10.5.2, 9.12.3 and 8.11.4; controls pass; own fix 7 of 7; 76 upstream function tests pass stock and fixed. |
| Cross-model review, intended lens (OpenAI GPT-5.6) | real, low (a read-only review: real, medium) | Reading only; root cause agreed, inputs in contract, intended argument weak. Separate re-runs fail 4 of 5 on main -ea and -da. |
| Intended-behaviour devil's advocate | real, high that it is not intended | Built and rejected the intended case; own 6-test probe fails 3 of 6 on main -ea and 10.5.2 -da (ProductFloatFunction, call counts, explain), 6 of 6 with the fix. |
| Adjudication | confirmed, high | Re-ran the independent reproduction's test with a new seed: 5 of 7 fail with -ea and -da. |
| Registration check | still fails; fix passes | The finder's repro fails 4 of 5 with -ea and -da and passes 5 of 5 with the fix; the independent reproduction's test fails 5 of 7 with -da. |
| Repro minimizer | verified | The one-method repro fails 1 of 1 on main 645d4fe14a20 with -ea and -da and passes with option A. |
| This page's runs | reproduced; option B validated | Gradle stock fails with assertions on and off; 10.6.0, 10.5.2, 9.12.3 and 9.1.0 fail; option B: 531 module tests with assertions on and 95 suite tests with assertions off, 0 failures; ecjLint clean, the format check flags only the four added test files. |
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 522 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,432 words
- Registry title
- ValueSource#asDoubleValuesSource and #asLongValuesSource never call createWeight, so a ValueSource that keeps per-query state in its context (ttf, sttf, or a user ValueSource) fails with NullPointerException through FunctionScoreQuery, its explain, or a LongValuesSource sort
- Area
- queries / function queries (ValueSource to DoubleValuesSource/LongValuesSource bridge: asDoubleValuesSource, asLongValuesSource)
- Priority
- Low, rank 625: The ValueSource bridges skip createWeight and throw a loud NPE only for stateful sources, the same missing-call shape as LFV-776, and since the bridges feed FunctionScoreQuery and sorts they rank above the narrower map() sibling LFV-886.
- Impact class
- crash
- Impact note
- Hits applications that pass a ValueSource which keeps per-query state in createWeight (in-tree only TotalTermFreqValueSource (ttf) and SumTotalTermFreqValueSource (sttf), both @lucene.internal, or a function that contains them, or any user ValueSource that follows the createWeight javadoc) through asDoubleValuesSource into FunctionScoreQuery (scoring, boostByValue, explain) or through asLongValuesSource into a sort: every such search then throws, with -ea and -da, the default IndexWriterConfig and a plain IndexSearcher, at any index size (2 documents suffice), in every release from 6.4.0 to 10.6.0 and on main; the failure is loud and only that query fails, while stateless sources and the searcher-only statistics (maxdoc(), numdocs(), docfreq()) work through the bridge [exec].
- Summary
- You expose a ValueSource as a DoubleValuesSource with asDoubleValuesSource() and score with it through FunctionScoreQuery (for example FunctionScoreQuery.boostByValue, the documented replacement for the removed BoostedQuery), or sort by asLongValuesSource().getSortField(), and expect the per-document values the same ValueSource gives through FunctionQuery or its own getSortField (3 for ttf(text, test) in the repro). Instead the bridge builds a fresh context in getValues and explain and calls in.getValues without ever calling in.createWeight, so ttf and sttf, which compute their statistic in createWeight and return context.get(this) from getValues, throw NullPointerException ('this.val$fv' is null at ValueSource.java:210 or :144), and a user ValueSource that stores per-query state in createWeight, as the ValueSource javadoc describes, finds its context entry missing. FunctionQuery, FunctionRangeQuery and ValueSource#getSortField all call createWeight first, and the bridge's rewrite(searcher) already holds the searcher such a call needs.
- Introduced
- 713b65d1dcc (master) / a238610bab1 (branch_6x backport), 'LUCENE-7610: Deprecate ValueSource methods in facets module' (issue LUCENE-7610, now #8661; pre-GitHub-PR era, patch on the issue) [read: git blame -w -C -C -M of main ValueSource.java:126-134 and :198-206 attributes the method skeletons to 713b65d1dcc (the in.getValues line to the LUCENE-7723 refactor 1a278ae89be, explain :235-241 to LUCENE-7741 fb24d451cc9); exec: 6.3.0 has no such API (compile error) and 6.4.0 fails]; not a regression: the bridge has never called createWeight, and none of the 77 tagged releases 6.4.0-10.5.2 adds the call (2017-01-06 (author date of 713b65d1dcc and a238610bab1; committed 2017-01-07); first released in 6.4.0)
- First affected version
- 6.4.0 [exec] (the first release with the bridge API, CHANGES 'Lucene 6.4.0 / API Changes / LUCENE-7610'; a 6.x port fails 4 of 6 there with -ea and -da, through the bridges' getSortField, because FunctionScoreQuery only arrived in 6.5.0). 6.3.0 is not affected because the API is absent [exec: compile error 'cannot find symbol: method asDoubleValuesSource()'].
- Affected versions
- Every release from 6.4.0 to 10.5.2 (77 tagged releases) [read: no createWeight call in the bridge code of any of them], branch_10_6 4ae946e5a9b (10.6.0, released 2026-10-29) and main a9779ef3c193; not fixed upstream (ValueSource.java unchanged since #15406, author date 2025-12-02; upstream main c5ab3e57b9ba) [exec: the finder's JUnit or era ports fail on 6.4.0 (-ea and -da), 6.5.0, 6.6.6, 7.2.0, 7.7.3, 8.1.0, 8.11.4, 9.0.0, 9.1.0, 9.12.3 (-ea and -da), 10.0.0, 10.4.0, 10.5.2 (-ea and -da), branch_10_6 and main (-ea and -da), with exactly the bridge tests failing and every control passing; the independent reproduction's own JUnit fails 5 of 7 on main (-ea and -da), 10.5.2, 9.12.3 and 8.11.4].
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that could only read the code (its low-confidence “not real” is procedural), an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- semi-formal: exhaustive check
- Upstream and prior reports
- none found (the prior-report record verdict new, searched 2026-10-28, read-only, upstream main c5ab3e57b9ba): two scripted prior-report searches (89 and 81 queries: GitHub issues, PRs and commits, downstream repositories (Elasticsearch, OpenSearch, Solr), the dev@, java-user@, issues@ and commits@ lists, JIRA and web; 156 and 1,161 distinct hits, the only 'high' hit #8674 by term overlap) plus 110 own queries (GitHub with created/updated windows for the last 48 h, ASF JIRA LUCENE and SOLR, list archives, web) and the file lists of all 410 open PRs (only #14775, Sort#inverse, touches ValueSource.java, not the bridge); the finder's, the independent reproduction's, the intended-behaviour check's and the adjudication's searches found nothing either. Refreshed in the registration check (read-only gh): no apache/lucene issue or PR updated since 2026-10-26 matches createWeight ValueSource, TotalTermFreqValueSource, FunctionScoreQuery ValueSource or WrappedDoubleValuesSource; the only PR updated since 2026-10-26 whose files match queries/function or ValueSource is #16911 (unrelated Gradle build classes); issues and PRs created since 2026-10-26 (to #16928) are unrelated; the last commit to ValueSource.java is still #15406 (2025-12-02). Closest items, none the same bug: #9772 (LUCENE-8726, the bridge leaked the IndexSearcher), #11288 (LUCENE-10252, eager score), #9147 (LUCENE-8099, BoostedQuery deprecation; BoostedQuery's Weight called createWeight, and nobody discussed losing that call), #9786 (LUCENE-8741), #15406, #15328, SOLR-12407, SOLR-13126, SOLR-13303, SOLR-13315, SOLR-17951 (a newer downstream bridge path, read only), #6399 (LUCENE-5335, context design). Context, not a report: #16820 (open, 2026-10-12) proposes deprecating ValueSource in Lucene 11. Gaps: searches cannot prove absence; private reports are not searchable.
- Lucene commit tested
- main
645d4fe14a20(2026-10-28;lucene/queriesis identical to the study'sa9779ef3c193, and upstream main716d6f216f52has the sameValueSource.javaon 2026-10-30): the repro with gradle in a scratch worktree, stock with assertions on and off, and with the recommended fix together with the wholelucene/queriestest suite (the worktree was removed after the runs); a standalone runner on main with-eaand-daand on the 10.6.0, 10.5.2, 9.12.3 and 9.1.0 release jars; the study's own tests and raw-Mapports on 13 releases from 6.4.0 to 10.5.2,branch_10_6and maina9779ef3c193. JDK OpenJDK 25.0.3, Linux aarch64.
- Finding record
lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java:43-47 (getValues gets 'the context that was previously passed to createWeight()'), :65-71 (createWeight: sources 'can optionally store weight info in the context. The context object will be passed to getValues()'), :111-114 and :183-186 ('Expose this ValueSource as a LongValuesSource / DoubleValuesSource'), :126-134 (WrappedLongValuesSource.getValues: newContext(searcher), in.getValues, no createWeight), :198-211 (WrappedDoubleValuesSource.getValues, doubleValue at :210), :235-241 (explain, same), :177-180 and :243-246 (rewrite(searcher) keeps the searcher), :378-383 (ValueSourceSortField.rewrite calls createWeight) @ a9779ef3c193lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java:71 and FunctionRangeQuery.java:149 (native consumers call createWeight before getValues); FunctionScoreQuery.java:121 (createWeight passes source.rewrite(searcher)), :182-190 (explain), :239 (scorerSupplier calls valueSource.getValues)lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/TotalTermFreqValueSource.java:33 (@lucene.internal), :59-62 (getValues returns context.get(this)), :65-81 (createWeight computes the statistic and stores it); SumTotalTermFreqValueSource.java:32 (@lucene.internal)
- The finding in 30 seconds
Lucene main 645d4fe14a20: lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java:43-47, :65-71, :74-78, :111-114, :126-147, :177-180, :183-186, :198-222, :235-246, :379-382Lucene main 645d4fe14a20: lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java:84-86, :121, :239, :244-245, :294-299, :315-317; FunctionQuery.java:70-71; FunctionRangeQuery.java:148-149Lucene main 645d4fe14a20: lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/TotalTermFreqValueSource.java:33, :59-81; valuesource/SumTotalTermFreqValueSource.java:32, :51-74; valuesource/QueryValueSource.java:82-86, :114-126Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/DoubleValues.java:71-79Lucene main 645d4fe14a20: lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MaxDocValueSource.java:49, DocFreqValueSource.java:60 (context.get("searcher")), NumDocsValueSource.java:44-46 (ReaderUtil.getTopLevelContext(readerContext).reader().numDocs())
- Who is affected
Lucene main 645d4fe14a20: lucene/MIGRATE.md:1377-1381Lucene main 645d4fe14a20: lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java:177-178, :236-239 (no scorer or no match: the bridge is not evaluated)
- Minimal repro
Lucene main 645d4fe14a20: lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/TotalTermFreqValueSource.java:33 (@lucene.internal)
- Why it looks unintended
Lucene main 645d4fe14a20: lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java:43-47, :65-69, :111, :183, :381Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java:34-36, :92-104, :776-780Lucene main 645d4fe14a20: lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java:71; FunctionRangeQuery.java:149Lucene main 645d4fe14a20: lucene/MIGRATE.md:1377-1381Lucene main 645d4fe14a20: lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java:697-701, :935-940GitHub apache/lucene#6399 (LUCENE-5335), comment by Yonik Seeley, 2013-11-07 (read-only)
- Suggested fix
Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java:34-36, :94, :776-780Lucene main 645d4fe14a20: lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java:829-834 (assertNull(source2.searcher))Lucene main 645d4fe14a20: lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java:121, :236-239 (rewrite once per query; getValues only for segments with a scorer)
- Prior reports
GitHub REST API, read-only, 2026-10-30: repos/apache/lucene/commits?path=lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java; search/issues for the queries listed
- History
Lucene main 645d4fe14a20: lucene/CHANGES.txt:8612, :8633-8634
- TestValueSourceBridgeTotalTermFreq.java (JUnit for Lucene's build):
repro/LFV-893/src/org/apache/lucene/queries/function/TestValueSourceBridgeTotalTermFreq.java(57 lines)
Made in parallel with this page: a summary card (PNG), every element checked against the code, the repro and an executed probe. Two differences from this page: the card's fix sketch is the study's per-segment fix (option A here), while this page recommends option B, which calls createWeight once at rewrite and copies the context per segment (the card's caveat line describes that trade-off); and the card was made before 10.6.0 was run, so it says "6.4.0 to 10.5.2" where this page says "6.4.0 to 10.6.0".
assets/LFV-893/bug.png(522 KB)
