Skip to content

Commit 556ee9a

Browse files
Correct boost calculation in script_score query (#52478)
Before boost in script_score query was wrongly applied only to the subquery. This commit makes sure that the boost is applied to the whole score that comes out of script. Closes #48465
1 parent 841d961 commit 556ee9a

File tree

3 files changed

+119
-21
lines changed

3 files changed

+119
-21
lines changed

docs/reference/query-dsl/script-score-query.asciidoc

+4-1
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,12 @@ scores be positive or `0`.
4848
--
4949

5050
`min_score`::
51-
(Optional, float) Documents with a <<relevance-scores,relevance score>> lower
51+
(Optional, float) Documents with a score lower
5252
than this floating point number are excluded from the search results.
5353

54+
`boost`::
55+
(Optional, float) Documents' scores produced by `script` are
56+
multiplied by `boost` to produce final documents' scores. Defaults to `1.0`.
5457

5558
[[script-score-query-notes]]
5659
==== Notes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Integration tests for ScriptScoreQuery using Painless
2+
setup:
3+
- skip:
4+
version: " - 7.9.99"
5+
reason: "boost was corrected in script_score query from 8.0"
6+
- do:
7+
indices.create:
8+
index: test_index
9+
body:
10+
settings:
11+
index:
12+
number_of_shards: 1
13+
number_of_replicas: 0
14+
mappings:
15+
properties:
16+
k:
17+
type: keyword
18+
i:
19+
type: integer
20+
21+
- do:
22+
bulk:
23+
index: test_index
24+
refresh: true
25+
body:
26+
- '{"index": {"_id": "1"}}'
27+
- '{"k": "k", "i" : 1}'
28+
- '{"index": {"_id": "2"}}'
29+
- '{"k": "kk", "i" : 2}'
30+
- '{"index": {"_id": "3"}}'
31+
- '{"k": "kkk", "i" : 3}'
32+
---
33+
"Boost script_score":
34+
- do:
35+
search:
36+
index: test_index
37+
body:
38+
query:
39+
script_score:
40+
query: {match_all: {}}
41+
script:
42+
source: "doc['i'].value * _score"
43+
boost: 10
44+
45+
- match: { hits.total.value: 3 }
46+
- match: { hits.hits.0._score: 30 }
47+
- match: { hits.hits.1._score: 20 }
48+
- match: { hits.hits.2._score: 10 }
49+
50+
---
51+
"Boost script_score and boost internal query":
52+
- do:
53+
search:
54+
index: test_index
55+
body:
56+
query:
57+
script_score:
58+
query: {match_all: {boost: 5}}
59+
script:
60+
source: "doc['i'].value * _score"
61+
boost: 10
62+
63+
- match: { hits.total.value: 3 }
64+
- match: { hits.hits.0._score: 150 }
65+
- match: { hits.hits.1._score: 100 }
66+
- match: { hits.hits.2._score: 50 }
67+
68+
---
69+
"Boost script_score with explain":
70+
- do:
71+
search:
72+
index: test_index
73+
body:
74+
explain: true
75+
query:
76+
script_score:
77+
query: {term: {"k": "kkk"}}
78+
script:
79+
source: "doc['i'].value"
80+
boost: 10
81+
82+
- match: { hits.total.value: 1 }
83+
- match: { hits.hits.0._score: 30 }
84+
- match: { hits.hits.0._explanation.value: 30 }
85+
- match: { hits.hits.0._explanation.details.0.description: "boost" }
86+
- match: { hits.hits.0._explanation.details.0.value: 10}

server/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreQuery.java

+29-20
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.apache.lucene.search.Scorer;
3737
import org.apache.lucene.search.BulkScorer;
3838
import org.apache.lucene.util.Bits;
39-
import org.elasticsearch.ElasticsearchException;
4039
import org.elasticsearch.Version;
4140
import org.elasticsearch.script.ScoreScript;
4241
import org.elasticsearch.script.ScoreScript.ExplanationHolder;
@@ -85,7 +84,7 @@ public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float bo
8584
}
8685
boolean needsScore = scriptBuilder.needs_score();
8786
ScoreMode subQueryScoreMode = needsScore ? ScoreMode.COMPLETE : ScoreMode.COMPLETE_NO_SCORES;
88-
Weight subQueryWeight = subQuery.createWeight(searcher, subQueryScoreMode, boost);
87+
Weight subQueryWeight = subQuery.createWeight(searcher, subQueryScoreMode, 1.0f);
8988

