Skip to content

Commit acbc7b2

Browse files
committed
Merge pull request elastic#11628 from cbuescher/feature/query-refactoring-and
Query refactoring: AndQueryBuilder and Parser
2 parents f3a529e + 6ea0acd commit acbc7b2

File tree

3 files changed

+178
-22
lines changed

3 files changed

+178
-22
lines changed

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

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,24 @@
2121

2222
import com.google.common.collect.Lists;
2323

24+
import org.apache.lucene.search.BooleanClause.Occur;
25+
import org.apache.lucene.search.BooleanQuery;
26+
import org.apache.lucene.search.Query;
27+
import org.elasticsearch.common.io.stream.StreamInput;
28+
import org.elasticsearch.common.io.stream.StreamOutput;
2429
import org.elasticsearch.common.xcontent.XContentBuilder;
2530

2631
import java.io.IOException;
2732
import java.util.ArrayList;
33+
import java.util.List;
34+
import java.util.Objects;
2835

2936
/**
3037
* A filter that matches documents matching boolean combinations of other filters.
3138
* @deprecated Use {@link BoolQueryBuilder} instead
3239
*/
3340
@Deprecated
34-
public class AndQueryBuilder extends QueryBuilder {
41+
public class AndQueryBuilder extends QueryBuilder<AndQueryBuilder> {
3542

3643
public static final String NAME = "and";
3744

@@ -55,6 +62,13 @@ public AndQueryBuilder add(QueryBuilder filterBuilder) {
5562
return this;
5663
}
5764

65+
/**
66+
* @return the list of filters added to "and".
67+
*/
68+
public List<QueryBuilder> filters() {
69+
return this.filters;
70+
}
71+
5872
/**
5973
* Sets the filter name for the filter that can be used when searching for matched_filters per hit.
6074
*/
@@ -63,6 +77,14 @@ public AndQueryBuilder queryName(String queryName) {
6377
return this;
6478
}
6579

80+
/**
81+
* @return the query name.
82+
*/
83+
public Object queryName() {
84+
return this.queryName;
85+
}
86+
87+
6688
@Override
6789
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
6890
builder.startObject(NAME);
@@ -77,8 +99,61 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
7799
builder.endObject();
78100
}
79101

102+
@Override
103+
public Query toQuery(QueryParseContext parseContext) throws QueryParsingException, IOException {
104+
if (filters.isEmpty()) {
105+
// no filters provided, this should be ignored upstream
106+
return null;
107+
}
108+
109+
BooleanQuery query = new BooleanQuery();
110+
for (QueryBuilder f : filters) {
111+
query.add(f.toQuery(parseContext), Occur.MUST);
112+
}
113+
if (queryName != null) {
114+
parseContext.addNamedQuery(queryName, query);
115+
}
116+
return query;
117+
}
118+
80119
@Override
81120
public String queryId() {
82121
return NAME;
83122
}
123+
124+
@Override
125+
public int hashCode() {
126+
return Objects.hash(filters, queryName);
127+
}
128+
129+
@Override
130+
public boolean equals(Object obj) {
131+
if (this == obj) {
132+
return true;
133+
}
134+
if (obj == null || getClass() != obj.getClass()) {
135+
return false;
136+
}
137+
AndQueryBuilder other = (AndQueryBuilder) obj;
138+
return Objects.equals(filters, other.filters) &&
139+
Objects.equals(queryName, other.queryName);
140+
}
141+
142+
@Override
143+
public AndQueryBuilder readFrom(StreamInput in) throws IOException {
144+
AndQueryBuilder andQueryBuilder = new AndQueryBuilder();
145+
List<QueryBuilder> queryBuilders = in.readNamedWritableList();
146+
for (QueryBuilder queryBuilder : queryBuilders) {
147+
andQueryBuilder.add(queryBuilder);
148+
}
149+
andQueryBuilder.queryName = in.readOptionalString();
150+
return andQueryBuilder;
151+
152+
}
153+
154+
@Override
155+
public void writeTo(StreamOutput out) throws IOException {
156+
out.writeNamedWritableList(this.filters);
157+
out.writeOptionalString(queryName);
158+
}
84159
}

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

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@
1919

2020
package org.elasticsearch.index.query;
2121

22-
import org.apache.lucene.search.BooleanClause.Occur;
23-
import org.apache.lucene.search.BooleanQuery;
24-
import org.apache.lucene.search.Query;
2522
import org.elasticsearch.common.inject.Inject;
2623
import org.elasticsearch.common.xcontent.XContentParser;
2724

