Allow fields to be set to * (#42301)

Allow for SimpleQueryString, QueryString and MultiMatchQuery
to set the `fields` parameter to the wildcard `*`. If so, set
the leniency to `true`, to achieve the same behaviour as from the
`"default_field" : "*" setting.

Furthermore,  check if `*` is in the list of the `default_field` but
not necessarily as the 1st element.

Closes: #39577
(cherry picked from commit e75ff0c748e6b68232c2b08e19ac4a4934918264)
This commit is contained in:
Marios Trivyzas 2019-05-23 10:10:07 +02:00
parent fa98cbe320
commit 0777223bab
No known key found for this signature in database
GPG Key ID: 8817B46B0CF36A3F
7 changed files with 224 additions and 76 deletions

View File

@ -29,7 +29,6 @@ import org.elasticsearch.common.Strings;
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 @@ public class MultiMatchQueryBuilder extends AbstractQueryBuilder<MultiMatchQuery
multiMatchQuery.setTranspositions(fuzzyTranspositions);
Map<String, Float> newFieldsBoosts;
boolean isAllField;
if (fieldsBoosts.isEmpty()) {
// no fields provided, defaults to index.query.default_field
List<String> 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);
}

View File

@ -852,11 +852,14 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
}
} else if (fieldsAndWeights.size() > 0) {
final Map<String, Float> 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<String> 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<String, Float> resolvedFields = QueryParserHelper.resolveMappingFields(context,

View File

@ -29,7 +29,6 @@ import org.elasticsearch.common.Strings;
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 class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
protected Query doToQuery(QueryShardContext context) throws IOException {
Settings newSettings = new Settings(settings);
final Map<String, Float> resolvedFieldsAndWeights;
boolean isAllField;
if (fieldsAndWeights.isEmpty() == false) {
resolvedFieldsAndWeights = QueryParserHelper.resolveMappingFields(context, fieldsAndWeights);
isAllField = QueryParserHelper.hasAllFieldsWildcard(fieldsAndWeights.keySet());
} else {
List<String> 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;

View File

@ -161,4 +161,12 @@ public final class QueryParserHelper {
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<String> fields) {
return fields.stream().anyMatch(Regex::isMatchAllPattern);
}
}

View File

@ -55,6 +55,7 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertDisj
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 class MultiMatchQueryBuilderTests extends FullTextQueryTestCase<MultiMatc
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));
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())
);
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);
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")
.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\""));
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);
// 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);
context.getIndexSettings().updateIndexMetaData(
newIndexMeta("index", context.getIndexSettings().getSettings(),
Settings.builder().putNull("index.query.default_field").build())
);
} finally {
// Reset to the default value
context.getIndexSettings().updateIndexMetaData(
newIndexMeta("index", context.getIndexSettings().getSettings(),
Settings.builder().putNull("index.query.default_field").build())
);
}
}
public void testAllFieldsWildcard() throws Exception {
QueryShardContext context = createShardContext();
Query query = new MultiMatchQueryBuilder("hello")
.field("*")
.toQuery(context);
assertQueryWithAllFieldsWildcard(query);
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 @@ public class MultiMatchQueryBuilderTests extends FullTextQueryTestCase<MultiMatc
.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"))));
}
}

View File

@ -80,6 +80,7 @@ import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
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 class QueryStringQueryBuilderTests extends FullTextQueryTestCase<QueryStr
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 class QueryStringQueryBuilderTests extends FullTextQueryTestCase<QueryStr
}
}
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 @@ public class QueryStringQueryBuilderTests extends FullTextQueryTestCase<QueryStr
.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"))));
}
}

View File

@ -57,6 +57,7 @@ import java.util.Map;
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 class SimpleQueryStringBuilderTests extends FullTextQueryTestCase<SimpleQ
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 @@ public class SimpleQueryStringBuilderTests extends FullTextQueryTestCase<SimpleQ
.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"))));
}
}