From 5eac48dc47e6ca2b752d9d8266f27d3bbc0bd917 Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 10:45:08 +0530 Subject: [PATCH 01/15] perf: implement advanceShallow and getMaxScore logic for FunctionScoreQuery. --- .../apache/lucene/search/DoubleValues.java | 28 ++++++++ .../lucene/search/DoubleValuesSource.java | 21 ++++++ .../queries/function/FunctionScoreQuery.java | 14 +++- .../function/TestFunctionScoreQuery.java | 68 +++++++++++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java index 4ccf8eb9df04..56ad615696ab 100644 --- a/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java @@ -18,6 +18,7 @@ package org.apache.lucene.search; import java.io.IOException; +import org.apache.lucene.search.DocIdSetIterator; /** Per-segment, per-document double values, which can be calculated at search-time */ public abstract class DoubleValues { @@ -32,6 +33,22 @@ public abstract class DoubleValues { */ public abstract boolean advanceExact(int doc) throws IOException; + /** + * Advance this instance to the given target document id to compute block-level upper bounds. + * Default implementation returns {@link DocIdSetIterator#NO_MORE_DOCS}. + */ + public int advanceShallow(int target) throws IOException { + return DocIdSetIterator.NO_MORE_DOCS; + } + + /** + * Return the maximum score that documents between the current position and {@code upTo} can produce. + * Default implementation returns {@link Float#POSITIVE_INFINITY}. + */ + public float getMaxScore(int upTo) throws IOException { + return Float.POSITIVE_INFINITY; + } + /** Wrap a DoubleValues instance, returning a default if the wrapped instance has no value */ public static DoubleValues withDefault(DoubleValues in, double missingValue) { return new DoubleValues() { @@ -48,6 +65,16 @@ public boolean advanceExact(int doc) throws IOException { hasValue = in.advanceExact(doc); return true; } + + @Override + public int advanceShallow(int target) throws IOException { + return in.advanceShallow(target); + } + + @Override + public float getMaxScore(int upTo) throws IOException { + return in.getMaxScore(upTo); + } }; } @@ -68,3 +95,4 @@ public boolean advanceExact(int doc) throws IOException { } }; } + diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java index 6a3e638539ee..f1dc163b9ac7 100644 --- a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java @@ -22,6 +22,7 @@ import java.util.function.DoubleToLongFunction; import java.util.function.LongToDoubleFunction; import org.apache.lucene.index.DocValues; +import org.apache.lucene.index.DocValuesSkipper; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.search.comparators.DoubleComparator; @@ -482,6 +483,7 @@ public int hashCode() { @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { final NumericDocValues values = DocValues.getNumeric(ctx.reader(), field); + final DocValuesSkipper skipper = ctx.reader().getDocValuesSkipper(field); return new DoubleValues() { @Override public double doubleValue() throws IOException { @@ -492,6 +494,25 @@ public double doubleValue() throws IOException { public boolean advanceExact(int target) throws IOException { return values.advanceExact(target); } + + @Override + public int advanceShallow(int target) throws IOException { + if (skipper != null) { + skipper.advance(target); + return skipper.maxDocID(0); + } + return DocIdSetIterator.NO_MORE_DOCS; + } + + @Override + public float getMaxScore(int upTo) throws IOException { + if (skipper != null) { + if (skipper.minDocID(0) <= upTo) { + return (float) decoder.applyAsDouble(skipper.maxValue(0)); + } + } + return Float.POSITIVE_INFINITY; + } }; } diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java b/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java index d16749f73868..a2b972c039dd 100644 --- a/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java +++ b/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java @@ -251,9 +251,21 @@ public float score() throws IOException { return 0; } + @Override + public int advanceShallow(int target) throws IOException { + int innerShallow = in.advanceShallow(target); + int scoreShallow = scores.advanceShallow(target); + return Math.min(innerShallow, scoreShallow); + } + @Override public float getMaxScore(int upTo) throws IOException { - return Float.POSITIVE_INFINITY; + float innerMaxScore = in.getMaxScore(upTo); + float valueMaxScore = scores.getMaxScore(upTo); + if (Float.isInfinite(valueMaxScore)) { + return Float.POSITIVE_INFINITY; + } + return innerMaxScore * valueMaxScore * boost; } }; return new DefaultScorerSupplier(scorer); diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java index 7aed6cbe9c11..a644e847ccb6 100644 --- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java +++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java @@ -378,4 +378,72 @@ public void testQueryMatchesCount() throws Exception { } assertEquals(searchCount, weightCount); } + + public void testMaxScoreDelegationWithDocValuesSkipper() throws Exception { + try (Directory dir = newDirectory()) { + IndexWriterConfig conf = newIndexWriterConfig(); + try (IndexWriter indexWriter = new IndexWriter(dir, conf)) { + for (int i = 1; i <= 500; i++) { + Document doc = new Document(); + doc.add(new TextField(TEXT_FIELD, "test doc", Field.Store.NO)); + doc.add(new NumericDocValuesField("val", i * 10)); + indexWriter.addDocument(doc); + } + indexWriter.commit(); + } + + try (DirectoryReader reader = DirectoryReader.open(dir)) { + IndexSearcher searcher = new IndexSearcher(reader); + DoubleValuesSource valueSource = DoubleValuesSource.fromLongField("val"); + Query q = new FunctionScoreQuery(new TermQuery(new Term(TEXT_FIELD, "test")), valueSource); + + Weight weight = searcher.createWeight(q, ScoreMode.TOP_SCORES, 1f); + LeafReaderContext ctx = reader.leaves().get(0); + var scorerSupplier = weight.scorerSupplier(ctx); + assertNotNull(scorerSupplier); + var scorer = scorerSupplier.get(Long.MAX_VALUE); + assertNotNull(scorer); + + int maxDoc = ctx.reader().maxDoc(); + int shallowEnd = scorer.advanceShallow(0); + assertTrue(shallowEnd >= 0); + + float maxScore = scorer.getMaxScore(maxDoc); + if (ctx.reader().getDocValuesSkipper("val") != null) { + assertFalse(Float.isInfinite(maxScore)); + assertTrue(maxScore >= 5000f); + } else { + assertTrue(Float.isInfinite(maxScore)); + } + } + } + } + + public void testMaxScorePruningTopDocs() throws Exception { + try (Directory dir = newDirectory()) { + IndexWriterConfig conf = newIndexWriterConfig(); + try (IndexWriter indexWriter = new IndexWriter(dir, conf)) { + for (int i = 1; i <= 200; i++) { + Document doc = new Document(); + doc.add(new TextField(TEXT_FIELD, "prune search", Field.Store.NO)); + doc.add(new NumericDocValuesField("score_val", i)); + indexWriter.addDocument(doc); + } + indexWriter.commit(); + } + + try (DirectoryReader reader = DirectoryReader.open(dir)) { + IndexSearcher searcher = new IndexSearcher(reader); + Query baseQuery = new TermQuery(new Term(TEXT_FIELD, "prune")); + DoubleValuesSource valSource = DoubleValuesSource.fromLongField("score_val"); + Query scriptQuery = new FunctionScoreQuery(baseQuery, valSource); + + TopDocs topDocs = searcher.search(scriptQuery, 5); + assertEquals(5, topDocs.scoreDocs.length); + // Highest numeric values (200, 199, 198, 197, 196) must be returned + assertTrue(topDocs.scoreDocs[0].score >= 196f); + } + } + } } + From fb8e293e67cc31e2affa3dad7b48ff094815f98a Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 10:54:55 +0530 Subject: [PATCH 02/15] Implement DocValuesSkipper WAND pruning for FunctionScoreQuery and add benchmark --- .../benchmark-jmh/src/java/module-info.java | 1 + ...nctionScoreWANDMainVsFeatureBenchmark.java | 125 ++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java diff --git a/lucene/benchmark-jmh/src/java/module-info.java b/lucene/benchmark-jmh/src/java/module-info.java index 8090c7554739..bebcd829e56a 100644 --- a/lucene/benchmark-jmh/src/java/module-info.java +++ b/lucene/benchmark-jmh/src/java/module-info.java @@ -24,6 +24,7 @@ requires jdk.unsupported; requires org.apache.lucene.core; requires org.apache.lucene.expressions; + requires org.apache.lucene.queries; requires org.apache.lucene.join; requires org.apache.lucene.sandbox; requires commons.math3; diff --git a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java new file mode 100644 index 000000000000..3712dc242a29 --- /dev/null +++ b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java @@ -0,0 +1,125 @@ +/* + * 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.benchmark.jmh; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.NumericDocValuesField; +import org.apache.lucene.document.TextField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.Term; +import org.apache.lucene.queries.function.FunctionScoreQuery; +import org.apache.lucene.search.DoubleValuesSource; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.store.ByteBuffersDirectory; +import org.apache.lucene.store.Directory; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; + +/** + * JMH Micro-benchmark comparing actual FunctionScoreQuery search throughput + * on main branch vs feature/function-score-wand-skip-index branch over 1 Million Lucene index documents. + */ +@State(Scope.Benchmark) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@Warmup(iterations = 2, time = 2) +@Measurement(iterations = 3, time = 3) +@Fork( + value = 1, + warmups = 1, + jvmArgsAppend = {"-Xmx8g", "-Xms8g"}) +public class FunctionScoreWANDMainVsFeatureBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkState { + + @Param({"1000000"}) + public int numDocs; + + @Param({"100"}) + public int topK; + + public Directory dir; + public IndexReader reader; + public IndexSearcher searcher; + public Query functionScoreQuery; + + @Setup(Level.Trial) + public void setup() throws IOException { + dir = new ByteBuffersDirectory(); + IndexWriterConfig iwc = new IndexWriterConfig(); + iwc.setRAMBufferSizeMB(256); + + try (IndexWriter writer = new IndexWriter(dir, iwc)) { + Random random = new Random(42); + for (int i = 0; i < numDocs; i++) { + Document doc = new Document(); + // 10% of documents match "match_term" + if (i % 10 == 0) { + doc.add(new TextField("body", "match_term token", Field.Store.NO)); + } else { + doc.add(new TextField("body", "other_term token", Field.Store.NO)); + } + long scoreVal = random.nextInt(100000); + doc.add(new NumericDocValuesField("score_field", scoreVal)); + writer.addDocument(doc); + } + writer.commit(); + } + + reader = DirectoryReader.open(dir); + searcher = new IndexSearcher(reader); + + Query baseQuery = new TermQuery(new Term("body", "match_term")); + DoubleValuesSource valueSource = DoubleValuesSource.fromLongField("score_field"); + functionScoreQuery = new FunctionScoreQuery(baseQuery, valueSource); + } + + @TearDown(Level.Trial) + public void tearDown() throws IOException { + reader.close(); + dir.close(); + } + } + + @Benchmark + public TopDocs searchFunctionScoreQuery(BenchmarkState state) throws IOException { + return state.searcher.search(state.functionScoreQuery, state.topK); + } +} From 29ef08ceac54dce24f2c81db1d59c3720ca7f656 Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 10:58:51 +0530 Subject: [PATCH 03/15] Update benchmark to test indexSort --- .../jmh/FunctionScoreWANDMainVsFeatureBenchmark.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java index 3712dc242a29..5dcb3efa8624 100644 --- a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java +++ b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java @@ -72,6 +72,9 @@ public static class BenchmarkState { @Param({"1000000"}) public int numDocs; + @Param({"true", "false"}) + public boolean indexSort; + @Param({"100"}) public int topK; @@ -85,6 +88,9 @@ public void setup() throws IOException { dir = new ByteBuffersDirectory(); IndexWriterConfig iwc = new IndexWriterConfig(); iwc.setRAMBufferSizeMB(256); + if (indexSort) { + iwc.setIndexSort(new org.apache.lucene.search.Sort(new org.apache.lucene.search.SortField("score_field", org.apache.lucene.search.SortField.Type.LONG, true))); + } try (IndexWriter writer = new IndexWriter(dir, iwc)) { Random random = new Random(42); From 168517cfe8cba8e2c6f56e7e7bc029635967f1e8 Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 11:08:38 +0530 Subject: [PATCH 04/15] Update benchmark to use BooleanQuery disjunction base query --- ...nctionScoreWANDMainVsFeatureBenchmark.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java index 5dcb3efa8624..f9a99ce75c11 100644 --- a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java +++ b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java @@ -30,6 +30,8 @@ import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.Term; import org.apache.lucene.queries.function.FunctionScoreQuery; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; @@ -94,14 +96,11 @@ public void setup() throws IOException { try (IndexWriter writer = new IndexWriter(dir, iwc)) { Random random = new Random(42); + String[] terms = {"term_a", "term_b", "term_c", "term_d", "term_e"}; for (int i = 0; i < numDocs; i++) { Document doc = new Document(); - // 10% of documents match "match_term" - if (i % 10 == 0) { - doc.add(new TextField("body", "match_term token", Field.Store.NO)); - } else { - doc.add(new TextField("body", "other_term token", Field.Store.NO)); - } + String chosenTerm = terms[random.nextInt(terms.length)]; + doc.add(new TextField("body", chosenTerm, Field.Store.NO)); long scoreVal = random.nextInt(100000); doc.add(new NumericDocValuesField("score_field", scoreVal)); writer.addDocument(doc); @@ -112,7 +111,13 @@ public void setup() throws IOException { reader = DirectoryReader.open(dir); searcher = new IndexSearcher(reader); - Query baseQuery = new TermQuery(new Term("body", "match_term")); + BooleanQuery.Builder bq = new BooleanQuery.Builder(); + bq.add(new TermQuery(new Term("body", "term_a")), org.apache.lucene.search.BooleanClause.Occur.SHOULD); + bq.add(new TermQuery(new Term("body", "term_b")), org.apache.lucene.search.BooleanClause.Occur.SHOULD); + bq.add(new TermQuery(new Term("body", "term_c")), org.apache.lucene.search.BooleanClause.Occur.SHOULD); + bq.add(new TermQuery(new Term("body", "term_d")), org.apache.lucene.search.BooleanClause.Occur.SHOULD); + bq.add(new TermQuery(new Term("body", "term_e")), org.apache.lucene.search.BooleanClause.Occur.SHOULD); + Query baseQuery = bq.build(); DoubleValuesSource valueSource = DoubleValuesSource.fromLongField("score_field"); functionScoreQuery = new FunctionScoreQuery(baseQuery, valueSource); } From 1222a4eae470c6f4a49f3732e74769bb6d67db53 Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 11:12:58 +0530 Subject: [PATCH 05/15] Set DEFAULT_SKIP_INDEX_INTERVAL_SIZE to 128 --- .../apache/lucene/codecs/lucene90/Lucene90DocValuesFormat.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesFormat.java b/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesFormat.java index 22cd7581f1db..90b19828c08a 100644 --- a/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesFormat.java +++ b/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesFormat.java @@ -213,7 +213,7 @@ public DocValuesProducer fieldsProducer(SegmentReadState state) throws IOExcepti static final int TERMS_DICT_REVERSE_INDEX_MASK = TERMS_DICT_REVERSE_INDEX_SIZE - 1; // number of documents in an interval - private static final int DEFAULT_SKIP_INDEX_INTERVAL_SIZE = 4096; + public static final int DEFAULT_SKIP_INDEX_INTERVAL_SIZE = 128; // bytes on an interval: // * 1 byte : number of levels // * 16 bytes: min / max value, From 4a73cce4fcbdc0579d359fd8fb1e52dc46453d0f Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 12:24:01 +0530 Subject: [PATCH 06/15] Keep DEFAULT_SKIP_INDEX_INTERVAL_SIZE at 4096 in FunctionScoreQuery PR --- .../apache/lucene/codecs/lucene90/Lucene90DocValuesFormat.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesFormat.java b/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesFormat.java index 90b19828c08a..22cd7581f1db 100644 --- a/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesFormat.java +++ b/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesFormat.java @@ -213,7 +213,7 @@ public DocValuesProducer fieldsProducer(SegmentReadState state) throws IOExcepti static final int TERMS_DICT_REVERSE_INDEX_MASK = TERMS_DICT_REVERSE_INDEX_SIZE - 1; // number of documents in an interval - public static final int DEFAULT_SKIP_INDEX_INTERVAL_SIZE = 128; + private static final int DEFAULT_SKIP_INDEX_INTERVAL_SIZE = 4096; // bytes on an interval: // * 1 byte : number of levels // * 16 bytes: min / max value, From ad7c840d7c2e05e9e76f6811d20355797d9efade Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 12:24:23 +0530 Subject: [PATCH 07/15] Commit benchmark class --- .../benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java index f9a99ce75c11..77fe63755fea 100644 --- a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java +++ b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java @@ -101,7 +101,8 @@ public void setup() throws IOException { Document doc = new Document(); String chosenTerm = terms[random.nextInt(terms.length)]; doc.add(new TextField("body", chosenTerm, Field.Store.NO)); - long scoreVal = random.nextInt(100000); + // Real-world Power-law / Zipfian score distribution (98% low scores, 2% high scores) + long scoreVal = (random.nextFloat() < 0.02f) ? (50000 + random.nextInt(50000)) : random.nextInt(100); doc.add(new NumericDocValuesField("score_field", scoreVal)); writer.addDocument(doc); } From 21042fa03241ecbd2cdc62fd90b24d20e76c93d6 Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 12:59:25 +0530 Subject: [PATCH 08/15] Add support for monotonically decreasing functions in DoubleValuesSource --- .../lucene/search/DoubleValuesSource.java | 24 ++++++++--- .../function/TestFunctionScoreQuery.java | 41 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java index f1dc163b9ac7..5323cf5d8c68 100644 --- a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java @@ -285,7 +285,18 @@ public static DoubleValues similarityToQueryVector( * @param decoder a function to convert the long-valued doc values to doubles */ public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder) { - return new FieldValuesSource(field, decoder); + return fromField(field, decoder, true); + } + + /** + * Creates a DoubleValuesSource that wraps a generic NumericDocValues field + * + * @param field the field to wrap, must have NumericDocValues + * @param decoder a function to convert the long-valued doc values to doubles + * @param increasing true if the decoder function is monotonically increasing; false if monotonically decreasing + */ + public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder, boolean increasing) { + return new FieldValuesSource(field, decoder, increasing); } /** Creates a DoubleValuesSource that wraps a double-valued field */ @@ -456,10 +467,12 @@ private static class FieldValuesSource extends DoubleValuesSource { final String field; final LongToDoubleFunction decoder; + final boolean increasing; - private FieldValuesSource(String field, LongToDoubleFunction decoder) { + private FieldValuesSource(String field, LongToDoubleFunction decoder, boolean increasing) { this.field = field; this.decoder = decoder; + this.increasing = increasing; } @Override @@ -467,7 +480,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FieldValuesSource that = (FieldValuesSource) o; - return Objects.equals(field, that.field) && Objects.equals(decoder, that.decoder); + return Objects.equals(field, that.field) && Objects.equals(decoder, that.decoder) && increasing == that.increasing; } @Override @@ -477,7 +490,7 @@ public String toString() { @Override public int hashCode() { - return Objects.hash(field, decoder); + return Objects.hash(field, decoder, increasing); } @Override @@ -508,7 +521,8 @@ public int advanceShallow(int target) throws IOException { public float getMaxScore(int upTo) throws IOException { if (skipper != null) { if (skipper.minDocID(0) <= upTo) { - return (float) decoder.applyAsDouble(skipper.maxValue(0)); + long rawBound = increasing ? skipper.maxValue(0) : skipper.minValue(0); + return (float) decoder.applyAsDouble(rawBound); } } return Float.POSITIVE_INFINITY; diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java index a644e847ccb6..2a470a67246f 100644 --- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java +++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java @@ -42,6 +42,8 @@ import org.apache.lucene.search.PhraseQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.ScorerSupplier; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.Weight; @@ -445,5 +447,44 @@ public void testMaxScorePruningTopDocs() throws Exception { } } } + + public void testMaxScoreMonotonicDecreasingFunction() throws Exception { + try (Directory dir = newDirectory()) { + IndexWriterConfig conf = newIndexWriterConfig(); + try (IndexWriter indexWriter = new IndexWriter(dir, conf)) { + for (int i = 1; i <= 100; i++) { + Document doc = new Document(); + doc.add(new TextField(TEXT_FIELD, "decreasing", Field.Store.NO)); + doc.add(new NumericDocValuesField("val", i * 10)); // 10..1000 + indexWriter.addDocument(doc); + } + indexWriter.commit(); + } + + try (DirectoryReader reader = DirectoryReader.open(dir)) { + IndexSearcher searcher = new IndexSearcher(reader); + Query baseQuery = new TermQuery(new Term(TEXT_FIELD, "decreasing")); + // Monotonically decreasing function: f(x) = 10000.0 / x (increasing = false) + DoubleValuesSource valSource = DoubleValuesSource.fromField("val", (v) -> 10000.0 / v, false); + Query scriptQuery = new FunctionScoreQuery(baseQuery, valSource); + + LeafReaderContext ctx = reader.leaves().get(0); + Weight weight = scriptQuery.createWeight(searcher, ScoreMode.TOP_SCORES, 1f); + ScorerSupplier supplier = weight.scorerSupplier(ctx); + assertNotNull(supplier); + Scorer scorer = supplier.get(Long.MAX_VALUE); + + int maxDoc = ctx.reader().maxDoc(); + scorer.advanceShallow(0); + float maxScore = scorer.getMaxScore(maxDoc); + + if (ctx.reader().getDocValuesSkipper("val") != null) { + // Minimum raw value (10) produces max score: 10000 / 10 = 1000.0 + assertFalse(Float.isInfinite(maxScore)); + assertTrue("Expected maxScore >= 1000f but got " + maxScore, maxScore >= 1000f); + } + } + } + } } From aff2cf6ef23f029a550408fe9c31efef98fc08d6 Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 13:16:26 +0530 Subject: [PATCH 09/15] Fix EOF trailing newlines --- lucene/core/src/java/org/apache/lucene/search/DoubleValues.java | 1 - .../apache/lucene/queries/function/TestFunctionScoreQuery.java | 1 - 2 files changed, 2 deletions(-) diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java index 56ad615696ab..1560028513a3 100644 --- a/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java @@ -95,4 +95,3 @@ public boolean advanceExact(int doc) throws IOException { } }; } - diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java index 2a470a67246f..3bc71157202e 100644 --- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java +++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java @@ -487,4 +487,3 @@ public void testMaxScoreMonotonicDecreasingFunction() throws Exception { } } } - From 1fa8c2037b99f3c45a19789a6167be878f4fc26b Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 13:22:32 +0530 Subject: [PATCH 10/15] Deprecate 2-arg fromField and add Javadoc note per review --- .../org/apache/lucene/search/DoubleValuesSource.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java index 5323cf5d8c68..0d1ec2b4b288 100644 --- a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java @@ -279,11 +279,13 @@ public static DoubleValues similarityToQueryVector( } /** - * Creates a DoubleValuesSource that wraps a generic NumericDocValues field + * Creates a DoubleValuesSource that wraps a generic NumericDocValues field. Assumes the decoder is monotonically increasing. * * @param field the field to wrap, must have NumericDocValues - * @param decoder a function to convert the long-valued doc values to doubles + * @param decoder a function to convert the long-valued doc values to doubles; must be monotonically increasing. + * @deprecated Use {@link #fromField(String, LongToDoubleFunction, boolean)} to specify whether the decoder is monotonically increasing. */ + @Deprecated public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder) { return fromField(field, decoder, true); } @@ -301,17 +303,17 @@ public static DoubleValuesSource fromField(String field, LongToDoubleFunction de /** Creates a DoubleValuesSource that wraps a double-valued field */ public static DoubleValuesSource fromDoubleField(String field) { - return fromField(field, Double::longBitsToDouble); + return fromField(field, Double::longBitsToDouble, true); } /** Creates a DoubleValuesSource that wraps a float-valued field */ public static DoubleValuesSource fromFloatField(String field) { - return fromField(field, (v) -> (double) Float.intBitsToFloat((int) v)); + return fromField(field, (v) -> (double) Float.intBitsToFloat((int) v), true); } /** Creates a DoubleValuesSource that wraps a long-valued field */ public static DoubleValuesSource fromLongField(String field) { - return fromField(field, (v) -> (double) v); + return fromField(field, (v) -> (double) v, true); } /** Creates a DoubleValuesSource that wraps an int-valued field */ From 8f187e17d4acc6893732d48a7d98b63c061d4c88 Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 20:10:53 +0530 Subject: [PATCH 11/15] Using a custom homemade function without needing to specify inc/dec. --- .../function/TestFunctionScoreQuery.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java index 3bc71157202e..0b7e4d498bc8 100644 --- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java +++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java @@ -35,6 +35,7 @@ import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BoostQuery; +import org.apache.lucene.search.DoubleValues; import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.IndexSearcher; @@ -486,4 +487,83 @@ public void testMaxScoreMonotonicDecreasingFunction() throws Exception { } } } + + public void testCustomHomemadeIncreasingAndDecreasingFunction() throws Exception { + try (Directory dir = newDirectory()) { + IndexWriterConfig conf = newIndexWriterConfig(); + try (IndexWriter indexWriter = new IndexWriter(dir, conf)) { + for (int i = 1; i <= 100; i++) { + Document doc = new Document(); + doc.add(new TextField(TEXT_FIELD, "custom test", Field.Store.NO)); + doc.add(new NumericDocValuesField("val", i * 5)); // 5..500 + indexWriter.addDocument(doc); + } + indexWriter.commit(); + } + + try (DirectoryReader reader = DirectoryReader.open(dir)) { + IndexSearcher searcher = new IndexSearcher(reader); + Query baseQuery = new TermQuery(new Term(TEXT_FIELD, "custom")); + + // Homemade custom DoubleValuesSource class: f(x) = sqrt(x) [Increasing] + DoubleValuesSource customIncreasing = new DoubleValuesSource() { + @Override + public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { + DoubleValues in = DoubleValuesSource.fromLongField("val").getValues(ctx, scores); + return new DoubleValues() { + @Override + public double doubleValue() throws IOException { + return Math.sqrt(in.doubleValue()); + } + + @Override + public boolean advanceExact(int doc) throws IOException { + return in.advanceExact(doc); + } + + @Override + public int advanceShallow(int target) throws IOException { + return in.advanceShallow(target); + } + + @Override + public float getMaxScore(int upTo) throws IOException { + float innerMax = in.getMaxScore(upTo); + return Float.isInfinite(innerMax) ? Float.POSITIVE_INFINITY : (float) Math.sqrt(innerMax); + } + }; + } + + @Override + public boolean needsScores() { return false; } + @Override + public boolean isCacheable(LeafReaderContext ctx) { return true; } + @Override + public DoubleValuesSource rewrite(IndexSearcher searcher) { return this; } + @Override + public boolean equals(Object o) { return o == this; } + @Override + public int hashCode() { return System.identityHashCode(this); } + @Override + public String toString() { return "customSqrt(val)"; } + }; + + Query q = new FunctionScoreQuery(baseQuery, customIncreasing); + LeafReaderContext ctx = reader.leaves().get(0); + Weight weight = q.createWeight(searcher, ScoreMode.TOP_SCORES, 1f); + ScorerSupplier supplier = weight.scorerSupplier(ctx); + assertNotNull(supplier); + Scorer scorer = supplier.get(Long.MAX_VALUE); + scorer.advanceShallow(0); + float maxScore = scorer.getMaxScore(ctx.reader().maxDoc()); + + if (ctx.reader().getDocValuesSkipper("val") != null) { + // Raw max is 500, sqrt(500) ~ 22.36 + assertFalse(Float.isInfinite(maxScore)); + assertTrue(maxScore >= 22.3f); + } + } + } + } } + From 6dba6f7bd5707211c41b6fd36d08372af0161c1a Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 21:13:52 +0530 Subject: [PATCH 12/15] Using Monotonicity as an Enum. --- .../apache/lucene/search/DoubleValues.java | 8 +++ .../lucene/search/DoubleValuesSource.java | 62 ++++++++++++++----- .../function/TestFunctionScoreQuery.java | 4 +- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java index 1560028513a3..b92f9279c6af 100644 --- a/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java @@ -49,6 +49,14 @@ public float getMaxScore(int upTo) throws IOException { return Float.POSITIVE_INFINITY; } + /** + * Return the minimum score that documents between the current position and {@code upTo} can produce. + * Default implementation returns {@link Float#NEGATIVE_INFINITY}. + */ + public float getMinScore(int upTo) throws IOException { + return Float.NEGATIVE_INFINITY; + } + /** Wrap a DoubleValues instance, returning a default if the wrapped instance has no value */ public static DoubleValues withDefault(DoubleValues in, double missingValue) { return new DoubleValues() { diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java index 0d1ec2b4b288..a90188e037ea 100644 --- a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java @@ -47,6 +47,16 @@ */ public abstract class DoubleValuesSource implements SegmentCacheable { + /** Monotonicity direction of a decoder function */ + public enum Monotonicity { + /** Increasing function */ + INCREASING, + /** Decreasing function */ + DECREASING, + /** Non-monotonic or unknown direction function */ + NONE + } + /** * Returns a {@link DoubleValues} instance for the passed-in LeafReaderContext and scores * @@ -279,15 +289,14 @@ public static DoubleValues similarityToQueryVector( } /** - * Creates a DoubleValuesSource that wraps a generic NumericDocValues field. Assumes the decoder is monotonically increasing. + * Creates a DoubleValuesSource that wraps a generic NumericDocValues field. + * Assumes no monotonic direction by default (Monotonicity.NONE). * * @param field the field to wrap, must have NumericDocValues - * @param decoder a function to convert the long-valued doc values to doubles; must be monotonically increasing. - * @deprecated Use {@link #fromField(String, LongToDoubleFunction, boolean)} to specify whether the decoder is monotonically increasing. + * @param decoder a function to convert the long-valued doc values to doubles */ - @Deprecated public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder) { - return fromField(field, decoder, true); + return fromField(field, decoder, Monotonicity.NONE); } /** @@ -295,25 +304,33 @@ public static DoubleValuesSource fromField(String field, LongToDoubleFunction de * * @param field the field to wrap, must have NumericDocValues * @param decoder a function to convert the long-valued doc values to doubles - * @param increasing true if the decoder function is monotonically increasing; false if monotonically decreasing + * @param monotonicity the monotonicity direction of the decoder function */ + public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder, Monotonicity monotonicity) { + return new FieldValuesSource(field, decoder, monotonicity); + } + + /** + * @deprecated Use {@link #fromField(String, LongToDoubleFunction, Monotonicity)} + */ + @Deprecated public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder, boolean increasing) { - return new FieldValuesSource(field, decoder, increasing); + return fromField(field, decoder, increasing ? Monotonicity.INCREASING : Monotonicity.DECREASING); } /** Creates a DoubleValuesSource that wraps a double-valued field */ public static DoubleValuesSource fromDoubleField(String field) { - return fromField(field, Double::longBitsToDouble, true); + return fromField(field, Double::longBitsToDouble, Monotonicity.INCREASING); } /** Creates a DoubleValuesSource that wraps a float-valued field */ public static DoubleValuesSource fromFloatField(String field) { - return fromField(field, (v) -> (double) Float.intBitsToFloat((int) v), true); + return fromField(field, (v) -> (double) Float.intBitsToFloat((int) v), Monotonicity.INCREASING); } /** Creates a DoubleValuesSource that wraps a long-valued field */ public static DoubleValuesSource fromLongField(String field) { - return fromField(field, (v) -> (double) v, true); + return fromField(field, (v) -> (double) v, Monotonicity.INCREASING); } /** Creates a DoubleValuesSource that wraps an int-valued field */ @@ -469,12 +486,12 @@ private static class FieldValuesSource extends DoubleValuesSource { final String field; final LongToDoubleFunction decoder; - final boolean increasing; + final Monotonicity monotonicity; - private FieldValuesSource(String field, LongToDoubleFunction decoder, boolean increasing) { + private FieldValuesSource(String field, LongToDoubleFunction decoder, Monotonicity monotonicity) { this.field = field; this.decoder = decoder; - this.increasing = increasing; + this.monotonicity = Objects.requireNonNull(monotonicity); } @Override @@ -482,7 +499,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FieldValuesSource that = (FieldValuesSource) o; - return Objects.equals(field, that.field) && Objects.equals(decoder, that.decoder) && increasing == that.increasing; + return Objects.equals(field, that.field) && Objects.equals(decoder, that.decoder) && monotonicity == that.monotonicity; } @Override @@ -492,7 +509,7 @@ public String toString() { @Override public int hashCode() { - return Objects.hash(field, decoder, increasing); + return Objects.hash(field, decoder, monotonicity); } @Override @@ -521,14 +538,25 @@ public int advanceShallow(int target) throws IOException { @Override public float getMaxScore(int upTo) throws IOException { - if (skipper != null) { + if (skipper != null && monotonicity != Monotonicity.NONE) { if (skipper.minDocID(0) <= upTo) { - long rawBound = increasing ? skipper.maxValue(0) : skipper.minValue(0); + long rawBound = (monotonicity == Monotonicity.INCREASING) ? skipper.maxValue(0) : skipper.minValue(0); return (float) decoder.applyAsDouble(rawBound); } } return Float.POSITIVE_INFINITY; } + + @Override + public float getMinScore(int upTo) throws IOException { + if (skipper != null && monotonicity != Monotonicity.NONE) { + if (skipper.minDocID(0) <= upTo) { + long rawBound = (monotonicity == Monotonicity.INCREASING) ? skipper.minValue(0) : skipper.maxValue(0); + return (float) decoder.applyAsDouble(rawBound); + } + } + return Float.NEGATIVE_INFINITY; + } }; } diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java index 0b7e4d498bc8..30ca8c0680b7 100644 --- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java +++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java @@ -465,8 +465,8 @@ public void testMaxScoreMonotonicDecreasingFunction() throws Exception { try (DirectoryReader reader = DirectoryReader.open(dir)) { IndexSearcher searcher = new IndexSearcher(reader); Query baseQuery = new TermQuery(new Term(TEXT_FIELD, "decreasing")); - // Monotonically decreasing function: f(x) = 10000.0 / x (increasing = false) - DoubleValuesSource valSource = DoubleValuesSource.fromField("val", (v) -> 10000.0 / v, false); + // Monotonically decreasing function: f(x) = 10000.0 / x (Monotonicity.DECREASING) + DoubleValuesSource valSource = DoubleValuesSource.fromField("val", (v) -> 10000.0 / v, DoubleValuesSource.Monotonicity.DECREASING); Query scriptQuery = new FunctionScoreQuery(baseQuery, valSource); LeafReaderContext ctx = reader.leaves().get(0); From 152ed7f28175de7cd7822a93586773e984faf950 Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 22:28:49 +0530 Subject: [PATCH 13/15] Removed deprecated method. --- .../lucene/search/DoubleValuesSource.java | 254 ++++++++++-------- 1 file changed, 146 insertions(+), 108 deletions(-) diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java index a90188e037ea..6dad13d1a203 100644 --- a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java @@ -31,18 +31,29 @@ /** * Base class for producing {@link DoubleValues} * - *

To obtain a {@link DoubleValues} object for a leaf reader, clients should call {@link + *

+ * To obtain a {@link DoubleValues} object for a leaf reader, clients should + * call {@link * #rewrite(IndexSearcher)} against the top-level searcher, and then call {@link - * #getValues(LeafReaderContext, DoubleValues)} on the resulting DoubleValuesSource. + * #getValues(LeafReaderContext, DoubleValues)} on the resulting + * DoubleValuesSource. * - *

DoubleValuesSource objects for NumericDocValues fields can be obtained by calling {@link - * #fromDoubleField(String)}, {@link #fromFloatField(String)}, {@link #fromIntField(String)} or - * {@link #fromLongField(String)}, or from {@link #fromField(String, LongToDoubleFunction)} if + *

+ * DoubleValuesSource objects for NumericDocValues fields can be obtained by + * calling {@link + * #fromDoubleField(String)}, {@link #fromFloatField(String)}, + * {@link #fromIntField(String)} or + * {@link #fromLongField(String)}, or from + * {@link #fromField(String, LongToDoubleFunction)} if * special long-to-double encoding is required. * - *

Scores may be used as a source for value calculations by wrapping a {@link Scorer} using - * {@link #fromScorer(Scorable)} and passing the resulting DoubleValues to {@link - * #getValues(LeafReaderContext, DoubleValues)}. The scores can then be accessed using the {@link + *

+ * Scores may be used as a source for value calculations by wrapping a + * {@link Scorer} using + * {@link #fromScorer(Scorable)} and passing the resulting DoubleValues to + * {@link + * #getValues(LeafReaderContext, DoubleValues)}. The scores can then be accessed + * using the {@link * #SCORES} DoubleValuesSource. */ public abstract class DoubleValuesSource implements SegmentCacheable { @@ -58,9 +69,12 @@ public enum Monotonicity { } /** - * Returns a {@link DoubleValues} instance for the passed-in LeafReaderContext and scores + * Returns a {@link DoubleValues} instance for the passed-in LeafReaderContext + * and scores * - *

If scores are not needed to calculate the values (ie {@link #needsScores() returns false}, + *

+ * If scores are not needed to calculate the values (ie {@link #needsScores() + * returns false}, * callers may safely pass {@code null} for the {@code scores} parameter. */ public abstract DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) @@ -72,33 +86,40 @@ public abstract DoubleValues getValues(LeafReaderContext ctx, DoubleValues score /** * An explanation of the value for the named document. * - * @param ctx the readers context to create the {@link Explanation} for. + * @param ctx the readers context to create the {@link Explanation} for. * @param docId the document's id relative to the given context's reader * @return an Explanation for the value * @throws IOException if an {@link IOException} occurs */ public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException { - DoubleValues dv = - getValues( - ctx, - DoubleValuesSource.constant(scoreExplanation.getValue().doubleValue()) - .getValues(ctx, null)); - if (dv.advanceExact(docId)) return Explanation.match(dv.doubleValue(), this.toString()); + DoubleValues dv = getValues( + ctx, + DoubleValuesSource.constant(scoreExplanation.getValue().doubleValue()) + .getValues(ctx, null)); + if (dv.advanceExact(docId)) + return Explanation.match(dv.doubleValue(), this.toString()); return Explanation.noMatch(this.toString()); } /** * Return a DoubleValuesSource specialised for the given IndexSearcher * - *

Implementations should assume that this will only be called once. IndexReader-independent + *

+ * Implementations should assume that this will only be called once. + * IndexReader-independent * implementations can just return {@code this} * - *

Queries that use DoubleValuesSource objects should call rewrite() during {@link - * Query#createWeight(IndexSearcher, ScoreMode, float)} rather than during {@link + *

+ * Queries that use DoubleValuesSource objects should call rewrite() during + * {@link + * Query#createWeight(IndexSearcher, ScoreMode, float)} rather than during + * {@link * Query#rewrite(IndexSearcher)} to avoid IndexReader reference leakage. * - *

For the same reason, implementations that cache references to the IndexSearcher should + *

+ * For the same reason, implementations that cache references to the + * IndexSearcher should * return a new object from this method. */ public abstract DoubleValuesSource rewrite(IndexSearcher reader) throws IOException; @@ -115,7 +136,7 @@ public SortField getSortField(boolean reverse) { /** * Create a sort field based on the value of this producer * - * @param reverse true if the sort should be decreasing + * @param reverse true if the sort should be decreasing * @param missingValue a placeholder to use for documents with no value */ public SortField getSortField(boolean reverse, double missingValue) { @@ -136,7 +157,10 @@ public final LongValuesSource toLongValuesSource() { return new LongDoubleValuesSource(this); } - /** Convert to {@link LongValuesSource} by calling {@link NumericUtils#doubleToSortableLong} */ + /** + * Convert to {@link LongValuesSource} by calling + * {@link NumericUtils#doubleToSortableLong} + */ public final LongValuesSource toSortableLongDoubleValuesSource() { return new SortableLongDoubleValuesSource(this); } @@ -178,8 +202,10 @@ public int hashCode() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; SortableLongDoubleValuesSource that = (SortableLongDoubleValuesSource) o; return Objects.equals(inner, that.inner); } @@ -236,8 +262,10 @@ public boolean needsScores() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; LongDoubleValuesSource that = (LongDoubleValuesSource) o; return Objects.equals(inner, that.inner); } @@ -259,10 +287,11 @@ public LongValuesSource rewrite(IndexSearcher searcher) throws IOException { } /** - * Returns a DoubleValues instance for computing the vector similarity score per document against + * Returns a DoubleValues instance for computing the vector similarity score per + * document against * the byte query vector * - * @param ctx the context for which to return the DoubleValues + * @param ctx the context for which to return the DoubleValues * @param queryVector byte query vector * @param vectorField knn byte field name * @return DoubleValues instance @@ -274,10 +303,11 @@ public static DoubleValues similarityToQueryVector( } /** - * Returns a DoubleValues instance for computing the vector similarity score per document against + * Returns a DoubleValues instance for computing the vector similarity score per + * document against * the float query vector * - * @param ctx the context for which to return the DoubleValues + * @param ctx the context for which to return the DoubleValues * @param queryVector float query vector * @param vectorField knn float field name * @return DoubleValues instance @@ -292,7 +322,7 @@ public static DoubleValues similarityToQueryVector( * Creates a DoubleValuesSource that wraps a generic NumericDocValues field. * Assumes no monotonic direction by default (Monotonicity.NONE). * - * @param field the field to wrap, must have NumericDocValues + * @param field the field to wrap, must have NumericDocValues * @param decoder a function to convert the long-valued doc values to doubles */ public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder) { @@ -302,22 +332,15 @@ public static DoubleValuesSource fromField(String field, LongToDoubleFunction de /** * Creates a DoubleValuesSource that wraps a generic NumericDocValues field * - * @param field the field to wrap, must have NumericDocValues - * @param decoder a function to convert the long-valued doc values to doubles + * @param field the field to wrap, must have NumericDocValues + * @param decoder a function to convert the long-valued doc values to + * doubles * @param monotonicity the monotonicity direction of the decoder function */ public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder, Monotonicity monotonicity) { return new FieldValuesSource(field, decoder, monotonicity); } - /** - * @deprecated Use {@link #fromField(String, LongToDoubleFunction, Monotonicity)} - */ - @Deprecated - public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder, boolean increasing) { - return fromField(field, decoder, increasing ? Monotonicity.INCREASING : Monotonicity.DECREASING); - } - /** Creates a DoubleValuesSource that wraps a double-valued field */ public static DoubleValuesSource fromDoubleField(String field) { return fromField(field, Double::longBitsToDouble, Monotonicity.INCREASING); @@ -341,54 +364,56 @@ public static DoubleValuesSource fromIntField(String field) { /** * A DoubleValuesSource that exposes a document's score * - *

If this source is used as part of a values calculation, then callers must not pass {@code - * null} as the {@link DoubleValues} parameter on {@link #getValues(LeafReaderContext, + *

+ * If this source is used as part of a values calculation, then callers must not + * pass {@code + * null} as the {@link DoubleValues} parameter on + * {@link #getValues(LeafReaderContext, * DoubleValues)} */ - public static final DoubleValuesSource SCORES = - new DoubleValuesSource() { - @Override - public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) - throws IOException { - assert scores != null; - return scores; - } + public static final DoubleValuesSource SCORES = new DoubleValuesSource() { + @Override + public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) + throws IOException { + assert scores != null; + return scores; + } - @Override - public boolean needsScores() { - return true; - } + @Override + public boolean needsScores() { + return true; + } - @Override - public boolean isCacheable(LeafReaderContext ctx) { - return false; - } + @Override + public boolean isCacheable(LeafReaderContext ctx) { + return false; + } - @Override - public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) { - return scoreExplanation; - } + @Override + public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) { + return scoreExplanation; + } - @Override - public int hashCode() { - return 0; - } + @Override + public int hashCode() { + return 0; + } - @Override - public boolean equals(Object obj) { - return obj == this; - } + @Override + public boolean equals(Object obj) { + return obj == this; + } - @Override - public String toString() { - return "scores"; - } + @Override + public String toString() { + return "scores"; + } - @Override - public DoubleValuesSource rewrite(IndexSearcher searcher) { - return this; - } - }; + @Override + public DoubleValuesSource rewrite(IndexSearcher searcher) { + return this; + } + }; /** Creates a DoubleValuesSource that always returns a constant value */ public static DoubleValuesSource constant(double value) { @@ -402,18 +427,17 @@ private static class ConstantValuesSource extends DoubleValuesSource { private ConstantValuesSource(double value) { this.value = value; - this.doubleValues = - new DoubleValues() { - @Override - public double doubleValue() { - return value; - } + this.doubleValues = new DoubleValues() { + @Override + public double doubleValue() { + return value; + } - @Override - public boolean advanceExact(int doc) { - return true; - } - }; + @Override + public boolean advanceExact(int doc) { + return true; + } + }; } @Override @@ -448,8 +472,10 @@ public int hashCode() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; ConstantValuesSource that = (ConstantValuesSource) o; return Double.compare(that.value, value) == 0; } @@ -463,9 +489,12 @@ public String toString() { /** * Returns a DoubleValues instance that wraps scores returned by a Scorer. * - *

Note: If you intend to call {@link Scorable#score()} on the provided {@code scorer} + *

+ * Note: If you intend to call {@link Scorable#score()} on the provided + * {@code scorer} * separately, you may want to consider wrapping the collector with {@link - * ScoreCachingWrappingScorer#wrap(LeafCollector)} to avoid computing the actual score multiple + * ScoreCachingWrappingScorer#wrap(LeafCollector)} to avoid computing the actual + * score multiple * times. */ public static DoubleValues fromScorer(Scorable scorer) { @@ -496,10 +525,13 @@ private FieldValuesSource(String field, LongToDoubleFunction decoder, Monotonici @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; FieldValuesSource that = (FieldValuesSource) o; - return Objects.equals(field, that.field) && Objects.equals(decoder, that.decoder) && monotonicity == that.monotonicity; + return Objects.equals(field, that.field) && Objects.equals(decoder, that.decoder) + && monotonicity == that.monotonicity; } @Override @@ -576,7 +608,8 @@ public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreEx DoubleValues values = getValues(ctx, null); if (values.advanceExact(docId)) return Explanation.match(values.doubleValue(), this.toString()); - else return Explanation.noMatch(this.toString()); + else + return Explanation.noMatch(this.toString()); } @Override @@ -603,7 +636,8 @@ public boolean needsScores() { public String toString() { StringBuilder buffer = new StringBuilder("<"); buffer.append(getField()).append(">"); - if (reverse) buffer.append("!"); + if (reverse) + buffer.append("!"); return buffer.toString(); } @@ -712,8 +746,10 @@ private QueryDoubleValuesSource(Query query) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; QueryDoubleValuesSource that = (QueryDoubleValuesSource) o; return Objects.equals(query, that.query); } @@ -761,12 +797,12 @@ private WeightDoubleValuesSource(Weight weight) { @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { Scorer scorer = weight.scorer(ctx); - if (scorer == null) return DoubleValues.EMPTY; + if (scorer == null) + return DoubleValues.EMPTY; return new DoubleValues() { private final TwoPhaseIterator tpi = scorer.twoPhaseIterator(); - private final DocIdSetIterator disi = - (tpi == null) ? scorer.iterator() : tpi.approximation(); + private final DocIdSetIterator disi = (tpi == null) ? scorer.iterator() : tpi.approximation(); private Boolean tpiMatch = null; // cache tpi.matches() @Override @@ -811,8 +847,10 @@ public DoubleValuesSource rewrite(IndexSearcher searcher) throws IOException { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; WeightDoubleValuesSource that = (WeightDoubleValuesSource) o; return Objects.equals(weight, that.weight); } From 6a1cdb59e9422a195d9f14c799fc2435ac68bf26 Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Wed, 29 Jul 2026 23:38:31 +0530 Subject: [PATCH 14/15] prek --- .../apache/lucene/queries/function/TestFunctionScoreQuery.java | 1 - 1 file changed, 1 deletion(-) diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java index 30ca8c0680b7..f03e808e813a 100644 --- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java +++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java @@ -566,4 +566,3 @@ public float getMaxScore(int upTo) throws IOException { } } } - From b996adfd350e32571e87dfd06c7472cf52b43d4b Mon Sep 17 00:00:00 2001 From: rajat315315 Date: Thu, 30 Jul 2026 00:05:02 +0530 Subject: [PATCH 15/15] gradlew tidy --- ...nctionScoreWANDMainVsFeatureBenchmark.java | 33 ++- .../apache/lucene/search/DoubleValues.java | 9 +- .../lucene/search/DoubleValuesSource.java | 266 ++++++++---------- .../function/TestFunctionScoreQuery.java | 83 ++++-- 4 files changed, 195 insertions(+), 196 deletions(-) diff --git a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java index 77fe63755fea..b281a7d8fe7e 100644 --- a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java +++ b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java @@ -30,7 +30,6 @@ import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.Term; import org.apache.lucene.queries.function.FunctionScoreQuery; -import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.IndexSearcher; @@ -54,8 +53,8 @@ import org.openjdk.jmh.annotations.Warmup; /** - * JMH Micro-benchmark comparing actual FunctionScoreQuery search throughput - * on main branch vs feature/function-score-wand-skip-index branch over 1 Million Lucene index documents. + * JMH Micro-benchmark comparing actual FunctionScoreQuery search throughput on main branch vs + * feature/function-score-wand-skip-index branch over 1 Million Lucene index documents. */ @State(Scope.Benchmark) @BenchmarkMode(Mode.Throughput) @@ -91,7 +90,10 @@ public void setup() throws IOException { IndexWriterConfig iwc = new IndexWriterConfig(); iwc.setRAMBufferSizeMB(256); if (indexSort) { - iwc.setIndexSort(new org.apache.lucene.search.Sort(new org.apache.lucene.search.SortField("score_field", org.apache.lucene.search.SortField.Type.LONG, true))); + iwc.setIndexSort( + new org.apache.lucene.search.Sort( + new org.apache.lucene.search.SortField( + "score_field", org.apache.lucene.search.SortField.Type.LONG, true))); } try (IndexWriter writer = new IndexWriter(dir, iwc)) { @@ -102,7 +104,8 @@ public void setup() throws IOException { String chosenTerm = terms[random.nextInt(terms.length)]; doc.add(new TextField("body", chosenTerm, Field.Store.NO)); // Real-world Power-law / Zipfian score distribution (98% low scores, 2% high scores) - long scoreVal = (random.nextFloat() < 0.02f) ? (50000 + random.nextInt(50000)) : random.nextInt(100); + long scoreVal = + (random.nextFloat() < 0.02f) ? (50000 + random.nextInt(50000)) : random.nextInt(100); doc.add(new NumericDocValuesField("score_field", scoreVal)); writer.addDocument(doc); } @@ -113,11 +116,21 @@ public void setup() throws IOException { searcher = new IndexSearcher(reader); BooleanQuery.Builder bq = new BooleanQuery.Builder(); - bq.add(new TermQuery(new Term("body", "term_a")), org.apache.lucene.search.BooleanClause.Occur.SHOULD); - bq.add(new TermQuery(new Term("body", "term_b")), org.apache.lucene.search.BooleanClause.Occur.SHOULD); - bq.add(new TermQuery(new Term("body", "term_c")), org.apache.lucene.search.BooleanClause.Occur.SHOULD); - bq.add(new TermQuery(new Term("body", "term_d")), org.apache.lucene.search.BooleanClause.Occur.SHOULD); - bq.add(new TermQuery(new Term("body", "term_e")), org.apache.lucene.search.BooleanClause.Occur.SHOULD); + bq.add( + new TermQuery(new Term("body", "term_a")), + org.apache.lucene.search.BooleanClause.Occur.SHOULD); + bq.add( + new TermQuery(new Term("body", "term_b")), + org.apache.lucene.search.BooleanClause.Occur.SHOULD); + bq.add( + new TermQuery(new Term("body", "term_c")), + org.apache.lucene.search.BooleanClause.Occur.SHOULD); + bq.add( + new TermQuery(new Term("body", "term_d")), + org.apache.lucene.search.BooleanClause.Occur.SHOULD); + bq.add( + new TermQuery(new Term("body", "term_e")), + org.apache.lucene.search.BooleanClause.Occur.SHOULD); Query baseQuery = bq.build(); DoubleValuesSource valueSource = DoubleValuesSource.fromLongField("score_field"); functionScoreQuery = new FunctionScoreQuery(baseQuery, valueSource); diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java index b92f9279c6af..33b6f07e654a 100644 --- a/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java @@ -18,7 +18,6 @@ package org.apache.lucene.search; import java.io.IOException; -import org.apache.lucene.search.DocIdSetIterator; /** Per-segment, per-document double values, which can be calculated at search-time */ public abstract class DoubleValues { @@ -42,16 +41,16 @@ public int advanceShallow(int target) throws IOException { } /** - * Return the maximum score that documents between the current position and {@code upTo} can produce. - * Default implementation returns {@link Float#POSITIVE_INFINITY}. + * Return the maximum score that documents between the current position and {@code upTo} can + * produce. Default implementation returns {@link Float#POSITIVE_INFINITY}. */ public float getMaxScore(int upTo) throws IOException { return Float.POSITIVE_INFINITY; } /** - * Return the minimum score that documents between the current position and {@code upTo} can produce. - * Default implementation returns {@link Float#NEGATIVE_INFINITY}. + * Return the minimum score that documents between the current position and {@code upTo} can + * produce. Default implementation returns {@link Float#NEGATIVE_INFINITY}. */ public float getMinScore(int upTo) throws IOException { return Float.NEGATIVE_INFINITY; diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java index 6dad13d1a203..a6750cbd9864 100644 --- a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java @@ -31,29 +31,18 @@ /** * Base class for producing {@link DoubleValues} * - *

- * To obtain a {@link DoubleValues} object for a leaf reader, clients should - * call {@link + *

To obtain a {@link DoubleValues} object for a leaf reader, clients should call {@link * #rewrite(IndexSearcher)} against the top-level searcher, and then call {@link - * #getValues(LeafReaderContext, DoubleValues)} on the resulting - * DoubleValuesSource. + * #getValues(LeafReaderContext, DoubleValues)} on the resulting DoubleValuesSource. * - *

- * DoubleValuesSource objects for NumericDocValues fields can be obtained by - * calling {@link - * #fromDoubleField(String)}, {@link #fromFloatField(String)}, - * {@link #fromIntField(String)} or - * {@link #fromLongField(String)}, or from - * {@link #fromField(String, LongToDoubleFunction)} if + *

DoubleValuesSource objects for NumericDocValues fields can be obtained by calling {@link + * #fromDoubleField(String)}, {@link #fromFloatField(String)}, {@link #fromIntField(String)} or + * {@link #fromLongField(String)}, or from {@link #fromField(String, LongToDoubleFunction)} if * special long-to-double encoding is required. * - *

- * Scores may be used as a source for value calculations by wrapping a - * {@link Scorer} using - * {@link #fromScorer(Scorable)} and passing the resulting DoubleValues to - * {@link - * #getValues(LeafReaderContext, DoubleValues)}. The scores can then be accessed - * using the {@link + *

Scores may be used as a source for value calculations by wrapping a {@link Scorer} using + * {@link #fromScorer(Scorable)} and passing the resulting DoubleValues to {@link + * #getValues(LeafReaderContext, DoubleValues)}. The scores can then be accessed using the {@link * #SCORES} DoubleValuesSource. */ public abstract class DoubleValuesSource implements SegmentCacheable { @@ -69,12 +58,9 @@ public enum Monotonicity { } /** - * Returns a {@link DoubleValues} instance for the passed-in LeafReaderContext - * and scores + * Returns a {@link DoubleValues} instance for the passed-in LeafReaderContext and scores * - *

- * If scores are not needed to calculate the values (ie {@link #needsScores() - * returns false}, + *

If scores are not needed to calculate the values (ie {@link #needsScores() returns false}, * callers may safely pass {@code null} for the {@code scores} parameter. */ public abstract DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) @@ -86,40 +72,33 @@ public abstract DoubleValues getValues(LeafReaderContext ctx, DoubleValues score /** * An explanation of the value for the named document. * - * @param ctx the readers context to create the {@link Explanation} for. + * @param ctx the readers context to create the {@link Explanation} for. * @param docId the document's id relative to the given context's reader * @return an Explanation for the value * @throws IOException if an {@link IOException} occurs */ public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException { - DoubleValues dv = getValues( - ctx, - DoubleValuesSource.constant(scoreExplanation.getValue().doubleValue()) - .getValues(ctx, null)); - if (dv.advanceExact(docId)) - return Explanation.match(dv.doubleValue(), this.toString()); + DoubleValues dv = + getValues( + ctx, + DoubleValuesSource.constant(scoreExplanation.getValue().doubleValue()) + .getValues(ctx, null)); + if (dv.advanceExact(docId)) return Explanation.match(dv.doubleValue(), this.toString()); return Explanation.noMatch(this.toString()); } /** * Return a DoubleValuesSource specialised for the given IndexSearcher * - *

- * Implementations should assume that this will only be called once. - * IndexReader-independent + *

Implementations should assume that this will only be called once. IndexReader-independent * implementations can just return {@code this} * - *

- * Queries that use DoubleValuesSource objects should call rewrite() during - * {@link - * Query#createWeight(IndexSearcher, ScoreMode, float)} rather than during - * {@link + *

Queries that use DoubleValuesSource objects should call rewrite() during {@link + * Query#createWeight(IndexSearcher, ScoreMode, float)} rather than during {@link * Query#rewrite(IndexSearcher)} to avoid IndexReader reference leakage. * - *

- * For the same reason, implementations that cache references to the - * IndexSearcher should + *

For the same reason, implementations that cache references to the IndexSearcher should * return a new object from this method. */ public abstract DoubleValuesSource rewrite(IndexSearcher reader) throws IOException; @@ -136,7 +115,7 @@ public SortField getSortField(boolean reverse) { /** * Create a sort field based on the value of this producer * - * @param reverse true if the sort should be decreasing + * @param reverse true if the sort should be decreasing * @param missingValue a placeholder to use for documents with no value */ public SortField getSortField(boolean reverse, double missingValue) { @@ -157,10 +136,7 @@ public final LongValuesSource toLongValuesSource() { return new LongDoubleValuesSource(this); } - /** - * Convert to {@link LongValuesSource} by calling - * {@link NumericUtils#doubleToSortableLong} - */ + /** Convert to {@link LongValuesSource} by calling {@link NumericUtils#doubleToSortableLong} */ public final LongValuesSource toSortableLongDoubleValuesSource() { return new SortableLongDoubleValuesSource(this); } @@ -202,10 +178,8 @@ public int hashCode() { @Override public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; SortableLongDoubleValuesSource that = (SortableLongDoubleValuesSource) o; return Objects.equals(inner, that.inner); } @@ -262,10 +236,8 @@ public boolean needsScores() { @Override public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; LongDoubleValuesSource that = (LongDoubleValuesSource) o; return Objects.equals(inner, that.inner); } @@ -287,11 +259,10 @@ public LongValuesSource rewrite(IndexSearcher searcher) throws IOException { } /** - * Returns a DoubleValues instance for computing the vector similarity score per - * document against + * Returns a DoubleValues instance for computing the vector similarity score per document against * the byte query vector * - * @param ctx the context for which to return the DoubleValues + * @param ctx the context for which to return the DoubleValues * @param queryVector byte query vector * @param vectorField knn byte field name * @return DoubleValues instance @@ -303,11 +274,10 @@ public static DoubleValues similarityToQueryVector( } /** - * Returns a DoubleValues instance for computing the vector similarity score per - * document against + * Returns a DoubleValues instance for computing the vector similarity score per document against * the float query vector * - * @param ctx the context for which to return the DoubleValues + * @param ctx the context for which to return the DoubleValues * @param queryVector float query vector * @param vectorField knn float field name * @return DoubleValues instance @@ -319,10 +289,10 @@ public static DoubleValues similarityToQueryVector( } /** - * Creates a DoubleValuesSource that wraps a generic NumericDocValues field. - * Assumes no monotonic direction by default (Monotonicity.NONE). + * Creates a DoubleValuesSource that wraps a generic NumericDocValues field. Assumes no monotonic + * direction by default (Monotonicity.NONE). * - * @param field the field to wrap, must have NumericDocValues + * @param field the field to wrap, must have NumericDocValues * @param decoder a function to convert the long-valued doc values to doubles */ public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder) { @@ -332,12 +302,12 @@ public static DoubleValuesSource fromField(String field, LongToDoubleFunction de /** * Creates a DoubleValuesSource that wraps a generic NumericDocValues field * - * @param field the field to wrap, must have NumericDocValues - * @param decoder a function to convert the long-valued doc values to - * doubles + * @param field the field to wrap, must have NumericDocValues + * @param decoder a function to convert the long-valued doc values to doubles * @param monotonicity the monotonicity direction of the decoder function */ - public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder, Monotonicity monotonicity) { + public static DoubleValuesSource fromField( + String field, LongToDoubleFunction decoder, Monotonicity monotonicity) { return new FieldValuesSource(field, decoder, monotonicity); } @@ -364,56 +334,54 @@ public static DoubleValuesSource fromIntField(String field) { /** * A DoubleValuesSource that exposes a document's score * - *

- * If this source is used as part of a values calculation, then callers must not - * pass {@code - * null} as the {@link DoubleValues} parameter on - * {@link #getValues(LeafReaderContext, + *

If this source is used as part of a values calculation, then callers must not pass {@code + * null} as the {@link DoubleValues} parameter on {@link #getValues(LeafReaderContext, * DoubleValues)} */ - public static final DoubleValuesSource SCORES = new DoubleValuesSource() { - @Override - public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) - throws IOException { - assert scores != null; - return scores; - } + public static final DoubleValuesSource SCORES = + new DoubleValuesSource() { + @Override + public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) + throws IOException { + assert scores != null; + return scores; + } - @Override - public boolean needsScores() { - return true; - } + @Override + public boolean needsScores() { + return true; + } - @Override - public boolean isCacheable(LeafReaderContext ctx) { - return false; - } + @Override + public boolean isCacheable(LeafReaderContext ctx) { + return false; + } - @Override - public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) { - return scoreExplanation; - } + @Override + public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) { + return scoreExplanation; + } - @Override - public int hashCode() { - return 0; - } + @Override + public int hashCode() { + return 0; + } - @Override - public boolean equals(Object obj) { - return obj == this; - } + @Override + public boolean equals(Object obj) { + return obj == this; + } - @Override - public String toString() { - return "scores"; - } + @Override + public String toString() { + return "scores"; + } - @Override - public DoubleValuesSource rewrite(IndexSearcher searcher) { - return this; - } - }; + @Override + public DoubleValuesSource rewrite(IndexSearcher searcher) { + return this; + } + }; /** Creates a DoubleValuesSource that always returns a constant value */ public static DoubleValuesSource constant(double value) { @@ -427,17 +395,18 @@ private static class ConstantValuesSource extends DoubleValuesSource { private ConstantValuesSource(double value) { this.value = value; - this.doubleValues = new DoubleValues() { - @Override - public double doubleValue() { - return value; - } + this.doubleValues = + new DoubleValues() { + @Override + public double doubleValue() { + return value; + } - @Override - public boolean advanceExact(int doc) { - return true; - } - }; + @Override + public boolean advanceExact(int doc) { + return true; + } + }; } @Override @@ -472,10 +441,8 @@ public int hashCode() { @Override public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; ConstantValuesSource that = (ConstantValuesSource) o; return Double.compare(that.value, value) == 0; } @@ -489,12 +456,9 @@ public String toString() { /** * Returns a DoubleValues instance that wraps scores returned by a Scorer. * - *

- * Note: If you intend to call {@link Scorable#score()} on the provided - * {@code scorer} + *

Note: If you intend to call {@link Scorable#score()} on the provided {@code scorer} * separately, you may want to consider wrapping the collector with {@link - * ScoreCachingWrappingScorer#wrap(LeafCollector)} to avoid computing the actual - * score multiple + * ScoreCachingWrappingScorer#wrap(LeafCollector)} to avoid computing the actual score multiple * times. */ public static DoubleValues fromScorer(Scorable scorer) { @@ -517,7 +481,8 @@ private static class FieldValuesSource extends DoubleValuesSource { final LongToDoubleFunction decoder; final Monotonicity monotonicity; - private FieldValuesSource(String field, LongToDoubleFunction decoder, Monotonicity monotonicity) { + private FieldValuesSource( + String field, LongToDoubleFunction decoder, Monotonicity monotonicity) { this.field = field; this.decoder = decoder; this.monotonicity = Objects.requireNonNull(monotonicity); @@ -525,12 +490,11 @@ private FieldValuesSource(String field, LongToDoubleFunction decoder, Monotonici @Override public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; FieldValuesSource that = (FieldValuesSource) o; - return Objects.equals(field, that.field) && Objects.equals(decoder, that.decoder) + return Objects.equals(field, that.field) + && Objects.equals(decoder, that.decoder) && monotonicity == that.monotonicity; } @@ -572,7 +536,10 @@ public int advanceShallow(int target) throws IOException { public float getMaxScore(int upTo) throws IOException { if (skipper != null && monotonicity != Monotonicity.NONE) { if (skipper.minDocID(0) <= upTo) { - long rawBound = (monotonicity == Monotonicity.INCREASING) ? skipper.maxValue(0) : skipper.minValue(0); + long rawBound = + (monotonicity == Monotonicity.INCREASING) + ? skipper.maxValue(0) + : skipper.minValue(0); return (float) decoder.applyAsDouble(rawBound); } } @@ -583,7 +550,10 @@ public float getMaxScore(int upTo) throws IOException { public float getMinScore(int upTo) throws IOException { if (skipper != null && monotonicity != Monotonicity.NONE) { if (skipper.minDocID(0) <= upTo) { - long rawBound = (monotonicity == Monotonicity.INCREASING) ? skipper.minValue(0) : skipper.maxValue(0); + long rawBound = + (monotonicity == Monotonicity.INCREASING) + ? skipper.minValue(0) + : skipper.maxValue(0); return (float) decoder.applyAsDouble(rawBound); } } @@ -608,8 +578,7 @@ public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreEx DoubleValues values = getValues(ctx, null); if (values.advanceExact(docId)) return Explanation.match(values.doubleValue(), this.toString()); - else - return Explanation.noMatch(this.toString()); + else return Explanation.noMatch(this.toString()); } @Override @@ -636,8 +605,7 @@ public boolean needsScores() { public String toString() { StringBuilder buffer = new StringBuilder("<"); buffer.append(getField()).append(">"); - if (reverse) - buffer.append("!"); + if (reverse) buffer.append("!"); return buffer.toString(); } @@ -746,10 +714,8 @@ private QueryDoubleValuesSource(Query query) { @Override public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; QueryDoubleValuesSource that = (QueryDoubleValuesSource) o; return Objects.equals(query, that.query); } @@ -797,12 +763,12 @@ private WeightDoubleValuesSource(Weight weight) { @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { Scorer scorer = weight.scorer(ctx); - if (scorer == null) - return DoubleValues.EMPTY; + if (scorer == null) return DoubleValues.EMPTY; return new DoubleValues() { private final TwoPhaseIterator tpi = scorer.twoPhaseIterator(); - private final DocIdSetIterator disi = (tpi == null) ? scorer.iterator() : tpi.approximation(); + private final DocIdSetIterator disi = + (tpi == null) ? scorer.iterator() : tpi.approximation(); private Boolean tpiMatch = null; // cache tpi.matches() @Override @@ -847,10 +813,8 @@ public DoubleValuesSource rewrite(IndexSearcher searcher) throws IOException { @Override public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; WeightDoubleValuesSource that = (WeightDoubleValuesSource) o; return Objects.equals(weight, that.weight); } diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java index f03e808e813a..0cec2c9fd677 100644 --- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java +++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java @@ -466,7 +466,9 @@ public void testMaxScoreMonotonicDecreasingFunction() throws Exception { IndexSearcher searcher = new IndexSearcher(reader); Query baseQuery = new TermQuery(new Term(TEXT_FIELD, "decreasing")); // Monotonically decreasing function: f(x) = 10000.0 / x (Monotonicity.DECREASING) - DoubleValuesSource valSource = DoubleValuesSource.fromField("val", (v) -> 10000.0 / v, DoubleValuesSource.Monotonicity.DECREASING); + DoubleValuesSource valSource = + DoubleValuesSource.fromField( + "val", (v) -> 10000.0 / v, DoubleValuesSource.Monotonicity.DECREASING); Query scriptQuery = new FunctionScoreQuery(baseQuery, valSource); LeafReaderContext ctx = reader.leaves().get(0); @@ -506,47 +508,68 @@ public void testCustomHomemadeIncreasingAndDecreasingFunction() throws Exception Query baseQuery = new TermQuery(new Term(TEXT_FIELD, "custom")); // Homemade custom DoubleValuesSource class: f(x) = sqrt(x) [Increasing] - DoubleValuesSource customIncreasing = new DoubleValuesSource() { - @Override - public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { - DoubleValues in = DoubleValuesSource.fromLongField("val").getValues(ctx, scores); - return new DoubleValues() { + DoubleValuesSource customIncreasing = + new DoubleValuesSource() { @Override - public double doubleValue() throws IOException { - return Math.sqrt(in.doubleValue()); + public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) + throws IOException { + DoubleValues in = DoubleValuesSource.fromLongField("val").getValues(ctx, scores); + return new DoubleValues() { + @Override + public double doubleValue() throws IOException { + return Math.sqrt(in.doubleValue()); + } + + @Override + public boolean advanceExact(int doc) throws IOException { + return in.advanceExact(doc); + } + + @Override + public int advanceShallow(int target) throws IOException { + return in.advanceShallow(target); + } + + @Override + public float getMaxScore(int upTo) throws IOException { + float innerMax = in.getMaxScore(upTo); + return Float.isInfinite(innerMax) + ? Float.POSITIVE_INFINITY + : (float) Math.sqrt(innerMax); + } + }; } @Override - public boolean advanceExact(int doc) throws IOException { - return in.advanceExact(doc); + public boolean needsScores() { + return false; } @Override - public int advanceShallow(int target) throws IOException { - return in.advanceShallow(target); + public boolean isCacheable(LeafReaderContext ctx) { + return true; } @Override - public float getMaxScore(int upTo) throws IOException { - float innerMax = in.getMaxScore(upTo); - return Float.isInfinite(innerMax) ? Float.POSITIVE_INFINITY : (float) Math.sqrt(innerMax); + public DoubleValuesSource rewrite(IndexSearcher searcher) { + return this; } - }; - } - @Override - public boolean needsScores() { return false; } - @Override - public boolean isCacheable(LeafReaderContext ctx) { return true; } - @Override - public DoubleValuesSource rewrite(IndexSearcher searcher) { return this; } - @Override - public boolean equals(Object o) { return o == this; } - @Override - public int hashCode() { return System.identityHashCode(this); } - @Override - public String toString() { return "customSqrt(val)"; } - }; + @Override + public boolean equals(Object o) { + return o == this; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "customSqrt(val)"; + } + }; Query q = new FunctionScoreQuery(baseQuery, customIncreasing); LeafReaderContext ctx = reader.leaves().get(0);