diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java index 82a57a1652dd9..6ecf2231659f6 100644 --- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java +++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java @@ -509,6 +509,20 @@ public C readNamedWriteable() throws IOException { return namedWriteable.readFrom(this); } + /** + * Reads a list of {@link NamedWriteable} from the current stream, by first reading its size and then + * reading the individual objects using {@link #readNamedWriteable()}. + */ + public List readNamedWritableList() throws IOException { + List list = new ArrayList<>(); + int size = readInt(); + for (int i = 0; i < size; i++) { + C obj = readNamedWriteable(); + list.add(obj); + } + return list; + } + public static StreamInput wrap(BytesReference reference) { if (reference.hasArray() == false) { reference = reference.toBytesArray(); diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java index 02f01e599e86e..e3ceb1028c9d2 100644 --- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java +++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java @@ -457,4 +457,15 @@ public void writeNamedWriteable(NamedWriteable namedWriteable) throws IOExceptio writeString(namedWriteable.getName()); namedWriteable.writeTo(this); } + + /** + * Writes a list of {@link NamedWriteable} to the current stream, by first writing its size and then iterating over the objects + * in the list + */ + public void writeNamedWritableList(List list) throws IOException { + writeInt(list.size()); + for (NamedWriteable obj : list) { + writeNamedWriteable(obj); + } + } } diff --git a/core/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java index 6977e100a4fe9..f86d3169033c9 100644 --- a/core/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java @@ -19,20 +19,36 @@ package org.elasticsearch.index.query; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.MatchAllDocsQuery; +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 java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; + +import static org.elasticsearch.common.lucene.search.Queries.fixNegativeQueryIfNeeded; /** * A Query that matches documents matching boolean combinations of other queries. */ -public class BoolQueryBuilder extends QueryBuilder implements BoostableQueryBuilder { +public class BoolQueryBuilder extends QueryBuilder implements BoostableQueryBuilder { public static final String NAME = "bool"; + static final boolean ADJUST_PURE_NEGATIVE_DEFAULT = true; + + static final boolean DISABLE_COORD_DEFAULT = false; + + static final BoolQueryBuilder PROTOTYPE = new BoolQueryBuilder(); + private final List mustClauses = new ArrayList<>(); private final List mustNotClauses = new ArrayList<>(); @@ -41,18 +57,16 @@ public class BoolQueryBuilder extends QueryBuilder implements BoostableQueryBuil private final List shouldClauses = new ArrayList<>(); - private float boost = -1; + private float boost = 1.0f; - private Boolean disableCoord; + private boolean disableCoord = DISABLE_COORD_DEFAULT; - private String minimumShouldMatch; + private boolean adjustPureNegative = ADJUST_PURE_NEGATIVE_DEFAULT; - private Boolean adjustPureNegative; + private String minimumShouldMatch; private String queryName; - static final BoolQueryBuilder PROTOTYPE = new BoolQueryBuilder(); - /** * Adds a query that must appear in the matching documents and will * contribute to scoring. @@ -62,6 +76,22 @@ public BoolQueryBuilder must(QueryBuilder queryBuilder) { return this; } + /** + * Adds a list of queries that must appear in the matching documents and will + * contribute to scoring. + */ + public BoolQueryBuilder must(List queryBuilders) { + mustClauses.addAll(queryBuilders); + return this; + } + + /** + * Gets the queries that must appear in the matching documents. + */ + public List must() { + return this.mustClauses; + } + /** * Adds a query that must appear in the matching documents but will * not contribute to scoring. @@ -72,8 +102,23 @@ public BoolQueryBuilder filter(QueryBuilder queryBuilder) { } /** - * Adds a query that must not appear in the matching documents and - * will not contribute to scoring. + * Adds a list of queries that must appear in the matching documents but will + * not contribute to scoring. + */ + public BoolQueryBuilder filter(List queryBuilders) { + filterClauses.addAll(queryBuilders); + return this; + } + + /** + * Gets the queries that must appear in the matching documents but don't conntribute to scoring + */ + public List filter() { + return this.filterClauses; + } + + /** + * Adds a query that must not appear in the matching documents. */ public BoolQueryBuilder mustNot(QueryBuilder queryBuilder) { mustNotClauses.add(queryBuilder); @@ -81,7 +126,22 @@ public BoolQueryBuilder mustNot(QueryBuilder queryBuilder) { } /** - * Adds a query that should appear in the matching documents. For a boolean query with no + * Adds a list of queries that must not appear in the matching documents. + */ + public BoolQueryBuilder mustNot(List queryBuilders) { + mustNotClauses.addAll(queryBuilders); + return this; + } + + /** + * Gets the queries that must not appear in the matching documents. + */ + public List mustNot() { + return this.mustNotClauses; + } + + /** + * Adds a clause that should be matched by the returned documents. For a boolean query with no * MUST clauses one or more SHOULD clauses must match a document * for the BooleanQuery to match. * @@ -92,6 +152,28 @@ public BoolQueryBuilder should(QueryBuilder queryBuilder) { return this; } + /** + * Adds a list of clauses that should be matched by the returned documents. For a boolean query with no + * MUST clauses one or more SHOULD clauses must match a document + * for the BooleanQuery to match. + * + * @see #minimumNumberShouldMatch(int) + */ + public BoolQueryBuilder should(List queryBuilders) { + shouldClauses.addAll(queryBuilders); + return this; + } + + /** + * Gets the list of clauses that should be matched by the returned documents. + * + * @see #should(QueryBuilder) + * @see #minimumNumberShouldMatch(int) + */ + public List should() { + return this.shouldClauses; + } + /** * 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. @@ -102,6 +184,13 @@ public BoolQueryBuilder boost(float boost) { return this; } + /** + * Get the boost for this query. + */ + public float boost() { + return this.boost; + } + /** * Disables Similarity#coord(int,int) in scoring. Defaults to false. */ @@ -110,6 +199,13 @@ public BoolQueryBuilder disableCoord(boolean disableCoord) { return this; } + /** + * @return whether the Similarity#coord(int,int) in scoring are disabled. Defaults to false. + */ + public boolean disableCoord() { + return this.disableCoord; + } + /** * Specifies a minimum number of the optional (should) boolean clauses which must be satisfied. *

@@ -128,6 +224,23 @@ public BoolQueryBuilder minimumNumberShouldMatch(int minimumNumberShouldMatch) { return this; } + + /** + * Specifies a minimum number of the optional (should) boolean clauses which must be satisfied. + * @see BoolQueryBuilder#minimumNumberShouldMatch(int) + */ + public BoolQueryBuilder minimumNumberShouldMatch(String minimumNumberShouldMatch) { + this.minimumShouldMatch = minimumNumberShouldMatch; + return this; + } + + /** + * @return the string representation of the minimumShouldMatch settings for this query + */ + public String minimumNumberShouldMatch() { + return this.minimumShouldMatch; + } + /** * Sets the minimum should match using the special syntax (for example, supporting percentage). */ @@ -154,6 +267,13 @@ public BoolQueryBuilder adjustPureNegative(boolean adjustPureNegative) { return this; } + /** + * @return the setting for the adjust_pure_negative setting in this query + */ + public boolean adjustPureNegative() { + return this.adjustPureNegative; + } + /** * Sets the query name for the filter that can be used when searching for matched_filters per hit. */ @@ -162,6 +282,13 @@ public BoolQueryBuilder queryName(String queryName) { return this; } + /** + * Gets the query name for the filter that can be used when searching for matched_filters per hit. + */ + public String queryName() { + return this.queryName; + } + @Override protected void doXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(NAME); @@ -169,25 +296,19 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep doXArrayContent("filter", filterClauses, builder, params); doXArrayContent("must_not", mustNotClauses, builder, params); doXArrayContent("should", shouldClauses, builder, params); - if (boost != -1) { - builder.field("boost", boost); - } - if (disableCoord != null) { - builder.field("disable_coord", disableCoord); - } + builder.field("boost", boost); + builder.field("disable_coord", disableCoord); + builder.field("adjust_pure_negative", adjustPureNegative); if (minimumShouldMatch != null) { builder.field("minimum_should_match", minimumShouldMatch); } - if (adjustPureNegative != null) { - builder.field("adjust_pure_negative", adjustPureNegative); - } if (queryName != null) { builder.field("_name", queryName); } builder.endObject(); } - private void doXArrayContent(String field, List clauses, XContentBuilder builder, Params params) throws IOException { + private static void doXArrayContent(String field, List clauses, XContentBuilder builder, Params params) throws IOException { if (clauses.isEmpty()) { return; } @@ -207,4 +328,100 @@ private void doXArrayContent(String field, List clauses, XContentB public String queryId() { return NAME; } + + @Override + public Query toQuery(QueryParseContext parseContext) throws QueryParsingException, IOException { + if (!hasClauses()) { + return new MatchAllDocsQuery(); + } + + BooleanQuery booleanQuery = new BooleanQuery(this.disableCoord); + addBooleanClauses(parseContext, booleanQuery, this.mustClauses, BooleanClause.Occur.MUST); + addBooleanClauses(parseContext, booleanQuery, this.mustNotClauses, BooleanClause.Occur.MUST_NOT); + addBooleanClauses(parseContext, booleanQuery, this.shouldClauses, BooleanClause.Occur.SHOULD); + addBooleanClauses(parseContext, booleanQuery, this.filterClauses, BooleanClause.Occur.FILTER); + + booleanQuery.setBoost(this.boost); + Queries.applyMinimumShouldMatch(booleanQuery, this.minimumShouldMatch); + Query query = this.adjustPureNegative ? fixNegativeQueryIfNeeded(booleanQuery) : booleanQuery; + if (this.queryName != null) { + parseContext.addNamedQuery(this.queryName, query); + } + return query; + } + + @Override + public QueryValidationException validate() { + // nothing to validate, clauses are optional, see hasClauses(), other parameters have defaults + return null; + } + + private static void addBooleanClauses(QueryParseContext parseContext, BooleanQuery booleanQuery, List clauses, Occur occurs) + throws IOException { + for (QueryBuilder query : clauses) { + Query luceneQuery = query.toQuery(parseContext); + if (luceneQuery != null) { + booleanQuery.add(new BooleanClause(luceneQuery, occurs)); + } + } + } + + @Override + public int hashCode() { + return Objects.hash(boost, adjustPureNegative, disableCoord, + minimumShouldMatch, queryName, mustClauses, shouldClauses, mustNotClauses, filterClauses); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + BoolQueryBuilder other = (BoolQueryBuilder) obj; + return Objects.equals(boost, other.boost) && + Objects.equals(adjustPureNegative, other.adjustPureNegative) && + Objects.equals(disableCoord, other.disableCoord) && + Objects.equals(minimumShouldMatch, other.minimumShouldMatch) && + Objects.equals(queryName, other.queryName) && + Objects.equals(mustClauses, other.mustClauses) && + Objects.equals(shouldClauses, other.shouldClauses) && + Objects.equals(mustNotClauses, other.mustNotClauses) && + Objects.equals(filterClauses, other.filterClauses); + } + + @Override + public BoolQueryBuilder readFrom(StreamInput in) throws IOException { + BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder(); + List queryBuilders = in.readNamedWritableList(); + boolQueryBuilder.must(queryBuilders); + queryBuilders = in.readNamedWritableList(); + boolQueryBuilder.mustNot(queryBuilders); + queryBuilders = in.readNamedWritableList(); + boolQueryBuilder.should(queryBuilders); + queryBuilders = in.readNamedWritableList(); + boolQueryBuilder.filter(queryBuilders); + boolQueryBuilder.boost = in.readFloat(); + boolQueryBuilder.adjustPureNegative = in.readBoolean(); + boolQueryBuilder.disableCoord = in.readBoolean(); + boolQueryBuilder.queryName = in.readOptionalString(); + boolQueryBuilder.minimumShouldMatch = in.readOptionalString(); + return boolQueryBuilder; + + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeNamedWritableList(this.mustClauses); + out.writeNamedWritableList(this.mustNotClauses); + out.writeNamedWritableList(this.shouldClauses); + out.writeNamedWritableList(this.filterClauses); + out.writeFloat(this.boost); + out.writeBoolean(this.adjustPureNegative); + out.writeBoolean(this.disableCoord); + out.writeOptionalString(queryName); + out.writeOptionalString(this.minimumShouldMatch); + } } diff --git a/core/src/main/java/org/elasticsearch/index/query/BoolQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/BoolQueryParser.java index 34a947c302f86..68725c0f8881f 100644 --- a/core/src/main/java/org/elasticsearch/index/query/BoolQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/BoolQueryParser.java @@ -19,12 +19,8 @@ package org.elasticsearch.index.query; -import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.MatchAllDocsQuery; -import org.apache.lucene.search.Query; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentParser; @@ -32,12 +28,11 @@ import java.util.List; import static com.google.common.collect.Lists.newArrayList; -import static org.elasticsearch.common.lucene.search.Queries.fixNegativeQueryIfNeeded; /** - * + * Parser for the {@link BoolQueryBuilder} */ -public class BoolQueryParser extends BaseQueryParserTemp { +public class BoolQueryParser extends BaseQueryParser { @Inject public BoolQueryParser(Settings settings) { @@ -50,19 +45,23 @@ public String[] names() { } @Override - public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException { + public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, QueryParsingException { XContentParser parser = parseContext.parser(); - boolean disableCoord = false; + boolean disableCoord = BoolQueryBuilder.DISABLE_COORD_DEFAULT; + boolean adjustPureNegative = BoolQueryBuilder.ADJUST_PURE_NEGATIVE_DEFAULT; float boost = 1.0f; String minimumShouldMatch = null; - List clauses = newArrayList(); - boolean adjustPureNegative = true; + List mustClauses = newArrayList(); + List mustNotClauses = newArrayList(); + List shouldClauses = newArrayList(); + List filterClauses = newArrayList(); String queryName = null; String currentFieldName = null; XContentParser.Token token; + QueryBuilder query; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { currentFieldName = parser.currentName(); @@ -71,31 +70,31 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars } else if (token == XContentParser.Token.START_OBJECT) { switch (currentFieldName) { case "must": - Query query = parseContext.parseInnerQuery(); + query = parseContext.parseInnerQueryBuilder(); if (query != null) { - clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST)); + mustClauses.add(query); } break; case "should": - query = parseContext.parseInnerQuery(); + query = parseContext.parseInnerQueryBuilder(); if (query != null) { - clauses.add(new BooleanClause(query, BooleanClause.Occur.SHOULD)); + shouldClauses.add(query); if (parseContext.isFilter() && minimumShouldMatch == null) { minimumShouldMatch = "1"; } } break; case "filter": - query = parseContext.parseInnerFilter(); + query = parseContext.parseInnerFilterToQueryBuilder(); if (query != null) { - clauses.add(new BooleanClause(query, BooleanClause.Occur.FILTER)); + filterClauses.add(query); } break; case "must_not": case "mustNot": - query = parseContext.parseInnerFilter(); + query = parseContext.parseInnerFilterToQueryBuilder(); if (query != null) { - clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST_NOT)); + mustNotClauses.add(query); } break; default: @@ -105,31 +104,31 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { switch (currentFieldName) { case "must": - Query query = parseContext.parseInnerQuery(); + query = parseContext.parseInnerQueryBuilder(); if (query != null) { - clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST)); + mustClauses.add(query); } break; case "should": - query = parseContext.parseInnerQuery(); + query = parseContext.parseInnerQueryBuilder(); if (query != null) { - clauses.add(new BooleanClause(query, BooleanClause.Occur.SHOULD)); + shouldClauses.add(query); if (parseContext.isFilter() && minimumShouldMatch == null) { minimumShouldMatch = "1"; } } break; case "filter": - query = parseContext.parseInnerFilter(); + query = parseContext.parseInnerFilterToQueryBuilder(); if (query != null) { - clauses.add(new BooleanClause(query, BooleanClause.Occur.FILTER)); + filterClauses.add(query); } break; case "must_not": case "mustNot": - query = parseContext.parseInnerFilter(); + query = parseContext.parseInnerFilterToQueryBuilder(); if (query != null) { - clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST_NOT)); + mustNotClauses.add(query); } break; default: @@ -154,22 +153,18 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars } } } - - if (clauses.isEmpty()) { - return new MatchAllDocsQuery(); - } - - BooleanQuery booleanQuery = new BooleanQuery(disableCoord); - for (BooleanClause clause : clauses) { - booleanQuery.add(clause); - } - booleanQuery.setBoost(boost); - Queries.applyMinimumShouldMatch(booleanQuery, minimumShouldMatch); - Query query = adjustPureNegative ? fixNegativeQueryIfNeeded(booleanQuery) : booleanQuery; - if (queryName != null) { - parseContext.addNamedQuery(queryName, query); - } - return query; + BoolQueryBuilder boolQuery = new BoolQueryBuilder(); + boolQuery.must(mustClauses); + boolQuery.mustNot(mustNotClauses); + boolQuery.should(shouldClauses); + boolQuery.filter(filterClauses); + boolQuery.boost(boost); + boolQuery.disableCoord(disableCoord); + boolQuery.adjustPureNegative(adjustPureNegative); + boolQuery.minimumNumberShouldMatch(minimumShouldMatch); + boolQuery.queryName(queryName); + boolQuery.validate(); + return boolQuery; } @Override diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java b/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java index 40e00d4df5703..8e45743e43be0 100644 --- a/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java +++ b/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java @@ -267,18 +267,37 @@ public Query parseInnerQuery() throws IOException, QueryParsingException { return result; } + /** + * @deprecated replaced by calls to parseInnerFilterToQueryBuilder() for the resulting queries + */ @Nullable + @Deprecated public Query parseInnerFilter() throws QueryParsingException, IOException { + QueryBuilder builder = parseInnerFilterToQueryBuilder(); + Query result = null; + if (builder != null) { + result = builder.toQuery(this); + } + return result; + } + + /** + * @return + * @throws QueryParsingException + * @throws IOException + */ + @Nullable + public QueryBuilder parseInnerFilterToQueryBuilder() throws QueryParsingException, IOException { final boolean originalIsFilter = isFilter; try { isFilter = true; - return parseInnerQuery(); + return parseInnerQueryBuilder(); } finally { isFilter = originalIsFilter; } } - public Query parseInnerFilter(String queryName) throws IOException, QueryParsingException { + public QueryBuilder parseInnerFilterToQueryBuilder(String queryName) throws IOException, QueryParsingException { final boolean originalIsFilter = isFilter; try { isFilter = true; @@ -286,12 +305,22 @@ public Query parseInnerFilter(String queryName) throws IOException, QueryParsing if (queryParser == null) { throw new QueryParsingException(this, "No query registered for [" + queryName + "]"); } - return queryParser.parse(this); + return queryParser.fromXContent(this); } finally { isFilter = originalIsFilter; } } + /** + * @deprecated replaced by calls to parseInnerFilterToQueryBuilder(String queryName) for the resulting queries + */ + @Nullable + @Deprecated + public Query parseInnerFilter(String queryName) throws IOException, QueryParsingException { + QueryBuilder builder = parseInnerFilterToQueryBuilder(queryName); + return (builder != null) ? builder.toQuery(this) : null; + } + public Collection simpleMatchToIndexNames(String pattern) { return indexQueryParser.mapperService.simpleMatchToIndexNames(pattern, getTypes()); } diff --git a/core/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTest.java b/core/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTest.java new file mode 100644 index 0000000000000..d362a0d783a03 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTest.java @@ -0,0 +1,115 @@ +/* + * 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.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.BooleanClause.Occur; +import org.elasticsearch.common.lucene.search.Queries; + +import java.io.IOException; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; +import static org.elasticsearch.common.lucene.search.Queries.fixNegativeQueryIfNeeded; + +public class BoolQueryBuilderTest extends BaseQueryTestCase { + + @Override + protected void assertLuceneQuery(BoolQueryBuilder queryBuilder, Query query, QueryParseContext context) { + if (queryBuilder.queryName() != null) { + Query namedQuery = context.copyNamedFilters().get(queryBuilder.queryName()); + if (queryBuilder.hasClauses()) { + assertThat(namedQuery, equalTo(query)); + } else { + assertNull(namedQuery); + } + } + } + + @Override + protected BoolQueryBuilder createTestQueryBuilder() { + BoolQueryBuilder query = new BoolQueryBuilder(); + if (randomBoolean()) { + query.boost(2.0f / randomIntBetween(1, 20)); + } + if (randomBoolean()) { + query.adjustPureNegative(randomBoolean()); + } + if (randomBoolean()) { + query.disableCoord(randomBoolean()); + } + if (randomBoolean()) { + query.minimumNumberShouldMatch(randomIntBetween(1, 10)); + } + int mustClauses = randomIntBetween(0, 3); + for (int i = 0; i < mustClauses; i++) { + query.must(RandomQueryBuilder.create(random())); + } + int mustNotClauses = randomIntBetween(0, 3); + for (int i = 0; i < mustNotClauses; i++) { + query.mustNot(RandomQueryBuilder.create(random())); + } + int shouldClauses = randomIntBetween(0, 3); + for (int i = 0; i < shouldClauses; i++) { + query.should(RandomQueryBuilder.create(random())); + } + int filterClauses = randomIntBetween(0, 3); + for (int i = 0; i < filterClauses; i++) { + query.filter(RandomQueryBuilder.create(random())); + } + if (randomBoolean()) { + query.queryName(randomUnicodeOfLengthBetween(3, 15)); + } + return query; + } + + @Override + protected BoolQueryBuilder createEmptyQueryBuilder() { + return new BoolQueryBuilder(); + } + + @Override + protected Query createExpectedQuery(BoolQueryBuilder queryBuilder, QueryParseContext context) throws IOException { + if (!queryBuilder.hasClauses()) { + return new MatchAllDocsQuery(); + } + + BooleanQuery boolQuery = new BooleanQuery(queryBuilder.disableCoord()); + boolQuery.setBoost(queryBuilder.boost()); + addBooleanClauses(context, boolQuery, queryBuilder.must(), BooleanClause.Occur.MUST); + addBooleanClauses(context, boolQuery, queryBuilder.mustNot(), BooleanClause.Occur.MUST_NOT); + addBooleanClauses(context, boolQuery, queryBuilder.should(), BooleanClause.Occur.SHOULD); + addBooleanClauses(context, boolQuery, queryBuilder.filter(), BooleanClause.Occur.FILTER); + + Queries.applyMinimumShouldMatch(boolQuery, queryBuilder.minimumNumberShouldMatch()); + Query returnedQuery = queryBuilder.adjustPureNegative() ? fixNegativeQueryIfNeeded(boolQuery) : boolQuery; + return returnedQuery; + } + + private static void addBooleanClauses(QueryParseContext parseContext, BooleanQuery booleanQuery, List clauses, Occur occurs) + throws IOException { + for (QueryBuilder query : clauses) { + booleanQuery.add(new BooleanClause(query.toQuery(parseContext), occurs)); + } + } +} diff --git a/core/src/test/java/org/elasticsearch/index/query/RandomQueryBuilder.java b/core/src/test/java/org/elasticsearch/index/query/RandomQueryBuilder.java new file mode 100644 index 0000000000000..ea24723bbd729 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/index/query/RandomQueryBuilder.java @@ -0,0 +1,49 @@ +/* + * 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 com.carrotsearch.randomizedtesting.generators.RandomInts; + +import java.util.Random; + +/** + * Utility class for creating random QueryBuilders. + * So far only leaf queries like {@link MatchAllQueryBuilder}, {@link TermQueryBuilder} or + * {@link IdsQueryBuilder} are returned. + */ +public class RandomQueryBuilder { + + /** + * @param r random seed + * @return a random {@link QueryBuilder} + */ + public static QueryBuilder create(Random r) { + QueryBuilder query = null; + switch (RandomInts.randomIntBetween(r, 0, 2)) { + case 0: + return new MatchAllQueryBuilderTest().createTestQueryBuilder(); + case 1: + return new TermQueryBuilderTest().createTestQueryBuilder(); + case 2: + return new IdsQueryBuilderTest().createTestQueryBuilder(); + } + return query; + } +}