From a624410450d65ce377177777e0ce5d220e6f7770 Mon Sep 17 00:00:00 2001 From: javanna Date: Thu, 11 Feb 2016 17:59:50 +0100 Subject: [PATCH] Function Score Query: make parsing stricter Function Score Query now checks the type of token that we are parsing, which makes parsing stricter and allows to throw useful errors in case the json is malformed. It also makes code more readable as in what gets parsed when. Closes #16583 --- .../FunctionScoreQueryParser.java | 123 +++--- .../FunctionScoreQueryBuilderTests.java | 413 +++++++++++------- .../query/faulty-function-score-query.json | 15 - 3 files changed, 323 insertions(+), 228 deletions(-) delete mode 100644 core/src/test/resources/org/elasticsearch/index/query/faulty-function-score-query.json diff --git a/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java index 8e503e24d3c..3e6ffd2c880 100644 --- a/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java @@ -19,10 +19,6 @@ package org.elasticsearch.index.query.functionscore; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; @@ -39,6 +35,10 @@ import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryParser; import org.elasticsearch.index.query.functionscore.weight.WeightBuilder; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + /** * Parser for function_score query */ @@ -86,48 +86,69 @@ public class FunctionScoreQueryParser implements QueryParser scoreFunction; - if (parseContext.parseFieldMatcher().match(currentFieldName, WEIGHT_FIELD)) { - scoreFunction = new WeightBuilder().setWeight(parser.floatValue()); + } else if (token == XContentParser.Token.START_OBJECT) { + if ("query".equals(currentFieldName)) { + if (query != null) { + throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. [query] is already defined.", FunctionScoreQueryBuilder.NAME); + } + query = parseContext.parseInnerQueryBuilder(); } else { - // we try to parse a score function. If there is no score - // function for the current field name, - // functionParserMapper.get() will throw an Exception. - scoreFunction = functionParserMapper.get(parser.getTokenLocation(), currentFieldName).fromXContent(parseContext, parser); + if (singleFunctionFound) { + throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. already found function [{}], now encountering [{}]. use [functions] array if you want to define several functions.", FunctionScoreQueryBuilder.NAME, singleFunctionName, currentFieldName); + } + if (functionArrayFound) { + String errorString = "already found [functions] array, now encountering [" + currentFieldName + "]."; + handleMisplacedFunctionsDeclaration(parser.getTokenLocation(), errorString); + } + singleFunctionFound = true; + singleFunctionName = currentFieldName; + + // we try to parse a score function. If there is no score function for the current field name, + // functionParserMapper.get() may throw an Exception. + ScoreFunctionBuilder scoreFunction = functionParserMapper.get(parser.getTokenLocation(), currentFieldName).fromXContent(parseContext, parser); + filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(scoreFunction)); + } + } else if (token == XContentParser.Token.START_ARRAY) { + if ("functions".equals(currentFieldName)) { + if (singleFunctionFound) { + String errorString = "already found [" + singleFunctionName + "], now encountering [functions]."; + handleMisplacedFunctionsDeclaration(parser.getTokenLocation(), errorString); + } + functionArrayFound = true; + currentFieldName = parseFiltersAndFunctions(parseContext, parser, filterFunctionBuilders); + } else { + throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. array [{}] is not supported", FunctionScoreQueryBuilder.NAME, currentFieldName); + } + + } else if (token.isValue()) { + if ("score_mode".equals(currentFieldName) || "scoreMode".equals(currentFieldName)) { + scoreMode = FiltersFunctionScoreQuery.ScoreMode.fromString(parser.text()); + } else if ("boost_mode".equals(currentFieldName) || "boostMode".equals(currentFieldName)) { + combineFunction = CombineFunction.fromString(parser.text()); + } else if ("max_boost".equals(currentFieldName) || "maxBoost".equals(currentFieldName)) { + maxBoost = parser.floatValue(); + } else if ("boost".equals(currentFieldName)) { + boost = parser.floatValue(); + } else if ("_name".equals(currentFieldName)) { + queryName = parser.text(); + } else if ("min_score".equals(currentFieldName) || "minScore".equals(currentFieldName)) { + minScore = parser.floatValue(); + } else { + if (singleFunctionFound) { + throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. already found function [{}], now encountering [{}]. use [functions] array if you want to define several functions.", FunctionScoreQueryBuilder.NAME, singleFunctionName, currentFieldName); + } + if (functionArrayFound) { + String errorString = "already found [functions] array, now encountering [" + currentFieldName + "]."; + handleMisplacedFunctionsDeclaration(parser.getTokenLocation(), errorString); + } + if (parseContext.parseFieldMatcher().match(currentFieldName, WEIGHT_FIELD)) { + filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(new WeightBuilder().setWeight(parser.floatValue()))); + singleFunctionFound = true; + singleFunctionName = currentFieldName; + } else { + throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. field [{}] is not supported", FunctionScoreQueryBuilder.NAME, currentFieldName); + } } - filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(scoreFunction)); } } @@ -167,21 +188,23 @@ public class FunctionScoreQueryParser implements QueryParser)null); + new FunctionScoreQueryBuilder((QueryBuilder) null); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { - new FunctionScoreQueryBuilder((ScoreFunctionBuilder)null); + new FunctionScoreQueryBuilder((ScoreFunctionBuilder) null); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { - new FunctionScoreQueryBuilder((FunctionScoreQueryBuilder.FilterFunctionBuilder[])null); + new FunctionScoreQueryBuilder((FunctionScoreQueryBuilder.FilterFunctionBuilder[]) null); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { new FunctionScoreQueryBuilder(null, ScoreFunctionBuilders.randomFunction(123)); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { - new FunctionScoreQueryBuilder(new MatchAllQueryBuilder(), (ScoreFunctionBuilder)null); + new FunctionScoreQueryBuilder(new MatchAllQueryBuilder(), (ScoreFunctionBuilder) null); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { - new FunctionScoreQueryBuilder(new MatchAllQueryBuilder(), (FunctionScoreQueryBuilder.FilterFunctionBuilder[])null); + new FunctionScoreQueryBuilder(new MatchAllQueryBuilder(), (FunctionScoreQueryBuilder.FilterFunctionBuilder[]) null); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { new FunctionScoreQueryBuilder(null, new FunctionScoreQueryBuilder.FilterFunctionBuilder[0]); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { new FunctionScoreQueryBuilder(QueryBuilders.matchAllQuery(), new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{null}); fail("content of array must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { new FunctionScoreQueryBuilder.FilterFunctionBuilder(null); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { new FunctionScoreQueryBuilder.FilterFunctionBuilder(null, ScoreFunctionBuilders.randomFunction(123)); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { new FunctionScoreQueryBuilder.FilterFunctionBuilder(new MatchAllQueryBuilder(), null); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { new FunctionScoreQueryBuilder(new MatchAllQueryBuilder()).scoreMode(null); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } try { new FunctionScoreQueryBuilder(new MatchAllQueryBuilder()).boostMode(null); fail("must not be null"); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { //all good } } public void testParseFunctionsArray() throws IOException { String functionScoreQuery = "{\n" + - " \"function_score\":{\n" + - " \"query\":{\n" + - " \"term\":{\n" + - " \"field1\":\"value1\"\n" + - " }\n" + - " },\n" + - " \"functions\": [\n" + - " {\n" + - " \"random_score\": {\n" + - " \"seed\":123456\n" + - " },\n" + - " \"weight\": 3,\n" + - " \"filter\": {\n" + - " \"term\":{\n" + - " \"field2\":\"value2\"\n" + - " }\n" + - " }\n" + - " },\n" + - " {\n" + - " \"filter\": {\n" + - " \"term\":{\n" + - " \"field3\":\"value3\"\n" + - " }\n" + - " },\n" + - " \"weight\": 9\n" + - " },\n" + - " {\n" + - " \"gauss\": {\n" + - " \"field_name\": {\n" + - " \"origin\":0.5,\n" + - " \"scale\":0.6\n" + - " }\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"boost\" : 3,\n" + - " \"score_mode\" : \"avg\",\n" + - " \"boost_mode\" : \"replace\",\n" + - " \"max_boost\" : 10\n" + - " }\n" + - "}"; + " \"function_score\":{\n" + + " \"query\":{\n" + + " \"term\":{\n" + + " \"field1\":\"value1\"\n" + + " }\n" + + " },\n" + + " \"functions\": [\n" + + " {\n" + + " \"random_score\": {\n" + + " \"seed\":123456\n" + + " },\n" + + " \"weight\": 3,\n" + + " \"filter\": {\n" + + " \"term\":{\n" + + " \"field2\":\"value2\"\n" + + " }\n" + + " }\n" + + " },\n" + + " {\n" + + " \"filter\": {\n" + + " \"term\":{\n" + + " \"field3\":\"value3\"\n" + + " }\n" + + " },\n" + + " \"weight\": 9\n" + + " },\n" + + " {\n" + + " \"gauss\": {\n" + + " \"field_name\": {\n" + + " \"origin\":0.5,\n" + + " \"scale\":0.6\n" + + " }\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"boost\" : 3,\n" + + " \"score_mode\" : \"avg\",\n" + + " \"boost_mode\" : \"replace\",\n" + + " \"max_boost\" : 10\n" + + " }\n" + + "}"; QueryBuilder queryBuilder = parseQuery(functionScoreQuery); //given that we copy part of the decay functions as bytes, we test that fromXContent and toXContent both work no matter what the initial format was @@ -368,31 +366,31 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase)queryBuilder).buildAsBytes(XContentType.values()[i])); + queryBuilder = parseQuery(((AbstractQueryBuilder) queryBuilder).buildAsBytes(XContentType.values()[i])); } } } public void testParseSingleFunction() throws IOException { String functionScoreQuery = "{\n" + - " \"function_score\":{\n" + - " \"query\":{\n" + - " \"term\":{\n" + - " \"field1\":\"value1\"\n" + - " }\n" + - " },\n" + - " \"gauss\": {\n" + - " \"field_name\": {\n" + - " \"origin\":0.5,\n" + - " \"scale\":0.6\n" + - " }\n" + - " },\n" + - " \"boost\" : 3,\n" + - " \"score_mode\" : \"avg\",\n" + - " \"boost_mode\" : \"replace\",\n" + - " \"max_boost\" : 10\n" + - " }\n" + - "}"; + " \"function_score\":{\n" + + " \"query\":{\n" + + " \"term\":{\n" + + " \"field1\":\"value1\"\n" + + " }\n" + + " },\n" + + " \"gauss\": {\n" + + " \"field_name\": {\n" + + " \"origin\":0.5,\n" + + " \"scale\":0.6\n" + + " }\n" + + " },\n" + + " \"boost\" : 3,\n" + + " \"score_mode\" : \"avg\",\n" + + " \"boost_mode\" : \"replace\",\n" + + " \"max_boost\" : 10\n" + + " }\n" + + "}"; QueryBuilder queryBuilder = parseQuery(functionScoreQuery); //given that we copy part of the decay functions as bytes, we test that fromXContent and toXContent both work no matter what the initial format was @@ -415,7 +413,7 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase)queryBuilder).buildAsBytes(XContentType.values()[i])); + queryBuilder = parseQuery(((AbstractQueryBuilder) queryBuilder).buildAsBytes(XContentType.values()[i])); } } } @@ -423,69 +421,69 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase 0); String queryString = jsonBuilder().startObject() - .startObject("function_score") - .startArray("functions") - .startObject() - .startObject("field_value_factor") - .field("field", INT_FIELD_NAME) - .endObject() - .field("weight", 1.0) - .endObject() - .endArray() - .endObject() - .endObject().string(); + .startObject("function_score") + .startArray("functions") + .startObject() + .startObject("field_value_factor") + .field("field", INT_FIELD_NAME) + .endObject() + .field("weight", 1.0) + .endObject() + .endArray() + .endObject() + .endObject().string(); QueryBuilder query = parseQuery(queryString); assertThat(query, instanceOf(FunctionScoreQueryBuilder.class)); FunctionScoreQueryBuilder functionScoreQueryBuilder = (FunctionScoreQueryBuilder) query; @@ -526,11 +524,11 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase