diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/QueryBuilderBWCIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/QueryBuilderBWCIT.java index 6d5b44adc644..dbbd55f7725a 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/QueryBuilderBWCIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/QueryBuilderBWCIT.java @@ -63,26 +63,26 @@ public class QueryBuilderBWCIT extends AbstractFullClusterRestartTestCase { static { addCandidate(""" - "match": { "keyword_field": "value"} - """, new MatchQueryBuilder("keyword_field", "value")); + "match": { "text_field": "value"} + """, new MatchQueryBuilder("text_field", "value")); addCandidate(""" - "match": { "keyword_field": {"query": "value", "operator": "and"} } - """, new MatchQueryBuilder("keyword_field", "value").operator(Operator.AND)); + "match": { "text_field": {"query": "value", "operator": "and"} } + """, new MatchQueryBuilder("text_field", "value").operator(Operator.AND)); addCandidate(""" - "match": { "keyword_field": {"query": "value", "analyzer": "english"} } - """, new MatchQueryBuilder("keyword_field", "value").analyzer("english")); + "match": { "text_field": {"query": "value", "analyzer": "english"} } + """, new MatchQueryBuilder("text_field", "value").analyzer("english")); addCandidate(""" - "match": { "keyword_field": {"query": "value", "minimum_should_match": 3} } - """, new MatchQueryBuilder("keyword_field", "value").minimumShouldMatch("3")); + "match": { "text_field": {"query": "value", "minimum_should_match": 3} } + """, new MatchQueryBuilder("text_field", "value").minimumShouldMatch("3")); addCandidate(""" - "match": { "keyword_field": {"query": "value", "fuzziness": "auto"} } - """, new MatchQueryBuilder("keyword_field", "value").fuzziness(Fuzziness.AUTO)); + "match": { "text_field": {"query": "value", "fuzziness": "auto"} } + """, new MatchQueryBuilder("text_field", "value").fuzziness(Fuzziness.AUTO)); addCandidate(""" - "match_phrase": { "keyword_field": "value"} - """, new MatchPhraseQueryBuilder("keyword_field", "value")); + "match_phrase": { "text_field": "value"} + """, new MatchPhraseQueryBuilder("text_field", "value")); addCandidate(""" - "match_phrase": { "keyword_field": {"query": "value", "slop": 3}} - """, new MatchPhraseQueryBuilder("keyword_field", "value").slop(3)); + "match_phrase": { "text_field": {"query": "value", "slop": 3}} + """, new MatchPhraseQueryBuilder("text_field", "value").slop(3)); addCandidate(""" "range": { "long_field": {"gte": 1, "lte": 9}} """, new RangeQueryBuilder("long_field").from(1).to(9)); @@ -179,6 +179,11 @@ public class QueryBuilderBWCIT extends AbstractFullClusterRestartTestCase { mappingsAndSettings.field("type", "keyword"); mappingsAndSettings.endObject(); } + { + mappingsAndSettings.startObject("text_field"); + mappingsAndSettings.field("type", "text"); + mappingsAndSettings.endObject(); + } { mappingsAndSettings.startObject("long_field"); mappingsAndSettings.field("type", "long"); diff --git a/server/src/main/java/org/elasticsearch/index/query/MatchPhraseQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/MatchPhraseQueryBuilder.java index f75a0a9db3d1..15a38944e582 100644 --- a/server/src/main/java/org/elasticsearch/index/query/MatchPhraseQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/MatchPhraseQueryBuilder.java @@ -8,11 +8,14 @@ package org.elasticsearch.index.query; +import org.apache.lucene.analysis.core.KeywordAnalyzer; import org.apache.lucene.search.Query; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.index.analysis.NamedAnalyzer; +import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.search.MatchQueryParser; import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.XContentBuilder; @@ -148,6 +151,35 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder { builder.endObject(); } + @Override + protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException { + if (fuzziness != null || lenient) { + // Term queries can be neither fuzzy nor lenient, so don't rewrite under these conditions + return this; + } + SearchExecutionContext sec = queryRewriteContext.convertToSearchExecutionContext(); + if (sec == null) { + return this; + } + // If we're using a keyword analyzer then we can rewrite this to a TermQueryBuilder + // and possibly shortcut + NamedAnalyzer configuredAnalyzer = configuredAnalyzer(sec); + if (configuredAnalyzer != null && configuredAnalyzer.analyzer() instanceof KeywordAnalyzer) { + TermQueryBuilder termQueryBuilder = new TermQueryBuilder(fieldName, value); + return termQueryBuilder.rewrite(sec); + } + return this; + } + + private NamedAnalyzer configuredAnalyzer(SearchExecutionContext context) { + if (analyzer != null) { + return context.getIndexAnalyzers().get(analyzer); + } + MappedFieldType mft = context.getFieldType(fieldName); + if (mft != null) { + return mft.getTextSearchInfo().getSearchAnalyzer(); + } + return null; + } + @Override protected Query doToQuery(SearchExecutionContext context) throws IOException { // validate context specific fields diff --git a/server/src/test/java/org/elasticsearch/index/query/MatchPhraseQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/MatchPhraseQueryBuilderTests.java index bc3047dbc4ce..8ed1882c6743 100644 --- a/server/src/test/java/org/elasticsearch/index/query/MatchPhraseQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/MatchPhraseQueryBuilderTests.java @@ -16,6 +16,7 @@ import org.apache.lucene.search.PhraseQuery; import org.apache.lucene.search.PointRangeQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; +import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.ParsingException; import org.elasticsearch.test.AbstractQueryTestCase; @@ -190,4 +191,39 @@ public class MatchPhraseQueryBuilderTests extends AbstractQueryTestCase parseQuery(shortJson)); assertEquals("[match_phrase] query doesn't support multiple fields, found [message1] and [message2]", e.getMessage()); } + + public void testRewriteToTermQueries() throws IOException { + QueryBuilder queryBuilder = new MatchPhraseQueryBuilder(KEYWORD_FIELD_NAME, "value"); + SearchExecutionContext context = createSearchExecutionContext(); + QueryBuilder rewritten = queryBuilder.rewrite(context); + assertThat(rewritten, instanceOf(TermQueryBuilder.class)); + TermQueryBuilder tqb = (TermQueryBuilder) rewritten; + assertEquals(KEYWORD_FIELD_NAME, tqb.fieldName); + assertEquals(new BytesRef("value"), tqb.value); + } + + public void testRewriteToTermQueryWithAnalyzer() throws IOException { + MatchPhraseQueryBuilder queryBuilder = new MatchPhraseQueryBuilder(TEXT_FIELD_NAME, "value"); + queryBuilder.analyzer("keyword"); + SearchExecutionContext context = createSearchExecutionContext(); + QueryBuilder rewritten = queryBuilder.rewrite(context); + assertThat(rewritten, instanceOf(TermQueryBuilder.class)); + TermQueryBuilder tqb = (TermQueryBuilder) rewritten; + assertEquals(TEXT_FIELD_NAME, tqb.fieldName); + assertEquals(new BytesRef("value"), tqb.value); + } + + public void testRewriteIndexQueryToMatchNone() throws IOException { + QueryBuilder query = new MatchPhraseQueryBuilder("_index", "does_not_exist"); + SearchExecutionContext searchExecutionContext = createSearchExecutionContext(); + QueryBuilder rewritten = query.rewrite(searchExecutionContext); + assertThat(rewritten, instanceOf(MatchNoneQueryBuilder.class)); + } + + public void testRewriteIndexQueryToNotMatchNone() throws IOException { + QueryBuilder query = new MatchPhraseQueryBuilder("_index", getIndex().getName()); + SearchExecutionContext searchExecutionContext = createSearchExecutionContext(); + QueryBuilder rewritten = query.rewrite(searchExecutionContext); + assertThat(rewritten, instanceOf(MatchAllQueryBuilder.class)); + } } diff --git a/server/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java index 1191119431cf..b1819af573a4 100644 --- a/server/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java @@ -33,6 +33,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery; import org.elasticsearch.common.lucene.search.Queries; +import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.search.MatchQueryParser; @@ -574,4 +575,59 @@ public class MatchQueryBuilderTests extends AbstractQueryTestCase