Skip to content

Commit 81e1ca9

Browse files
author
Christoph Büscher
committed
Query refactoring: refactored IdsQuery and added test
1 parent e0b1e84 commit 81e1ca9

File tree

3 files changed

+215
-52
lines changed

3 files changed

+215
-52
lines changed

src/main/java/org/elasticsearch/index/query/IdsQueryBuilder.java

Lines changed: 123 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,45 +19,73 @@
1919

2020
package org.elasticsearch.index.query;
2121

22+
import com.google.common.collect.Iterables;
23+
24+
import org.apache.lucene.queries.TermsQuery;
25+
import org.apache.lucene.search.Query;
26+
import org.apache.lucene.util.BytesRef;
27+
import org.elasticsearch.common.io.stream.StreamInput;
28+
import org.elasticsearch.common.io.stream.StreamOutput;
29+
import org.elasticsearch.common.io.stream.Streamable;
30+
import org.elasticsearch.common.lucene.search.Queries;
2231
import org.elasticsearch.common.xcontent.XContentBuilder;
32+
import org.elasticsearch.index.mapper.Uid;
33+
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
2334

2435
import java.io.IOException;
2536
import java.util.ArrayList;
2637
import java.util.Arrays;
38+
import java.util.Collection;
2739
import java.util.List;
40+
import java.util.Objects;
2841

