/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.index.query; import org.apache.lucene.index.Term; import org.apache.lucene.search.BoostQuery; import org.apache.lucene.search.FuzzyQuery; import org.apache.lucene.search.Query; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.test.AbstractQueryTestCase; import java.io.IOException; import java.util.HashMap; import java.util.Map; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; public class FuzzyQueryBuilderTests extends AbstractQueryTestCase<FuzzyQueryBuilder> { @Override protected FuzzyQueryBuilder doCreateTestQueryBuilder() { FuzzyQueryBuilder query = new FuzzyQueryBuilder(STRING_FIELD_NAME, getRandomValueForFieldName(STRING_FIELD_NAME)); if (randomBoolean()) { query.fuzziness(randomFuzziness(query.fieldName())); } if (randomBoolean()) { query.prefixLength(randomIntBetween(0, 10)); } if (randomBoolean()) { query.maxExpansions(randomIntBetween(1, 10)); } if (randomBoolean()) { query.transpositions(randomBoolean()); } if (randomBoolean()) { query.rewrite(getRandomRewriteMethod()); } return query; } @Override protected Map<String, FuzzyQueryBuilder> getAlternateVersions() { Map<String, FuzzyQueryBuilder> alternateVersions = new HashMap<>(); FuzzyQueryBuilder fuzzyQuery = new FuzzyQueryBuilder(randomAlphaOfLengthBetween(1, 10), randomAlphaOfLengthBetween(1, 10)); String contentString = "{\n" + " \"fuzzy\" : {\n" + " \"" + fuzzyQuery.fieldName() + "\" : \"" + fuzzyQuery.value() + "\"\n" + " }\n" + "}"; alternateVersions.put(contentString, fuzzyQuery); return alternateVersions; } @Override protected void doAssertLuceneQuery(FuzzyQueryBuilder queryBuilder, Query query, SearchContext context) throws IOException { assertThat(query, instanceOf(FuzzyQuery.class)); } public void testIllegalArguments() { IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new FuzzyQueryBuilder(null, "text")); assertEquals("field name cannot be null or empty", e.getMessage()); e = expectThrows(IllegalArgumentException.class, () -> new FuzzyQueryBuilder("", "text")); assertEquals("field name cannot be null or empty", e.getMessage()); e = expectThrows(IllegalArgumentException.class, () -> new FuzzyQueryBuilder("field", null)); assertEquals("query value cannot be null", e.getMessage()); } public void testUnsupportedFuzzinessForStringType() throws IOException { QueryShardContext context = createShardContext(); context.setAllowUnmappedFields(true); FuzzyQueryBuilder fuzzyQueryBuilder = new FuzzyQueryBuilder(STRING_FIELD_NAME, "text"); fuzzyQueryBuilder.fuzziness(Fuzziness.build(randomFrom("a string which is not auto", "3h", "200s"))); NumberFormatException e = expectThrows(NumberFormatException.class, () -> fuzzyQueryBuilder.toQuery(context)); assertThat(e.getMessage(), containsString("For input string")); } public void testToQueryWithStringField() throws IOException { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); String query = "{\n" + " \"fuzzy\":{\n" + " \"" + STRING_FIELD_NAME + "\":{\n" + " \"value\":\"sh\",\n" + " \"fuzziness\": \"AUTO\",\n" + " \"prefix_length\":1,\n" + " \"boost\":2.0\n" + " }\n" + " }\n" + "}"; Query parsedQuery = parseQuery(query).toQuery(createShardContext()); assertThat(parsedQuery, instanceOf(BoostQuery.class)); BoostQuery boostQuery = (BoostQuery) parsedQuery; assertThat(boostQuery.getBoost(), equalTo(2.0f)); assertThat(boostQuery.getQuery(), instanceOf(FuzzyQuery.class)); FuzzyQuery fuzzyQuery = (FuzzyQuery) boostQuery.getQuery(); assertThat(fuzzyQuery.getTerm(), equalTo(new Term(STRING_FIELD_NAME, "sh"))); assertThat(fuzzyQuery.getMaxEdits(), equalTo(Fuzziness.AUTO.asDistance("sh"))); assertThat(fuzzyQuery.getPrefixLength(), equalTo(1)); } public void testToQueryWithNumericField() throws IOException { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); String query = "{\n" + " \"fuzzy\":{\n" + " \"" + INT_FIELD_NAME + "\":{\n" + " \"value\":12,\n" + " \"fuzziness\":5\n" + " }\n" + " }\n" + "}\n"; IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> parseQuery(query).toQuery(createShardContext())); assertEquals("Can only use fuzzy queries on keyword and text fields - not on [mapped_int] which is of type [integer]", e.getMessage()); } public void testFromJson() throws IOException { String json = "{\n" + " \"fuzzy\" : {\n" + " \"user\" : {\n" + " \"value\" : \"ki\",\n" + " \"fuzziness\" : \"2\",\n" + " \"prefix_length\" : 0,\n" + " \"max_expansions\" : 100,\n" + " \"transpositions\" : false,\n" + " \"boost\" : 42.0\n" + " }\n" + " }\n" + "}"; FuzzyQueryBuilder parsed = (FuzzyQueryBuilder) parseQuery(json); checkGeneratedJson(json, parsed); assertEquals(json, 42.0, parsed.boost(), 0.00001); assertEquals(json, 2, parsed.fuzziness().asFloat(), 0f); } public void testParseFailsWithMultipleFields() throws IOException { String json1 = "{\n" + " \"fuzzy\" : {\n" + " \"message1\" : {\n" + " \"value\" : \"this is a test\"\n" + " }\n" + " }\n" + "}"; parseQuery(json1); // should be all good String json2 = "{\n" + " \"fuzzy\" : {\n" + " \"message1\" : {\n" + " \"value\" : \"this is a test\"\n" + " },\n" + " \"message2\" : {\n" + " \"value\" : \"this is a test\"\n" + " }\n" + " }\n" + "}"; ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(json2)); assertEquals("[fuzzy] query doesn't support multiple fields, found [message1] and [message2]", e.getMessage()); String shortJson = "{\n" + " \"fuzzy\" : {\n" + " \"message1\" : \"this is a test\",\n" + " \"message2\" : \"value\" : \"this is a test\"\n" + " }\n" + "}"; e = expectThrows(ParsingException.class, () -> parseQuery(shortJson)); assertEquals("[fuzzy] query doesn't support multiple fields, found [message1] and [message2]", e.getMessage()); } public void testParseFailsWithValueArray() { String query = "{\n" + " \"fuzzy\" : {\n" + " \"message1\" : {\n" + " \"value\" : [ \"one\", \"two\", \"three\"]\n" + " }\n" + " }\n" + "}"; ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(query)); assertEquals("[fuzzy] unexpected token [START_ARRAY] after [value]", e.getMessage()); } }