Pārlūkot izejas kodu

Expose `fuzzy_transpositions` parameter in fuzzy queries (#26870)

Add fuzzy_transpositions parameter to multi_match and query_string queries.
Add fuzzy_transpositions, fuzzy_prefix_length and fuzzy_max_expansions
parameters to simple_query_string query.
Alexander Kazakov 8 gadi atpakaļ
vecāks
revīzija
9c95e91471

+ 35 - 3
core/src/main/java/org/elasticsearch/index/query/MultiMatchQueryBuilder.java

@@ -57,6 +57,7 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
     public static final int DEFAULT_MAX_EXPANSIONS = FuzzyQuery.defaultMaxExpansions;
     public static final boolean DEFAULT_LENIENCY = MatchQuery.DEFAULT_LENIENCY;
     public static final MatchQuery.ZeroTermsQuery DEFAULT_ZERO_TERMS_QUERY = MatchQuery.DEFAULT_ZERO_TERMS_QUERY;
+    public static final boolean DEFAULT_FUZZY_TRANSPOSITIONS = FuzzyQuery.defaultTranspositions;
 
     private static final ParseField SLOP_FIELD = new ParseField("slop");
     private static final ParseField ZERO_TERMS_QUERY_FIELD = new ParseField("zero_terms_query");
@@ -74,6 +75,7 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
     private static final ParseField QUERY_FIELD = new ParseField("query");
     private static final ParseField FIELDS_FIELD = new ParseField("fields");
     private static final ParseField GENERATE_SYNONYMS_PHRASE_QUERY = new ParseField("auto_generate_synonyms_phrase_query");
+    private static final ParseField FUZZY_TRANSPOSITIONS_FIELD = new ParseField("fuzzy_transpositions");
 
 
     private final Object value;
@@ -93,6 +95,7 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
     private Float cutoffFrequency = null;
     private MatchQuery.ZeroTermsQuery zeroTermsQuery = DEFAULT_ZERO_TERMS_QUERY;
     private boolean autoGenerateSynonymsPhraseQuery = true;
+    private boolean fuzzyTranspositions = DEFAULT_FUZZY_TRANSPOSITIONS;
 
     public enum Type implements Writeable {
 
@@ -226,6 +229,9 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
         if (in.getVersion().onOrAfter(Version.V_6_1_0)) {
             autoGenerateSynonymsPhraseQuery = in.readBoolean();
         }
+        if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
+            fuzzyTranspositions = in.readBoolean();
+        }
     }
 
     @Override
@@ -253,6 +259,9 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
         if (out.getVersion().onOrAfter(Version.V_6_1_0)) {
             out.writeBoolean(autoGenerateSynonymsPhraseQuery);
         }
+        if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
+            out.writeBoolean(fuzzyTranspositions);
+        }
     }
 
     public Object value() {
@@ -535,6 +544,22 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
         return autoGenerateSynonymsPhraseQuery;
     }
 
+    public boolean fuzzyTranspositions() {
+        return fuzzyTranspositions;
+    }
+
+    /**
+     * Sets whether transpositions are supported in fuzzy queries.<p>
+     * The default metric used by fuzzy queries to determine a match is the Damerau-Levenshtein
+     * distance formula which supports transpositions. Setting transposition to false will
+     * switch to classic Levenshtein distance.<br>
+     * If not set, Damerau-Levenshtein distance metric will be used.
+     */
+    public MultiMatchQueryBuilder fuzzyTranspositions(boolean fuzzyTranspositions) {
+        this.fuzzyTranspositions = fuzzyTranspositions;
+        return this;
+    }
+
     @Override
     public void doXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject(NAME);
@@ -573,6 +598,7 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
         }
         builder.field(ZERO_TERMS_QUERY_FIELD.getPreferredName(), zeroTermsQuery.toString());
         builder.field(GENERATE_SYNONYMS_PHRASE_QUERY.getPreferredName(), autoGenerateSynonymsPhraseQuery);
+        builder.field(FUZZY_TRANSPOSITIONS_FIELD.getPreferredName(), fuzzyTranspositions);
         printBoostAndQueryName(builder);
         builder.endObject();
     }
@@ -595,6 +621,7 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
         boolean lenient = DEFAULT_LENIENCY;
         MatchQuery.ZeroTermsQuery zeroTermsQuery = DEFAULT_ZERO_TERMS_QUERY;
         boolean autoGenerateSynonymsPhraseQuery = true;
+        boolean fuzzyTranspositions = DEFAULT_FUZZY_TRANSPOSITIONS;
 
         float boost = AbstractQueryBuilder.DEFAULT_BOOST;
         String queryName = null;
