Explorar o código

Refactor QueryStringQuery for 6.0 (#25646)

This change refactors the query_string query to analyze the query text around logical operators of the query string the same way than a match_query/multi_match_query.
It also adds a type parameter that can be used to change the way multi fields query are built the same way than a multi_match query does.

Now that these queries share the same behavior regarding text analysis, some parameters are obsolete and have been deprecated:

split_on_whitespace: This setting is now ignored with a deprecation notice
if it is used explicitely. With this PR The query_string always splits on logical operator.
It simplifies the understanding of the other parameters that can have different meanings
depending on the value of split_on_whitespace.

auto_generate_phrase_queries: This setting is now ignored with a deprecation notice
if it is used explicitely. This setting only makes sense when the parser splits on whitespace.

use_dismax: This setting is now ignored with a deprecation notice
if it is used explicitely. The tie_breaker parameter is sufficient to handle best_fields/most_fields.

Fixes #25574
Jim Ferenczi %!s(int64=8) %!d(string=hai) anos
pai
achega
13da3eb53e
Modificáronse 21 ficheiros con 658 adicións e 912 borrados
  1. 0 282
      core/src/main/java/org/apache/lucene/queryparser/classic/QueryParserSettings.java
  2. 40 0
      core/src/main/java/org/apache/lucene/queryparser/classic/XQueryParser.java
  3. 3 1
      core/src/main/java/org/elasticsearch/index/query/MatchPhrasePrefixQueryBuilder.java
  4. 3 1
      core/src/main/java/org/elasticsearch/index/query/MatchPhraseQueryBuilder.java
  5. 3 1
      core/src/main/java/org/elasticsearch/index/query/MatchQueryBuilder.java
  6. 1 1
      core/src/main/java/org/elasticsearch/index/query/QueryBuilders.java
  7. 0 8
      core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java
  8. 145 140
      core/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java
  9. 1 1
      core/src/main/java/org/elasticsearch/index/search/ExistsFieldQueryExtension.java
  10. 1 1
      core/src/main/java/org/elasticsearch/index/search/FieldQueryExtension.java
  11. 16 16
      core/src/main/java/org/elasticsearch/index/search/MatchQuery.java
  12. 1 2
      core/src/main/java/org/elasticsearch/index/search/MultiMatchQuery.java
  13. 321 283
      core/src/main/java/org/elasticsearch/index/search/QueryStringQueryParser.java
  14. 2 12
      core/src/test/java/org/elasticsearch/index/query/DisableGraphQueryTests.java
  15. 59 123
      core/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java
  16. 2 2
      core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java
  17. 3 16
      core/src/test/java/org/elasticsearch/search/query/QueryStringIT.java
  18. 1 1
      core/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java
  19. 14 2
      docs/reference/migration/migrate_6_0/search.asciidoc
  20. 41 18
      docs/reference/query-dsl/query-string-query.asciidoc
  21. 1 1
      modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/QueryStringWithAnalyzersTests.java

+ 0 - 282
core/src/main/java/org/apache/lucene/queryparser/classic/QueryParserSettings.java

@@ -1,282 +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.apache.lucene.queryparser.classic;
-
-import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.search.MultiTermQuery;
-import org.elasticsearch.common.unit.Fuzziness;
-import org.joda.time.DateTimeZone;
-
-import java.util.Map;
-
-/**
- * Encapsulates settings that affect query_string parsing via {@link MapperQueryParser}
- */
-public class QueryParserSettings {
-
-    private final String queryString;
-
-    private String defaultField;
-
-    private Map<String, Float> fieldsAndWeights;
-
-    private QueryParser.Operator defaultOperator;
-
-    private Analyzer analyzer;
-    private boolean forceAnalyzer;
-    private Analyzer quoteAnalyzer;
-    private boolean forceQuoteAnalyzer;
-
-    private String quoteFieldSuffix;
-
-    private boolean autoGeneratePhraseQueries;
-
-    private boolean allowLeadingWildcard;
-
-    private boolean analyzeWildcard;
-
-    private boolean enablePositionIncrements;
-
-    private Fuzziness fuzziness;
-    private int fuzzyPrefixLength;
-    private int fuzzyMaxExpansions;
-    private MultiTermQuery.RewriteMethod fuzzyRewriteMethod;
-
-    private int phraseSlop;
-
-    private boolean useDisMax;
-
-    private float tieBreaker;
-
-    private MultiTermQuery.RewriteMethod rewriteMethod;
-
-    private boolean lenient;
-
-    private DateTimeZone timeZone;
-
-    /** To limit effort spent determinizing regexp queries. */
-    private int maxDeterminizedStates;
-
-    private boolean splitOnWhitespace;
-
-    public QueryParserSettings(String queryString) {
-        this.queryString = queryString;
-    }
-
-    public String queryString() {
-        return queryString;
-    }
-
-    public String defaultField() {
-        return defaultField;
-    }
-
-    public void defaultField(String defaultField) {
-        this.defaultField = defaultField;
-    }
-
-    public Map<String, Float> fieldsAndWeights() {
-        return fieldsAndWeights;
-    }
-
-    public void fieldsAndWeights(Map<String, Float> fieldsAndWeights) {
-        this.fieldsAndWeights = fieldsAndWeights;
-    }
-
-    public QueryParser.Operator defaultOperator() {
-        return defaultOperator;
-    }
-
-    public void defaultOperator(QueryParser.Operator defaultOperator) {
-        this.defaultOperator = defaultOperator;
-    }
-
-    public boolean autoGeneratePhraseQueries() {
-        return autoGeneratePhraseQueries;
-    }
-
-    public void autoGeneratePhraseQueries(boolean autoGeneratePhraseQueries) {
-        this.autoGeneratePhraseQueries = autoGeneratePhraseQueries;
-    }
-
-    public int maxDeterminizedStates() {
-        return maxDeterminizedStates;
-    }
-
-    public void maxDeterminizedStates(int maxDeterminizedStates) {
-        this.maxDeterminizedStates = maxDeterminizedStates;
-    }
-
-    public boolean allowLeadingWildcard() {
-        return allowLeadingWildcard;
-    }
-
-    public void allowLeadingWildcard(boolean allowLeadingWildcard) {
-        this.allowLeadingWildcard = allowLeadingWildcard;
-    }
-
-    public boolean enablePositionIncrements() {
-        return enablePositionIncrements;
-    }
-
-    public void enablePositionIncrements(boolean enablePositionIncrements) {
-        this.enablePositionIncrements = enablePositionIncrements;
-    }
-
-    public int phraseSlop() {
-        return phraseSlop;
-    }
-
-    public void phraseSlop(int phraseSlop) {
-        this.phraseSlop = phraseSlop;
-    }
-
-    public int fuzzyPrefixLength() {
-        return fuzzyPrefixLength;
-    }
-
-    public void fuzzyPrefixLength(int fuzzyPrefixLength) {
-        this.fuzzyPrefixLength = fuzzyPrefixLength;
-    }
-
-    public int fuzzyMaxExpansions() {
-        return fuzzyMaxExpansions;
-    }
-
-    public void fuzzyMaxExpansions(int fuzzyMaxExpansions) {
-        this.fuzzyMaxExpansions = fuzzyMaxExpansions;
-    }
-
-    public MultiTermQuery.RewriteMethod fuzzyRewriteMethod() {
-        return fuzzyRewriteMethod;
-    }
-
-    public void fuzzyRewriteMethod(MultiTermQuery.RewriteMethod fuzzyRewriteMethod) {
-        this.fuzzyRewriteMethod = fuzzyRewriteMethod;
-    }
-
-    public void defaultAnalyzer(Analyzer analyzer) {
-        this.analyzer = analyzer;
-        this.forceAnalyzer = false;
-    }
-
-    public void forceAnalyzer(Analyzer analyzer) {
-        this.analyzer = analyzer;
-        this.forceAnalyzer = true;
-    }
-
-    public Analyzer analyzer() {
-        return analyzer;
-    }
-
-    public boolean forceAnalyzer() {
-        return forceAnalyzer;
-    }
-
-    public void defaultQuoteAnalyzer(Analyzer quoteAnalyzer) {
-        this.quoteAnalyzer = quoteAnalyzer;
-        this.forceQuoteAnalyzer = false;
-    }
-
-    public void forceQuoteAnalyzer(Analyzer quoteAnalyzer) {
-        this.quoteAnalyzer = quoteAnalyzer;
-        this.forceQuoteAnalyzer = true;
-    }
-
-    public Analyzer quoteAnalyzer() {
-        return quoteAnalyzer;
-    }
-
-    public boolean forceQuoteAnalyzer() {
-        return forceQuoteAnalyzer;
-    }
-
-    public boolean analyzeWildcard() {
-        return this.analyzeWildcard;
-    }
-
-    public void analyzeWildcard(boolean analyzeWildcard) {
-        this.analyzeWildcard = analyzeWildcard;
-    }
-
-    public MultiTermQuery.RewriteMethod rewriteMethod() {
-        return this.rewriteMethod;
-    }
-
-    public void rewriteMethod(MultiTermQuery.RewriteMethod rewriteMethod) {
-        this.rewriteMethod = rewriteMethod;
-    }
-
-    public void quoteFieldSuffix(String quoteFieldSuffix) {
-        this.quoteFieldSuffix = quoteFieldSuffix;
-    }
-
-    public String quoteFieldSuffix() {
-        return this.quoteFieldSuffix;
-    }
-
-    public void lenient(boolean lenient) {
-        this.lenient = lenient;
-    }
-
-    public boolean lenient() {
-        return this.lenient;
-    }
-
-    public float tieBreaker() {
-        return tieBreaker;
-    }
-
-    public void tieBreaker(float tieBreaker) {
-        this.tieBreaker = tieBreaker;
-    }
-
-    public boolean useDisMax() {
-        return useDisMax;
-    }
-
-    public void useDisMax(boolean useDisMax) {
-        this.useDisMax = useDisMax;
-    }
-
-    public void timeZone(DateTimeZone timeZone) {
-        this.timeZone = timeZone;
-    }
-
-    public DateTimeZone timeZone() {
-        return this.timeZone;
-    }
-
-    public void fuzziness(Fuzziness fuzziness) {
-        this.fuzziness = fuzziness;
-    }
-
-    public Fuzziness fuzziness() {
-        return fuzziness;
-    }
-
-    public void splitOnWhitespace(boolean value) {
-        this.splitOnWhitespace = value;
-    }
-
-    public boolean splitOnWhitespace() {
-        return splitOnWhitespace;
-    }
-}

+ 40 - 0
core/src/main/java/org/apache/lucene/queryparser/classic/XQueryParser.java

@@ -0,0 +1,40 @@
+/*
+ * 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.apache.lucene.queryparser.classic;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.search.Query;
+
+/**
+ * This class is just a workaround to make {@link QueryParser#handleBareFuzzy(String, Token, String)} accessible by sub-classes.
+ * It is needed for {@link QueryParser}s that need to override the parsing of the slop in a fuzzy query (e.g. word<b>~2</b>, word<b>~</b>).
+ *
+ * TODO: We should maybe rewrite this with the flexible query parser which matches the same syntax with more freedom.
+ */
+public class XQueryParser extends QueryParser {
+    public XQueryParser(String f, Analyzer a) {
+        super(f, a);
+    }
+
+    @Override
+    protected Query handleBareFuzzy(String field, Token fuzzySlop, String termImage) throws ParseException {
+        return super.handleBareFuzzy(field, fuzzySlop, termImage);
+    }
+}

+ 3 - 1
core/src/main/java/org/elasticsearch/index/query/MatchPhrasePrefixQueryBuilder.java

@@ -168,7 +168,9 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
         }
 
         MatchQuery matchQuery = new MatchQuery(context);
-        matchQuery.setAnalyzer(analyzer);
+        if (analyzer != null) {
+            matchQuery.setAnalyzer(analyzer);
+        }
         matchQuery.setPhraseSlop(slop);
         matchQuery.setMaxExpansions(maxExpansions);
 

+ 3 - 1
core/src/main/java/org/elasticsearch/index/query/MatchPhraseQueryBuilder.java

@@ -144,7 +144,9 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQue
         }
 
         MatchQuery matchQuery = new MatchQuery(context);
-        matchQuery.setAnalyzer(analyzer);
+        if (analyzer != null) {
+            matchQuery.setAnalyzer(analyzer);
+        }
         matchQuery.setPhraseSlop(slop);
 
         return matchQuery.parse(MatchQuery.Type.PHRASE, fieldName, value);

+ 3 - 1
core/src/main/java/org/elasticsearch/index/query/MatchQueryBuilder.java

