Add support to match_phrase query for zero_terms_query. (#29598)

This commit is contained in:
Julie Tibshirani 2018-04-19 11:25:27 -07:00 committed by GitHub
parent 00d88a5d3e
commit b9e1a00213
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 162 additions and 3 deletions

View File

@ -39,3 +39,5 @@ GET /_search
}
--------------------------------------------------
// CONSOLE
This query also accepts `zero_terms_query`, as explained in <<query-dsl-match-query, `match` query>>.

View File

@ -20,6 +20,7 @@
package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings;
@ -28,6 +29,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.search.MatchQuery;
import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery;
import java.io.IOException;
import java.util.Objects;
@ -39,6 +41,7 @@ import java.util.Objects;
public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQueryBuilder> {
public static final String NAME = "match_phrase";
public static final ParseField SLOP_FIELD = new ParseField("slop");
public static final ParseField ZERO_TERMS_QUERY_FIELD = new ParseField("zero_terms_query");
private final String fieldName;
@ -48,6 +51,8 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQue
private int slop = MatchQuery.DEFAULT_PHRASE_SLOP;
private ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY;
public MatchPhraseQueryBuilder(String fieldName, Object value) {
if (Strings.isEmpty(fieldName)) {
throw new IllegalArgumentException("[" + NAME + "] requires fieldName");
@ -67,6 +72,9 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQue
fieldName = in.readString();
value = in.readGenericValue();
slop = in.readVInt();
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
zeroTermsQuery = ZeroTermsQuery.readFromStream(in);
}
analyzer = in.readOptionalString();
}
@ -75,6 +83,9 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQue
out.writeString(fieldName);
out.writeGenericValue(value);
out.writeVInt(slop);
if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
zeroTermsQuery.writeTo(out);
}
out.writeOptionalString(analyzer);
}
@ -116,6 +127,23 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQue
return this.slop;
}
/**
* 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 MatchPhraseQueryBuilder 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
public String getWriteableName() {
return NAME;
@ -131,6 +159,7 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQue
builder.field(MatchQueryBuilder.ANALYZER_FIELD.getPreferredName(), analyzer);
}
builder.field(SLOP_FIELD.getPreferredName(), slop);
builder.field(ZERO_TERMS_QUERY_FIELD.getPreferredName(), zeroTermsQuery.toString());
printBoostAndQueryName(builder);
builder.endObject();
builder.endObject();
@ -148,14 +177,18 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQue
matchQuery.setAnalyzer(analyzer);
}
matchQuery.setPhraseSlop(slop);
matchQuery.setZeroTermsQuery(zeroTermsQuery);
return matchQuery.parse(MatchQuery.Type.PHRASE, fieldName, value);
}
@Override
protected boolean doEquals(MatchPhraseQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName) && Objects.equals(value, other.value) && Objects.equals(analyzer, other.analyzer)
&& Objects.equals(slop, other.slop);
return Objects.equals(fieldName, other.fieldName)
&& Objects.equals(value, other.value)
&& Objects.equals(analyzer, other.analyzer)
&& Objects.equals(slop, other.slop)
&& Objects.equals(zeroTermsQuery, other.zeroTermsQuery);
}
@Override
@ -169,6 +202,7 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQue
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String analyzer = null;
int slop = MatchQuery.DEFAULT_PHRASE_SLOP;
ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY;
String queryName = null;
String currentFieldName = null;
XContentParser.Token token;
@ -192,6 +226,16 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQue
slop = parser.intValue();
} else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
queryName = parser.text();
} else if (ZERO_TERMS_QUERY_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
String zeroTermsDocs = parser.text();
if ("none".equalsIgnoreCase(zeroTermsDocs)) {
zeroTermsQuery = ZeroTermsQuery.NONE;
} else if ("all".equalsIgnoreCase(zeroTermsDocs)) {
zeroTermsQuery = ZeroTermsQuery.ALL;
} else {
throw new ParsingException(parser.getTokenLocation(),
"Unsupported zero_terms_docs value [" + zeroTermsDocs + "]");
}
} else {
throw new ParsingException(parser.getTokenLocation(),
"[" + NAME + "] query does not support [" + currentFieldName + "]");
@ -211,6 +255,7 @@ public class MatchPhraseQueryBuilder extends AbstractQueryBuilder<MatchPhraseQue
MatchPhraseQueryBuilder matchQuery = new MatchPhraseQueryBuilder(fieldName, value);
matchQuery.analyzer(analyzer);
matchQuery.slop(slop);
matchQuery.zeroTermsQuery(zeroTermsQuery);
matchQuery.queryName(queryName);
matchQuery.boost(boost);
return matchQuery;

View File

@ -21,12 +21,14 @@ package org.elasticsearch.index.query;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexOrDocValuesQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.PointRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.test.AbstractQueryTestCase;
@ -37,6 +39,7 @@ import java.util.Map;
import static org.hamcrest.CoreMatchers.either;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
public class MatchPhraseQueryBuilderTests extends AbstractQueryTestCase<MatchPhraseQueryBuilder> {
@ -68,6 +71,11 @@ public class MatchPhraseQueryBuilderTests extends AbstractQueryTestCase<MatchPhr
if (randomBoolean()) {
matchQuery.slop(randomIntBetween(0, 10));
}
if (randomBoolean()) {
matchQuery.zeroTermsQuery(randomFrom(ZeroTermsQuery.ALL, ZeroTermsQuery.NONE));
}
return matchQuery;
}
@ -88,6 +96,12 @@ public class MatchPhraseQueryBuilderTests extends AbstractQueryTestCase<MatchPhr
@Override
protected void doAssertLuceneQuery(MatchPhraseQueryBuilder queryBuilder, Query query, SearchContext context) throws IOException {
assertThat(query, notNullValue());
if (query instanceof MatchAllDocsQuery) {
assertThat(queryBuilder.zeroTermsQuery(), equalTo(ZeroTermsQuery.ALL));
return;
}
assertThat(query, either(instanceOf(BooleanQuery.class)).or(instanceOf(PhraseQuery.class))
.or(instanceOf(TermQuery.class)).or(instanceOf(PointRangeQuery.class))
.or(instanceOf(IndexOrDocValuesQuery.class)).or(instanceOf(MatchNoDocsQuery.class)));
@ -108,7 +122,7 @@ public class MatchPhraseQueryBuilderTests extends AbstractQueryTestCase<MatchPhr
assertThat(e.getMessage(), containsString("analyzer [bogusAnalyzer] not found"));
}
public void testPhraseMatchQuery() throws IOException {
public void testFromSimpleJson() throws IOException {
String json1 = "{\n" +
" \"match_phrase\" : {\n" +
" \"message\" : \"this is a test\"\n" +
@ -120,6 +134,7 @@ public class MatchPhraseQueryBuilderTests extends AbstractQueryTestCase<MatchPhr
" \"message\" : {\n" +
" \"query\" : \"this is a test\",\n" +
" \"slop\" : 0,\n" +
" \"zero_terms_query\" : \"NONE\",\n" +
" \"boost\" : 1.0\n" +
" }\n" +
" }\n" +
@ -128,6 +143,26 @@ public class MatchPhraseQueryBuilderTests extends AbstractQueryTestCase<MatchPhr
checkGeneratedJson(expected, qb);
}
public void testFromJson() throws IOException {
String json = "{\n" +
" \"match_phrase\" : {\n" +
" \"message\" : {\n" +
" \"query\" : \"this is a test\",\n" +
" \"slop\" : 2,\n" +
" \"zero_terms_query\" : \"ALL\",\n" +
" \"boost\" : 1.0\n" +
" }\n" +
" }\n" +
"}";
MatchPhraseQueryBuilder parsed = (MatchPhraseQueryBuilder) parseQuery(json);
checkGeneratedJson(json, parsed);
assertEquals(json, "this is a test", parsed.value());
assertEquals(json, 2, parsed.slop());
assertEquals(json, ZeroTermsQuery.ALL, parsed.zeroTermsQuery());
}
public void testParseFailsWithMultipleFields() throws IOException {
String json = "{\n" +
" \"match_phrase\" : {\n" +

View File

@ -0,0 +1,77 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.index.search;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.MatchPhraseQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery;
import org.elasticsearch.test.ESIntegTestCase;
import org.junit.Before;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
public class MatchPhraseQueryIT extends ESIntegTestCase {
private static final String INDEX = "test";
@Before
public void setUp() throws Exception {
super.setUp();
CreateIndexRequestBuilder createIndexRequest = prepareCreate(INDEX).setSettings(
Settings.builder()
.put(indexSettings())
.put("index.analysis.analyzer.standard_stopwords.type", "standard")
.putList("index.analysis.analyzer.standard_stopwords.stopwords", "of", "the", "who"));
assertAcked(createIndexRequest);
ensureGreen();
}
public void testZeroTermsQuery() throws ExecutionException, InterruptedException {
List<IndexRequestBuilder> indexRequests = getIndexRequests();
indexRandom(true, false, indexRequests);
MatchPhraseQueryBuilder baseQuery = QueryBuilders.matchPhraseQuery("name", "the who")
.analyzer("standard_stopwords");
MatchPhraseQueryBuilder matchNoneQuery = baseQuery.zeroTermsQuery(ZeroTermsQuery.NONE);
SearchResponse matchNoneResponse = client().prepareSearch(INDEX).setQuery(matchNoneQuery).get();
assertHitCount(matchNoneResponse, 0L);
MatchPhraseQueryBuilder matchAllQuery = baseQuery.zeroTermsQuery(ZeroTermsQuery.ALL);
SearchResponse matchAllResponse = client().prepareSearch(INDEX).setQuery(matchAllQuery).get();
assertHitCount(matchAllResponse, 2L);
}
private List<IndexRequestBuilder> getIndexRequests() {
List<IndexRequestBuilder> requests = new ArrayList<>();
requests.add(client().prepareIndex(INDEX, "band").setSource("name", "the beatles"));
requests.add(client().prepareIndex(INDEX, "band").setSource("name", "led zeppelin"));
return requests;
}
}