@@ -659,6 +686,8 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
                     queryName = parser.text();
                 } else if (GENERATE_SYNONYMS_PHRASE_QUERY.match(currentFieldName)) {
                     autoGenerateSynonymsPhraseQuery = parser.booleanValue();
+                } else if (FUZZY_TRANSPOSITIONS_FIELD.match(currentFieldName)) {
+                    fuzzyTranspositions = parser.booleanValue();
                 } else {
                     throw new ParsingException(parser.getTokenLocation(),
                             "[" + NAME + "] query does not support [" + currentFieldName + "]");
@@ -700,7 +729,8 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
                 .zeroTermsQuery(zeroTermsQuery)
                 .autoGenerateSynonymsPhraseQuery(autoGenerateSynonymsPhraseQuery)
                 .boost(boost)
-                .queryName(queryName);
+                .queryName(queryName)
+                .fuzzyTranspositions(fuzzyTranspositions);
     }
 
     private static void parseFieldAndBoost(XContentParser parser, Map<String, Float> fieldsBoosts) throws IOException {
@@ -755,6 +785,7 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
         multiMatchQuery.setLenient(lenient);
         multiMatchQuery.setZeroTermsQuery(zeroTermsQuery);
         multiMatchQuery.setAutoGenerateSynonymsPhraseQuery(autoGenerateSynonymsPhraseQuery);
+        multiMatchQuery.setTranspositions(fuzzyTranspositions);
 
         if (useDisMax != null) { // backwards foobar
             boolean typeUsesDismax = type.tieBreaker() != 1.0f;
@@ -775,7 +806,7 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
     protected int doHashCode() {
         return Objects.hash(value, fieldsBoosts, type, operator, analyzer, slop, fuzziness,
                 prefixLength, maxExpansions, minimumShouldMatch, fuzzyRewrite, useDisMax, tieBreaker, lenient,
-                cutoffFrequency, zeroTermsQuery, autoGenerateSynonymsPhraseQuery);
+                cutoffFrequency, zeroTermsQuery, autoGenerateSynonymsPhraseQuery, fuzzyTranspositions);
     }
 
     @Override
@@ -796,6 +827,7 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
                 Objects.equals(lenient, other.lenient) &&
                 Objects.equals(cutoffFrequency, other.cutoffFrequency) &&
                 Objects.equals(zeroTermsQuery, other.zeroTermsQuery) &&
-                Objects.equals(autoGenerateSynonymsPhraseQuery, other.autoGenerateSynonymsPhraseQuery);
+                Objects.equals(autoGenerateSynonymsPhraseQuery, other.autoGenerateSynonymsPhraseQuery) &&
+                Objects.equals(fuzzyTranspositions, other.fuzzyTranspositions);
     }
 }

+ 37 - 2
core/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java

@@ -68,6 +68,7 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
     public static final Fuzziness DEFAULT_FUZZINESS = Fuzziness.AUTO;
     public static final Operator DEFAULT_OPERATOR = Operator.OR;
     public static final MultiMatchQueryBuilder.Type DEFAULT_TYPE = MultiMatchQueryBuilder.Type.BEST_FIELDS;
+    public static final boolean DEFAULT_FUZZY_TRANSPOSITIONS = FuzzyQuery.defaultTranspositions;
 
     private static final ParseField QUERY_FIELD = new ParseField("query");
     private static final ParseField FIELDS_FIELD = new ParseField("fields");
@@ -104,6 +105,7 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
             .withAllDeprecated("Set [default_field] to `*` instead");
     private static final ParseField TYPE_FIELD = new ParseField("type");
     private static final ParseField GENERATE_SYNONYMS_PHRASE_QUERY = new ParseField("auto_generate_synonyms_phrase_query");
+    private static final ParseField FUZZY_TRANSPOSITIONS_FIELD = new ParseField("fuzzy_transpositions");
 
     private final String queryString;
 
@@ -161,6 +163,8 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
 
     private boolean autoGenerateSynonymsPhraseQuery = true;
 
+    private boolean fuzzyTranspositions = DEFAULT_FUZZY_TRANSPOSITIONS;
+
     public QueryStringQueryBuilder(String queryString) {
         if (queryString == null) {
             throw new IllegalArgumentException("query text missing");
@@ -226,6 +230,9 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         if (in.getVersion().onOrAfter(Version.V_6_1_0)) {
             autoGenerateSynonymsPhraseQuery = in.readBoolean();
         }
+        if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
+            fuzzyTranspositions = in.readBoolean();
+        }
     }
 
     @Override
@@ -281,6 +288,9 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         if (out.getVersion().onOrAfter(Version.V_6_1_0)) {
             out.writeBoolean(autoGenerateSynonymsPhraseQuery);
         }
+        if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
+            out.writeBoolean(fuzzyTranspositions);
+        }
     }
 
     public String queryString() {
@@ -648,6 +658,22 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         return autoGenerateSynonymsPhraseQuery;
     }
 
+    public boolean fuzzyTranspositions() {
+        return fuzzyTranspositions;
+    }
+
+    /**
+     * Sets whether transpositions are supported in fuzzy queries.<p>
+     * The default metric used by fuzzy queries to determine a match is the Damerau-Levenshtein
+     * distance formula which supports transpositions. Setting transposition to false will
+     * switch to classic Levenshtein distance.<br>
+     * If not set, Damerau-Levenshtein distance metric will be used.
+     */
+    public QueryStringQueryBuilder fuzzyTranspositions(boolean fuzzyTranspositions) {
+        this.fuzzyTranspositions = fuzzyTranspositions;
+        return this;
+    }
+
     @Override
     protected void doXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject(NAME);
@@ -706,6 +732,7 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         }
         builder.field(ESCAPE_FIELD.getPreferredName(), this.escape);
         builder.field(GENERATE_SYNONYMS_PHRASE_QUERY.getPreferredName(), autoGenerateSynonymsPhraseQuery);
+        builder.field(FUZZY_TRANSPOSITIONS_FIELD.getPreferredName(), fuzzyTranspositions);
         printBoostAndQueryName(builder);
         builder.endObject();
     }
@@ -739,6 +766,8 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         String rewrite = null;
         Map<String, Float> fieldsAndWeights = null;
         boolean autoGenerateSynonymsPhraseQuery = true;
+        boolean fuzzyTranspositions = DEFAULT_FUZZY_TRANSPOSITIONS;
+
         while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
             if (token == XContentParser.Token.FIELD_NAME) {
                 currentFieldName = parser.currentName();
@@ -813,6 +842,8 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
                     queryName = parser.text();
                 } else if (GENERATE_SYNONYMS_PHRASE_QUERY.match(currentFieldName)) {
                     autoGenerateSynonymsPhraseQuery = parser.booleanValue();
+                } else if (FUZZY_TRANSPOSITIONS_FIELD.match(currentFieldName)) {
+                    fuzzyTranspositions = parser.booleanValue();
                 } else if (AUTO_GENERATE_PHRASE_QUERIES_FIELD.match(currentFieldName)) {
                     // ignore, deprecated setting
                 } else if (LOWERCASE_EXPANDED_TERMS_FIELD.match(currentFieldName)) {
@@ -866,6 +897,7 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         queryStringQuery.boost(boost);
         queryStringQuery.queryName(queryName);
         queryStringQuery.autoGenerateSynonymsPhraseQuery(autoGenerateSynonymsPhraseQuery);
+        queryStringQuery.fuzzyTranspositions(fuzzyTranspositions);
         return queryStringQuery;
     }
 
@@ -900,7 +932,8 @@ 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(autoGenerateSynonymsPhraseQuery, other.autoGenerateSynonymsPhraseQuery);
+                Objects.equals(autoGenerateSynonymsPhraseQuery, other.autoGenerateSynonymsPhraseQuery) &&
+                Objects.equals(fuzzyTranspositions, other.fuzzyTranspositions);
     }
 
     @Override
@@ -909,7 +942,8 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
                 quoteFieldSuffix, allowLeadingWildcard, analyzeWildcard,
                 enablePositionIncrements, fuzziness, fuzzyPrefixLength,
                 fuzzyMaxExpansions, fuzzyRewrite, phraseSlop, type, tieBreaker, rewrite, minimumShouldMatch, lenient,
-                timeZone == null ? 0 : timeZone.getID(), escape, maxDeterminizedStates, autoGenerateSynonymsPhraseQuery);
+                timeZone == null ? 0 : timeZone.getID(), escape, maxDeterminizedStates, autoGenerateSynonymsPhraseQuery,
+                fuzzyTranspositions);
     }
 
     @Override
@@ -979,6 +1013,7 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         queryParser.setTimeZone(timeZone);
         queryParser.setMaxDeterminizedStates(maxDeterminizedStates);
         queryParser.setAutoGenerateMultiTermSynonymsPhraseQuery(autoGenerateSynonymsPhraseQuery);
+        queryParser.setFuzzyTranspositions(fuzzyTranspositions);
 
         Query query;
         try {

+ 68 - 0
core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.index.query;
 
 import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.search.FuzzyQuery;
 import org.apache.lucene.search.Query;
 import org.elasticsearch.Version;
 import org.elasticsearch.common.ParseField;
@@ -89,6 +90,12 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
     public static final Operator DEFAULT_OPERATOR = Operator.OR;
     /** Default for search flags to use. */
     public static final int DEFAULT_FLAGS = SimpleQueryStringFlag.ALL.value;
+    /** Default for prefix length in fuzzy queries.*/
+    public static final int DEFAULT_FUZZY_PREFIX_LENGTH = FuzzyQuery.defaultPrefixLength;
+    /** Default number of terms fuzzy queries will expand to.*/
+    public static final int DEFAULT_FUZZY_MAX_EXPANSIONS = FuzzyQuery.defaultMaxExpansions;
+    /** Default for using transpositions in fuzzy queries.*/
+    public static final boolean DEFAULT_FUZZY_TRANSPOSITIONS = FuzzyQuery.defaultTranspositions;
 
     /** Name for (de-)serialization. */
     public static final String NAME = "simple_query_string";
@@ -109,6 +116,9 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
     private static final ParseField ALL_FIELDS_FIELD = new ParseField("all_fields")
             .withAllDeprecated("Set [fields] to `*` instead");
     private static final ParseField GENERATE_SYNONYMS_PHRASE_QUERY = new ParseField("auto_generate_synonyms_phrase_query");
+    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_TRANSPOSITIONS_FIELD = new ParseField("fuzzy_transpositions");
 
     /** Query text to parse. */
     private final String queryText;
@@ -182,6 +192,11 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
         if (in.getVersion().onOrAfter(Version.V_6_1_0)) {
             settings.autoGenerateSynonymsPhraseQuery(in.readBoolean());
         }
+        if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
+            settings.fuzzyPrefixLength(in.readVInt());
+            settings.fuzzyMaxExpansions(in.readVInt());
+            settings.fuzzyTranspositions(in.readBoolean());
+        }
     }
 
     @Override
@@ -220,6 +235,11 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
         if (out.getVersion().onOrAfter(Version.V_6_1_0)) {
             out.writeBoolean(settings.autoGenerateSynonymsPhraseQuery());
         }
+        if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
+            out.writeVInt(settings.fuzzyPrefixLength());
+            out.writeVInt(settings.fuzzyMaxExpansions());
+            out.writeBoolean(settings.fuzzyTranspositions());
+        }
     }
 
     /** Returns the text to parse the query from. */
@@ -395,6 +415,39 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
         return settings.autoGenerateSynonymsPhraseQuery();
     }
 
+    public SimpleQueryStringBuilder fuzzyPrefixLength(int fuzzyPrefixLength) {
+        this.settings.fuzzyPrefixLength(fuzzyPrefixLength);
+        return this;
+    }
+
+    public int fuzzyPrefixLength() {
+        return settings.fuzzyPrefixLength();
+    }
+
+    public SimpleQueryStringBuilder fuzzyMaxExpansions(int fuzzyMaxExpansions) {
+        this.settings.fuzzyMaxExpansions(fuzzyMaxExpansions);
+        return this;
+    }
+
+    public int fuzzyMaxExpansions() {
+        return settings.fuzzyMaxExpansions();
+    }
+
+    public boolean fuzzyTranspositions() {
+        return settings.fuzzyTranspositions();
+    }
+
+    /**
+     * Sets whether transpositions are supported in fuzzy queries.<p>
+     * The default metric used by fuzzy queries to determine a match is the Damerau-Levenshtein
+     * distance formula which supports transpositions. Setting transposition to false will
+     * switch to classic Levenshtein distance.<br>
+     * If not set, Damerau-Levenshtein distance metric will be used.
+     */
+    public SimpleQueryStringBuilder fuzzyTranspositions(boolean fuzzyTranspositions) {
+        this.settings.fuzzyTranspositions(fuzzyTranspositions);
+        return this;
+    }
 
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
@@ -460,6 +513,9 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
             builder.field(MINIMUM_SHOULD_MATCH_FIELD.getPreferredName(), minimumShouldMatch);
         }
         builder.field(GENERATE_SYNONYMS_PHRASE_QUERY.getPreferredName(), settings.autoGenerateSynonymsPhraseQuery());
+        builder.field(FUZZY_PREFIX_LENGTH_FIELD.getPreferredName(), settings.fuzzyPrefixLength());
+        builder.field(FUZZY_MAX_EXPANSIONS_FIELD.getPreferredName(), settings.fuzzyMaxExpansions());
+        builder.field(FUZZY_TRANSPOSITIONS_FIELD.getPreferredName(), settings.fuzzyTranspositions());
         printBoostAndQueryName(builder);
         builder.endObject();
     }
@@ -478,6 +534,9 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
         boolean analyzeWildcard = SimpleQueryStringBuilder.DEFAULT_ANALYZE_WILDCARD;
         String quoteFieldSuffix = null;
         boolean autoGenerateSynonymsPhraseQuery = true;