2942
/**
3043
* A query that will return only documents matching specific ids (and a type).
3144
*/
32-
public class IdsQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder<IdsQueryBuilder> {
45+
public class IdsQueryBuilder extends BaseQueryBuilder implements Streamable, BoostableQueryBuilder<IdsQueryBuilder> {
3346

34-
private final List<String> types;
47+
private List<String> types = new ArrayList<>();
3548

36-
private List<String> values = new ArrayList<>();
49+
private List<String> ids = new ArrayList<>();
3750

38-
private float boost = -1;
51+
private float boost = 1.0f;
3952

4053
private String queryName;
4154

4255
public IdsQueryBuilder(String... types) {
43-
this.types = types == null ? null : Arrays.asList(types);
56+
this.types = (types == null || types.length == 0) ? new ArrayList<String>() : Arrays.asList(types);
57+
}
58+
59+
/**
60+
* Get the types used in this query
61+
* @return the types
62+
*/
63+
public Collection<String> types() {
64+
return this.types;
4465
}
4566

4667
/**
47-
* Adds ids to the filter.
68+
* Adds ids to the query.
4869
*/
4970
public IdsQueryBuilder addIds(String... ids) {
50-
values.addAll(Arrays.asList(ids));
71+
this.ids.addAll(Arrays.asList(ids));
5172
return this;
5273
}
5374

5475
/**
55-
* Adds ids to the filter.
76+
* Adds ids to the query.
5677
*/
5778
public IdsQueryBuilder ids(String... ids) {
5879
return addIds(ids);
5980
}
6081

82+
/**
83+
* Gets the ids for the query.
84+
*/
85+
public Collection<String> ids() {
86+
return this.ids;
87+
}
88+
6189
/**
6290
* Sets the boost for this query. Documents matching this query will (in addition to the normal
6391
* weightings) have their score multiplied by the boost provided.
@@ -69,29 +97,43 @@ public IdsQueryBuilder boost(float boost) {
6997
}
7098

7199
/**
72-
* Sets the query name for the filter that can be used when searching for matched_filters per hit.
100+
* Gets the boost for this query.
101+
*/
102+
public float boost() {
103+
return this.boost;
104+
}
105+
106+
/**
107+
* Sets the query name for the query that can be used when searching for matched_filters per hit.
73108
*/
74109
public IdsQueryBuilder queryName(String queryName) {
75110
this.queryName = queryName;
76111
return this;
77112
}
78113

114+
/**
115+
* Gets the query name for the query.
116+
*/
117+
public String queryName() {
118+
return this.queryName;
119+
}
120+
79121
@Override
80122
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
81123
builder.startObject(IdsQueryParser.NAME);
82124
if (types != null) {
83125
if (types.size() == 1) {
84-
builder.field("type", types.get(0));
126+
builder.field("type", types.iterator().next());
85127
} else {
86128
builder.startArray("types");
87-
for (Object type : types) {
129+
for (String type : types) {
88130
builder.value(type);
89131
}
90132
builder.endArray();
91133
}
92134
}
93135
builder.startArray("values");
94-
for (Object value : values) {
136+
for (String value : ids) {
95137
builder.value(value);
96138
}
97139
builder.endArray();
@@ -108,4 +150,73 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
108150
protected String parserName() {
109151
return IdsQueryParser.NAME;
110152
}
153+
154+
public Query toQuery(QueryParseContext parseContext) throws IOException, QueryParsingException {
155+
if (this.ids.isEmpty()) {
156+
return Queries.newMatchNoDocsQuery();
157+
}
158+
159+
Collection<String> typesForQuery = this.types;
160+
if (typesForQuery == null || typesForQuery.isEmpty()) {
161+
typesForQuery = parseContext.queryTypes();
162+
} else if (typesForQuery.size() == 1 && Iterables.getFirst(typesForQuery, null).equals("_all")) {
163+
typesForQuery = parseContext.mapperService().types();
164+
}
165+
166+
List<BytesRef> ids = new ArrayList<>(this.ids.size());
167+
for (String value : this.ids) {
168+
BytesRef ref = new BytesRef(value);
169+
ids.add(ref);
170+
}
171+
172+
TermsQuery query = new TermsQuery(UidFieldMapper.NAME, Uid.createTypeUids(typesForQuery, ids));
173+
query.setBoost(boost);
174+
if (queryName != null) {
175+
parseContext.addNamedQuery(queryName, query);
176+
}
177+
return query;
178+
}
179+
180+
@Override
181+
public QueryValidationException validate() {
182+
// all fields can be empty or null
183+
return null;
184+
}
185+
186+
@SuppressWarnings("unchecked")
187+
@Override
188+
public void readFrom(StreamInput in) throws IOException {
189+
this.types = (List<String>) in.readGenericValue();
190+
this.ids = (List<String>) in.readGenericValue();
191+
queryName = in.readOptionalString();
192+
boost = in.readFloat();
193+
}
194+
195+
@Override
196+
public void writeTo(StreamOutput out) throws IOException {
197+
out.writeGenericValue(this.types);
198+
out.writeGenericValue(this.ids);
199+
out.writeOptionalString(queryName);
200+
out.writeFloat(boost);
201+
}
202+
203+
@Override
204+
public int hashCode() {
205+
return Objects.hash(ids, types, boost, queryName);
206+
}
207+
208+
@Override
209+
public boolean equals(Object obj) {
210+
if (this == obj) {
211+
return true;
212+
}
213+
if (obj == null || getClass() != obj.getClass()) {
214+
return false;
215+
}
216+
IdsQueryBuilder other = (IdsQueryBuilder) obj;
217+
return Objects.equals(ids, other.ids) &&
218+
Objects.equals(types, other.types) &&
219+
Objects.equals(boost, other.boost) &&
220+
Objects.equals(queryName, other.queryName);
221+
}
111222
}

src/main/java/org/elasticsearch/index/query/IdsQueryParser.java

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,18 @@
2020
package org.elasticsearch.index.query;
2121

2222
import com.google.common.collect.ImmutableList;
23-
import com.google.common.collect.Iterables;
2423

25-
26-
import org.apache.lucene.queries.TermsQuery;
27-
import org.apache.lucene.search.Query;
28-
import org.apache.lucene.util.BytesRef;
2924
import org.elasticsearch.common.inject.Inject;
30-
import org.elasticsearch.common.lucene.search.Queries;
3125
import org.elasticsearch.common.xcontent.XContentParser;
32-
import org.elasticsearch.index.mapper.Uid;
33-
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
3426

3527
import java.io.IOException;
3628
import java.util.ArrayList;
37-
import java.util.Collection;
3829
import java.util.List;
3930

4031
/**
41-
*
32+
* Parser for the IdsQuery.
4233
*/
43-
public class IdsQueryParser extends BaseQueryParserTemp {
34+
public class IdsQueryParser extends BaseQueryParser {
4435

4536
public static final String NAME = "ids";
4637

@@ -53,38 +44,38 @@ public String[] names() {
5344
return new String[]{NAME};
5445
}
5546

47+
/**
48+
* @return a QueryBuilder representation of the query passed in as XContent in the parse context
49+
*/
5650
@Override
57-
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
51+
public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
5852
XContentParser parser = parseContext.parser();
59-
60-
List<BytesRef> ids = new ArrayList<>();
61-
Collection<String> types = null;
62-
String currentFieldName = null;
53+
List<String> ids = new ArrayList<>();
54+
List<String> types = new ArrayList<>();
6355
float boost = 1.0f;
6456
String queryName = null;
57+
58+
String currentFieldName = null;
6559
XContentParser.Token token;
66-
boolean idsProvided = false;
6760
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
6861
if (token == XContentParser.Token.FIELD_NAME) {
6962
currentFieldName = parser.currentName();
7063
} else if (token == XContentParser.Token.START_ARRAY) {
7164
if ("values".equals(currentFieldName)) {
72-
idsProvided = true;
7365
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
7466
if ((token == XContentParser.Token.VALUE_STRING) ||
7567
(token == XContentParser.Token.VALUE_NUMBER)) {
76-
BytesRef value = parser.utf8BytesOrNull();
77-
if (value == null) {
68+
String id = parser.textOrNull();
69+
if (id == null) {
7870
throw new QueryParsingException(parseContext, "No value specified for term filter");
7971
}
80-
ids.add(value);
72+
ids.add(id);
8173
} else {
8274
throw new QueryParsingException(parseContext, "Illegal value for id, expecting a string or number, got: "
8375
+ token);
8476
}
8577
}
8678
} else if ("types".equals(currentFieldName) || "type".equals(currentFieldName)) {
87-
types = new ArrayList<>();
8879
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
8980
String value = parser.textOrNull();
9081
if (value == null) {
@@ -107,26 +98,13 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
10798
}
10899
}
109100
}
110-
111-
if (!idsProvided) {
112-
throw new QueryParsingException(parseContext, "[ids] query, no ids values provided");
113-
}
114-
115101
if (ids.isEmpty()) {
116-
return Queries.newMatchNoDocsQuery();
117-
}
118-
119-
if (types == null || types.isEmpty()) {
120-
types = parseContext.queryTypes();
121-
} else if (types.size() == 1 && Iterables.getFirst(types, null).equals("_all")) {
122-
types = parseContext.mapperService().types();
123-
}
124-
125-
TermsQuery query = new TermsQuery(UidFieldMapper.NAME, Uid.createTypeUids(types, ids));
126-
query.setBoost(boost);
127-
if (queryName != null) {
128-
parseContext.addNamedQuery(queryName, query);
102+
throw new QueryParsingException(parseContext, "[ids] query, no ids values provided");
129103
}
104+
IdsQueryBuilder query = new IdsQueryBuilder(types.toArray(new String[types.size()]));
105+
query.addIds(ids.toArray(new String[ids.size()]));
106+
query.boost(boost).queryName(queryName);
107+
query.validate();
130108
return query;
131109
}
132110
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
23+
import org.apache.lucene.queries.TermsQuery;
24+
import org.apache.lucene.search.BooleanQuery;
25+
import org.apache.lucene.search.Query;
26+
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
27+
28+
import java.io.IOException;
29+
30+
import static org.hamcrest.Matchers.instanceOf;
31+
import static org.hamcrest.Matchers.is;
32+
33+
public class IdsQueryBuilderTest extends BaseQueryTestCase<IdsQueryBuilder> {
34+
35+
@Override
36+
protected IdsQueryBuilder createEmptyQueryBuilder() {
37+
return new IdsQueryBuilder();
38+
}
39+
40+
@Override
41+
protected void assertLuceneQuery(IdsQueryBuilder queryBuilder, Query query, QueryParseContext context) throws IOException {
42+
if (testQuery.ids().size() == 0) {
43+
assertThat(query, is(instanceOf(BooleanQuery.class)));
44+
} else {
45+
assertThat(query, is(instanceOf(TermsQuery.class)));
46+
TermsQuery termQuery = (TermsQuery) query;
47+
assertThat(termQuery.getBoost(), is(testQuery.boost()));
48+
// because internals of TermsFilter are well hidden, check string representation
49+
String[] parts = termQuery.toString().split(" ");
50+
assertThat(parts.length, is(queryBuilder.ids().size() * queryBuilder.types().size()));
51+
assertThat(parts[0].substring(0, parts[0].indexOf(":")), is(UidFieldMapper.NAME));
52+
}
53+
}
54+
55+
public IdsQueryBuilder createTestQueryBuilder() {
56+
IdsQueryBuilder query = new IdsQueryBuilder();
57+
int numberOfTypes = randomIntBetween(1, 10);
58+
String[] types = new String[numberOfTypes];
59+
for (int i = 0; i < numberOfTypes; i++) {
60+
types[i] = randomAsciiOfLength(8);
61+
}
62+
query = new IdsQueryBuilder(types);
63+
if (randomBoolean()) {
64+
int numberOfIds = randomIntBetween(1, 10);
65+
for (int i = 0; i < numberOfIds; i++) {
66+
query.addIds(randomAsciiOfLength(8));
67+
}
68+
}
69+
if (randomBoolean()) {
70+
query.boost(2.0f / randomIntBetween(1, 20));
71+
}
72+
return query;
73+
}
74+
}

0 commit comments

Comments
 (0)