Skip to content

Commit 105dce0

Browse files
authored
fix explain in function_score if no function filter matches (#19185)
* fix explain in function_score if no function filter matches When each function in function_score has a filter but none of them matches we always assume 1 for the combined functions and then combine that with the sub query score. But the explanation did not reflect that because in case no function matched we did not even use the actual score that was computed in the explanation.
1 parent ebf96bb commit 105dce0

File tree

2 files changed

+40
-7
lines changed

2 files changed

+40
-7
lines changed

core/src/main/java/org/elasticsearch/common/lucene/search/function/FiltersFunctionScoreQuery.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import java.io.IOException;
3838
import java.util.ArrayList;
3939
import java.util.Arrays;
40+
import java.util.Collection;
41+
import java.util.Collections;
4042
import java.util.List;
4143
import java.util.Locale;
4244
import java.util.Objects;
@@ -224,17 +226,23 @@ public Explanation explain(LeafReaderContext context, int doc) throws IOExceptio
224226
filterExplanations.add(filterExplanation);
225227
}
226228
}
229+
FiltersFunctionFactorScorer scorer = functionScorer(context);
230+
int actualDoc = scorer.iterator().advance(doc);
231+
assert (actualDoc == doc);
232+
double score = scorer.computeScore(doc, expl.getValue());
233+
Explanation factorExplanation;
227234
if (filterExplanations.size() > 0) {
228-
FiltersFunctionFactorScorer scorer = functionScorer(context);
229-
int actualDoc = scorer.iterator().advance(doc);
230-
assert (actualDoc == doc);
231-
double score = scorer.computeScore(doc, expl.getValue());
232-
Explanation factorExplanation = Explanation.match(
235+
factorExplanation = Explanation.match(
233236
CombineFunction.toFloat(score),
234237
"function score, score mode [" + scoreMode.toString().toLowerCase(Locale.ROOT) + "]",
235238
filterExplanations);
236-
expl = combineFunction.explain(expl, factorExplanation, maxBoost);
239+
240+
} else {
241+
// it is a little weird to add a match although no function matches but that is the way function_score behaves right now
242+
factorExplanation = Explanation.match(1.0f,
243+
"No function matched", Collections.emptyList());
237244
}
245+
expl = combineFunction.explain(expl, factorExplanation, maxBoost);
238246
if (minScore != null && minScore > expl.getValue()) {
239247
expl = Explanation.noMatch("Score value is too low, expected at least " + minScore + " but got " + expl.getValue(), expl);
240248
}

core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreTests.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ public void testMinScoreExplain() throws IOException {
599599
Explanation ffsqExpl = searcher.explain(ffsq, 0);
600600
assertTrue(ffsqExpl.isMatch());
601601
assertEquals(queryExpl.getValue(), ffsqExpl.getValue(), 0f);
602-
assertEquals(queryExpl.getDescription(), ffsqExpl.getDescription());
602+
assertEquals(queryExpl.getDescription(), ffsqExpl.getDetails()[0].getDescription());
603603

604604
ffsq = new FiltersFunctionScoreQuery(query, ScoreMode.SUM, new FilterFunction[0], Float.POSITIVE_INFINITY, 10f,
605605
CombineFunction.MULTIPLY);
@@ -726,6 +726,31 @@ public void testFilterFunctionScoreHashCodeAndEquals() {
726726
}
727727
}
728728

729+
public void testExplanationAndScoreEqualsEvenIfNoFunctionMatches() throws IOException {
730+
IndexSearcher localSearcher = newSearcher(reader);
731+
ScoreMode scoreMode = randomFrom(new
732+
ScoreMode[]{ScoreMode.SUM, ScoreMode.AVG, ScoreMode.FIRST, ScoreMode.MIN, ScoreMode.MAX, ScoreMode.MULTIPLY});
733+
CombineFunction combineFunction = randomFrom(new
734+
CombineFunction[]{CombineFunction.SUM, CombineFunction.AVG, CombineFunction.MIN, CombineFunction.MAX,
735+
CombineFunction.MULTIPLY, CombineFunction.REPLACE});
736+
737+
// check for document that has no macthing function
738+
FiltersFunctionScoreQuery query = new FiltersFunctionScoreQuery(new TermQuery(new Term(FIELD, "out")), scoreMode,
739+
new FilterFunction[]{new FilterFunction(new TermQuery(new Term("_uid", "2")), new WeightFactorFunction(10))},
740+
Float.MAX_VALUE, Float.NEGATIVE_INFINITY, combineFunction);
741+
TopDocs searchResult = localSearcher.search(query, 1);
742+
Explanation explanation = localSearcher.explain(query, searchResult.scoreDocs[0].doc);
743+
assertThat(searchResult.scoreDocs[0].score, equalTo(explanation.getValue()));
744+
745+
// check for document that has a matching function
746+
query = new FiltersFunctionScoreQuery(new TermQuery(new Term(FIELD, "out")), scoreMode,
747+
new FilterFunction[]{new FilterFunction(new TermQuery(new Term("_uid", "1")), new WeightFactorFunction(10))},
748+
Float.MAX_VALUE, Float.NEGATIVE_INFINITY, combineFunction);
749+
searchResult = localSearcher.search(query, 1);
750+
explanation = localSearcher.explain(query, searchResult.scoreDocs[0].doc);
751+
assertThat(searchResult.scoreDocs[0].score, equalTo(explanation.getValue()));
752+
}
753+
729754
private static class DummyScoreFunction extends ScoreFunction {
730755
protected DummyScoreFunction(CombineFunction scoreCombiner) {
731756
super(scoreCombiner);

0 commit comments

Comments
 (0)