+        int fuzzyPrefixLenght = SimpleQueryStringBuilder.DEFAULT_FUZZY_PREFIX_LENGTH;
+        int fuzzyMaxExpansions = SimpleQueryStringBuilder.DEFAULT_FUZZY_MAX_EXPANSIONS;
+        boolean fuzzyTranspositions = SimpleQueryStringBuilder.DEFAULT_FUZZY_TRANSPOSITIONS;
 
         XContentParser.Token token;
         while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@@ -532,6 +591,12 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
                     // Ignore deprecated option
                 } else if (GENERATE_SYNONYMS_PHRASE_QUERY.match(currentFieldName)) {
                     autoGenerateSynonymsPhraseQuery = parser.booleanValue();
+                } else if (FUZZY_PREFIX_LENGTH_FIELD.match(currentFieldName)) {
+                    fuzzyPrefixLenght = parser.intValue();
+                } else if (FUZZY_MAX_EXPANSIONS_FIELD.match(currentFieldName)) {
+                    fuzzyMaxExpansions = parser.intValue();
+                } else if (FUZZY_TRANSPOSITIONS_FIELD.match(currentFieldName)) {
+                    fuzzyTranspositions = parser.booleanValue();
                 } else {
                     throw new ParsingException(parser.getTokenLocation(), "[" + SimpleQueryStringBuilder.NAME +
                             "] unsupported field [" + parser.currentName() + "]");
@@ -558,6 +623,9 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
         }
         qb.analyzeWildcard(analyzeWildcard).boost(boost).quoteFieldSuffix(quoteFieldSuffix);
         qb.autoGenerateSynonymsPhraseQuery(autoGenerateSynonymsPhraseQuery);
+        qb.fuzzyPrefixLength(fuzzyPrefixLenght);
+        qb.fuzzyMaxExpansions(fuzzyMaxExpansions);
+        qb.fuzzyTranspositions(fuzzyTranspositions);
         return qb;
     }
 

+ 11 - 2
core/src/main/java/org/elasticsearch/index/search/QueryStringQueryParser.java

