Add `zero_terms_query` support to `match_phrase_prefix` (#58822) (#59784)

Currently `match_phrase_prefix` doesn't support `zero_terms_query` like the
other match-type queries. This change adds this support.

Closes #58468
This commit is contained in:
Christoph Büscher 2020-07-17 17:23:23 +02:00 committed by GitHub
parent 6ccf3548e7
commit f4ff5fe93b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 69 additions and 2 deletions

View File

@ -21,6 +21,7 @@ package org.elasticsearch.index.query;
import org.apache.lucene.search.FuzzyQuery; import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
@ -28,6 +29,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.search.MatchQuery; import org.elasticsearch.index.search.MatchQuery;
import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
@ -39,6 +41,7 @@ import java.util.Objects;
public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhrasePrefixQueryBuilder> { public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhrasePrefixQueryBuilder> {
public static final String NAME = "match_phrase_prefix"; public static final String NAME = "match_phrase_prefix";
public static final ParseField MAX_EXPANSIONS_FIELD = new ParseField("max_expansions"); public static final ParseField MAX_EXPANSIONS_FIELD = new ParseField("max_expansions");
public static final ParseField ZERO_TERMS_QUERY_FIELD = new ParseField("zero_terms_query");
private final String fieldName; private final String fieldName;
@ -50,6 +53,8 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
private int maxExpansions = FuzzyQuery.defaultMaxExpansions; private int maxExpansions = FuzzyQuery.defaultMaxExpansions;
private ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY;
public MatchPhrasePrefixQueryBuilder(String fieldName, Object value) { public MatchPhrasePrefixQueryBuilder(String fieldName, Object value) {
if (fieldName == null) { if (fieldName == null) {
throw new IllegalArgumentException("[" + NAME + "] requires fieldName"); throw new IllegalArgumentException("[" + NAME + "] requires fieldName");
@ -71,6 +76,9 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
slop = in.readVInt(); slop = in.readVInt();
maxExpansions = in.readVInt(); maxExpansions = in.readVInt();
analyzer = in.readOptionalString(); analyzer = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_7_10_0)) {
this.zeroTermsQuery = ZeroTermsQuery.readFromStream(in);
}
} }
@Override @Override
@ -80,6 +88,9 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
out.writeVInt(slop); out.writeVInt(slop);
out.writeVInt(maxExpansions); out.writeVInt(maxExpansions);
out.writeOptionalString(analyzer); out.writeOptionalString(analyzer);
if (out.getVersion().onOrAfter(Version.V_7_10_0)) {
zeroTermsQuery.writeTo(out);
}
} }
/** Returns the field name used in this query. */ /** Returns the field name used in this query. */
@ -139,6 +150,23 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
return this.maxExpansions; return this.maxExpansions;
} }
/**
* Sets query to use in case no query terms are available, e.g. after analysis removed them.
* Defaults to {@link ZeroTermsQuery#NONE}, but can be set to
* {@link ZeroTermsQuery#ALL} instead.
*/
public MatchPhrasePrefixQueryBuilder zeroTermsQuery(ZeroTermsQuery zeroTermsQuery) {
if (zeroTermsQuery == null) {
throw new IllegalArgumentException("[" + NAME + "] requires zeroTermsQuery to be non-null");
}
this.zeroTermsQuery = zeroTermsQuery;
return this;
}
public ZeroTermsQuery zeroTermsQuery() {
return this.zeroTermsQuery;
}
@Override @Override
public String getWriteableName() { public String getWriteableName() {
return NAME; return NAME;
@ -155,6 +183,7 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
} }
builder.field(MatchPhraseQueryBuilder.SLOP_FIELD.getPreferredName(), slop); builder.field(MatchPhraseQueryBuilder.SLOP_FIELD.getPreferredName(), slop);
builder.field(MAX_EXPANSIONS_FIELD.getPreferredName(), maxExpansions); builder.field(MAX_EXPANSIONS_FIELD.getPreferredName(), maxExpansions);
builder.field(ZERO_TERMS_QUERY_FIELD.getPreferredName(), zeroTermsQuery.toString());
printBoostAndQueryName(builder); printBoostAndQueryName(builder);
builder.endObject(); builder.endObject();
builder.endObject(); builder.endObject();
@ -173,6 +202,7 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
} }
matchQuery.setPhraseSlop(slop); matchQuery.setPhraseSlop(slop);
matchQuery.setMaxExpansions(maxExpansions); matchQuery.setMaxExpansions(maxExpansions);
matchQuery.setZeroTermsQuery(zeroTermsQuery);
return matchQuery.parse(MatchQuery.Type.PHRASE_PREFIX, fieldName, value); return matchQuery.parse(MatchQuery.Type.PHRASE_PREFIX, fieldName, value);
} }
@ -183,12 +213,13 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
Objects.equals(value, other.value) && Objects.equals(value, other.value) &&
Objects.equals(analyzer, other.analyzer)&& Objects.equals(analyzer, other.analyzer)&&
Objects.equals(slop, other.slop) && Objects.equals(slop, other.slop) &&
Objects.equals(maxExpansions, other.maxExpansions); Objects.equals(maxExpansions, other.maxExpansions) &&
Objects.equals(zeroTermsQuery, other.zeroTermsQuery);
} }
@Override @Override
protected int doHashCode() { protected int doHashCode() {
return Objects.hash(fieldName, value, analyzer, slop, maxExpansions); return Objects.hash(fieldName, value, analyzer, slop, maxExpansions, zeroTermsQuery);
} }
public static MatchPhrasePrefixQueryBuilder fromXContent(XContentParser parser) throws IOException { public static MatchPhrasePrefixQueryBuilder fromXContent(XContentParser parser) throws IOException {
@ -201,6 +232,7 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
String queryName = null; String queryName = null;
XContentParser.Token token; XContentParser.Token token;
String currentFieldName = null; String currentFieldName = null;
ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
@ -223,6 +255,16 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
maxExpansion = parser.intValue(); maxExpansion = parser.intValue();
} else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { } else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
queryName = parser.text(); queryName = parser.text();
} else if (ZERO_TERMS_QUERY_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
String zeroTermsValue = parser.text();
if ("none".equalsIgnoreCase(zeroTermsValue)) {
zeroTermsQuery = ZeroTermsQuery.NONE;
} else if ("all".equalsIgnoreCase(zeroTermsValue)) {
zeroTermsQuery = ZeroTermsQuery.ALL;
} else {
throw new ParsingException(parser.getTokenLocation(),
"Unsupported zero_terms_query value [" + zeroTermsValue + "]");
}
} else { } else {
throw new ParsingException(parser.getTokenLocation(), throw new ParsingException(parser.getTokenLocation(),
"[" + NAME + "] query does not support [" + currentFieldName + "]"); "[" + NAME + "] query does not support [" + currentFieldName + "]");
@ -245,6 +287,7 @@ public class MatchPhrasePrefixQueryBuilder extends AbstractQueryBuilder<MatchPhr
matchQuery.maxExpansions(maxExpansion); matchQuery.maxExpansions(maxExpansion);
matchQuery.queryName(queryName); matchQuery.queryName(queryName);
matchQuery.boost(boost); matchQuery.boost(boost);
matchQuery.zeroTermsQuery(zeroTermsQuery);
return matchQuery; return matchQuery;
} }
} }

