Add support to match_phrase query for zero_terms_query. (#29598)
This commit is contained in:
parent
00d88a5d3e
commit
b9e1a00213
|
@ -39,3 +39,5 @@ GET /_search
|
|||
}
|
||||
--------------------------------------------------
|
||||
// CONSOLE
|
||||
|
||||
This query also accepts `zero_terms_query`, as explained in <<query-dsl-match-query, `match` query>>.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue