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.
This commit is contained in:
Britta Weber 2016-07-28 13:14:08 +02:00 committed by GitHub
parent ebf96bbc35
commit 105dce0e07
2 changed files with 40 additions and 7 deletions

View File

@ -37,6 +37,8 @@ import org.elasticsearch.common.lucene.Lucene;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
@ -224,17 +226,23 @@ public class FiltersFunctionScoreQuery extends Query {
filterExplanations.add(filterExplanation); filterExplanations.add(filterExplanation);
} }
} }
if (filterExplanations.size() > 0) {
FiltersFunctionFactorScorer scorer = functionScorer(context); FiltersFunctionFactorScorer scorer = functionScorer(context);
int actualDoc = scorer.iterator().advance(doc); int actualDoc = scorer.iterator().advance(doc);
assert (actualDoc == doc); assert (actualDoc == doc);
double score = scorer.computeScore(doc, expl.getValue()); double score = scorer.computeScore(doc, expl.getValue());
Explanation factorExplanation = Explanation.match( Explanation factorExplanation;
if (filterExplanations.size() > 0) {
factorExplanation = Explanation.match(
CombineFunction.toFloat(score), CombineFunction.toFloat(score),
"function score, score mode [" + scoreMode.toString().toLowerCase(Locale.ROOT) + "]", "function score, score mode [" + scoreMode.toString().toLowerCase(Locale.ROOT) + "]",
filterExplanations); filterExplanations);
expl = combineFunction.explain(expl, factorExplanation, maxBoost);
} else {
// it is a little weird to add a match although no function matches but that is the way function_score behaves right now
factorExplanation = Explanation.match(1.0f,
"No function matched", Collections.emptyList());
} }
expl = combineFunction.explain(expl, factorExplanation, maxBoost);
if (minScore != null && minScore > expl.getValue()) { if (minScore != null && minScore > expl.getValue()) {
expl = Explanation.noMatch("Score value is too low, expected at least " + minScore + " but got " + expl.getValue(), expl); expl = Explanation.noMatch("Score value is too low, expected at least " + minScore + " but got " + expl.getValue(), expl);
} }

View File

@ -599,7 +599,7 @@ public class FunctionScoreTests extends ESTestCase {
Explanation ffsqExpl = searcher.explain(ffsq, 0); Explanation ffsqExpl = searcher.explain(ffsq, 0);
assertTrue(ffsqExpl.isMatch()); assertTrue(ffsqExpl.isMatch());
assertEquals(queryExpl.getValue(), ffsqExpl.getValue(), 0f); assertEquals(queryExpl.getValue(), ffsqExpl.getValue(), 0f);
assertEquals(queryExpl.getDescription(), ffsqExpl.getDescription()); assertEquals(queryExpl.getDescription(), ffsqExpl.getDetails()[0].getDescription());
ffsq = new FiltersFunctionScoreQuery(query, ScoreMode.SUM, new FilterFunction[0], Float.POSITIVE_INFINITY, 10f, ffsq = new FiltersFunctionScoreQuery(query, ScoreMode.SUM, new FilterFunction[0], Float.POSITIVE_INFINITY, 10f,
CombineFunction.MULTIPLY); CombineFunction.MULTIPLY);
@ -726,6 +726,31 @@ public class FunctionScoreTests extends ESTestCase {
} }
} }
public void testExplanationAndScoreEqualsEvenIfNoFunctionMatches() throws IOException {
IndexSearcher localSearcher = newSearcher(reader);
ScoreMode scoreMode = randomFrom(new
ScoreMode[]{ScoreMode.SUM, ScoreMode.AVG, ScoreMode.FIRST, ScoreMode.MIN, ScoreMode.MAX, ScoreMode.MULTIPLY});
CombineFunction combineFunction = randomFrom(new
CombineFunction[]{CombineFunction.SUM, CombineFunction.AVG, CombineFunction.MIN, CombineFunction.MAX,
CombineFunction.MULTIPLY, CombineFunction.REPLACE});
// check for document that has no macthing function
FiltersFunctionScoreQuery query = new FiltersFunctionScoreQuery(new TermQuery(new Term(FIELD, "out")), scoreMode,
new FilterFunction[]{new FilterFunction(new TermQuery(new Term("_uid", "2")), new WeightFactorFunction(10))},
Float.MAX_VALUE, Float.NEGATIVE_INFINITY, combineFunction);
TopDocs searchResult = localSearcher.search(query, 1);
Explanation explanation = localSearcher.explain(query, searchResult.scoreDocs[0].doc);
assertThat(searchResult.scoreDocs[0].score, equalTo(explanation.getValue()));
// check for document that has a matching function
query = new FiltersFunctionScoreQuery(new TermQuery(new Term(FIELD, "out")), scoreMode,
new FilterFunction[]{new FilterFunction(new TermQuery(new Term("_uid", "1")), new WeightFactorFunction(10))},
Float.MAX_VALUE, Float.NEGATIVE_INFINITY, combineFunction);
searchResult = localSearcher.search(query, 1);
explanation = localSearcher.explain(query, searchResult.scoreDocs[0].doc);
assertThat(searchResult.scoreDocs[0].score, equalTo(explanation.getValue()));
}
private static class DummyScoreFunction extends ScoreFunction { private static class DummyScoreFunction extends ScoreFunction {
protected DummyScoreFunction(CombineFunction scoreCombiner) { protected DummyScoreFunction(CombineFunction scoreCombiner) {
super(scoreCombiner); super(scoreCombiner);