@@ -34,7 +31,7 @@
3431
*
3532
*/
3633
@Deprecated
37-
public class AndQueryParser extends BaseQueryParserTemp {
34+
public class AndQueryParser extends BaseQueryParser {
3835

3936
@Inject
4037
public AndQueryParser() {
@@ -46,10 +43,10 @@ public String[] names() {
4643
}
4744

4845
@Override
49-
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
46+
public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, QueryParsingException {
5047
XContentParser parser = parseContext.parser();
5148

52-
ArrayList<Query> queries = newArrayList();
49+
ArrayList<QueryBuilder> queries = newArrayList();
5350
boolean queriesFound = false;
5451

5552
String queryName = null;
@@ -58,7 +55,7 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
5855
if (token == XContentParser.Token.START_ARRAY) {
5956
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
6057
queriesFound = true;
61-
Query filter = parseContext.parseInnerFilter();
58+
QueryBuilder filter = parseContext.parseInnerFilterToQueryBuilder();
6259
if (filter != null) {
6360
queries.add(filter);
6461
}
@@ -73,15 +70,15 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
7370
if ("filters".equals(currentFieldName)) {
7471
queriesFound = true;
7572
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
76-
Query filter = parseContext.parseInnerFilter();
73+
QueryBuilder filter = parseContext.parseInnerFilterToQueryBuilder();
7774
if (filter != null) {
7875
queries.add(filter);
7976
}
8077
}
8178
} else {
8279
queriesFound = true;
8380
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
84-
Query filter = parseContext.parseInnerFilter();
81+
QueryBuilder filter = parseContext.parseInnerFilterToQueryBuilder();
8582
if (filter != null) {
8683
queries.add(filter);
8784
}
@@ -101,19 +98,13 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
10198
throw new QueryParsingException(parseContext, "[and] query requires 'filters' to be set on it'");
10299
}
103100

104-
if (queries.isEmpty()) {
105-
// no filters provided, this should be ignored upstream
106-
return null;
101+
AndQueryBuilder andQuery = new AndQueryBuilder();
102+
for (QueryBuilder query : queries) {
103+
andQuery.add(query);
107104
}
108-
109-
BooleanQuery query = new BooleanQuery();
110-
for (Query f : queries) {
111-
query.add(f, Occur.MUST);
112-
}
113-
if (queryName != null) {
114-
parseContext.addNamedQuery(queryName, query);
115-
}
116-
return query;
105+
andQuery.queryName(queryName);
106+
andQuery.validate();
107+
return andQuery;
117108
}
118109

119110
@Override
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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.BooleanQuery;
23+
import org.apache.lucene.search.Query;
24+
import org.apache.lucene.search.BooleanClause.Occur;
25+
import org.elasticsearch.common.xcontent.XContentFactory;
26+
import org.elasticsearch.common.xcontent.XContentParser;
27+
import org.junit.Test;
28+
29+
import java.io.IOException;
30+
31+
import static org.hamcrest.Matchers.equalTo;
32+
33+
@SuppressWarnings("deprecation")
34+
public class AndQueryBuilderTest extends BaseQueryTestCase<AndQueryBuilder> {
35+
36+
@Override
37+
protected Query createExpectedQuery(AndQueryBuilder queryBuilder, QueryParseContext context) throws QueryParsingException, IOException {
38+
if (queryBuilder.filters().isEmpty()) {
39+
return null;
40+
}
41+
BooleanQuery query = new BooleanQuery();
42+
for (QueryBuilder subQuery : queryBuilder.filters()) {
43+
query.add(subQuery.toQuery(context), Occur.MUST);
44+
}
45+
return query;
46+
}
47+
48+
/**
49+
* @return a AndQueryBuilder with random limit between 0 and 20
50+
*/
51+
@Override
52+
protected AndQueryBuilder createTestQueryBuilder() {
53+
AndQueryBuilder query = new AndQueryBuilder();
54+
int subQueries = randomIntBetween(1, 5);
55+
for (int i = 0; i < subQueries; i++ ) {
56+
query.add(RandomQueryBuilder.create(random()));
57+
}
58+
if (randomBoolean()) {
59+
query.queryName(randomAsciiOfLengthBetween(1, 10));
60+
}
61+
return query;
62+
}
63+
64+
@Override
65+
protected void assertLuceneQuery(AndQueryBuilder queryBuilder, Query query, QueryParseContext context) {
66+
if (queryBuilder.queryName() != null) {
67+
Query namedQuery = context.copyNamedFilters().get(queryBuilder.queryName());
68+
assertThat(namedQuery, equalTo(query));
69+
}
70+
}
71+
72+
/**
73+
* test corner case where no inner queries exist
74+
*/
75+
@Test
76+
public void testNoInnerQueries() throws QueryParsingException, IOException {
77+
AndQueryBuilder andQuery = new AndQueryBuilder();
78+
assertNull(andQuery.toQuery(createContext()));
79+
}
80+
81+
@Test(expected=QueryParsingException.class)
82+
public void testMissingFiltersSection() throws IOException {
83+
QueryParseContext context = createContext();
84+
String queryString = "{ \"and\" : {}";
85+
XContentParser parser = XContentFactory.xContent(queryString).createParser(queryString);
86+
context.reset(parser);
87+
assertQueryHeader(parser, AndQueryBuilder.PROTOTYPE.queryId());
88+
context.indexQueryParserService().queryParser(AndQueryBuilder.PROTOTYPE.queryId()).fromXContent(context);
89+
}
90+
}

0 commit comments

Comments
 (0)