@@ -93,6 +93,7 @@ public class QueryStringQueryParser extends XQueryParser {
     private int fuzzyMaxExpansions = FuzzyQuery.defaultMaxExpansions;
     private MappedFieldType currentFieldType;
     private MultiTermQuery.RewriteMethod fuzzyRewriteMethod;
+    private boolean fuzzyTranspositions = FuzzyQuery.defaultTranspositions;
 
     /**
      * @param context The query shard context.
@@ -236,6 +237,14 @@ public class QueryStringQueryParser extends XQueryParser {
         queryBuilder.setAutoGenerateSynonymsPhraseQuery(enable);
     }
 
+    /**
+     * @param fuzzyTranspositions Sets whether transpositions are supported in fuzzy queries.
+     * Defaults to {@link FuzzyQuery#defaultTranspositions}.
+     */
+    public void setFuzzyTranspositions(boolean fuzzyTranspositions) {
+        this.fuzzyTranspositions = fuzzyTranspositions;
+    }
+
     private Query applyBoost(Query q, Float boost) {
         if (boost != null && boost != 1f) {
             return new BoostQuery(q, boost);
@@ -442,7 +451,7 @@ public class QueryStringQueryParser extends XQueryParser {
             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);
+                getFuzzyPrefixLength(), fuzzyMaxExpansions, fuzzyTranspositions);
         } catch (RuntimeException e) {
             if (lenient) {
                 return newLenientFieldQuery(field, e);
@@ -455,7 +464,7 @@ public class QueryStringQueryParser extends XQueryParser {
     protected Query newFuzzyQuery(Term term, float minimumSimilarity, int prefixLength) {
         int numEdits = Fuzziness.build(minimumSimilarity).asDistance(term.text());
         FuzzyQuery query = new FuzzyQuery(term, numEdits, prefixLength,
-            fuzzyMaxExpansions, FuzzyQuery.defaultTranspositions);
+            fuzzyMaxExpansions, fuzzyTranspositions);
         QueryParsers.setRewriteMethod(query, fuzzyRewriteMethod);
         return query;
     }

+ 41 - 5
core/src/main/java/org/elasticsearch/index/search/SimpleQueryStringQueryParser.java

@@ -28,7 +28,6 @@ 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.PrefixQuery;
 import org.apache.lucene.search.Query;
@@ -132,8 +131,8 @@ public class SimpleQueryStringQueryParser extends SimpleQueryParser {
             }
             try {
                 final BytesRef term = getAnalyzer(ft).normalize(fieldName, text);
-                Query query = ft.fuzzyQuery(term, Fuzziness.fromEdits(fuzziness), FuzzyQuery.defaultPrefixLength,
-                    FuzzyQuery.defaultMaxExpansions, FuzzyQuery.defaultTranspositions);
+                Query query = ft.fuzzyQuery(term, Fuzziness.fromEdits(fuzziness), settings.fuzzyPrefixLength,
+                    settings.fuzzyMaxExpansions, settings.fuzzyTranspositions);
                 disjuncts.add(wrapWithBoost(query, entry.getValue()));
             } catch (RuntimeException e) {
                 rethrowUnlessLenient(e);
@@ -293,6 +292,12 @@ public class SimpleQueryStringQueryParser extends SimpleQueryParser {
         private String quoteFieldSuffix = null;
         /** Whether phrase queries should be automatically generated for multi terms synonyms. */
         private boolean autoGenerateSynonymsPhraseQuery = true;
+        /** Prefix length in fuzzy queries.*/
+        private int fuzzyPrefixLength = SimpleQueryStringBuilder.DEFAULT_FUZZY_PREFIX_LENGTH;
+        /** The number of terms fuzzy queries will expand to.*/
+        private int fuzzyMaxExpansions = SimpleQueryStringBuilder.DEFAULT_FUZZY_MAX_EXPANSIONS;
+        /** Whether transpositions are supported in fuzzy queries.*/
+        private boolean fuzzyTranspositions = SimpleQueryStringBuilder.DEFAULT_FUZZY_TRANSPOSITIONS;
 
         /**
          * Generates default {@link Settings} object (uses ROOT locale, does
@@ -306,6 +311,9 @@ public class SimpleQueryStringQueryParser extends SimpleQueryParser {
             this.analyzeWildcard = other.analyzeWildcard;
             this.quoteFieldSuffix = other.quoteFieldSuffix;
             this.autoGenerateSynonymsPhraseQuery = other.autoGenerateSynonymsPhraseQuery;
+            this.fuzzyPrefixLength = other.fuzzyPrefixLength;
+            this.fuzzyMaxExpansions = other.fuzzyMaxExpansions;
+            this.fuzzyTranspositions = other.fuzzyTranspositions;
         }
 
         /** Specifies whether to use lenient parsing, defaults to false. */
@@ -355,9 +363,34 @@ public class SimpleQueryStringQueryParser extends SimpleQueryParser {
             return autoGenerateSynonymsPhraseQuery;
         }
 
+        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 boolean fuzzyTranspositions() {
+            return fuzzyTranspositions;
+        }
+
+        public void fuzzyTranspositions(boolean fuzzyTranspositions) {
+            this.fuzzyTranspositions = fuzzyTranspositions;
+        }
+
         @Override
         public int hashCode() {
-            return Objects.hash(lenient, analyzeWildcard, quoteFieldSuffix, autoGenerateSynonymsPhraseQuery);
+            return Objects.hash(lenient, analyzeWildcard, quoteFieldSuffix, autoGenerateSynonymsPhraseQuery,
+                fuzzyPrefixLength, fuzzyMaxExpansions, fuzzyTranspositions);
         }
 
         @Override
@@ -372,7 +405,10 @@ public class SimpleQueryStringQueryParser extends SimpleQueryParser {
             return Objects.equals(lenient, other.lenient) &&
                 Objects.equals(analyzeWildcard, other.analyzeWildcard) &&
                 Objects.equals(quoteFieldSuffix, other.quoteFieldSuffix) &&
-                Objects.equals(autoGenerateSynonymsPhraseQuery, other.autoGenerateSynonymsPhraseQuery);
+                Objects.equals(autoGenerateSynonymsPhraseQuery, other.autoGenerateSynonymsPhraseQuery) &&
+                Objects.equals(fuzzyPrefixLength, fuzzyPrefixLength) &&
+                Objects.equals(fuzzyMaxExpansions, fuzzyMaxExpansions) &&
+                Objects.equals(fuzzyTranspositions, fuzzyTranspositions);
         }
     }
 }

+ 21 - 0
core/src/test/java/org/elasticsearch/index/query/MultiMatchQueryBuilderTests.java

@@ -34,6 +34,7 @@ import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
 import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
+import org.elasticsearch.common.unit.Fuzziness;
 import org.elasticsearch.index.query.MultiMatchQueryBuilder.Type;
 import org.elasticsearch.index.search.MatchQuery;
 import org.elasticsearch.search.internal.SearchContext;
@@ -123,6 +124,9 @@ public class MultiMatchQueryBuilderTests extends AbstractQueryTestCase<MultiMatc
         if (randomBoolean()) {
             query.autoGenerateSynonymsPhraseQuery(randomBoolean());
         }
+        if (randomBoolean()) {
+            query.fuzzyTranspositions(randomBoolean());
+        }
         // test with fields with boost and patterns delegated to the tests further below
         return query;
     }
@@ -241,6 +245,7 @@ public class MultiMatchQueryBuilderTests extends AbstractQueryTestCase<MultiMatc
                 "    \"lenient\" : false,\n" +
                 "    \"zero_terms_query\" : \"NONE\",\n" +
                 "    \"auto_generate_synonyms_phrase_query\" : true,\n" +
+                "    \"fuzzy_transpositions\" : false,\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
                 "}";
@@ -252,6 +257,7 @@ public class MultiMatchQueryBuilderTests extends AbstractQueryTestCase<MultiMatc
         assertEquals(json, 3, parsed.fields().size());
         assertEquals(json, MultiMatchQueryBuilder.Type.MOST_FIELDS, parsed.type());
         assertEquals(json, Operator.OR, parsed.operator());
+        assertEquals(json, false, parsed.fuzzyTranspositions());
     }
 
     /**
@@ -317,4 +323,19 @@ public class MultiMatchQueryBuilderTests extends AbstractQueryTestCase<MultiMatc
         query.analyzer(null);
         query.toQuery(context); // no exception
     }
+
+    public void testToFuzzyQuery() throws Exception {
+        assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
+
+        MultiMatchQueryBuilder qb = new MultiMatchQueryBuilder("text").field(STRING_FIELD_NAME);
+        qb.fuzziness(Fuzziness.TWO);
+        qb.prefixLength(2);
+        qb.maxExpansions(5);
+        qb.fuzzyTranspositions(false);
+
+        Query query = qb.toQuery(createShardContext());
+        FuzzyQuery expected = new FuzzyQuery(new Term(STRING_FIELD_NAME, "text"), 2, 2, 5, false);
+
+        assertEquals(expected, query);
+    }
 }

+ 18 - 0
core/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java

@@ -162,6 +162,9 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         if (randomBoolean()) {
             queryStringQueryBuilder.autoGenerateSynonymsPhraseQuery(randomBoolean());
         }
+        if (randomBoolean()) {
+            queryStringQueryBuilder.fuzzyTranspositions(randomBoolean());
+        }
         queryStringQueryBuilder.type(randomFrom(MultiMatchQueryBuilder.Type.values()));
         return queryStringQueryBuilder;
     }
@@ -864,6 +867,7 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
                 "    \"phrase_slop\" : 0,\n" +
                 "    \"escape\" : false,\n" +
                 "    \"auto_generate_synonyms_phrase_query\" : true,\n" +
+                "    \"fuzzy_transpositions\" : false,\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
                 "}";
@@ -873,6 +877,7 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
 
         assertEquals(json, "this AND that OR thus", parsed.queryString());
         assertEquals(json, "content", parsed.defaultField());
+        assertEquals(json, false, parsed.fuzzyTranspositions());
     }
 
     public void testExpandedTerms() throws Exception {
@@ -1029,6 +1034,19 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         assertEquals(expectedQuery, query);
     }
 
+    public void testToFuzzyQuery() throws Exception {
+        assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
+
+        Query query = new QueryStringQueryBuilder("text~2")
+            .field(STRING_FIELD_NAME)
+            .fuzzyPrefixLength(2)
+            .fuzzyMaxExpansions(5)
+            .fuzzyTranspositions(false)
+            .toQuery(createShardContext());
+        FuzzyQuery expected = new FuzzyQuery(new Term(STRING_FIELD_NAME, "text"), 2, 2, 5, false);
+        assertEquals(expected, query);
+    }
+
     private static IndexMetaData newIndexMeta(String name, Settings oldIndexSettings, Settings indexSettings) {
         Settings build = Settings.builder().put(oldIndexSettings)
             .put(indexSettings)

+ 40 - 0
core/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java

@@ -105,6 +105,15 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase<SimpleQ
         if (randomBoolean()) {
             result.autoGenerateSynonymsPhraseQuery(randomBoolean());
         }
+        if (randomBoolean()) {
+            result.fuzzyPrefixLength(randomIntBetween(0, 5));
+        }
+        if (randomBoolean()) {
+            result.fuzzyMaxExpansions(randomIntBetween(1, 5));
+        }
+        if (randomBoolean()) {
+            result.fuzzyTranspositions(randomBoolean());
+        }
         return result;
     }
 
@@ -126,6 +135,18 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase<SimpleQ
 
         assertEquals("Wrong default default lenient.", false, qb.lenient());
         assertEquals("Wrong default default lenient field.", false, SimpleQueryStringBuilder.DEFAULT_LENIENT);
+
+        assertEquals("Wrong default default fuzzy prefix length.", FuzzyQuery.defaultPrefixLength, qb.fuzzyPrefixLength());
+        assertEquals("Wrong default default fuzzy prefix length field.",
+            FuzzyQuery.defaultPrefixLength, SimpleQueryStringBuilder.DEFAULT_FUZZY_PREFIX_LENGTH);
+
+        assertEquals("Wrong default default fuzzy max expansions.", FuzzyQuery.defaultMaxExpansions, qb.fuzzyMaxExpansions());
+        assertEquals("Wrong default default fuzzy max expansions field.",
+            FuzzyQuery.defaultMaxExpansions, SimpleQueryStringBuilder.DEFAULT_FUZZY_MAX_EXPANSIONS);
+
+        assertEquals("Wrong default default fuzzy transpositions.", FuzzyQuery.defaultTranspositions, qb.fuzzyTranspositions());
+        assertEquals("Wrong default default fuzzy transpositions field.",
+            FuzzyQuery.defaultTranspositions, SimpleQueryStringBuilder.DEFAULT_FUZZY_TRANSPOSITIONS);
     }
 
     public void testDefaultNullComplainFlags() {
@@ -336,6 +357,9 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase<SimpleQ
                 "    \"analyze_wildcard\" : false,\n" +
                 "    \"quote_field_suffix\" : \".quote\",\n" +
                 "    \"auto_generate_synonyms_phrase_query\" : true,\n" +
+                "    \"fuzzy_prefix_length\" : 1,\n" +
+                "    \"fuzzy_max_expansions\" : 5,\n" +
+                "    \"fuzzy_transpositions\" : false,\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
                 "}";
@@ -347,6 +371,9 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase<SimpleQ
         assertEquals(json, 1, parsed.fields().size());
         assertEquals(json, "snowball", parsed.analyzer());
         assertEquals(json, ".quote", parsed.quoteFieldSuffix());
+        assertEquals(json, 1, parsed.fuzzyPrefixLength());
+        assertEquals(json, 5, parsed.fuzzyMaxExpansions());
+        assertEquals(json, false, parsed.fuzzyTranspositions());
     }
 
     public void testMinimumShouldMatch() throws IOException {
@@ -567,6 +594,19 @@ public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase<SimpleQ
         );
     }
 
+    public void testToFuzzyQuery() throws Exception {
+        assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
+
+        Query query = new SimpleQueryStringBuilder("text~2")
+            .field(STRING_FIELD_NAME)
+            .fuzzyPrefixLength(2)
+            .fuzzyMaxExpansions(5)
+            .fuzzyTranspositions(false)
+            .toQuery(createShardContext());
+        FuzzyQuery expected = new FuzzyQuery(new Term(STRING_FIELD_NAME, "text"), 2, 2, 5, false);
+        assertEquals(expected, query);
+    }
+
     private static IndexMetaData newIndexMeta(String name, Settings oldIndexSettings, Settings indexSettings) {
         Settings build = Settings.builder().put(oldIndexSettings)
             .put(indexSettings)

+ 4 - 0
docs/reference/query-dsl/fuzzy-query.asciidoc

@@ -63,6 +63,10 @@ GET /_search
     The maximum number of terms that the `fuzzy` query will expand to.
     Defaults to `50`.
 
+`transpositions`::
+
+    Whether fuzzy transpositions (`ab` -> `ba`) are supported.
+    Default is `false`.
 
 WARNING: This query can be very heavy if `prefix_length` is set to `0` and if
 `max_expansions` is set to a high number. It could result in every term in the

+ 2 - 1
docs/reference/query-dsl/multi-match-query.asciidoc

@@ -137,7 +137,8 @@ follows:
 
 Also, accepts `analyzer`, `boost`, `operator`, `minimum_should_match`,
 `fuzziness`, `lenient`, `prefix_length`, `max_expansions`, `rewrite`, `zero_terms_query`,
- `cutoff_frequency` and `auto_generate_synonyms_phrase_query`, as explained in <<query-dsl-match-query, match query>>.
+ `cutoff_frequency`, `auto_generate_synonyms_phrase_query` and `fuzzy_transpositions`,
+  as explained in <<query-dsl-match-query, match query>>.
 
 [IMPORTANT]
 [[operator-min]]

+ 3 - 0
docs/reference/query-dsl/query-string-query.asciidoc

@@ -83,6 +83,9 @@ to `AUTO`. See <<fuzziness>> for allowed settings.
 |`fuzzy_prefix_length` |Set the prefix length for fuzzy queries. Default
 is `0`.
 
+|`fuzzy_transpositions` |Set to `false` to disable fuzzy transpositions (`ab` -> `ba`).
+Default is `true`.
+
 |`phrase_slop` |Sets the default slop for phrases. If zero, then exact
 phrase matches are required. Default value is `0`.
 

+ 9 - 0
docs/reference/query-dsl/simple-query-string-query.asciidoc

@@ -70,6 +70,15 @@ Defaults to `true`.
 |`all_fields` |  deprecated[6.0.0, set `fields` to `*` instead]
 Perform the query on all fields detected in the mapping that can
 be queried.
+
+|`fuzzy_prefix_length` |Set the prefix length for fuzzy queries. Default
+is `0`.
+
+|`fuzzy_max_expansions` |Controls the number of terms fuzzy queries will
+expand to. Defaults to `50`
+
+|`fuzzy_transpositions` |Set to `false` to disable fuzzy transpositions (`ab` -> `ba`).
+Default is `true`.
 |=======================================================================
 
 [float]