diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FuzzyLikeThisQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FuzzyLikeThisQueryParser.java index 3cf08b4a598..4ffeaa57a15 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FuzzyLikeThisQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FuzzyLikeThisQueryParser.java @@ -84,6 +84,10 @@ public class FuzzyLikeThisQueryParser extends AbstractIndexComponent implements boost = parser.floatValue(); } else if ("ignore_tf".equals(currentFieldName) || "ignoreTF".equals(currentFieldName)) { ignoreTF = parser.booleanValue(); + } else if ("min_similarity".equals(currentFieldName) || "minSimilarity".equals(currentFieldName)) { + minSimilarity = parser.floatValue(); + } else if ("prefix_length".equals(currentFieldName) || "prefixLength".equals(currentFieldName)) { + prefixLength = parser.intValue(); } } else if (token == XContentParser.Token.START_ARRAY) { if ("fields".equals(currentFieldName)) { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FuzzyQueryBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FuzzyQueryBuilder.java new file mode 100644 index 00000000000..05178228f5b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FuzzyQueryBuilder.java @@ -0,0 +1,93 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search licenses this + * file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.index.query.xcontent; + +import org.elasticsearch.util.xcontent.builder.XContentBuilder; + +import java.io.IOException; + +/** + * A Query that does fuzzy matching for a specific value. + * + * @author kimchy (shay.banon) + */ +public class FuzzyQueryBuilder extends BaseQueryBuilder { + + private final String name; + + private final String value; + + private float boost = -1; + + private Float minSimilarity; + + private Integer prefixLength; + + /** + * Constructs a new term query. + * + * @param name The name of the field + * @param value The value of the term + */ + public FuzzyQueryBuilder(String name, String value) { + this.name = name; + this.value = value; + } + + /** + * Sets the boost for this query. Documents matching this query will (in addition to the normal + * weightings) have their score multiplied by the boost provided. + */ + public FuzzyQueryBuilder boost(float boost) { + this.boost = boost; + return this; + } + + public FuzzyQueryBuilder minSimilarity(float defaultMinSimilarity) { + this.minSimilarity = defaultMinSimilarity; + return this; + } + + public FuzzyQueryBuilder prefixLength(int prefixLength) { + this.prefixLength = prefixLength; + return this; + } + + @Override public void doXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(FuzzyQueryParser.NAME); + if (boost == -1 && minSimilarity == null && prefixLength == null) { + builder.field(name, value); + } else { + builder.startObject(name); + builder.field("value", value); + if (boost != -1) { + builder.field("boost", boost); + } + if (minSimilarity != null) { + builder.field("min_similarity", minSimilarity); + } + if (prefixLength != null) { + builder.field("prefix_length", prefixLength); + } + builder.endObject(); + } + builder.endObject(); + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FuzzyQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FuzzyQueryParser.java new file mode 100644 index 00000000000..97304dad427 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/FuzzyQueryParser.java @@ -0,0 +1,101 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search licenses this + * file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.index.query.xcontent; + +import org.apache.lucene.index.Term; +import org.apache.lucene.search.FuzzyQuery; +import org.apache.lucene.search.Query; +import org.elasticsearch.index.AbstractIndexComponent; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.query.QueryParsingException; +import org.elasticsearch.index.settings.IndexSettings; +import org.elasticsearch.util.settings.Settings; +import org.elasticsearch.util.xcontent.XContentParser; + +import java.io.IOException; + +import static org.elasticsearch.index.query.support.QueryParsers.*; + +/** + * @author kimchy (shay.banon) + */ +public class FuzzyQueryParser extends AbstractIndexComponent implements XContentQueryParser { + + public static final String NAME = "fuzzy"; + + public FuzzyQueryParser(Index index, @IndexSettings Settings indexSettings) { + super(index, indexSettings); + } + + @Override public String[] names() { + return new String[]{NAME}; + } + + @Override public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException { + XContentParser parser = parseContext.parser(); + + XContentParser.Token token = parser.nextToken(); + assert token == XContentParser.Token.FIELD_NAME; + String fieldName = parser.currentName(); + + String value = null; + float boost = 1.0f; + float minSimilarity = FuzzyQuery.defaultMinSimilarity; + int prefixLength = FuzzyQuery.defaultPrefixLength; + token = parser.nextToken(); + if (token == XContentParser.Token.START_OBJECT) { + String currentFieldName = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else { + if ("term".equals(currentFieldName)) { + value = parser.text(); + } else if ("value".equals(currentFieldName)) { + value = parser.text(); + } else if ("boost".equals(currentFieldName)) { + boost = parser.floatValue(); + } else if ("min_similarity".equals(currentFieldName) || "minSimilarity".equals(currentFieldName)) { + minSimilarity = parser.floatValue(); + } else if ("prefix_length".equals(currentFieldName) || "prefixLength".equals(currentFieldName)) { + prefixLength = parser.intValue(); + } + } + } + parser.nextToken(); + } else { + value = parser.text(); + // move to the next token + parser.nextToken(); + } + + if (value == null) { + throw new QueryParsingException(index, "No value specified for fuzzy query"); + } + + MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); + + FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term(fieldName, value), minSimilarity, prefixLength); + fuzzyQuery.setBoost(boost); + + return wrapSmartNameQuery(fuzzyQuery, smartNameFieldMappers, parseContext); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/QueryBuilders.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/QueryBuilders.java index f4f43d02458..8f6efbfa6a3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/QueryBuilders.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/QueryBuilders.java @@ -82,6 +82,16 @@ public abstract class QueryBuilders { return new TermQueryBuilder(name, value); } + /** + * A Query that matches documents using fuzzy query. + * + * @param name The name of the field + * @param value The value of the term + */ + public static FuzzyQueryBuilder fuzzyQuery(String name, String value) { + return new FuzzyQueryBuilder(name, value); + } + /** * A Query that matches documents containing a term. * diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/XContentQueryParserRegistry.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/XContentQueryParserRegistry.java index 8bb1b8e94b2..0f9e093735b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/XContentQueryParserRegistry.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/xcontent/XContentQueryParserRegistry.java @@ -52,6 +52,7 @@ public class XContentQueryParserRegistry { add(queryParsersMap, new QueryStringQueryParser(index, indexSettings, analysisService)); add(queryParsersMap, new BoolQueryParser(index, indexSettings)); add(queryParsersMap, new TermQueryParser(index, indexSettings)); + add(queryParsersMap, new FuzzyQueryParser(index, indexSettings)); add(queryParsersMap, new FieldQueryParser(index, indexSettings, analysisService)); add(queryParsersMap, new RangeQueryParser(index, indexSettings)); add(queryParsersMap, new PrefixQueryParser(index, indexSettings)); diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/SimpleIndexQueryParserTests.java b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/SimpleIndexQueryParserTests.java index 849e6a8c61c..ba29717f451 100644 --- a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/SimpleIndexQueryParserTests.java +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/SimpleIndexQueryParserTests.java @@ -220,6 +220,46 @@ public class SimpleIndexQueryParserTests { assertThat(fieldQuery.includesMin(), equalTo(true)); } + @Test public void testFuzzyQueryBuilder() throws IOException { + IndexQueryParser queryParser = newQueryParser(); + Query parsedQuery = queryParser.parse(fuzzyQuery("name.first", "sh").buildAsBytes()); + assertThat(parsedQuery, instanceOf(FuzzyQuery.class)); + FuzzyQuery fuzzyQuery = (FuzzyQuery) parsedQuery; + assertThat(fuzzyQuery.getTerm(), equalTo(new Term("name.first", "sh"))); + } + + @Test public void testFuzzyQuery() throws IOException { + IndexQueryParser queryParser = newQueryParser(); + String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/fuzzy.json"); + Query parsedQuery = queryParser.parse(query); + assertThat(parsedQuery, instanceOf(FuzzyQuery.class)); + FuzzyQuery fuzzyQuery = (FuzzyQuery) parsedQuery; + assertThat(fuzzyQuery.getTerm(), equalTo(new Term("name.first", "sh"))); + } + + @Test public void testFuzzyQueryWithFieldsBuilder() throws IOException { + IndexQueryParser queryParser = newQueryParser(); + Query parsedQuery = queryParser.parse(fuzzyQuery("name.first", "sh").minSimilarity(0.1f).prefixLength(1).boost(2.0f).buildAsBytes()); + assertThat(parsedQuery, instanceOf(FuzzyQuery.class)); + FuzzyQuery fuzzyQuery = (FuzzyQuery) parsedQuery; + assertThat(fuzzyQuery.getTerm(), equalTo(new Term("name.first", "sh"))); + assertThat(fuzzyQuery.getMinSimilarity(), equalTo(0.1f)); + assertThat(fuzzyQuery.getPrefixLength(), equalTo(1)); + assertThat(fuzzyQuery.getBoost(), equalTo(2.0f)); + } + + @Test public void testFuzzyQueryWithFields() throws IOException { + IndexQueryParser queryParser = newQueryParser(); + String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/fuzzy-with-fields.json"); + Query parsedQuery = queryParser.parse(query); + assertThat(parsedQuery, instanceOf(FuzzyQuery.class)); + FuzzyQuery fuzzyQuery = (FuzzyQuery) parsedQuery; + assertThat(fuzzyQuery.getTerm(), equalTo(new Term("name.first", "sh"))); + assertThat(fuzzyQuery.getMinSimilarity(), equalTo(0.1f)); + assertThat(fuzzyQuery.getPrefixLength(), equalTo(1)); + assertThat(fuzzyQuery.getBoost(), equalTo(2.0f)); + } + @Test public void testFieldQueryBuilder1() throws IOException { IndexQueryParser queryParser = newQueryParser(); Query parsedQuery = queryParser.parse(fieldQuery("age", 34).buildAsBytes()); diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/fuzzy-with-fields.json b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/fuzzy-with-fields.json new file mode 100644 index 00000000000..e6523a60519 --- /dev/null +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/fuzzy-with-fields.json @@ -0,0 +1,10 @@ +{ + "fuzzy" : { + "name.first" : { + "value" : "sh", + "min_similarity" : 0.1, + "prefix_length" : 1, + "boost" : 2.0 + } + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/fuzzy.json b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/fuzzy.json new file mode 100644 index 00000000000..fe623b8dc31 --- /dev/null +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/fuzzy.json @@ -0,0 +1,3 @@ +{ + "fuzzy" : { "name.first" : "sh" } +} \ No newline at end of file