Skip to content

Commit 427d7f5

Browse files
author
Christoph Büscher
authored
Add zero_terms_query support to match_phrase_prefix (#58822)
Currently `match_phrase_prefix` doesn't support `zero_terms_query` like the other match-type queries. This change adds this support. Closes #58468
1 parent f72b893 commit 427d7f5

File tree

2 files changed

+69
-2
lines changed

2 files changed

+69
-2
lines changed

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

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121

2222
import org.apache.lucene.search.FuzzyQuery;
2323
import org.apache.lucene.search.Query;
24+
import org.elasticsearch.Version;
2425
import org.elasticsearch.common.ParseField;
2526
import org.elasticsearch.common.ParsingException;
2627
import org.elasticsearch.common.io.stream.StreamInput;
2728
import org.elasticsearch.common.io.stream.StreamOutput;
2829
import org.elasticsearch.common.xcontent.XContentBuilder;
2930
import org.elasticsearch.common.xcontent.XContentParser;
3031
import org.elasticsearch.index.search.MatchQuery;
32+
import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery;
3133

3234
import java.io.IOException;
3335
import java.util.Objects;
@@ -39,6 +41,7 @@
3941
public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhrasePrefixQueryBuilder> {
4042
public static final String NAME = "match_phrase_prefix";
4143
public static final ParseField MAX_EXPANSIONS_FIELD = new ParseField("max_expansions");
44+
public static final ParseField ZERO_TERMS_QUERY_FIELD = new ParseField("zero_terms_query");
4245

4346
private final String fieldName;
4447

@@ -50,6 +53,8 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
5053

5154
private int maxExpansions = FuzzyQuery.defaultMaxExpansions;
5255

56+
private ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY;
57+
5358
public MatchPhrasePrefixQueryBuilder(String fieldName, Object value) {
5459
if (fieldName == null) {
5560
throw new IllegalArgumentException("[" + NAME + "] requires fieldName");
@@ -71,6 +76,9 @@ public MatchPhrasePrefixQueryBuilder(StreamInput in) throws IOException {
7176
slop = in.readVInt();
7277
maxExpansions = in.readVInt();
7378
analyzer = in.readOptionalString();
79+
if (in.getVersion().onOrAfter(Version.V_8_0_0)) {
80+
this.zeroTermsQuery = ZeroTermsQuery.readFromStream(in);
81+
}
7482
}
7583

7684
@Override
@@ -80,6 +88,9 @@ protected void doWriteTo(StreamOutput out) throws IOException {
8088
out.writeVInt(slop);
8189
out.writeVInt(maxExpansions);
8290
out.writeOptionalString(analyzer);
91+
if (out.getVersion().onOrAfter(Version.V_8_0_0)) {
92+
zeroTermsQuery.writeTo(out);
93+
}
8394
}
8495

8596
/** Returns the field name used in this query. */
@@ -139,6 +150,23 @@ public int maxExpansions() {
139150
return this.maxExpansions;
140151
}
141152

153+
/**
154+
* Sets query to use in case no query terms are available, e.g. after analysis removed them.
155+
* Defaults to {@link ZeroTermsQuery#NONE}, but can be set to
156+
* {@link ZeroTermsQuery#ALL} instead.
157+
*/
158+
public MatchPhrasePrefixQueryBuilder zeroTermsQuery(ZeroTermsQuery zeroTermsQuery) {
159+
if (zeroTermsQuery == null) {
160+
throw new IllegalArgumentException("[" + NAME + "] requires zeroTermsQuery to be non-null");
161+
}
162+
this.zeroTermsQuery = zeroTermsQuery;
163+
return this;
164+
}
165+
166+
public ZeroTermsQuery zeroTermsQuery() {
167+
return this.zeroTermsQuery;
168+
}
169+
142170
@Override
143171
public String getWriteableName() {
144172
return NAME;
@@ -155,6 +183,7 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
155183
}
156184
builder.field(MatchPhraseQueryBuilder.SLOP_FIELD.getPreferredName(), slop);
157185
builder.field(MAX_EXPANSIONS_FIELD.getPreferredName(), maxExpansions);
186+
builder.field(ZERO_TERMS_QUERY_FIELD.getPreferredName(), zeroTermsQuery.toString());
158187
printBoostAndQueryName(builder);
159188
builder.endObject();
160189
builder.endObject();
@@ -173,6 +202,7 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
173202
}
174203
matchQuery.setPhraseSlop(slop);
175204
matchQuery.setMaxExpansions(maxExpansions);
205+
matchQuery.setZeroTermsQuery(zeroTermsQuery);
176206

177207
return matchQuery.parse(MatchQuery.Type.PHRASE_PREFIX, fieldName, value);
178208
}
@@ -183,12 +213,13 @@ protected boolean doEquals(MatchPhrasePrefixQueryBuilder other) {
183213
Objects.equals(value, other.value) &&
184214
Objects.equals(analyzer, other.analyzer)&&
185215
Objects.equals(slop, other.slop) &&
186-
Objects.equals(maxExpansions, other.maxExpansions);
216+
Objects.equals(maxExpansions, other.maxExpansions) &&
217+
Objects.equals(zeroTermsQuery, other.zeroTermsQuery);
187218
}
188219

189220
@Override
190221
protected int doHashCode() {
191-
return Objects.hash(fieldName, value, analyzer, slop, maxExpansions);
222+
return Objects.hash(fieldName, value, analyzer, slop, maxExpansions, zeroTermsQuery);
192223
}
193224

194225
public static MatchPhrasePrefixQueryBuilder fromXContent(XContentParser parser) throws IOException {
@@ -201,6 +232,7 @@ public static MatchPhrasePrefixQueryBuilder fromXContent(XContentParser parser)
201232
String queryName = null;
202233
XContentParser.Token token;
203234
String currentFieldName = null;
235+
ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY;
204236
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
205237
if (token == XContentParser.Token.FIELD_NAME) {
206238
currentFieldName = parser.currentName();
@@ -223,6 +255,16 @@ public static MatchPhrasePrefixQueryBuilder fromXContent(XContentParser parser)
223255
maxExpansion = parser.intValue();
224256
} else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
225257
queryName = parser.text();
258+
} else if (ZERO_TERMS_QUERY_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
259+
String zeroTermsValue = parser.text();
260+
if ("none".equalsIgnoreCase(zeroTermsValue)) {
261+
zeroTermsQuery = ZeroTermsQuery.NONE;
262+
} else if ("all".equalsIgnoreCase(zeroTermsValue)) {
263+
zeroTermsQuery = ZeroTermsQuery.ALL;
264+
} else {
265+
throw new ParsingException(parser.getTokenLocation(),
266+
"Unsupported zero_terms_query value [" + zeroTermsValue + "]");
267+
}
226268
} else {
227269
throw new ParsingException(parser.getTokenLocation(),
228270
"[" + NAME + "] query does not support [" + currentFieldName + "]");
@@ -245,6 +287,7 @@ public static MatchPhrasePrefixQueryBuilder fromXContent(XContentParser parser)
245287
matchQuery.maxExpansions(maxExpansion);
246288
matchQuery.queryName(queryName);
247289
matchQuery.boost(boost);
290+
matchQuery.zeroTermsQuery(zeroTermsQuery);
248291
return matchQuery;
249292
}
250293
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919

2020
package org.elasticsearch.index.query;
2121

22+
import org.apache.lucene.search.MatchAllDocsQuery;
2223
import org.apache.lucene.search.MatchNoDocsQuery;
2324
import org.apache.lucene.search.Query;
2425
import org.apache.lucene.search.SynonymQuery;
2526
import org.elasticsearch.common.ParsingException;
2627
import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
28+
import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery;
2729
import org.elasticsearch.test.AbstractQueryTestCase;
2830

2931
import java.io.IOException;
@@ -33,6 +35,7 @@
3335
import static org.hamcrest.CoreMatchers.either;
3436
import static org.hamcrest.CoreMatchers.instanceOf;
3537
import static org.hamcrest.Matchers.containsString;
38+
import static org.hamcrest.Matchers.equalTo;
3639
import static org.hamcrest.Matchers.notNullValue;
3740

3841
public class MatchPhrasePrefixQueryBuilderTests extends AbstractQueryTestCase<MatchPhrasePrefixQueryBuilder> {
@@ -64,6 +67,9 @@ protected MatchPhrasePrefixQueryBuilder doCreateTestQueryBuilder() {
6467
if (randomBoolean()) {
6568
matchQuery.maxExpansions(randomIntBetween(1, 10000));
6669
}
70+
if (randomBoolean()) {
71+
matchQuery.zeroTermsQuery(randomFrom(ZeroTermsQuery.ALL, ZeroTermsQuery.NONE));
72+
}
6773
return matchQuery;
6874
}
6975

@@ -85,6 +91,12 @@ protected Map<String, MatchPhrasePrefixQueryBuilder> getAlternateVersions() {
8591
protected void doAssertLuceneQuery(MatchPhrasePrefixQueryBuilder queryBuilder, Query query, QueryShardContext context)
8692
throws IOException {
8793
assertThat(query, notNullValue());
94+
95+
if (query instanceof MatchAllDocsQuery) {
96+
assertThat(queryBuilder.zeroTermsQuery(), equalTo(ZeroTermsQuery.ALL));
97+
return;
98+
}
99+
88100
assertThat(query, either(instanceOf(MultiPhrasePrefixQuery.class))
89101
.or(instanceOf(SynonymQuery.class))
90102
.or(instanceOf(MatchNoDocsQuery.class)));
@@ -115,6 +127,16 @@ public void testPhraseOnFieldWithNoTerms() {
115127
expectThrows(IllegalStateException.class, () -> matchQuery.doToQuery(createShardContext()));
116128
}
117129

130+
public void testPhrasePrefixZeroTermsQuery() throws IOException {
131+
MatchPhrasePrefixQueryBuilder matchQuery = new MatchPhrasePrefixQueryBuilder(TEXT_FIELD_NAME, "");
132+
matchQuery.zeroTermsQuery(ZeroTermsQuery.NONE);
133+
assertEquals(new MatchNoDocsQuery(), matchQuery.doToQuery(createShardContext()));
134+
135+
matchQuery = new MatchPhrasePrefixQueryBuilder(TEXT_FIELD_NAME, "");
136+
matchQuery.zeroTermsQuery(ZeroTermsQuery.ALL);
137+
assertEquals(new MatchAllDocsQuery(), matchQuery.doToQuery(createShardContext()));
138+
}
139+
118140
public void testPhrasePrefixMatchQuery() throws IOException {
119141
String json1 = "{\n" +
120142
" \"match_phrase_prefix\" : {\n" +
@@ -128,6 +150,7 @@ public void testPhrasePrefixMatchQuery() throws IOException {
128150
" \"query\" : \"this is a test\",\n" +
129151
" \"slop\" : 0,\n" +
130152
" \"max_expansions\" : 50,\n" +
153+
" \"zero_terms_query\" : \"NONE\",\n" +
131154
" \"boost\" : 1.0\n" +
132155
" }\n" +
133156
" }\n" +
@@ -149,6 +172,7 @@ public void testPhrasePrefixMatchQuery() throws IOException {
149172
" \"query\" : \"this is a test\",\n" +
150173
" \"slop\" : 0,\n" +
151174
" \"max_expansions\" : 10,\n" +
175+
" \"zero_terms_query\" : \"NONE\",\n" +
152176
" \"boost\" : 1.0\n" +
153177
" }\n" +
154178
" }\n" +

0 commit comments

Comments
 (0)