9089
return new Weight(this){
9190
@Override
@@ -95,7 +94,7 @@ public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
9594
if (subQueryBulkScorer == null) {
9695
return null;
9796
}
98-
return new ScriptScoreBulkScorer(subQueryBulkScorer, subQueryScoreMode, makeScoreScript(context));
97+
return new ScriptScoreBulkScorer(subQueryBulkScorer, subQueryScoreMode, makeScoreScript(context), boost);
9998
} else {
10099
return super.bulkScorer(context);
101100
}
@@ -112,7 +111,7 @@ public Scorer scorer(LeafReaderContext context) throws IOException {
112111
if (subQueryScorer == null) {
113112
return null;
114113
}
115-
Scorer scriptScorer = new ScriptScorer(this, makeScoreScript(context), subQueryScorer, subQueryScoreMode, null);
114+
Scorer scriptScorer = new ScriptScorer(this, makeScoreScript(context), subQueryScorer, subQueryScoreMode, boost, null);
116115
if (minScore != null) {
117116
scriptScorer = new MinScoreScorer(this, scriptScorer, minScore);
118117
}
@@ -127,11 +126,11 @@ public Explanation explain(LeafReaderContext context, int doc) throws IOExceptio
127126
}
128127
ExplanationHolder explanationHolder = new ExplanationHolder();
129128
Scorer scorer = new ScriptScorer(this, makeScoreScript(context),
130-
subQueryWeight.scorer(context), subQueryScoreMode, explanationHolder);
129+
subQueryWeight.scorer(context), subQueryScoreMode, 1f, explanationHolder);
131130
int newDoc = scorer.iterator().advance(doc);
132131
assert doc == newDoc; // subquery should have already matched above
133-
float score = scorer.score();
134-
132+
float score = scorer.score(); // score without boost
133+
135134
Explanation explanation = explanationHolder.get(score, needsScore ? subQueryExplanation : null);
136135
if (explanation == null) {
137136
// no explanation provided by user; give a simple one
@@ -143,7 +142,10 @@ public Explanation explain(LeafReaderContext context, int doc) throws IOExceptio
143142
explanation = Explanation.match(score, desc);
144143
}
145144
}
146-
145+
if (boost != 1f) {
146+
explanation = Explanation.match(boost * explanation.getValue().floatValue(), "Boosted score, product of:",
147+
Explanation.match(boost, "boost"), explanation);
148+
}
147149
if (minScore != null && minScore > explanation.getValue().floatValue()) {
148150
explanation = Explanation.noMatch("Score value is too low, expected at least " + minScore +
149151
" but got " + explanation.getValue(), explanation);
@@ -203,16 +205,18 @@ public int hashCode() {
203205
private static class ScriptScorer extends Scorer {
204206
private final ScoreScript scoreScript;
205207
private final Scorer subQueryScorer;
208+
private final float boost;
206209
private final ExplanationHolder explanation;
207210

208211
ScriptScorer(Weight weight, ScoreScript scoreScript, Scorer subQueryScorer,
209-
ScoreMode subQueryScoreMode, ExplanationHolder explanation) {
212+
ScoreMode subQueryScoreMode, float boost, ExplanationHolder explanation) {
210213
super(weight);
211214
this.scoreScript = scoreScript;
212215
if (subQueryScoreMode == ScoreMode.COMPLETE) {
213216
scoreScript.setScorer(subQueryScorer);
214217
}
215218
this.subQueryScorer = subQueryScorer;
219+
this.boost = boost;
216220
this.explanation = explanation;
217221
}
218222

@@ -221,12 +225,13 @@ public float score() throws IOException {
221225
int docId = docID();
222226
scoreScript.setDocument(docId);
223227
float score = (float) scoreScript.execute(explanation);
224-
if (score == Float.NEGATIVE_INFINITY || Float.isNaN(score)) {
225-
throw new ElasticsearchException(
226-
"script_score query returned an invalid score [" + score + "] for doc [" + docId + "].");
228+
if (score < 0f || Float.isNaN(score)) {
229+
throw new IllegalArgumentException("script_score script returned an invalid score [" + score + "] " +
230+
"for doc [" + docId + "]. Must be a non-negative score!");
227231
}
228-
return score;
232+
return score * boost;
229233
}
234+
230235
@Override
231236
public int docID() {
232237
return subQueryScorer.docID();
@@ -247,15 +252,17 @@ public float getMaxScore(int upTo) {
247252
private static class ScriptScorable extends Scorable {
248253
private final ScoreScript scoreScript;
249254
private final Scorable subQueryScorer;
255+
private final float boost;
250256
private final ExplanationHolder explanation;
251257

252258
ScriptScorable(ScoreScript scoreScript, Scorable subQueryScorer,
253-
ScoreMode subQueryScoreMode, ExplanationHolder explanation) {
259+
ScoreMode subQueryScoreMode, float boost, ExplanationHolder explanation) {
254260
this.scoreScript = scoreScript;
255261
if (subQueryScoreMode == ScoreMode.COMPLETE) {
256262
scoreScript.setScorer(subQueryScorer);
257263
}
258264
this.subQueryScorer = subQueryScorer;
265+
this.boost = boost;
259266
this.explanation = explanation;
260267
}
261268

@@ -264,11 +271,11 @@ public float score() throws IOException {
264271
int docId = docID();
265272
scoreScript.setDocument(docId);
266273
float score = (float) scoreScript.execute(explanation);
267-
if (score == Float.NEGATIVE_INFINITY || Float.isNaN(score)) {
268-
throw new ElasticsearchException(
269-
"script_score query returned an invalid score [" + score + "] for doc [" + docId + "].");
274+
if (score < 0f || Float.isNaN(score)) {
275+
throw new IllegalArgumentException("script_score script returned an invalid score [" + score + "] " +
276+
"for doc [" + docId + "]. Must be a non-negative score!");
270277
}
271-
return score;
278+
return score * boost;
272279
}
273280
@Override
274281
public int docID() {
@@ -284,11 +291,13 @@ private static class ScriptScoreBulkScorer extends BulkScorer {
284291
private final BulkScorer subQueryBulkScorer;
285292
private final ScoreMode subQueryScoreMode;
286293
private final ScoreScript scoreScript;
294+
private final float boost;
287295

288-
ScriptScoreBulkScorer(BulkScorer subQueryBulkScorer, ScoreMode subQueryScoreMode, ScoreScript scoreScript) {
296+
ScriptScoreBulkScorer(BulkScorer subQueryBulkScorer, ScoreMode subQueryScoreMode, ScoreScript scoreScript, float boost) {
289297
this.subQueryBulkScorer = subQueryBulkScorer;
290298
this.subQueryScoreMode = subQueryScoreMode;
291299
this.scoreScript = scoreScript;
300+
this.boost = boost;
292301
}
293302

294303
@Override
@@ -300,7 +309,7 @@ private LeafCollector wrapCollector(LeafCollector collector) {
300309
return new FilterLeafCollector(collector) {
301310
@Override
302311
public void setScorer(Scorable scorer) throws IOException {
303-
in.setScorer(new ScriptScorable(scoreScript, scorer, subQueryScoreMode, null));
312+
in.setScorer(new ScriptScorable(scoreScript, scorer, subQueryScoreMode, boost, null));
304313
}
305314
};
306315
}

0 commit comments

Comments
 (0)