Browse Source

Start to rework query registration

Changes QueryParser into a @FunctionalInterface and provides a way to
register queries using that. Cuts match and function_score queries over
to that registration method as a proof of concept.

Once all queries have been cut over we can remove their PROTOTYPES.
Nik Everett 9 years ago
parent
commit
75a9899813

+ 170 - 14
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<MatchQueryBuilder> {
+    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<MatchQueryBuilder> {
 
     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<MatchQueryBuilder> {
         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<MatchQueryBuilder> {
     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;
+    }
+
 }

+ 0 - 197
core/src/main/java/org/elasticsearch/index/query/MatchQueryParser.java

@@ -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<MatchQueryBuilder> {
-
-    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;
-    }
-}

+ 1 - 1
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<MultiMatchQueryBuilder> {
 

+ 7 - 2
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<QB extends QueryBuilder<QB>> {
 
     /**
      * 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<QB extends QueryBuilder<QB>> {
     /**
      * @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();
+    }
 }

+ 206 - 7
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<FunctionScoreQueryBuilder> {
 
+    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<FunctionScor
     protected void doXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject(NAME);
         if (query != null) {
-            builder.field(FunctionScoreQueryParser.QUERY_FIELD.getPreferredName());
+            builder.field(QUERY_FIELD.getPreferredName());
             query.toXContent(builder, params);
         }
-        builder.startArray(FunctionScoreQueryParser.FUNCTIONS_FIELD.getPreferredName());
+        builder.startArray(FUNCTIONS_FIELD.getPreferredName());
         for (FilterFunctionBuilder filterFunctionBuilder : filterFunctionBuilders) {
             filterFunctionBuilder.toXContent(builder, params);
         }
         builder.endArray();
 
-        builder.field(FunctionScoreQueryParser.SCORE_MODE_FIELD.getPreferredName(), scoreMode.name().toLowerCase(Locale.ROOT));
+        builder.field(SCORE_MODE_FIELD.getPreferredName(), scoreMode.name().toLowerCase(Locale.ROOT));
         if (boostMode != null) {
-            builder.field(FunctionScoreQueryParser.BOOST_MODE_FIELD.getPreferredName(), boostMode.name().toLowerCase(Locale.ROOT));
+            builder.field(BOOST_MODE_FIELD.getPreferredName(), boostMode.name().toLowerCase(Locale.ROOT));
         }
-        builder.field(FunctionScoreQueryParser.MAX_BOOST_FIELD.getPreferredName(), maxBoost);
+        builder.field(MAX_BOOST_FIELD.getPreferredName(), maxBoost);
         if (minScore != null) {
-            builder.field(FunctionScoreQueryParser.MIN_SCORE_FIELD.getPreferredName(), minScore);
+            builder.field(MIN_SCORE_FIELD.getPreferredName(), minScore);
         }
         printBoostAndQueryName(builder);
         builder.endObject();
@@ -359,7 +383,7 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
         @Override
         public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
             builder.startObject();
-            builder.field(FunctionScoreQueryParser.FILTER_FIELD.getPreferredName());
+            builder.field(FILTER_FIELD.getPreferredName());
             filter.toXContent(builder, params);
             scoreFunction.toXContent(builder, params);
             builder.endObject();
@@ -423,4 +447,179 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
         }
         return this;
     }
+
+    public static FunctionScoreQueryBuilder fromXContent(Function<String, ScoreFunctionParser<?>> 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<FunctionScoreQueryBuilder.FilterFunctionBuilder> 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<String, ScoreFunctionParser<?>> scoreFunctionLookup,
+            QueryParseContext parseContext, XContentParser parser,
+            List<FunctionScoreQueryBuilder.FilterFunctionBuilder> 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<String, ScoreFunctionParser<?>> 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;
+    }
 }

+ 0 - 240
core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java

@@ -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<FunctionScoreQueryBuilder> {
-
-    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<FunctionScoreQueryBuilder.FilterFunctionBuilder> 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<FunctionScoreQueryBuilder.FilterFunctionBuilder> 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;
-    }
-}

+ 1 - 1
core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionBuilder.java

@@ -48,7 +48,7 @@ public abstract class ScoreFunctionBuilder<FB extends 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);
         }
     }
 

+ 1 - 1
core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionParser.java

@@ -34,7 +34,7 @@ public interface ScoreFunctionParser<FB extends ScoreFunctionBuilder<FB>> {
     /**
      * 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();
 }

+ 0 - 48
core/src/main/java/org/elasticsearch/index/query/functionscore/ScoreFunctionParserMapper.java

@@ -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<String, ScoreFunctionParser<?>> functionParsers;
-
-    public ScoreFunctionParserMapper(Map<String, ScoreFunctionParser<?>> 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);
-    }
-}

+ 59 - 18
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<QueryRegistration<?>> queries = new ArrayList<>();
     private final List<Supplier<QueryParser<?>>> queryParsers = new ArrayList<>();
     private final Set<Class<? extends FetchSubPhase>> fetchSubPhases = new HashSet<>();
     private final Set<SignificanceHeuristicParser> 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 <QB extends QueryBuilder<QB>> void registerQuery(Writeable.Reader<QB> reader, QueryParser<QB> parser, String... names) {
+        queries.add(new QueryRegistration<QB>(names, reader, parser));
+    }
+
+    /**
+     * Register a query.
+     * TODO remove this in favor of registerQuery
+     */
     public void registerQueryParser(Supplier<QueryParser<?>> parser) {
         queryParsers.add(parser);
     }
@@ -364,16 +377,28 @@ public class SearchModule extends AbstractModule {
 
     public IndicesQueriesRegistry buildQueryParserRegistry() {
         Map<String, QueryParser<?>> queryParsersMap = new HashMap<>();
+
+        // TODO remove this when we retire registerQueryParser
         for (Supplier<QueryParser<?>> parserSupplier : queryParsers) {
-            QueryParser<? extends QueryBuilder> parser = parserSupplier.get();
+            QueryParser<? extends QueryBuilder<?>> 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<? extends QueryBuilder> qb = parser.getBuilderPrototype();
-            namedWriteableRegistry.registerPrototype(QueryBuilder.class, qb);
+            namedWriteableRegistry.registerPrototype(QueryBuilder.class, parser.getBuilderPrototype());
+        }
+
+        for (QueryRegistration<?> query : queries) {
+            QueryParser<? extends QueryBuilder<?>> 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<FunctionScoreQueryBuilder> 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<QB extends QueryBuilder<QB>> {
+        private final String[] names;
+        private final Writeable.Reader<QB> reader;
+        private final QueryParser<QB> parser;
+
+        private QueryRegistration(String[] names, Reader<QB> reader, QueryParser<QB> parser) {
+            this.names = names;
+            this.reader = reader;
+            this.parser = parser;
+        }
+    }
 }

+ 5 - 33
core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java

@@ -637,12 +637,9 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
     @SuppressWarnings("unchecked")
     protected <QB extends QueryBuilder> 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<QB extends AbstractQueryBuilder<QB>>
         }
     }
 
-    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();
             }
         }
     }

+ 1 - 0
core/src/test/java/org/elasticsearch/search/sort/AbstractSortTestCase.java

@@ -106,6 +106,7 @@ public abstract class AbstractSortTestCase<T extends SortBuilder<T>> extends EST
     @AfterClass
     public static void afterClass() throws Exception {
         namedWriteableRegistry = null;
+        indicesQueriesRegistry = null;
     }
 
     /** Returns random sort that is put under test */

+ 1 - 0
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;
     }
 
     /**