Skip to content

Commit f3a529e

Browse files
committed
Merge pull request elastic#11629 from cbuescher/feature/query-refactoring-constantscore
Query refactoring: ConstantScoreQueryBuilder and Parser
2 parents 2d5f4bc + 32021c2 commit f3a529e

File tree

3 files changed

+158
-24
lines changed

3 files changed

+158
-24
lines changed

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

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919

2020
package org.elasticsearch.index.query;
2121

22+
import org.apache.lucene.search.ConstantScoreQuery;
2223
import org.apache.lucene.search.Query;
24+
import org.elasticsearch.common.io.stream.StreamInput;
25+
import org.elasticsearch.common.io.stream.StreamOutput;
2326
import org.elasticsearch.common.xcontent.XContentBuilder;
2427

2528
import java.io.IOException;
@@ -29,31 +32,31 @@
2932
* A query that wraps a filter and simply returns a constant score equal to the
3033
* query boost for every document in the filter.
3134
*/
32-
public class ConstantScoreQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<ConstantScoreQueryBuilder> {
35+
public class ConstantScoreQueryBuilder extends QueryBuilder<ConstantScoreQueryBuilder> implements BoostableQueryBuilder<ConstantScoreQueryBuilder> {
3336

3437
public static final String NAME = "constant_score";
3538

3639
private final QueryBuilder filterBuilder;
3740

38-
private float boost = -1;
41+
private float boost = 1.0f;
3942

40-
static final ConstantScoreQueryBuilder PROTOTYPE = new ConstantScoreQueryBuilder();
43+
static final ConstantScoreQueryBuilder PROTOTYPE = new ConstantScoreQueryBuilder(null);
4144

4245
/**
43-
* A query that wraps a query and simply returns a constant score equal to the
46+
* A query that wraps another query and simply returns a constant score equal to the
4447
* query boost for every document in the query.
4548
*
4649
* @param filterBuilder The query to wrap in a constant score query
4750
*/
4851
public ConstantScoreQueryBuilder(QueryBuilder filterBuilder) {
49-
this.filterBuilder = Objects.requireNonNull(filterBuilder);
52+
this.filterBuilder = filterBuilder;
5053
}
5154

5255
/**
53-
* private constructor only used for serialization
56+
* @return the query that was wrapped in this constant score query
5457
*/
55-
private ConstantScoreQueryBuilder() {
56-
this.filterBuilder = null;
58+
public QueryBuilder query() {
59+
return this.filterBuilder;
5760
}
5861

5962
/**
@@ -66,20 +69,74 @@ public ConstantScoreQueryBuilder boost(float boost) {
6669
return this;
6770
}
6871

72+
/**
73+
* @return the boost factor
74+
*/
75+
public float boost() {
76+
return this.boost;
77+
}
78+
6979
@Override
7080
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
7181
builder.startObject(NAME);
7282
builder.field("filter");
7383
filterBuilder.toXContent(builder, params);
84+
builder.field("boost", boost);
85+
builder.endObject();
86+
}
7487

75-
if (boost != -1) {
76-
builder.field("boost", boost);
88+
@Override
89+
public Query toQuery(QueryParseContext parseContext) throws QueryParsingException, IOException {
90+
// current DSL allows empty inner filter clauses, we ignore them
91+
if (filterBuilder == null) {
92+
return null;
7793
}
78-
builder.endObject();
94+
95+
Query innerFilter = filterBuilder.toQuery(parseContext);
96+
if (innerFilter == null ) {
97+
// return null so that parent queries (e.g. bool) also ignore this
98+
return null;
99+
}
100+
101+
Query filter = new ConstantScoreQuery(filterBuilder.toQuery(parseContext));
102+
filter.setBoost(boost);
103+
return filter;
79104
}
80105

81106
@Override
82107
public String queryId() {
83108
return NAME;
84109
}
110+
111+
@Override
112+
public int hashCode() {
113+
return Objects.hash(boost, filterBuilder);
114+
}
115+
116+
@Override
117+
public boolean equals(Object obj) {
118+
if (this == obj) {
119+
return true;
120+
}
121+
if (obj == null || getClass() != obj.getClass()) {
122+
return false;
123+
}
124+
ConstantScoreQueryBuilder other = (ConstantScoreQueryBuilder) obj;
125+
return Objects.equals(boost, other.boost) &&
126+
Objects.equals(filterBuilder, other.filterBuilder);
127+
}
128+
129+
@Override
130+
public ConstantScoreQueryBuilder readFrom(StreamInput in) throws IOException {
131+
QueryBuilder innerFilterBuilder = in.readNamedWriteable();
132+
ConstantScoreQueryBuilder constantScoreQueryBuilder = new ConstantScoreQueryBuilder(innerFilterBuilder);
133+
constantScoreQueryBuilder.boost = in.readFloat();
134+
return constantScoreQueryBuilder;
135+
}
136+
137+
@Override
138+
public void writeTo(StreamOutput out) throws IOException {
139+
out.writeNamedWriteable(this.filterBuilder);
140+
out.writeFloat(boost);
141+
}
85142
}

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

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

2020
package org.elasticsearch.index.query;
2121

22-
import org.apache.lucene.search.ConstantScoreQuery;
23-
import org.apache.lucene.search.Query;
2422
import org.elasticsearch.common.ParseField;
2523
import org.elasticsearch.common.Strings;
2624
import org.elasticsearch.common.inject.Inject;
@@ -31,7 +29,7 @@
3129
/**
3230
*
3331
*/
34-
public class ConstantScoreQueryParser extends BaseQueryParserTemp {
32+
public class ConstantScoreQueryParser extends BaseQueryParser {
3533

3634
private static final ParseField INNER_QUERY_FIELD = new ParseField("filter", "query");
3735

@@ -45,10 +43,10 @@ public String[] names() {
4543
}
4644

4745
@Override
48-
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
46+
public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, QueryParsingException {
4947
XContentParser parser = parseContext.parser();
5048

51-
Query filter = null;
49+
QueryBuilder query = null;
5250
boolean queryFound = false;
5351
float boost = 1.0f;
5452

@@ -61,7 +59,7 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
6159
// skip
6260
} else if (token == XContentParser.Token.START_OBJECT) {
6361
if (INNER_QUERY_FIELD.match(currentFieldName)) {
64-
filter = parseContext.parseInnerFilter();
62+
query = parseContext.parseInnerFilterToQueryBuilder();
6563
queryFound = true;
6664
} else {
6765
throw new QueryParsingException(parseContext, "[constant_score] query does not support [" + currentFieldName + "]");
@@ -78,13 +76,10 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
7876
throw new QueryParsingException(parseContext, "[constant_score] requires a 'filter' element");
7977
}
8078

81-
if (filter == null) {
82-
return null;
83-
}
84-
85-
filter = new ConstantScoreQuery(filter);
86-
filter.setBoost(boost);
87-
return filter;
79+
ConstantScoreQueryBuilder constantScoreBuilder = new ConstantScoreQueryBuilder(query);
80+
constantScoreBuilder.boost(boost);
81+
constantScoreBuilder.validate();
82+
return constantScoreBuilder;
8883
}
8984

9085
@Override
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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.xcontent.XContentFactory;
25+
import org.elasticsearch.common.xcontent.XContentParser;
26+
import org.junit.Test;
27+
28+
import java.io.IOException;
29+
30+
public class ConstantScoreQueryBuilderTest extends BaseQueryTestCase<ConstantScoreQueryBuilder> {
31+
32+
@Override
33+
protected Query createExpectedQuery(ConstantScoreQueryBuilder testBuilder, QueryParseContext context) throws QueryParsingException, IOException {
34+
Query expectedQuery = new ConstantScoreQuery(testBuilder.query().toQuery(context));
35+
expectedQuery.setBoost(testBuilder.boost());
36+
return expectedQuery;
37+
}
38+
39+
/**
40+
* @return a {@link ConstantScoreQueryBuilder} with random boost between 0.1f and 2.0f
41+
*/
42+
@Override
43+
protected ConstantScoreQueryBuilder createTestQueryBuilder() {
44+
ConstantScoreQueryBuilder query = new ConstantScoreQueryBuilder(RandomQueryBuilder.create(random()));
45+
if (randomBoolean()) {
46+
query.boost(2.0f / randomIntBetween(1, 20));
47+
}
48+
return query;
49+
}
50+
51+
/**
52+
* test that missing "filter" element causes {@link QueryParsingException}
53+
*/
54+
@Test(expected=QueryParsingException.class)
55+
public void testNoFilterElement() throws IOException {
56+
QueryParseContext context = createContext();
57+
String queryId = ConstantScoreQueryBuilder.PROTOTYPE.queryId();
58+
String queryString = "{ \""+queryId+"\" : {}";
59+
XContentParser parser = XContentFactory.xContent(queryString).createParser(queryString);
60+
context.reset(parser);
61+
assertQueryHeader(parser, queryId);
62+
context.indexQueryParserService().queryParser(queryId).fromXContent(context);
63+
}
64+
65+
/**
66+
* Test empty "filter" element.
67+
* Current DSL allows inner filter element to be empty, returning a `null` inner filter builder
68+
*/
69+
@Test
70+
public void testEmptyFilterElement() throws IOException {
71+
QueryParseContext context = createContext();
72+
String queryId = ConstantScoreQueryBuilder.PROTOTYPE.queryId();
73+
String queryString = "{ \""+queryId+"\" : { \"filter\" : { } }";
74+
XContentParser parser = XContentFactory.xContent(queryString).createParser(queryString);
75+
context.reset(parser);
76+
assertQueryHeader(parser, queryId);
77+
ConstantScoreQueryBuilder queryBuilder = (ConstantScoreQueryBuilder) context.indexQueryParserService()
78+
.queryParser(queryId).fromXContent(context);
79+
assertNull(queryBuilder.query());
80+
assertNull(queryBuilder.toQuery(createContext()));
81+
}
82+
}

0 commit comments

Comments
 (0)