diff --git a/server/src/main/java/org/elasticsearch/index/query/MultiMatchQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/MultiMatchQueryBuilder.java index ec8392c90c8de..8200a8068afa4 100644 --- a/server/src/main/java/org/elasticsearch/index/query/MultiMatchQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/MultiMatchQueryBuilder.java @@ -29,7 +29,6 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.xcontent.DeprecationHandler; import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; @@ -803,18 +802,20 @@ protected Query doToQuery(QueryShardContext context) throws IOException { multiMatchQuery.setTranspositions(fuzzyTranspositions); Map newFieldsBoosts; + boolean isAllField; if (fieldsBoosts.isEmpty()) { // no fields provided, defaults to index.query.default_field List defaultFields = context.defaultFields(); - boolean isAllField = defaultFields.size() == 1 && Regex.isMatchAllPattern(defaultFields.get(0)); - if (isAllField && lenient == null) { - // Sets leniency to true if not explicitly - // set in the request - multiMatchQuery.setLenient(true); - } newFieldsBoosts = QueryParserHelper.resolveMappingFields(context, QueryParserHelper.parseFieldsAndWeights(defaultFields)); + isAllField = QueryParserHelper.hasAllFieldsWildcard(defaultFields); } else { newFieldsBoosts = QueryParserHelper.resolveMappingFields(context, fieldsBoosts); + isAllField = QueryParserHelper.hasAllFieldsWildcard(fieldsBoosts.keySet()); + } + if (isAllField && lenient == null) { + // Sets leniency to true if not explicitly + // set in the request + multiMatchQuery.setLenient(true); } return multiMatchQuery.parse(type, newFieldsBoosts, value, minimumShouldMatch); } diff --git a/server/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java index 3f8a0acc91695..932d3bc726d6b 100644 --- a/server/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java @@ -852,11 +852,14 @@ protected Query doToQuery(QueryShardContext context) throws IOException { } } else if (fieldsAndWeights.size() > 0) { final Map resolvedFields = QueryParserHelper.resolveMappingFields(context, fieldsAndWeights); - queryParser = new QueryStringQueryParser(context, resolvedFields, isLenient); + if (QueryParserHelper.hasAllFieldsWildcard(fieldsAndWeights.keySet())) { + queryParser = new QueryStringQueryParser(context, resolvedFields, lenient == null ? true : lenient); + } else { + queryParser = new QueryStringQueryParser(context, resolvedFields, isLenient); + } } else { List defaultFields = context.defaultFields(); - boolean isAllField = defaultFields.size() == 1 && Regex.isMatchAllPattern(defaultFields.get(0)); - if (isAllField) { + if (QueryParserHelper.hasAllFieldsWildcard(defaultFields)) { queryParser = new QueryStringQueryParser(context, lenient == null ? true : lenient); } else { final Map resolvedFields = QueryParserHelper.resolveMappingFields(context, diff --git a/server/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java b/server/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java index 2b2045266455b..1141dd9597035 100644 --- a/server/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java @@ -29,7 +29,6 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.lucene.search.Queries; -import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.search.QueryParserHelper; @@ -404,16 +403,19 @@ public SimpleQueryStringBuilder fuzzyTranspositions(boolean fuzzyTranspositions) protected Query doToQuery(QueryShardContext context) throws IOException { Settings newSettings = new Settings(settings); final Map resolvedFieldsAndWeights; + boolean isAllField; if (fieldsAndWeights.isEmpty() == false) { resolvedFieldsAndWeights = QueryParserHelper.resolveMappingFields(context, fieldsAndWeights); + isAllField = QueryParserHelper.hasAllFieldsWildcard(fieldsAndWeights.keySet()); } else { List defaultFields = context.defaultFields(); - boolean isAllField = defaultFields.size() == 1 && Regex.isMatchAllPattern(defaultFields.get(0)); - if (isAllField) { - newSettings.lenient(lenientSet ? settings.lenient() : true); - } resolvedFieldsAndWeights = QueryParserHelper.resolveMappingFields(context, QueryParserHelper.parseFieldsAndWeights(defaultFields)); + isAllField = QueryParserHelper.hasAllFieldsWildcard(defaultFields); + } + + if (isAllField) { + newSettings.lenient(lenientSet ? settings.lenient() : true); } final SimpleQueryStringQueryParser sqp; diff --git a/server/src/main/java/org/elasticsearch/index/search/QueryParserHelper.java b/server/src/main/java/org/elasticsearch/index/search/QueryParserHelper.java index adc1691608b23..3acf2929687c5 100644 --- a/server/src/main/java/org/elasticsearch/index/search/QueryParserHelper.java +++ b/server/src/main/java/org/elasticsearch/index/search/QueryParserHelper.java @@ -161,4 +161,12 @@ private static void checkForTooManyFields(Map fields, QueryShardC throw new IllegalArgumentException("field expansion matches too many fields, limit: " + limit + ", got: " + fields.size()); } } + + /** + * Returns true if any of the fields is the wildcard {@code *}, false otherwise. + * @param fields A collection of field names + */ + public static boolean hasAllFieldsWildcard(Collection fields) { + return fields.stream().anyMatch(Regex::isMatchAllPattern); + } } 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 ab9b3c732135d..6590a5609353a 100644 --- a/server/src/test/java/org/elasticsearch/index/query/MultiMatchQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/MultiMatchQueryBuilderTests.java @@ -55,6 +55,7 @@ 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; @@ -409,52 +410,79 @@ public void testToFuzzyQuery() throws Exception { public void testDefaultField() throws Exception { QueryShardContext context = createShardContext(); MultiMatchQueryBuilder builder = new MultiMatchQueryBuilder("hello"); - // should pass because we set lenient to true when default field is `*` + // default value `*` sets leniency to true Query query = builder.toQuery(context); - assertThat(query, instanceOf(DisjunctionMaxQuery.class)); - - context.getIndexSettings().updateIndexMetaData( - newIndexMeta("index", context.getIndexSettings().getSettings(), - Settings.builder().putList("index.query.default_field", STRING_FIELD_NAME, STRING_FIELD_NAME_2 + "^5") - .build()) - ); - - MultiMatchQueryBuilder qb = new MultiMatchQueryBuilder("hello"); - query = qb.toQuery(context); - DisjunctionMaxQuery expected = new DisjunctionMaxQuery( - Arrays.asList( - new TermQuery(new Term(STRING_FIELD_NAME, "hello")), - new BoostQuery(new TermQuery(new Term(STRING_FIELD_NAME_2, "hello")), 5.0f) - ), 0.0f - ); - assertEquals(expected, query); + assertQueryWithAllFieldsWildcard(query); + + try { + // `*` is in the list of the default_field => leniency set to true + context.getIndexSettings().updateIndexMetaData( + newIndexMeta("index", context.getIndexSettings().getSettings(), Settings.builder().putList("index.query.default_field", + STRING_FIELD_NAME, "*", STRING_FIELD_NAME_2).build()) + ); + query = new MultiMatchQueryBuilder("hello") + .toQuery(context); + assertQueryWithAllFieldsWildcard(query); + + context.getIndexSettings().updateIndexMetaData( + newIndexMeta("index", context.getIndexSettings().getSettings(), + Settings.builder().putList("index.query.default_field", STRING_FIELD_NAME, STRING_FIELD_NAME_2 + "^5") + .build()) + ); + MultiMatchQueryBuilder qb = new MultiMatchQueryBuilder("hello"); + query = qb.toQuery(context); + DisjunctionMaxQuery expected = new DisjunctionMaxQuery( + Arrays.asList( + new TermQuery(new Term(STRING_FIELD_NAME, "hello")), + new BoostQuery(new TermQuery(new Term(STRING_FIELD_NAME_2, "hello")), 5.0f) + ), 0.0f + ); + assertEquals(expected, query); + + context.getIndexSettings().updateIndexMetaData( + newIndexMeta("index", context.getIndexSettings().getSettings(), + Settings.builder().putList("index.query.default_field", + STRING_FIELD_NAME, STRING_FIELD_NAME_2 + "^5", INT_FIELD_NAME).build()) + ); + // should fail because lenient defaults to false + IllegalArgumentException exc = expectThrows(IllegalArgumentException.class, () -> qb.toQuery(context)); + assertThat(exc, instanceOf(NumberFormatException.class)); + assertThat(exc.getMessage(), equalTo("For input string: \"hello\"")); + + // explicitly sets lenient + qb.lenient(true); + query = qb.toQuery(context); + expected = new DisjunctionMaxQuery( + Arrays.asList( + new TermQuery(new Term(STRING_FIELD_NAME, "hello")), + new BoostQuery(new TermQuery(new Term(STRING_FIELD_NAME_2, "hello")), 5.0f), + new MatchNoDocsQuery("failed [mapped_int] query, caused by number_format_exception:[For input string: \"hello\"]") + ), 0.0f + ); + assertEquals(expected, query); + + } finally { + // Reset to the default value + context.getIndexSettings().updateIndexMetaData( + newIndexMeta("index", context.getIndexSettings().getSettings(), + Settings.builder().putNull("index.query.default_field").build()) + ); + } + } - context.getIndexSettings().updateIndexMetaData( - newIndexMeta("index", context.getIndexSettings().getSettings(), - Settings.builder().putList("index.query.default_field", - STRING_FIELD_NAME, STRING_FIELD_NAME_2 + "^5", INT_FIELD_NAME).build()) - ); - // should fail because lenient defaults to false - IllegalArgumentException exc = expectThrows(IllegalArgumentException.class, () -> qb.toQuery(context)); - assertThat(exc, instanceOf(NumberFormatException.class)); - assertThat(exc.getMessage(), equalTo("For input string: \"hello\"")); - - // explicitly sets lenient - qb.lenient(true); - query = qb.toQuery(context); - expected = new DisjunctionMaxQuery( - Arrays.asList( - new TermQuery(new Term(STRING_FIELD_NAME, "hello")), - new BoostQuery(new TermQuery(new Term(STRING_FIELD_NAME_2, "hello")), 5.0f), - new MatchNoDocsQuery("failed [mapped_int] query, caused by number_format_exception:[For input string: \"hello\"]") - ), 0.0f - ); - assertEquals(expected, query); + public void testAllFieldsWildcard() throws Exception { + QueryShardContext context = createShardContext(); + Query query = new MultiMatchQueryBuilder("hello") + .field("*") + .toQuery(context); + assertQueryWithAllFieldsWildcard(query); - context.getIndexSettings().updateIndexMetaData( - newIndexMeta("index", context.getIndexSettings().getSettings(), - Settings.builder().putNull("index.query.default_field").build()) - ); + query = new MultiMatchQueryBuilder("hello") + .field(STRING_FIELD_NAME) + .field("*") + .field(STRING_FIELD_NAME_2) + .toQuery(context); + assertQueryWithAllFieldsWildcard(query); } public void testWithStopWords() throws Exception { @@ -536,4 +564,18 @@ private static IndexMetaData newIndexMeta(String name, Settings oldIndexSettings .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(11, noMatchNoDocsQueries); + assertThat(disjunctionMaxQuery.getDisjuncts(), hasItems(new TermQuery(new Term(STRING_FIELD_NAME, "hello")), + new TermQuery(new Term(STRING_FIELD_NAME_2, "hello")))); + } } diff --git a/server/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java index a49f0a5755cf9..e5bc81a5da0e3 100644 --- a/server/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java @@ -80,6 +80,7 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBooleanSubQuery; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertDisjunctionSubQuery; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; @@ -1258,12 +1259,27 @@ public void testUnmappedFieldRewriteToMatchNoDocs() throws IOException { public void testDefaultField() throws Exception { QueryShardContext context = createShardContext(); - context.getIndexSettings().updateIndexMetaData( - newIndexMeta("index", context.getIndexSettings().getSettings(), Settings.builder().putList("index.query.default_field", - STRING_FIELD_NAME, STRING_FIELD_NAME_2 + "^5").build()) - ); + // default value `*` sets leniency to true + Query query = new QueryStringQueryBuilder("hello") + .toQuery(context); + assertQueryWithAllFieldsWildcard(query); + try { - Query query = new QueryStringQueryBuilder("hello") + // `*` is in the list of the default_field => leniency set to true + context.getIndexSettings().updateIndexMetaData( + newIndexMeta("index", context.getIndexSettings().getSettings(), Settings.builder().putList("index.query.default_field", + STRING_FIELD_NAME, "*", STRING_FIELD_NAME_2).build()) + ); + query = new QueryStringQueryBuilder("hello") + .toQuery(context); + assertQueryWithAllFieldsWildcard(query); + + + context.getIndexSettings().updateIndexMetaData( + newIndexMeta("index", context.getIndexSettings().getSettings(), Settings.builder().putList("index.query.default_field", + STRING_FIELD_NAME, STRING_FIELD_NAME_2 + "^5").build()) + ); + query = new QueryStringQueryBuilder("hello") .toQuery(context); Query expected = new DisjunctionMaxQuery( Arrays.asList( @@ -1281,6 +1297,21 @@ public void testDefaultField() throws Exception { } } + public void testAllFieldsWildcard() throws Exception { + QueryShardContext context = createShardContext(); + Query query = new QueryStringQueryBuilder("hello") + .field("*") + .toQuery(context); + assertQueryWithAllFieldsWildcard(query); + + query = new QueryStringQueryBuilder("hello") + .field(STRING_FIELD_NAME) + .field("*") + .field(STRING_FIELD_NAME_2) + .toQuery(context); + assertQueryWithAllFieldsWildcard(query); + } + /** * the quote analyzer should overwrite any other forced analyzer in quoted parts of the query */ @@ -1516,4 +1547,18 @@ private static IndexMetaData newIndexMeta(String name, Settings oldIndexSettings .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(11, noMatchNoDocsQueries); + assertThat(disjunctionMaxQuery.getDisjuncts(), hasItems(new TermQuery(new Term(STRING_FIELD_NAME, "hello")), + new TermQuery(new Term(STRING_FIELD_NAME_2, "hello")))); + } } diff --git a/server/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java index daed696f02fd9..de9ba08a2f01c 100644 --- a/server/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java @@ -57,6 +57,7 @@ import java.util.Set; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.either; import static org.hamcrest.Matchers.equalTo; @@ -580,24 +581,56 @@ public void testQuoteFieldSuffix() { public void testDefaultField() throws Exception { QueryShardContext context = createShardContext(); - context.getIndexSettings().updateIndexMetaData( - newIndexMeta("index", context.getIndexSettings().getSettings(), Settings.builder().putList("index.query.default_field", - STRING_FIELD_NAME, STRING_FIELD_NAME_2 + "^5").build()) - ); + // default value `*` sets leniency to true Query query = new SimpleQueryStringBuilder("hello") .toQuery(context); - Query expected = new DisjunctionMaxQuery( - Arrays.asList( - new TermQuery(new Term(STRING_FIELD_NAME, "hello")), - new BoostQuery(new TermQuery(new Term(STRING_FIELD_NAME_2, "hello")), 5.0f) - ), 1.0f - ); - assertEquals(expected, query); - // Reset the default value - context.getIndexSettings().updateIndexMetaData( - newIndexMeta("index", - context.getIndexSettings().getSettings(), Settings.builder().putList("index.query.default_field", "*").build()) - ); + assertQueryWithAllFieldsWildcard(query); + + try { + // `*` is in the list of the default_field => leniency set to true + context.getIndexSettings().updateIndexMetaData( + newIndexMeta("index", context.getIndexSettings().getSettings(), Settings.builder().putList("index.query.default_field", + STRING_FIELD_NAME, "*", STRING_FIELD_NAME_2).build()) + ); + query = new SimpleQueryStringBuilder("hello") + .toQuery(context); + assertQueryWithAllFieldsWildcard(query); + + context.getIndexSettings().updateIndexMetaData( + newIndexMeta("index", context.getIndexSettings().getSettings(), Settings.builder().putList("index.query.default_field", + STRING_FIELD_NAME, STRING_FIELD_NAME_2 + "^5").build()) + ); + query = new SimpleQueryStringBuilder("hello") + .toQuery(context); + Query expected = new DisjunctionMaxQuery( + Arrays.asList( + new TermQuery(new Term(STRING_FIELD_NAME, "hello")), + new BoostQuery(new TermQuery(new Term(STRING_FIELD_NAME_2, "hello")), 5.0f) + ), 1.0f + ); + assertEquals(expected, query); + } finally { + // Reset to the default value + context.getIndexSettings().updateIndexMetaData( + newIndexMeta("index", + context.getIndexSettings().getSettings(), Settings.builder().putList("index.query.default_field", "*").build()) + ); + } + } + + public void testAllFieldsWildcard() throws Exception { + QueryShardContext context = createShardContext(); + Query query = new SimpleQueryStringBuilder("hello") + .field("*") + .toQuery(context); + assertQueryWithAllFieldsWildcard(query); + + query = new SimpleQueryStringBuilder("hello") + .field(STRING_FIELD_NAME) + .field("*") + .field(STRING_FIELD_NAME_2) + .toQuery(context); + assertQueryWithAllFieldsWildcard(query); } public void testToFuzzyQuery() throws Exception { @@ -743,4 +776,18 @@ private static IndexMetaData newIndexMeta(String name, Settings oldIndexSettings .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(11, noMatchNoDocsQueries); + assertThat(disjunctionMaxQuery.getDisjuncts(), hasItems(new TermQuery(new Term(STRING_FIELD_NAME, "hello")), + new TermQuery(new Term(STRING_FIELD_NAME_2, "hello")))); + } }