View File

@ -19,11 +19,13 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.search.SynonymQuery; import org.apache.lucene.search.SynonymQuery;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery; import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery;
import org.elasticsearch.test.AbstractQueryTestCase; import org.elasticsearch.test.AbstractQueryTestCase;
import java.io.IOException; import java.io.IOException;
@ -33,6 +35,7 @@ import java.util.Map;
import static org.hamcrest.CoreMatchers.either; import static org.hamcrest.CoreMatchers.either;
import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
public class MatchPhrasePrefixQueryBuilderTests extends AbstractQueryTestCase<MatchPhrasePrefixQueryBuilder> { public class MatchPhrasePrefixQueryBuilderTests extends AbstractQueryTestCase<MatchPhrasePrefixQueryBuilder> {
@ -64,6 +67,9 @@ public class MatchPhrasePrefixQueryBuilderTests extends AbstractQueryTestCase<Ma
if (randomBoolean()) { if (randomBoolean()) {
matchQuery.maxExpansions(randomIntBetween(1, 10000)); matchQuery.maxExpansions(randomIntBetween(1, 10000));
} }
if (randomBoolean()) {
matchQuery.zeroTermsQuery(randomFrom(ZeroTermsQuery.ALL, ZeroTermsQuery.NONE));
}
return matchQuery; return matchQuery;
} }
@ -85,6 +91,12 @@ public class MatchPhrasePrefixQueryBuilderTests extends AbstractQueryTestCase<Ma
protected void doAssertLuceneQuery(MatchPhrasePrefixQueryBuilder queryBuilder, Query query, QueryShardContext context) protected void doAssertLuceneQuery(MatchPhrasePrefixQueryBuilder queryBuilder, Query query, QueryShardContext context)
throws IOException { throws IOException {
assertThat(query, notNullValue()); assertThat(query, notNullValue());
if (query instanceof MatchAllDocsQuery) {
assertThat(queryBuilder.zeroTermsQuery(), equalTo(ZeroTermsQuery.ALL));
return;
}
assertThat(query, either(instanceOf(MultiPhrasePrefixQuery.class)) assertThat(query, either(instanceOf(MultiPhrasePrefixQuery.class))
.or(instanceOf(SynonymQuery.class)) .or(instanceOf(SynonymQuery.class))
.or(instanceOf(MatchNoDocsQuery.class))); .or(instanceOf(MatchNoDocsQuery.class)));
@ -115,6 +127,16 @@ public class MatchPhrasePrefixQueryBuilderTests extends AbstractQueryTestCase<Ma
expectThrows(IllegalStateException.class, () -> matchQuery.doToQuery(createShardContext())); expectThrows(IllegalStateException.class, () -> matchQuery.doToQuery(createShardContext()));
} }
public void testPhrasePrefixZeroTermsQuery() throws IOException {
MatchPhrasePrefixQueryBuilder matchQuery = new MatchPhrasePrefixQueryBuilder(TEXT_FIELD_NAME, "");
matchQuery.zeroTermsQuery(ZeroTermsQuery.NONE);
assertEquals(new MatchNoDocsQuery(), matchQuery.doToQuery(createShardContext()));
matchQuery = new MatchPhrasePrefixQueryBuilder(TEXT_FIELD_NAME, "");
matchQuery.zeroTermsQuery(ZeroTermsQuery.ALL);
assertEquals(new MatchAllDocsQuery(), matchQuery.doToQuery(createShardContext()));
}
public void testPhrasePrefixMatchQuery() throws IOException { public void testPhrasePrefixMatchQuery() throws IOException {
String json1 = "{\n" + String json1 = "{\n" +
" \"match_phrase_prefix\" : {\n" + " \"match_phrase_prefix\" : {\n" +
@ -128,6 +150,7 @@ public class MatchPhrasePrefixQueryBuilderTests extends AbstractQueryTestCase<Ma
" \"query\" : \"this is a test\",\n" + " \"query\" : \"this is a test\",\n" +
" \"slop\" : 0,\n" + " \"slop\" : 0,\n" +
" \"max_expansions\" : 50,\n" + " \"max_expansions\" : 50,\n" +
" \"zero_terms_query\" : \"NONE\",\n" +
" \"boost\" : 1.0\n" + " \"boost\" : 1.0\n" +
" }\n" + " }\n" +
" }\n" + " }\n" +
@ -149,6 +172,7 @@ public class MatchPhrasePrefixQueryBuilderTests extends AbstractQueryTestCase<Ma
" \"query\" : \"this is a test\",\n" + " \"query\" : \"this is a test\",\n" +
" \"slop\" : 0,\n" + " \"slop\" : 0,\n" +
" \"max_expansions\" : 10,\n" + " \"max_expansions\" : 10,\n" +
" \"zero_terms_query\" : \"NONE\",\n" +
" \"boost\" : 1.0\n" + " \"boost\" : 1.0\n" +
" }\n" + " }\n" +
" }\n" + " }\n" +