@@ -445,7 +445,9 @@ public class MatchQueryBuilder extends AbstractQueryBuilder<MatchQueryBuilder> {
 
         MatchQuery matchQuery = new MatchQuery(context);
         matchQuery.setOccur(operator.toBooleanClauseOccur());
-        matchQuery.setAnalyzer(analyzer);
+        if (analyzer != null) {
+            matchQuery.setAnalyzer(analyzer);
+        }
         matchQuery.setPhraseSlop(slop);
         matchQuery.setFuzziness(fuzziness);
         matchQuery.setFuzzyPrefixLength(prefixLength);

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

@@ -267,7 +267,7 @@ public abstract class QueryBuilders {
      * when no field is added (using {@link QueryStringQueryBuilder#field(String)}, will run the query once and non prefixed fields
      * will use the {@link QueryStringQueryBuilder#defaultField(String)} set. The second, when one or more fields are added
      * (using {@link QueryStringQueryBuilder#field(String)}), will run the parsed query against the provided fields, and combine
-     * them either using DisMax or a plain boolean query (see {@link QueryStringQueryBuilder#useDisMax(boolean)}).
+     * them either using Dismax.
      *
      * @param queryString The query string to run
      */

+ 0 - 8
core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java

@@ -21,8 +21,6 @@ package org.elasticsearch.index.query;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.queryparser.classic.MapperQueryParser;
-import org.apache.lucene.queryparser.classic.QueryParserSettings;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.join.BitSetProducer;
 import org.apache.lucene.search.similarities.Similarity;
@@ -87,7 +85,6 @@ public class QueryShardContext extends QueryRewriteContext {
     }
 
     private final Map<String, Query> namedQueries = new HashMap<>();
-    private final MapperQueryParser queryParser = new MapperQueryParser(this);
     private boolean allowUnmappedFields;
     private boolean mapUnmappedFieldAsString;
     private NestedScope nestedScope;
@@ -148,11 +145,6 @@ public class QueryShardContext extends QueryRewriteContext {
         return indexSettings.isQueryStringAllowLeadingWildcard();
     }
 
-    public MapperQueryParser queryParser(QueryParserSettings settings) {
-        queryParser.reset(settings);
-        return queryParser;
-    }
-
     public BitSetProducer bitsetFilter(Query filter) {
         return bitsetFilterCache.getBitSetProducer(filter);
     }

+ 145 - 140
core/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java

@@ -19,8 +19,6 @@
 
 package org.elasticsearch.index.query;
 
-import org.apache.lucene.queryparser.classic.MapperQueryParser;
-import org.apache.lucene.queryparser.classic.QueryParserSettings;
 import org.apache.lucene.search.BoostQuery;
 import org.apache.lucene.search.FuzzyQuery;
 import org.apache.lucene.search.Query;
@@ -45,6 +43,7 @@ import org.elasticsearch.index.mapper.NumberFieldMapper;
 import org.elasticsearch.index.mapper.ScaledFloatFieldMapper;
 import org.elasticsearch.index.mapper.TextFieldMapper;
 import org.elasticsearch.index.query.support.QueryParsers;
+import org.elasticsearch.index.search.QueryStringQueryParser;
 import org.joda.time.DateTimeZone;
 
 import java.io.IOException;
@@ -64,24 +63,21 @@ import java.util.TreeMap;
  * when no field is added (using {@link #field(String)}, will run the query once and non prefixed fields
  * will use the {@link #defaultField(String)} set. The second, when one or more fields are added
  * (using {@link #field(String)}), will run the parsed query against the provided fields, and combine
- * them either using DisMax or a plain boolean query (see {@link #useDisMax(boolean)}).
+ * them using Dismax.
  */
 public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQueryBuilder> {
 
     public static final String NAME = "query_string";
 
-    public static final boolean DEFAULT_AUTO_GENERATE_PHRASE_QUERIES = false;
     public static final int DEFAULT_MAX_DETERMINED_STATES = Operations.DEFAULT_MAX_DETERMINIZED_STATES;
     public static final boolean DEFAULT_ENABLE_POSITION_INCREMENTS = true;
     public static final boolean DEFAULT_ESCAPE = false;
-    public static final boolean DEFAULT_USE_DIS_MAX = true;
     public static final int DEFAULT_FUZZY_PREFIX_LENGTH = FuzzyQuery.defaultPrefixLength;
     public static final int DEFAULT_FUZZY_MAX_EXPANSIONS = FuzzyQuery.defaultMaxExpansions;
     public static final int DEFAULT_PHRASE_SLOP = 0;
-    public static final float DEFAULT_TIE_BREAKER = 0.0f;
     public static final Fuzziness DEFAULT_FUZZINESS = Fuzziness.AUTO;
     public static final Operator DEFAULT_OPERATOR = Operator.OR;
-    public static final boolean DEFAULT_SPLIT_ON_WHITESPACE = true;
+    public static final MultiMatchQueryBuilder.Type DEFAULT_TYPE = MultiMatchQueryBuilder.Type.BEST_FIELDS;
 
     private static final ParseField QUERY_FIELD = new ParseField("query");
     private static final ParseField FIELDS_FIELD = new ParseField("fields");
@@ -90,13 +86,15 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
     private static final ParseField ANALYZER_FIELD = new ParseField("analyzer");
     private static final ParseField QUOTE_ANALYZER_FIELD = new ParseField("quote_analyzer");
     private static final ParseField ALLOW_LEADING_WILDCARD_FIELD = new ParseField("allow_leading_wildcard");
-    private static final ParseField AUTO_GENERATE_PHRASE_QUERIES_FIELD = new ParseField("auto_generate_phrase_queries");
+    private static final ParseField AUTO_GENERATE_PHRASE_QUERIES_FIELD = new ParseField("auto_generate_phrase_queries")
+            .withAllDeprecated("This setting is ignored, use [type=phrase] instead");
     private static final ParseField MAX_DETERMINIZED_STATES_FIELD = new ParseField("max_determinized_states");
     private static final ParseField LOWERCASE_EXPANDED_TERMS_FIELD = new ParseField("lowercase_expanded_terms")
             .withAllDeprecated("Decision is now made by the analyzer");
     private static final ParseField ENABLE_POSITION_INCREMENTS_FIELD = new ParseField("enable_position_increments");
     private static final ParseField ESCAPE_FIELD = new ParseField("escape");
-    private static final ParseField USE_DIS_MAX_FIELD = new ParseField("use_dis_max");
+    private static final ParseField USE_DIS_MAX_FIELD = new ParseField("use_dis_max")
+            .withAllDeprecated("Set [tie_breaker] to 1 instead");
     private static final ParseField FUZZY_PREFIX_LENGTH_FIELD = new ParseField("fuzzy_prefix_length");
     private static final ParseField FUZZY_MAX_EXPANSIONS_FIELD = new ParseField("fuzzy_max_expansions");
     private static final ParseField FUZZY_REWRITE_FIELD = new ParseField("fuzzy_rewrite");
@@ -110,8 +108,10 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
     private static final ParseField LOCALE_FIELD = new ParseField("locale")
             .withAllDeprecated("Decision is now made by the analyzer");
     private static final ParseField TIME_ZONE_FIELD = new ParseField("time_zone");
-    private static final ParseField SPLIT_ON_WHITESPACE = new ParseField("split_on_whitespace");
+    private static final ParseField SPLIT_ON_WHITESPACE = new ParseField("split_on_whitespace")
+            .withAllDeprecated("This setting is ignored, the parser always splits on logical operator");
     private static final ParseField ALL_FIELDS_FIELD = new ParseField("all_fields");
+    private static final ParseField TYPE_FIELD = new ParseField("type");
 
     // Mapping types the "all-ish" query can be executed against
     public static final Set<String> ALLOWED_QUERY_MAPPER_TYPES;
@@ -131,6 +131,7 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
     private final String queryString;
 
     private String defaultField;
+
     /**
      * Fields to query against. If left empty will query default field,
      * currently _ALL. Uses a TreeMap to hold the fields so boolean clauses are
@@ -148,8 +149,6 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
 
     private String quoteFieldSuffix;
 
-    private boolean autoGeneratePhraseQueries = DEFAULT_AUTO_GENERATE_PHRASE_QUERIES;
-
     private Boolean allowLeadingWildcard;
 
     private Boolean analyzeWildcard;
@@ -170,9 +169,9 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
 
     private int phraseSlop = DEFAULT_PHRASE_SLOP;
 
-    private boolean useDisMax = DEFAULT_USE_DIS_MAX;
+    private MultiMatchQueryBuilder.Type type = DEFAULT_TYPE;
 
-    private float tieBreaker = DEFAULT_TIE_BREAKER;
+    private Float tieBreaker;
 
     private String minimumShouldMatch;
 
@@ -185,8 +184,6 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
     /** To limit effort spent determinizing regexp queries. */
     private int maxDeterminizedStates = DEFAULT_MAX_DETERMINED_STATES;
 
-    private boolean splitOnWhitespace = DEFAULT_SPLIT_ON_WHITESPACE;
-
     public QueryStringQueryBuilder(String queryString) {
         if (queryString == null) {
             throw new IllegalArgumentException("query text missing");
@@ -209,7 +206,9 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         analyzer = in.readOptionalString();
         quoteAnalyzer = in.readOptionalString();
         quoteFieldSuffix = in.readOptionalString();
-        autoGeneratePhraseQueries = in.readBoolean();
+        if (in.getVersion().before(Version.V_6_0_0_beta1)) {
+            in.readBoolean(); // auto_generate_phrase_query
+        }
         allowLeadingWildcard = in.readOptionalBoolean();
         analyzeWildcard = in.readOptionalBoolean();
         if (in.getVersion().before(Version.V_5_1_1)) {
@@ -224,8 +223,14 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         fuzzyMaxExpansions = in.readVInt();
         fuzzyRewrite = in.readOptionalString();
         phraseSlop = in.readVInt();
-        useDisMax = in.readBoolean();
-        tieBreaker = in.readFloat();
+        if (in.getVersion().before(Version.V_6_0_0_beta1)) {
+            in.readBoolean(); // use_dismax
+            tieBreaker = in.readFloat();
+            type = DEFAULT_TYPE;
+        } else {
+            type = MultiMatchQueryBuilder.Type.readFromStream(in);
+            tieBreaker = in.readOptionalFloat();
+        }
         rewrite = in.readOptionalString();
         minimumShouldMatch = in.readOptionalString();
         lenient = in.readOptionalBoolean();
@@ -233,10 +238,10 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         escape = in.readBoolean();
         maxDeterminizedStates = in.readVInt();
         if (in.getVersion().onOrAfter(Version.V_5_1_1)) {
-            splitOnWhitespace = in.readBoolean();
+            if (in.getVersion().before(Version.V_6_0_0_beta1)) {
+                in.readBoolean(); // split_on_whitespace
+            }
             useAllFields = in.readOptionalBoolean();
-        } else {
-            splitOnWhitespace = DEFAULT_SPLIT_ON_WHITESPACE;
         }
     }
 
@@ -253,7 +258,9 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         out.writeOptionalString(this.analyzer);
         out.writeOptionalString(this.quoteAnalyzer);
         out.writeOptionalString(this.quoteFieldSuffix);
-        out.writeBoolean(this.autoGeneratePhraseQueries);
+        if (out.getVersion().before(Version.V_6_0_0_beta1)) {
+            out.writeBoolean(false); // auto_generate_phrase_query
+        }
         out.writeOptionalBoolean(this.allowLeadingWildcard);
         out.writeOptionalBoolean(this.analyzeWildcard);
         if (out.getVersion().before(Version.V_5_1_1)) {
@@ -268,8 +275,13 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         out.writeVInt(this.fuzzyMaxExpansions);
         out.writeOptionalString(this.fuzzyRewrite);
         out.writeVInt(this.phraseSlop);
-        out.writeBoolean(this.useDisMax);
-        out.writeFloat(this.tieBreaker);
+        if (out.getVersion().before(Version.V_6_0_0_beta1)) {
+            out.writeBoolean(true); // use_dismax
+            out.writeFloat(tieBreaker != null ? tieBreaker : 0.0f);
+        } else {
+            type.writeTo(out);
+            out.writeOptionalFloat(tieBreaker);
+        }
         out.writeOptionalString(this.rewrite);
         out.writeOptionalString(this.minimumShouldMatch);
         out.writeOptionalBoolean(this.lenient);
@@ -277,7 +289,9 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         out.writeBoolean(this.escape);
         out.writeVInt(this.maxDeterminizedStates);
         if (out.getVersion().onOrAfter(Version.V_5_1_1)) {
-            out.writeBoolean(this.splitOnWhitespace);
+            if (out.getVersion().before(Version.V_6_0_0_beta1)) {
+                out.writeBoolean(false); // split_on_whitespace
+            }
             out.writeOptionalBoolean(this.useAllFields);
         }
     }
@@ -345,16 +359,26 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
     }
 
     /**
-     * When more than one field is used with the query string, should queries be combined using
-     * dis max, or boolean query. Defaults to dis max (<tt>true</tt>).
+     * @param type Sets how multiple fields should be combined to build textual part queries.
      */
+    public void type(MultiMatchQueryBuilder.Type type) {
+        this.type = type;
+    }
+
+    /**
+     * Use {@link QueryStringQueryBuilder#tieBreaker} instead.
+     */
+    @Deprecated
     public QueryStringQueryBuilder useDisMax(boolean useDisMax) {
-        this.useDisMax = useDisMax;
         return this;
     }
 
+    /**
+     * Use {@link QueryStringQueryBuilder#tieBreaker} instead.
+     */
+    @Deprecated
     public boolean useDisMax() {
-        return this.useDisMax;
+        return true;
     }
 
     /**
@@ -408,21 +432,19 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
     }
 
     /**
-     * Set to true if phrase queries will be automatically generated
-     * when the analyzer returns more than one term from whitespace
-     * delimited text.
-     * NOTE: this behavior may not be suitable for all languages.
-     * <p>
-     * Set to false if phrase queries should only be generated when
-     * surrounded by double quotes.
+     * This setting is ignored
      */
+    @Deprecated
     public QueryStringQueryBuilder autoGeneratePhraseQueries(boolean autoGeneratePhraseQueries) {
-        this.autoGeneratePhraseQueries = autoGeneratePhraseQueries;
         return this;
     }
 
+    /**
+     * This setting is ignored
+     */
+    @Deprecated
     public boolean autoGeneratePhraseQueries() {
-        return this.autoGeneratePhraseQueries;
+        return false;
     }
 
     /**
@@ -609,16 +631,19 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
     }
 
     /**
-     * Whether query text should be split on whitespace prior to analysis.
-     * Default is <code>{@value #DEFAULT_SPLIT_ON_WHITESPACE}</code>.
+     * This setting is ignored, this query parser splits on operator only.
      */
+    @Deprecated
     public QueryStringQueryBuilder splitOnWhitespace(boolean value) {
-        this.splitOnWhitespace = value;
         return this;
     }
 
+    /**
+     * This setting is ignored, this query parser splits on operator only.
+     */
+    @Deprecated
     public boolean splitOnWhitespace() {
-        return splitOnWhitespace;
+        return false;
     }
 
     @Override
@@ -633,8 +658,12 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
             builder.value(fieldEntry.getKey() + "^" + fieldEntry.getValue());
         }
         builder.endArray();
-        builder.field(USE_DIS_MAX_FIELD.getPreferredName(), this.useDisMax);
-        builder.field(TIE_BREAKER_FIELD.getPreferredName(), this.tieBreaker);
+        if (this.type != null) {
+            builder.field(TYPE_FIELD.getPreferredName(), type.toString().toLowerCase(Locale.ENGLISH));
+        }
+        if (tieBreaker != null) {
+            builder.field(TIE_BREAKER_FIELD.getPreferredName(), this.tieBreaker);
+        }
         builder.field(DEFAULT_OPERATOR_FIELD.getPreferredName(),
                 this.defaultOperator.name().toLowerCase(Locale.ROOT));
         if (this.analyzer != null) {
@@ -643,7 +672,6 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         if (this.quoteAnalyzer != null) {
             builder.field(QUOTE_ANALYZER_FIELD.getPreferredName(), this.quoteAnalyzer);
         }
-        builder.field(AUTO_GENERATE_PHRASE_QUERIES_FIELD.getPreferredName(), this.autoGeneratePhraseQueries);
         builder.field(MAX_DETERMINIZED_STATES_FIELD.getPreferredName(), this.maxDeterminizedStates);
         if (this.allowLeadingWildcard != null) {
             builder.field(ALLOW_LEADING_WILDCARD_FIELD.getPreferredName(), this.allowLeadingWildcard);
@@ -675,7 +703,6 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
             builder.field(TIME_ZONE_FIELD.getPreferredName(), this.timeZone.getID());
         }
         builder.field(ESCAPE_FIELD.getPreferredName(), this.escape);
-        builder.field(SPLIT_ON_WHITESPACE.getPreferredName(), this.splitOnWhitespace);
         if (this.useAllFields != null) {
             builder.field(ALL_FIELDS_FIELD.getPreferredName(), this.useAllFields);
         }
@@ -692,15 +719,14 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         String quoteAnalyzer = null;
         String queryName = null;
         float boost = AbstractQueryBuilder.DEFAULT_BOOST;
-        boolean autoGeneratePhraseQueries = QueryStringQueryBuilder.DEFAULT_AUTO_GENERATE_PHRASE_QUERIES;
         int maxDeterminizedStates = QueryStringQueryBuilder.DEFAULT_MAX_DETERMINED_STATES;
         boolean enablePositionIncrements = QueryStringQueryBuilder.DEFAULT_ENABLE_POSITION_INCREMENTS;
         boolean escape = QueryStringQueryBuilder.DEFAULT_ESCAPE;
-        boolean useDisMax = QueryStringQueryBuilder.DEFAULT_USE_DIS_MAX;
         int fuzzyPrefixLength = QueryStringQueryBuilder.DEFAULT_FUZZY_PREFIX_LENGTH;
         int fuzzyMaxExpansions = QueryStringQueryBuilder.DEFAULT_FUZZY_MAX_EXPANSIONS;
         int phraseSlop = QueryStringQueryBuilder.DEFAULT_PHRASE_SLOP;
-        float tieBreaker = QueryStringQueryBuilder.DEFAULT_TIE_BREAKER;
+        MultiMatchQueryBuilder.Type type = DEFAULT_TYPE;
+        Float tieBreaker = null;
         Boolean analyzeWildcard = null;
         Boolean allowLeadingWildcard = null;
         String minimumShouldMatch = null;
@@ -711,7 +737,6 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         Fuzziness fuzziness = QueryStringQueryBuilder.DEFAULT_FUZZINESS;
         String fuzzyRewrite = null;
         String rewrite = null;
-        boolean splitOnWhitespace = DEFAULT_SPLIT_ON_WHITESPACE;
         Boolean useAllFields = null;
         Map<String, Float> fieldsAndWeights = new HashMap<>();
         while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@@ -754,18 +779,12 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
                     quoteAnalyzer = parser.text();
                 } else if (ALLOW_LEADING_WILDCARD_FIELD.match(currentFieldName)) {
                     allowLeadingWildcard = parser.booleanValue();
-                } else if (AUTO_GENERATE_PHRASE_QUERIES_FIELD.match(currentFieldName)) {
-                    autoGeneratePhraseQueries = parser.booleanValue();
                 } else if (MAX_DETERMINIZED_STATES_FIELD.match(currentFieldName)) {
                     maxDeterminizedStates = parser.intValue();
-                } else if (LOWERCASE_EXPANDED_TERMS_FIELD.match(currentFieldName)) {
-                    // ignore, deprecated setting
                 } else if (ENABLE_POSITION_INCREMENTS_FIELD.match(currentFieldName)) {
                     enablePositionIncrements = parser.booleanValue();
                 } else if (ESCAPE_FIELD.match(currentFieldName)) {
                     escape = parser.booleanValue();
-                } else if (USE_DIS_MAX_FIELD.match(currentFieldName)) {
-                    useDisMax = parser.booleanValue();
                 } else if (FUZZY_PREFIX_LENGTH_FIELD.match(currentFieldName)) {
                     fuzzyPrefixLength = parser.intValue();
                 } else if (FUZZY_MAX_EXPANSIONS_FIELD.match(currentFieldName)) {
@@ -778,6 +797,8 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
                     fuzziness = Fuzziness.parse(parser);
                 } else if (AbstractQueryBuilder.BOOST_FIELD.match(currentFieldName)) {
                     boost = parser.floatValue();
+                } else if (TYPE_FIELD.match(currentFieldName)) {
+                    type = MultiMatchQueryBuilder.Type.parse(parser.text());
                 } else if (TIE_BREAKER_FIELD.match(currentFieldName)) {
                     tieBreaker = parser.floatValue();
                 } else if (ANALYZE_WILDCARD_FIELD.match(currentFieldName)) {
@@ -790,8 +811,6 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
                     quoteFieldSuffix = parser.textOrNull();
                 } else if (LENIENT_FIELD.match(currentFieldName)) {
                     lenient = parser.booleanValue();
-                } else if (LOCALE_FIELD.match(currentFieldName)) {
-                    // ignore, deprecated setting
                 } else if (ALL_FIELDS_FIELD.match(currentFieldName)) {
                     useAllFields = parser.booleanValue();
                 } else if (MAX_DETERMINIZED_STATES_FIELD.match(currentFieldName)) {
@@ -805,8 +824,16 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
                     }
                 } else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName)) {
                     queryName = parser.text();
+                } else if (AUTO_GENERATE_PHRASE_QUERIES_FIELD.match(currentFieldName)) {
+                    // ignore, deprecated setting
+                } else if (LOWERCASE_EXPANDED_TERMS_FIELD.match(currentFieldName)) {
+                    // ignore, deprecated setting
+                } else if (LOCALE_FIELD.match(currentFieldName)) {
+                    // ignore, deprecated setting
+                } else if (USE_DIS_MAX_FIELD.match(currentFieldName)) {
+                    // ignore, deprecated setting
                 } else if (SPLIT_ON_WHITESPACE.match(currentFieldName)) {
-                    splitOnWhitespace = parser.booleanValue();
+                    // ignore, deprecated setting
                 } else {
                     throw new ParsingException(parser.getTokenLocation(), "[" + QueryStringQueryBuilder.NAME +
                             "] query does not support [" + currentFieldName + "]");
@@ -833,17 +860,18 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         queryStringQuery.analyzer(analyzer);
         queryStringQuery.quoteAnalyzer(quoteAnalyzer);
         queryStringQuery.allowLeadingWildcard(allowLeadingWildcard);
-        queryStringQuery.autoGeneratePhraseQueries(autoGeneratePhraseQueries);
         queryStringQuery.maxDeterminizedStates(maxDeterminizedStates);
         queryStringQuery.enablePositionIncrements(enablePositionIncrements);
         queryStringQuery.escape(escape);
-        queryStringQuery.useDisMax(useDisMax);
         queryStringQuery.fuzzyPrefixLength(fuzzyPrefixLength);
         queryStringQuery.fuzzyMaxExpansions(fuzzyMaxExpansions);
         queryStringQuery.fuzzyRewrite(fuzzyRewrite);
         queryStringQuery.phraseSlop(phraseSlop);
         queryStringQuery.fuzziness(fuzziness);
-        queryStringQuery.tieBreaker(tieBreaker);
+        queryStringQuery.type(type);
+        if (tieBreaker != null) {
+            queryStringQuery.tieBreaker(tieBreaker);
+        }
         queryStringQuery.analyzeWildcard(analyzeWildcard);
         queryStringQuery.rewrite(rewrite);
         queryStringQuery.minimumShouldMatch(minimumShouldMatch);
@@ -852,7 +880,6 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         queryStringQuery.timeZone(timeZone);
         queryStringQuery.boost(boost);
         queryStringQuery.queryName(queryName);
-        queryStringQuery.splitOnWhitespace(splitOnWhitespace);
         queryStringQuery.useAllFields(useAllFields);
         return queryStringQuery;
     }
@@ -871,7 +898,6 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
                 Objects.equals(analyzer, other.analyzer) &&
                 Objects.equals(quoteAnalyzer, other.quoteAnalyzer) &&
                 Objects.equals(quoteFieldSuffix, other.quoteFieldSuffix) &&
-                Objects.equals(autoGeneratePhraseQueries, other.autoGeneratePhraseQueries) &&
                 Objects.equals(allowLeadingWildcard, other.allowLeadingWildcard) &&
                 Objects.equals(enablePositionIncrements, other.enablePositionIncrements) &&
                 Objects.equals(analyzeWildcard, other.analyzeWildcard) &&
@@ -880,7 +906,7 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
                 Objects.equals(fuzzyMaxExpansions, other.fuzzyMaxExpansions) &&
                 Objects.equals(fuzzyRewrite, other.fuzzyRewrite) &&
                 Objects.equals(phraseSlop, other.phraseSlop) &&
-                Objects.equals(useDisMax, other.useDisMax) &&
+                Objects.equals(type, other.type) &&
                 Objects.equals(tieBreaker, other.tieBreaker) &&
                 Objects.equals(rewrite, other.rewrite) &&
                 Objects.equals(minimumShouldMatch, other.minimumShouldMatch) &&
@@ -889,17 +915,16 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
                 Objects.equals(timeZone.getID(), other.timeZone.getID()) &&
                 Objects.equals(escape, other.escape) &&
                 Objects.equals(maxDeterminizedStates, other.maxDeterminizedStates) &&
-                Objects.equals(splitOnWhitespace, other.splitOnWhitespace) &&
                 Objects.equals(useAllFields, other.useAllFields);
     }
 
     @Override
     protected int doHashCode() {
         return Objects.hash(queryString, defaultField, fieldsAndWeights, defaultOperator, analyzer, quoteAnalyzer,
-                quoteFieldSuffix, autoGeneratePhraseQueries, allowLeadingWildcard, analyzeWildcard,
+                quoteFieldSuffix, allowLeadingWildcard, analyzeWildcard,
                 enablePositionIncrements, fuzziness, fuzzyPrefixLength,
-                fuzzyMaxExpansions, fuzzyRewrite, phraseSlop, useDisMax, tieBreaker, rewrite, minimumShouldMatch, lenient,
-                timeZone == null ? 0 : timeZone.getID(), escape, maxDeterminizedStates, splitOnWhitespace, useAllFields);
+                fuzzyMaxExpansions, fuzzyRewrite, phraseSlop, type, tieBreaker, rewrite, minimumShouldMatch, lenient,
+                timeZone == null ? 0 : timeZone.getID(), escape, maxDeterminizedStates, useAllFields);
     }
 
     /**
@@ -930,43 +955,17 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
 
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
-        //TODO would be nice to have all the settings in one place: some change though at query execution time
-        //e.g. field names get expanded to concrete names, defaults get resolved sometimes to settings values etc.
-        if (splitOnWhitespace == false && autoGeneratePhraseQueries) {
-            throw new IllegalArgumentException("it is disallowed to disable [split_on_whitespace] " +
-                "if [auto_generate_phrase_queries] is activated");
-        }
-        QueryParserSettings qpSettings;
-        if (this.escape) {
-            qpSettings = new QueryParserSettings(org.apache.lucene.queryparser.classic.QueryParser.escape(this.queryString));
-        } else {
-            qpSettings = new QueryParserSettings(this.queryString);
-        }
-
-        Map<String, Float> resolvedFields = new TreeMap<>();
-
+        String rewrittenQueryString = escape ? org.apache.lucene.queryparser.classic.QueryParser.escape(this.queryString) : queryString;
         if ((useAllFields != null && useAllFields) && (fieldsAndWeights.size() != 0 || this.defaultField != null)) {
             throw addValidationError("cannot use [all_fields] parameter in conjunction with [default_field] or [fields]", null);
         }
 
-        // If explicitly required to use all fields, use all fields, OR:
-        // Automatically determine the fields (to replace the _all field) if all of the following are true:
-        // - The _all field is disabled,
-        // - and the default_field has not been changed in the settings
-        // - and default_field is not specified in the request
-        // - and no fields are specified in the request
-        if ((this.useAllFields != null && this.useAllFields) ||
-                (context.getMapperService().allEnabled() == false &&
-                        "_all".equals(context.defaultField()) &&
-                        this.defaultField == null &&
-                        this.fieldsAndWeights.size() == 0)) {
-            // Use the automatically determined expansion of all queryable fields
-            resolvedFields = allQueryableDefaultFields(context);
-            // Automatically set leniency to "true" if unset so mismatched fields don't cause exceptions
-            qpSettings.lenient(lenient == null ? true : lenient);
-        } else {
-            qpSettings.defaultField(this.defaultField == null ? context.defaultField() : this.defaultField);
-
+        QueryStringQueryParser queryParser;
+        boolean isLenient = lenient == null ? context.queryStringLenient() : lenient;
+        if (defaultField != null) {
+            queryParser = new QueryStringQueryParser(context, defaultField, isLenient);
+        } else if (fieldsAndWeights.size() > 0) {
+            final Map<String, Float> resolvedFields = new TreeMap<>();
             for (Map.Entry<String, Float> fieldsEntry : fieldsAndWeights.entrySet()) {
                 String fieldName = fieldsEntry.getKey();
                 Float weight = fieldsEntry.getValue();
@@ -978,57 +977,64 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
                     resolvedFields.put(fieldName, weight);
                 }
             }
-            qpSettings.lenient(lenient == null ? context.queryStringLenient() : lenient);
-        }
-        if (fieldsAndWeights.isEmpty() == false || resolvedFields.isEmpty() == false) {
-            // We set the fields and weight only if we have explicit fields to query
-            // Otherwise we set it to null and fallback to the default field.
-            qpSettings.fieldsAndWeights(resolvedFields);
+            queryParser = new QueryStringQueryParser(context, resolvedFields, isLenient);
+        } else {
+            // If explicitly required to use all fields, use all fields, OR:
+            // Automatically determine the fields (to replace the _all field) if all of the following are true:
+            // - The _all field is disabled,
+            // - and the default_field has not been changed in the settings
+            // - and default_field is not specified in the request
+            // - and no fields are specified in the request
+            if ((useAllFields != null && useAllFields) ||
+                    (context.getMapperService().allEnabled() == false && "_all".equals(context.defaultField()))) {
+                // Automatically determine the fields from the index mapping.
+                // Automatically set leniency to "true" if unset so mismatched fields don't cause exceptions;
+                queryParser = new QueryStringQueryParser(context, allQueryableDefaultFields(context), lenient == null ? true : lenient);
+            } else {
+                queryParser = new QueryStringQueryParser(context, context.defaultField(), isLenient);
+            }
         }
-        qpSettings.defaultOperator(defaultOperator.toQueryParserOperator());
 
-        if (analyzer == null) {
-            qpSettings.defaultAnalyzer(context.getMapperService().searchAnalyzer());
-        } else {
+        if (analyzer != null) {
             NamedAnalyzer namedAnalyzer = context.getIndexAnalyzers().get(analyzer);
             if (namedAnalyzer == null) {
                 throw new QueryShardException(context, "[query_string] analyzer [" + analyzer + "] not found");
             }
-            qpSettings.forceAnalyzer(namedAnalyzer);
+            queryParser.setForceAnalyzer(namedAnalyzer);
         }
+
         if (quoteAnalyzer != null) {
-            NamedAnalyzer namedAnalyzer = context.getIndexAnalyzers().get(quoteAnalyzer);
-            if (namedAnalyzer == null) {
+            NamedAnalyzer forceQuoteAnalyzer = context.getIndexAnalyzers().get(quoteAnalyzer);
+            if (forceQuoteAnalyzer == null) {
                 throw new QueryShardException(context, "[query_string] quote_analyzer [" + quoteAnalyzer + "] not found");
             }
-            qpSettings.forceQuoteAnalyzer(namedAnalyzer);
-        } else if (analyzer != null) {
-            qpSettings.forceQuoteAnalyzer(qpSettings.analyzer());
+            queryParser.setForceQuoteAnalyzer(forceQuoteAnalyzer);
+        }
+
+        queryParser.setDefaultOperator(defaultOperator.toQueryParserOperator());
+        queryParser.setType(type);
+        if (tieBreaker != null) {
+            queryParser.setGroupTieBreaker(tieBreaker);
         } else {
-            qpSettings.defaultQuoteAnalyzer(context.getMapperService().searchQuoteAnalyzer());
+            queryParser.setGroupTieBreaker(type.tieBreaker());
         }
+        queryParser.setPhraseSlop(phraseSlop);
+        queryParser.setQuoteFieldSuffix(quoteFieldSuffix);
+        queryParser.setAllowLeadingWildcard(allowLeadingWildcard == null ?
+            context.queryStringAllowLeadingWildcard() : allowLeadingWildcard);
+        queryParser.setAnalyzeWildcard(analyzeWildcard == null ? context.queryStringAnalyzeWildcard() : analyzeWildcard);
+        queryParser.setEnablePositionIncrements(enablePositionIncrements);
+        queryParser.setFuzziness(fuzziness);
+        queryParser.setFuzzyPrefixLength(fuzzyPrefixLength);
+        queryParser.setFuzzyMaxExpansions(fuzzyMaxExpansions);
+        queryParser.setFuzzyRewriteMethod(QueryParsers.parseRewriteMethod(this.fuzzyRewrite));
+        queryParser.setMultiTermRewriteMethod(QueryParsers.parseRewriteMethod(this.rewrite));
+        queryParser.setTimeZone(timeZone);
+        queryParser.setMaxDeterminizedStates(maxDeterminizedStates);
 
-        qpSettings.quoteFieldSuffix(quoteFieldSuffix);
-        qpSettings.autoGeneratePhraseQueries(autoGeneratePhraseQueries);
-        qpSettings.allowLeadingWildcard(allowLeadingWildcard == null ? context.queryStringAllowLeadingWildcard() : allowLeadingWildcard);
-        qpSettings.analyzeWildcard(analyzeWildcard == null ? context.queryStringAnalyzeWildcard() : analyzeWildcard);
-        qpSettings.enablePositionIncrements(enablePositionIncrements);
-        qpSettings.fuzziness(fuzziness);
-        qpSettings.fuzzyPrefixLength(fuzzyPrefixLength);
-        qpSettings.fuzzyMaxExpansions(fuzzyMaxExpansions);
-        qpSettings.fuzzyRewriteMethod(QueryParsers.parseRewriteMethod(this.fuzzyRewrite));
-        qpSettings.phraseSlop(phraseSlop);
-        qpSettings.useDisMax(useDisMax);
-        qpSettings.tieBreaker(tieBreaker);
-        qpSettings.rewriteMethod(QueryParsers.parseRewriteMethod(this.rewrite));
-        qpSettings.timeZone(timeZone);
-        qpSettings.maxDeterminizedStates(maxDeterminizedStates);
-        qpSettings.splitOnWhitespace(splitOnWhitespace);
-
-        MapperQueryParser queryParser = context.queryParser(qpSettings);
         Query query;
         try {
-            query = queryParser.parse(queryString);
+            query = queryParser.parse(rewrittenQueryString);
         } catch (org.apache.lucene.queryparser.classic.ParseException e) {
             throw new QueryShardException(context, "Failed to parse query [" + this.queryString + "]", e);
         }
@@ -1055,5 +1061,4 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
 
         return query;
     }
-
 }

+ 1 - 1
core/src/main/java/org/apache/lucene/queryparser/classic/ExistsFieldQueryExtension.java → core/src/main/java/org/elasticsearch/index/search/ExistsFieldQueryExtension.java

@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.lucene.queryparser.classic;
+package org.elasticsearch.index.search;
 
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.Query;

+ 1 - 1
core/src/main/java/org/apache/lucene/queryparser/classic/FieldQueryExtension.java → core/src/main/java/org/elasticsearch/index/search/FieldQueryExtension.java

@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.lucene.queryparser.classic;
+package org.elasticsearch.index.search;
 
 import org.apache.lucene.search.Query;
 import org.elasticsearch.index.query.QueryShardContext;

+ 16 - 16
core/src/main/java/org/elasticsearch/index/search/MatchQuery.java

@@ -139,7 +139,7 @@ public class MatchQuery {
 
     protected final QueryShardContext context;
 
-    protected String analyzer;
+    protected Analyzer analyzer;
 
     protected BooleanClause.Occur occur = BooleanClause.Occur.SHOULD;
 
@@ -167,7 +167,14 @@ public class MatchQuery {
         this.context = context;
     }
 
-    public void setAnalyzer(String analyzer) {
+    public void setAnalyzer(String analyzerName) {
+        this.analyzer = context.getMapperService().getIndexAnalyzers().get(analyzerName);
+        if (analyzer == null) {
+            throw new IllegalArgumentException("No analyzer found for [" + analyzerName + "]");
+        }
+    }
+
+    public void setAnalyzer(Analyzer analyzer) {
         this.analyzer = analyzer;
     }
 
@@ -215,17 +222,13 @@ public class MatchQuery {
         this.zeroTermsQuery = zeroTermsQuery;
     }
 
-    protected Analyzer getAnalyzer(MappedFieldType fieldType) {
-        if (this.analyzer == null) {
+    protected Analyzer getAnalyzer(MappedFieldType fieldType, boolean quoted) {
+        if (analyzer == null) {
             if (fieldType != null) {
-                return context.getSearchAnalyzer(fieldType);
+                return quoted ? context.getSearchQuoteAnalyzer(fieldType) : context.getSearchAnalyzer(fieldType);
             }
-            return context.getMapperService().searchAnalyzer();
+            return quoted ? context.getMapperService().searchQuoteAnalyzer() : context.getMapperService().searchAnalyzer();
         } else {
-            Analyzer analyzer = context.getMapperService().getIndexAnalyzers().get(this.analyzer);
-            if (analyzer == null) {
-                throw new IllegalArgumentException("No analyzer found for [" + this.analyzer + "]");
-            }
             return analyzer;
         }
     }
@@ -252,7 +255,7 @@ public class MatchQuery {
             return blendTermQuery(new Term(fieldName, value.toString()), fieldType);
         }
 
-        Analyzer analyzer = getAnalyzer(fieldType);
+        Analyzer analyzer = getAnalyzer(fieldType, type == Type.PHRASE);
         assert analyzer != null;
         MatchQueryBuilder builder = new MatchQueryBuilder(analyzer, fieldType);
         builder.setEnablePositionIncrements(this.enablePositionIncrements);
@@ -460,7 +463,7 @@ public class MatchQuery {
                     return query;
                 } catch (RuntimeException e) {
                     if (lenient) {
-                        return new TermQuery(term);
+                        return null;
                     } else {
                         throw e;
                     }
@@ -472,10 +475,7 @@ public class MatchQuery {
             return query;
         }
         if (fieldType != null) {
-            Query query = termQuery(fieldType, term.bytes(), lenient);
-            if (query != null) {
-                return query;
-            }
+            return termQuery(fieldType, term.bytes(), lenient);
         }
         return new TermQuery(term);
     }

+ 1 - 2
core/src/main/java/org/elasticsearch/index/search/MultiMatchQuery.java

@@ -89,7 +89,6 @@ public class MultiMatchQuery extends MatchQuery {
             final List<? extends Query> queries = queryBuilder.buildGroupedQueries(type, fieldNames, value, minimumShouldMatch);
             result = queryBuilder.combineGrouped(queries);
         }
-        assert result != null;
         return result;
     }
 
@@ -160,7 +159,7 @@ public class MultiMatchQuery extends MatchQuery {
                 String name = entry.getKey();
                 MappedFieldType fieldType = context.fieldMapper(name);
                 if (fieldType != null) {
-                    Analyzer actualAnalyzer = getAnalyzer(fieldType);
+                    Analyzer actualAnalyzer = getAnalyzer(fieldType, type == MultiMatchQueryBuilder.Type.PHRASE);
                     name = fieldType.name();
                     if (!groups.containsKey(actualAnalyzer)) {
                        groups.put(actualAnalyzer, new ArrayList<>());

+ 321 - 283
core/src/main/java/org/apache/lucene/queryparser/classic/MapperQueryParser.java → core/src/main/java/org/elasticsearch/index/search/QueryStringQueryParser.java

@@ -17,21 +17,24 @@
  * under the License.
  */
 
-package org.apache.lucene.queryparser.classic;
+package org.elasticsearch.index.search;
 
 import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.analysis.miscellaneous.DisableGraphAttribute;
 import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.miscellaneous.DisableGraphAttribute;
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 import org.apache.lucene.index.Term;
+import org.apache.lucene.queryparser.classic.ParseException;
+import org.apache.lucene.queryparser.classic.Token;
+import org.apache.lucene.queryparser.classic.XQueryParser;
 import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.BoostQuery;
 import org.apache.lucene.search.DisjunctionMaxQuery;
 import org.apache.lucene.search.FuzzyQuery;
 import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.MultiPhraseQuery;
+import org.apache.lucene.search.MultiTermQuery;
 import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.SynonymQuery;
@@ -40,38 +43,38 @@ import org.apache.lucene.search.spans.SpanOrQuery;
 import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.IOUtils;
-import org.apache.lucene.util.automaton.RegExp;
 import org.elasticsearch.common.lucene.search.Queries;
 import org.elasticsearch.common.unit.Fuzziness;
+import org.elasticsearch.index.analysis.ShingleTokenFilterFactory;
 import org.elasticsearch.index.mapper.AllFieldMapper;
 import org.elasticsearch.index.mapper.DateFieldMapper;
 import org.elasticsearch.index.mapper.MappedFieldType;
-import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.index.mapper.StringFieldType;
+import org.elasticsearch.index.mapper.MapperService;
+import org.elasticsearch.index.query.MultiMatchQueryBuilder;
 import org.elasticsearch.index.query.QueryShardContext;
 import org.elasticsearch.index.query.support.QueryParsers;
-import org.elasticsearch.index.analysis.ShingleTokenFilterFactory;
+import org.joda.time.DateTimeZone;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Collections;
+
 import static java.util.Collections.unmodifiableMap;
 import static org.elasticsearch.common.lucene.search.Queries.fixNegativeQueryIfNeeded;
 
 /**
- * A query parser that uses the {@link MapperService} in order to build smarter
+ * A {@link XQueryParser} that uses the {@link MapperService} in order to build smarter
  * queries based on the mapping information.
- * <p>
- * Also breaks fields with [type].[name] into a boolean query that must include the type
- * as well as the query on the name.
+ * This class uses {@link MultiMatchQuery} to build the text query around logical operators and {@link XQueryParser}
+ * to assemble the result logically.
  */
-public class MapperQueryParser extends QueryParser {
-
-    public static final Map<String, FieldQueryExtension> FIELD_QUERY_EXTENSIONS;
+public class QueryStringQueryParser extends XQueryParser {
+    private static final Map<String, FieldQueryExtension> FIELD_QUERY_EXTENSIONS;
 
     static {
         Map<String, FieldQueryExtension> fieldQueryExtensions = new HashMap<>();
@@ -80,48 +83,172 @@ public class MapperQueryParser extends QueryParser {
     }
 
     private final QueryShardContext context;
+    private final Map<String, Float> fieldsAndWeights;
+    private final boolean lenient;
+
+    private final MultiMatchQuery queryBuilder;
+    private MultiMatchQueryBuilder.Type type = MultiMatchQueryBuilder.Type.BEST_FIELDS;
+    private Float groupTieBreaker;
+
+    private Analyzer forceAnalyzer;
+    private Analyzer forceQuoteAnalyzer;
+    private String quoteFieldSuffix;
+    private boolean analyzeWildcard;
+    private DateTimeZone timeZone;
+    private Fuzziness fuzziness = Fuzziness.AUTO;
+    private int fuzzyMaxExpansions = FuzzyQuery.defaultMaxExpansions;
+    private MappedFieldType currentFieldType;
+    private MultiTermQuery.RewriteMethod fuzzyRewriteMethod;
 
-    private QueryParserSettings settings;
+    /**
+     * @param context The query shard context.
+     * @param defaultField The default field for query terms.
+     */
+    public QueryStringQueryParser(QueryShardContext context, String defaultField) {
+        this(context, defaultField, Collections.emptyMap(), false, context.getMapperService().searchAnalyzer());
+    }
 
-    private MappedFieldType currentFieldType;
+    /**
+     * @param context The query shard context.
+     * @param defaultField The default field for query terms.
+     * @param lenient If set to `true` will cause format based failures (like providing text to a numeric field) to be ignored.
+     */
+    public QueryStringQueryParser(QueryShardContext context, String defaultField, boolean lenient) {
+        this(context, defaultField, Collections.emptyMap(), lenient, context.getMapperService().searchAnalyzer());
+    }
+
+    /**
+     * @param context The query shard context
+     * @param fieldsAndWeights The default fields and weights expansion for query terms
+     */
+    public QueryStringQueryParser(QueryShardContext context, Map<String, Float> fieldsAndWeights) {
+        this(context, null, fieldsAndWeights, false, context.getMapperService().searchAnalyzer());
+    }
 
-    public MapperQueryParser(QueryShardContext context) {
-        super(null, null);
+    /**
+     * @param context The query shard context.
+     * @param fieldsAndWeights The default fields and weights expansion for query terms.
+     * @param lenient If set to `true` will cause format based failures (like providing text to a numeric field) to be ignored.
+     */
+    public QueryStringQueryParser(QueryShardContext context, Map<String, Float> fieldsAndWeights, boolean lenient) {
+        this(context, null, fieldsAndWeights, lenient, context.getMapperService().searchAnalyzer());
+    }
+
+    private QueryStringQueryParser(QueryShardContext context, String defaultField, Map<String, Float> fieldsAndWeights,
+                                   boolean lenient, Analyzer analyzer) {
+        super(defaultField, analyzer);
         this.context = context;
+        this.fieldsAndWeights = Collections.unmodifiableMap(fieldsAndWeights);
+        this.queryBuilder = new MultiMatchQuery(context);
+        queryBuilder.setLenient(lenient);
+        this.lenient = lenient;
     }
 
-    public void reset(QueryParserSettings settings) {
-        this.settings = settings;
-        if (settings.fieldsAndWeights() == null) {
-            // this query has no explicit fields to query so we fallback to the default field
-            this.field = settings.defaultField();
-        } else if (settings.fieldsAndWeights().size() == 1) {
-            this.field = settings.fieldsAndWeights().keySet().iterator().next();
-        } else {
-            this.field = null;
-        }
-        setAnalyzer(settings.analyzer());
-        setMultiTermRewriteMethod(settings.rewriteMethod());
-        setEnablePositionIncrements(settings.enablePositionIncrements());
-        setSplitOnWhitespace(settings.splitOnWhitespace());
-        setAutoGeneratePhraseQueries(settings.autoGeneratePhraseQueries());
-        setMaxDeterminizedStates(settings.maxDeterminizedStates());
-        setAllowLeadingWildcard(settings.allowLeadingWildcard());
-        setPhraseSlop(settings.phraseSlop());
-        setDefaultOperator(settings.defaultOperator());
-        setFuzzyPrefixLength(settings.fuzzyPrefixLength());
+    @Override
+    public void setDefaultOperator(Operator op) {
+        super.setDefaultOperator(op);
+        queryBuilder.setOccur(op == Operator.AND ? BooleanClause.Occur.MUST : BooleanClause.Occur.SHOULD);
     }
 
     /**
-     * We override this one so we can get the fuzzy part to be treated as string,
-     * so people can do: "age:10~5" or "timestamp:2012-10-10~5d"
+     * @param type Sets how multiple fields should be combined to build textual part queries.
      */
-    @Override
-    Query handleBareFuzzy(String qfield, Token fuzzySlop, String termImage) throws ParseException {
-        if (fuzzySlop.image.length() == 1) {
-            return getFuzzyQuery(qfield, termImage, Float.toString(settings.fuzziness().asDistance(termImage)));
+    public void setType(MultiMatchQueryBuilder.Type type) {
+        this.type = type;
+    }
+
+    /**
+     * @param fuzziness Sets the default {@link Fuzziness} for fuzzy query.
+     * Defaults to {@link Fuzziness#AUTO}.
+     */
+    public void setFuzziness(Fuzziness fuzziness) {
+        this.fuzziness = fuzziness;
+    }
+
+    /**
+     * @param fuzzyRewriteMethod Sets the default rewrite method for fuzzy query.
+     */
+    public void setFuzzyRewriteMethod(MultiTermQuery.RewriteMethod fuzzyRewriteMethod) {
+        this.fuzzyRewriteMethod = fuzzyRewriteMethod;
+    }
+
+    /**
+     * @param fuzzyMaxExpansions Sets the maximum number of expansions allowed in a fuzzy query.
+     * Defaults to {@link FuzzyQuery#defaultMaxExpansions}.
+     */
+    public void setFuzzyMaxExpansions(int fuzzyMaxExpansions) {
+        this.fuzzyMaxExpansions = fuzzyMaxExpansions;
+    }
+
+    /**
+     * @param analyzer Force the provided analyzer to be used for all query analysis regardless of the field.
+     */
+    public void setForceAnalyzer(Analyzer analyzer) {
+        this.forceAnalyzer = analyzer;
+    }
+
+    /**
+     * @param analyzer Force the provided analyzer to be used for all phrase query analysis regardless of the field.
+     */
+    public void setForceQuoteAnalyzer(Analyzer analyzer) {
+        this.forceQuoteAnalyzer = analyzer;
+    }
+
+    /**
+     * @param quoteFieldSuffix The suffix to append to fields for quoted parts of the query string.
+     */
+    public void setQuoteFieldSuffix(String quoteFieldSuffix) {
+        this.quoteFieldSuffix = quoteFieldSuffix;
+    }
+
+    /**
+     * @param analyzeWildcard If true, the wildcard operator analyzes the term to build a wildcard query.
+     *                        Otherwise the query terms are only normalized.
+     */
+    public void setAnalyzeWildcard(boolean analyzeWildcard) {
+        this.analyzeWildcard = analyzeWildcard;
+    }
+
+    /**
+     * @param timeZone Time Zone to be applied to any range query related to dates.
+     */
+    public void setTimeZone(DateTimeZone timeZone) {
+        this.timeZone = timeZone;
+    }
+
+    /**
+     * @param groupTieBreaker The tie breaker to apply when multiple fields are used.
+     */
+    public void setGroupTieBreaker(float groupTieBreaker) {
+        // Force the tie breaker in the query builder too
+        queryBuilder.setTieBreaker(groupTieBreaker);
+        this.groupTieBreaker = groupTieBreaker;
+    }
+
+    private Query applyBoost(Query q, Float boost) {
+        if (boost != null && boost != 1f) {
+            return new BoostQuery(q, boost);
+        }
+        return q;
+    }
+
+    private Map<String, Float> extractMultiFields(String field, boolean quoted) {
+        if (field != null) {
+            Collection<String> fields = queryBuilder.context.simpleMatchToIndexNames(field);
+            Map<String, Float> weights = new HashMap<>();
+            for (String fieldName : fields) {
+                Float weight = fieldsAndWeights.get(fieldName);
+                if (quoted && quoteFieldSuffix != null
+                        && queryBuilder.context.fieldMapper(fieldName + quoteFieldSuffix) != null) {
+                    fieldName = fieldName + quoteFieldSuffix;
+                    weight = fieldsAndWeights.get(fieldName);
+                }
+                weights.put(fieldName, weight == null ? 1.0f : weight);
+            }
+            return weights;
+        } else {
+            return fieldsAndWeights;
         }
-        return getFuzzyQuery(qfield, termImage, fuzzySlop.image.substring(1));
     }
 
     @Override
@@ -144,124 +271,78 @@ public class MapperQueryParser extends QueryParser {
     public Query getFieldQuery(String field, String queryText, boolean quoted) throws ParseException {
         FieldQueryExtension fieldQueryExtension = FIELD_QUERY_EXTENSIONS.get(field);
         if (fieldQueryExtension != null) {
-            return fieldQueryExtension.query(context, queryText);
+            return fieldQueryExtension.query(queryBuilder.context, queryText);
         }
-        Collection<String> fields = extractMultiFields(field);
-        if (fields != null) {
-            if (fields.size() == 1) {
-                return getFieldQuerySingle(fields.iterator().next(), queryText, quoted);
-            } else if (fields.isEmpty()) {
-                // the requested fields do not match any field in the mapping
-                // happens for wildcard fields only since we cannot expand to a valid field name
-                // if there is no match in the mappings.
-                return new MatchNoDocsQuery("empty fields");
-            }
-            float tiebreaker = settings.useDisMax() ? settings.tieBreaker() : 1.0f;
-            List<Query> queries = new ArrayList<>();
-            boolean added = false;
-            for (String mField : fields) {
-                Query q = getFieldQuerySingle(mField, queryText, quoted);
-                if (q != null) {
-                    added = true;
-                    queries.add(applyBoost(mField, q));
-                }
-            }
-            if (!added) {
-                return null;
-            }
-            return new DisjunctionMaxQuery(queries, tiebreaker);
-        } else {
-            return getFieldQuerySingle(field, queryText, quoted);
+        if (quoted) {
+            return getFieldQuery(field, queryText, getPhraseSlop());
         }
-    }
 
-    private Query getFieldQuerySingle(String field, String queryText, boolean quoted) throws ParseException {
-        if (!quoted && queryText.length() > 1) {
-            if (queryText.charAt(0) == '>') {
-                if (queryText.length() > 2) {
-                    if (queryText.charAt(1) == '=') {
-                        return getRangeQuerySingle(field, queryText.substring(2), null, true, true, context);
+        // Detects additional operators '<', '<=', '>', '>=' to handle range query with one side unbounded.
+        // It is required to use a prefix field operator to enable the detection since they are not treated
+        // as logical operator by the query parser (e.g. age:>=10).
+        if (field != null) {
+            if (queryText.length() > 1) {
+                if (queryText.charAt(0) == '>') {
+                    if (queryText.length() > 2) {
+                        if (queryText.charAt(1) == '=') {
+                            return getRangeQuery(field, queryText.substring(2), null, true, true);
+                        }
                     }
-                }
-                return getRangeQuerySingle(field, queryText.substring(1), null, false, true, context);
-            } else if (queryText.charAt(0) == '<') {
-                if (queryText.length() > 2) {
-                    if (queryText.charAt(1) == '=') {
-                        return getRangeQuerySingle(field, null, queryText.substring(2), true, true, context);
+                    return getRangeQuery(field, queryText.substring(1), null, false, true);
+                } else if (queryText.charAt(0) == '<') {
+                    if (queryText.length() > 2) {
+                        if (queryText.charAt(1) == '=') {
+                            return getRangeQuery(field, null, queryText.substring(2), true, true);
+                        }
                     }
+                    return getRangeQuery(field, null, queryText.substring(1), true, false);
                 }
-                return getRangeQuerySingle(field, null, queryText.substring(1), true, false, context);
             }
         }
-        currentFieldType = null;
-        Analyzer oldAnalyzer = getAnalyzer();
+
+        Map<String, Float> fields = extractMultiFields(field, quoted);
+        if (fields.isEmpty()) {
+            // the requested fields do not match any field in the mapping
+            // happens for wildcard fields only since we cannot expand to a valid field name
+            // if there is no match in the mappings.
+            return new MatchNoDocsQuery("empty fields");
+        }
+        Analyzer oldAnalyzer = queryBuilder.analyzer;
         try {
-            if (quoted) {
-                setAnalyzer(settings.quoteAnalyzer());
-                if (settings.quoteFieldSuffix() != null) {
-                    currentFieldType = context.fieldMapper(field + settings.quoteFieldSuffix());
-                }
-            }
-            if (currentFieldType == null) {
-                currentFieldType = context.fieldMapper(field);
-            }
-            if (currentFieldType != null) {
-                if (quoted) {
-                    if (!settings.forceQuoteAnalyzer()) {
-                        setAnalyzer(context.getSearchQuoteAnalyzer(currentFieldType));
-                    }
-                } else {
-                    if (!settings.forceAnalyzer()) {
-                        setAnalyzer(context.getSearchAnalyzer(currentFieldType));
-                    }
-                }
-                if (currentFieldType != null) {
-                    Query query = null;
-                    if (currentFieldType.tokenized() == false) {
-                        // this might be a structured field like a numeric
-                        try {
-                            query = currentFieldType.termQuery(queryText, context);
-                        } catch (RuntimeException e) {
-                            if (settings.lenient()) {
-                                return null;
-                            } else {
-                                throw e;
-                            }
-                        }
-                    }
-                    if (query == null) {
-                        query = super.getFieldQuery(currentFieldType.name(), queryText, quoted);
-                    }
-                    return query;
-                }
+            if (forceAnalyzer != null) {
+                queryBuilder.setAnalyzer(forceAnalyzer);
             }
-            return super.getFieldQuery(field, queryText, quoted);
+            return queryBuilder.parse(type, fields, queryText, null);
+        } catch (IOException e) {
+            throw new ParseException(e.getMessage());
         } finally {
-            setAnalyzer(oldAnalyzer);
+            queryBuilder.setAnalyzer(oldAnalyzer);
         }
     }
 
     @Override
     protected Query getFieldQuery(String field, String queryText, int slop) throws ParseException {
-        Collection<String> fields = extractMultiFields(field);
-        if (fields != null) {
-            float tiebreaker = settings.useDisMax() ? settings.tieBreaker() : 1.0f;
-            List<Query> queries = new ArrayList<>();
-            boolean added = false;
-            for (String mField : fields) {
-                Query q = super.getFieldQuery(mField, queryText, slop);
-                if (q != null) {
-                    added = true;
-                    q = applySlop(q, slop);
-                    queries.add(applyBoost(mField, q));
-                }
-            }
-            if (!added) {
-                return null;
-            }
-            return new DisjunctionMaxQuery(queries, tiebreaker);
-        } else {
-            return super.getFieldQuery(field, queryText, slop);
+        Map<String, Float> fields = extractMultiFields(field, true);
+        if (fields.isEmpty()) {
+            return new MatchNoDocsQuery("empty fields");
+        }
+        final Query query;
+        Analyzer oldAnalyzer = queryBuilder.analyzer;
+        int oldSlop = queryBuilder.phraseSlop;
+        try {
+            if (forceQuoteAnalyzer != null) {
+                queryBuilder.setAnalyzer(forceQuoteAnalyzer);
+            } else if (forceAnalyzer != null) {
+                queryBuilder.setAnalyzer(forceAnalyzer);
+            }
+            queryBuilder.setPhraseSlop(slop);
+            query = queryBuilder.parse(MultiMatchQueryBuilder.Type.PHRASE, fields, queryText, null);
+            return applySlop(query, slop);
+        } catch (IOException e) {
+            throw new ParseException(e.getMessage());
+        } finally {
+            queryBuilder.setAnalyzer(oldAnalyzer);
+            queryBuilder.setPhraseSlop(oldSlop);
         }
     }
 
@@ -275,51 +356,46 @@ public class MapperQueryParser extends QueryParser {
             part2 = null;
         }
 
-        Collection<String> fields = extractMultiFields(field);
-
+        Map<String, Float> fields = extractMultiFields(field, false);
         if (fields == null) {
-            return getRangeQuerySingle(field, part1, part2, startInclusive, endInclusive, context);
-        }
-
-
-        if (fields.size() == 1) {
-            return getRangeQuerySingle(fields.iterator().next(), part1, part2, startInclusive, endInclusive, context);
+            return getRangeQuerySingle(field, part1, part2, startInclusive, endInclusive, queryBuilder.context);
         }
 
-        float tiebreaker = settings.useDisMax() ? settings.tieBreaker() : 1.0f;
         List<Query> queries = new ArrayList<>();
-        boolean added = false;
-        for (String mField : fields) {
-            Query q = getRangeQuerySingle(mField, part1, part2, startInclusive, endInclusive, context);
+        for (Map.Entry<String, Float> entry : fields.entrySet()) {
+            Query q = getRangeQuerySingle(entry.getKey(), part1, part2, startInclusive, endInclusive, context);
             if (q != null) {
-                added = true;
-                queries.add(applyBoost(mField, q));
+                queries.add(applyBoost(q, entry.getValue()));
             }
         }
-        if (!added) {
+        if (queries.size() == 0) {
             return null;
+        } else if (queries.size() == 1) {
+            return queries.get(0);
         }
+        float tiebreaker = groupTieBreaker == null ? type.tieBreaker() : groupTieBreaker;
         return new DisjunctionMaxQuery(queries, tiebreaker);
     }
 
     private Query getRangeQuerySingle(String field, String part1, String part2,
-            boolean startInclusive, boolean endInclusive, QueryShardContext context) {
+                                      boolean startInclusive, boolean endInclusive, QueryShardContext context) {
         currentFieldType = context.fieldMapper(field);
         if (currentFieldType != null) {
             try {
-                BytesRef part1Binary = part1 == null ? null : getAnalyzer().normalize(field, part1);
-                BytesRef part2Binary = part2 == null ? null : getAnalyzer().normalize(field, part2);
+                Analyzer normalizer = forceAnalyzer == null ? queryBuilder.context.getSearchAnalyzer(currentFieldType) : forceAnalyzer;
+                BytesRef part1Binary = part1 == null ? null : normalizer.normalize(field, part1);
+                BytesRef part2Binary = part2 == null ? null : normalizer.normalize(field, part2);
                 Query rangeQuery;
-                if (currentFieldType instanceof DateFieldMapper.DateFieldType && settings.timeZone() != null) {
+                if (currentFieldType instanceof DateFieldMapper.DateFieldType && timeZone != null) {
                     DateFieldMapper.DateFieldType dateFieldType = (DateFieldMapper.DateFieldType) this.currentFieldType;
                     rangeQuery = dateFieldType.rangeQuery(part1Binary, part2Binary,
-                            startInclusive, endInclusive, settings.timeZone(), null, context);
+                        startInclusive, endInclusive, timeZone, null, context);
                 } else {
                     rangeQuery = currentFieldType.rangeQuery(part1Binary, part2Binary, startInclusive, endInclusive, context);
                 }
                 return rangeQuery;
             } catch (RuntimeException e) {
-                if (settings.lenient()) {
+                if (lenient) {
                     return null;
                 }
                 throw e;
@@ -328,79 +404,80 @@ public class MapperQueryParser extends QueryParser {
         return newRangeQuery(field, part1, part2, startInclusive, endInclusive);
     }
 
-    protected Query getFuzzyQuery(String field, String termStr, String minSimilarity) throws ParseException {
-        Collection<String> fields = extractMultiFields(field);
-        if (fields != null) {
-            if (fields.size() == 1) {
-                return getFuzzyQuerySingle(fields.iterator().next(), termStr, minSimilarity);
-            }
-            float tiebreaker = settings.useDisMax() ? settings.tieBreaker() : 1.0f;
-            List<Query> queries = new ArrayList<>();
-            boolean added = false;
-            for (String mField : fields) {
-                Query q = getFuzzyQuerySingle(mField, termStr, minSimilarity);
-                if (q != null) {
-                    added = true;
-                    queries.add(applyBoost(mField, q));
-                }
-            }
-            if (!added) {
-                return null;
+    @Override
+    protected Query handleBareFuzzy(String field, Token fuzzySlop, String termImage) throws ParseException {
+        if (fuzzySlop.image.length() == 1) {
+            return getFuzzyQuery(field, termImage, fuzziness.asDistance(termImage));
+        }
+        return getFuzzyQuery(field, termImage, Fuzziness.build(fuzzySlop.image.substring(1)).asFloat());
+    }
+
+    @Override
+    protected Query getFuzzyQuery(String field, String termStr, float minSimilarity) throws ParseException {
+        Map<String, Float> fields = extractMultiFields(field, false);
+        float tiebreaker = groupTieBreaker == null ? type.tieBreaker() : groupTieBreaker;
+        List<Query> queries = new ArrayList<>();
+        for (Map.Entry<String, Float> entry : fields.entrySet()) {
+            Query q = getFuzzyQuerySingle(entry.getKey(), termStr, minSimilarity);
+            if (q != null) {
+                queries.add(applyBoost(q, entry.getValue()));
             }
-            return new DisjunctionMaxQuery(queries, tiebreaker);
+        }
+        if (queries.size() == 0) {
+            return null;
+        } else if (queries.size() == 1) {
+            return queries.get(0);
         } else {
-            return getFuzzyQuerySingle(field, termStr, minSimilarity);
+            return new DisjunctionMaxQuery(queries, tiebreaker);
         }
     }
 
-    private Query getFuzzyQuerySingle(String field, String termStr, String minSimilarity) throws ParseException {
+    private Query getFuzzyQuerySingle(String field, String termStr, float minSimilarity) throws ParseException {
         currentFieldType = context.fieldMapper(field);
         if (currentFieldType != null) {
             try {
-                BytesRef term = termStr == null ? null : getAnalyzer().normalize(field, termStr);
-                return currentFieldType.fuzzyQuery(term, Fuzziness.build(minSimilarity),
-                    getFuzzyPrefixLength(), settings.fuzzyMaxExpansions(), FuzzyQuery.defaultTranspositions);
+                Analyzer normalizer = forceAnalyzer == null ? queryBuilder.context.getSearchAnalyzer(currentFieldType) : forceAnalyzer;
+                BytesRef term = termStr == null ? null : normalizer.normalize(field, termStr);
+                return currentFieldType.fuzzyQuery(term, Fuzziness.fromEdits((int) minSimilarity),
+                    getFuzzyPrefixLength(), fuzzyMaxExpansions, FuzzyQuery.defaultTranspositions);
             } catch (RuntimeException e) {
-                if (settings.lenient()) {
+                if (lenient) {
                     return null;
                 }
                 throw e;
             }
         }
-        return super.getFuzzyQuery(field, termStr, Float.parseFloat(minSimilarity));
+        return super.getFuzzyQuery(field, termStr, minSimilarity);
     }
 
     @Override
     protected Query newFuzzyQuery(Term term, float minimumSimilarity, int prefixLength) {
-        String text = term.text();
-        int numEdits = FuzzyQuery.floatToEdits(minimumSimilarity, text.codePointCount(0, text.length()));
+        int numEdits = Fuzziness.build(minimumSimilarity).asDistance(term.text());
         FuzzyQuery query = new FuzzyQuery(term, numEdits, prefixLength,
-            settings.fuzzyMaxExpansions(), FuzzyQuery.defaultTranspositions);
-        QueryParsers.setRewriteMethod(query, settings.fuzzyRewriteMethod());
+            fuzzyMaxExpansions, FuzzyQuery.defaultTranspositions);
+        QueryParsers.setRewriteMethod(query, fuzzyRewriteMethod);
         return query;
     }
 
     @Override
     protected Query getPrefixQuery(String field, String termStr) throws ParseException {
-        Collection<String> fields = extractMultiFields(field);
+        Map<String, Float> fields = extractMultiFields(field, false);
         if (fields != null) {
-            if (fields.size() == 1) {
-                return getPrefixQuerySingle(fields.iterator().next(), termStr);
-            }
-            float tiebreaker = settings.useDisMax() ? settings.tieBreaker() : 1.0f;
+            float tiebreaker = groupTieBreaker == null ? type.tieBreaker() : groupTieBreaker;
             List<Query> queries = new ArrayList<>();
-            boolean added = false;
-            for (String mField : fields) {
-                Query q = getPrefixQuerySingle(mField, termStr);
+            for (Map.Entry<String, Float> entry : fields.entrySet()) {
+                Query q = getPrefixQuerySingle(entry.getKey(), termStr);
                 if (q != null) {
-                    added = true;
-                    queries.add(applyBoost(mField, q));
+                    queries.add(applyBoost(q, entry.getValue()));
                 }
             }
-            if (!added) {
+            if (queries.size() == 0) {
                 return null;
+            } else if (queries.size() == 1) {
+                return queries.get(0);
+            } else {
+                return new DisjunctionMaxQuery(queries, tiebreaker);
             }
-            return new DisjunctionMaxQuery(queries, tiebreaker);
         } else {
             return getPrefixQuerySingle(field, termStr);
         }
@@ -412,9 +489,7 @@ public class MapperQueryParser extends QueryParser {
         try {
             currentFieldType = context.fieldMapper(field);
             if (currentFieldType != null) {
-                if (!settings.forceAnalyzer()) {
-                    setAnalyzer(context.getSearchAnalyzer(currentFieldType));
-                }
+                setAnalyzer(forceAnalyzer == null ? queryBuilder.context.getSearchAnalyzer(currentFieldType) : forceAnalyzer);
                 Query query = null;
                 if (currentFieldType instanceof StringFieldType == false) {
                     query = currentFieldType.prefixQuery(termStr, getMultiTermRewriteMethod(), context);
@@ -426,7 +501,7 @@ public class MapperQueryParser extends QueryParser {
             }
             return getPossiblyAnalyzedPrefixQuery(field, termStr);
         } catch (RuntimeException e) {
-            if (settings.lenient()) {
+            if (lenient) {
                 return null;
             }
             throw e;
@@ -436,7 +511,7 @@ public class MapperQueryParser extends QueryParser {
     }
 
     private Query getPossiblyAnalyzedPrefixQuery(String field, String termStr) throws ParseException {
-        if (!settings.analyzeWildcard()) {
+        if (analyzeWildcard == false) {
             return super.getPrefixQuery(field, termStr);
         }
         List<List<String> > tlist;
@@ -531,27 +606,26 @@ public class MapperQueryParser extends QueryParser {
                 actualField = this.field;
             }
             // effectively, we check if a field exists or not
-            return FIELD_QUERY_EXTENSIONS.get(ExistsFieldQueryExtension.NAME).query(context, actualField);
+            return FIELD_QUERY_EXTENSIONS.get(ExistsFieldQueryExtension.NAME).query(queryBuilder.context, actualField);
         }
-        Collection<String> fields = extractMultiFields(field);
+
+        Map<String, Float> fields = extractMultiFields(field, false);
         if (fields != null) {
-            if (fields.size() == 1) {
-                return getWildcardQuerySingle(fields.iterator().next(), termStr);
-            }
-            float tiebreaker = settings.useDisMax() ? settings.tieBreaker() : 1.0f;
+            float tiebreaker = groupTieBreaker == null ? type.tieBreaker() : groupTieBreaker;
             List<Query> queries = new ArrayList<>();
-            boolean added = false;
-            for (String mField : fields) {
-                Query q = getWildcardQuerySingle(mField, termStr);
+            for (Map.Entry<String, Float> entry : fields.entrySet()) {
+                Query q = getWildcardQuerySingle(entry.getKey(), termStr);
                 if (q != null) {
-                    added = true;
-                    queries.add(applyBoost(mField, q));
+                    queries.add(applyBoost(q, entry.getValue()));
                 }
             }
-            if (!added) {
+            if (queries.size() == 0) {
                 return null;
+            } else if (queries.size() == 1) {
+                return queries.get(0);
+            } else {
+                return new DisjunctionMaxQuery(queries, tiebreaker);
             }
-            return new DisjunctionMaxQuery(queries, tiebreaker);
         } else {
             return getWildcardQuerySingle(field, termStr);
         }
@@ -560,22 +634,20 @@ public class MapperQueryParser extends QueryParser {
     private Query getWildcardQuerySingle(String field, String termStr) throws ParseException {
         if ("*".equals(termStr)) {
             // effectively, we check if a field exists or not
-            return FIELD_QUERY_EXTENSIONS.get(ExistsFieldQueryExtension.NAME).query(context, field);
+            return FIELD_QUERY_EXTENSIONS.get(ExistsFieldQueryExtension.NAME).query(queryBuilder.context, field);
         }
         String indexedNameField = field;
         currentFieldType = null;
         Analyzer oldAnalyzer = getAnalyzer();
         try {
-            currentFieldType = context.fieldMapper(field);
+            currentFieldType = queryBuilder.context.fieldMapper(field);
             if (currentFieldType != null) {
-                if (!settings.forceAnalyzer()) {
-                    setAnalyzer(context.getSearchAnalyzer(currentFieldType));
-                }
+                setAnalyzer(forceAnalyzer == null ? queryBuilder.context.getSearchAnalyzer(currentFieldType) : forceAnalyzer);
                 indexedNameField = currentFieldType.name();
             }
             return super.getWildcardQuery(indexedNameField, termStr);
         } catch (RuntimeException e) {
-            if (settings.lenient()) {
+            if (lenient) {
                 return null;
             }
             throw e;
@@ -586,25 +658,23 @@ public class MapperQueryParser extends QueryParser {
 
     @Override
     protected Query getRegexpQuery(String field, String termStr) throws ParseException {
-        Collection<String> fields = extractMultiFields(field);
+        Map<String, Float> fields = extractMultiFields(field, false);
         if (fields != null) {
-            if (fields.size() == 1) {
-                return getRegexpQuerySingle(fields.iterator().next(), termStr);
-            }
-            float tiebreaker = settings.useDisMax() ? settings.tieBreaker() : 1.0f;
+            float tiebreaker = groupTieBreaker == null ? type.tieBreaker() : groupTieBreaker;
             List<Query> queries = new ArrayList<>();
-            boolean added = false;
-            for (String mField : fields) {
-                Query q = getRegexpQuerySingle(mField, termStr);
+            for (Map.Entry<String, Float> entry : fields.entrySet()) {
+                Query q = getRegexpQuerySingle(entry.getKey(), termStr);
                 if (q != null) {
-                    added = true;
-                    queries.add(applyBoost(mField, q));
+                    queries.add(applyBoost(q, entry.getValue()));
                 }
             }
-            if (!added) {
+            if (queries.size() == 0) {
                 return null;
+            } else if (queries.size() == 1) {
+                return queries.get(0);
+            } else {
+                return new DisjunctionMaxQuery(queries, tiebreaker);
             }
-            return new DisjunctionMaxQuery(queries, tiebreaker);
         } else {
             return getRegexpQuerySingle(field, termStr);
         }
@@ -614,24 +684,15 @@ public class MapperQueryParser extends QueryParser {
         currentFieldType = null;
         Analyzer oldAnalyzer = getAnalyzer();
         try {
-            currentFieldType = context.fieldMapper(field);
+            currentFieldType = queryBuilder.context.fieldMapper(field);
             if (currentFieldType != null) {
-                if (!settings.forceAnalyzer()) {
-                    setAnalyzer(context.getSearchAnalyzer(currentFieldType));
-                }
-                Query query = null;
-                if (currentFieldType.tokenized() == false) {
-                    query = currentFieldType.regexpQuery(termStr, RegExp.ALL,
-                        getMaxDeterminizedStates(), getMultiTermRewriteMethod(), context);
-                }
-                if (query == null) {
-                    query = super.getRegexpQuery(field, termStr);
-                }
+                setAnalyzer(forceAnalyzer == null ? queryBuilder.context.getSearchAnalyzer(currentFieldType) : forceAnalyzer);
+                Query query = super.getRegexpQuery(field, termStr);
                 return query;
             }
             return super.getRegexpQuery(field, termStr);
         } catch (RuntimeException e) {
-            if (settings.lenient()) {
+            if (lenient) {
                 return null;
             }
             throw e;
@@ -640,7 +701,6 @@ public class MapperQueryParser extends QueryParser {
         }
     }
 
-
     @Override
     protected Query getBooleanQuery(List<BooleanClause> clauses) throws ParseException {
         Query q = super.getBooleanQuery(clauses);
@@ -650,14 +710,6 @@ public class MapperQueryParser extends QueryParser {
         return fixNegativeQueryIfNeeded(q);
     }
 
-    private Query applyBoost(String field, Query q) {
-        Float fieldBoost = settings.fieldsAndWeights() == null ? null : settings.fieldsAndWeights().get(field);
-        if (fieldBoost != null && fieldBoost != 1f) {
-            return new BoostQuery(q, fieldBoost);
-        }
-        return q;
-    }
-
     private Query applySlop(Query q, int slop) {
         if (q instanceof PhraseQuery) {
             //make sure that the boost hasn't been set beforehand, otherwise we'd lose it
@@ -705,24 +757,10 @@ public class MapperQueryParser extends QueryParser {
         return builder.build();
     }
 
-    private Collection<String> extractMultiFields(String field) {
-        Collection<String> fields;
-        if (field != null) {
-            fields = context.simpleMatchToIndexNames(field);
-        } else {
-            Map<String, Float> fieldsAndWeights = settings.fieldsAndWeights();
-            fields = fieldsAndWeights == null ? Collections.emptyList() : fieldsAndWeights.keySet();
-        }
-        return fields;
-    }
-
     @Override
     public Query parse(String query) throws ParseException {
         if (query.trim().isEmpty()) {
-            // if the query string is empty we return no docs / empty result
-            // the behavior is simple to change in the client if all docs is required
-            // or a default query
-            return new MatchNoDocsQuery();
+            return queryBuilder.zeroTermsQuery();
         }
         return super.parse(query);
     }

+ 2 - 12
core/src/test/java/org/elasticsearch/index/query/DisableGraphQueryTests.java

@@ -220,32 +220,22 @@ public class DisableGraphQueryTests extends ESSingleNodeTestCase {
     public void testQueryString() throws IOException {
         QueryStringQueryBuilder builder = new QueryStringQueryBuilder("foo bar baz");
         builder.field("text_shingle_unigram");
-        builder.splitOnWhitespace(false);
         Query query = builder.doToQuery(shardContext);
         assertThat(expectedQueryWithUnigram, equalTo(query));
 
         builder = new QueryStringQueryBuilder("\"foo bar baz\"");
         builder.field("text_shingle_unigram");
-        builder.splitOnWhitespace(false);
         query = builder.doToQuery(shardContext);
-        assertThat(query, instanceOf(DisjunctionMaxQuery.class));
-        DisjunctionMaxQuery maxQuery = (DisjunctionMaxQuery) query;
-        assertThat(maxQuery.getDisjuncts().size(), equalTo(1));
-        assertThat(expectedPhraseQueryWithUnigram, equalTo(maxQuery.getDisjuncts().get(0)));
+        assertThat(expectedPhraseQueryWithUnigram, equalTo(query));
 
         builder = new QueryStringQueryBuilder("foo bar baz biz");
         builder.field("text_shingle");
-        builder.splitOnWhitespace(false);
         query = builder.doToQuery(shardContext);
         assertThat(expectedQuery, equalTo(query));
 
         builder = new QueryStringQueryBuilder("\"foo bar baz biz\"");
         builder.field("text_shingle");
-        builder.splitOnWhitespace(false);
         query = builder.doToQuery(shardContext);
-        assertThat(query, instanceOf(DisjunctionMaxQuery.class));
-        maxQuery = (DisjunctionMaxQuery) query;
-        assertThat(maxQuery.getDisjuncts().size(), equalTo(1));
-        assertThat(expectedPhraseQuery, equalTo(maxQuery.getDisjuncts().get(0)));
+        assertThat(expectedPhraseQuery, equalTo(query));
     }
 }

+ 59 - 123
core/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java

@@ -21,8 +21,6 @@ package org.elasticsearch.index.query;
 
 import org.apache.lucene.analysis.MockSynonymAnalyzer;
 import org.apache.lucene.index.Term;
-import org.apache.lucene.queryparser.classic.MapperQueryParser;
-import org.apache.lucene.queryparser.classic.QueryParserSettings;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanClause.Occur;
 import org.apache.lucene.search.BooleanQuery;
@@ -43,6 +41,7 @@ import org.apache.lucene.search.TermRangeQuery;
 import org.apache.lucene.search.WildcardQuery;
 import org.apache.lucene.search.spans.SpanNearQuery;
 import org.apache.lucene.search.spans.SpanOrQuery;
+import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.lucene.search.spans.SpanTermQuery;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.automaton.TooComplexToDeterminizeException;
@@ -51,10 +50,12 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
 import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.common.compress.CompressedXContent;
 import org.elasticsearch.common.lucene.all.AllTermQuery;
+import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
 import org.elasticsearch.common.unit.Fuzziness;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.index.mapper.MapperService;
+import org.elasticsearch.index.search.QueryStringQueryParser;
 import org.elasticsearch.search.internal.SearchContext;
 import org.elasticsearch.test.AbstractQueryTestCase;
 import org.hamcrest.Matchers;
@@ -62,7 +63,6 @@ import org.joda.time.DateTimeZone;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 import static org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder;
@@ -160,10 +160,7 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         if (randomBoolean()) {
             queryStringQueryBuilder.timeZone(randomDateTimeZone().getID());
         }
-        if (queryStringQueryBuilder.autoGeneratePhraseQueries() == false) {
-            // setSplitOnWhitespace(false) is disallowed when getAutoGeneratePhraseQueries() == true
-            queryStringQueryBuilder.splitOnWhitespace(randomBoolean());
-        }
+        queryStringQueryBuilder.type(randomFrom(MultiMatchQueryBuilder.Type.values()));
         return queryStringQueryBuilder;
     }
 
@@ -175,7 +172,8 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         } else {
             assertThat(query, either(instanceOf(TermQuery.class)).or(instanceOf(AllTermQuery.class))
                     .or(instanceOf(BooleanQuery.class)).or(instanceOf(DisjunctionMaxQuery.class))
-                    .or(instanceOf(PhraseQuery.class)));
+                    .or(instanceOf(PhraseQuery.class)).or(instanceOf(BoostQuery.class))
+                    .or(instanceOf(MultiPhrasePrefixQuery.class)).or(instanceOf(PrefixQuery.class)).or(instanceOf(SpanQuery.class)));
         }
     }
 
@@ -202,11 +200,8 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
             .defaultField(STRING_FIELD_NAME)
             .phraseSlop(3)
             .toQuery(createShardContext());
-        assertThat(query, instanceOf(DisjunctionMaxQuery.class));
-        DisjunctionMaxQuery disjunctionMaxQuery = (DisjunctionMaxQuery) query;
-        assertThat(disjunctionMaxQuery.getDisjuncts().size(), equalTo(1));
-        assertThat(disjunctionMaxQuery.getDisjuncts().get(0), instanceOf(PhraseQuery.class));
-        PhraseQuery phraseQuery = (PhraseQuery)disjunctionMaxQuery.getDisjuncts().get(0);
+        assertThat(query, instanceOf(PhraseQuery.class));
+        PhraseQuery phraseQuery = (PhraseQuery) query;
         assertThat(phraseQuery.getTerms().length, equalTo(2));
         assertThat(phraseQuery.getTerms()[0], equalTo(new Term(STRING_FIELD_NAME, "term1")));
         assertThat(phraseQuery.getTerms()[1], equalTo(new Term(STRING_FIELD_NAME, "term2")));
@@ -256,7 +251,6 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
     public void testToQueryMultipleTermsBooleanQuery() throws Exception {
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         Query query = queryStringQuery("test1 test2").field(STRING_FIELD_NAME)
-            .useDisMax(false)
             .toQuery(createShardContext());
         assertThat(query, instanceOf(BooleanQuery.class));
         BooleanQuery bQuery = (BooleanQuery) query;
@@ -271,7 +265,6 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         Query query = queryStringQuery("test").field(STRING_FIELD_NAME)
             .field(STRING_FIELD_NAME_2)
-            .useDisMax(false)
             .toQuery(createShardContext());
         assertThat(query, instanceOf(DisjunctionMaxQuery.class));
         DisjunctionMaxQuery bQuery = (DisjunctionMaxQuery) query;
@@ -285,7 +278,6 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
     public void testToQueryMultipleFieldsDisMaxQuery() throws Exception {
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         Query query = queryStringQuery("test").field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2)
-            .useDisMax(true)
             .toQuery(createShardContext());
         assertThat(query, instanceOf(DisjunctionMaxQuery.class));
         DisjunctionMaxQuery disMaxQuery = (DisjunctionMaxQuery) query;
@@ -296,7 +288,7 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
 
     public void testToQueryFieldsWildcard() throws Exception {
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
-        Query query = queryStringQuery("test").field("mapped_str*").useDisMax(false).toQuery(createShardContext());
+        Query query = queryStringQuery("test").field("mapped_str*").toQuery(createShardContext());
         assertThat(query, instanceOf(DisjunctionMaxQuery.class));
         DisjunctionMaxQuery dQuery = (DisjunctionMaxQuery) query;
         assertThat(dQuery.getDisjuncts().size(), equalTo(2));
@@ -310,7 +302,6 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         Query query = queryStringQuery("test").field(STRING_FIELD_NAME, 2.2f)
             .field(STRING_FIELD_NAME_2)
-            .useDisMax(true)
             .toQuery(createShardContext());
         assertThat(query, instanceOf(DisjunctionMaxQuery.class));
         DisjunctionMaxQuery disMaxQuery = (DisjunctionMaxQuery) query;
@@ -323,14 +314,10 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         for (Operator op : Operator.values()) {
             BooleanClause.Occur defaultOp = op.toBooleanClauseOccur();
-            MapperQueryParser queryParser = new MapperQueryParser(createShardContext());
-            QueryParserSettings settings = new QueryParserSettings("first foo-bar-foobar* last");
-            settings.defaultField(STRING_FIELD_NAME);
-            settings.analyzeWildcard(true);
-            settings.fuzziness(Fuzziness.AUTO);
-            settings.rewriteMethod(MultiTermQuery.CONSTANT_SCORE_REWRITE);
-            settings.defaultOperator(op.toQueryParserOperator());
-            queryParser.reset(settings);
+            QueryStringQueryParser queryParser = new QueryStringQueryParser(createShardContext(), STRING_FIELD_NAME);
+            queryParser.setAnalyzeWildcard(true);
+            queryParser.setMultiTermRewriteMethod(MultiTermQuery.CONSTANT_SCORE_REWRITE);
+            queryParser.setDefaultOperator(op.toQueryParserOperator());
             Query query = queryParser.parse("first foo-bar-foobar* last");
             Query expectedQuery =
                 new BooleanQuery.Builder()
@@ -350,15 +337,11 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         for (Operator op : Operator.values()) {
             BooleanClause.Occur defaultOp = op.toBooleanClauseOccur();
-            MapperQueryParser queryParser = new MapperQueryParser(createShardContext());
-            QueryParserSettings settings = new QueryParserSettings("first foo-bar-foobar* last");
-            settings.defaultField(STRING_FIELD_NAME);
-            settings.analyzeWildcard(true);
-            settings.fuzziness(Fuzziness.AUTO);
-            settings.rewriteMethod(MultiTermQuery.CONSTANT_SCORE_REWRITE);
-            settings.defaultOperator(op.toQueryParserOperator());
-            settings.forceAnalyzer(new MockRepeatAnalyzer());
-            queryParser.reset(settings);
+            QueryStringQueryParser queryParser = new QueryStringQueryParser(createShardContext(), STRING_FIELD_NAME);
+            queryParser.setAnalyzeWildcard(true);
+            queryParser.setMultiTermRewriteMethod(MultiTermQuery.CONSTANT_SCORE_REWRITE);
+            queryParser.setDefaultOperator(op.toQueryParserOperator());
+            queryParser.setForceAnalyzer(new MockRepeatAnalyzer());
             Query query = queryParser.parse("first foo-bar-foobar* last");
 
             Query expectedQuery = new BooleanQuery.Builder()
@@ -387,16 +370,14 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         for (Operator op : Operator.values()) {
             BooleanClause.Occur defaultOp = op.toBooleanClauseOccur();
-            MapperQueryParser queryParser = new MapperQueryParser(createShardContext());
-            QueryParserSettings settings = new QueryParserSettings("");
-            settings.defaultField(STRING_FIELD_NAME);
-            settings.fuzziness(Fuzziness.AUTO);
-            settings.analyzeWildcard(true);
-            settings.rewriteMethod(MultiTermQuery.CONSTANT_SCORE_REWRITE);
-            settings.defaultOperator(op.toQueryParserOperator());
-            settings.forceAnalyzer(new MockSynonymAnalyzer());
-            settings.forceQuoteAnalyzer(new MockSynonymAnalyzer());
-            queryParser.reset(settings);
+            QueryStringQueryParser queryParser = new QueryStringQueryParser(createShardContext(), STRING_FIELD_NAME);
+            queryParser.setAnalyzeWildcard(true);
+            queryParser.setMultiTermRewriteMethod(MultiTermQuery.CONSTANT_SCORE_REWRITE);
+            queryParser.setDefaultOperator(op.toQueryParserOperator());
+            queryParser.setAnalyzeWildcard(true);
+            queryParser.setMultiTermRewriteMethod(MultiTermQuery.CONSTANT_SCORE_REWRITE);
+            queryParser.setDefaultOperator(op.toQueryParserOperator());
+            queryParser.setForceAnalyzer(new MockSynonymAnalyzer());
 
             // simple multi-term
             Query query = queryParser.parse("guinea pig");
@@ -455,7 +436,7 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
             // span query
             query = queryParser.parse("\"that guinea pig smells\"");
 
-            SpanNearQuery nearQuery = new SpanNearQuery.Builder(STRING_FIELD_NAME, true)
+            expectedQuery = new SpanNearQuery.Builder(STRING_FIELD_NAME, true)
                 .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "that")))
                 .addClause(
                     new SpanOrQuery(
@@ -465,12 +446,11 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
                         new SpanTermQuery(new Term(STRING_FIELD_NAME, "cavy"))))
                     .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "smells")))
                     .build();
-            expectedQuery = new DisjunctionMaxQuery(Collections.singletonList(nearQuery), 1.0f);
             assertThat(query, Matchers.equalTo(expectedQuery));
 
             // span query with slop
             query = queryParser.parse("\"that guinea pig smells\"~2");
-            nearQuery = new SpanNearQuery.Builder(STRING_FIELD_NAME, true)
+            expectedQuery = new SpanNearQuery.Builder(STRING_FIELD_NAME, true)
                 .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "that")))
                 .addClause(
                     new SpanOrQuery(
@@ -481,7 +461,6 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
                 .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "smells")))
                 .setSlop(2)
                 .build();
-            expectedQuery = new DisjunctionMaxQuery(Collections.singletonList(nearQuery), 1.0f);
             assertThat(query, Matchers.equalTo(expectedQuery));
         }
     }
@@ -678,11 +657,8 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         QueryStringQueryBuilder queryStringQueryBuilder =
             new QueryStringQueryBuilder("\"test phrase\"~2").field(STRING_FIELD_NAME, 5f);
         Query query = queryStringQueryBuilder.toQuery(createShardContext());
-        assertThat(query, instanceOf(DisjunctionMaxQuery.class));
-        DisjunctionMaxQuery disjunctionMaxQuery = (DisjunctionMaxQuery) query;
-        assertThat(disjunctionMaxQuery.getDisjuncts().size(), equalTo(1));
-        assertThat(disjunctionMaxQuery.getDisjuncts().get(0), instanceOf(BoostQuery.class));
-        BoostQuery boostQuery = (BoostQuery) disjunctionMaxQuery.getDisjuncts().get(0);
+        assertThat(query, instanceOf(BoostQuery.class));
+        BoostQuery boostQuery = (BoostQuery) query;
         assertThat(boostQuery.getBoost(), equalTo(5f));
         assertThat(boostQuery.getQuery(), instanceOf(PhraseQuery.class));
         PhraseQuery phraseQuery = (PhraseQuery) boostQuery.getQuery();
@@ -695,10 +671,8 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         QueryStringQueryBuilder queryStringQueryBuilder =
             new QueryStringQueryBuilder("foo bar").field("invalid*");
         Query query = queryStringQueryBuilder.toQuery(createShardContext());
-        Query expectedQuery = new BooleanQuery.Builder()
-            .add(new MatchNoDocsQuery("empty fields"), Occur.SHOULD)
-            .add(new MatchNoDocsQuery("empty fields"), Occur.SHOULD)
-            .build();
+
+        Query expectedQuery = new MatchNoDocsQuery("empty fields");
         assertThat(expectedQuery, equalTo(query));
 
         queryStringQueryBuilder =
@@ -709,18 +683,14 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
             .add(new MatchNoDocsQuery("empty fields"), Occur.SHOULD)
             .build();
         assertThat(expectedQuery, equalTo(query));
-
-
     }
 
-    public void testToQuerySplitOnWhitespace() throws IOException {
+    public void testToQueryTextParsing() throws IOException {
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
-        // splitOnWhitespace=false
         {
             QueryStringQueryBuilder queryBuilder =
                 new QueryStringQueryBuilder("foo bar")
-                    .field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2)
-                    .splitOnWhitespace(false);
+                    .field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2);
             Query query = queryBuilder.toQuery(createShardContext());
             BooleanQuery bq1 =
                 new BooleanQuery.Builder()
@@ -734,11 +704,29 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
             assertThat(query, equalTo(expectedQuery));
         }
 
+        //  type=phrase
+        {
+            QueryStringQueryBuilder queryBuilder =
+                new QueryStringQueryBuilder("foo bar")
+                    .field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2);
+            queryBuilder.type(MultiMatchQueryBuilder.Type.PHRASE);
+            Query query = queryBuilder.toQuery(createShardContext());
+
+            List<Query> disjuncts = new ArrayList<>();
+            PhraseQuery pq = new PhraseQuery.Builder()
+                .add(new Term(STRING_FIELD_NAME, "foo"))
+                .add(new Term(STRING_FIELD_NAME, "bar"))
+                .build();
+            disjuncts.add(pq);
+            disjuncts.add(new TermQuery(new Term(STRING_FIELD_NAME_2, "foo bar")));
+            DisjunctionMaxQuery expectedQuery = new DisjunctionMaxQuery(disjuncts, 0.0f);
+            assertThat(query, equalTo(expectedQuery));
+        }
+
         {
             QueryStringQueryBuilder queryBuilder =
                 new QueryStringQueryBuilder("mapped_string:other foo bar")
-                    .field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2)
-                    .splitOnWhitespace(false);
+                    .field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2);
             Query query = queryBuilder.toQuery(createShardContext());
             BooleanQuery bq1 =
                 new BooleanQuery.Builder()
@@ -760,8 +748,7 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         {
             QueryStringQueryBuilder queryBuilder =
                 new QueryStringQueryBuilder("foo OR bar")
-                    .field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2)
-                    .splitOnWhitespace(false);
+                    .field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2);
             Query query = queryBuilder.toQuery(createShardContext());
 
             List<Query> disjuncts1 = new ArrayList<>();
@@ -782,54 +769,15 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
             assertThat(query, equalTo(expectedQuery));
         }
 
-        // split_on_whitespace=false breaks range query with simple syntax
+        // non-prefix queries do not work with range queries simple syntax
         {
             // throws an exception when lenient is set to false
             QueryStringQueryBuilder queryBuilder =
                 new QueryStringQueryBuilder(">10 foo")
-                    .field(INT_FIELD_NAME)
-                    .splitOnWhitespace(false);
+                    .field(INT_FIELD_NAME);
             IllegalArgumentException exc =
                 expectThrows(IllegalArgumentException.class, () -> queryBuilder.toQuery(createShardContext()));
-            assertThat(exc.getMessage(), equalTo("For input string: \"10 foo\""));
-        }
-
-        {
-            // returns an empty boolean query when lenient is set to true
-            QueryStringQueryBuilder queryBuilder =
-                new QueryStringQueryBuilder(">10 foo")
-                    .field(INT_FIELD_NAME)
-                    .splitOnWhitespace(false)
-                    .lenient(true);
-            Query query = queryBuilder.toQuery(createShardContext());
-            BooleanQuery bq = new BooleanQuery.Builder().build();
-            assertThat(bq, equalTo(query));
-        }
-
-        // splitOnWhitespace=true
-        {
-            QueryStringQueryBuilder queryBuilder =
-                new QueryStringQueryBuilder("foo bar")
-                    .field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2)
-                    .splitOnWhitespace(true);
-            Query query = queryBuilder.toQuery(createShardContext());
-
-            List<Query> disjuncts1 = new ArrayList<>();
-            disjuncts1.add(new TermQuery(new Term(STRING_FIELD_NAME, "foo")));
-            disjuncts1.add(new TermQuery(new Term(STRING_FIELD_NAME_2, "foo")));
-            DisjunctionMaxQuery maxQuery1 = new DisjunctionMaxQuery(disjuncts1, 0.0f);
-
-            List<Query> disjuncts2 = new ArrayList<>();
-            disjuncts2.add(new TermQuery(new Term(STRING_FIELD_NAME, "bar")));
-            disjuncts2.add(new TermQuery(new Term(STRING_FIELD_NAME_2, "bar")));
-            DisjunctionMaxQuery maxQuery2 = new DisjunctionMaxQuery(disjuncts2, 0.0f);
-
-            BooleanQuery expectedQuery =
-                new BooleanQuery.Builder()
-                    .add(new BooleanClause(maxQuery1, BooleanClause.Occur.SHOULD))
-                    .add(new BooleanClause(maxQuery2, BooleanClause.Occur.SHOULD))
-                    .build();
-            assertThat(query, equalTo(expectedQuery));
+            assertThat(exc.getMessage(), equalTo("For input string: \">10 foo\""));
         }
     }
 
@@ -893,10 +841,9 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
                 "    \"query\" : \"this AND that OR thus\",\n" +
                 "    \"default_field\" : \"content\",\n" +
                 "    \"fields\" : [ ],\n" +
-                "    \"use_dis_max\" : true,\n" +
+                "    \"type\" : \"best_fields\",\n" +
                 "    \"tie_breaker\" : 0.0,\n" +
                 "    \"default_operator\" : \"or\",\n" +
-                "    \"auto_generate_phrase_queries\" : false,\n" +
                 "    \"max_determinized_states\" : 10000,\n" +
                 "    \"enable_position_increments\" : true,\n" +
                 "    \"fuzziness\" : \"AUTO\",\n" +
@@ -904,7 +851,6 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
                 "    \"fuzzy_max_expansions\" : 50,\n" +
                 "    \"phrase_slop\" : 0,\n" +
                 "    \"escape\" : false,\n" +
-                "    \"split_on_whitespace\" : true,\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
                 "}";
@@ -995,14 +941,4 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         assertThat(e.getMessage(),
                 containsString("cannot use [all_fields] parameter in conjunction with [default_field] or [fields]"));
     }
-
-    public void testInvalidCombo() throws IOException {
-        QueryStringQueryBuilder builder = new QueryStringQueryBuilder("foo bar");
-        builder.autoGeneratePhraseQueries(true);
-        builder.splitOnWhitespace(false);
-        IllegalArgumentException exc =
-            expectThrows(IllegalArgumentException.class, () -> builder.toQuery(createShardContext()));
-        assertEquals(exc.getMessage(),
-            "it is disallowed to disable [split_on_whitespace] if [auto_generate_phrase_queries] is activated");
-    }
 }

+ 2 - 2
core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java

@@ -2558,8 +2558,8 @@ public class HighlighterSearchIT extends ESIntegTestCase {
 
         // Query string with a single field without dismax
         phraseBoostTestCaseForClauses(highlighterType, 100f,
-                queryStringQuery("highlight words together").field("field1").useDisMax(false),
-                queryStringQuery("\"highlight words together\"").field("field1").useDisMax(false).autoGeneratePhraseQueries(true));
+                queryStringQuery("highlight words together").field("field1"),
+                queryStringQuery("\"highlight words together\"").field("field1").autoGeneratePhraseQueries(true));
 
         // Query string with more than one field
         phraseBoostTestCaseForClauses(highlighterType, 100f,

+ 3 - 16
core/src/test/java/org/elasticsearch/search/query/QueryStringIT.java

@@ -140,7 +140,7 @@ public class QueryStringIT extends ESIntegTestCase {
         assertHits(resp.getHits(), "1", "2");
         assertHitCount(resp, 2L);
 
-        resp = client().prepareSearch("test").setQuery(queryStringQuery("127.0.0.1 1.8")).get();
+        resp = client().prepareSearch("test").setQuery(queryStringQuery("127.0.0.1 OR 1.8")).get();
         assertHits(resp.getHits(), "1", "2");
         assertHitCount(resp, 2L);
     }
@@ -201,7 +201,7 @@ public class QueryStringIT extends ESIntegTestCase {
         assertHitCount(resp, 2L);
 
         resp = client().prepareSearch("test")
-                .setQuery(queryStringQuery("Foo Bar").splitOnWhitespace(false))
+                .setQuery(queryStringQuery("Foo Bar"))
                 .get();
         assertHits(resp.getHits(), "1", "2", "3");
         assertHitCount(resp, 3L);
@@ -221,7 +221,7 @@ public class QueryStringIT extends ESIntegTestCase {
         assertHitCount(resp, 0L);
 
         resp = client().prepareSearch("test2").setQuery(
-                queryStringQuery("foo eggplant").defaultOperator(Operator.AND).useAllFields(true)).get();
+                queryStringQuery("foo eggplant").defaultOperator(Operator.OR).useAllFields(true)).get();
         assertHits(resp.getHits(), "1");
         assertHitCount(resp, 1L);
 
@@ -305,28 +305,16 @@ public class QueryStringIT extends ESIntegTestCase {
         searchResponse = client().prepareSearch(index).setQuery(
             QueryBuilders.queryStringQuery("say what the fudge")
                 .defaultField("field")
-                .splitOnWhitespace(false)
                 .defaultOperator(Operator.AND)
                 .analyzer("lower_graphsyns")).get();
 
         assertHitCount(searchResponse, 1L);
         assertSearchHits(searchResponse, "1");
 
-        // and, split on whitespace means we should not recognize the multi-word synonym
-        searchResponse = client().prepareSearch(index).setQuery(
-            QueryBuilders.queryStringQuery("say what the fudge")
-                .defaultField("field")
-                .splitOnWhitespace(true)
-                .defaultOperator(Operator.AND)
-                .analyzer("lower_graphsyns")).get();
-
-        assertNoSearchHits(searchResponse);
-
         // or
         searchResponse = client().prepareSearch(index).setQuery(
             QueryBuilders.queryStringQuery("three what the fudge foo")
                 .defaultField("field")
-                .splitOnWhitespace(false)
                 .defaultOperator(Operator.OR)
                 .analyzer("lower_graphsyns")).get();
 
@@ -337,7 +325,6 @@ public class QueryStringIT extends ESIntegTestCase {
         searchResponse = client().prepareSearch(index).setQuery(
             QueryBuilders.queryStringQuery("three what the fudge foo")
                 .defaultField("field")
-                .splitOnWhitespace(false)
                 .defaultOperator(Operator.OR)
                 .analyzer("lower_graphsyns")
                 .minimumShouldMatch("80%")).get();

+ 1 - 1
core/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java

@@ -971,7 +971,7 @@ public class SearchQueryIT extends ESIntegTestCase {
         assertThat((double)searchResponse.getHits().getAt(0).getScore(), closeTo(boost * searchResponse.getHits().getAt(1).getScore(), .1));
 
         searchResponse = client().prepareSearch()
-                .setQuery(queryStringQuery("\"phrase match\"").field("important", boost).field("less_important").useDisMax(false)).get();
+                .setQuery(queryStringQuery("\"phrase match\"").field("important", boost).field("less_important")).get();
         assertHitCount(searchResponse, 2L);
         assertFirstHit(searchResponse, hasId("1"));
         assertSecondHit(searchResponse, hasId("2"));

+ 14 - 2
docs/reference/migration/migrate_6_0/search.asciidoc

@@ -56,6 +56,18 @@
 * The `percolate` query's `document_type` has been deprecated. From 6.0 and later
   it is no longer required to specify the `document_type` parameter.
 
+* The `split_on_whitespace` parameter for the `query_string` query has been removed.
+  If provided, it will be ignored and issue a deprecation warning.
+  The `query_string` query now splits on logical operator only.
+
+* The `use_dismax` parameter for the `query_string` query has been removed.
+  If provided, it will be ignored and issue a deprecation warning.
+  The `tie_breaker` parameter must be used instead.
+
+* The `auto_generate_phrase_queries` parameter for the `query_string` query has been removed,
+  use an explicit quoted query instead.
+  If provided, it will be ignored and issue a deprecation warning.
+
 ==== Search shards API
 
 The search shards API no longer accepts the `type` url parameter, which didn't
@@ -102,7 +114,7 @@ for the `random_score` function. If you really need access to the id of
 documents for sorting, aggregations or search scripts, the recommendation is
 to duplicate the id as a field in the document.
 
-==== Highlighers
+==== Highlighters
 
 The `unified` highlighter is the new default choice for highlighter.
 The offset strategy for each field is picked internally by this highlighter depending on the
@@ -115,4 +127,4 @@ The `unified` highlighter outputs the same highlighting when `index_options` is
 
 ==== `fielddata_fields`
 
-The deprecated `fielddata_fields` have now been removed. `docvalue_fields` should be used instead.
+The deprecated `fielddata_fields` have now been removed. `docvalue_fields` should be used instead.

+ 41 - 18
docs/reference/query-dsl/query-string-query.asciidoc

@@ -18,6 +18,29 @@ GET /_search
 --------------------------------------------------
 // CONSOLE
 
+The `query_string` query parses the input and splits text around logical operators.
+Each textual part is analyzed independently of each other. For instance the following query:
+
+[source,js]
+--------------------------------------------------
+GET /_search
+{
+    "query": {
+        "query_string" : {
+            "default_field" : "content",
+            "query" : "(new york) AND ny"
+        }
+    }
+}
+--------------------------------------------------
+// CONSOLE
+
+... will be splitted in `new york` and `ny` and each part is analyzed independently.
+When multiple fields are provided it is also possible to modify how the different
+field queries are combined inside each textual part using the `type` parameter.
+The possible modes are described <<multi-match-types, here>> and the default is `best_fields`.
+
+
 The `query_string` top level parameters include:
 
 [cols="<,<",options="header",]
@@ -84,10 +107,6 @@ the query string. This allows to use a field that has a different analysis chain
 for exact matching. Look <<mixing-exact-search-with-stemming,here>> for a
 comprehensive example.
 
-|`split_on_whitespace` |Whether query text should be split on whitespace prior to analysis.
-Instead  the queryparser would parse around only real 'operators'. Default to `false`.
-It is not allowed to set this option to `false` if `auto_generate_phrase_queries` is already set to `true`.
-
 |`all_fields` | Perform the query on all fields detected in the mapping that can
 be queried. Will be used by default when the `_all` field is disabled and no
 `default_field` is specified (either in the index settings or in the request
@@ -156,9 +175,8 @@ GET /_search
 // CONSOLE
 
 Since several queries are generated from the individual search terms,
-combining them can be automatically done using either a `dis_max` query or a
-simple `bool` query. For example (the `name` is boosted by 5 using `^5`
-notation):
+combining them is automatically done using a `dis_max` query with a tie_breaker.
+For example (the `name` is boosted by 5 using `^5` notation):
 
 [source,js]
 --------------------------------------------------
@@ -168,7 +186,7 @@ GET /_search
         "query_string" : {
             "fields" : ["content", "name^5"],
             "query" : "this AND that OR thus",
-            "use_dis_max" : true
+            "tie_breaker" : 0
         }
     }
 }
@@ -187,8 +205,7 @@ GET /_search
     "query": {
         "query_string" : {
             "fields" : ["city.*"],
-            "query" : "this AND that OR thus",
-            "use_dis_max" : true
+            "query" : "this AND that OR thus"
         }
     }
 }
@@ -205,8 +222,7 @@ GET /_search
 {
     "query": {
         "query_string" : {
-            "query" : "city.\\*:(this AND that OR thus)",
-            "use_dis_max" : true
+            "query" : "city.\\*:(this AND that OR thus)"
         }
     }
 }
@@ -222,11 +238,19 @@ following additional parameters are allowed:
 [cols="<,<",options="header",]
 |=======================================================================
 |Parameter |Description
-|`use_dis_max` |Should the queries be combined using `dis_max` (set it
-to `true`), or a `bool` query (set it to `false`). Defaults to `true`.
 
-|`tie_breaker` |When using `dis_max`, the disjunction max tie breaker.
-Defaults to `0`.
+|`type` |How the fields should be combined to build the text query.
+See <<multi-match-types, types>> for a complete example.
+Defaults to `best_fields`
+|=======================================================================
+
+
+[cols="<,<",options="header",]
+|=======================================================================
+|Parameter |Description
+
+|`tie_breaker` |The disjunction max tie breaker for multi fields.
+Defaults to `0`
 |=======================================================================
 
 The fields parameter can also include pattern based field names,
@@ -240,8 +264,7 @@ GET /_search
     "query": {
         "query_string" : {
             "fields" : ["content", "name.*^5"],
-            "query" : "this AND that OR thus",
-            "use_dis_max" : true
+            "query" : "this AND that OR thus"
         }
     }
 }

+ 1 - 1
modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/QueryStringWithAnalyzersTests.java

@@ -65,7 +65,7 @@ public class QueryStringWithAnalyzersTests extends ESIntegTestCase {
         SearchResponse response = client()
                 .prepareSearch("test")
                 .setQuery(
-                        queryStringQuery("foo.baz").useDisMax(false).defaultOperator(Operator.AND)
+                        queryStringQuery("foo.baz").defaultOperator(Operator.AND)
                                 .field("field1").field("field2")).get();
         assertHitCount(response, 1L);
     }