Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/110079.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 110079
summary: "feat: Add new option omit_zero_term_query to bool query"
area: Search
type: feature
issues:
- 110080
7 changes: 7 additions & 0 deletions docs/reference/query-dsl/bool-query.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ POST _search
{ "term" : { "tags" : "deployed" } }
],
"minimum_should_match" : 1,
"omit_zero_term_query" : false
"boost" : 1.0
}
}
Expand Down Expand Up @@ -206,3 +207,9 @@ response. Typically, this adds a small overhead to a request. However, using
computationally expensive named queries on a large number of hits may add
significant overhead. For example, named queries in combination with a
`top_hits` aggregation on many buckets may lead to longer response times.

[[bool-omit-zero-term-query]]
==== Using `omit_zero_term_query`

(Optional, Boolean) If set to `true`, the query will be omitted if the analyzer
removes all tokens using a stop filter or similar. Defaults to `false`.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.TransportVersion;
import org.elasticsearch.TransportVersions;
Expand Down Expand Up @@ -41,13 +42,15 @@ public class BoolQueryBuilder extends AbstractQueryBuilder<BoolQueryBuilder> {
public static final String NAME = "bool";

public static final boolean ADJUST_PURE_NEGATIVE_DEFAULT = true;
public static final boolean OMIT_ZERO_TERM_QUERY_DEFAULT = false;

private static final ParseField MUST_NOT = new ParseField("must_not").withDeprecation("mustNot");
private static final ParseField FILTER = new ParseField("filter");
private static final ParseField SHOULD = new ParseField("should");
private static final ParseField MUST = new ParseField("must");
private static final ParseField MINIMUM_SHOULD_MATCH = new ParseField("minimum_should_match");
private static final ParseField ADJUST_PURE_NEGATIVE = new ParseField("adjust_pure_negative");
private static final ParseField OMIT_ZERO_TERM_QUERY = new ParseField("omit_zero_term_query");

private final List<QueryBuilder> mustClauses = new ArrayList<>();

Expand All @@ -61,6 +64,8 @@ public class BoolQueryBuilder extends AbstractQueryBuilder<BoolQueryBuilder> {

private String minimumShouldMatch;

private boolean omitZeroTermQuery = OMIT_ZERO_TERM_QUERY_DEFAULT;

/**
* Build an empty bool query.
*/
Expand All @@ -77,6 +82,7 @@ public BoolQueryBuilder(StreamInput in) throws IOException {
filterClauses.addAll(readQueries(in));
adjustPureNegative = in.readBoolean();
minimumShouldMatch = in.readOptionalString();
omitZeroTermQuery = in.readBoolean();
}

@Override
Expand All @@ -87,6 +93,7 @@ protected void doWriteTo(StreamOutput out) throws IOException {
writeQueries(out, filterClauses);
out.writeBoolean(adjustPureNegative);
out.writeOptionalString(minimumShouldMatch);
out.writeBoolean(omitZeroTermQuery);
}

/**
Expand Down Expand Up @@ -230,6 +237,22 @@ public boolean adjustPureNegative() {
return this.adjustPureNegative;
}

/**
* Decide whether to omit query to use in case no query terms are available, e.g. after analysis removed them.
* The default is <code>true</code>.
*/
public BoolQueryBuilder omitZeroTermQuery(boolean omitZeroTermQuery) {
this.omitZeroTermQuery = omitZeroTermQuery;
return this;
}

/**
* @return the setting for the omit_zero_term_query setting in this query
*/
public boolean omitZeroTermQuery() {
return this.omitZeroTermQuery;
}

@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
Expand All @@ -245,6 +268,7 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
if (minimumShouldMatch != null) {
builder.field(MINIMUM_SHOULD_MATCH.getPreferredName(), minimumShouldMatch);
}
builder.field(OMIT_ZERO_TERM_QUERY.getPreferredName(), omitZeroTermQuery);
printBoostAndQueryName(builder);
builder.endObject();
}
Expand Down Expand Up @@ -286,6 +310,7 @@ private static void doXArrayContent(ParseField field, List<QueryBuilder> clauses
MINIMUM_SHOULD_MATCH,
ObjectParser.ValueType.VALUE
);
PARSER.declareBoolean(BoolQueryBuilder::omitZeroTermQuery, OMIT_ZERO_TERM_QUERY);
PARSER.declareString(BoolQueryBuilder::queryName, NAME_FIELD);
PARSER.declareFloat(BoolQueryBuilder::boost, BOOST_FIELD);
}
Expand All @@ -308,33 +333,50 @@ protected Query doToQuery(SearchExecutionContext context) throws IOException {
addBooleanClauses(context, booleanQueryBuilder, filterClauses, BooleanClause.Occur.FILTER);
BooleanQuery booleanQuery = booleanQueryBuilder.build();
if (booleanQuery.clauses().isEmpty()) {
return new MatchAllDocsQuery();
if (omitZeroTermQuery) {
return new MatchNoDocsQuery();
} else {
return new MatchAllDocsQuery();
}

}

Query query = Queries.applyMinimumShouldMatch(booleanQuery, minimumShouldMatch);
return adjustPureNegative ? fixNegativeQueryIfNeeded(query) : query;
}

