From e1d98c607be1ca57126a33c2e5c5a2d0b5052ea4 Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Wed, 10 May 2023 15:14:04 +0200 Subject: [PATCH] Add tests for multi_match phrase prefix query across multiple fields (#95772) This adds unit test coverage for a bug that was recently found in Lucene. We would have caught it earlier if we were testing the underlying lucene query being generated. Closes #95738 --- .../query/MultiMatchQueryBuilderTests.java | 24 ---------- .../search/MultiMatchQueryParserTests.java | 48 +++++++++++++++++-- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/index/query/MultiMatchQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/MultiMatchQueryBuilderTests.java index aba90ec5fda43..f6332a74025b1 100644 --- a/server/src/test/java/org/elasticsearch/index/query/MultiMatchQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/MultiMatchQueryBuilderTests.java @@ -22,10 +22,8 @@ import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; -import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.core.Strings; import org.elasticsearch.index.query.MultiMatchQueryBuilder.Type; @@ -43,7 +41,6 @@ import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; @@ -453,27 +450,6 @@ public void testNegativeFieldBoost() { assertThat(exc.getMessage(), containsString("negative [boost]")); } - private static IndexMetadata newIndexMeta(String name, Settings oldIndexSettings, Settings indexSettings) { - Settings build = Settings.builder().put(oldIndexSettings).put(indexSettings).build(); - return IndexMetadata.builder(name).settings(build).build(); - } - - private void assertQueryWithAllFieldsWildcard(Query query) { - assertEquals(DisjunctionMaxQuery.class, query.getClass()); - DisjunctionMaxQuery disjunctionMaxQuery = (DisjunctionMaxQuery) query; - int noMatchNoDocsQueries = 0; - for (Query q : disjunctionMaxQuery.getDisjuncts()) { - if (q.getClass() == MatchNoDocsQuery.class) { - noMatchNoDocsQueries++; - } - } - assertEquals(9, noMatchNoDocsQueries); - assertThat( - disjunctionMaxQuery.getDisjuncts(), - hasItems(new TermQuery(new Term(TEXT_FIELD_NAME, "hello")), new TermQuery(new Term(KEYWORD_FIELD_NAME, "hello"))) - ); - } - /** * "now" on date fields should make the query non-cachable. */ diff --git a/server/src/test/java/org/elasticsearch/index/search/MultiMatchQueryParserTests.java b/server/src/test/java/org/elasticsearch/index/search/MultiMatchQueryParserTests.java index 84135f7c55ad4..38b6a16fdc35d 100644 --- a/server/src/test/java/org/elasticsearch/index/search/MultiMatchQueryParserTests.java +++ b/server/src/test/java/org/elasticsearch/index/search/MultiMatchQueryParserTests.java @@ -44,12 +44,15 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import static java.util.Collections.emptyMap; import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; public class MultiMatchQueryParserTests extends ESSingleNodeTestCase { @@ -73,15 +76,27 @@ public void setup() throws IOException { "properties": { "first": { "type": "text", - "analyzer": "standard" + "analyzer": "standard", + "index_prefixes": { + "min_chars" : 1, + "max_chars" : 19 + } }, "last": { "type": "text", - "analyzer": "standard" + "analyzer": "standard", + "index_prefixes": { + "min_chars" : 1, + "max_chars" : 19 + } }, "nickname": { "type": "text", - "analyzer": "whitespace" + "analyzer": "whitespace", + "index_prefixes": { + "min_chars" : 1, + "max_chars" : 19 + } } } } @@ -93,6 +108,33 @@ public void setup() throws IOException { this.indexService = indexService; } + public void testToQueryPhrasePrefix() throws IOException { + SearchExecutionContext searchExecutionContext = indexService.newSearchExecutionContext(randomInt(20), 0, null, () -> { + throw new UnsupportedOperationException(); + }, null, emptyMap()); + searchExecutionContext.setAllowUnmappedFields(true); + MultiMatchQueryBuilder multiMatchQueryBuilder = new MultiMatchQueryBuilder("Har", "name.first", "name.last", "name.nickname"); + multiMatchQueryBuilder.type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX); + Query query = multiMatchQueryBuilder.toQuery(searchExecutionContext); + assertThat(query, instanceOf(DisjunctionMaxQuery.class)); + DisjunctionMaxQuery disjunctionMaxQuery = (DisjunctionMaxQuery) query; + Set expectedTerms = new HashSet<>( + Arrays.asList( + new Term("name.first._index_prefix", "har"), + new Term("name.last._index_prefix", "har"), + new Term("name.nickname._index_prefix", "Har") + ) + ); + for (Query disjunct : disjunctionMaxQuery.getDisjuncts()) { + assertThat(disjunct, instanceOf(SynonymQuery.class)); + SynonymQuery synonymQuery = (SynonymQuery) disjunct; + assertEquals(1, synonymQuery.getTerms().size()); + Term term = synonymQuery.getTerms().get(0); + assertTrue("Unexpected term " + term, expectedTerms.remove(term)); + } + assertEquals("Expected terms not found in the query: " + expectedTerms, 0, expectedTerms.size()); + } + public void testCrossFieldMultiMatchQuery() throws IOException { SearchExecutionContext searchExecutionContext = indexService.newSearchExecutionContext(randomInt(20), 0, null, () -> { throw new UnsupportedOperationException();