Skip to content

Commit b6014d9

Browse files
Forbid negative scores in functon_score query (#35709)
* Forbid negative scores in functon_score query - Throw an exception when scores are negative in field_value_factor function - Throw an exception when scores are negative in script_score function Relates to #33309
1 parent 92acf47 commit b6014d9

File tree

7 files changed

+75
-2
lines changed

7 files changed

+75
-2
lines changed

docs/reference/migration/migrate_7_0/search.asciidoc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,12 @@ instead which is a more appropriate value for a scenario where scores are not av
148148
==== Negative boosts are not allowed
149149

150150
Setting a negative `boost` in a query, deprecated in 6x, are not allowed in this version.
151-
To deboost a specific query you can use a `boost` comprise between 0 and 1.
151+
To deboost a specific query you can use a `boost` comprise between 0 and 1.
152+
153+
[float]
154+
==== Negative scores are not allowed in Function Score Query
155+
156+
Negative scores in the Function Score Query are deprecated in 6.x, and are
157+
not allowed in this version. If a negative score is produced as a result
158+
of computation (e.g. in `script_score` or `field_value_factor` functions),
159+
an error will be thrown.

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ GET /_search
153153
// CONSOLE
154154
// TEST[setup:twitter]
155155

156+
NOTE: Scores produced by the `script_score` function must be non-negative,
157+
otherwise an error will be thrown.
158+
156159
On top of the different scripting field values and expression, the
157160
`_score` script parameter can be used to retrieve the score based on the
158161
wrapped query.
@@ -324,6 +327,9 @@ There are a number of options for the `field_value_factor` function:
324327
values of the field with a range filter to avoid this, or use `log1p` and
325328
`ln1p`.
326329

330+
NOTE: Scores produced by the `field_value_score` function must be non-negative,
331+
otherwise an error will be thrown.
332+
327333
[[function-decay]]
328334
==== Decay functions
329335

modules/lang-painless/src/test/resources/rest-api-spec/test/painless/30_search.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,41 @@
442442
- match: { error.root_cause.0.reason: "Iterable object is self-referencing itself (ScriptBytesValues value)" }
443443
- match: { error.type: "search_phase_execution_exception" }
444444
- match: { error.reason: "all shards failed" }
445+
446+
447+
---
448+
449+
"Exception on negative score":
450+
- skip:
451+
version: " - 6.99.99"
452+
reason: "check on negative scores was added from 7.0.0 on"
453+
454+
- do:
455+
index:
456+
index: test
457+
type: test
458+
id: 1
459+
body: { "test": "value beck", "num1": 1.0 }
460+
- do:
461+
indices.refresh: {}
462+
463+
- do:
464+
catch: bad_request
465+
search:
466+
index: test
467+
body:
468+
query:
469+
function_score:
470+
query:
471+
term:
472+
test: value
473+
"functions": [{
474+
"script_score": {
475+
"script": {
476+
"lang": "painless",
477+
"source": "doc['num1'].value - 10.0"
478+
}
479+
}
480+
}]
481+
- match: { error.root_cause.0.type: "illegal_argument_exception" }
482+
- match: { error.root_cause.0.reason: "script score function must not produce negative scores, but got: [-9.0]"}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ public double score(int docId, float subQueryScore) throws IOException {
8383
}
8484
double val = value * boostFactor;
8585
double result = modifier.apply(val);
86+
if (result < 0f) {
87+
throw new IllegalArgumentException("field value function must not produce negative scores, but got: [" +
88+
result + "] for field value: [" + value + "]");
89+
}
8690
return result;
8791
}
8892

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ public float score() throws IOException {
382382
}
383383
double factor = computeScore(docId, subQueryScore);
384384
float finalScore = scoreCombiner.combine(subQueryScore, factor, maxBoost);
385-
if (finalScore == Float.NEGATIVE_INFINITY || Float.isNaN(finalScore)) {
385+
if (finalScore < 0f || Float.isNaN(finalScore)) {
386386
/*
387387
These scores are invalid for score based {@link org.apache.lucene.search.TopDocsCollector}s.
388388
See {@link org.apache.lucene.search.TopScoreDocCollector} for details.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ public double score(int docId, float subQueryScore) throws IOException {
6969
scorer.docid = docId;
7070
scorer.score = subQueryScore;
7171
double result = leafScript.execute();
72+
if (result < 0f) {
73+
throw new IllegalArgumentException("script score function must not produce negative scores, but got: [" + result + "]");
74+
}
7275
return result;
7376
}
7477

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,20 @@ public void testWithInvalidScores() {
824824
assertThat(exc.getMessage(), containsString("function score query returned an invalid score: " + Float.NEGATIVE_INFINITY));
825825
}
826826

827+
828+
public void testExceptionOnNegativeScores() {
829+
IndexSearcher localSearcher = new IndexSearcher(reader);
830+
TermQuery termQuery = new TermQuery(new Term(FIELD, "out"));
831+
832+
// test that field_value_factor function throws an exception on negative scores
833+
FieldValueFactorFunction.Modifier modifier = FieldValueFactorFunction.Modifier.NONE;
834+
final ScoreFunction fvfFunction = new FieldValueFactorFunction(FIELD, -10, modifier, 1.0, new IndexNumericFieldDataStub());
835+
FunctionScoreQuery fsQuery1 =
836+
new FunctionScoreQuery(termQuery, fvfFunction, CombineFunction.REPLACE, null, Float.POSITIVE_INFINITY);
837+
IllegalArgumentException exc = expectThrows(IllegalArgumentException.class, () -> localSearcher.search(fsQuery1, 1));
838+
assertThat(exc.getMessage(), containsString("field value function must not produce negative scores"));
839+
}
840+
827841
private static class DummyScoreFunction extends ScoreFunction {
828842
protected DummyScoreFunction(CombineFunction scoreCombiner) {
829843
super(scoreCombiner);

0 commit comments

Comments
 (0)