-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Correct boost in script_score query and error on negative scores #52478
Changes from 1 commit
7c9f5f8
1d8313e
96db563
c16c444
8882f39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# Integration tests for ScriptScoreQuery using Painless | ||
setup: | ||
- skip: | ||
version: " - 7.9.99" | ||
reason: "boost was corrected in script_score query from 8.0" | ||
- do: | ||
indices.create: | ||
index: test_index | ||
body: | ||
settings: | ||
index: | ||
number_of_shards: 1 | ||
number_of_replicas: 0 | ||
mappings: | ||
properties: | ||
k: | ||
type: keyword | ||
i: | ||
type: integer | ||
|
||
- do: | ||
bulk: | ||
index: test_index | ||
refresh: true | ||
body: | ||
- '{"index": {"_id": "1"}}' | ||
- '{"k": "k", "i" : 1}' | ||
- '{"index": {"_id": "2"}}' | ||
- '{"k": "kk", "i" : 2}' | ||
- '{"index": {"_id": "3"}}' | ||
- '{"k": "kkk", "i" : 3}' | ||
--- | ||
"Boost script_score": | ||
- do: | ||
search: | ||
index: test_index | ||
body: | ||
query: | ||
script_score: | ||
query: {match_all: {}} | ||
script: | ||
source: "doc['i'].value * _score" | ||
boost: 10 | ||
|
||
- match: { hits.total.value: 3 } | ||
- match: { hits.hits.0._score: 30 } | ||
- match: { hits.hits.1._score: 20 } | ||
- match: { hits.hits.2._score: 10 } | ||
|
||
--- | ||
"Boost script_score and boost internal query": | ||
- do: | ||
search: | ||
index: test_index | ||
body: | ||
query: | ||
script_score: | ||
query: {match_all: {boost: 10}} | ||
script: | ||
source: "doc['i'].value * _score" | ||
boost: 10 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe use a different score here, e.g.: |
||
|
||
- match: { hits.total.value: 3 } | ||
- match: { hits.hits.0._score: 300 } | ||
- match: { hits.hits.1._score: 200 } | ||
- match: { hits.hits.2._score: 100 } | ||
|
||
--- | ||
"Boost script_score with explain": | ||
- do: | ||
search: | ||
index: test_index | ||
body: | ||
explain: true | ||
query: | ||
script_score: | ||
query: {term: {"k": "kkk"}} | ||
script: | ||
source: "doc['i'].value" | ||
boost: 10 | ||
|
||
- match: { hits.total.value: 1 } | ||
- match: { hits.hits.0._score: 30 } | ||
- match: { hits.hits.0._explanation.value: 30 } | ||
- match: { hits.hits.0._explanation.details.0.description: "boost" } | ||
- match: { hits.hits.0._explanation.details.0.value: 10} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,9 @@ | |
import org.elasticsearch.script.Script; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
|
||
|
@@ -85,7 +88,7 @@ public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float bo | |
} | ||
boolean needsScore = scriptBuilder.needs_score(); | ||
ScoreMode subQueryScoreMode = needsScore ? ScoreMode.COMPLETE : ScoreMode.COMPLETE_NO_SCORES; | ||
Weight subQueryWeight = subQuery.createWeight(searcher, subQueryScoreMode, boost); | ||
Weight subQueryWeight = subQuery.createWeight(searcher, subQueryScoreMode, 1.0f); | ||
|
||
return new Weight(this){ | ||
@Override | ||
|
@@ -95,7 +98,7 @@ public BulkScorer bulkScorer(LeafReaderContext context) throws IOException { | |
if (subQueryBulkScorer == null) { | ||
return null; | ||
} | ||
return new ScriptScoreBulkScorer(subQueryBulkScorer, subQueryScoreMode, makeScoreScript(context)); | ||
return new ScriptScoreBulkScorer(subQueryBulkScorer, subQueryScoreMode, makeScoreScript(context), boost); | ||
} else { | ||
return super.bulkScorer(context); | ||
} | ||
|
@@ -112,7 +115,7 @@ public Scorer scorer(LeafReaderContext context) throws IOException { | |
if (subQueryScorer == null) { | ||
return null; | ||
} | ||
Scorer scriptScorer = new ScriptScorer(this, makeScoreScript(context), subQueryScorer, subQueryScoreMode, null); | ||
Scorer scriptScorer = new ScriptScorer(this, makeScoreScript(context), subQueryScorer, subQueryScoreMode, boost, null); | ||
if (minScore != null) { | ||
scriptScorer = new MinScoreScorer(this, scriptScorer, minScore); | ||
} | ||
|
@@ -127,12 +130,14 @@ public Explanation explain(LeafReaderContext context, int doc) throws IOExceptio | |
} | ||
ExplanationHolder explanationHolder = new ExplanationHolder(); | ||
Scorer scorer = new ScriptScorer(this, makeScoreScript(context), | ||
subQueryWeight.scorer(context), subQueryScoreMode, explanationHolder); | ||
subQueryWeight.scorer(context), subQueryScoreMode, boost, explanationHolder); | ||
int newDoc = scorer.iterator().advance(doc); | ||
assert doc == newDoc; // subquery should have already matched above | ||
float score = scorer.score(); | ||
float score = scorer.score(); // score already computed with boost | ||
|
||
Explanation explanation = explanationHolder.get(score, needsScore ? subQueryExplanation : null); | ||
|
||
|
||
if (explanation == null) { | ||
// no explanation provided by user; give a simple one | ||
String desc = "script score function, computed with script:\"" + script + "\""; | ||
|
@@ -143,7 +148,12 @@ public Explanation explain(LeafReaderContext context, int doc) throws IOExceptio | |
explanation = Explanation.match(score, desc); | ||
} | ||
} | ||
|
||
if (boost != 1.0f) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be executed also when Also, why not return an explanation if the boost is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way we handle
Not exactly. We start with
When is explanation is not asked (
I was thinking since |
||
List<Explanation> subs = new ArrayList<>(); | ||
subs.addAll(Arrays.asList(explanation.getDetails())); | ||
subs.add(Explanation.match(boost, "boost")); | ||
explanation = Explanation.match(explanation.getValue(), explanation.getDescription(), subs); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest wrapping the explanation instead of modifying it in-place: create the scorer with boost=1f a couple lines above, and then here: if (boost != 1f) {
explanation = Explanation.match(boost * explanation.getValue().floatValue(), "Boosted score, product of:",
Explanation.match(boost, "boost"),
explanation);
} |
||
if (minScore != null && minScore > explanation.getValue().floatValue()) { | ||
explanation = Explanation.noMatch("Score value is too low, expected at least " + minScore + | ||
" but got " + explanation.getValue(), explanation); | ||
|
@@ -203,30 +213,33 @@ public int hashCode() { | |
private static class ScriptScorer extends Scorer { | ||
private final ScoreScript scoreScript; | ||
private final Scorer subQueryScorer; | ||
private final float boost; | ||
private final ExplanationHolder explanation; | ||
|
||
ScriptScorer(Weight weight, ScoreScript scoreScript, Scorer subQueryScorer, | ||
ScoreMode subQueryScoreMode, ExplanationHolder explanation) { | ||
ScoreMode subQueryScoreMode, float boost, ExplanationHolder explanation) { | ||
super(weight); | ||
this.scoreScript = scoreScript; | ||
if (subQueryScoreMode == ScoreMode.COMPLETE) { | ||
scoreScript.setScorer(subQueryScorer); | ||
} | ||
this.subQueryScorer = subQueryScorer; | ||
this.boost = boost; | ||
this.explanation = explanation; | ||
} | ||
|
||
@Override | ||
public float score() throws IOException { | ||
int docId = docID(); | ||
scoreScript.setDocument(docId); | ||
float score = (float) scoreScript.execute(explanation); | ||
float score = (float) scoreScript.execute(explanation) * boost; | ||
if (score == Float.NEGATIVE_INFINITY || Float.isNaN(score)) { | ||
throw new ElasticsearchException( | ||
"script_score query returned an invalid score [" + score + "] for doc [" + docId + "]."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better if this error message returned the value produced by the script without the boost. |
||
} | ||
return score; | ||
} | ||
|
||
@Override | ||
public int docID() { | ||
return subQueryScorer.docID(); | ||
|
@@ -247,23 +260,25 @@ public float getMaxScore(int upTo) { | |
private static class ScriptScorable extends Scorable { | ||
private final ScoreScript scoreScript; | ||
private final Scorable subQueryScorer; | ||
private final float boost; | ||
private final ExplanationHolder explanation; | ||
|
||
ScriptScorable(ScoreScript scoreScript, Scorable subQueryScorer, | ||
ScoreMode subQueryScoreMode, ExplanationHolder explanation) { | ||
ScoreMode subQueryScoreMode, float boost, ExplanationHolder explanation) { | ||
this.scoreScript = scoreScript; | ||
if (subQueryScoreMode == ScoreMode.COMPLETE) { | ||
scoreScript.setScorer(subQueryScorer); | ||
} | ||
this.subQueryScorer = subQueryScorer; | ||
this.boost = boost; | ||
this.explanation = explanation; | ||
} | ||
|
||
@Override | ||
public float score() throws IOException { | ||
int docId = docID(); | ||
scoreScript.setDocument(docId); | ||
float score = (float) scoreScript.execute(explanation); | ||
float score = (float) scoreScript.execute(explanation) * boost; | ||
if (score == Float.NEGATIVE_INFINITY || Float.isNaN(score)) { | ||
throw new ElasticsearchException( | ||
"script_score query returned an invalid score [" + score + "] for doc [" + docId + "]."); | ||
|
@@ -284,11 +299,13 @@ private static class ScriptScoreBulkScorer extends BulkScorer { | |
private final BulkScorer subQueryBulkScorer; | ||
private final ScoreMode subQueryScoreMode; | ||
private final ScoreScript scoreScript; | ||
private final float boost; | ||
|
||
ScriptScoreBulkScorer(BulkScorer subQueryBulkScorer, ScoreMode subQueryScoreMode, ScoreScript scoreScript) { | ||
ScriptScoreBulkScorer(BulkScorer subQueryBulkScorer, ScoreMode subQueryScoreMode, ScoreScript scoreScript, float boost) { | ||
this.subQueryBulkScorer = subQueryBulkScorer; | ||
this.subQueryScoreMode = subQueryScoreMode; | ||
this.scoreScript = scoreScript; | ||
this.boost = boost; | ||
} | ||
|
||
@Override | ||
|
@@ -300,7 +317,7 @@ private LeafCollector wrapCollector(LeafCollector collector) { | |
return new FilterLeafCollector(collector) { | ||
@Override | ||
public void setScorer(Scorable scorer) throws IOException { | ||
in.setScorer(new ScriptScorable(scoreScript, scorer, subQueryScoreMode, null)); | ||
in.setScorer(new ScriptScorable(scoreScript, scorer, subQueryScoreMode, boost, null)); | ||
} | ||
}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why did you remove the reference to the score docs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before we were referencing relevance scores, while I think the goal of script_score query is to calculate custom scores through some scripts, not a traditional textual relevance score.