Skip to content

Commit 0e219ff

Browse files
committed
Merge pull request elastic#11823 from cbuescher/feature/query-refactoring-not
Query refactoring: NotQueryBuilder and Parser
2 parents f5d4743 + 023d031 commit 0e219ff

File tree

3 files changed

+180
-23
lines changed

3 files changed

+180
-23
lines changed

core/src/main/java/org/elasticsearch/index/query/NotQueryBuilder.java

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919

2020
package org.elasticsearch.index.query;
2121

22+
import org.apache.lucene.search.Query;
23+
import org.elasticsearch.common.io.stream.StreamInput;
24+
import org.elasticsearch.common.io.stream.StreamOutput;
25+
import org.elasticsearch.common.lucene.search.Queries;
2226
import org.elasticsearch.common.xcontent.XContentBuilder;
2327

2428
import java.io.IOException;
@@ -35,24 +39,34 @@ public class NotQueryBuilder extends AbstractQueryBuilder<NotQueryBuilder> {
3539

3640
private String queryName;
3741

38-
static final NotQueryBuilder PROTOTYPE = new NotQueryBuilder();
42+
static final NotQueryBuilder PROTOTYPE = new NotQueryBuilder(null);
3943

4044
public NotQueryBuilder(QueryBuilder filter) {
41-
this.filter = Objects.requireNonNull(filter);
45+
this.filter = filter;
4246
}
4347

4448
/**
45-
* private constructor for internal use
49+
* @return the filter added to "not".
4650
*/
47-
private NotQueryBuilder() {
48-
this.filter = null;
51+
public QueryBuilder filter() {
52+
return this.filter;
4953
}
5054

55+
/**
56+
* Sets the filter name for the filter that can be used when searching for matched_filters per hit.
57+
*/
5158
public NotQueryBuilder queryName(String queryName) {
5259
this.queryName = queryName;
5360
return this;
5461
}
5562

63+
/**
64+
* @return the query name.
65+
*/
66+
public String queryName() {
67+
return this.queryName;
68+
}
69+
5670
@Override
5771
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
5872
builder.startObject(NAME);
@@ -64,8 +78,64 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
6478
builder.endObject();
6579
}
6680

81+
@Override
82+
public Query toQuery(QueryParseContext parseContext) throws QueryParsingException, IOException {
83+
if (filter == null) {
84+
return null;
85+
}
86+
87+
Query luceneQuery = filter.toQuery(parseContext);
88+
if (luceneQuery == null) {
89+
return null;
90+
}
91+
92+
Query notQuery = Queries.not(luceneQuery);
93+
if (queryName != null) {
94+
parseContext.addNamedQuery(queryName, notQuery);
95+
}
96+
return notQuery;
97+
}
98+
99+
@Override
100+
public QueryValidationException validate() {
101+
// nothing to validate.
102+
return null;
103+
}
104+
105+
@Override
106+
public int hashCode() {
107+
return Objects.hash(filter, queryName);
108+
}
109+
110+
@Override
111+
public boolean equals(Object obj) {
112+
if (this == obj) {
113+
return true;
114+
}
115+
if (obj == null || getClass() != obj.getClass()) {
116+
return false;
117+
}
118+
NotQueryBuilder other = (NotQueryBuilder) obj;
119+
return Objects.equals(filter, other.filter) &&
120+
Objects.equals(queryName, other.queryName);
121+
}
122+
123+
@Override
124+
public NotQueryBuilder readFrom(StreamInput in) throws IOException {
125+
QueryBuilder queryBuilder = in.readNamedWriteable();
126+
NotQueryBuilder notQueryBuilder = new NotQueryBuilder(queryBuilder);
127+
notQueryBuilder.queryName = in.readOptionalString();
128+
return notQueryBuilder;
129+
}
130+
131+
@Override
132+
public void writeTo(StreamOutput out) throws IOException {
133+
out.writeNamedWriteable(filter);
134+
out.writeOptionalString(queryName);
135+
}
136+
67137
@Override
68138
public String getName() {
69139
return NAME;
70140
}
71-
}
141+
}

core/src/main/java/org/elasticsearch/index/query/NotQueryParser.java

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,16 @@
1919

2020
package org.elasticsearch.index.query;
2121

22-
import org.apache.lucene.search.Query;
2322
import org.elasticsearch.common.ParseField;
2423
import org.elasticsearch.common.inject.Inject;
25-
import org.elasticsearch.common.lucene.search.Queries;
2624
import org.elasticsearch.common.xcontent.XContentParser;
2725

2826
import java.io.IOException;
2927

