Separates JSON parsing from Lucene query creation, adds support for streaming, hashCode and equals as well as unit tests.

Relates to #10217
This commit is contained in:
Isabel Drost-Fromm 2015-06-23 11:19:56 +02:00
parent de277d99d9
commit 0202c99e50
4 changed files with 221 additions and 64 deletions

View File

@ -19,10 +19,15 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.elasticsearch.common.Nullable; import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query;
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.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
/** /**
* A query that applies a filter to the results of another query. * A query that applies a filter to the results of another query.
@ -31,36 +36,103 @@ import java.io.IOException;
@Deprecated @Deprecated
public class FilteredQueryBuilder extends AbstractQueryBuilder<FilteredQueryBuilder> { public class FilteredQueryBuilder extends AbstractQueryBuilder<FilteredQueryBuilder> {
/** Name of the query in the REST API. */
public static final String NAME = "filtered"; public static final String NAME = "filtered";
/** The query to filter. */
private final QueryBuilder queryBuilder; private final QueryBuilder queryBuilder;
/** The filter to apply to the query. */
private final QueryBuilder filterBuilder; private final QueryBuilder filterBuilder;
static final FilteredQueryBuilder PROTOTYPE = new FilteredQueryBuilder(null, null); static final FilteredQueryBuilder PROTOTYPE = new FilteredQueryBuilder(null, null);
/**
* Returns a {@link MatchAllQueryBuilder} instance that will be used as
* default queryBuilder if none is supplied by the user. Feel free to
* set queryName and boost on that instance - it's always a new one.
* */
private static QueryBuilder generateDefaultQuery() {
return new MatchAllQueryBuilder();
}
/**
* A query that applies a filter to the results of a match_all query.
* @param filterBuilder The filter to apply on the query (Can be null)
* */
public FilteredQueryBuilder(QueryBuilder filterBuilder) {
this(generateDefaultQuery(), filterBuilder);
}
/** /**
* A query that applies a filter to the results of another query. * A query that applies a filter to the results of another query.
* *
* @param queryBuilder The query to apply the filter to (Can be null) * @param queryBuilder The query to apply the filter to
* @param filterBuilder The filter to apply on the query (Can be null) * @param filterBuilder The filter to apply on the query (Can be null)
*/ */
public FilteredQueryBuilder(@Nullable QueryBuilder queryBuilder, @Nullable QueryBuilder filterBuilder) { public FilteredQueryBuilder(QueryBuilder queryBuilder, QueryBuilder filterBuilder) {
this.queryBuilder = queryBuilder; this.queryBuilder = (queryBuilder != null) ? queryBuilder : generateDefaultQuery();
this.filterBuilder = filterBuilder; this.filterBuilder = (filterBuilder != null) ? filterBuilder : EmptyQueryBuilder.PROTOTYPE;
}
/** Returns the query to apply the filter to. */
public QueryBuilder query() {
return queryBuilder;
}
/** Returns the filter to apply to the query results. */
public QueryBuilder filter() {
return filterBuilder;
}
@Override
protected boolean doEquals(FilteredQueryBuilder other) {
return Objects.equals(queryBuilder, other.queryBuilder) &&
Objects.equals(filterBuilder, other.filterBuilder);
}
@Override
public int doHashCode() {
return Objects.hash(queryBuilder, filterBuilder);
}
@Override
public Query doToQuery(QueryParseContext parseContext) throws QueryParsingException, IOException {
Query query = queryBuilder.toQuery(parseContext);
Query filter = filterBuilder.toQuery(parseContext);
if (query == null) {
// Most likely this query was generated from the JSON query DSL - it parsed to an EmptyQueryBuilder so we ignore
// the whole filtered query as there is nothing to filter on. See FilteredQueryParser for an example.
return null;
}
if (filter == null || Queries.isConstantMatchAllQuery(filter)) {
// no filter, or match all filter
return query;
} else if (Queries.isConstantMatchAllQuery(query)) {
// if its a match_all query, use constant_score
return new ConstantScoreQuery(filter);
}
// use a BooleanQuery
return Queries.filtered(query, filter);
}
@Override
public QueryValidationException validate() {
QueryValidationException validationException = null;
validationException = validateInnerQuery(queryBuilder, validationException);
validationException = validateInnerQuery(filterBuilder, validationException);
return validationException;
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME); builder.startObject(NAME);
if (queryBuilder != null) { builder.field("query");
builder.field("query"); queryBuilder.toXContent(builder, params);
queryBuilder.toXContent(builder, params); builder.field("filter");
} filterBuilder.toXContent(builder, params);
if (filterBuilder != null) {
builder.field("filter");
filterBuilder.toXContent(builder, params);
}
printBoostAndQueryName(builder); printBoostAndQueryName(builder);
builder.endObject(); builder.endObject();
} }
@ -69,4 +141,18 @@ public class FilteredQueryBuilder extends AbstractQueryBuilder<FilteredQueryBuil
public String getName() { public String getName() {
return NAME; return NAME;
} }
@Override
public FilteredQueryBuilder doReadFrom(StreamInput in) throws IOException {
QueryBuilder query = in.readNamedWriteable();
QueryBuilder filter = in.readNamedWriteable();
FilteredQueryBuilder qb = new FilteredQueryBuilder(query, filter);
return qb;
}
@Override
public void doWriteTo(StreamOutput out) throws IOException {
out.writeNamedWriteable(queryBuilder);
out.writeNamedWriteable(filterBuilder);
}
} }

