You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by jp...@apache.org on 2023/09/12 13:23:33 UTC
[lucene] branch main updated: Reduce the overhead of ImpactsDISI. (#12490)
This is an automated email from the ASF dual-hosted git repository.
jpountz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/lucene.git
The following commit(s) were added to refs/heads/main by this push:
new 37a42219fc7 Reduce the overhead of ImpactsDISI. (#12490)
37a42219fc7 is described below
commit 37a42219fc7e9b2807e69827b1709fe056b7764e
Author: Adrien Grand <jp...@gmail.com>
AuthorDate: Tue Sep 12 15:23:28 2023 +0200
Reduce the overhead of ImpactsDISI. (#12490)
`ImpactsDISI` is nice: you give it an `ImpactsEnum`, typically coming from the
`PostingsFormat` and it will automatically skip hits whose score cannot be
greater than the minimum competitive score. This is the class that yields 10x
or more speedups on top-level `TermQuery`s compared to exhaustive evaluation.
However, when nested under a disjunction or a conjunction, `ImpactsDISI`
typically adds more overhead than it enables skipping. The reason is that on a
disjunction `a OR b`, the minimum competitive score of `a` is the minimum score
for the disjunction minus the maximum score of `b`. While this sort of
propagation of minimum competitive scores down the query tree sometimes helps,
it does hurt more than it helps on average, because `ImpactsDISI` adds quite
some overhead and the per-clauses minimum scores are usually so low that they
don't actually enable skipping hits. I looked into reducing this overhead, but
a big part of it is the additional virtual call, so the only way to get rid of
this overhead is to not wrap with an `ImpactsDISI` at all.
This means that scorers need a way to know whether they are producing the
top-level score, or whether they are producing a partial score that then gets
combined into the top-level score. Term queries would then only wrap with
`ImpactsDISI` when they produce the top-level score. Note that this does not
only include top-level term queries, but also conjunctions that have a single
scoring clause (`a #b`) or combinations of a term query and one or more
prohibited clauses (`a -b`).
---
lucene/CHANGES.txt | 2 +
.../org/apache/lucene/document/FeatureQuery.java | 55 +++--
.../lucene/search/BlockMaxConjunctionScorer.java | 3 -
.../org/apache/lucene/search/BlockMaxDISI.java | 94 --------
.../lucene/search/Boolean2ScorerSupplier.java | 43 +++-
.../apache/lucene/search/ConstantScoreScorer.java | 4 +
.../apache/lucene/search/DisjunctionMaxQuery.java | 69 ++++--
.../apache/lucene/search/DisjunctionMaxScorer.java | 10 +-
.../apache/lucene/search/DisjunctionScorer.java | 17 +-
.../apache/lucene/search/ExactPhraseMatcher.java | 9 +-
.../java/org/apache/lucene/search/ImpactsDISI.java | 46 +---
.../apache/lucene/search/MaxScoreBulkScorer.java | 9 +-
.../org/apache/lucene/search/MaxScoreCache.java | 21 +-
.../lucene/search/MaxScoreSumPropagator.java | 191 ---------------
.../org/apache/lucene/search/PhraseScorer.java | 6 +-
.../org/apache/lucene/search/ReqOptSumScorer.java | 8 -
.../org/apache/lucene/search/ScorerSupplier.java | 10 +
.../apache/lucene/search/SloppyPhraseMatcher.java | 2 +-
.../org/apache/lucene/search/SynonymQuery.java | 12 +-
.../java/org/apache/lucene/search/TermQuery.java | 57 ++++-
.../java/org/apache/lucene/search/TermScorer.java | 33 ++-
.../java/org/apache/lucene/search/WANDScorer.java | 24 +-
.../src/java/org/apache/lucene/search/Weight.java | 10 +-
.../src/java/org/apache/lucene/util/MathUtil.java | 25 ++
.../lucene/search/TestBoolean2ScorerSupplier.java | 91 ++++++++
.../lucene/search/TestMaxScoreSumPropagator.java | 255 ---------------------
.../apache/lucene/search/TestReqOptSumScorer.java | 12 +-
.../org/apache/lucene/search/TestWANDScorer.java | 110 ++++-----
.../lucene/search/join/TestBlockJoinScorer.java | 13 +-
.../lucene/tests/search/AssertingScorer.java | 11 +-
.../lucene/tests/search/AssertingWeight.java | 15 +-
.../org/apache/lucene/tests/search/CheckHits.java | 21 +-
32 files changed, 508 insertions(+), 780 deletions(-)
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 92a595b9804..a0bdaf972b1 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -186,6 +186,8 @@ Optimizations
* GITHUB##12371: Lazy computation of similarity score during initializeFromGraph (Jack Wang)
+* GITHUB#12490: Faster computation of top-k hits on boolean queries. (Adrien Grand)
+
Changes in runtime behavior
---------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/FeatureQuery.java b/lucene/core/src/java/org/apache/lucene/document/FeatureQuery.java
index 8b1ea8c4725..8c2f16fc110 100644
--- a/lucene/core/src/java/org/apache/lucene/document/FeatureQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/FeatureQuery.java
@@ -25,15 +25,16 @@ import org.apache.lucene.index.PostingsEnum;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.Explanation;
-import org.apache.lucene.search.ImpactsDISI;
import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.LeafSimScorer;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryVisitor;
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.TermScorer;
import org.apache.lucene.search.Weight;
import org.apache.lucene.search.similarities.Similarity.SimScorer;
import org.apache.lucene.util.BytesRef;
@@ -118,50 +119,48 @@ final class FeatureQuery extends Query {
}
@Override
- public Scorer scorer(LeafReaderContext context) throws IOException {
+ public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
+ final Weight thisWeight = this;
Terms terms = Terms.getTerms(context.reader(), fieldName);
TermsEnum termsEnum = terms.iterator();
if (termsEnum.seekExact(new BytesRef(featureName)) == false) {
return null;
}
+ final int docFreq = termsEnum.docFreq();
- final SimScorer scorer = function.scorer(boost);
- final ImpactsEnum impacts = termsEnum.impacts(PostingsEnum.FREQS);
- final ImpactsDISI impactsDisi = new ImpactsDISI(impacts, impacts, scorer);
-
- return new Scorer(this) {
-
- @Override
- public int docID() {
- return impacts.docID();
- }
-
- @Override
- public float score() throws IOException {
- return scorer.score(impacts.freq(), 1L);
- }
+ return new ScorerSupplier() {
- @Override
- public DocIdSetIterator iterator() {
- return impactsDisi;
- }
+ private boolean topLevelScoringClause = false;
@Override
- public int advanceShallow(int target) throws IOException {
- return impactsDisi.advanceShallow(target);
+ public Scorer get(long leadCost) throws IOException {
+ final SimScorer scorer = function.scorer(boost);
+ final LeafSimScorer simScorer =
+ new LeafSimScorer(scorer, context.reader(), fieldName, false);
+ final ImpactsEnum impacts = termsEnum.impacts(PostingsEnum.FREQS);
+ return new TermScorer(thisWeight, impacts, simScorer, topLevelScoringClause);
}
@Override
- public float getMaxScore(int upTo) throws IOException {
- return impactsDisi.getMaxScore(upTo);
+ public long cost() {
+ return docFreq;
}
@Override
- public void setMinCompetitiveScore(float minScore) {
- impactsDisi.setMinCompetitiveScore(minScore);
+ public void setTopLevelScoringClause() throws IOException {
+ topLevelScoringClause = true;
}
};
}
+
+ @Override
+ public Scorer scorer(LeafReaderContext context) throws IOException {
+ ScorerSupplier supplier = scorerSupplier(context);
+ if (supplier == null) {
+ return null;
+ }
+ return supplier.get(Long.MAX_VALUE);
+ }
};
}
diff --git a/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionScorer.java b/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionScorer.java
index 78107267c1f..127d6346edd 100644
--- a/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionScorer.java
@@ -31,7 +31,6 @@ final class BlockMaxConjunctionScorer extends Scorer {
final Scorer[] scorers;
final DocIdSetIterator[] approximations;
final TwoPhaseIterator[] twoPhases;
- final MaxScoreSumPropagator maxScorePropagator;
float minScore;
/** Create a new {@link BlockMaxConjunctionScorer} from scoring clauses. */
@@ -40,7 +39,6 @@ final class BlockMaxConjunctionScorer extends Scorer {
this.scorers = scorersList.toArray(new Scorer[scorersList.size()]);
// Sort scorer by cost
Arrays.sort(this.scorers, Comparator.comparingLong(s -> s.iterator().cost()));
- this.maxScorePropagator = new MaxScoreSumPropagator(Arrays.asList(scorers));
this.approximations = new DocIdSetIterator[scorers.length];
List<TwoPhaseIterator> twoPhaseList = new ArrayList<>();
@@ -237,7 +235,6 @@ final class BlockMaxConjunctionScorer extends Scorer {
@Override
public void setMinCompetitiveScore(float score) throws IOException {
minScore = score;
- maxScorePropagator.setMinCompetitiveScore(score);
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/BlockMaxDISI.java b/lucene/core/src/java/org/apache/lucene/search/BlockMaxDISI.java
deleted file mode 100644
index 2f59d46acd8..00000000000
--- a/lucene/core/src/java/org/apache/lucene/search/BlockMaxDISI.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.search;
-
-import java.io.IOException;
-
-/**
- * {@link DocIdSetIterator} that skips non-competitive docs by checking the max score of the
- * provided {@link Scorer} for the current block. Call {@link #setMinCompetitiveScore(float)} in
- * order to give this iterator the ability to skip low-scoring documents.
- *
- * @lucene.internal
- */
-public class BlockMaxDISI extends DocIdSetIterator {
- protected final Scorer scorer;
- private final DocIdSetIterator in;
- private float minScore;
- private float maxScore;
- private int upTo = -1;
-
- public BlockMaxDISI(DocIdSetIterator iterator, Scorer scorer) {
- this.in = iterator;
- this.scorer = scorer;
- }
-
- @Override
- public int docID() {
- return in.docID();
- }
-
- @Override
- public int nextDoc() throws IOException {
- return advance(docID() + 1);
- }
-
- @Override
- public int advance(int target) throws IOException {
- int doc = advanceImpacts(target);
- return in.advance(doc);
- }
-
- @Override
- public long cost() {
- return in.cost();
- }
-
- public void setMinCompetitiveScore(float minScore) {
- this.minScore = minScore;
- }
-
- private void moveToNextBlock(int target) throws IOException {
- upTo = scorer.advanceShallow(target);
- maxScore = scorer.getMaxScore(upTo);
- }
-
- private int advanceImpacts(int target) throws IOException {
- if (minScore == -1 || target == NO_MORE_DOCS) {
- return target;
- }
-
- if (target > upTo) {
- moveToNextBlock(target);
- }
-
- while (true) {
- if (maxScore >= minScore) {
- return target;
- }
-
- if (upTo == NO_MORE_DOCS) {
- return NO_MORE_DOCS;
- }
-
- target = upTo + 1;
-
- moveToNextBlock(target);
- }
- }
-}
diff --git a/lucene/core/src/java/org/apache/lucene/search/Boolean2ScorerSupplier.java b/lucene/core/src/java/org/apache/lucene/search/Boolean2ScorerSupplier.java
index bdf085d4669..56b676b88cb 100644
--- a/lucene/core/src/java/org/apache/lucene/search/Boolean2ScorerSupplier.java
+++ b/lucene/core/src/java/org/apache/lucene/search/Boolean2ScorerSupplier.java
@@ -34,6 +34,7 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
private final ScoreMode scoreMode;
private final int minShouldMatch;
private long cost = -1;
+ private boolean topLevelScoringClause;
Boolean2ScorerSupplier(
Weight weight,
@@ -83,6 +84,20 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
}
}
+ @Override
+ public void setTopLevelScoringClause() throws IOException {
+ topLevelScoringClause = true;
+ if (subs.get(Occur.SHOULD).size() + subs.get(Occur.MUST).size() == 1) {
+ // If there is a single scoring clause, propagate the call.
+ for (ScorerSupplier ss : subs.get(Occur.SHOULD)) {
+ ss.setTopLevelScoringClause();
+ }
+ for (ScorerSupplier ss : subs.get(Occur.MUST)) {
+ ss.setTopLevelScoringClause();
+ }
+ }
+ }
+
@Override
public long cost() {
if (cost == -1) {
@@ -113,7 +128,7 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
// pure conjunction
if (subs.get(Occur.SHOULD).isEmpty()) {
return excl(
- req(subs.get(Occur.FILTER), subs.get(Occur.MUST), leadCost),
+ req(subs.get(Occur.FILTER), subs.get(Occur.MUST), leadCost, topLevelScoringClause),
subs.get(Occur.MUST_NOT),
leadCost);
}
@@ -121,7 +136,7 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
// pure disjunction
if (subs.get(Occur.FILTER).isEmpty() && subs.get(Occur.MUST).isEmpty()) {
return excl(
- opt(subs.get(Occur.SHOULD), minShouldMatch, scoreMode, leadCost),
+ opt(subs.get(Occur.SHOULD), minShouldMatch, scoreMode, leadCost, topLevelScoringClause),
subs.get(Occur.MUST_NOT),
leadCost);
}
@@ -134,19 +149,19 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
if (minShouldMatch > 0) {
Scorer req =
excl(
- req(subs.get(Occur.FILTER), subs.get(Occur.MUST), leadCost),
+ req(subs.get(Occur.FILTER), subs.get(Occur.MUST), leadCost, false),
subs.get(Occur.MUST_NOT),
leadCost);
- Scorer opt = opt(subs.get(Occur.SHOULD), minShouldMatch, scoreMode, leadCost);
+ Scorer opt = opt(subs.get(Occur.SHOULD), minShouldMatch, scoreMode, leadCost, false);
return new ConjunctionScorer(weight, Arrays.asList(req, opt), Arrays.asList(req, opt));
} else {
assert scoreMode.needsScores();
return new ReqOptSumScorer(
excl(
- req(subs.get(Occur.FILTER), subs.get(Occur.MUST), leadCost),
+ req(subs.get(Occur.FILTER), subs.get(Occur.MUST), leadCost, false),
subs.get(Occur.MUST_NOT),
leadCost),
- opt(subs.get(Occur.SHOULD), minShouldMatch, scoreMode, leadCost),
+ opt(subs.get(Occur.SHOULD), minShouldMatch, scoreMode, leadCost, false),
scoreMode);
}
}
@@ -158,7 +173,8 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
private Scorer req(
Collection<ScorerSupplier> requiredNoScoring,
Collection<ScorerSupplier> requiredScoring,
- long leadCost)
+ long leadCost,
+ boolean topLevelScoringClause)
throws IOException {
if (requiredNoScoring.size() + requiredScoring.size() == 1) {
Scorer req =
@@ -199,7 +215,7 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
Scorer scorer = s.get(leadCost);
scoringScorers.add(scorer);
}
- if (scoreMode == ScoreMode.TOP_SCORES && scoringScorers.size() > 1) {
+ if (scoreMode == ScoreMode.TOP_SCORES && scoringScorers.size() > 1 && topLevelScoringClause) {
Scorer blockMaxScorer = new BlockMaxConjunctionScorer(weight, scoringScorers);
if (requiredScorers.isEmpty()) {
return blockMaxScorer;
@@ -216,12 +232,17 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
if (prohibited.isEmpty()) {
return main;
} else {
- return new ReqExclScorer(main, opt(prohibited, 1, ScoreMode.COMPLETE_NO_SCORES, leadCost));
+ return new ReqExclScorer(
+ main, opt(prohibited, 1, ScoreMode.COMPLETE_NO_SCORES, leadCost, false));
}
}
private Scorer opt(
- Collection<ScorerSupplier> optional, int minShouldMatch, ScoreMode scoreMode, long leadCost)
+ Collection<ScorerSupplier> optional,
+ int minShouldMatch,
+ ScoreMode scoreMode,
+ long leadCost,
+ boolean topLevelScoringClause)
throws IOException {
if (optional.size() == 1) {
return optional.iterator().next().get(leadCost);
@@ -238,7 +259,7 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
//
// However, as WANDScorer uses more complex algorithm and data structure, we would like to
// still use DisjunctionSumScorer to handle exhaustive pure disjunctions, which may be faster
- if (scoreMode == ScoreMode.TOP_SCORES || minShouldMatch > 1) {
+ if ((scoreMode == ScoreMode.TOP_SCORES && topLevelScoringClause) || minShouldMatch > 1) {
return new WANDScorer(weight, optionalScorers, minShouldMatch, scoreMode);
} else {
return new DisjunctionSumScorer(weight, optionalScorers, scoreMode);
diff --git a/lucene/core/src/java/org/apache/lucene/search/ConstantScoreScorer.java b/lucene/core/src/java/org/apache/lucene/search/ConstantScoreScorer.java
index 759300ecb80..d7a3a812a51 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ConstantScoreScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ConstantScoreScorer.java
@@ -74,6 +74,8 @@ public final class ConstantScoreScorer extends Scorer {
super(weight);
this.score = score;
this.scoreMode = scoreMode;
+ // TODO: Only wrap when it is the top-level scoring clause? See
+ // ScorerSupplier#setTopLevelScoringClause
this.approximation =
scoreMode == ScoreMode.TOP_SCORES ? new DocIdSetIteratorWrapper(disi) : disi;
this.twoPhaseIterator = null;
@@ -95,6 +97,8 @@ public final class ConstantScoreScorer extends Scorer {
this.score = score;
this.scoreMode = scoreMode;
if (scoreMode == ScoreMode.TOP_SCORES) {
+ // TODO: Only wrap when it is the top-level scoring clause? See
+ // ScorerSupplier#setTopLevelScoringClause
this.approximation = new DocIdSetIteratorWrapper(twoPhaseIterator.approximation());
this.twoPhaseIterator =
new TwoPhaseIterator(this.approximation) {
diff --git a/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java b/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java
index 1ab4f3b5081..47e8b8f25e9 100644
--- a/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java
@@ -127,26 +127,69 @@ public final class DisjunctionMaxQuery extends Query implements Iterable<Query>
return MatchesUtils.fromSubMatches(mis);
}
- /** Create the scorer used to score our associated DisjunctionMaxQuery */
@Override
- public Scorer scorer(LeafReaderContext context) throws IOException {
- List<Scorer> scorers = new ArrayList<>();
+ public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
+ List<ScorerSupplier> scorerSuppliers = new ArrayList<>();
for (Weight w : weights) {
- // we will advance() subscorers
- Scorer subScorer = w.scorer(context);
- if (subScorer != null) {
- scorers.add(subScorer);
+ ScorerSupplier ss = w.scorerSupplier(context);
+ if (ss != null) {
+ scorerSuppliers.add(ss);
}
}
- if (scorers.isEmpty()) {
- // no sub-scorers had any documents
+
+ if (scorerSuppliers.isEmpty()) {
return null;
- } else if (scorers.size() == 1) {
- // only one sub-scorer in this segment
- return scorers.get(0);
+ } else if (scorerSuppliers.size() == 1) {
+ return scorerSuppliers.get(0);
} else {
- return new DisjunctionMaxScorer(this, tieBreakerMultiplier, scorers, scoreMode);
+ final Weight thisWeight = this;
+ return new ScorerSupplier() {
+
+ private long cost = -1;
+
+ @Override
+ public Scorer get(long leadCost) throws IOException {
+ List<Scorer> scorers = new ArrayList<>();
+ for (ScorerSupplier ss : scorerSuppliers) {
+ scorers.add(ss.get(leadCost));
+ }
+ return new DisjunctionMaxScorer(thisWeight, tieBreakerMultiplier, scorers, scoreMode);
+ }
+
+ @Override
+ public long cost() {
+ if (cost == -1) {
+ long cost = 0;
+ for (ScorerSupplier ss : scorerSuppliers) {
+ cost += ss.cost();
+ }
+ this.cost = cost;
+ }
+ return cost;
+ }
+
+ @Override
+ public void setTopLevelScoringClause() throws IOException {
+ if (tieBreakerMultiplier == 0) {
+ for (ScorerSupplier ss : scorerSuppliers) {
+ // sub scorers need to be able to skip too as calls to setMinCompetitiveScore get
+ // propagated
+ ss.setTopLevelScoringClause();
+ }
+ }
+ }
+ };
+ }
+ }
+
+ @Override
+ public Scorer scorer(LeafReaderContext context) throws IOException {
+ ScorerSupplier supplier = scorerSupplier(context);
+ if (supplier == null) {
+ return null;
}
+ supplier.setTopLevelScoringClause();
+ return supplier.get(Long.MAX_VALUE);
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxScorer.java b/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxScorer.java
index affe77193b5..a43077a725e 100644
--- a/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxScorer.java
@@ -75,7 +75,10 @@ final class DisjunctionMaxScorer extends DisjunctionScorer {
@Override
public int advanceShallow(int target) throws IOException {
- return disjunctionBlockPropagator.advanceShallow(target);
+ if (disjunctionBlockPropagator != null) {
+ return disjunctionBlockPropagator.advanceShallow(target);
+ }
+ return super.advanceShallow(target);
}
@Override
@@ -108,8 +111,9 @@ final class DisjunctionMaxScorer extends DisjunctionScorer {
@Override
public void setMinCompetitiveScore(float minScore) throws IOException {
- getBlockMaxApprox().setMinCompetitiveScore(minScore);
- disjunctionBlockPropagator.setMinCompetitiveScore(minScore);
+ if (disjunctionBlockPropagator != null) {
+ disjunctionBlockPropagator.setMinCompetitiveScore(minScore);
+ }
if (tieBreakerMultiplier == 0) {
// TODO: we could even remove some scorers from the priority queue?
for (Scorer scorer : subScorers) {
diff --git a/lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java b/lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java
index e7c75420b86..74fb42f01b3 100644
--- a/lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java
@@ -29,7 +29,6 @@ abstract class DisjunctionScorer extends Scorer {
private final DisiPriorityQueue subScorers;
private final DocIdSetIterator approximation;
- private final BlockMaxDISI blockMaxApprox;
private final TwoPhase twoPhase;
protected DisjunctionScorer(Weight weight, List<Scorer> subScorers, ScoreMode scoreMode)
@@ -44,17 +43,7 @@ abstract class DisjunctionScorer extends Scorer {
this.subScorers.add(w);
}
this.needsScores = scoreMode != ScoreMode.COMPLETE_NO_SCORES;
- if (scoreMode == ScoreMode.TOP_SCORES) {
- for (Scorer scorer : subScorers) {
- scorer.advanceShallow(0);
- }
- this.blockMaxApprox =
- new BlockMaxDISI(new DisjunctionDISIApproximation(this.subScorers), this);
- this.approximation = blockMaxApprox;
- } else {
- this.approximation = new DisjunctionDISIApproximation(this.subScorers);
- this.blockMaxApprox = null;
- }
+ this.approximation = new DisjunctionDISIApproximation(this.subScorers);
boolean hasApproximation = false;
float sumMatchCost = 0;
@@ -176,10 +165,6 @@ abstract class DisjunctionScorer extends Scorer {
return subScorers.top().doc;
}
- BlockMaxDISI getBlockMaxApprox() {
- return blockMaxApprox;
- }
-
DisiWrapper getSubMatches() throws IOException {
if (twoPhase == null) {
return subScorers.topList();
diff --git a/lucene/core/src/java/org/apache/lucene/search/ExactPhraseMatcher.java b/lucene/core/src/java/org/apache/lucene/search/ExactPhraseMatcher.java
index b35f2d868dc..f6bcadb4b8f 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ExactPhraseMatcher.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ExactPhraseMatcher.java
@@ -67,12 +67,15 @@ public final class ExactPhraseMatcher extends PhraseMatcher {
final ImpactsSource impactsSource =
mergeImpacts(Arrays.stream(postings).map(p -> p.impacts).toArray(ImpactsEnum[]::new));
+ this.impactsApproximation =
+ new ImpactsDISI(approximation, new MaxScoreCache(impactsSource, scorer));
if (scoreMode == ScoreMode.TOP_SCORES) {
- this.approximation =
- this.impactsApproximation = new ImpactsDISI(approximation, impactsSource, scorer);
+ // TODO: only do this when this is the top-level scoring clause
+ // (ScorerSupplier#setTopLevelScoringClause) to save the overhead of wrapping with ImpactsDISI
+ // when it would not help
+ this.approximation = impactsApproximation;
} else {
this.approximation = approximation;
- this.impactsApproximation = new ImpactsDISI(approximation, impactsSource, scorer);
}
List<PostingsAndPosition> postingsAndPositions = new ArrayList<>();
diff --git a/lucene/core/src/java/org/apache/lucene/search/ImpactsDISI.java b/lucene/core/src/java/org/apache/lucene/search/ImpactsDISI.java
index 20efacc25c9..5ab19747246 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ImpactsDISI.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ImpactsDISI.java
@@ -17,10 +17,6 @@
package org.apache.lucene.search;
import java.io.IOException;
-import org.apache.lucene.index.Impacts;
-import org.apache.lucene.index.ImpactsEnum;
-import org.apache.lucene.index.ImpactsSource;
-import org.apache.lucene.search.similarities.Similarity.SimScorer;
/**
* {@link DocIdSetIterator} that skips non-competitive docs thanks to the indexed impacts. Call
@@ -32,7 +28,6 @@ import org.apache.lucene.search.similarities.Similarity.SimScorer;
public final class ImpactsDISI extends DocIdSetIterator {
private final DocIdSetIterator in;
- private final ImpactsSource impactsSource;
private final MaxScoreCache maxScoreCache;
private float minCompetitiveScore = 0;
private int upTo = DocIdSetIterator.NO_MORE_DOCS;
@@ -41,14 +36,17 @@ public final class ImpactsDISI extends DocIdSetIterator {
/**
* Sole constructor.
*
- * @param in wrapped iterator
- * @param impactsSource source of impacts
- * @param scorer scorer
+ * @param in the iterator, typically an ImpactsEnum
+ * @param maxScoreCache the cache of maximum scores, typically computed from the same ImpactsEnum
*/
- public ImpactsDISI(DocIdSetIterator in, ImpactsSource impactsSource, SimScorer scorer) {
+ public ImpactsDISI(DocIdSetIterator in, MaxScoreCache maxScoreCache) {
this.in = in;
- this.impactsSource = impactsSource;
- this.maxScoreCache = new MaxScoreCache(impactsSource, scorer);
+ this.maxScoreCache = maxScoreCache;
+ }
+
+ /** Get the {@link MaxScoreCache}. */
+ public MaxScoreCache getMaxScoreCache() {
+ return maxScoreCache;
}
/**
@@ -67,28 +65,6 @@ public final class ImpactsDISI extends DocIdSetIterator {
}
}
- /**
- * Implement the contract of {@link Scorer#advanceShallow(int)} based on the wrapped {@link
- * ImpactsEnum}.
- *
- * @see Scorer#advanceShallow(int)
- */
- public int advanceShallow(int target) throws IOException {
- impactsSource.advanceShallow(target);
- Impacts impacts = impactsSource.getImpacts();
- return impacts.getDocIdUpTo(0);
- }
-
- /**
- * Implement the contract of {@link Scorer#getMaxScore(int)} based on the wrapped {@link
- * ImpactsEnum} and {@link Scorer}.
- *
- * @see Scorer#getMaxScore(int)
- */
- public float getMaxScore(int upTo) throws IOException {
- return maxScoreCache.getMaxScore(upTo);
- }
-
private int advanceTarget(int target) throws IOException {
if (target <= upTo) {
// we are still in the current block, which is considered competitive
@@ -96,7 +72,7 @@ public final class ImpactsDISI extends DocIdSetIterator {
return target;
}
- upTo = advanceShallow(target);
+ upTo = maxScoreCache.advanceShallow(target);
maxScore = maxScoreCache.getMaxScoreForLevelZero();
while (true) {
@@ -118,7 +94,7 @@ public final class ImpactsDISI extends DocIdSetIterator {
} else {
target = skipUpTo + 1;
}
- upTo = advanceShallow(target);
+ upTo = maxScoreCache.advanceShallow(target);
maxScore = maxScoreCache.getMaxScoreForLevelZero();
}
}
diff --git a/lucene/core/src/java/org/apache/lucene/search/MaxScoreBulkScorer.java b/lucene/core/src/java/org/apache/lucene/search/MaxScoreBulkScorer.java
index 0ef5a1d07bd..4cc14cdd0b3 100644
--- a/lucene/core/src/java/org/apache/lucene/search/MaxScoreBulkScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/MaxScoreBulkScorer.java
@@ -22,6 +22,7 @@ import java.util.Comparator;
import java.util.List;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.MathUtil;
final class MaxScoreBulkScorer extends BulkScorer {
@@ -37,7 +38,6 @@ final class MaxScoreBulkScorer extends BulkScorer {
// Index of the first essential scorer, ie. essentialQueue contains all scorers from
// allScorers[firstEssentialScorer:]. All scorers below this index are non-essential.
private int firstEssentialScorer;
- private final MaxScoreSumPropagator maxScorePropagator;
private final long cost;
private float minCompetitiveScore;
private boolean minCompetitiveScoreUpdated;
@@ -59,7 +59,6 @@ final class MaxScoreBulkScorer extends BulkScorer {
allScorers[i++] = w;
}
this.cost = cost;
- maxScorePropagator = new MaxScoreSumPropagator(scorers);
essentialQueue = new DisiPriorityQueue(allScorers.length);
maxScoreSums = new double[allScorers.length];
}
@@ -227,7 +226,8 @@ final class MaxScoreBulkScorer extends BulkScorer {
throws IOException {
double score = essentialScore;
for (int i = firstEssentialScorer - 1; i >= 0; --i) {
- float maxPossibleScore = maxScorePropagator.scoreSumUpperBound(score + maxScoreSums[i]);
+ float maxPossibleScore =
+ (float) MathUtil.sumUpperBound(score + maxScoreSums[i], allScorers.length);
if (maxPossibleScore < minCompetitiveScore) {
// Hit is not competitive.
return;
@@ -268,7 +268,7 @@ final class MaxScoreBulkScorer extends BulkScorer {
final DisiWrapper w = scratch[i];
double newMaxScoreSum = maxScoreSum + w.maxWindowScore;
float maxScoreSumFloat =
- MaxScoreSumPropagator.scoreSumUpperBound(newMaxScoreSum, firstEssentialScorer + 1);
+ (float) MathUtil.sumUpperBound(newMaxScoreSum, firstEssentialScorer + 1);
if (maxScoreSumFloat < minCompetitiveScore) {
maxScoreSum = newMaxScoreSum;
allScorers[firstEssentialScorer] = w;
@@ -324,7 +324,6 @@ final class MaxScoreBulkScorer extends BulkScorer {
@Override
public void setMinCompetitiveScore(float minScore) throws IOException {
MaxScoreBulkScorer.this.minCompetitiveScore = minScore;
- maxScorePropagator.setMinCompetitiveScore(minScore);
minCompetitiveScoreUpdated = true;
}
}
diff --git a/lucene/core/src/java/org/apache/lucene/search/MaxScoreCache.java b/lucene/core/src/java/org/apache/lucene/search/MaxScoreCache.java
index 70a10ee0561..2d38370e86a 100644
--- a/lucene/core/src/java/org/apache/lucene/search/MaxScoreCache.java
+++ b/lucene/core/src/java/org/apache/lucene/search/MaxScoreCache.java
@@ -31,7 +31,7 @@ import org.apache.lucene.util.ArrayUtil;
*
* @lucene.internal
*/
-final class MaxScoreCache {
+public final class MaxScoreCache {
private final ImpactsSource impactsSource;
private final SimScorer scorer;
@@ -48,6 +48,18 @@ final class MaxScoreCache {
maxScoreCacheUpTo = new int[0];
}
+ /**
+ * Implement the contract of {@link Scorer#advanceShallow(int)} based on the wrapped {@link
+ * ImpactsSource}.
+ *
+ * @see Scorer#advanceShallow(int)
+ */
+ public int advanceShallow(int target) throws IOException {
+ impactsSource.advanceShallow(target);
+ Impacts impacts = impactsSource.getImpacts();
+ return impacts.getDocIdUpTo(0);
+ }
+
private void ensureCacheSize(int size) {
if (maxScoreCache.length < size) {
int oldLength = maxScoreCache.length;
@@ -65,7 +77,12 @@ final class MaxScoreCache {
return maxScore;
}
- float getMaxScore(int upTo) throws IOException {
+ /**
+ * Return the maximum score up to upTo included.
+ *
+ * @see Scorer#getMaxScore(int)
+ */
+ public float getMaxScore(int upTo) throws IOException {
final int level = getLevel(upTo);
if (level == -1) {
return globalMaxScore;
diff --git a/lucene/core/src/java/org/apache/lucene/search/MaxScoreSumPropagator.java b/lucene/core/src/java/org/apache/lucene/search/MaxScoreSumPropagator.java
deleted file mode 100644
index 676dee04e4d..00000000000
--- a/lucene/core/src/java/org/apache/lucene/search/MaxScoreSumPropagator.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * 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.search;
-
-import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
-
-import java.io.IOException;
-import java.util.Collection;
-import org.apache.lucene.util.InPlaceMergeSorter;
-import org.apache.lucene.util.MathUtil;
-
-/**
- * Utility class to propagate scoring information in {@link BooleanQuery}, which compute the score
- * as the sum of the scores of its matching clauses. This helps propagate information about the
- * maximum produced score
- */
-final class MaxScoreSumPropagator {
-
- /**
- * Return an array which, at index i, stores the sum of all entries of {@code v} except the one at
- * index i.
- */
- private static double[] computeSumOfComplement(float[] v) {
- // We do not use subtraction on purpose because it would defeat the
- // upperbound formula that we use for sums.
- // Naive approach would be O(n^2), but we can do O(n) by computing the
- // sum for i<j and i>j and then sum them.
- double[] sum1 = new double[v.length];
- for (int i = 1; i < sum1.length; ++i) {
- sum1[i] = sum1[i - 1] + v[i - 1];
- }
-
- double[] sum2 = new double[v.length];
- for (int i = sum2.length - 2; i >= 0; --i) {
- sum2[i] = sum2[i + 1] + v[i + 1];
- }
-
- double[] result = new double[v.length];
- for (int i = 0; i < result.length; ++i) {
- result[i] = sum1[i] + sum2[i];
- }
- return result;
- }
-
- private final int numClauses;
- private final Scorer[] scorers;
- private final double[] sumOfOtherMaxScores;
-
- MaxScoreSumPropagator(Collection<? extends Scorer> scorerList) throws IOException {
- numClauses = scorerList.size();
- scorers = scorerList.toArray(new Scorer[numClauses]);
-
- // We'll need max scores multiple times so we cache them
- float[] maxScores = new float[numClauses];
- for (int i = 0; i < numClauses; ++i) {
- scorers[i].advanceShallow(0);
- maxScores[i] = scorers[i].getMaxScore(NO_MORE_DOCS);
- }
- // Sort by decreasing max score
- new InPlaceMergeSorter() {
- @Override
- protected void swap(int i, int j) {
- Scorer tmp = scorers[i];
- scorers[i] = scorers[j];
- scorers[j] = tmp;
- float tmpF = maxScores[i];
- maxScores[i] = maxScores[j];
- maxScores[j] = tmpF;
- }
-
- @Override
- protected int compare(int i, int j) {
- return Float.compare(maxScores[j], maxScores[i]);
- }
- }.sort(0, scorers.length);
-
- sumOfOtherMaxScores = computeSumOfComplement(maxScores);
- }
-
- void advanceShallow(int target) throws IOException {
- for (Scorer s : scorers) {
- if (s.docID() < target) {
- s.advanceShallow(target);
- }
- }
- }
-
- float getMaxScore(int upTo) throws IOException {
- double maxScore = 0;
- for (Scorer s : scorers) {
- if (s.docID() <= upTo) {
- maxScore += s.getMaxScore(upTo);
- }
- }
- return scoreSumUpperBound(maxScore);
- }
-
- void setMinCompetitiveScore(float minScore) throws IOException {
- if (minScore == 0) {
- return;
- }
- // A double that is less than 'minScore' might still be converted to 'minScore'
- // when casted to a float, so we go to the previous float to avoid this issue
- float minScoreDown = Math.nextDown(minScore);
- for (int i = 0; i < numClauses; ++i) {
- double sumOfOtherMaxScores = this.sumOfOtherMaxScores[i];
- float minCompetitiveScore = getMinCompetitiveScore(minScoreDown, sumOfOtherMaxScores);
- if (minCompetitiveScore <= 0) {
- // given that scorers are sorted by decreasing max score, next scorers will
- // have 0 as a minimum competitive score too
- break;
- }
- scorers[i].setMinCompetitiveScore(minCompetitiveScore);
- }
- }
-
- /**
- * Return the minimum score that a Scorer must produce in order for a hit to be competitive.
- *
- * <p>The way that boolean queries combine scores of their sub clauses together is by summing up
- * the float scores into a double and finally casting back that double back to a float. This
- * method undoes this operation by taking the float score sum and subtracting the sum of other
- * scores as a double as a first approximation of the minimum score that this clause must have.
- */
- private float getMinCompetitiveScore(float minScoreSum, double sumOfOtherMaxScores) {
- assert numClauses > 0;
-
- // We need to find a value 'minScore' so that 'minScore + sumOfOtherMaxScores <= minScoreSum'
- // TODO: is there an efficient way to find the greatest value that meets this requirement?
- float minScore = (float) (minScoreSum - sumOfOtherMaxScores);
- for (int iter = 0;
- minScore > 0 && scoreSumUpperBound(minScore + sumOfOtherMaxScores) > minScoreSum;
- ++iter) {
- // Important: use ulp of minScoreSum and not minScore to make sure that we
- // converge quickly.
- minScore -= Math.ulp(minScoreSum);
- // this should converge in at most two iterations:
- // - one because of the subtraction rounding error
- // - one because of the error introduced by sumUpperBound
- if (iter > 2) {
- throw new IllegalStateException(
- "Could not compute a minimum score for minScore="
- + minScore
- + ", minScoreSum="
- + minScoreSum
- + ", sumOfOtherMaxScores="
- + sumOfOtherMaxScores
- + ", numClauses="
- + numClauses);
- }
- }
- return Math.max(minScore, 0f);
- }
-
- float scoreSumUpperBound(double sum) {
- return scoreSumUpperBound(sum, numClauses);
- }
-
- static float scoreSumUpperBound(double sum, int numClauses) {
- if (numClauses <= 2) {
- // When there are only two clauses, the sum is always the same regardless
- // of the order.
- return (float) sum;
- }
-
- // The error of sums depends on the order in which values are summed up. In
- // order to avoid this issue, we compute an upper bound of the value that
- // the sum may take. If the max relative error is b, then it means that two
- // sums are always within 2*b of each other.
- // For conjunctions, we could skip this error factor since the order in which
- // scores are summed up is predictable, but in practice, this wouldn't help
- // much since the delta that is introduced by this error factor is usually
- // cancelled by the float cast.
- double b = MathUtil.sumRelativeErrorBound(numClauses);
- return (float) ((1.0 + 2 * b) * sum);
- }
-}
diff --git a/lucene/core/src/java/org/apache/lucene/search/PhraseScorer.java b/lucene/core/src/java/org/apache/lucene/search/PhraseScorer.java
index f1a4b0bbed0..4925de479f8 100644
--- a/lucene/core/src/java/org/apache/lucene/search/PhraseScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/PhraseScorer.java
@@ -23,6 +23,7 @@ class PhraseScorer extends Scorer {
final DocIdSetIterator approximation;
final ImpactsDISI impactsApproximation;
+ final MaxScoreCache maxScoreCache;
final PhraseMatcher matcher;
final ScoreMode scoreMode;
private final LeafSimScorer simScorer;
@@ -39,6 +40,7 @@ class PhraseScorer extends Scorer {
this.matchCost = matcher.getMatchCost();
this.approximation = matcher.approximation();
this.impactsApproximation = matcher.impactsApproximation();
+ this.maxScoreCache = impactsApproximation.getMaxScoreCache();
}
@Override
@@ -94,12 +96,12 @@ class PhraseScorer extends Scorer {
@Override
public int advanceShallow(int target) throws IOException {
- return impactsApproximation.advanceShallow(target);
+ return maxScoreCache.advanceShallow(target);
}
@Override
public float getMaxScore(int upTo) throws IOException {
- return impactsApproximation.getMaxScore(upTo);
+ return maxScoreCache.getMaxScore(upTo);
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/ReqOptSumScorer.java b/lucene/core/src/java/org/apache/lucene/search/ReqOptSumScorer.java
index 31e0fa31112..f4730288296 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ReqOptSumScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ReqOptSumScorer.java
@@ -20,7 +20,6 @@ import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
/**
@@ -36,7 +35,6 @@ class ReqOptSumScorer extends Scorer {
private final DocIdSetIterator approximation;
private final TwoPhaseIterator twoPhase;
- private final MaxScoreSumPropagator maxScorePropagator;
private float minScore = 0;
private float reqMaxScore;
private boolean optIsRequired;
@@ -53,11 +51,6 @@ class ReqOptSumScorer extends Scorer {
super(reqScorer.weight);
assert reqScorer != null;
assert optScorer != null;
- if (scoreMode == ScoreMode.TOP_SCORES) {
- this.maxScorePropagator = new MaxScoreSumPropagator(Arrays.asList(reqScorer, optScorer));
- } else {
- this.maxScorePropagator = null;
- }
this.reqScorer = reqScorer;
this.optScorer = optScorer;
@@ -303,7 +296,6 @@ class ReqOptSumScorer extends Scorer {
if (reqMaxScore < minScore) {
optIsRequired = true;
}
- maxScorePropagator.setMinCompetitiveScore(minScore);
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java b/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java
index e6af2d6b13c..8053ac94540 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java
@@ -42,4 +42,14 @@ public abstract class ScorerSupplier {
* @see DocIdSetIterator#cost
*/
public abstract long cost();
+
+ /**
+ * Inform this {@link ScorerSupplier} that its returned scorers produce scores that get passed to
+ * the collector, as opposed to partial scores that then need to get combined (e.g. summed up).
+ * Note that this method also gets called if scores are not requested, e.g. because the score mode
+ * is {@link ScoreMode#COMPLETE_NO_SCORES}, so implementations should look at both the score mode
+ * and this boolean to know whether to prepare for reacting to {@link
+ * Scorer#setMinCompetitiveScore(float)} calls.
+ */
+ public void setTopLevelScoringClause() throws IOException {}
}
diff --git a/lucene/core/src/java/org/apache/lucene/search/SloppyPhraseMatcher.java b/lucene/core/src/java/org/apache/lucene/search/SloppyPhraseMatcher.java
index 259ea8f7e57..ca70a52fbb7 100644
--- a/lucene/core/src/java/org/apache/lucene/search/SloppyPhraseMatcher.java
+++ b/lucene/core/src/java/org/apache/lucene/search/SloppyPhraseMatcher.java
@@ -133,7 +133,7 @@ public final class SloppyPhraseMatcher extends PhraseMatcher {
@Override
public void advanceShallow(int target) throws IOException {}
};
- impactsApproximation = new ImpactsDISI(approximation, impactsSource, scorer);
+ impactsApproximation = new ImpactsDISI(approximation, new MaxScoreCache(impactsSource, scorer));
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/SynonymQuery.java b/lucene/core/src/java/org/apache/lucene/search/SynonymQuery.java
index 7e314870f9c..2ab11ed495a 100644
--- a/lucene/core/src/java/org/apache/lucene/search/SynonymQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/SynonymQuery.java
@@ -333,9 +333,13 @@ public final class SynonymQuery extends Query {
boosts[i] = termBoosts.get(i);
}
ImpactsSource impactsSource = mergeImpacts(impacts.toArray(new ImpactsEnum[0]), boosts);
- ImpactsDISI impactsDisi = new ImpactsDISI(iterator, impactsSource, simScorer.getSimScorer());
+ MaxScoreCache maxScoreCache = new MaxScoreCache(impactsSource, simScorer.getSimScorer());
+ ImpactsDISI impactsDisi = new ImpactsDISI(iterator, maxScoreCache);
if (scoreMode == ScoreMode.TOP_SCORES) {
+ // TODO: only do this when this is the top-level scoring clause
+ // (ScorerSupplier#setTopLevelScoringClause) to save the overhead of wrapping with
+ // ImpactsDISI when it would not help
iterator = impactsDisi;
}
@@ -520,6 +524,7 @@ public final class SynonymQuery extends Query {
private final DisiPriorityQueue queue;
private final DocIdSetIterator iterator;
+ private final MaxScoreCache maxScoreCache;
private final ImpactsDISI impactsDisi;
private final LeafSimScorer simScorer;
@@ -532,6 +537,7 @@ public final class SynonymQuery extends Query {
super(weight);
this.queue = queue;
this.iterator = iterator;
+ this.maxScoreCache = impactsDisi.getMaxScoreCache();
this.impactsDisi = impactsDisi;
this.simScorer = simScorer;
}
@@ -562,12 +568,12 @@ public final class SynonymQuery extends Query {
@Override
public float getMaxScore(int upTo) throws IOException {
- return impactsDisi.getMaxScore(upTo);
+ return maxScoreCache.getMaxScore(upTo);
}
@Override
public int advanceShallow(int target) throws IOException {
- return impactsDisi.advanceShallow(target);
+ return maxScoreCache.advanceShallow(target);
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/TermQuery.java b/lucene/core/src/java/org/apache/lucene/search/TermQuery.java
index c7d5ac325b9..58251a3f6d7 100644
--- a/lucene/core/src/java/org/apache/lucene/search/TermQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/TermQuery.java
@@ -114,26 +114,59 @@ public class TermQuery extends Query {
}
@Override
- public Scorer scorer(LeafReaderContext context) throws IOException {
+ public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
assert termStates == null || termStates.wasBuiltFor(ReaderUtil.getTopLevelContext(context))
: "The top-reader used to create Weight is not the same as the current reader's top-reader ("
+ ReaderUtil.getTopLevelContext(context);
- ;
+
final TermsEnum termsEnum = getTermsEnum(context);
if (termsEnum == null) {
return null;
}
- LeafSimScorer scorer =
- new LeafSimScorer(simScorer, context.reader(), term.field(), scoreMode.needsScores());
- if (scoreMode == ScoreMode.TOP_SCORES) {
- return new TermScorer(this, termsEnum.impacts(PostingsEnum.FREQS), scorer);
- } else {
- return new TermScorer(
- this,
- termsEnum.postings(
- null, scoreMode.needsScores() ? PostingsEnum.FREQS : PostingsEnum.NONE),
- scorer);
+ final int docFreq = termsEnum.docFreq();
+
+ return new ScorerSupplier() {
+
+ private boolean topLevelScoringClause = false;
+
+ @Override
+ public Scorer get(long leadCost) throws IOException {
+ LeafSimScorer scorer =
+ new LeafSimScorer(simScorer, context.reader(), term.field(), scoreMode.needsScores());
+ if (scoreMode == ScoreMode.TOP_SCORES) {
+ return new TermScorer(
+ TermWeight.this,
+ termsEnum.impacts(PostingsEnum.FREQS),
+ scorer,
+ topLevelScoringClause);
+ } else {
+ return new TermScorer(
+ TermWeight.this,
+ termsEnum.postings(
+ null, scoreMode.needsScores() ? PostingsEnum.FREQS : PostingsEnum.NONE),
+ scorer);
+ }
+ }
+
+ @Override
+ public long cost() {
+ return docFreq;
+ }
+
+ @Override
+ public void setTopLevelScoringClause() throws IOException {
+ topLevelScoringClause = true;
+ }
+ };
+ }
+
+ @Override
+ public Scorer scorer(LeafReaderContext context) throws IOException {
+ ScorerSupplier supplier = scorerSupplier(context);
+ if (supplier == null) {
+ return null;
}
+ return supplier.get(Long.MAX_VALUE);
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/TermScorer.java b/lucene/core/src/java/org/apache/lucene/search/TermScorer.java
index 51b8068806e..12d59db76a3 100644
--- a/lucene/core/src/java/org/apache/lucene/search/TermScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/TermScorer.java
@@ -28,17 +28,18 @@ import org.apache.lucene.index.SlowImpactsEnum;
*/
public final class TermScorer extends Scorer {
private final PostingsEnum postingsEnum;
- private final ImpactsEnum impactsEnum;
private final DocIdSetIterator iterator;
private final LeafSimScorer docScorer;
private final ImpactsDISI impactsDisi;
+ private final MaxScoreCache maxScoreCache;
/** Construct a {@link TermScorer} that will iterate all documents. */
public TermScorer(Weight weight, PostingsEnum postingsEnum, LeafSimScorer docScorer) {
super(weight);
iterator = this.postingsEnum = postingsEnum;
- impactsEnum = new SlowImpactsEnum(postingsEnum);
- impactsDisi = new ImpactsDISI(impactsEnum, impactsEnum, docScorer.getSimScorer());
+ ImpactsEnum impactsEnum = new SlowImpactsEnum(postingsEnum);
+ maxScoreCache = new MaxScoreCache(impactsEnum, docScorer.getSimScorer());
+ impactsDisi = null;
this.docScorer = docScorer;
}
@@ -46,11 +47,21 @@ public final class TermScorer extends Scorer {
* Construct a {@link TermScorer} that will use impacts to skip blocks of non-competitive
* documents.
*/
- TermScorer(Weight weight, ImpactsEnum impactsEnum, LeafSimScorer docScorer) {
+ public TermScorer(
+ Weight weight,
+ ImpactsEnum impactsEnum,
+ LeafSimScorer docScorer,
+ boolean topLevelScoringClause) {
super(weight);
- postingsEnum = this.impactsEnum = impactsEnum;
- impactsDisi = new ImpactsDISI(impactsEnum, impactsEnum, docScorer.getSimScorer());
- iterator = impactsDisi;
+ postingsEnum = impactsEnum;
+ maxScoreCache = new MaxScoreCache(impactsEnum, docScorer.getSimScorer());
+ if (topLevelScoringClause) {
+ impactsDisi = new ImpactsDISI(impactsEnum, maxScoreCache);
+ iterator = impactsDisi;
+ } else {
+ impactsDisi = null;
+ iterator = impactsEnum;
+ }
this.docScorer = docScorer;
}
@@ -82,17 +93,19 @@ public final class TermScorer extends Scorer {
@Override
public int advanceShallow(int target) throws IOException {
- return impactsDisi.advanceShallow(target);
+ return maxScoreCache.advanceShallow(target);
}
@Override
public float getMaxScore(int upTo) throws IOException {
- return impactsDisi.getMaxScore(upTo);
+ return maxScoreCache.getMaxScore(upTo);
}
@Override
public void setMinCompetitiveScore(float minScore) {
- impactsDisi.setMinCompetitiveScore(minScore);
+ if (impactsDisi != null) {
+ impactsDisi.setMinCompetitiveScore(minScore);
+ }
}
/** Returns a string representation of this <code>TermScorer</code>. */
diff --git a/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java b/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java
index 620858bccc0..7fa2a338653 100644
--- a/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java
@@ -25,6 +25,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import org.apache.lucene.util.MathUtil;
/**
* This implements the WAND (Weak AND) algorithm for dynamic pruning described in "Efficient Query
@@ -121,6 +122,8 @@ final class WANDScorer extends Scorer {
// scaled min competitive score
private long minCompetitiveScore = 0;
+ private final Scorer[] allScorers;
+
// list of scorers which 'lead' the iteration and are currently
// positioned on 'doc'. This is sometimes called the 'pivot' in
// some descriptions of WAND (Weak AND).
@@ -139,7 +142,6 @@ final class WANDScorer extends Scorer {
int tailSize;
final long cost;
- final MaxScoreSumPropagator maxScorePropagator;
int upTo; // upper bound for which max scores are valid
@@ -156,6 +158,7 @@ final class WANDScorer extends Scorer {
throw new IllegalArgumentException("minShouldMatch should be < the number of scorers");
}
+ allScorers = scorers.toArray(Scorer[]::new);
this.minCompetitiveScore = 0;
assert minShouldMatch >= 0 : "minShouldMatch should not be negative, but got " + minShouldMatch;
@@ -185,12 +188,10 @@ final class WANDScorer extends Scorer {
float maxScore = scorer.getMaxScore(DocIdSetIterator.NO_MORE_DOCS);
maxScoreSumDouble += maxScore;
}
- this.maxScorePropagator = new MaxScoreSumPropagator(scorers);
- final float maxScoreSum = maxScorePropagator.scoreSumUpperBound(maxScoreSumDouble);
+ final float maxScoreSum = (float) MathUtil.sumUpperBound(maxScoreSumDouble, scorers.size());
this.scalingFactor = scalingFactor(maxScoreSum);
} else {
this.scalingFactor = 0;
- this.maxScorePropagator = null;
}
for (Scorer scorer : scorers) {
@@ -245,7 +246,6 @@ final class WANDScorer extends Scorer {
long scaledMinScore = scaleMinScore(minScore, scalingFactor);
assert scaledMinScore >= minCompetitiveScore;
minCompetitiveScore = scaledMinScore;
- maxScorePropagator.setMinCompetitiveScore(minScore);
}
@Override
@@ -532,7 +532,11 @@ final class WANDScorer extends Scorer {
@Override
public int advanceShallow(int target) throws IOException {
// Propagate to improve score bounds
- maxScorePropagator.advanceShallow(target);
+ for (Scorer scorer : allScorers) {
+ if (scorer.docID() < target) {
+ scorer.advanceShallow(target);
+ }
+ }
if (target <= upTo) {
return upTo;
}
@@ -542,7 +546,13 @@ final class WANDScorer extends Scorer {
@Override
public float getMaxScore(int upTo) throws IOException {
- return maxScorePropagator.getMaxScore(upTo);
+ double maxScoreSum = 0;
+ for (Scorer scorer : allScorers) {
+ if (scorer.docID() <= upTo) {
+ maxScoreSum += scorer.getMaxScore(upTo);
+ }
+ }
+ return (float) MathUtil.sumUpperBound(maxScoreSum, allScorers.length);
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/Weight.java b/lucene/core/src/java/org/apache/lucene/search/Weight.java
index 57930cd5440..54c49d365a1 100644
--- a/lucene/core/src/java/org/apache/lucene/search/Weight.java
+++ b/lucene/core/src/java/org/apache/lucene/search/Weight.java
@@ -164,15 +164,15 @@ public abstract class Weight implements SegmentCacheable {
*/
public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
- Scorer scorer = scorer(context);
- if (scorer == null) {
+ ScorerSupplier scorerSupplier = scorerSupplier(context);
+ if (scorerSupplier == null) {
// No docs match
return null;
}
- // This impl always scores docs in order, so we can
- // ignore scoreDocsInOrder:
- return new DefaultBulkScorer(scorer);
+ scorerSupplier.setTopLevelScoringClause();
+
+ return new DefaultBulkScorer(scorerSupplier.get(Long.MAX_VALUE));
}
/**
diff --git a/lucene/core/src/java/org/apache/lucene/util/MathUtil.java b/lucene/core/src/java/org/apache/lucene/util/MathUtil.java
index 973383585d4..141a4566595 100644
--- a/lucene/core/src/java/org/apache/lucene/util/MathUtil.java
+++ b/lucene/core/src/java/org/apache/lucene/util/MathUtil.java
@@ -167,4 +167,29 @@ public final class MathUtil {
double u = Math.scalb(1.0, -52);
return (numValues - 1) * u;
}
+
+ /**
+ * Return the maximum possible sum across {@code numValues} non-negative doubles, assuming one sum
+ * yielded {@code sum}.
+ *
+ * @see #sumRelativeErrorBound(int)
+ */
+ public static double sumUpperBound(double sum, int numValues) {
+ if (numValues <= 2) {
+ // When there are only two clauses, the sum is always the same regardless
+ // of the order.
+ return sum;
+ }
+
+ // The error of sums depends on the order in which values are summed up. In
+ // order to avoid this issue, we compute an upper bound of the value that
+ // the sum may take. If the max relative error is b, then it means that two
+ // sums are always within 2*b of each other.
+ // For conjunctions, we could skip this error factor since the order in which
+ // scores are summed up is predictable, but in practice, this wouldn't help
+ // much since the delta that is introduced by this error factor is usually
+ // cancelled by the float cast.
+ double b = MathUtil.sumRelativeErrorBound(numValues);
+ return (1.0 + 2 * b) * sum;
+ }
}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestBoolean2ScorerSupplier.java b/lucene/core/src/test/org/apache/lucene/search/TestBoolean2ScorerSupplier.java
index b35969aab86..360d0536e1f 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestBoolean2ScorerSupplier.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestBoolean2ScorerSupplier.java
@@ -91,6 +91,7 @@ public class TestBoolean2ScorerSupplier extends LuceneTestCase {
private final long cost;
private final Long leadCost;
+ private boolean topLevelScoringClause = false;
FakeScorerSupplier(long cost) {
this.cost = cost;
@@ -119,6 +120,11 @@ public class TestBoolean2ScorerSupplier extends LuceneTestCase {
public String toString() {
return "FakeLazyScorer(cost=" + cost + ",leadCost=" + leadCost + ")";
}
+
+ @Override
+ public void setTopLevelScoringClause() throws IOException {
+ topLevelScoringClause = true;
+ }
}
public void testConjunctionCost() {
@@ -430,4 +436,89 @@ public class TestBoolean2ScorerSupplier extends LuceneTestCase {
new Boolean2ScorerSupplier(new FakeWeight(), subs, ScoreMode.COMPLETE, 0)
.get(20); // triggers assertions as a side-effect
}
+
+ public void testDisjunctionTopLevelScoringClause() throws Exception {
+ Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+ for (Occur occur : Occur.values()) {
+ subs.put(occur, new ArrayList<>());
+ }
+
+ FakeScorerSupplier clause1 = new FakeScorerSupplier(10, 10);
+ subs.get(Occur.SHOULD).add(clause1);
+ FakeScorerSupplier clause2 = new FakeScorerSupplier(10, 10);
+ subs.get(Occur.SHOULD).add(clause2);
+
+ new Boolean2ScorerSupplier(new FakeWeight(), subs, ScoreMode.TOP_SCORES, 0)
+ .setTopLevelScoringClause();
+ assertFalse(clause1.topLevelScoringClause);
+ assertFalse(clause2.topLevelScoringClause);
+ }
+
+ public void testConjunctionTopLevelScoringClause() throws Exception {
+ Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+ for (Occur occur : Occur.values()) {
+ subs.put(occur, new ArrayList<>());
+ }
+
+ FakeScorerSupplier clause1 = new FakeScorerSupplier(10, 10);
+ subs.get(Occur.MUST).add(clause1);
+ FakeScorerSupplier clause2 = new FakeScorerSupplier(10, 10);
+ subs.get(Occur.MUST).add(clause2);
+
+ new Boolean2ScorerSupplier(new FakeWeight(), subs, ScoreMode.TOP_SCORES, 0)
+ .setTopLevelScoringClause();
+ assertFalse(clause1.topLevelScoringClause);
+ assertFalse(clause2.topLevelScoringClause);
+ }
+
+ public void testFilterTopLevelScoringClause() throws Exception {
+ Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+ for (Occur occur : Occur.values()) {
+ subs.put(occur, new ArrayList<>());
+ }
+
+ FakeScorerSupplier clause1 = new FakeScorerSupplier(10, 10);
+ subs.get(Occur.FILTER).add(clause1);
+ FakeScorerSupplier clause2 = new FakeScorerSupplier(10, 10);
+ subs.get(Occur.FILTER).add(clause2);
+
+ new Boolean2ScorerSupplier(new FakeWeight(), subs, ScoreMode.TOP_SCORES, 0)
+ .setTopLevelScoringClause();
+ assertFalse(clause1.topLevelScoringClause);
+ assertFalse(clause2.topLevelScoringClause);
+ }
+
+ public void testSingleMustScoringClause() throws Exception {
+ Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+ for (Occur occur : Occur.values()) {
+ subs.put(occur, new ArrayList<>());
+ }
+
+ FakeScorerSupplier clause1 = new FakeScorerSupplier(10, 10);
+ subs.get(Occur.MUST).add(clause1);
+ FakeScorerSupplier clause2 = new FakeScorerSupplier(10, 10);
+ subs.get(Occur.FILTER).add(clause2);
+
+ new Boolean2ScorerSupplier(new FakeWeight(), subs, ScoreMode.TOP_SCORES, 0)
+ .setTopLevelScoringClause();
+ assertTrue(clause1.topLevelScoringClause);
+ assertFalse(clause2.topLevelScoringClause);
+ }
+
+ public void testSingleShouldScoringClause() throws Exception {
+ Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+ for (Occur occur : Occur.values()) {
+ subs.put(occur, new ArrayList<>());
+ }
+
+ FakeScorerSupplier clause1 = new FakeScorerSupplier(10, 10);
+ subs.get(Occur.SHOULD).add(clause1);
+ FakeScorerSupplier clause2 = new FakeScorerSupplier(10, 10);
+ subs.get(Occur.MUST_NOT).add(clause2);
+
+ new Boolean2ScorerSupplier(new FakeWeight(), subs, ScoreMode.TOP_SCORES, 0)
+ .setTopLevelScoringClause();
+ assertTrue(clause1.topLevelScoringClause);
+ assertFalse(clause2.topLevelScoringClause);
+ }
}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestMaxScoreSumPropagator.java b/lucene/core/src/test/org/apache/lucene/search/TestMaxScoreSumPropagator.java
deleted file mode 100644
index 0cbe28fa8c3..00000000000
--- a/lucene/core/src/test/org/apache/lucene/search/TestMaxScoreSumPropagator.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * 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.search;
-
-import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.tests.util.LuceneTestCase;
-import org.apache.lucene.tests.util.TestUtil;
-
-public class TestMaxScoreSumPropagator extends LuceneTestCase {
-
- private static class FakeWeight extends Weight {
-
- FakeWeight() {
- super(new MatchNoDocsQuery());
- }
-
- @Override
- public Explanation explain(LeafReaderContext context, int doc) throws IOException {
- return null;
- }
-
- @Override
- public Scorer scorer(LeafReaderContext context) throws IOException {
- return null;
- }
-
- @Override
- public boolean isCacheable(LeafReaderContext ctx) {
- return false;
- }
- }
-
- private static class FakeScorer extends Scorer {
-
- final float maxScore;
- float minCompetitiveScore;
-
- FakeScorer(float maxScore) throws IOException {
- super(new FakeWeight());
- this.maxScore = maxScore;
- }
-
- @Override
- public int docID() {
- return 0;
- }
-
- @Override
- public float score() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public DocIdSetIterator iterator() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setMinCompetitiveScore(float minCompetitiveScore) {
- this.minCompetitiveScore = minCompetitiveScore;
- }
-
- @Override
- public float getMaxScore(int upTo) throws IOException {
- assert upTo == NO_MORE_DOCS;
- return maxScore;
- }
- }
-
- public void test0Clause() throws IOException {
- MaxScoreSumPropagator p = new MaxScoreSumPropagator(Collections.emptyList());
- p.setMinCompetitiveScore(0f); // no exception
- p.setMinCompetitiveScore(0.5f); // no exception
- }
-
- public void test1Clause() throws IOException {
- FakeScorer a = new FakeScorer(1);
-
- MaxScoreSumPropagator p = new MaxScoreSumPropagator(Collections.singletonList(a));
- assertEquals(1f, p.getMaxScore(NO_MORE_DOCS), 0f);
- p.setMinCompetitiveScore(0f);
- assertEquals(0f, a.minCompetitiveScore, 0f);
- p.setMinCompetitiveScore(0.5f);
- assertEquals(Math.nextDown(0.5f), a.minCompetitiveScore, 0f);
- p.setMinCompetitiveScore(1f);
- assertEquals(Math.nextDown(1f), a.minCompetitiveScore, 0f);
- }
-
- public void test2Clauses() throws IOException {
- FakeScorer a = new FakeScorer(1);
- FakeScorer b = new FakeScorer(2);
-
- MaxScoreSumPropagator p = new MaxScoreSumPropagator(Arrays.asList(a, b));
- assertEquals(3f, p.getMaxScore(NO_MORE_DOCS), 0f);
-
- p.setMinCompetitiveScore(1f);
- assertEquals(0f, a.minCompetitiveScore, 0f);
- assertEquals(0f, b.minCompetitiveScore, 0f);
-
- p.setMinCompetitiveScore(2f);
- assertEquals(0f, a.minCompetitiveScore, 0f);
- assertEquals(Math.nextDown(2f) - 1f, b.minCompetitiveScore, 0f);
-
- p.setMinCompetitiveScore(2.5f);
- assertEquals(Math.nextDown(2.5f) - 2f, a.minCompetitiveScore, 0f);
- assertEquals(Math.nextDown(2.5f) - 1f, b.minCompetitiveScore, 0f);
-
- p.setMinCompetitiveScore(3f);
- assertEquals(Math.nextDown(3f) - 2f, a.minCompetitiveScore, 0f);
- assertEquals(Math.nextDown(3f) - 1f, b.minCompetitiveScore, 0f);
- }
-
- public void test3Clauses() throws IOException {
- FakeScorer a = new FakeScorer(1);
- FakeScorer b = new FakeScorer(2);
- FakeScorer c = new FakeScorer(1.5f);
-
- MaxScoreSumPropagator p = new MaxScoreSumPropagator(Arrays.asList(a, b, c));
- assertEquals(4.5f, p.getMaxScore(NO_MORE_DOCS), 0f);
-
- p.setMinCompetitiveScore(1f);
- assertEquals(0f, a.minCompetitiveScore, 0f);
- assertEquals(0f, b.minCompetitiveScore, 0f);
- assertEquals(0f, c.minCompetitiveScore, 0f);
-
- p.setMinCompetitiveScore(2f);
- assertEquals(0f, a.minCompetitiveScore, 0f);
- assertEquals(0f, b.minCompetitiveScore, 0f);
- assertEquals(0f, c.minCompetitiveScore, 0f);
-
- p.setMinCompetitiveScore(3f);
- assertEquals(0f, a.minCompetitiveScore, 0f);
- assertEquals(Math.nextDown(3f) - 1f - 1.5f, b.minCompetitiveScore, 0f);
- assertEquals(0f, c.minCompetitiveScore, 0f);
-
- p.setMinCompetitiveScore(4f);
- assertEquals(Math.nextDown(4f) - 2f - 1.5f, a.minCompetitiveScore, 0f);
- assertEquals(Math.nextDown(4f) - 1f - 1.5f, b.minCompetitiveScore, 0f);
- assertEquals(Math.nextDown(4f) - 1f - 2f, c.minCompetitiveScore, 0f);
- }
-
- public void test2ClausesRandomScore() throws IOException {
- for (int iter = 0; iter < 10; ++iter) {
- FakeScorer a = new FakeScorer(random().nextFloat());
- FakeScorer b =
- new FakeScorer(Math.nextUp(a.getMaxScore(NO_MORE_DOCS)) + random().nextFloat());
-
- MaxScoreSumPropagator p = new MaxScoreSumPropagator(Arrays.asList(a, b));
- assertEquals(
- a.getMaxScore(NO_MORE_DOCS) + b.getMaxScore(NO_MORE_DOCS),
- p.getMaxScore(NO_MORE_DOCS),
- 0f);
- assertMinCompetitiveScore(Arrays.asList(a, b), p, Math.nextUp(a.getMaxScore(NO_MORE_DOCS)));
- assertMinCompetitiveScore(
- Arrays.asList(a, b), p, (a.getMaxScore(NO_MORE_DOCS) + b.getMaxScore(NO_MORE_DOCS)) / 2);
- assertMinCompetitiveScore(
- Arrays.asList(a, b),
- p,
- Math.nextDown(a.getMaxScore(NO_MORE_DOCS) + b.getMaxScore(NO_MORE_DOCS)));
- assertMinCompetitiveScore(
- Arrays.asList(a, b), p, a.getMaxScore(NO_MORE_DOCS) + b.getMaxScore(NO_MORE_DOCS));
- }
- }
-
- public void testNClausesRandomScore() throws IOException {
- for (int iter = 0; iter < 100; ++iter) {
- List<FakeScorer> scorers = new ArrayList<>();
- int numScorers = TestUtil.nextInt(random(), 3, 4 << random().nextInt(8));
- double sumOfMaxScore = 0;
- for (int i = 0; i < numScorers; ++i) {
- float maxScore = random().nextFloat();
- scorers.add(new FakeScorer(maxScore));
- sumOfMaxScore += maxScore;
- }
-
- MaxScoreSumPropagator p = new MaxScoreSumPropagator(scorers);
- assertTrue(p.getMaxScore(NO_MORE_DOCS) >= (float) sumOfMaxScore);
- for (int i = 0; i < 10; ++i) {
- final float minCompetitiveScore = random().nextFloat() * numScorers;
- assertMinCompetitiveScore(scorers, p, minCompetitiveScore);
- // reset
- for (FakeScorer scorer : scorers) {
- scorer.minCompetitiveScore = 0;
- }
- }
- }
- }
-
- private void assertMinCompetitiveScore(
- Collection<FakeScorer> scorers, MaxScoreSumPropagator p, float minCompetitiveScore)
- throws IOException {
- p.setMinCompetitiveScore(minCompetitiveScore);
-
- for (FakeScorer scorer : scorers) {
- if (scorer.minCompetitiveScore == 0f) {
- // no propagation is performed, still visiting all hits
- break;
- }
- double scoreSum = scorer.minCompetitiveScore;
- for (FakeScorer scorer2 : scorers) {
- if (scorer2 != scorer) {
- scoreSum += scorer2.getMaxScore(NO_MORE_DOCS);
- }
- }
- assertTrue(
- "scoreSum=" + scoreSum + ", minCompetitiveScore=" + minCompetitiveScore,
- (float) scoreSum <= minCompetitiveScore);
- }
- }
-
- /*
- In https://issues.apache.org/jira/browse/LUCENE-10428 we have observed an issue in production
- causing an infinite loop in MaxScoreSumPropagator.getMinCompetitiveScore. This is likely
- caused by calling code that is breaking the assumptions made by MaxScoreSumPropagator,
- e.g. a query that produces negative or NaN scores. This test reproduces that scenario,
- and asserts that getMinCompetitiveScore aborts after more than 2 iterations.
-
- See https://github.com/apache/lucene/pull/711.
- */
- public void testMinCompetitiveScoreIllegalState() throws Exception {
- List<FakeScorer> scorers = new ArrayList<>();
- scorers.add(new FakeScorer(-0.16903716f));
- scorers.add(new FakeScorer(0.62573546f));
- scorers.add(new FakeScorer(-0.64014715f));
- float minScoreSum = 0.31314075f;
- MaxScoreSumPropagator p = new MaxScoreSumPropagator(scorers);
- Throwable exception =
- assertThrows(IllegalStateException.class, () -> p.setMinCompetitiveScore(minScoreSum));
- assertEquals(
- "Could not compute a minimum score for minScore=1.1223251, minScoreSum=0.31314072, sumOfOtherMaxScores=-0.8091843128204346, numClauses=3",
- exception.getMessage());
- }
-}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestReqOptSumScorer.java b/lucene/core/src/test/org/apache/lucene/search/TestReqOptSumScorer.java
index 3358a3576f8..5d99816037e 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestReqOptSumScorer.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestReqOptSumScorer.java
@@ -88,7 +88,9 @@ public class TestReqOptSumScorer extends LuceneTestCase {
assertEquals(4, scorer.iterator().nextDoc());
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer = weight.scorer(context);
+ ScorerSupplier ss = weight.scorerSupplier(context);
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
scorer.setMinCompetitiveScore(Math.nextDown(1f));
assertEquals(0, scorer.iterator().nextDoc());
assertEquals(1, scorer.iterator().nextDoc());
@@ -96,13 +98,17 @@ public class TestReqOptSumScorer extends LuceneTestCase {
assertEquals(4, scorer.iterator().nextDoc());
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer = weight.scorer(context);
+ ss = weight.scorerSupplier(context);
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
scorer.setMinCompetitiveScore(Math.nextUp(1f));
assertEquals(1, scorer.iterator().nextDoc());
assertEquals(4, scorer.iterator().nextDoc());
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer = weight.scorer(context);
+ ss = weight.scorerSupplier(context);
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
assertEquals(0, scorer.iterator().nextDoc());
scorer.setMinCompetitiveScore(Math.nextUp(1f));
assertEquals(1, scorer.iterator().nextDoc());
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java b/lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java
index 188b7db34bc..b811b2e7859 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java
@@ -121,10 +121,10 @@ public class TestWANDScorer extends LuceneTestCase {
Occur.SHOULD)
.build());
- Scorer scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ Weight weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1);
+ ScorerSupplier ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ Scorer scorer = ss.get(Long.MAX_VALUE);
assertEquals(0, scorer.iterator().nextDoc());
assertEquals(2 + 1, scorer.score(), 0);
@@ -143,10 +143,9 @@ public class TestWANDScorer extends LuceneTestCase {
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
scorer.setMinCompetitiveScore(4);
assertEquals(3, scorer.iterator().nextDoc());
@@ -157,10 +156,9 @@ public class TestWANDScorer extends LuceneTestCase {
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
assertEquals(0, scorer.iterator().nextDoc());
assertEquals(2 + 1, scorer.score(), 0);
@@ -187,10 +185,10 @@ public class TestWANDScorer extends LuceneTestCase {
.add(new TermQuery(new Term("foo", "C")), Occur.FILTER)
.build();
- scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1);
+ ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
assertEquals(3, scorer.iterator().nextDoc());
assertEquals(2 + 1, scorer.score(), 0);
@@ -200,10 +198,9 @@ public class TestWANDScorer extends LuceneTestCase {
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
scorer.setMinCompetitiveScore(2);
@@ -230,10 +227,10 @@ public class TestWANDScorer extends LuceneTestCase {
.add(new TermQuery(new Term("foo", "C")), Occur.MUST_NOT)
.build();
- scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1);
+ ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
assertEquals(0, scorer.iterator().nextDoc());
assertEquals(2 + 1, scorer.score(), 0);
@@ -246,10 +243,9 @@ public class TestWANDScorer extends LuceneTestCase {
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
scorer.setMinCompetitiveScore(3);
@@ -303,10 +299,10 @@ public class TestWANDScorer extends LuceneTestCase {
.setMinimumNumberShouldMatch(2)
.build());
- Scorer scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ Weight weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1);
+ ScorerSupplier ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ Scorer scorer = ss.get(Long.MAX_VALUE);
assertEquals(0, scorer.iterator().nextDoc());
assertEquals(2 + 1, scorer.score(), 0);
@@ -319,10 +315,9 @@ public class TestWANDScorer extends LuceneTestCase {
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
scorer.setMinCompetitiveScore(4);
assertEquals(3, scorer.iterator().nextDoc());
@@ -333,10 +328,9 @@ public class TestWANDScorer extends LuceneTestCase {
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
assertEquals(0, scorer.iterator().nextDoc());
assertEquals(2 + 1, scorer.score(), 0);
@@ -385,10 +379,10 @@ public class TestWANDScorer extends LuceneTestCase {
.setMinimumNumberShouldMatch(2)
.build());
- Scorer scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ Weight weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1);
+ ScorerSupplier ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ Scorer scorer = ss.get(Long.MAX_VALUE);
assertEquals(0, scorer.iterator().nextDoc());
scorer.setMinCompetitiveScore(scorer.score());
@@ -500,10 +494,10 @@ public class TestWANDScorer extends LuceneTestCase {
.add(new TermQuery(new Term("foo", "C")), Occur.FILTER)
.build();
- Scorer scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ Weight weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1);
+ ScorerSupplier ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ Scorer scorer = ss.get(Long.MAX_VALUE);
assertEquals(1, scorer.iterator().nextDoc());
assertEquals(2 + 4, scorer.score(), 0);
@@ -513,10 +507,9 @@ public class TestWANDScorer extends LuceneTestCase {
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
scorer.setMinCompetitiveScore(2 + 1 + 4);
@@ -637,10 +630,8 @@ public class TestWANDScorer extends LuceneTestCase {
.add(new TermQuery(new Term("foo", "C")), Occur.MUST_NOT)
.build();
- Scorer scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ Weight weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1);
+ Scorer scorer = weight.scorer(searcher.getIndexReader().leaves().get(0));
assertEquals(0, scorer.iterator().nextDoc());
assertEquals(2 + 1, scorer.score(), 0);
@@ -650,10 +641,9 @@ public class TestWANDScorer extends LuceneTestCase {
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer =
- searcher
- .createWeight(searcher.rewrite(query), ScoreMode.TOP_SCORES, 1)
- .scorer(searcher.getIndexReader().leaves().get(0));
+ ScorerSupplier ss = weight.scorerSupplier(searcher.getIndexReader().leaves().get(0));
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
scorer.setMinCompetitiveScore(4);
diff --git a/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinScorer.java b/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinScorer.java
index 22f4ae6a5d7..6acca015c0e 100644
--- a/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinScorer.java
+++ b/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinScorer.java
@@ -30,6 +30,7 @@ import org.apache.lucene.search.MatchAllDocsQuery;
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.Weight;
import org.apache.lucene.store.Directory;
@@ -90,7 +91,9 @@ public class TestBlockJoinScorer extends LuceneTestCase {
}
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer = weight.scorer(context);
+ ScorerSupplier ss = weight.scorerSupplier(context);
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
scorer.setMinCompetitiveScore(0f);
parent = 0;
for (int i = 0; i < 9; i++) {
@@ -99,11 +102,15 @@ public class TestBlockJoinScorer extends LuceneTestCase {
}
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer = weight.scorer(context);
+ ss = weight.scorerSupplier(context);
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
scorer.setMinCompetitiveScore(Math.nextUp(0f));
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
- scorer = weight.scorer(context);
+ ss = weight.scorerSupplier(context);
+ ss.setTopLevelScoringClause();
+ scorer = ss.get(Long.MAX_VALUE);
assertEquals(2, scorer.iterator().nextDoc());
scorer.setMinCompetitiveScore(Math.nextUp(0f));
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/search/AssertingScorer.java b/lucene/test-framework/src/java/org/apache/lucene/tests/search/AssertingScorer.java
index 8792535375d..a68ca05400a 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/tests/search/AssertingScorer.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/search/AssertingScorer.java
@@ -35,28 +35,32 @@ public class AssertingScorer extends Scorer {
FINISHED
};
- public static Scorer wrap(Random random, Scorer other, ScoreMode scoreMode) {
+ public static Scorer wrap(
+ Random random, Scorer other, ScoreMode scoreMode, boolean canCallMinCompetitiveScore) {
if (other == null) {
return null;
}
- return new AssertingScorer(random, other, scoreMode);
+ return new AssertingScorer(random, other, scoreMode, canCallMinCompetitiveScore);
}
final Random random;
final Scorer in;
final ScoreMode scoreMode;
+ final boolean canCallMinCompetitiveScore;
IteratorState state = IteratorState.ITERATING;
int doc;
float minCompetitiveScore = 0;
int lastShallowTarget = -1;
- private AssertingScorer(Random random, Scorer in, ScoreMode scoreMode) {
+ private AssertingScorer(
+ Random random, Scorer in, ScoreMode scoreMode, boolean canCallMinCompetitiveScore) {
super(in.getWeight());
this.random = random;
this.in = in;
this.scoreMode = scoreMode;
this.doc = in.docID();
+ this.canCallMinCompetitiveScore = canCallMinCompetitiveScore;
}
public Scorer getIn() {
@@ -77,6 +81,7 @@ public class AssertingScorer extends Scorer {
@Override
public void setMinCompetitiveScore(float score) throws IOException {
assert scoreMode == ScoreMode.TOP_SCORES;
+ assert canCallMinCompetitiveScore;
assert Float.isNaN(score) == false;
assert score >= minCompetitiveScore;
in.setMinCompetitiveScore(score);
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/search/AssertingWeight.java b/lucene/test-framework/src/java/org/apache/lucene/tests/search/AssertingWeight.java
index 9dec127789b..574031c6dc4 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/tests/search/AssertingWeight.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/search/AssertingWeight.java
@@ -61,7 +61,7 @@ class AssertingWeight extends FilterWeight {
if (random.nextBoolean()) {
final Scorer inScorer = in.scorer(context);
assert inScorer == null || inScorer.docID() == -1;
- return AssertingScorer.wrap(new Random(random.nextLong()), inScorer, scoreMode);
+ return AssertingScorer.wrap(new Random(random.nextLong()), inScorer, scoreMode, false);
} else {
final ScorerSupplier scorerSupplier = scorerSupplier(context);
if (scorerSupplier == null) {
@@ -83,6 +83,7 @@ class AssertingWeight extends FilterWeight {
}
return new ScorerSupplier() {
private boolean getCalled = false;
+ private boolean topLevelScoringClause = false;
@Override
public Scorer get(long leadCost) throws IOException {
@@ -90,7 +91,10 @@ class AssertingWeight extends FilterWeight {
getCalled = true;
assert leadCost >= 0 : leadCost;
return AssertingScorer.wrap(
- new Random(random.nextLong()), inScorerSupplier.get(leadCost), scoreMode);
+ new Random(random.nextLong()),
+ inScorerSupplier.get(leadCost),
+ scoreMode,
+ topLevelScoringClause);
}
@Override
@@ -99,6 +103,13 @@ class AssertingWeight extends FilterWeight {
assert cost >= 0;
return cost;
}
+
+ @Override
+ public void setTopLevelScoringClause() throws IOException {
+ assert getCalled == false;
+ topLevelScoringClause = true;
+ inScorerSupplier.setTopLevelScoringClause();
+ }
};
}
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/search/CheckHits.java b/lucene/test-framework/src/java/org/apache/lucene/tests/search/CheckHits.java
index cbcf99c536e..668c308dd0a 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/tests/search/CheckHits.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/search/CheckHits.java
@@ -44,6 +44,7 @@ import org.apache.lucene.search.Scorable;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.ScorerSupplier;
import org.apache.lucene.search.SimpleCollector;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TopDocs;
@@ -717,7 +718,15 @@ public class CheckHits {
// Check boundaries and max scores when iterating all matches
for (LeafReaderContext ctx : searcher.getIndexReader().leaves()) {
Scorer s1 = w1.scorer(ctx);
- Scorer s2 = w2.scorer(ctx);
+ ScorerSupplier ss2 = w2.scorerSupplier(ctx);
+ Scorer s2;
+ if (ss2 == null) {
+ s2 = null;
+ } else {
+ // We'll call setMinCompetitiveScore on s2
+ ss2.setTopLevelScoringClause();
+ s2 = ss2.get(Long.MAX_VALUE);
+ }
if (s1 == null) {
assertTrue(s2 == null || s2.iterator().nextDoc() == DocIdSetIterator.NO_MORE_DOCS);
continue;
@@ -765,7 +774,15 @@ public class CheckHits {
// Now check advancing
for (LeafReaderContext ctx : searcher.getIndexReader().leaves()) {
Scorer s1 = w1.scorer(ctx);
- Scorer s2 = w2.scorer(ctx);
+ ScorerSupplier ss2 = w2.scorerSupplier(ctx);
+ Scorer s2;
+ if (ss2 == null) {
+ s2 = null;
+ } else {
+ // We'll call setMinCompetitiveScore on s2
+ ss2.setTopLevelScoringClause();
+ s2 = ss2.get(Long.MAX_VALUE);
+ }
if (s1 == null) {
assertTrue(s2 == null || s2.iterator().nextDoc() == DocIdSetIterator.NO_MORE_DOCS);
continue;