/* * 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.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; import org.apache.lucene.search.BoostQuery; import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.DisjunctionMaxQuery; import org.apache.lucene.search.FuzzyQuery; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.PhraseQuery; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.RegexpQuery; import org.apache.lucene.search.SynonymQuery; import org.apache.lucene.search.TermQuery; 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.SpanTermQuery; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.automaton.TooComplexToDeterminizeException; 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.unit.Fuzziness; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.test.AbstractQueryTestCase; import org.hamcrest.Matchers; 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.QueryBuilders.queryStringQuery; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBooleanSubQuery; import static org.hamcrest.CoreMatchers.either; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStringQueryBuilder> { @Override protected QueryStringQueryBuilder doCreateTestQueryBuilder() { int numTerms = randomIntBetween(0, 5); String query = ""; for (int i = 0; i < numTerms; i++) { //min length 4 makes sure that the text is not an operator (AND/OR) so toQuery won't break query += (randomBoolean() ? STRING_FIELD_NAME + ":" : "") + randomAlphaOfLengthBetween(4, 10) + " "; } QueryStringQueryBuilder queryStringQueryBuilder = new QueryStringQueryBuilder(query); if (randomBoolean()) { queryStringQueryBuilder.defaultField(randomBoolean() ? STRING_FIELD_NAME : randomAlphaOfLengthBetween(1, 10)); } else { int numFields = randomIntBetween(1, 5); for (int i = 0; i < numFields; i++) { String fieldName = randomBoolean() ? STRING_FIELD_NAME : randomAlphaOfLengthBetween(1, 10); if (randomBoolean()) { queryStringQueryBuilder.field(fieldName); } else { queryStringQueryBuilder.field(fieldName, randomFloat()); } } } if (randomBoolean()) { queryStringQueryBuilder.defaultOperator(randomFrom(Operator.values())); } if (randomBoolean()) { //we only use string fields (either mapped or unmapped) queryStringQueryBuilder.fuzziness(randomFuzziness(STRING_FIELD_NAME)); } if (randomBoolean()) { queryStringQueryBuilder.analyzer(randomAnalyzer()); } if (randomBoolean()) { queryStringQueryBuilder.quoteAnalyzer(randomAnalyzer()); } if (randomBoolean()) { queryStringQueryBuilder.allowLeadingWildcard(randomBoolean()); } if (randomBoolean()) { queryStringQueryBuilder.analyzeWildcard(randomBoolean()); } if (randomBoolean()) { queryStringQueryBuilder.maxDeterminizedStates(randomIntBetween(1, 100)); } if (randomBoolean()) { queryStringQueryBuilder.autoGeneratePhraseQueries(randomBoolean()); } if (randomBoolean()) { queryStringQueryBuilder.enablePositionIncrements(randomBoolean()); } if (randomBoolean()) { queryStringQueryBuilder.escape(randomBoolean()); } if (randomBoolean()) { queryStringQueryBuilder.phraseSlop(randomIntBetween(0, 10)); } if (randomBoolean()) { queryStringQueryBuilder.fuzzyMaxExpansions(randomIntBetween(0, 100)); } if (randomBoolean()) { queryStringQueryBuilder.fuzzyPrefixLength(randomIntBetween(0, 10)); } if (randomBoolean()) { queryStringQueryBuilder.fuzzyRewrite(getRandomRewriteMethod()); } if (randomBoolean()) { queryStringQueryBuilder.rewrite(getRandomRewriteMethod()); } if (randomBoolean()) { queryStringQueryBuilder.quoteFieldSuffix(randomAlphaOfLengthBetween(1, 3)); } if (randomBoolean()) { queryStringQueryBuilder.tieBreaker(randomFloat()); } if (randomBoolean()) { queryStringQueryBuilder.minimumShouldMatch(randomMinimumShouldMatch()); } if (randomBoolean()) { queryStringQueryBuilder.useDisMax(randomBoolean()); } if (randomBoolean()) { queryStringQueryBuilder.timeZone(randomDateTimeZone().getID()); } if (queryStringQueryBuilder.autoGeneratePhraseQueries() == false) { // setSplitOnWhitespace(false) is disallowed when getAutoGeneratePhraseQueries() == true queryStringQueryBuilder.splitOnWhitespace(randomBoolean()); } return queryStringQueryBuilder; } @Override protected void doAssertLuceneQuery(QueryStringQueryBuilder queryBuilder, Query query, SearchContext context) throws IOException { if ("".equals(queryBuilder.queryString())) { assertThat(query, instanceOf(MatchNoDocsQuery.class)); } else { assertThat(query, either(instanceOf(TermQuery.class)).or(instanceOf(AllTermQuery.class)) .or(instanceOf(BooleanQuery.class)).or(instanceOf(DisjunctionMaxQuery.class)) .or(instanceOf(PhraseQuery.class))); } } public void testIllegalArguments() { expectThrows(IllegalArgumentException.class, () -> new QueryStringQueryBuilder((String) null)); } public void testToQueryMatchAllQuery() throws Exception { Query query = queryStringQuery("*:*").toQuery(createShardContext()); assertThat(query, instanceOf(MatchAllDocsQuery.class)); } public void testToQueryTermQuery() throws IOException { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); Query query = queryStringQuery("test").defaultField(STRING_FIELD_NAME).toQuery(createShardContext()); assertThat(query, instanceOf(TermQuery.class)); TermQuery termQuery = (TermQuery) query; assertThat(termQuery.getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test"))); } public void testToQueryPhraseQuery() throws IOException { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); Query query = queryStringQuery("\"term1 term2\"") .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(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"))); assertThat(phraseQuery.getSlop(), equalTo(3)); } public void testToQueryBoosts() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); QueryShardContext shardContext = createShardContext(); QueryStringQueryBuilder queryStringQuery = queryStringQuery(STRING_FIELD_NAME + ":boosted^2"); Query query = queryStringQuery.toQuery(shardContext); assertThat(query, instanceOf(BoostQuery.class)); BoostQuery boostQuery = (BoostQuery) query; assertThat(boostQuery.getBoost(), equalTo(2.0f)); assertThat(boostQuery.getQuery(), instanceOf(TermQuery.class)); assertThat(((TermQuery) boostQuery.getQuery()).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "boosted"))); queryStringQuery.boost(2.0f); query = queryStringQuery.toQuery(shardContext); assertThat(query, instanceOf(BoostQuery.class)); boostQuery = (BoostQuery) query; assertThat(boostQuery.getBoost(), equalTo(2.0f)); assertThat(boostQuery .getQuery(), instanceOf(BoostQuery.class)); boostQuery = (BoostQuery) boostQuery.getQuery(); assertThat(boostQuery.getBoost(), equalTo(2.0f)); queryStringQuery = queryStringQuery("((" + STRING_FIELD_NAME + ":boosted^2) AND (" + STRING_FIELD_NAME + ":foo^1.5))^3"); query = queryStringQuery.toQuery(shardContext); assertThat(query, instanceOf(BoostQuery.class)); boostQuery = (BoostQuery) query; assertThat(boostQuery.getBoost(), equalTo(3.0f)); BoostQuery boostQuery1 = assertBooleanSubQuery(boostQuery.getQuery(), BoostQuery.class, 0); assertThat(boostQuery1.getBoost(), equalTo(2.0f)); assertThat(boostQuery1.getQuery(), instanceOf(TermQuery.class)); assertThat(((TermQuery)boostQuery1.getQuery()).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "boosted"))); BoostQuery boostQuery2 = assertBooleanSubQuery(boostQuery.getQuery(), BoostQuery.class, 1); assertThat(boostQuery2.getBoost(), equalTo(1.5f)); assertThat(boostQuery2.getQuery(), instanceOf(TermQuery.class)); assertThat(((TermQuery)boostQuery2.getQuery()).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "foo"))); queryStringQuery.boost(2.0f); query = queryStringQuery.toQuery(shardContext); assertThat(query, instanceOf(BoostQuery.class)); boostQuery = (BoostQuery) query; assertThat(boostQuery.getBoost(), equalTo(2.0f)); } 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; assertThat(bQuery.clauses().size(), equalTo(2)); assertThat(assertBooleanSubQuery(query, TermQuery.class, 0).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test1"))); assertThat(assertBooleanSubQuery(query, TermQuery.class, 1).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test2"))); } public void testToQueryMultipleFieldsBooleanQuery() 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(false) .toQuery(createShardContext()); assertThat(query, instanceOf(BooleanQuery.class)); BooleanQuery bQuery = (BooleanQuery) query; assertThat(bQuery.clauses().size(), equalTo(2)); assertThat(assertBooleanSubQuery(query, TermQuery.class, 0).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test"))); assertThat(assertBooleanSubQuery(query, TermQuery.class, 1).getTerm(), equalTo(new Term(STRING_FIELD_NAME_2, "test"))); } 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; List<Query> disjuncts = disMaxQuery.getDisjuncts(); assertThat(((TermQuery) disjuncts.get(0)).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test"))); assertThat(((TermQuery) disjuncts.get(1)).getTerm(), equalTo(new Term(STRING_FIELD_NAME_2, "test"))); } 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()); assertThat(query, instanceOf(BooleanQuery.class)); BooleanQuery bQuery = (BooleanQuery) query; assertThat(bQuery.clauses().size(), equalTo(2)); assertThat(assertBooleanSubQuery(query, TermQuery.class, 0).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test"))); assertThat(assertBooleanSubQuery(query, TermQuery.class, 1).getTerm(), equalTo(new Term(STRING_FIELD_NAME_2, "test"))); } public void testToQueryDisMaxQuery() throws Exception { 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; List<Query> disjuncts = disMaxQuery.getDisjuncts(); assertTermOrBoostQuery(disjuncts.get(0), STRING_FIELD_NAME, "test", 2.2f); assertTermOrBoostQuery(disjuncts.get(1), STRING_FIELD_NAME_2, "test", 1.0f); } public void testToQueryWildcarQuery() throws Exception { 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); Query query = queryParser.parse("first foo-bar-foobar* last"); Query expectedQuery = new BooleanQuery.Builder() .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "first")), defaultOp)) .add(new BooleanQuery.Builder() .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "foo")), defaultOp)) .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "bar")), defaultOp)) .add(new BooleanClause(new PrefixQuery(new Term(STRING_FIELD_NAME, "foobar")), defaultOp)) .build(), defaultOp) .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "last")), defaultOp)) .build(); assertThat(query, Matchers.equalTo(expectedQuery)); } } public void testToQueryWilcardQueryWithSynonyms() throws Exception { 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); Query query = queryParser.parse("first foo-bar-foobar* last"); Query expectedQuery = new BooleanQuery.Builder() .add(new BooleanClause(new SynonymQuery(new Term(STRING_FIELD_NAME, "first"), new Term(STRING_FIELD_NAME, "first")), defaultOp)) .add(new BooleanQuery.Builder() .add(new BooleanClause(new SynonymQuery(new Term(STRING_FIELD_NAME, "foo"), new Term(STRING_FIELD_NAME, "foo")), defaultOp)) .add(new BooleanClause(new SynonymQuery(new Term(STRING_FIELD_NAME, "bar"), new Term(STRING_FIELD_NAME, "bar")), defaultOp)) .add(new BooleanQuery.Builder() .add(new BooleanClause(new PrefixQuery(new Term(STRING_FIELD_NAME, "foobar")), BooleanClause.Occur.SHOULD)) .add(new BooleanClause(new PrefixQuery(new Term(STRING_FIELD_NAME, "foobar")), BooleanClause.Occur.SHOULD)) .build(), defaultOp) .build(), defaultOp) .add(new BooleanClause(new SynonymQuery(new Term(STRING_FIELD_NAME, "last"), new Term(STRING_FIELD_NAME, "last")), defaultOp)) .build(); assertThat(query, Matchers.equalTo(expectedQuery)); } } public void testToQueryWithGraph() throws Exception { 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); // simple multi-term Query query = queryParser.parse("guinea pig"); Query expectedQuery = new BooleanQuery.Builder() .add(new BooleanQuery.Builder() .add(new TermQuery(new Term(STRING_FIELD_NAME, "guinea")), Occur.MUST) .add(new TermQuery(new Term(STRING_FIELD_NAME, "pig")), Occur.MUST).build(), defaultOp) .add(new TermQuery(new Term(STRING_FIELD_NAME, "cavy")), defaultOp) .build(); assertThat(query, Matchers.equalTo(expectedQuery)); // simple with additional tokens query = queryParser.parse("that guinea pig smells"); expectedQuery = new BooleanQuery.Builder() .add(new TermQuery(new Term(STRING_FIELD_NAME, "that")), defaultOp) .add(new BooleanQuery.Builder() .add(new BooleanQuery.Builder() .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "guinea")), Occur.MUST)) .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "pig")), Occur.MUST)) .build(), Occur.SHOULD) .add(new TermQuery(new Term(STRING_FIELD_NAME, "cavy")), Occur.SHOULD).build(), defaultOp) .add(new TermQuery(new Term(STRING_FIELD_NAME, "smells")), defaultOp) .build(); assertThat(query, Matchers.equalTo(expectedQuery)); // complex query = queryParser.parse("+that -(guinea pig) +smells"); expectedQuery = new BooleanQuery.Builder() .add(new TermQuery(new Term(STRING_FIELD_NAME, "that")), Occur.MUST) .add(new BooleanQuery.Builder() .add(new BooleanQuery.Builder() .add(new TermQuery(new Term(STRING_FIELD_NAME, "guinea")), Occur.MUST) .add(new TermQuery(new Term(STRING_FIELD_NAME, "pig")), Occur.MUST) .build(), defaultOp) .add(new TermQuery(new Term(STRING_FIELD_NAME, "cavy")), defaultOp) .build(), Occur.MUST_NOT) .add(new TermQuery(new Term(STRING_FIELD_NAME, "smells")), Occur.MUST) .build(); assertThat(query, Matchers.equalTo(expectedQuery)); // no parent should cause guinea and pig to be treated as separate tokens query = queryParser.parse("+that -guinea pig +smells"); expectedQuery = new BooleanQuery.Builder() .add(new TermQuery(new Term(STRING_FIELD_NAME, "that")), BooleanClause.Occur.MUST) .add(new TermQuery(new Term(STRING_FIELD_NAME, "guinea")), BooleanClause.Occur.MUST_NOT) .add(new TermQuery(new Term(STRING_FIELD_NAME, "pig")), defaultOp) .add(new TermQuery(new Term(STRING_FIELD_NAME, "smells")), BooleanClause.Occur.MUST) .build(); assertThat(query, Matchers.equalTo(expectedQuery)); // span query query = queryParser.parse("\"that guinea pig smells\""); expectedQuery = new BooleanQuery.Builder() .add(new SpanNearQuery.Builder(STRING_FIELD_NAME, true) .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "that"))) .addClause(new SpanOrQuery( new SpanNearQuery.Builder(STRING_FIELD_NAME, true) .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "guinea"))) .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "pig"))).build(), new SpanTermQuery(new Term(STRING_FIELD_NAME, "cavy")))) .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "smells"))) .build(), Occur.SHOULD) .build(); assertThat(query, Matchers.equalTo(expectedQuery)); // span query with slop query = queryParser.parse("\"that guinea pig smells\"~2"); expectedQuery = new BooleanQuery.Builder() .add(new SpanNearQuery.Builder(STRING_FIELD_NAME, true) .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "that"))) .addClause(new SpanOrQuery( new SpanNearQuery.Builder(STRING_FIELD_NAME, true) .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "guinea"))) .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "pig"))).build(), new SpanTermQuery(new Term(STRING_FIELD_NAME, "cavy")))) .addClause(new SpanTermQuery(new Term(STRING_FIELD_NAME, "smells"))) .setSlop(2) .build(), Occur.SHOULD) .build(); assertThat(query, Matchers.equalTo(expectedQuery)); } } public void testToQueryRegExpQuery() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); Query query = queryStringQuery("/foo*bar/").defaultField(STRING_FIELD_NAME) .maxDeterminizedStates(5000) .toQuery(createShardContext()); assertThat(query, instanceOf(RegexpQuery.class)); RegexpQuery regexpQuery = (RegexpQuery) query; assertTrue(regexpQuery.toString().contains("/foo*bar/")); } public void testToQueryRegExpQueryTooComplex() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); QueryStringQueryBuilder queryBuilder = queryStringQuery("/[ac]*a[ac]{50,200}/").defaultField(STRING_FIELD_NAME); TooComplexToDeterminizeException e = expectThrows(TooComplexToDeterminizeException.class, () -> queryBuilder.toQuery(createShardContext())); assertThat(e.getMessage(), containsString("Determinizing [ac]*")); assertThat(e.getMessage(), containsString("would result in more than 10000 states")); } /** * Validates that {@code max_determinized_states} can be parsed and lowers the allowed number of determinized states. */ public void testToQueryRegExpQueryMaxDeterminizedStatesParsing() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); XContentBuilder builder = JsonXContent.contentBuilder(); builder.startObject(); { builder.startObject("query_string"); { builder.field("query", "/[ac]*a[ac]{1,10}/"); builder.field("default_field", STRING_FIELD_NAME); builder.field("max_determinized_states", 10); } builder.endObject(); } builder.endObject(); QueryBuilder queryBuilder = new QueryParseContext(createParser(builder)).parseInnerQueryBuilder(); TooComplexToDeterminizeException e = expectThrows(TooComplexToDeterminizeException.class, () -> queryBuilder.toQuery(createShardContext())); assertThat(e.getMessage(), containsString("Determinizing [ac]*")); assertThat(e.getMessage(), containsString("would result in more than 10 states")); } /** * Validates that {@code max_determinized_states} can be parsed and lowers the allowed number of determinized states. */ public void testEnabledPositionIncrements() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); XContentBuilder builder = JsonXContent.contentBuilder(); builder.startObject(); { builder.startObject("query_string"); { builder.field("query", "text"); builder.field("default_field", STRING_FIELD_NAME); builder.field("enable_position_increments", false); } builder.endObject(); } builder.endObject(); QueryStringQueryBuilder queryBuilder = (QueryStringQueryBuilder) new QueryParseContext(createParser(builder)) .parseInnerQueryBuilder(); assertFalse(queryBuilder.enablePositionIncrements()); } public void testToQueryFuzzyQueryAutoFuziness() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); int length = randomIntBetween(1, 10); StringBuilder queryString = new StringBuilder(); for (int i = 0; i < length; i++) { queryString.append("a"); } queryString.append("~"); int expectedEdits; if (length <= 2) { expectedEdits = 0; } else if (3 <= length && length <= 5) { expectedEdits = 1; } else { expectedEdits = 2; } Query query = queryStringQuery(queryString.toString()).defaultField(STRING_FIELD_NAME).fuzziness(Fuzziness.AUTO) .toQuery(createShardContext()); assertThat(query, instanceOf(FuzzyQuery.class)); FuzzyQuery fuzzyQuery = (FuzzyQuery) query; assertEquals(expectedEdits, fuzzyQuery.getMaxEdits()); } public void testFuzzyNumeric() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); QueryStringQueryBuilder query = queryStringQuery("12~0.2").defaultField(INT_FIELD_NAME); QueryShardContext context = createShardContext(); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> query.toQuery(context)); assertEquals("Can only use fuzzy queries on keyword and text fields - not on [mapped_int] which is of type [integer]", e.getMessage()); query.lenient(true); query.toQuery(context); // no exception } public void testPrefixNumeric() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); QueryStringQueryBuilder query = queryStringQuery("12*").defaultField(INT_FIELD_NAME); QueryShardContext context = createShardContext(); QueryShardException e = expectThrows(QueryShardException.class, () -> query.toQuery(context)); assertEquals("Can only use prefix queries on keyword and text fields - not on [mapped_int] which is of type [integer]", e.getMessage()); query.lenient(true); query.toQuery(context); // no exception } public void testExactGeo() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); QueryStringQueryBuilder query = queryStringQuery("2,3").defaultField(GEO_POINT_FIELD_NAME); QueryShardContext context = createShardContext(); QueryShardException e = expectThrows(QueryShardException.class, () -> query.toQuery(context)); assertEquals("Geo fields do not support exact searching, use dedicated geo queries instead: [mapped_geo_point]", e.getMessage()); query.lenient(true); query.toQuery(context); // no exception } public void testTimezone() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); String queryAsString = "{\n" + " \"query_string\":{\n" + " \"time_zone\":\"Europe/Paris\",\n" + " \"query\":\"" + DATE_FIELD_NAME + ":[2012 TO 2014]\"\n" + " }\n" + "}"; QueryBuilder queryBuilder = parseQuery(queryAsString); assertThat(queryBuilder, instanceOf(QueryStringQueryBuilder.class)); QueryStringQueryBuilder queryStringQueryBuilder = (QueryStringQueryBuilder) queryBuilder; assertThat(queryStringQueryBuilder.timeZone(), equalTo(DateTimeZone.forID("Europe/Paris"))); String invalidQueryAsString = "{\n" + " \"query_string\":{\n" + " \"time_zone\":\"This timezone does not exist\",\n" + " \"query\":\"" + DATE_FIELD_NAME + ":[2012 TO 2014]\"\n" + " }\n" + "}"; expectThrows(IllegalArgumentException.class, () -> parseQuery(invalidQueryAsString)); } public void testToQueryBooleanQueryMultipleBoosts() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); int numBoosts = randomIntBetween(2, 10); float[] boosts = new float[numBoosts + 1]; String queryStringPrefix = ""; String queryStringSuffix = ""; for (int i = 0; i < boosts.length - 1; i++) { float boost = 2.0f / randomIntBetween(3, 20); boosts[i] = boost; queryStringPrefix += "("; queryStringSuffix += ")^" + boost; } String queryString = queryStringPrefix + "foo bar" + queryStringSuffix; float mainBoost = 2.0f / randomIntBetween(3, 20); boosts[boosts.length - 1] = mainBoost; QueryStringQueryBuilder queryStringQueryBuilder = new QueryStringQueryBuilder(queryString).field(STRING_FIELD_NAME) .minimumShouldMatch("2").boost(mainBoost); Query query = queryStringQueryBuilder.toQuery(createShardContext()); for (int i = boosts.length - 1; i >= 0; i--) { assertThat(query, instanceOf(BoostQuery.class)); BoostQuery boostQuery = (BoostQuery) query; assertThat(boostQuery.getBoost(), equalTo(boosts[i])); query = boostQuery.getQuery(); } assertThat(query, instanceOf(BooleanQuery.class)); BooleanQuery booleanQuery = (BooleanQuery) query; assertThat(booleanQuery.getMinimumNumberShouldMatch(), equalTo(2)); assertThat(booleanQuery.clauses().get(0).getOccur(), equalTo(BooleanClause.Occur.SHOULD)); assertThat(booleanQuery.clauses().get(0).getQuery(), equalTo(new TermQuery(new Term(STRING_FIELD_NAME, "foo")))); assertThat(booleanQuery.clauses().get(1).getOccur(), equalTo(BooleanClause.Occur.SHOULD)); assertThat(booleanQuery.clauses().get(1).getQuery(), equalTo(new TermQuery(new Term(STRING_FIELD_NAME, "bar")))); } public void testToQueryPhraseQueryBoostAndSlop() throws IOException { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); 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(boostQuery.getBoost(), equalTo(5f)); assertThat(boostQuery.getQuery(), instanceOf(PhraseQuery.class)); PhraseQuery phraseQuery = (PhraseQuery) boostQuery.getQuery(); assertThat(phraseQuery.getSlop(), Matchers.equalTo(2)); assertThat(phraseQuery.getTerms().length, equalTo(2)); } public void testToQueryWildcardNonExistingFields() throws IOException { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); 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(); assertThat(expectedQuery, equalTo(query)); queryStringQueryBuilder = new QueryStringQueryBuilder("field:foo bar").field("invalid*"); query = queryStringQueryBuilder.toQuery(createShardContext()); expectedQuery = new BooleanQuery.Builder() .add(new TermQuery(new Term("field", "foo")), Occur.SHOULD) .add(new MatchNoDocsQuery("empty fields"), Occur.SHOULD) .build(); assertThat(expectedQuery, equalTo(query)); } public void testToQuerySplitOnWhitespace() 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); Query query = queryBuilder.toQuery(createShardContext()); BooleanQuery bq1 = new BooleanQuery.Builder() .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "foo")), BooleanClause.Occur.SHOULD)) .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "bar")), BooleanClause.Occur.SHOULD)) .build(); List<Query> disjuncts = new ArrayList<>(); disjuncts.add(bq1); 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); Query query = queryBuilder.toQuery(createShardContext()); BooleanQuery bq1 = new BooleanQuery.Builder() .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "foo")), BooleanClause.Occur.SHOULD)) .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "bar")), BooleanClause.Occur.SHOULD)) .build(); List<Query> disjuncts = new ArrayList<>(); disjuncts.add(bq1); disjuncts.add(new TermQuery(new Term(STRING_FIELD_NAME_2, "foo bar"))); DisjunctionMaxQuery disjunctionMaxQuery = new DisjunctionMaxQuery(disjuncts, 0.0f); BooleanQuery expectedQuery = new BooleanQuery.Builder() .add(disjunctionMaxQuery, BooleanClause.Occur.SHOULD) .add(new TermQuery(new Term(STRING_FIELD_NAME, "other")), BooleanClause.Occur.SHOULD) .build(); assertThat(query, equalTo(expectedQuery)); } { QueryStringQueryBuilder queryBuilder = new QueryStringQueryBuilder("foo OR bar") .field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2) .splitOnWhitespace(false); 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)); } // split_on_whitespace=false breaks range query with simple syntax { // throws an exception when lenient is set to false QueryStringQueryBuilder queryBuilder = new QueryStringQueryBuilder(">10 foo") .field(INT_FIELD_NAME) .splitOnWhitespace(false); 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)); } } public void testExistsFieldQuery() throws Exception { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); QueryShardContext context = createShardContext(); QueryStringQueryBuilder queryBuilder = new QueryStringQueryBuilder("foo:*"); Query query = queryBuilder.toQuery(context); Query expected = new ConstantScoreQuery(new TermQuery(new Term("_field_names", "foo"))); assertThat(query, equalTo(expected)); queryBuilder = new QueryStringQueryBuilder("_all:*"); query = queryBuilder.toQuery(context); expected = new MatchAllDocsQuery(); assertThat(query, equalTo(expected)); queryBuilder = new QueryStringQueryBuilder("*:*"); query = queryBuilder.toQuery(context); expected = new MatchAllDocsQuery(); assertThat(query, equalTo(expected)); queryBuilder = new QueryStringQueryBuilder("*"); query = queryBuilder.toQuery(context); List<Query> fieldQueries = new ArrayList<> (); for (String type : QueryStringQueryBuilder.allQueryableDefaultFields(context).keySet()) { fieldQueries.add(new ConstantScoreQuery(new TermQuery(new Term("_field_names", type)))); } expected = new DisjunctionMaxQuery(fieldQueries, 0f); assertThat(query, equalTo(expected)); } public void testDisabledFieldNamesField() throws Exception { QueryShardContext context = createShardContext(); context.getMapperService().merge("new_type", new CompressedXContent( PutMappingRequest.buildFromSimplifiedDef("new_type", "foo", "type=text", "_field_names", "enabled=false").string()), MapperService.MergeReason.MAPPING_UPDATE, true); QueryStringQueryBuilder queryBuilder = new QueryStringQueryBuilder("foo:*"); Query query = queryBuilder.toQuery(context); Query expected = new WildcardQuery(new Term("foo", "*")); assertThat(query, equalTo(expected)); context.getMapperService().merge("new_type", new CompressedXContent( PutMappingRequest.buildFromSimplifiedDef("new_type", "foo", "type=text", "_field_names", "enabled=true").string()), MapperService.MergeReason.MAPPING_UPDATE, true); } public void testFromJson() throws IOException { String json = "{\n" + " \"query_string\" : {\n" + " \"query\" : \"this AND that OR thus\",\n" + " \"default_field\" : \"content\",\n" + " \"fields\" : [ ],\n" + " \"use_dis_max\" : true,\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" + " \"fuzzy_prefix_length\" : 0,\n" + " \"fuzzy_max_expansions\" : 50,\n" + " \"phrase_slop\" : 0,\n" + " \"escape\" : false,\n" + " \"split_on_whitespace\" : true,\n" + " \"boost\" : 1.0\n" + " }\n" + "}"; QueryStringQueryBuilder parsed = (QueryStringQueryBuilder) parseQuery(json); checkGeneratedJson(json, parsed); assertEquals(json, "this AND that OR thus", parsed.queryString()); assertEquals(json, "content", parsed.defaultField()); } public void testExpandedTerms() throws Exception { // Prefix Query query = new QueryStringQueryBuilder("aBc*") .field(STRING_FIELD_NAME) .analyzer("whitespace") .toQuery(createShardContext()); assertEquals(new PrefixQuery(new Term(STRING_FIELD_NAME, "aBc")), query); query = new QueryStringQueryBuilder("aBc*") .field(STRING_FIELD_NAME) .analyzer("standard") .toQuery(createShardContext()); assertEquals(new PrefixQuery(new Term(STRING_FIELD_NAME, "abc")), query); // Wildcard query = new QueryStringQueryBuilder("aBc*D") .field(STRING_FIELD_NAME) .analyzer("whitespace") .toQuery(createShardContext()); assertEquals(new WildcardQuery(new Term(STRING_FIELD_NAME, "aBc*D")), query); query = new QueryStringQueryBuilder("aBc*D") .field(STRING_FIELD_NAME) .analyzer("standard") .toQuery(createShardContext()); assertEquals(new WildcardQuery(new Term(STRING_FIELD_NAME, "abc*d")), query); // Fuzzy query = new QueryStringQueryBuilder("aBc~1") .field(STRING_FIELD_NAME) .analyzer("whitespace") .toQuery(createShardContext()); FuzzyQuery fuzzyQuery = (FuzzyQuery) query; assertEquals(new Term(STRING_FIELD_NAME, "aBc"), fuzzyQuery.getTerm()); query = new QueryStringQueryBuilder("aBc~1") .field(STRING_FIELD_NAME) .analyzer("standard") .toQuery(createShardContext()); fuzzyQuery = (FuzzyQuery) query; assertEquals(new Term(STRING_FIELD_NAME, "abc"), fuzzyQuery.getTerm()); // Range query = new QueryStringQueryBuilder("[aBc TO BcD]") .field(STRING_FIELD_NAME) .analyzer("whitespace") .toQuery(createShardContext()); assertEquals(new TermRangeQuery(STRING_FIELD_NAME, new BytesRef("aBc"), new BytesRef("BcD"), true, true), query); query = new QueryStringQueryBuilder("[aBc TO BcD]") .field(STRING_FIELD_NAME) .analyzer("standard") .toQuery(createShardContext()); assertEquals(new TermRangeQuery(STRING_FIELD_NAME, new BytesRef("abc"), new BytesRef("bcd"), true, true), query); } public void testAllFieldsWithFields() throws IOException { String json = "{\n" + " \"query_string\" : {\n" + " \"query\" : \"this AND that OR thus\",\n" + " \"fields\" : [\"foo\"],\n" + " \"all_fields\" : true\n" + " }\n" + "}"; ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(json)); assertThat(e.getMessage(), containsString("cannot use [all_fields] parameter in conjunction with [default_field] or [fields]")); String json2 = "{\n" + " \"query_string\" : {\n" + " \"query\" : \"this AND that OR thus\",\n" + " \"default_field\" : \"foo\",\n" + " \"all_fields\" : true\n" + " }\n" + "}"; e = expectThrows(ParsingException.class, () -> parseQuery(json2)); 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"); } }