View File

@ -19,20 +19,17 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException; import java.io.IOException;
/** /**
* * @deprecated Use {@link BoolQueryParser} instead.
*/ */
@Deprecated @Deprecated
public class FilteredQueryParser extends BaseQueryParserTemp { public class FilteredQueryParser extends BaseQueryParser {
@Inject @Inject
public FilteredQueryParser() { public FilteredQueryParser() {
@ -44,12 +41,11 @@ public class FilteredQueryParser extends BaseQueryParserTemp {
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException { public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
Query query = Queries.newMatchAllQuery(); QueryBuilder query = null;
Query filter = null; QueryBuilder filter = null;
boolean filterFound = false;
float boost = AbstractQueryBuilder.DEFAULT_BOOST; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null; String queryName = null;
@ -63,10 +59,9 @@ public class FilteredQueryParser extends BaseQueryParserTemp {
// skip // skip
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
if ("query".equals(currentFieldName)) { if ("query".equals(currentFieldName)) {
query = parseContext.parseInnerQuery(); query = parseContext.parseInnerQueryBuilder();
} else if ("filter".equals(currentFieldName)) { } else if ("filter".equals(currentFieldName)) {
filterFound = true; filter = parseContext.parseInnerFilterToQueryBuilder();
filter = parseContext.parseInnerFilter();
} else { } else {
throw new QueryParsingException(parseContext, "[filtered] query does not support [" + currentFieldName + "]"); throw new QueryParsingException(parseContext, "[filtered] query does not support [" + currentFieldName + "]");
} }
@ -83,44 +78,15 @@ public class FilteredQueryParser extends BaseQueryParserTemp {
} }
} }
// parsed internally, but returned null during parsing... FilteredQueryBuilder qb = new FilteredQueryBuilder(query, filter);
if (query == null) { qb.boost(boost);
return null; qb.queryName(queryName);
} return qb;
if (filter == null) {
if (!filterFound) {
// we allow for null filter, so it makes compositions on the client side to be simpler
return query;
} else {
// even if the filter is not found, and its null, we should simply ignore it, and go
// by the query
return query;
}
}
if (Queries.isConstantMatchAllQuery(filter)) {
// this is an instance of match all filter, just execute the query
return query;
}
// if its a match_all query, use constant_score
if (Queries.isConstantMatchAllQuery(query)) {
Query q = new ConstantScoreQuery(filter);
q.setBoost(boost);
return q;
}
BooleanQuery filteredQuery = Queries.filtered(query, filter);
filteredQuery.setBoost(boost);
if (queryName != null) {
parseContext.addNamedQuery(queryName, filteredQuery);
}
return filteredQuery;
} }
@Override @Override
public FilteredQueryBuilder getBuilderPrototype() { public FilteredQueryBuilder getBuilderPrototype() {
return FilteredQueryBuilder.PROTOTYPE; return FilteredQueryBuilder.PROTOTYPE;
} }
} }

