diff --git a/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java b/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java index 5345f60b85b..8b4801d4457 100644 --- a/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java +++ b/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java @@ -22,6 +22,7 @@ package org.elasticsearch.common.lucene.search.function; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.Scorer; +import org.elasticsearch.script.ScriptException; import org.elasticsearch.script.SearchScript; import java.io.IOException; @@ -96,7 +97,11 @@ public class ScriptScoreFunction extends ScoreFunction { script.setNextDocId(docId); scorer.docid = docId; scorer.score = subQueryScore; - return script.runAsDouble(); + double result = script.runAsDouble(); + if (Double.isNaN(result)) { + throw new ScriptException("script_score returned NaN"); + } + return result; } @Override diff --git a/src/test/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunctionTests.java b/src/test/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunctionTests.java new file mode 100644 index 00000000000..a1b8d5e1070 --- /dev/null +++ b/src/test/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunctionTests.java @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.lucene.search.function; + +import org.elasticsearch.script.AbstractFloatSearchScript; +import org.elasticsearch.script.ScriptException; +import org.elasticsearch.test.ElasticsearchTestCase; +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; + +public class ScriptScoreFunctionTests extends ElasticsearchTestCase { + + /** + * Tests https://github.com/elasticsearch/elasticsearch/issues/2426 + */ + @Test + public void testScriptScoresReturnsNaN() { + try { + ScoreFunction scoreFunction = new ScriptScoreFunction("Float.NaN", null, new FloatValueScript(Float.NaN)); + scoreFunction.score(randomInt(), randomFloat()); + fail("should have thrown an exception about the script_score returning NaN"); + } catch (ScriptException e) { + assertThat("message contains error about script_score returning NaN: " + e.getMessage(), + e.getMessage().contains("NaN"), equalTo(true)); + } + } + + static class FloatValueScript extends AbstractFloatSearchScript { + + private final float value; + + FloatValueScript(float value) { + this.value = value; + } + + @Override + public float runAsFloat() { + return value; + } + + @Override + public void setNextDocId(int doc) { + // nothing here + } + } +} diff --git a/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreTests.java b/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreTests.java index 93e9d5017bb..409a1548f25 100644 --- a/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreTests.java +++ b/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreTests.java @@ -169,7 +169,7 @@ public class FunctionScoreTests extends ElasticsearchIntegrationTest { assertThat(responseWithWeights.getHits().getAt(0).getExplanation().toString(), equalTo("6.0 = (MATCH) function score, product of:\n 1.0 = (MATCH) ConstantScore(text_field:value), product of:\n 1.0 = boost\n 1.0 = queryNorm\n 6.0 = (MATCH) Math.min of\n 6.0 = (MATCH) function score, score mode [multiply]\n 1.0 = (MATCH) function score, product of:\n 1.0 = match filter: *:*\n 1.0 = (MATCH) Function for field geo_point_field:\n 1.0 = exp(-0.5*pow(MIN of: [Math.max(arcDistance([10.0, 20.0](=doc value),[10.0, 20.0](=origin)) - 0.0(=offset), 0)],2.0)/7.213475204444817E11)\n 2.0 = (MATCH) function score, product of:\n 1.0 = match filter: *:*\n 2.0 = (MATCH) product of:\n 1.0 = field value function: ln(doc['double_field'].value * factor=1.0)\n 2.0 = weight\n 3.0 = (MATCH) function score, product of:\n 1.0 = match filter: *:*\n 3.0 = (MATCH) product of:\n 1.0 = script score function, computed with script:\"_index['text_field']['value'].tf()\n 3.0 = weight\n 3.4028235E38 = maxBoost\n 1.0 = queryBoost\n") - ); + ); responseWithWeights = client().search( searchRequest().source( searchSource().query(