3028
/**
3129
*
3230
*/
33-
public class NotQueryParser extends BaseQueryParserTemp {
31+
public class NotQueryParser extends BaseQueryParser {
3432

3533
private static final ParseField QUERY_FIELD = new ParseField("filter", "query");
3634

@@ -44,10 +42,10 @@ public String[] names() {
4442
}
4543

4644
@Override
47-
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
45+
public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, QueryParsingException {
4846
XContentParser parser = parseContext.parser();
4947

50-
Query query = null;
48+
QueryBuilder query = null;
5149
boolean queryFound = false;
5250

5351
String queryName = null;
@@ -60,17 +58,17 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
6058
// skip
6159
} else if (token == XContentParser.Token.START_OBJECT) {
6260
if (QUERY_FIELD.match(currentFieldName)) {
63-
query = parseContext.parseInnerFilter();
61+
query = parseContext.parseInnerFilterToQueryBuilder();
6462
queryFound = true;
6563
} else {
6664
queryFound = true;
6765
// its the filter, and the name is the field
68-
query = parseContext.parseInnerFilter(currentFieldName);
66+
query = parseContext.parseInnerFilterToQueryBuilder(currentFieldName);
6967
}
7068
} else if (token == XContentParser.Token.START_ARRAY) {
7169
queryFound = true;
7270
// its the filter, and the name is the field
73-
query = parseContext.parseInnerFilter(currentFieldName);
71+
query = parseContext.parseInnerFilterToQueryBuilder(currentFieldName);
7472
} else if (token.isValue()) {
7573
if ("_name".equals(currentFieldName)) {
7674
queryName = parser.text();
@@ -84,15 +82,9 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
8482
throw new QueryParsingException(parseContext, "filter is required when using `not` query");
8583
}
8684

87-
if (query == null) {
88-
return null;
89-
}
90-
91-
Query notQuery = Queries.not(query);
92-
if (queryName != null) {
93-
parseContext.addNamedQuery(queryName, notQuery);
94-
}
95-
return notQuery;
85+
NotQueryBuilder notQueryBuilder = new NotQueryBuilder(query);
86+
notQueryBuilder.queryName(queryName);
87+
return notQueryBuilder;
9688
}
9789

9890
@Override
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.index.query;
21+
22+
import org.apache.lucene.search.Query;
23+
import org.elasticsearch.common.lucene.search.Queries;
24+
import org.elasticsearch.common.xcontent.XContentFactory;
25+
import org.elasticsearch.common.xcontent.XContentParser;
26+
import org.junit.Test;
27+
28+
import java.io.IOException;
29+
30+
import static org.hamcrest.Matchers.equalTo;
31+
32+
public class NotQueryBuilderTest extends BaseQueryTestCase<NotQueryBuilder> {
33+
34+
@Override
35+
protected Query createExpectedQuery(NotQueryBuilder queryBuilder, QueryParseContext context) throws QueryParsingException, IOException {
36+
if (queryBuilder.filter() == null) {
37+
return null;
38+
}
39+
return Queries.not(queryBuilder.filter().toQuery(context));
40+
}
41+
42+
/**
43+
* @return a NotQueryBuilder with random limit between 0 and 20
44+
*/
45+
@Override
46+
protected NotQueryBuilder createTestQueryBuilder() {
47+
NotQueryBuilder query = new NotQueryBuilder(RandomQueryBuilder.create(random()));
48+
if (randomBoolean()) {
49+
query.queryName(randomAsciiOfLengthBetween(1, 10));
50+
}
51+
return query;
52+
}
53+
54+
@Override
55+
protected void assertLuceneQuery(NotQueryBuilder queryBuilder, Query query, QueryParseContext context) {
56+
if (queryBuilder.queryName() != null) {
57+
Query namedQuery = context.copyNamedFilters().get(queryBuilder.queryName());
58+
assertThat(namedQuery, equalTo(query));
59+
}
60+
}
61+
62+
/**
63+
* test corner case where no inner query exist
64+
*/
65+
@Test
66+
public void testNoInnerQuerry() throws QueryParsingException, IOException {
67+
NotQueryBuilder notQuery = new NotQueryBuilder(null);
68+
assertNull(notQuery.toQuery(createContext()));
69+
}
70+
71+
/**
72+
* Test corner case where inner queries returns null.
73+
* This is legal, e.g. here {@link ConstantScoreQueryBuilder} can wrap a filter
74+
* element that itself parses to <tt>null</tt>, e.g.
75+
* '{ "constant_score" : "filter {} }'
76+
*/
77+
@Test
78+
public void testInnerQuery() throws QueryParsingException, IOException {
79+
NotQueryBuilder notQuery = new NotQueryBuilder(new ConstantScoreQueryBuilder(null));
80+
assertNull(notQuery.toQuery(createContext()));
81+
}
82+
83+
/**
84+
* @throws IOException
85+
*/
86+
@Test(expected=QueryParsingException.class)
87+
public void testMissingFilterSection() throws IOException {
88+
QueryParseContext context = createContext();
89+
String queryString = "{ \"not\" : {}";
90+
XContentParser parser = XContentFactory.xContent(queryString).createParser(queryString);
91+
context.reset(parser);
92+
assertQueryHeader(parser, NotQueryBuilder.PROTOTYPE.getName());
93+
context.indexQueryParserService().queryParser(NotQueryBuilder.PROTOTYPE.getName()).fromXContent(context);
94+
}
95+
}

0 commit comments

Comments
 (0)