Skip to content

Commit d1394f8

Browse files
authored
Rewrite wrapper queries to match_none if possible. (#55271)
Queries like script_score wrap a query and modify its score. If the inner query rewrites to match_none, then the entire query can rewrite to match_none. This lets us detect that certain shards can be skipped during the 'can match' phase. This was a simple change that seemed like it would help in some cases. But it will likely not have a huge impact, since in many use cases where the 'can match' phase is helpful, the search is not sorted by score.
1 parent cc18caf commit d1394f8

File tree

6 files changed

+67
-8
lines changed

6 files changed

+67
-8
lines changed

server/src/main/java/org/elasticsearch/index/query/BoostingQueryBuilder.java

+4
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ protected boolean doEquals(BoostingQueryBuilder other) {
219219
@Override
220220
protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
221221
QueryBuilder positiveQuery = this.positiveQuery.rewrite(queryRewriteContext);
222+
if (positiveQuery instanceof MatchNoneQueryBuilder) {
223+
return positiveQuery;
224+
}
225+
222226
QueryBuilder negativeQuery = this.negativeQuery.rewrite(queryRewriteContext);
223227
if (positiveQuery != this.positiveQuery || negativeQuery != this.negativeQuery) {
224228
BoostingQueryBuilder newQueryBuilder = new BoostingQueryBuilder(positiveQuery, negativeQuery);

server/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java

+5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.elasticsearch.index.query.AbstractQueryBuilder;
3737
import org.elasticsearch.index.query.InnerHitContextBuilder;
3838
import org.elasticsearch.index.query.MatchAllQueryBuilder;
39+
import org.elasticsearch.index.query.MatchNoneQueryBuilder;
3940
import org.elasticsearch.index.query.QueryBuilder;
4041
import org.elasticsearch.index.query.QueryRewriteContext;
4142
import org.elasticsearch.index.query.QueryShardContext;
@@ -405,6 +406,10 @@ public FilterFunctionBuilder rewrite(QueryRewriteContext context) throws IOExcep
405406
@Override
406407
protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
407408
QueryBuilder queryBuilder = this.query.rewrite(queryRewriteContext);
409+
if (queryBuilder instanceof MatchNoneQueryBuilder) {
410+
return queryBuilder;
411+
}
412+
408413
FilterFunctionBuilder[] rewrittenBuilders = new FilterFunctionBuilder[this.filterFunctionBuilders.length];
409414
boolean rewritten = false;
410415
for (int i = 0; i < rewrittenBuilders.length; i++) {

server/src/main/java/org/elasticsearch/index/query/functionscore/ScriptScoreQueryBuilder.java

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.elasticsearch.common.xcontent.XContentParser;
3232
import org.elasticsearch.index.query.AbstractQueryBuilder;
3333
import org.elasticsearch.index.query.InnerHitContextBuilder;
34+
import org.elasticsearch.index.query.MatchNoneQueryBuilder;
3435
import org.elasticsearch.index.query.QueryBuilder;
3536
import org.elasticsearch.index.query.QueryRewriteContext;
3637
import org.elasticsearch.index.query.QueryShardContext;
@@ -187,6 +188,10 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
187188
@Override
188189
protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
189190
QueryBuilder newQuery = this.query.rewrite(queryRewriteContext);
191+
if (newQuery instanceof MatchNoneQueryBuilder) {
192+
return newQuery;
193+
}
194+
190195
if (newQuery != query) {
191196
ScriptScoreQueryBuilder newQueryBuilder = new ScriptScoreQueryBuilder(newQuery, script);
192197
if (minScore != null) {

server/src/test/java/org/elasticsearch/index/query/BoostingQueryBuilderTests.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.index.query;
2121

2222
import org.apache.lucene.queries.function.FunctionScoreQuery;
23+
import org.apache.lucene.search.MatchNoDocsQuery;
2324
import org.apache.lucene.search.Query;
2425
import org.elasticsearch.test.AbstractQueryTestCase;
2526

@@ -44,6 +45,8 @@ protected void doAssertLuceneQuery(BoostingQueryBuilder queryBuilder, Query quer
4445
Query negative = queryBuilder.negativeQuery().rewrite(context).toQuery(context);
4546
if (positive == null || negative == null) {
4647
assertThat(query, nullValue());
48+
} else if (positive instanceof MatchNoDocsQuery) {
49+
assertThat(query, instanceOf(MatchNoDocsQuery.class));
4750
} else {
4851
assertThat(query, instanceOf(FunctionScoreQuery.class));
4952
}
@@ -91,9 +94,9 @@ public void testFromJson() throws IOException {
9194

9295
public void testRewrite() throws IOException {
9396
QueryBuilder positive = randomBoolean() ? new MatchAllQueryBuilder() :
94-
new WrapperQueryBuilder(new TermQueryBuilder("pos", "bar").toString());
97+
new WrapperQueryBuilder(new TermQueryBuilder(KEYWORD_FIELD_NAME, "bar").toString());
9598
QueryBuilder negative = randomBoolean() ? new MatchAllQueryBuilder() :
96-
new WrapperQueryBuilder(new TermQueryBuilder("neg", "bar").toString());
99+
new WrapperQueryBuilder(new TermQueryBuilder(TEXT_FIELD_NAME, "bar").toString());
97100
BoostingQueryBuilder qb = new BoostingQueryBuilder(positive, negative);
98101
QueryBuilder rewrite = qb.rewrite(createShardContext());
99102
if (positive instanceof MatchAllQueryBuilder && negative instanceof MatchAllQueryBuilder) {
@@ -104,6 +107,14 @@ public void testRewrite() throws IOException {
104107
}
105108
}
106109

110+
public void testRewriteToMatchNone() throws IOException {
111+
BoostingQueryBuilder builder = new BoostingQueryBuilder(
112+
new TermQueryBuilder("unmapped_field", "value"),
113+
new TermQueryBuilder(KEYWORD_FIELD_NAME, "other_value"));
114+
QueryBuilder rewrite = builder.rewrite(createShardContext());
115+
assertThat(rewrite, instanceOf(MatchNoneQueryBuilder.class));
116+
}
117+
107118
@Override
108119
public void testMustRewrite() throws IOException {
109120
QueryShardContext context = createShardContext();

server/src/test/java/org/elasticsearch/index/query/ScriptScoreQueryBuilderTests.java

+18-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.index.query;
2121

22+
import org.apache.lucene.search.MatchNoDocsQuery;
2223
import org.apache.lucene.search.Query;
2324
import org.elasticsearch.ElasticsearchException;
2425
import org.elasticsearch.common.lucene.search.function.ScriptScoreQuery;
@@ -51,7 +52,12 @@ protected ScriptScoreQueryBuilder doCreateTestQueryBuilder() {
5152

5253
@Override
5354
protected void doAssertLuceneQuery(ScriptScoreQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
54-
assertThat(query, instanceOf(ScriptScoreQuery.class));
55+
Query wrappedQuery = queryBuilder.query().rewrite(context).toQuery(context);
56+
if (wrappedQuery instanceof MatchNoDocsQuery) {
57+
assertThat(query, instanceOf(MatchNoDocsQuery.class));
58+
} else {
59+
assertThat(query, instanceOf(ScriptScoreQuery.class));
60+
}
5561
}
5662

5763
public void testFromJson() throws IOException {
@@ -92,7 +98,10 @@ public void testIllegalArguments() {
9298
*/
9399
@Override
94100
public void testCacheability() throws IOException {
95-
ScriptScoreQueryBuilder queryBuilder = createTestQueryBuilder();
101+
Script script = new Script(ScriptType.INLINE, MockScriptEngine.NAME, "1", Collections.emptyMap());
102+
ScriptScoreQueryBuilder queryBuilder = new ScriptScoreQueryBuilder(
103+
new TermQueryBuilder(KEYWORD_FIELD_NAME, "value"), script);
104+
96105
QueryShardContext context = createShardContext();
97106
QueryBuilder rewriteQuery = rewriteQuery(queryBuilder, new QueryShardContext(context));
98107
assertNotNull(rewriteQuery.toQuery(context));
@@ -112,6 +121,13 @@ public void testMustRewrite() throws IOException {
112121
assertEquals("Rewrite first", e.getMessage());
113122
}
114123

124+
public void testRewriteToMatchNone() throws IOException {
125+
Script script = new Script(ScriptType.INLINE, MockScriptEngine.NAME, "1", Collections.emptyMap());
126+
ScriptScoreQueryBuilder builder = new ScriptScoreQueryBuilder(new TermQueryBuilder("unmapped_field", "value"), script);
127+
QueryBuilder rewrite = builder.rewrite(createShardContext());
128+
assertThat(rewrite, instanceOf(MatchNoneQueryBuilder.class));
129+
}
130+
115131
public void testDisallowExpensiveQueries() {
116132
QueryShardContext queryShardContext = mock(QueryShardContext.class);
117133
when(queryShardContext.allowExpensiveQueries()).thenReturn(false);

server/src/test/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java

+22-4
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
package org.elasticsearch.index.query.functionscore;
2121

2222
import com.fasterxml.jackson.core.JsonParseException;
23-
2423
import org.apache.lucene.index.Term;
2524
import org.apache.lucene.search.MatchAllDocsQuery;
25+
import org.apache.lucene.search.MatchNoDocsQuery;
2626
import org.apache.lucene.search.Query;
2727
import org.apache.lucene.search.TermQuery;
2828
import org.elasticsearch.common.ParsingException;
@@ -40,6 +40,7 @@
4040
import org.elasticsearch.common.xcontent.XContentType;
4141
import org.elasticsearch.index.mapper.SeqNoFieldMapper;
4242
import org.elasticsearch.index.query.MatchAllQueryBuilder;
43+
import org.elasticsearch.index.query.MatchNoneQueryBuilder;
4344
import org.elasticsearch.index.query.QueryBuilder;
4445
import org.elasticsearch.index.query.QueryShardContext;
4546
import org.elasticsearch.index.query.RandomQueryBuilder;
@@ -54,6 +55,7 @@
5455
import org.elasticsearch.search.MultiValueMode;
5556
import org.elasticsearch.test.AbstractQueryTestCase;
5657
import org.elasticsearch.test.TestGeoShapeFieldMapperPlugin;
58+
import org.hamcrest.CoreMatchers;
5759
import org.hamcrest.Matcher;
5860
import org.joda.time.DateTime;
5961
import org.joda.time.DateTimeZone;
@@ -77,7 +79,6 @@
7779
import static org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders.weightFactorFunction;
7880
import static org.hamcrest.Matchers.closeTo;
7981
import static org.hamcrest.Matchers.containsString;
80-
import static org.hamcrest.Matchers.either;
8182
import static org.hamcrest.Matchers.equalTo;
8283
import static org.hamcrest.Matchers.instanceOf;
8384
import static org.hamcrest.Matchers.nullValue;
@@ -254,7 +255,12 @@ private static DecayFunctionBuilder<?> createRandomDecayFunction() {
254255

255256
@Override
256257
protected void doAssertLuceneQuery(FunctionScoreQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
257-
assertThat(query, either(instanceOf(FunctionScoreQuery.class)).or(instanceOf(FunctionScoreQuery.class)));
258+
Query wrappedQuery = queryBuilder.query().rewrite(context).toQuery(context);
259+
if (wrappedQuery instanceof MatchNoDocsQuery) {
260+
assertThat(query, CoreMatchers.instanceOf(MatchNoDocsQuery.class));
261+
} else {
262+
assertThat(query, CoreMatchers.instanceOf(FunctionScoreQuery.class));
263+
}
258264
}
259265

260266
public void testIllegalArguments() {
@@ -674,11 +680,23 @@ public void testRewriteWithFunction() throws IOException {
674680
assertSame(rewrite.filterFunctionBuilders()[1].getFilter(), secondFunction);
675681
}
676682

683+
public void testRewriteToMatchNone() throws IOException {
684+
FunctionScoreQueryBuilder functionScoreQueryBuilder =
685+
new FunctionScoreQueryBuilder(new TermQueryBuilder("unmapped_field", "value"))
686+
.boostMode(CombineFunction.REPLACE)
687+
.scoreMode(FunctionScoreQuery.ScoreMode.SUM)
688+
.setMinScore(1)
689+
.maxBoost(100);
690+
691+
QueryBuilder rewrite = functionScoreQueryBuilder.rewrite(createShardContext());
692+
assertThat(rewrite, instanceOf(MatchNoneQueryBuilder.class));
693+
}
694+
677695
/**
678696
* Please see https://github.com/elastic/elasticsearch/issues/35123 for context.
679697
*/
680698
public void testSingleScriptFunction() throws IOException {
681-
QueryBuilder queryBuilder = RandomQueryBuilder.createQuery(random());
699+
QueryBuilder queryBuilder = termQuery(KEYWORD_FIELD_NAME, "value");
682700
ScoreFunctionBuilder<ScriptScoreFunctionBuilder> functionBuilder = new ScriptScoreFunctionBuilder(
683701
new Script(ScriptType.INLINE, MockScriptEngine.NAME, "1", Collections.emptyMap()));
684702

0 commit comments

Comments
 (0)