Using ObjectParser in MatchAllQueryBuilder and IdsQueryBuilder

A first step moving away from the current parsing to use the generalized
Objectparser and ConstructingObjectParser. This PR start by making use of it in
MatchAllQueryBuilder and IdsQueryBuilder.
This commit is contained in:
Christoph Büscher 2016-11-01 14:22:14 +01:00
parent 2a1e08f76a
commit b8cae39b7c
4 changed files with 83 additions and 126 deletions

View File

@ -26,10 +26,12 @@ import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.action.support.ToXContentToBytes;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcherSupplier;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.xcontent.AbstractObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentLocation;
import org.elasticsearch.common.xcontent.XContentType;
@ -300,4 +302,12 @@ public abstract class AbstractQueryBuilder<QB extends AbstractQueryBuilder<QB>>
+ processedFieldName + "] and [" + currentFieldName + "]");
}
}
/**
* Adds 'boost' and 'query_name' parsing to all query builder parsers passed in
*/
protected static void declareStandardFields(AbstractObjectParser<? extends QueryBuilder, ? extends ParseFieldMatcherSupplier> parser) {
parser.declareFloat((builder, value) -> builder.boost(value), AbstractQueryBuilder.BOOST_FIELD);
parser.declareString((builder, value) -> builder.queryName(value), AbstractQueryBuilder.NAME_FIELD);
}
}

View File

@ -27,13 +27,12 @@ import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.mapper.UidFieldMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -43,6 +42,8 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
/**
* A query that will return only documents matching specific ids (and a type).
*/
@ -56,13 +57,6 @@ public class IdsQueryBuilder extends AbstractQueryBuilder<IdsQueryBuilder> {
private final String[] types;
/**
* Creates a new IdsQueryBuilder without providing the types of the documents to look for
*/
public IdsQueryBuilder() {
this.types = new String[0];
}
/**
* Creates a new IdsQueryBuilder by providing the types of the documents to look for
*/
@ -126,71 +120,23 @@ public class IdsQueryBuilder extends AbstractQueryBuilder<IdsQueryBuilder> {
builder.endObject();
}
public static Optional<IdsQueryBuilder> fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
List<String> ids = new ArrayList<>();
List<String> types = new ArrayList<>();
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null;
@SuppressWarnings("unchecked")
private static ConstructingObjectParser<IdsQueryBuilder, QueryParseContext> PARSER = new ConstructingObjectParser<>(NAME,
a -> new IdsQueryBuilder(((List<String>) a[0]).toArray(new String[0])));
String currentFieldName = null;
XContentParser.Token token;
boolean idsProvided = false;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_ARRAY) {
if (parseContext.getParseFieldMatcher().match(currentFieldName, VALUES_FIELD)) {
idsProvided = true;
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if ((token == XContentParser.Token.VALUE_STRING) ||
(token == XContentParser.Token.VALUE_NUMBER)) {
String id = parser.textOrNull();
if (id == null) {
throw new ParsingException(parser.getTokenLocation(), "No value specified for term filter");
}
ids.add(id);
} else {
throw new ParsingException(parser.getTokenLocation(),
"Illegal value for id, expecting a string or number, got: " + token);
}
}
} else if (parseContext.getParseFieldMatcher().match(currentFieldName, TYPE_FIELD)) {
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
String value = parser.textOrNull();
if (value == null) {
throw new ParsingException(parser.getTokenLocation(), "No type specified for term filter");
}
types.add(value);
}
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + IdsQueryBuilder.NAME +
"] query does not support [" + currentFieldName + "]");
}
} else if (token.isValue()) {
if (parseContext.getParseFieldMatcher().match(currentFieldName, TYPE_FIELD)) {
types = Collections.singletonList(parser.text());
} else if (parseContext.getParseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
boost = parser.floatValue();
} else if (parseContext.getParseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
queryName = parser.text();
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + IdsQueryBuilder.NAME +
"] query does not support [" + currentFieldName + "]");
}
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + IdsQueryBuilder.NAME +
"] unknown token [" + token + "] after [" + currentFieldName + "]");
}
}
if (!idsProvided) {
throw new ParsingException(parser.getTokenLocation(), "[" + IdsQueryBuilder.NAME + "] query, no ids values provided");
}
static {
PARSER.declareStringArray(constructorArg(), IdsQueryBuilder.TYPE_FIELD);
PARSER.declareStringArray((builder, values) -> builder.addIds(values.toArray(new String[values.size()])),
IdsQueryBuilder.VALUES_FIELD);
declareStandardFields(PARSER);
}
IdsQueryBuilder query = new IdsQueryBuilder(types.toArray(new String[types.size()]));
query.addIds(ids.toArray(new String[ids.size()]));
query.boost(boost).queryName(queryName);
return Optional.of(query);
public static Optional<IdsQueryBuilder> fromXContent(QueryParseContext context) {
try {
return Optional.of(PARSER.apply(context.parser(), context));
} catch (IllegalArgumentException e) {
throw new ParsingException(context.parser().getTokenLocation(), e.getMessage(), e);
}
}

