Skip to content

Commit

Permalink
Query refactoring: refactored IdsQueryBuilder and Parser and added test
Browse files Browse the repository at this point in the history
Split the parse(QueryParseContext ctx) method into a parsing and a query building part,
adding Streamable for serialization and hashCode(), equals() for better testing.
Add basic unit test for Builder and Parser.

Closes elastic#10670
  • Loading branch information
cbuescher committed May 8, 2015
1 parent 304dddf commit 41b5b26
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 48 deletions.
126 changes: 115 additions & 11 deletions src/main/java/org/elasticsearch/index/query/IdsQueryBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,73 @@

package org.elasticsearch.index.query;

import com.google.common.collect.Iterables;

import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.mapper.internal.UidFieldMapper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
* A query that will return only documents matching specific ids (and a type).
*/
public class IdsQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder<IdsQueryBuilder> {
public class IdsQueryBuilder extends BaseQueryBuilder implements Streamable, BoostableQueryBuilder<IdsQueryBuilder> {

private final List<String> types;
private List<String> types = new ArrayList<>();

private List<String> values = new ArrayList<>();
private List<String> ids = new ArrayList<>();

private float boost = -1;
private float boost = 1.0f;

private String queryName;

public IdsQueryBuilder(String... types) {
this.types = types == null ? null : Arrays.asList(types);
this.types = (types == null || types.length == 0) ? new ArrayList<String>() : Arrays.asList(types);
}

/**
* Get the types used in this query
* @return the types
*/
public Collection<String> types() {
return this.types;
}

/**
* Adds ids to the filter.
* Adds ids to the query.
*/
public IdsQueryBuilder addIds(String... ids) {
values.addAll(Arrays.asList(ids));
this.ids.addAll(Arrays.asList(ids));
return this;
}

/**
* Adds ids to the filter.
* Adds ids to the query.
*/
public IdsQueryBuilder ids(String... ids) {
return addIds(ids);
}

/**
* Gets the ids for the query.
*/
public Collection<String> ids() {
return this.ids;
}

/**
* Sets the boost for this query. Documents matching this query will (in addition to the normal
* weightings) have their score multiplied by the boost provided.
Expand All @@ -69,13 +97,27 @@ public IdsQueryBuilder boost(float boost) {
}

/**
* Sets the query name for the filter that can be used when searching for matched_filters per hit.
* Gets the boost for this query.
*/
public float boost() {
return this.boost;
}

/**
* Sets the query name for the query that can be used when searching for matched_filters per hit.
*/
public IdsQueryBuilder queryName(String queryName) {
this.queryName = queryName;
return this;
}

/**
* Gets the query name for the query.
*/
public String queryName() {
return this.queryName;
}

@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(IdsQueryParser.NAME);
Expand All @@ -84,14 +126,14 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
builder.field("type", types.get(0));
} else {
builder.startArray("types");
for (Object type : types) {
for (String type : types) {
builder.value(type);
}
builder.endArray();
}
}
builder.startArray("values");
for (Object value : values) {
for (String value : ids) {
builder.value(value);
}
builder.endArray();
Expand All @@ -108,4 +150,66 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
protected String parserName() {
return IdsQueryParser.NAME;
}

public Query toQuery(QueryParseContext parseContext) throws IOException, QueryParsingException {
if (this.ids.isEmpty()) {
return Queries.newMatchNoDocsQuery();
}

Collection<String> typesForQuery = this.types;
if (typesForQuery == null || typesForQuery.isEmpty()) {
typesForQuery = parseContext.queryTypes();
} else if (typesForQuery.size() == 1 && Iterables.getFirst(typesForQuery, null).equals("_all")) {
typesForQuery = parseContext.mapperService().types();
}

TermsQuery query = new TermsQuery(UidFieldMapper.NAME, Uid.createTypeUids(typesForQuery, ids));
query.setBoost(boost);
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
return query;
}

@Override
public QueryValidationException validate() {
// all fields can be empty or null
return null;
}

@Override
public void readFrom(StreamInput in) throws IOException {
this.types = in.readStringList();
this.ids = in.readStringList();
queryName = in.readOptionalString();
boost = in.readFloat();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeStringList(this.types);
out.writeStringList(this.ids);
out.writeOptionalString(queryName);
out.writeFloat(boost);
}

@Override
public int hashCode() {
return Objects.hash(ids, types, boost, queryName);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
IdsQueryBuilder other = (IdsQueryBuilder) obj;
return Objects.equals(ids, other.ids) &&
Objects.equals(types, other.types) &&
Objects.equals(boost, other.boost) &&
Objects.equals(queryName, other.queryName);
}
}
53 changes: 17 additions & 36 deletions src/main/java/org/elasticsearch/index/query/IdsQueryParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,18 @@
package org.elasticsearch.index.query;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;


import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.mapper.internal.UidFieldMapper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
*
* Parser for the IdsQuery.
*/
public class IdsQueryParser extends BaseQueryParserTemp {
public class IdsQueryParser extends BaseQueryParser {

public static final String NAME = "ids";

Expand All @@ -53,15 +44,18 @@ public String[] names() {
return new String[]{NAME};
}

/**
* @return a QueryBuilder representation of the query passed in as XContent in the parse context
*/
@Override
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();

List<BytesRef> ids = new ArrayList<>();
Collection<String> types = null;
String currentFieldName = null;
List<String> ids = new ArrayList<>();
List<String> types = new ArrayList<>();
float boost = 1.0f;
String queryName = null;

String currentFieldName = null;
XContentParser.Token token;
boolean idsProvided = false;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
Expand All @@ -73,18 +67,17 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if ((token == XContentParser.Token.VALUE_STRING) ||
(token == XContentParser.Token.VALUE_NUMBER)) {
BytesRef value = parser.utf8BytesOrNull();
if (value == null) {
String id = parser.textOrNull();
if (id == null) {
throw new QueryParsingException(parseContext, "No value specified for term filter");
}
ids.add(value);
ids.add(id);
} else {
throw new QueryParsingException(parseContext, "Illegal value for id, expecting a string or number, got: "
+ token);
}
}
} else if ("types".equals(currentFieldName) || "type".equals(currentFieldName)) {
types = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
String value = parser.textOrNull();
if (value == null) {
Expand All @@ -107,26 +100,14 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
}
}
}

if (!idsProvided) {
throw new QueryParsingException(parseContext, "[ids] query, no ids values provided");
}

if (ids.isEmpty()) {
return Queries.newMatchNoDocsQuery();
}

if (types == null || types.isEmpty()) {
types = parseContext.queryTypes();
} else if (types.size() == 1 && Iterables.getFirst(types, null).equals("_all")) {
types = parseContext.mapperService().types();
}

TermsQuery query = new TermsQuery(UidFieldMapper.NAME, Uid.createTypeUids(types, ids));
query.setBoost(boost);
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
IdsQueryBuilder query = new IdsQueryBuilder(types.toArray(new String[types.size()]));
query.addIds(ids.toArray(new String[ids.size()]));
query.boost(boost).queryName(queryName);
query.validate();
return query;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ protected static QueryParseContext createContext() {
return new QueryParseContext(index, queryParserService);
}

private static void assertQueryHeader(XContentParser parser, String expectedParserName) throws IOException {
protected static void assertQueryHeader(XContentParser parser, String expectedParserName) throws IOException {
assertThat(parser.nextToken(), is(XContentParser.Token.START_OBJECT));
assertThat(parser.nextToken(), is(XContentParser.Token.FIELD_NAME));
assertThat(parser.currentName(), is(expectedParserName));
Expand Down
Loading

0 comments on commit 41b5b26

Please sign in to comment.