View File

@ -218,8 +218,8 @@ public abstract class BaseQueryTestCase<QB extends AbstractQueryBuilder<QB>> ext
QueryBuilder newQuery = queryParserService.queryParser(testQuery.getName()).fromXContent(context); QueryBuilder newQuery = queryParserService.queryParser(testQuery.getName()).fromXContent(context);
assertNotSame(newQuery, testQuery); assertNotSame(newQuery, testQuery);
assertEquals("Queries should be equal", testQuery, newQuery); assertEquals(testQuery, newQuery);
assertEquals("Queries should have equal hashcodes", testQuery.hashCode(), newQuery.hashCode()); assertEquals(testQuery.hashCode(), newQuery.hashCode());
} }
/** /**

View File

@ -0,0 +1,105 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.index.query;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.lucene.search.Queries;
import org.junit.Test;
import java.io.IOException;
@SuppressWarnings("deprecation")
public class FilteredQueryBuilderTest extends BaseQueryTestCase<FilteredQueryBuilder> {
@Override
protected FilteredQueryBuilder doCreateTestQueryBuilder() {
QueryBuilder queryBuilder = RandomQueryBuilder.createQuery(random());
QueryBuilder filterBuilder = RandomQueryBuilder.createQuery(random());
FilteredQueryBuilder query = new FilteredQueryBuilder(queryBuilder, filterBuilder);
return query;
}
@Override
protected Query doCreateExpectedQuery(FilteredQueryBuilder qb, QueryParseContext context) throws IOException {
Query query = qb.query().toQuery(context);
Query filter = qb.filter().toQuery(context);
if (query == null) {
return null;
}
Query result;
if (filter == null || Queries.isConstantMatchAllQuery(filter)) {
result = qb.query().toQuery(context);
} else if (Queries.isConstantMatchAllQuery(query)) {
result = new ConstantScoreQuery(filter);
} else {
result = Queries.filtered(qb.query().toQuery(context), filter);
}
result.setBoost(qb.boost());
return result;
}
@Test
public void testValidation() {
QueryBuilder valid = RandomQueryBuilder.createQuery(random());
QueryBuilder invalid = RandomQueryBuilder.createInvalidQuery(random());
// invalid cases
FilteredQueryBuilder qb = new FilteredQueryBuilder(invalid);
QueryValidationException result = qb.validate();
assertNotNull(result);
assertEquals(1, result.validationErrors().size());
qb = new FilteredQueryBuilder(valid, invalid);
result = qb.validate();
assertNotNull(result);
assertEquals(1, result.validationErrors().size());
qb = new FilteredQueryBuilder(invalid, valid);
result = qb.validate();
assertNotNull(result);
assertEquals(1, result.validationErrors().size());
qb = new FilteredQueryBuilder(invalid, invalid);
result = qb.validate();
assertNotNull(result);
assertEquals(2, result.validationErrors().size());
// valid cases
qb = new FilteredQueryBuilder(valid);
assertNull(qb.validate());
qb = new FilteredQueryBuilder(null);
assertNull(qb.validate());
qb = new FilteredQueryBuilder(null, valid);
assertNull(qb.validate());
qb = new FilteredQueryBuilder(valid, null);
assertNull(qb.validate());
qb = new FilteredQueryBuilder(valid, valid);
assertNull(qb.validate());
}
}