View File

@ -20,13 +20,12 @@
package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Optional;
@ -48,7 +47,7 @@ public class MatchAllQueryBuilder extends AbstractQueryBuilder<MatchAllQueryBuil
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
protected void doWriteTo(StreamOutput out) {
// only superclass has state
}
@ -59,38 +58,22 @@ public class MatchAllQueryBuilder extends AbstractQueryBuilder<MatchAllQueryBuil
builder.endObject();
}
public static Optional<MatchAllQueryBuilder> fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
private static ObjectParser<MatchAllQueryBuilder, QueryParseContext> PARSER = new ObjectParser<>(NAME, MatchAllQueryBuilder::new);
String currentFieldName = null;
XContentParser.Token token;
String queryName = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
while (((token = parser.nextToken()) != XContentParser.Token.END_OBJECT && token != XContentParser.Token.END_ARRAY)) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token.isValue()) {
if (parseContext.getParseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
queryName = parser.text();
} else if (parseContext.getParseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
boost = parser.floatValue();
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + MatchAllQueryBuilder.NAME +
"] query does not support [" + currentFieldName + "]");
}
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + MatchAllQueryBuilder.NAME +
"] unknown token [" + token + "] after [" + currentFieldName + "]");
}
static {
declareStandardFields(PARSER);
}
public static Optional<MatchAllQueryBuilder> fromXContent(QueryParseContext context) {
try {
return Optional.of(PARSER.apply(context.parser(), context));
} catch (IllegalArgumentException e) {
throw new ParsingException(context.parser().getTokenLocation(), e.getMessage(), e);
}
MatchAllQueryBuilder queryBuilder = new MatchAllQueryBuilder();
queryBuilder.boost(boost);
queryBuilder.queryName(queryName);
return Optional.of(queryBuilder);
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
protected Query doToQuery(QueryShardContext context) {
return Queries.newMatchAllQuery();
}

View File

@ -32,17 +32,9 @@ import org.elasticsearch.test.AbstractQueryTestCase;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.contains;
public class IdsQueryBuilderTests extends AbstractQueryTestCase<IdsQueryBuilder> {
/**
* Check that parser throws exception on missing values field.
*/
public void testIdsNotProvided() throws IOException {
String noIdsFieldQuery = "{\"ids\" : { \"type\" : \"my_type\" }";
ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(noIdsFieldQuery));
assertThat(e.getMessage(), containsString("no ids values provided"));
}
@Override
protected IdsQueryBuilder doCreateTestQueryBuilder() {
@ -102,7 +94,7 @@ public class IdsQueryBuilderTests extends AbstractQueryTestCase<IdsQueryBuilder>
public void testIdsQueryWithInvalidValues() throws Exception {
String query = "{ \"ids\": { \"values\": [[1]] } }";
ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(query));
assertEquals("Illegal value for id, expecting a string or number, got: START_ARRAY", e.getMessage());
assertEquals("[ids] failed to parse field [values]", e.getMessage());
}
public void testFromJson() throws IOException {
@ -116,44 +108,70 @@ public class IdsQueryBuilderTests extends AbstractQueryTestCase<IdsQueryBuilder>
"}";
IdsQueryBuilder parsed = (IdsQueryBuilder) parseQuery(json);
checkGeneratedJson(json, parsed);
assertEquals(json, 3, parsed.ids().size());
assertThat(parsed.ids(), contains("1","100","4"));
assertEquals(json, "my_type", parsed.types()[0]);
// check that type that is not an array and also ids that are numbers are parsed
json =
"{\n" +
" \"ids\" : {\n" +
" \"type\" : \"my_type\",\n" +
" \"values\" : [ 1, 100, 4 ],\n" +
" \"boost\" : 1.0\n" +
" }\n" +
"}";
parsed = (IdsQueryBuilder) parseQuery(json);
assertThat(parsed.ids(), contains("1","100","4"));
assertEquals(json, "my_type", parsed.types()[0]);
// check with empty type array
json =
"{\n" +
" \"ids\" : {\n" +
" \"type\" : [ ],\n" +
" \"values\" : [ \"1\", \"100\", \"4\" ],\n" +
" \"boost\" : 1.0\n" +
" }\n" +
"}";
parsed = (IdsQueryBuilder) parseQuery(json);
assertThat(parsed.ids(), contains("1","100","4"));
assertEquals(json, 0, parsed.types().length);
}
public void testFromJsonDeprecatedSyntax() throws IOException {
IdsQueryBuilder tempQuery = createTestQueryBuilder();
assumeTrue("test requires at least one type", tempQuery.types() != null && tempQuery.types().length > 0);
String type = tempQuery.types()[0];
IdsQueryBuilder testQuery = new IdsQueryBuilder(type);
IdsQueryBuilder testQuery = new IdsQueryBuilder("my_type");
//single value type can also be called _type
final String contentString = "{\n" +
" \"ids\" : {\n" +
" \"_type\" : \"" + type + "\",\n" +
" \"values\" : []\n" +
" \"_type\" : \"my_type\",\n" +
" \"values\" : [ ]\n" +
" }\n" +
"}";
IdsQueryBuilder parsed = (IdsQueryBuilder) parseQuery(contentString, ParseFieldMatcher.EMPTY);
assertEquals(testQuery, parsed);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> parseQuery(contentString));
assertEquals("Deprecated field [_type] used, expected [type] instead", e.getMessage());
ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(contentString));
checkWarningHeaders("Deprecated field [_type] used, expected [type] instead");
assertEquals("Deprecated field [_type] used, expected [type] instead", e.getMessage());
assertEquals(3, e.getLineNumber());
assertEquals(19, e.getColumnNumber());
//array of types can also be called type rather than types
final String contentString2 = "{\n" +
" \"ids\" : {\n" +
" \"types\" : [\"" + type + "\"],\n" +
" \"values\" : []\n" +
" \"types\" : [\"my_type\"],\n" +
" \"values\" : [ ]\n" +
" }\n" +
"}";
parsed = (IdsQueryBuilder) parseQuery(contentString, ParseFieldMatcher.EMPTY);
assertEquals(testQuery, parsed);
e = expectThrows(IllegalArgumentException.class, () -> parseQuery(contentString2));
assertEquals("Deprecated field [types] used, expected [type] instead", e.getMessage());
e = expectThrows(ParsingException.class, () -> parseQuery(contentString2));
checkWarningHeaders("Deprecated field [_type] used, expected [type] instead");
assertEquals("Deprecated field [types] used, expected [type] instead", e.getMessage());
assertEquals(3, e.getLineNumber());
assertEquals(19, e.getColumnNumber());
}
}