Skip to content

Commit a0cccec

Browse files
author
Isabel Drost-Fromm
committed
Merge pull request #11885 from MaineC/feature/filter-query-builder-refactoring
Refactors FilteredQueryBuilder. Relates to #10217
2 parents de277d9 + 0202c99 commit a0cccec

File tree

4 files changed

+221
-64
lines changed

4 files changed

+221
-64
lines changed

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

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

2020
package org.elasticsearch.index.query;
2121

22-
import org.elasticsearch.common.Nullable;
22+
import org.apache.lucene.search.ConstantScoreQuery;
23+
import org.apache.lucene.search.Query;
24+
import org.elasticsearch.common.io.stream.StreamInput;
25+
import org.elasticsearch.common.io.stream.StreamOutput;
26+
import org.elasticsearch.common.lucene.search.Queries;
2327
import org.elasticsearch.common.xcontent.XContentBuilder;
2428

2529
import java.io.IOException;
30+
import java.util.Objects;
2631

2732
/**
2833
* A query that applies a filter to the results of another query.
@@ -31,36 +36,103 @@
3136
@Deprecated
3237
public class FilteredQueryBuilder extends AbstractQueryBuilder<FilteredQueryBuilder> {
3338

39+
/** Name of the query in the REST API. */
3440
public static final String NAME = "filtered";
35-
41+
/** The query to filter. */
3642
private final QueryBuilder queryBuilder;
37-
43+
/** The filter to apply to the query. */
3844
private final QueryBuilder filterBuilder;
3945

4046
static final FilteredQueryBuilder PROTOTYPE = new FilteredQueryBuilder(null, null);
4147

48+
/**
49+
* Returns a {@link MatchAllQueryBuilder} instance that will be used as
50+
* default queryBuilder if none is supplied by the user. Feel free to
51+
* set queryName and boost on that instance - it's always a new one.
52+
* */
53+
private static QueryBuilder generateDefaultQuery() {
54+
return new MatchAllQueryBuilder();
55+
}
56+
57+
/**
58+
* A query that applies a filter to the results of a match_all query.
59+
* @param filterBuilder The filter to apply on the query (Can be null)
60+
* */
61+
public FilteredQueryBuilder(QueryBuilder filterBuilder) {
62+
this(generateDefaultQuery(), filterBuilder);
63+
}
64+
4265
/**
4366
* A query that applies a filter to the results of another query.
4467
*
45-
* @param queryBuilder The query to apply the filter to (Can be null)
68+
* @param queryBuilder The query to apply the filter to
4669
* @param filterBuilder The filter to apply on the query (Can be null)
4770
*/
48-
public FilteredQueryBuilder(@Nullable QueryBuilder queryBuilder, @Nullable QueryBuilder filterBuilder) {
49-
this.queryBuilder = queryBuilder;
50-
this.filterBuilder = filterBuilder;
71+
public FilteredQueryBuilder(QueryBuilder queryBuilder, QueryBuilder filterBuilder) {
72+
this.queryBuilder = (queryBuilder != null) ? queryBuilder : generateDefaultQuery();
73+
this.filterBuilder = (filterBuilder != null) ? filterBuilder : EmptyQueryBuilder.PROTOTYPE;
74+
}
75+
76+
/** Returns the query to apply the filter to. */
77+
public QueryBuilder query() {
78+
return queryBuilder;
79+
}
80+
81+
/** Returns the filter to apply to the query results. */
82+
public QueryBuilder filter() {
83+
return filterBuilder;
5184
}
5285

5386
@Override
54-
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
55-
builder.startObject(NAME);
56-
if (queryBuilder != null) {
57-
builder.field("query");
58-
queryBuilder.toXContent(builder, params);
87+
protected boolean doEquals(FilteredQueryBuilder other) {
88+
return Objects.equals(queryBuilder, other.queryBuilder) &&
89+
Objects.equals(filterBuilder, other.filterBuilder);
90+
}
91+
92+
@Override
93+
public int doHashCode() {
94+
return Objects.hash(queryBuilder, filterBuilder);
95+
}
96+
97+
@Override
98+
public Query doToQuery(QueryParseContext parseContext) throws QueryParsingException, IOException {
99+
Query query = queryBuilder.toQuery(parseContext);
100+
Query filter = filterBuilder.toQuery(parseContext);
101+
102+
if (query == null) {
103+
// Most likely this query was generated from the JSON query DSL - it parsed to an EmptyQueryBuilder so we ignore
104+
// the whole filtered query as there is nothing to filter on. See FilteredQueryParser for an example.
105+
return null;
59106
}
60-
if (filterBuilder != null) {
61-
builder.field("filter");
62-
filterBuilder.toXContent(builder, params);
107+
108+
if (filter == null || Queries.isConstantMatchAllQuery(filter)) {
109+
// no filter, or match all filter
110+
return query;
111+
} else if (Queries.isConstantMatchAllQuery(query)) {
112+
// if its a match_all query, use constant_score
113+
return new ConstantScoreQuery(filter);
63114
}
115+
116+
// use a BooleanQuery
117+
return Queries.filtered(query, filter);
118+
}
119+
120+
@Override
121+
public QueryValidationException validate() {
122+
QueryValidationException validationException = null;
123+
validationException = validateInnerQuery(queryBuilder, validationException);
124+
validationException = validateInnerQuery(filterBuilder, validationException);
125+
return validationException;
126+
127+
}
128+
129+
@Override
130+
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
131+
builder.startObject(NAME);
132+
builder.field("query");
133+
queryBuilder.toXContent(builder, params);
134+
builder.field("filter");
135+
filterBuilder.toXContent(builder, params);
64136
printBoostAndQueryName(builder);
65137
builder.endObject();
66138
}
@@ -69,4 +141,18 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
69141
public String getName() {
70142
return NAME;
71143
}
144+
145+
@Override
146+
public FilteredQueryBuilder doReadFrom(StreamInput in) throws IOException {
147+
QueryBuilder query = in.readNamedWriteable();
148+
QueryBuilder filter = in.readNamedWriteable();
149+
FilteredQueryBuilder qb = new FilteredQueryBuilder(query, filter);
150+
return qb;
151+
}
152+
153+
@Override
154+
public void doWriteTo(StreamOutput out) throws IOException {
155+
out.writeNamedWriteable(queryBuilder);
156+
out.writeNamedWriteable(filterBuilder);
157+
}
72158
}

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

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,17 @@
1919

2020
package org.elasticsearch.index.query;
2121

22-
import org.apache.lucene.search.BooleanQuery;
23-
import org.apache.lucene.search.ConstantScoreQuery;
24-
import org.apache.lucene.search.Query;
2522
import org.elasticsearch.common.inject.Inject;
26-
import org.elasticsearch.common.lucene.search.Queries;
2723
import org.elasticsearch.common.xcontent.XContentParser;
2824

2925
import java.io.IOException;
3026

3127
/**
32-
*
28+
* @deprecated Use {@link BoolQueryParser} instead.
3329
*/
30+
3431
@Deprecated
35-
public class FilteredQueryParser extends BaseQueryParserTemp {
32+
public class FilteredQueryParser extends BaseQueryParser {
3633

3734
@Inject
3835
public FilteredQueryParser() {
@@ -44,12 +41,11 @@ public String[] names() {
4441
}
4542

4643
@Override
47-
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
44+
public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, QueryParsingException {
4845
XContentParser parser = parseContext.parser();
4946

50-
Query query = Queries.newMatchAllQuery();
51-
Query filter = null;
52-
boolean filterFound = false;
47+
QueryBuilder query = null;
48+
QueryBuilder filter = null;
5349
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
5450
String queryName = null;
5551

@@ -63,10 +59,9 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
6359
// skip
6460
} else if (token == XContentParser.Token.START_OBJECT) {
6561
if ("query".equals(currentFieldName)) {
66-
query = parseContext.parseInnerQuery();
62+
query = parseContext.parseInnerQueryBuilder();
6763
} else if ("filter".equals(currentFieldName)) {
68-
filterFound = true;
69-
filter = parseContext.parseInnerFilter();
64+
filter = parseContext.parseInnerFilterToQueryBuilder();
7065
} else {
7166
throw new QueryParsingException(parseContext, "[filtered] query does not support [" + currentFieldName + "]");
7267
}
@@ -83,44 +78,15 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
8378
}
8479
}
8580

86-
// parsed internally, but returned null during parsing...
87-
if (query == null) {
88-
return null;
89-
}
90-
91-
if (filter == null) {
92-
if (!filterFound) {
93-
// we allow for null filter, so it makes compositions on the client side to be simpler
94-
return query;
95-
} else {
96-
// even if the filter is not found, and its null, we should simply ignore it, and go
97-
// by the query
98-
return query;
99-
}
100-
}
101-
if (Queries.isConstantMatchAllQuery(filter)) {
102-
// this is an instance of match all filter, just execute the query
103-
return query;
104-
}
105-
106-
// if its a match_all query, use constant_score
107-
if (Queries.isConstantMatchAllQuery(query)) {
108-
Query q = new ConstantScoreQuery(filter);
109-
q.setBoost(boost);
110-
return q;
111-
}
112-
113-
BooleanQuery filteredQuery = Queries.filtered(query, filter);
114-
115-
filteredQuery.setBoost(boost);
116-
if (queryName != null) {
117-
parseContext.addNamedQuery(queryName, filteredQuery);
118-
}
119-
return filteredQuery;
81+
FilteredQueryBuilder qb = new FilteredQueryBuilder(query, filter);
82+
qb.boost(boost);
83+
qb.queryName(queryName);
84+
return qb;
12085
}
12186

12287
@Override
12388
public FilteredQueryBuilder getBuilderPrototype() {
12489
return FilteredQueryBuilder.PROTOTYPE;
12590
}
91+
12692
}

core/src/test/java/org/elasticsearch/index/query/BaseQueryTestCase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ public void testFromXContent() throws IOException {
218218

219219
QueryBuilder newQuery = queryParserService.queryParser(testQuery.getName()).fromXContent(context);
220220
assertNotSame(newQuery, testQuery);
221-
assertEquals("Queries should be equal", testQuery, newQuery);
222-
assertEquals("Queries should have equal hashcodes", testQuery.hashCode(), newQuery.hashCode());
221+
assertEquals(testQuery, newQuery);
222+
assertEquals(testQuery.hashCode(), newQuery.hashCode());
223223
}
224224

225225
/**
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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.ConstantScoreQuery;
23+
import org.apache.lucene.search.Query;
24+
import org.elasticsearch.common.lucene.search.Queries;
25+
import org.junit.Test;
26+
27+
import java.io.IOException;
28+
29+
@SuppressWarnings("deprecation")
30+
public class FilteredQueryBuilderTest extends BaseQueryTestCase<FilteredQueryBuilder> {
31+
32+
@Override
33+
protected FilteredQueryBuilder doCreateTestQueryBuilder() {
34+
QueryBuilder queryBuilder = RandomQueryBuilder.createQuery(random());
35+
QueryBuilder filterBuilder = RandomQueryBuilder.createQuery(random());
36+
37+
FilteredQueryBuilder query = new FilteredQueryBuilder(queryBuilder, filterBuilder);
38+
return query;
39+
}
40+
41+
@Override
42+
protected Query doCreateExpectedQuery(FilteredQueryBuilder qb, QueryParseContext context) throws IOException {
43+
Query query = qb.query().toQuery(context);
44+
Query filter = qb.filter().toQuery(context);
45+
46+
if (query == null) {
47+
return null;
48+
}
49+
50+
Query result;
51+
if (filter == null || Queries.isConstantMatchAllQuery(filter)) {
52+
result = qb.query().toQuery(context);
53+
} else if (Queries.isConstantMatchAllQuery(query)) {
54+
result = new ConstantScoreQuery(filter);
55+
} else {
56+
result = Queries.filtered(qb.query().toQuery(context), filter);
57+
}
58+
result.setBoost(qb.boost());
59+
return result;
60+
}
61+
62+
@Test
63+
public void testValidation() {
64+
QueryBuilder valid = RandomQueryBuilder.createQuery(random());
65+
QueryBuilder invalid = RandomQueryBuilder.createInvalidQuery(random());
66+
67+
// invalid cases
68+
FilteredQueryBuilder qb = new FilteredQueryBuilder(invalid);
69+
QueryValidationException result = qb.validate();
70+
assertNotNull(result);
71+
assertEquals(1, result.validationErrors().size());
72+
73+
qb = new FilteredQueryBuilder(valid, invalid);
74+
result = qb.validate();
75+
assertNotNull(result);
76+
assertEquals(1, result.validationErrors().size());
77+
78+
qb = new FilteredQueryBuilder(invalid, valid);
79+
result = qb.validate();
80+
assertNotNull(result);
81+
assertEquals(1, result.validationErrors().size());
82+
83+
qb = new FilteredQueryBuilder(invalid, invalid);
84+
result = qb.validate();
85+
assertNotNull(result);
86+
assertEquals(2, result.validationErrors().size());
87+
88+
// valid cases
89+
qb = new FilteredQueryBuilder(valid);
90+
assertNull(qb.validate());
91+
92+
qb = new FilteredQueryBuilder(null);
93+
assertNull(qb.validate());
94+
95+
qb = new FilteredQueryBuilder(null, valid);
96+
assertNull(qb.validate());
97+
98+
qb = new FilteredQueryBuilder(valid, null);
99+
assertNull(qb.validate());
100+
101+
qb = new FilteredQueryBuilder(valid, valid);
102+
assertNull(qb.validate());
103+
}
104+
105+
}

0 commit comments

Comments
 (0)