diff --git a/core/src/main/java/org/elasticsearch/index/query/MatchQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/MatchQueryBuilder.java index 9184281607d..4d8d9bb3033 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MatchQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/MatchQueryBuilder.java @@ -23,13 +23,17 @@ import org.apache.lucene.queries.ExtendedCommonTermsQuery; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.FuzzyQuery; import org.apache.lucene.search.Query; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.support.QueryParsers; import org.elasticsearch.index.search.MatchQuery; +import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery; import java.io.IOException; import java.util.Locale; @@ -40,6 +44,21 @@ import java.util.Objects; * can construct different queries based on the type provided. */ public class MatchQueryBuilder extends AbstractQueryBuilder { + public static final ParseField MATCH_PHRASE_FIELD = new ParseField("match_phrase", "text_phrase"); + public static final ParseField MATCH_PHRASE_PREFIX_FIELD = new ParseField("match_phrase_prefix", "text_phrase_prefix"); + public static final ParseField SLOP_FIELD = new ParseField("slop", "phrase_slop"); + public static final ParseField ZERO_TERMS_QUERY_FIELD = new ParseField("zero_terms_query"); + public static final ParseField CUTOFF_FREQUENCY_FIELD = new ParseField("cutoff_frequency"); + public static final ParseField LENIENT_FIELD = new ParseField("lenient"); + public static final ParseField FUZZY_TRANSPOSITIONS_FIELD = new ParseField("fuzzy_transpositions"); + public static final ParseField FUZZY_REWRITE_FIELD = new ParseField("fuzzy_rewrite"); + public static final ParseField MINIMUM_SHOULD_MATCH_FIELD = new ParseField("minimum_should_match"); + public static final ParseField OPERATOR_FIELD = new ParseField("operator"); + public static final ParseField MAX_EXPANSIONS_FIELD = new ParseField("max_expansions"); + public static final ParseField PREFIX_LENGTH_FIELD = new ParseField("prefix_length"); + public static final ParseField ANALYZER_FIELD = new ParseField("analyzer"); + public static final ParseField TYPE_FIELD = new ParseField("type"); + public static final ParseField QUERY_FIELD = new ParseField("query"); /** The default name for the match query */ public static final String NAME = "match"; @@ -80,7 +99,7 @@ public class MatchQueryBuilder extends AbstractQueryBuilder { private Float cutoffFrequency = null; - static final MatchQueryBuilder PROTOTYPE = new MatchQueryBuilder("",""); + public static final MatchQueryBuilder PROTOTYPE = new MatchQueryBuilder("",""); /** * Constructs a new match query. @@ -317,30 +336,30 @@ public class MatchQueryBuilder extends AbstractQueryBuilder { builder.startObject(NAME); builder.startObject(fieldName); - builder.field(MatchQueryParser.QUERY_FIELD.getPreferredName(), value); - builder.field(MatchQueryParser.TYPE_FIELD.getPreferredName(), type.toString().toLowerCase(Locale.ENGLISH)); - builder.field(MatchQueryParser.OPERATOR_FIELD.getPreferredName(), operator.toString()); + builder.field(QUERY_FIELD.getPreferredName(), value); + builder.field(TYPE_FIELD.getPreferredName(), type.toString().toLowerCase(Locale.ENGLISH)); + builder.field(OPERATOR_FIELD.getPreferredName(), operator.toString()); if (analyzer != null) { - builder.field(MatchQueryParser.ANALYZER_FIELD.getPreferredName(), analyzer); + builder.field(ANALYZER_FIELD.getPreferredName(), analyzer); } - builder.field(MatchQueryParser.SLOP_FIELD.getPreferredName(), slop); + builder.field(SLOP_FIELD.getPreferredName(), slop); if (fuzziness != null) { fuzziness.toXContent(builder, params); } - builder.field(MatchQueryParser.PREFIX_LENGTH_FIELD.getPreferredName(), prefixLength); - builder.field(MatchQueryParser.MAX_EXPANSIONS_FIELD.getPreferredName(), maxExpansions); + builder.field(PREFIX_LENGTH_FIELD.getPreferredName(), prefixLength); + builder.field(MAX_EXPANSIONS_FIELD.getPreferredName(), maxExpansions); if (minimumShouldMatch != null) { - builder.field(MatchQueryParser.MINIMUM_SHOULD_MATCH_FIELD.getPreferredName(), minimumShouldMatch); + builder.field(MINIMUM_SHOULD_MATCH_FIELD.getPreferredName(), minimumShouldMatch); } if (fuzzyRewrite != null) { - builder.field(MatchQueryParser.FUZZY_REWRITE_FIELD.getPreferredName(), fuzzyRewrite); + builder.field(FUZZY_REWRITE_FIELD.getPreferredName(), fuzzyRewrite); } // LUCENE 4 UPGRADE we need to document this & test this - builder.field(MatchQueryParser.FUZZY_TRANSPOSITIONS_FIELD.getPreferredName(), fuzzyTranspositions); - builder.field(MatchQueryParser.LENIENT_FIELD.getPreferredName(), lenient); - builder.field(MatchQueryParser.ZERO_TERMS_QUERY_FIELD.getPreferredName(), zeroTermsQuery.toString()); + builder.field(FUZZY_TRANSPOSITIONS_FIELD.getPreferredName(), fuzzyTranspositions); + builder.field(LENIENT_FIELD.getPreferredName(), lenient); + builder.field(ZERO_TERMS_QUERY_FIELD.getPreferredName(), zeroTermsQuery.toString()); if (cutoffFrequency != null) { - builder.field(MatchQueryParser.CUTOFF_FREQUENCY_FIELD.getPreferredName(), cutoffFrequency); + builder.field(CUTOFF_FREQUENCY_FIELD.getPreferredName(), cutoffFrequency); } printBoostAndQueryName(builder); builder.endObject(); @@ -467,4 +486,141 @@ public class MatchQueryBuilder extends AbstractQueryBuilder { public String getWriteableName() { return NAME; } + + public static MatchQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException { + XContentParser parser = parseContext.parser(); + + MatchQuery.Type type = MatchQuery.Type.BOOLEAN; + if (parseContext.parseFieldMatcher().match(parser.currentName(), MATCH_PHRASE_FIELD)) { + type = MatchQuery.Type.PHRASE; + } else if (parseContext.parseFieldMatcher().match(parser.currentName(), MATCH_PHRASE_PREFIX_FIELD)) { + type = MatchQuery.Type.PHRASE_PREFIX; + } + + XContentParser.Token token = parser.nextToken(); + if (token != XContentParser.Token.FIELD_NAME) { + throw new ParsingException(parser.getTokenLocation(), "[" + MatchQueryBuilder.NAME + "] query malformed, no field"); + } + String fieldName = parser.currentName(); + + Object value = null; + float boost = AbstractQueryBuilder.DEFAULT_BOOST; + String minimumShouldMatch = null; + String analyzer = null; + Operator operator = MatchQueryBuilder.DEFAULT_OPERATOR; + int slop = MatchQuery.DEFAULT_PHRASE_SLOP; + Fuzziness fuzziness = null; + int prefixLength = FuzzyQuery.defaultPrefixLength; + int maxExpansion = FuzzyQuery.defaultMaxExpansions; + boolean fuzzyTranspositions = FuzzyQuery.defaultTranspositions; + String fuzzyRewrite = null; + boolean lenient = MatchQuery.DEFAULT_LENIENCY; + Float cutOffFrequency = null; + ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY; + String queryName = null; + + token = parser.nextToken(); + if (token == XContentParser.Token.START_OBJECT) { + String currentFieldName = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token.isValue()) { + if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) { + value = parser.objectText(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, TYPE_FIELD)) { + String tStr = parser.text(); + if ("boolean".equals(tStr)) { + type = MatchQuery.Type.BOOLEAN; + } else if ("phrase".equals(tStr)) { + type = MatchQuery.Type.PHRASE; + } else if ("phrase_prefix".equals(tStr) || ("phrasePrefix".equals(tStr))) { + type = MatchQuery.Type.PHRASE_PREFIX; + } else { + throw new ParsingException(parser.getTokenLocation(), + "[" + MatchQueryBuilder.NAME + "] query does not support type " + tStr); + } + } else if (parseContext.parseFieldMatcher().match(currentFieldName, ANALYZER_FIELD)) { + analyzer = parser.text(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) { + boost = parser.floatValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, SLOP_FIELD)) { + slop = parser.intValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fuzziness.FIELD)) { + fuzziness = Fuzziness.parse(parser); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, PREFIX_LENGTH_FIELD)) { + prefixLength = parser.intValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, MAX_EXPANSIONS_FIELD)) { + maxExpansion = parser.intValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, OPERATOR_FIELD)) { + operator = Operator.fromString(parser.text()); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, MINIMUM_SHOULD_MATCH_FIELD)) { + minimumShouldMatch = parser.textOrNull(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZY_REWRITE_FIELD)) { + fuzzyRewrite = parser.textOrNull(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZY_TRANSPOSITIONS_FIELD)) { + fuzzyTranspositions = parser.booleanValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, LENIENT_FIELD)) { + lenient = parser.booleanValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, CUTOFF_FREQUENCY_FIELD)) { + cutOffFrequency = parser.floatValue(); + } else if (parseContext.parseFieldMatcher().match(currentFieldName, ZERO_TERMS_QUERY_FIELD)) { + String zeroTermsDocs = parser.text(); + if ("none".equalsIgnoreCase(zeroTermsDocs)) { + zeroTermsQuery = MatchQuery.ZeroTermsQuery.NONE; + } else if ("all".equalsIgnoreCase(zeroTermsDocs)) { + zeroTermsQuery = MatchQuery.ZeroTermsQuery.ALL; + } else { + throw new ParsingException(parser.getTokenLocation(), + "Unsupported zero_terms_docs value [" + zeroTermsDocs + "]"); + } + } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) { + queryName = parser.text(); + } else { + throw new ParsingException(parser.getTokenLocation(), + "[" + MatchQueryBuilder.NAME + "] query does not support [" + currentFieldName + "]"); + } + } else { + throw new ParsingException(parser.getTokenLocation(), + "[" + MatchQueryBuilder.NAME + "] unknown token [" + token + "] after [" + currentFieldName + "]"); + } + } + parser.nextToken(); + } else { + value = parser.objectText(); + // move to the next token + token = parser.nextToken(); + if (token != XContentParser.Token.END_OBJECT) { + throw new ParsingException(parser.getTokenLocation(), "[match] query parsed in simplified form, with direct field name, " + + "but included more options than just the field name, possibly use its 'options' form, with 'query' element?"); + } + } + + if (value == null) { + throw new ParsingException(parser.getTokenLocation(), "No text specified for text query"); + } + + MatchQueryBuilder matchQuery = new MatchQueryBuilder(fieldName, value); + matchQuery.operator(operator); + matchQuery.type(type); + matchQuery.analyzer(analyzer); + matchQuery.slop(slop); + matchQuery.minimumShouldMatch(minimumShouldMatch); + if (fuzziness != null) { + matchQuery.fuzziness(fuzziness); + } + matchQuery.fuzzyRewrite(fuzzyRewrite); + matchQuery.prefixLength(prefixLength); + matchQuery.fuzzyTranspositions(fuzzyTranspositions); + matchQuery.maxExpansions(maxExpansion); + matchQuery.lenient(lenient); + if (cutOffFrequency != null) { + matchQuery.cutoffFrequency(cutOffFrequency); + } + matchQuery.zeroTermsQuery(zeroTermsQuery); + matchQuery.queryName(queryName); + matchQuery.boost(boost); + return matchQuery; + } + } diff --git a/core/src/main/java/org/elasticsearch/index/query/MatchQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/MatchQueryParser.java deleted file mode 100644 index 4b149dd6be3..00000000000 --- a/core/src/main/java/org/elasticsearch/index/query/MatchQueryParser.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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.index.query; - -import org.apache.lucene.search.FuzzyQuery; -import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.ParsingException; -import org.elasticsearch.common.unit.Fuzziness; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.search.MatchQuery; -import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery; - -import java.io.IOException; - -/** - * - */ -public class MatchQueryParser implements QueryParser { - - public static final ParseField MATCH_PHRASE_FIELD = new ParseField("match_phrase", "text_phrase"); - public static final ParseField MATCH_PHRASE_PREFIX_FIELD = new ParseField("match_phrase_prefix", "text_phrase_prefix"); - public static final ParseField SLOP_FIELD = new ParseField("slop", "phrase_slop"); - public static final ParseField ZERO_TERMS_QUERY_FIELD = new ParseField("zero_terms_query"); - public static final ParseField CUTOFF_FREQUENCY_FIELD = new ParseField("cutoff_frequency"); - public static final ParseField LENIENT_FIELD = new ParseField("lenient"); - public static final ParseField FUZZY_TRANSPOSITIONS_FIELD = new ParseField("fuzzy_transpositions"); - public static final ParseField FUZZY_REWRITE_FIELD = new ParseField("fuzzy_rewrite"); - public static final ParseField MINIMUM_SHOULD_MATCH_FIELD = new ParseField("minimum_should_match"); - public static final ParseField OPERATOR_FIELD = new ParseField("operator"); - public static final ParseField MAX_EXPANSIONS_FIELD = new ParseField("max_expansions"); - public static final ParseField PREFIX_LENGTH_FIELD = new ParseField("prefix_length"); - public static final ParseField ANALYZER_FIELD = new ParseField("analyzer"); - public static final ParseField TYPE_FIELD = new ParseField("type"); - public static final ParseField QUERY_FIELD = new ParseField("query"); - - @Override - public String[] names() { - return new String[]{ - MatchQueryBuilder.NAME, "match_phrase", "matchPhrase", "match_phrase_prefix", "matchPhrasePrefix", "matchFuzzy", "match_fuzzy", "fuzzy_match" - }; - } - - @Override - public MatchQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException { - XContentParser parser = parseContext.parser(); - - MatchQuery.Type type = MatchQuery.Type.BOOLEAN; - if (parseContext.parseFieldMatcher().match(parser.currentName(), MATCH_PHRASE_FIELD)) { - type = MatchQuery.Type.PHRASE; - } else if (parseContext.parseFieldMatcher().match(parser.currentName(), MATCH_PHRASE_PREFIX_FIELD)) { - type = MatchQuery.Type.PHRASE_PREFIX; - } - - XContentParser.Token token = parser.nextToken(); - if (token != XContentParser.Token.FIELD_NAME) { - throw new ParsingException(parser.getTokenLocation(), "[" + MatchQueryBuilder.NAME + "] query malformed, no field"); - } - String fieldName = parser.currentName(); - - Object value = null; - float boost = AbstractQueryBuilder.DEFAULT_BOOST; - String minimumShouldMatch = null; - String analyzer = null; - Operator operator = MatchQueryBuilder.DEFAULT_OPERATOR; - int slop = MatchQuery.DEFAULT_PHRASE_SLOP; - Fuzziness fuzziness = null; - int prefixLength = FuzzyQuery.defaultPrefixLength; - int maxExpansion = FuzzyQuery.defaultMaxExpansions; - boolean fuzzyTranspositions = FuzzyQuery.defaultTranspositions; - String fuzzyRewrite = null; - boolean lenient = MatchQuery.DEFAULT_LENIENCY; - Float cutOffFrequency = null; - ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY; - String queryName = null; - - token = parser.nextToken(); - if (token == XContentParser.Token.START_OBJECT) { - String currentFieldName = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token.isValue()) { - if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) { - value = parser.objectText(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, TYPE_FIELD)) { - String tStr = parser.text(); - if ("boolean".equals(tStr)) { - type = MatchQuery.Type.BOOLEAN; - } else if ("phrase".equals(tStr)) { - type = MatchQuery.Type.PHRASE; - } else if ("phrase_prefix".equals(tStr) || ("phrasePrefix".equals(tStr))) { - type = MatchQuery.Type.PHRASE_PREFIX; - } else { - throw new ParsingException(parser.getTokenLocation(), "[" + MatchQueryBuilder.NAME + "] query does not support type " + tStr); - } - } else if (parseContext.parseFieldMatcher().match(currentFieldName, ANALYZER_FIELD)) { - analyzer = parser.text(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) { - boost = parser.floatValue(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, SLOP_FIELD)) { - slop = parser.intValue(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fuzziness.FIELD)) { - fuzziness = Fuzziness.parse(parser); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, PREFIX_LENGTH_FIELD)) { - prefixLength = parser.intValue(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, MAX_EXPANSIONS_FIELD)) { - maxExpansion = parser.intValue(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, OPERATOR_FIELD)) { - operator = Operator.fromString(parser.text()); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, MINIMUM_SHOULD_MATCH_FIELD)) { - minimumShouldMatch = parser.textOrNull(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZY_REWRITE_FIELD)) { - fuzzyRewrite = parser.textOrNull(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZY_TRANSPOSITIONS_FIELD)) { - fuzzyTranspositions = parser.booleanValue(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, LENIENT_FIELD)) { - lenient = parser.booleanValue(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, CUTOFF_FREQUENCY_FIELD)) { - cutOffFrequency = parser.floatValue(); - } else if (parseContext.parseFieldMatcher().match(currentFieldName, ZERO_TERMS_QUERY_FIELD)) { - String zeroTermsDocs = parser.text(); - if ("none".equalsIgnoreCase(zeroTermsDocs)) { - zeroTermsQuery = MatchQuery.ZeroTermsQuery.NONE; - } else if ("all".equalsIgnoreCase(zeroTermsDocs)) { - zeroTermsQuery = MatchQuery.ZeroTermsQuery.ALL; - } else { - throw new ParsingException(parser.getTokenLocation(), "Unsupported zero_terms_docs value [" + zeroTermsDocs + "]"); - } - } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) { - queryName = parser.text(); - } else { - throw new ParsingException(parser.getTokenLocation(), "[" + MatchQueryBuilder.NAME + "] query does not support [" + currentFieldName + "]"); - } - } else { - throw new ParsingException(parser.getTokenLocation(), "[" + MatchQueryBuilder.NAME + "] unknown token [" + token + "] after [" + currentFieldName + "]"); - } - } - parser.nextToken(); - } else { - value = parser.objectText(); - // move to the next token - token = parser.nextToken(); - if (token != XContentParser.Token.END_OBJECT) { - throw new ParsingException(parser.getTokenLocation(), - "[match] query parsed in simplified form, with direct field name, but included more options than just the field name, possibly use its 'options' form, with 'query' element?"); - } - } - - if (value == null) { - throw new ParsingException(parser.getTokenLocation(), "No text specified for text query"); - } - - MatchQueryBuilder matchQuery = new MatchQueryBuilder(fieldName, value); - matchQuery.operator(operator); - matchQuery.type(type); - matchQuery.analyzer(analyzer); - matchQuery.slop(slop); - matchQuery.minimumShouldMatch(minimumShouldMatch); - if (fuzziness != null) { - matchQuery.fuzziness(fuzziness); - } - matchQuery.fuzzyRewrite(fuzzyRewrite); - matchQuery.prefixLength(prefixLength); - matchQuery.fuzzyTranspositions(fuzzyTranspositions); - matchQuery.maxExpansions(maxExpansion); - matchQuery.lenient(lenient); - if (cutOffFrequency != null) { - matchQuery.cutoffFrequency(cutOffFrequency); - } - matchQuery.zeroTermsQuery(zeroTermsQuery); - matchQuery.queryName(queryName); - matchQuery.boost(boost); - return matchQuery; - } - - @Override - public MatchQueryBuilder getBuilderPrototype() { - return MatchQueryBuilder.PROTOTYPE; - } -} diff --git a/core/src/main/java/org/elasticsearch/index/query/MultiMatchQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/MultiMatchQueryParser.java index 31054013a8e..bb3ef5fa17d 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MultiMatchQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/MultiMatchQueryParser.java @@ -30,7 +30,7 @@ import java.util.HashMap; import java.util.Map; /** - * Same as {@link MatchQueryParser} but has support for multiple fields. + * Same as {@link MatchQueryBuilder} but has support for multiple fields. */ public class MultiMatchQueryParser implements QueryParser { diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryParser.java b/core/src/main/java/org/elasticsearch/index/query/QueryParser.java index 1226564bb3b..74365d659aa 100644 --- a/core/src/main/java/org/elasticsearch/index/query/QueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/QueryParser.java @@ -25,12 +25,15 @@ import java.io.IOException; * Defines a query parser that is able to read and parse a query object in {@link org.elasticsearch.common.xcontent.XContent} * format and create an internal object representing the query, implementing {@link QueryBuilder}, which can be streamed to other nodes. */ +@FunctionalInterface public interface QueryParser> { /** * The names this query parser is registered under. */ - String[] names(); + default String[] names() { // TODO remove this when nothing implements it + throw new UnsupportedOperationException(); + } /** * Creates a new {@link QueryBuilder} from the query held by the {@link QueryParseContext} @@ -47,5 +50,7 @@ public interface QueryParser> { /** * @return an empty {@link QueryBuilder} instance for this parser that can be used for deserialization */ - QB getBuilderPrototype(); + default QB getBuilderPrototype() { // TODO remove this when nothing implements it + throw new UnsupportedOperationException(); + } } 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 9957a0bc1f4..5b3a2faccbf 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 @@ -21,6 +21,8 @@ package org.elasticsearch.index.query.functionscore; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; @@ -30,18 +32,25 @@ import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; import org.elasticsearch.common.lucene.search.function.ScoreFunction; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentLocation; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.AbstractQueryBuilder; import org.elasticsearch.index.query.EmptyQueryBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryRewriteContext; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.functionscore.random.RandomScoreFunctionBuilder; +import org.elasticsearch.index.query.functionscore.weight.WeightBuilder; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.function.Function; /** * A query that uses a filters with a script associated with them to compute the @@ -49,8 +58,23 @@ import java.util.Objects; */ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder { + public static final FunctionScoreQueryBuilder PROTOTYPE = new FunctionScoreQueryBuilder(EmptyQueryBuilder.PROTOTYPE, + new FunctionScoreQueryBuilder.FilterFunctionBuilder[0]); + public static final String NAME = "function_score"; + // For better readability of error message + static final String MISPLACED_FUNCTION_MESSAGE_PREFIX = "you can either define [functions] array or a single function, not both. "; + + public static final ParseField WEIGHT_FIELD = new ParseField("weight"); + public static final ParseField QUERY_FIELD = new ParseField("query"); + public static final ParseField FILTER_FIELD = new ParseField("filter"); + public static final ParseField FUNCTIONS_FIELD = new ParseField("functions"); + public static final ParseField SCORE_MODE_FIELD = new ParseField("score_mode"); + public static final ParseField BOOST_MODE_FIELD = new ParseField("boost_mode"); + public static final ParseField MAX_BOOST_FIELD = new ParseField("max_boost"); + public static final ParseField MIN_SCORE_FIELD = new ParseField("min_score"); + public static final CombineFunction DEFAULT_BOOST_MODE = CombineFunction.MULTIPLY; public static final FiltersFunctionScoreQuery.ScoreMode DEFAULT_SCORE_MODE = FiltersFunctionScoreQuery.ScoreMode.MULTIPLY; @@ -198,22 +222,22 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder> scoreFunctionLookup, + QueryParseContext parseContext) throws IOException { + XContentParser parser = parseContext.parser(); + + QueryBuilder query = null; + float boost = AbstractQueryBuilder.DEFAULT_BOOST; + String queryName = null; + + FiltersFunctionScoreQuery.ScoreMode scoreMode = FunctionScoreQueryBuilder.DEFAULT_SCORE_MODE; + float maxBoost = FunctionScoreQuery.DEFAULT_MAX_BOOST; + Float minScore = null; + + String currentFieldName = null; + XContentParser.Token token; + CombineFunction combineFunction = null; + // Either define array of functions and filters or only one function + boolean functionArrayFound = false; + boolean singleFunctionFound = false; + String singleFunctionName = null; + List filterFunctionBuilders = new ArrayList<>(); + + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } 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 { + 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 = lookupFunctionParser(scoreFunctionLookup, 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(scoreFunctionLookup, 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); + } + } + } + } + + if (query == null) { + query = new MatchAllQueryBuilder(); + } + + FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(query, + filterFunctionBuilders.toArray(new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()])); + if (combineFunction != null) { + functionScoreQueryBuilder.boostMode(combineFunction); + } + functionScoreQueryBuilder.scoreMode(scoreMode); + functionScoreQueryBuilder.maxBoost(maxBoost); + if (minScore != null) { + functionScoreQueryBuilder.setMinScore(minScore); + } + functionScoreQueryBuilder.boost(boost); + functionScoreQueryBuilder.queryName(queryName); + return functionScoreQueryBuilder; + } + + private static void handleMisplacedFunctionsDeclaration(XContentLocation contentLocation, String errorString) { + throw new ParsingException(contentLocation, "failed to parse [{}] query. [{}]", FunctionScoreQueryBuilder.NAME, MISPLACED_FUNCTION_MESSAGE_PREFIX + errorString); + } + + private static String parseFiltersAndFunctions(Function> scoreFunctionLookup, + QueryParseContext parseContext, XContentParser parser, + List filterFunctionBuilders) throws IOException { + String currentFieldName = null; + XContentParser.Token token; + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + QueryBuilder filter = null; + ScoreFunctionBuilder scoreFunction = null; + Float functionWeight = null; + if (token != XContentParser.Token.START_OBJECT) { + throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}]. malformed query, expected a [{}] while parsing functions but got a [{}] instead", XContentParser.Token.START_OBJECT, token, FunctionScoreQueryBuilder.NAME); + } else { + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token == XContentParser.Token.START_OBJECT) { + if (parseContext.parseFieldMatcher().match(currentFieldName, FILTER_FIELD)) { + filter = parseContext.parseInnerQueryBuilder(); + } else { + if (scoreFunction != null) { + throw new ParsingException(parser.getTokenLocation(), "failed to parse function_score functions. already found [{}], now encountering [{}].", scoreFunction.getName(), currentFieldName); + } + // do not need to check null here, functionParserMapper does it already + ScoreFunctionParser functionParser = lookupFunctionParser(scoreFunctionLookup, parser.getTokenLocation(), currentFieldName); + scoreFunction = functionParser.fromXContent(parseContext, parser); + } + } else if (token.isValue()) { + if (parseContext.parseFieldMatcher().match(currentFieldName, WEIGHT_FIELD)) { + functionWeight = parser.floatValue(); + } else { + throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. field [{}] is not supported", FunctionScoreQueryBuilder.NAME, currentFieldName); + } + } + } + if (functionWeight != null) { + if (scoreFunction == null) { + scoreFunction = new WeightBuilder().setWeight(functionWeight); + } else { + scoreFunction.setWeight(functionWeight); + } + } + } + if (filter == null) { + filter = new MatchAllQueryBuilder(); + } + if (scoreFunction == null) { + throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. an entry in functions list is missing a function.", FunctionScoreQueryBuilder.NAME); + } + filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(filter, scoreFunction)); + } + return currentFieldName; + } + + private static ScoreFunctionParser lookupFunctionParser(Function> scoreFunctionLookup, + XContentLocation contentLocation, String parserName) { + ScoreFunctionParser functionParser = scoreFunctionLookup.apply(parserName); + if (functionParser == null) { + throw new ParsingException(contentLocation, "No function with the name [" + parserName + "] is registered."); + } + return functionParser; + } } 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 deleted file mode 100644 index d408db4b6e4..00000000000 --- a/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 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.index.query.functionscore; - -import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.ParsingException; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.lucene.search.function.CombineFunction; -import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery; -import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; -import org.elasticsearch.common.xcontent.XContentLocation; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.query.AbstractQueryBuilder; -import org.elasticsearch.index.query.EmptyQueryBuilder; -import org.elasticsearch.index.query.MatchAllQueryBuilder; -import org.elasticsearch.index.query.QueryBuilder; -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 - */ -public class FunctionScoreQueryParser implements QueryParser { - - private static final FunctionScoreQueryBuilder PROTOTYPE = new FunctionScoreQueryBuilder(EmptyQueryBuilder.PROTOTYPE, new FunctionScoreQueryBuilder.FilterFunctionBuilder[0]); - - // For better readability of error message - static final String MISPLACED_FUNCTION_MESSAGE_PREFIX = "you can either define [functions] array or a single function, not both. "; - - public static final ParseField WEIGHT_FIELD = new ParseField("weight"); - public static final ParseField QUERY_FIELD = new ParseField("query"); - public static final ParseField FILTER_FIELD = new ParseField("filter"); - public static final ParseField FUNCTIONS_FIELD = new ParseField("functions"); - public static final ParseField SCORE_MODE_FIELD = new ParseField("score_mode"); - public static final ParseField BOOST_MODE_FIELD = new ParseField("boost_mode"); - public static final ParseField MAX_BOOST_FIELD = new ParseField("max_boost"); - public static final ParseField MIN_SCORE_FIELD = new ParseField("min_score"); - - private final ScoreFunctionParserMapper functionParserMapper; - - public FunctionScoreQueryParser(ScoreFunctionParserMapper functionParserMapper) { - this.functionParserMapper = functionParserMapper; - } - - @Override - public String[] names() { - return new String[] { FunctionScoreQueryBuilder.NAME, Strings.toCamelCase(FunctionScoreQueryBuilder.NAME) }; - } - - @Override - public FunctionScoreQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException { - XContentParser parser = parseContext.parser(); - - QueryBuilder query = null; - float boost = AbstractQueryBuilder.DEFAULT_BOOST; - String queryName = null; - - FiltersFunctionScoreQuery.ScoreMode scoreMode = FunctionScoreQueryBuilder.DEFAULT_SCORE_MODE; - float maxBoost = FunctionScoreQuery.DEFAULT_MAX_BOOST; - Float minScore = null; - - String currentFieldName = null; - XContentParser.Token token; - CombineFunction combineFunction = null; - // Either define array of functions and filters or only one function - boolean functionArrayFound = false; - boolean singleFunctionFound = false; - String singleFunctionName = null; - List filterFunctionBuilders = new ArrayList<>(); - - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } 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 { - 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); - } - } - } - } - - if (query == null) { - query = new MatchAllQueryBuilder(); - } - - FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(query, - filterFunctionBuilders.toArray(new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()])); - if (combineFunction != null) { - functionScoreQueryBuilder.boostMode(combineFunction); - } - functionScoreQueryBuilder.scoreMode(scoreMode); - functionScoreQueryBuilder.maxBoost(maxBoost); - if (minScore != null) { - functionScoreQueryBuilder.setMinScore(minScore); - } - functionScoreQueryBuilder.boost(boost); - functionScoreQueryBuilder.queryName(queryName); - return functionScoreQueryBuilder; - } - - private static void handleMisplacedFunctionsDeclaration(XContentLocation contentLocation, String errorString) { - throw new ParsingException(contentLocation, "failed to parse [{}] query. [{}]", FunctionScoreQueryBuilder.NAME, MISPLACED_FUNCTION_MESSAGE_PREFIX + errorString); - } - - private String parseFiltersAndFunctions(QueryParseContext parseContext, XContentParser parser, List filterFunctionBuilders) throws IOException { - String currentFieldName = null; - XContentParser.Token token; - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - QueryBuilder filter = null; - ScoreFunctionBuilder scoreFunction = null; - Float functionWeight = null; - if (token != XContentParser.Token.START_OBJECT) { - throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}]. malformed query, expected a [{}] while parsing functions but got a [{}] instead", XContentParser.Token.START_OBJECT, token, FunctionScoreQueryBuilder.NAME); - } else { - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token == XContentParser.Token.START_OBJECT) { - if (parseContext.parseFieldMatcher().match(currentFieldName, FILTER_FIELD)) { - filter = parseContext.parseInnerQueryBuilder(); - } else { - if (scoreFunction != null) { - throw new ParsingException(parser.getTokenLocation(), "failed to parse function_score functions. already found [{}], now encountering [{}].", scoreFunction.getName(), currentFieldName); - } - // do not need to check null here, functionParserMapper does it already - ScoreFunctionParser functionParser = functionParserMapper.get(parser.getTokenLocation(), currentFieldName); - scoreFunction = functionParser.fromXContent(parseContext, parser); - } - } else if (token.isValue()) { - if (parseContext.parseFieldMatcher().match(currentFieldName, WEIGHT_FIELD)) { - functionWeight = parser.floatValue(); - } else { - throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. field [{}] is not supported", FunctionScoreQueryBuilder.NAME, currentFieldName); - } - } - } - if (functionWeight != null) { - if (scoreFunction == null) { - scoreFunction = new WeightBuilder().setWeight(functionWeight); - } else { - scoreFunction.setWeight(functionWeight); - } - } - } - if (filter == null) { - filter = new MatchAllQueryBuilder(); - } - if (scoreFunction == null) { - throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. an entry in functions list is missing a function.", FunctionScoreQueryBuilder.NAME); - } - filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(filter, scoreFunction)); - } - return currentFieldName; - } - - @Override - public FunctionScoreQueryBuilder getBuilderPrototype() { - return PROTOTYPE; - } -} diff --git a/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionBuilder.java b/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionBuilder.java index c2346cc6a31..326daf95769 100644 --- a/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionBuilder.java @@ -48,7 +48,7 @@ public abstract class ScoreFunctionBuilder impl protected void buildWeight(XContentBuilder builder) throws IOException { if (weight != null) { - builder.field(FunctionScoreQueryParser.WEIGHT_FIELD.getPreferredName(), weight); + builder.field(FunctionScoreQueryBuilder.WEIGHT_FIELD.getPreferredName(), weight); } } diff --git a/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionParser.java b/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionParser.java index 52324b9654b..5df51a1e473 100644 --- a/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionParser.java @@ -34,7 +34,7 @@ public interface ScoreFunctionParser> { /** * Returns the name of the function, for example "linear", "gauss" etc. This * name is used for registering the parser in - * {@link FunctionScoreQueryParser}. + * {@link FunctionScoreQueryBuilder}. * */ String[] getNames(); } diff --git a/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionParserMapper.java b/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionParserMapper.java deleted file mode 100644 index eeabbb12184..00000000000 --- a/core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionParserMapper.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.index.query.functionscore; - -import java.util.Map; - -import org.elasticsearch.common.ParsingException; -import org.elasticsearch.common.xcontent.XContentLocation; - -import static java.util.Collections.unmodifiableMap; - -public class ScoreFunctionParserMapper { - - protected Map> functionParsers; - - public ScoreFunctionParserMapper(Map> functionParsers) { - this.functionParsers = unmodifiableMap(functionParsers); - } - - public ScoreFunctionParser get(XContentLocation contentLocation, String parserName) { - ScoreFunctionParser functionParser = get(parserName); - if (functionParser == null) { - throw new ParsingException(contentLocation, "No function with the name [" + parserName + "] is registered."); - } - return functionParser; - } - - private ScoreFunctionParser get(String parserName) { - return functionParsers.get(parserName); - } -} diff --git a/core/src/main/java/org/elasticsearch/search/SearchModule.java b/core/src/main/java/org/elasticsearch/search/SearchModule.java index 1b69a155e15..8622605fa6c 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchModule.java +++ b/core/src/main/java/org/elasticsearch/search/SearchModule.java @@ -20,12 +20,15 @@ package org.elasticsearch.search; import org.apache.lucene.search.BooleanQuery; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.geo.ShapesAvailability; import org.elasticsearch.common.geo.builders.ShapeBuilders; import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.inject.multibindings.Multibinder; import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.percolator.PercolatorHighlightSubFetchPhase; import org.elasticsearch.index.query.BoolQueryParser; @@ -49,7 +52,7 @@ import org.elasticsearch.index.query.IdsQueryParser; import org.elasticsearch.index.query.IndicesQueryParser; import org.elasticsearch.index.query.MatchAllQueryParser; import org.elasticsearch.index.query.MatchNoneQueryParser; -import org.elasticsearch.index.query.MatchQueryParser; +import org.elasticsearch.index.query.MatchQueryBuilder; import org.elasticsearch.index.query.MoreLikeThisQueryParser; import org.elasticsearch.index.query.MultiMatchQueryParser; import org.elasticsearch.index.query.NestedQueryParser; @@ -57,6 +60,7 @@ import org.elasticsearch.index.query.ParentIdQueryParser; import org.elasticsearch.index.query.PercolatorQueryParser; import org.elasticsearch.index.query.PrefixQueryParser; import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryParser; import org.elasticsearch.index.query.QueryStringQueryParser; import org.elasticsearch.index.query.RangeQueryParser; @@ -77,10 +81,9 @@ import org.elasticsearch.index.query.TermsQueryParser; import org.elasticsearch.index.query.TypeQueryParser; import org.elasticsearch.index.query.WildcardQueryParser; import org.elasticsearch.index.query.WrapperQueryParser; -import org.elasticsearch.index.query.functionscore.FunctionScoreQueryParser; +import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder; import org.elasticsearch.index.query.functionscore.ScoreFunctionParser; -import org.elasticsearch.index.query.functionscore.ScoreFunctionParserMapper; import org.elasticsearch.index.query.functionscore.exp.ExponentialDecayFunctionParser; import org.elasticsearch.index.query.functionscore.fieldvaluefactor.FieldValueFactorFunctionParser; import org.elasticsearch.index.query.functionscore.gauss.GaussDecayFunctionParser; @@ -224,14 +227,6 @@ import org.elasticsearch.search.sort.ScriptSortBuilder; import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.search.suggest.Suggester; import org.elasticsearch.search.suggest.Suggesters; -import org.elasticsearch.search.suggest.SuggestionBuilder; -import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder; -import org.elasticsearch.search.suggest.phrase.Laplace; -import org.elasticsearch.search.suggest.phrase.LinearInterpolation; -import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder; -import org.elasticsearch.search.suggest.phrase.SmoothingModel; -import org.elasticsearch.search.suggest.phrase.StupidBackoff; -import org.elasticsearch.search.suggest.term.TermSuggestionBuilder; import java.util.ArrayList; import java.util.HashMap; @@ -260,6 +255,7 @@ public class SearchModule extends AbstractModule { * at configure time because they depend on things that are registered by * plugins (function score parsers). */ + private final List> queries = new ArrayList<>(); private final List>> queryParsers = new ArrayList<>(); private final Set> fetchSubPhases = new HashSet<>(); private final Set heuristicParsers = new HashSet<>(); @@ -304,6 +300,23 @@ public class SearchModule extends AbstractModule { namedWriteableRegistry.registerPrototype(ScoreFunctionBuilder.class, sfb); } + /** + * Register a query. + * + * @param reader the reader registered for this query's builder. Typically a reference to a constructor that takes a + * {@link org.elasticsearch.common.io.stream.StreamInput} + * @param parser the parser the reads the query builder from xcontent + * @param names all names by which this query might be parsed. The first name is special as it is the name by under which the reader is + * registered. So it is the name that the query should use as its {@link NamedWriteable#getWriteableName()}. + */ + public > void registerQuery(Writeable.Reader reader, QueryParser parser, String... names) { + queries.add(new QueryRegistration(names, reader, parser)); + } + + /** + * Register a query. + * TODO remove this in favor of registerQuery + */ public void registerQueryParser(Supplier> parser) { queryParsers.add(parser); } @@ -364,16 +377,28 @@ public class SearchModule extends AbstractModule { public IndicesQueriesRegistry buildQueryParserRegistry() { Map> queryParsersMap = new HashMap<>(); + + // TODO remove this when we retire registerQueryParser for (Supplier> parserSupplier : queryParsers) { - QueryParser parser = parserSupplier.get(); + QueryParser> parser = parserSupplier.get(); for (String name: parser.names()) { Object oldValue = queryParsersMap.putIfAbsent(name, parser); if (oldValue != null) { throw new IllegalArgumentException("Query parser [" + oldValue + "] already registered for name [" + name + "] while trying to register [" + parser + "]"); } } - @SuppressWarnings("unchecked") NamedWriteable qb = parser.getBuilderPrototype(); - namedWriteableRegistry.registerPrototype(QueryBuilder.class, qb); + namedWriteableRegistry.registerPrototype(QueryBuilder.class, parser.getBuilderPrototype()); + } + + for (QueryRegistration query : queries) { + QueryParser> parser = query.parser; + for (String name: query.names) { + Object oldValue = queryParsersMap.putIfAbsent(name, parser); + if (oldValue != null) { + throw new IllegalArgumentException("Query parser [" + oldValue + "] already registered for name [" + name + "] while trying to register [" + parser + "]"); + } + } + namedWriteableRegistry.register(QueryBuilder.class, query.names[0], query.reader); } return new IndicesQueriesRegistry(settings, queryParsersMap); } @@ -488,7 +513,8 @@ public class SearchModule extends AbstractModule { } private void registerBuiltinQueryParsers() { - registerQueryParser(MatchQueryParser::new); + registerQuery(MatchQueryBuilder.PROTOTYPE::readFrom, MatchQueryBuilder::fromXContent, MatchQueryBuilder.NAME, + "match_phrase", "matchPhrase", "match_phrase_prefix", "matchPhrasePrefix", "matchFuzzy", "match_fuzzy", "fuzzy_match"); registerQueryParser(MultiMatchQueryParser::new); registerQueryParser(NestedQueryParser::new); registerQueryParser(HasChildQueryParser::new); @@ -498,7 +524,8 @@ public class SearchModule extends AbstractModule { registerQueryParser(MatchAllQueryParser::new); registerQueryParser(QueryStringQueryParser::new); registerQueryParser(BoostingQueryParser::new); - BooleanQuery.setMaxClauseCount(settings.getAsInt("index.query.bool.max_clause_count", settings.getAsInt("indices.query.bool.max_clause_count", BooleanQuery.getMaxClauseCount()))); + BooleanQuery.setMaxClauseCount(settings.getAsInt("index.query.bool.max_clause_count", + settings.getAsInt("indices.query.bool.max_clause_count", BooleanQuery.getMaxClauseCount()))); registerQueryParser(BoolQueryParser::new); registerQueryParser(TermQueryParser::new); registerQueryParser(TermsQueryParser::new); @@ -521,8 +548,10 @@ public class SearchModule extends AbstractModule { registerQueryParser(IndicesQueryParser::new); registerQueryParser(CommonTermsQueryParser::new); registerQueryParser(SpanMultiTermQueryParser::new); - // This is delayed until configure time to give plugins a chance to register parsers - registerQueryParser(() -> new FunctionScoreQueryParser(new ScoreFunctionParserMapper(functionScoreParsers))); + QueryParser functionScoreParser = (QueryParseContext c) -> FunctionScoreQueryBuilder + .fromXContent((String name) -> functionScoreParsers.get(name), c); + registerQuery(FunctionScoreQueryBuilder.PROTOTYPE::readFrom, functionScoreParser, FunctionScoreQueryBuilder.NAME, + Strings.toCamelCase(FunctionScoreQueryBuilder.NAME)); registerQueryParser(SimpleQueryStringParser::new); registerQueryParser(TemplateQueryParser::new); registerQueryParser(TypeQueryParser::new); @@ -609,4 +638,16 @@ public class SearchModule extends AbstractModule { public Suggesters getSuggesters() { return suggesters; } + + private static class QueryRegistration> { + private final String[] names; + private final Writeable.Reader reader; + private final QueryParser parser; + + private QueryRegistration(String[] names, Reader reader, QueryParser parser) { + this.names = names; + this.reader = reader; + this.parser = parser; + } + } } diff --git a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java index bc45ffc7df5..e404870d723 100644 --- a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java @@ -637,12 +637,9 @@ public abstract class AbstractQueryTestCase> @SuppressWarnings("unchecked") protected QB assertSerialization(QB testQuery) throws IOException { try (BytesStreamOutput output = new BytesStreamOutput()) { - testQuery.writeTo(output); + output.writeQuery(testQuery); try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) { - QueryParser queryParser = queryParser(testQuery.getName()); - assertNotNull("queryparser not found for query: [" + testQuery.getName() + "]", queryParser); - QueryBuilder prototype = queryParser.getBuilderPrototype(); - QueryBuilder deserializedQuery = prototype.readFrom(in); + QueryBuilder deserializedQuery = in.readQuery(); assertEquals(deserializedQuery, testQuery); assertEquals(deserializedQuery.hashCode(), testQuery.hashCode()); assertNotSame(deserializedQuery, testQuery); @@ -685,38 +682,13 @@ public abstract class AbstractQueryTestCase> } } - private QueryParser queryParser(String queryId) { - QueryParser queryParser = indicesQueriesRegistry.queryParsers().get(queryId); - if (queryParser == null && EmptyQueryBuilder.NAME.equals(queryId)) { - return new QueryParser() { - @Override - public String[] names() { - return new String[]{EmptyQueryBuilder.NAME}; - } - - @Override - public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException { - return new EmptyQueryBuilder(); - } - - @Override - public QueryBuilder getBuilderPrototype() { - return EmptyQueryBuilder.PROTOTYPE; - } - }; - } - return queryParser; - } - //we use the streaming infra to create a copy of the query provided as argument + @SuppressWarnings("unchecked") protected QB copyQuery(QB query) throws IOException { try (BytesStreamOutput output = new BytesStreamOutput()) { - query.writeTo(output); + output.writeQuery(query); try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) { - QueryBuilder prototype = queryParser(query.getName()).getBuilderPrototype(); - @SuppressWarnings("unchecked") - QB secondQuery = (QB) prototype.readFrom(in); - return secondQuery; + return (QB) in.readQuery(); } } } diff --git a/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java b/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java index e3aff9ac1be..0112e252ac9 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java @@ -106,6 +106,7 @@ public abstract class AbstractSortTestCase> extends EST @AfterClass public static void afterClass() throws Exception { namedWriteableRegistry = null; + indicesQueriesRegistry = null; } /** Returns random sort that is put under test */ diff --git a/core/src/test/java/org/elasticsearch/search/sort/SortBuilderTests.java b/core/src/test/java/org/elasticsearch/search/sort/SortBuilderTests.java index 7d182a5a8b0..461e7deb98a 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/SortBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/search/sort/SortBuilderTests.java @@ -57,6 +57,7 @@ public class SortBuilderTests extends ESTestCase { @AfterClass public static void afterClass() throws Exception { namedWriteableRegistry = null; + indicesQueriesRegistry = null; } /**