diff --git a/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java index 766911bb747..86b70c72238 100644 --- a/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java @@ -197,22 +197,22 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder scoreFunction; - if (parseContext.parseFieldMatcher().match(currentFieldName, WEIGHT_FIELD)) { - scoreFunction = new WeightBuilder().setWeight(parser.floatValue()); + } else if (token == XContentParser.Token.START_OBJECT) { + if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) { + 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 (parseContext.parseFieldMatcher().match(currentFieldName, FUNCTIONS_FIELD)) { + 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 (parseContext.parseFieldMatcher().match(currentFieldName, SCORE_MODE_FIELD)) { + scoreMode = FiltersFunctionScoreQuery.ScoreMode.fromString(parser.text()); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, BOOST_MODE_FIELD)) { + combineFunction = CombineFunction.fromString(parser.text()); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, MAX_BOOST_FIELD)) { + maxBoost = parser.floatValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) { + boost = parser.floatValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) { + queryName = parser.text(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, MIN_SCORE_FIELD)) { + 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 +195,23 @@ public class FunctionScoreQueryParser implements QueryParser> context.reset(parser); context.parseFieldMatcher(matcher); QueryBuilder parseInnerQueryBuilder = context.parseInnerQueryBuilder(); - assertTrue(parser.nextToken() == null); + assertNull(parser.nextToken()); return parseInnerQueryBuilder; } diff --git a/core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java index bc268c6e47a..2632f6272fe 100644 --- a/core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java @@ -20,7 +20,6 @@ package org.elasticsearch.index.query.functionscore; import com.fasterxml.jackson.core.JsonParseException; - import org.apache.lucene.index.Term; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; @@ -59,7 +58,6 @@ import java.util.Map; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.index.query.QueryBuilders.functionScoreQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery; -import static org.elasticsearch.test.StreamsUtils.copyToStringFromClasspath; import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.either; @@ -72,7 +70,7 @@ public class FunctionScoreQueryBuilderTests extends AbstractQueryTestCase)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