private static void addBooleanClauses(
private void addBooleanClauses(
SearchExecutionContext context,
BooleanQuery.Builder booleanQueryBuilder,
List<QueryBuilder> clauses,
Occur occurs
) throws IOException {
for (QueryBuilder query : clauses) {
Query luceneQuery = query.toQuery(context);
if (luceneQuery instanceof MatchNoDocsQuery && omitZeroTermQuery) {
continue;
}
booleanQueryBuilder.add(new BooleanClause(luceneQuery, occurs));
}
}

@Override
protected int doHashCode() {
return Objects.hash(adjustPureNegative, minimumShouldMatch, mustClauses, shouldClauses, mustNotClauses, filterClauses);
return Objects.hash(
omitZeroTermQuery,
adjustPureNegative,
minimumShouldMatch,
mustClauses,
shouldClauses,
mustNotClauses,
filterClauses
);
}

@Override
protected boolean doEquals(BoolQueryBuilder other) {
return Objects.equals(adjustPureNegative, other.adjustPureNegative)
return Objects.equals(omitZeroTermQuery, other.omitZeroTermQuery)
&& Objects.equals(adjustPureNegative, other.adjustPureNegative)
&& Objects.equals(minimumShouldMatch, other.minimumShouldMatch)
&& Objects.equals(mustClauses, other.mustClauses)
&& Objects.equals(shouldClauses, other.shouldClauses)
Expand Down Expand Up @@ -370,6 +412,7 @@ protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws
}

if (changed) {
newBuilder.omitZeroTermQuery = omitZeroTermQuery;
newBuilder.adjustPureNegative = adjustPureNegative;
newBuilder.minimumShouldMatch = minimumShouldMatch;
newBuilder.boost(boost());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ public void testFromJson() throws IOException {
+ " }"
+ " } ],"
+ " \"minimum_should_match\" : \"23\","
+ " \"omit_zero_term_query\" : false,"
+ " \"boost\" : 42.0"
+ "}"
+ "}";
Expand Down Expand Up @@ -463,4 +464,22 @@ public void testMustRewrite() throws IOException {
IllegalStateException e = expectThrows(IllegalStateException.class, () -> boolQuery.toQuery(context));
assertEquals("Rewrite first", e.getMessage());
}

public void testStopWordAndOmitZeroTermQueryIsTrue() throws Exception {
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder().omitZeroTermQuery(true);
boolQueryBuilder.must(new MultiMatchQueryBuilder("The").field(TEXT_FIELD_NAME).analyzer("stop"));
Query query = boolQueryBuilder.toQuery(createSearchExecutionContext());
assertThat(query, instanceOf(MatchNoDocsQuery.class));
}

public void testMultipleTokensAndOmitZeroTermQueryIsTrue() throws Exception {
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder().omitZeroTermQuery(true);
boolQueryBuilder.must(new MultiMatchQueryBuilder("The").field(TEXT_FIELD_NAME).analyzer("stop"));
boolQueryBuilder.must(new MultiMatchQueryBuilder("brown").field(TEXT_FIELD_NAME));
boolQueryBuilder.must(new MultiMatchQueryBuilder("fox").field(TEXT_FIELD_NAME));
Query query = boolQueryBuilder.toQuery(createSearchExecutionContext());
assertThat(query, instanceOf(BooleanQuery.class));
BooleanQuery booleanQuery = (BooleanQuery) query;
assertThat(booleanQuery.clauses().size(), equalTo(2));
}
}