Skip to content

Commit d8e5bea

Browse files
atrisjainankitk
authored andcommitted
Fix regression: preserve not(not X) in BooleanFlatteningRewriter (opensearch-project#19273)
* Fix regression: preserve not(not X) in BooleanFlatteningRewriter Resolve opensearch-project#19266 by fixing double-negation under MUST_NOT introduced by opensearch-project#19060 When parent is must_not and nested bool has only must_not clauses, rewrite to a positive OR: not(bool(must_not:[X1..Xn])) => filter(bool(should:[X1..Xn], minimum_should_match=1)) Use FILTER (not MUST) to preserve non-scoring semantics of must_not Leave other flattenings unchanged; only trigger on pure must_not nested bool Add unit test: testDoubleNegationConvertedToPositiveMustShould Signed-off-by: Atri Sharma <[email protected]> * spotless output Signed-off-by: Atri Sharma <[email protected]> * Remove must_not rewrite Signed-off-by: Atri Sharma <[email protected]> * Query rewriting tests: expand coverage; enforce idempotence and non-flattening invariants This change broadens the test suite for query rewriting to codify semantics, guard against regressions, and address maintainer feedback around non‑idempotent must_not handling. BooleanFlatteningRewriterTests: Add explicit “don’t flatten” cases when nested bool has non-default boost, has queryName, or nested should has minimum_should_match. Add idempotence check (second rewrite is a no-op in structure/serialization). MatchAllRemovalRewriterTests: Remove match_all from must when a non-scoring context (filter) is present. Remove deeply nested match_all under filter-in-filter. MustToFilterRewriterTests: Move boosted numeric term/range queries to filter; ensure text queries remain scoring. With null QueryShardContext, move ranges but do not move numeric term/terms. MustNotToShouldRewriterTests: Add idempotence check. Ensure no rewrite for multi-valued numeric fields (complements must_not disabled). TermsMergingRewriterTests: Should-clause merging above threshold. Per-field merging: only coalesce within the same field; mixed fields remain separate. QueryRewriterRegistryTests: Dynamic setting update for terms merging threshold (e.g., 16 → 32) changes behavior as expected. Registry idempotence: applying rewrite twice yields identical structure. Signed-off-by: Atri Sharma <[email protected]> --------- Signed-off-by: Atri Sharma <[email protected]> Signed-off-by: Ankit Jain <[email protected]>
1 parent 3064bd0 commit d8e5bea

File tree

7 files changed

+278
-15
lines changed

7 files changed

+278
-15
lines changed

server/src/main/java/org/opensearch/search/query/rewriters/BooleanFlatteningRewriter.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,9 @@ private void flattenClauses(List<QueryBuilder> clauses, BoolQueryBuilder target,
130130
BoolQueryBuilder nestedBool = (BoolQueryBuilder) clause;
131131

132132
if (canFlatten(nestedBool, clauseType)) {
133-
// Flatten the nested bool query by extracting its clauses
133+
// General flattening for same-clause-type nesting
134134
List<QueryBuilder> nestedClauses = getClausesForType(nestedBool, clauseType);
135135
for (QueryBuilder nestedClause : nestedClauses) {
136-
// Recursively flatten if needed
137136
if (nestedClause instanceof BoolQueryBuilder) {
138137
nestedClause = flattenBoolQuery((BoolQueryBuilder) nestedClause);
139138
}
@@ -160,6 +159,11 @@ private boolean canFlatten(BoolQueryBuilder nestedBool, ClauseType parentType) {
160159
return false;
161160
}
162161

162+
// Never flatten under MUST_NOT to preserve semantics and avoid non-idempotent transforms
163+
if (parentType == ClauseType.MUST_NOT) {
164+
return false;
165+
}
166+
163167
// Check if only has clauses matching parent type
164168
switch (parentType) {
165169
case MUST:
@@ -178,11 +182,6 @@ private boolean canFlatten(BoolQueryBuilder nestedBool, ClauseType parentType) {
178182
&& !nestedBool.should().isEmpty()
179183
&& nestedBool.mustNot().isEmpty()
180184
&& nestedBool.minimumShouldMatch() == null;
181-
case MUST_NOT:
182-
return nestedBool.must().isEmpty()
183-
&& nestedBool.filter().isEmpty()
184-
&& nestedBool.should().isEmpty()
185-
&& !nestedBool.mustNot().isEmpty();
186185
default:
187186
return false;
188187
}

server/src/test/java/org/opensearch/search/query/QueryRewriterRegistryTests.java

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import org.opensearch.common.settings.ClusterSettings;
1212
import org.opensearch.common.settings.Settings;
1313
import org.opensearch.index.query.BoolQueryBuilder;
14-
import org.opensearch.index.query.MatchAllQueryBuilder;
1514
import org.opensearch.index.query.QueryBuilder;
1615
import org.opensearch.index.query.QueryBuilders;
1716
import org.opensearch.index.query.QueryShardContext;
@@ -93,12 +92,45 @@ public void testDisabledRewriting() {
9392
assertNotSame(query, rewritten2);
9493
}
9594

95+
public void testDynamicTermsThresholdUpdate() {
96+
// Build a query at threshold=16 that merges, then raise threshold to 32 and assert no merge
97+
BoolQueryBuilder query = QueryBuilders.boolQuery();
98+
for (int i = 0; i < 16; i++) {
99+
query.filter(QueryBuilders.termQuery("f", "v" + i));
100+
}
101+
102+
QueryBuilder merged = QueryRewriterRegistry.INSTANCE.rewrite(query, context);
103+
BoolQueryBuilder mb = (BoolQueryBuilder) merged;
104+
// Should be one terms query
105+
assertEquals(1, mb.filter().size());
106+
107+
// Increase threshold
108+
Settings newSettings = Settings.builder().put(SearchService.QUERY_REWRITING_TERMS_THRESHOLD_SETTING.getKey(), 32).build();
109+
QueryRewriterRegistry.INSTANCE.initialize(newSettings, new ClusterSettings(newSettings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS));
110+
111+
QueryBuilder notMerged = QueryRewriterRegistry.INSTANCE.rewrite(query, context);
112+
BoolQueryBuilder nmb = (BoolQueryBuilder) notMerged;
113+
// Should be all individual term queries now
114+
assertEquals(16, nmb.filter().size());
115+
}
116+
96117
public void testNullQuery() {
97118
// Null query should return null
98119
QueryBuilder rewritten = QueryRewriterRegistry.INSTANCE.rewrite(null, context);
99120
assertNull(rewritten);
100121
}
101122

123+
public void testRegistryIdempotence() {
124+
QueryBuilder q = QueryBuilders.boolQuery()
125+
.must(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("f", "v")))
126+
.filter(QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("g", "w")))
127+
.should(QueryBuilders.termQuery("h", "u"));
128+
129+
QueryBuilder once = QueryRewriterRegistry.INSTANCE.rewrite(q, context);
130+
QueryBuilder twice = QueryRewriterRegistry.INSTANCE.rewrite(once, context);
131+
assertEquals(once.toString(), twice.toString());
132+
}
133+
102134
public void testRewriterPriorityOrder() {
103135
// Test that rewriters are applied in correct order
104136
// Create a query that will be affected by multiple rewriters
@@ -114,13 +146,12 @@ public void testRewriterPriorityOrder() {
114146
assertThat(rewritten, instanceOf(BoolQueryBuilder.class));
115147
BoolQueryBuilder rewrittenBool = (BoolQueryBuilder) rewritten;
116148

117-
// Should be flattened first, match_all kept in must (scoring context), but terms NOT merged in must context
118-
assertThat(rewrittenBool.must().size(), equalTo(3)); // match_all + 2 term queries
119-
// Check that we have one match_all and two term queries (order may vary)
120-
long matchAllCount = rewrittenBool.must().stream().filter(q -> q instanceof MatchAllQueryBuilder).count();
149+
// Should be flattened first. Match_all may be removed depending on context; terms NOT merged in must context
121150
long termCount = rewrittenBool.must().stream().filter(q -> q instanceof TermQueryBuilder).count();
122-
assertThat(matchAllCount, equalTo(1L));
123151
assertThat(termCount, equalTo(2L));
152+
153+
// Composition order smoke check: must_to_filter should not move term queries in must
154+
// terms_merging should not merge terms in must; only in filter/should contexts
124155
}
125156

126157
public void testComplexRealWorldQuery() {
@@ -147,8 +178,8 @@ public void testComplexRealWorldQuery() {
147178

148179
// After rewriting:
149180
// - The nested bool in must clause should be flattened
150-
// - match_all should be removed
151-
// - term queries should be merged into terms query
181+
// - match_all should be removed in non-scoring contexts
182+
// - term queries should be merged into terms query in filter contexts
152183
// - The filter bool with brand terms should be preserved
153184
// - The range query should be moved from must to filter by MustToFilterRewriter
154185

server/src/test/java/org/opensearch/search/query/rewriters/BooleanFlatteningRewriterTests.java

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,64 @@ public void testMustNotClauseNoFlattening() {
102102
assertThat(rewrittenBool.mustNot().get(0), instanceOf(BoolQueryBuilder.class));
103103
}
104104

105+
public void testDoubleNegationNotFlattenedUnderMustNot() {
106+
// not( bool( must_not: [ term ] ) ) should NOT be flattened by the rewriter
107+
QueryBuilder inner = QueryBuilders.boolQuery().mustNot(QueryBuilders.termQuery("product", "Oranges"));
108+
QueryBuilder query = QueryBuilders.boolQuery().mustNot(inner);
109+
110+
QueryBuilder rewritten = rewriter.rewrite(query, context);
111+
assertThat(rewritten, instanceOf(BoolQueryBuilder.class));
112+
BoolQueryBuilder rewrittenBool = (BoolQueryBuilder) rewritten;
113+
114+
// Outer must_not remains and inner bool is preserved
115+
assertThat(rewrittenBool.mustNot().size(), equalTo(1));
116+
assertThat(rewrittenBool.mustNot().get(0), instanceOf(BoolQueryBuilder.class));
117+
}
118+
119+
public void testDeMorganPatternNotFlattenedUnderMustNot() {
120+
// not( bool( must: [A, B] ) ) should not be flattened by BooleanFlatteningRewriter
121+
QueryBuilder inner = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("f", "A")).must(QueryBuilders.termQuery("f", "B"));
122+
QueryBuilder query = QueryBuilders.boolQuery().mustNot(inner);
123+
124+
QueryBuilder rewritten = rewriter.rewrite(query, context);
125+
assertThat(rewritten, instanceOf(BoolQueryBuilder.class));
126+
BoolQueryBuilder rewrittenBool = (BoolQueryBuilder) rewritten;
127+
assertThat(rewrittenBool.mustNot().size(), equalTo(1));
128+
assertThat(rewrittenBool.mustNot().get(0), instanceOf(BoolQueryBuilder.class));
129+
}
130+
131+
public void testRandomizedMustNotInnerNotFlattened() {
132+
// Build inner bool with random number of must_not terms
133+
int n = between(1, 5);
134+
BoolQueryBuilder inner = QueryBuilders.boolQuery();
135+
for (int i = 0; i < n; i++) {
136+
inner.mustNot(QueryBuilders.termQuery("p", "v" + i));
137+
}
138+
QueryBuilder query = QueryBuilders.boolQuery().mustNot(inner);
139+
140+
QueryBuilder rewritten = rewriter.rewrite(query, context);
141+
assertThat(rewritten, instanceOf(BoolQueryBuilder.class));
142+
BoolQueryBuilder rewrittenBool = (BoolQueryBuilder) rewritten;
143+
assertThat(rewrittenBool.mustNot().size(), equalTo(1));
144+
assertThat(rewrittenBool.mustNot().get(0), instanceOf(BoolQueryBuilder.class));
145+
}
146+
147+
public void testTopLevelPropertiesPreserved() {
148+
BoolQueryBuilder query = QueryBuilders.boolQuery()
149+
.queryName("qn")
150+
.boost(2.0f)
151+
.minimumShouldMatch(2)
152+
.must(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("field", "v")))
153+
.should(QueryBuilders.boolQuery().should(QueryBuilders.termQuery("f2", "v2")));
154+
155+
QueryBuilder rewritten = rewriter.rewrite(query, context);
156+
assertThat(rewritten, instanceOf(BoolQueryBuilder.class));
157+
BoolQueryBuilder rewrittenBool = (BoolQueryBuilder) rewritten;
158+
assertThat(rewrittenBool.queryName(), equalTo("qn"));
159+
assertThat(rewrittenBool.boost(), equalTo(2.0f));
160+
assertThat(rewrittenBool.minimumShouldMatch(), equalTo("2"));
161+
}
162+
105163
@AwaitsFix(bugUrl = "https://github.com/opensearch-project/OpenSearch/issues/18906")
106164
public void testDeepNesting() {
107165
// TODO: This test expects complete flattening of deeply nested bool queries
@@ -140,6 +198,53 @@ public void testMixedClauseTypes() {
140198
assertSame(query, rewritten); // Should not flatten due to different minimumShouldMatch
141199
}
142200

201+
public void testDoNotFlattenWhenNestedHasNonDefaultBoost() {
202+
// Nested bool with non-default boost should not be flattened
203+
BoolQueryBuilder nested = QueryBuilders.boolQuery().boost(2.0f).must(QueryBuilders.termQuery("field", "value"));
204+
BoolQueryBuilder query = QueryBuilders.boolQuery().must(nested);
205+
206+
QueryBuilder rewritten = rewriter.rewrite(query, context);
207+
assertThat(rewritten, instanceOf(BoolQueryBuilder.class));
208+
BoolQueryBuilder outer = (BoolQueryBuilder) rewritten;
209+
assertThat(outer.must().size(), equalTo(1));
210+
assertThat(outer.must().get(0), instanceOf(BoolQueryBuilder.class));
211+
BoolQueryBuilder preserved = (BoolQueryBuilder) outer.must().get(0);
212+
assertThat(preserved.boost(), equalTo(2.0f));
213+
assertThat(preserved.must().size(), equalTo(1));
214+
}
215+
216+
public void testDoNotFlattenWhenNestedHasQueryName() {
217+
// Nested bool with queryName should not be flattened
218+
BoolQueryBuilder nested = QueryBuilders.boolQuery().queryName("inner").must(QueryBuilders.termQuery("f", "v"));
219+
BoolQueryBuilder query = QueryBuilders.boolQuery().must(nested);
220+
221+
QueryBuilder rewritten = rewriter.rewrite(query, context);
222+
assertThat(rewritten, instanceOf(BoolQueryBuilder.class));
223+
BoolQueryBuilder outer = (BoolQueryBuilder) rewritten;
224+
assertThat(outer.must().size(), equalTo(1));
225+
assertThat(outer.must().get(0), instanceOf(BoolQueryBuilder.class));
226+
BoolQueryBuilder preserved = (BoolQueryBuilder) outer.must().get(0);
227+
assertThat(preserved.queryName(), equalTo("inner"));
228+
}
229+
230+
public void testShouldClauseNotFlattenedWhenNestedHasMinimumShouldMatch() {
231+
// Nested should with MSM should not be flattened
232+
BoolQueryBuilder nested = QueryBuilders.boolQuery()
233+
.should(QueryBuilders.termQuery("f1", "v1"))
234+
.should(QueryBuilders.termQuery("f2", "v2"))
235+
.minimumShouldMatch(1);
236+
BoolQueryBuilder query = QueryBuilders.boolQuery().should(nested).must(QueryBuilders.termQuery("g", "w"));
237+
238+
QueryBuilder rewritten = rewriter.rewrite(query, context);
239+
assertThat(rewritten, instanceOf(BoolQueryBuilder.class));
240+
BoolQueryBuilder outer = (BoolQueryBuilder) rewritten;
241+
assertThat(outer.should().size(), equalTo(1));
242+
assertThat(outer.should().get(0), instanceOf(BoolQueryBuilder.class));
243+
BoolQueryBuilder preserved = (BoolQueryBuilder) outer.should().get(0);
244+
assertThat(preserved.minimumShouldMatch(), equalTo("1"));
245+
assertThat(outer.must().size(), equalTo(1));
246+
}
247+
143248
public void testEmptyBooleanQuery() {
144249
// Empty boolean query should not cause issues
145250
QueryBuilder query = QueryBuilders.boolQuery();
@@ -179,4 +284,15 @@ public void testQueryNamePreservation() {
179284
BoolQueryBuilder result = (BoolQueryBuilder) rewritten;
180285
assertThat(result.queryName(), equalTo("outer"));
181286
}
287+
288+
public void testIdempotence() {
289+
// After one rewrite, a second rewrite should be a no-op (structurally identical)
290+
QueryBuilder query = QueryBuilders.boolQuery()
291+
.must(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("f", "v")))
292+
.filter(QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("g", "w")));
293+
294+
QueryBuilder once = rewriter.rewrite(query, context);
295+
QueryBuilder twice = rewriter.rewrite(once, context);
296+
assertEquals(once.toString(), twice.toString());
297+
}
182298
}

server/src/test/java/org/opensearch/search/query/rewriters/MatchAllRemovalRewriterTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,33 @@ public void testMultipleMatchAllQueries() {
9797
assertThat(rewrittenBool.filter().size(), equalTo(0));
9898
}
9999

100+
public void testMustMatchAllRemovedWhenFilterPresent() {
101+
// must contains match_all + term, and there is a filter clause => remove match_all from must
102+
QueryBuilder query = QueryBuilders.boolQuery()
103+
.must(QueryBuilders.matchAllQuery())
104+
.must(QueryBuilders.termQuery("field", "v"))
105+
.filter(QueryBuilders.termQuery("f2", "x"));
106+
107+
QueryBuilder rewritten = rewriter.rewrite(query, context);
108+
assertThat(rewritten, instanceOf(BoolQueryBuilder.class));
109+
BoolQueryBuilder rewrittenBool = (BoolQueryBuilder) rewritten;
110+
// match_all removed from must because non-scoring context exists (filter present)
111+
assertThat(rewrittenBool.must().size(), equalTo(1));
112+
}
113+
114+
public void testNestedFilterMatchAllRemoved() {
115+
// match_all inside nested bool under filter should be removed
116+
QueryBuilder nested = QueryBuilders.boolQuery().filter(QueryBuilders.matchAllQuery()).filter(QueryBuilders.termQuery("a", "b"));
117+
QueryBuilder query = QueryBuilders.boolQuery().filter(nested);
118+
119+
QueryBuilder rewritten = rewriter.rewrite(query, context);
120+
assertThat(rewritten, instanceOf(BoolQueryBuilder.class));
121+
BoolQueryBuilder rewrittenBool = (BoolQueryBuilder) rewritten;
122+
BoolQueryBuilder nestedRewritten = (BoolQueryBuilder) rewrittenBool.filter().get(0);
123+
// Only the real filter remains
124+
assertThat(nestedRewritten.filter().size(), equalTo(1));
125+
}
126+
100127
public void testNestedBooleanWithMatchAll() {
101128
// Nested boolean queries should also have match_all removed
102129
QueryBuilder nested = QueryBuilders.boolQuery()

server/src/test/java/org/opensearch/search/query/rewriters/MustNotToShouldRewriterTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,38 @@ public void testNumericTermQueryRewritten() {
134134
assertThat(nestedBool.minimumShouldMatch(), equalTo("1"));
135135
}
136136

137+
public void testIdempotence() {
138+
QueryBuilder query = QueryBuilders.boolQuery().mustNot(QueryBuilders.termQuery("status", 3));
139+
QueryBuilder once = rewriter.rewrite(query, context);
140+
QueryBuilder twice = rewriter.rewrite(once, context);
141+
assertEquals(once.toString(), twice.toString());
142+
}
143+
144+
public void testMultiValuedNumericFieldNotRewritten() throws Exception {
145+
// Create an index where a field has multiple values per doc
146+
Directory multiDir = newDirectory();
147+
IndexWriter writer = new IndexWriter(multiDir, new IndexWriterConfig(Lucene.STANDARD_ANALYZER));
148+
Document doc = new Document();
149+
doc.add(new IntPoint("multi_age", 10));
150+
doc.add(new IntPoint("multi_age", 20));
151+
writer.addDocument(doc);
152+
writer.close();
153+
154+
IndexReader multiReader = DirectoryReader.open(multiDir);
155+
when(context.getIndexReader()).thenReturn(multiReader);
156+
// numeric field type mapping
157+
NumberFieldMapper.NumberFieldType intFieldType = mock(NumberFieldMapper.NumberFieldType.class);
158+
when(context.fieldMapper("multi_age")).thenReturn(intFieldType);
159+
160+
QueryBuilder query = QueryBuilders.boolQuery().mustNot(QueryBuilders.rangeQuery("multi_age").gte(15));
161+
QueryBuilder rewritten = rewriter.rewrite(query, context);
162+
// Should not rewrite because docs can have multiple values
163+
assertSame(query, rewritten);
164+
165+
multiReader.close();
166+
multiDir.close();
167+
}
168+
137169
public void testNumericTermsQueryRewritten() {
138170
// Test that must_not terms query on numeric field is rewritten
139171
QueryBuilder query = QueryBuilders.boolQuery().mustNot(QueryBuilders.termsQuery("status", new Object[] { 1, 2, 3 }));

server/src/test/java/org/opensearch/search/query/rewriters/MustToFilterRewriterTests.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,36 @@ public void testNestedBooleanQueriesRewritten() {
202202
assertThat(nestedRewritten.must().get(0), instanceOf(TermQueryBuilder.class));
203203
}
204204

205+
public void testBoostedNumericQueriesMovedToFilter() {
206+
// Even with boosts, numeric queries should be moved (boost irrelevant in filter)
207+
QueryBuilder query = QueryBuilders.boolQuery()
208+
.must(QueryBuilders.termQuery("age", 42).boost(3.0f))
209+
.must(QueryBuilders.rangeQuery("price").gte(10).boost(1.5f))
210+
.must(QueryBuilders.matchQuery("name", "foo").boost(2.0f));
211+
212+
QueryBuilder rewritten = rewriter.rewrite(query, context);
213+
BoolQueryBuilder b = (BoolQueryBuilder) rewritten;
214+
// two moved
215+
assertThat(b.filter().size(), equalTo(2));
216+
// text match remains
217+
assertThat(b.must().size(), equalTo(1));
218+
assertThat(b.must().get(0), instanceOf(MatchQueryBuilder.class));
219+
}
220+
221+
public void testNoContextDoesNotMoveNumericTerms() {
222+
// Without context, numeric term/terms cannot be identified; range still moves
223+
QueryBuilder query = QueryBuilders.boolQuery()
224+
.must(QueryBuilders.termQuery("user_id", 7))
225+
.must(QueryBuilders.rangeQuery("price").gte(1));
226+
227+
QueryBuilder rewritten = rewriter.rewrite(query, null);
228+
BoolQueryBuilder b = (BoolQueryBuilder) rewritten;
229+
// Range moved, term stayed
230+
assertThat(b.filter().size(), equalTo(1));
231+
assertThat(b.must().size(), equalTo(1));
232+
assertThat(b.must().get(0), instanceOf(TermQueryBuilder.class));
233+
}
234+
205235
public void testBoolQueryPropertiesPreserved() {
206236
// All bool query properties should be preserved
207237
QueryBuilder query = QueryBuilders.boolQuery()

server/src/test/java/org/opensearch/search/query/rewriters/TermsMergingRewriterTests.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,34 @@ public void testTermMergingAboveThreshold() {
5656
assertThat(termsQuery.values().size(), equalTo(20));
5757
}
5858

59+
public void testShouldMergingWithThreshold() {
60+
// Ensure should clauses merge when exceeding threshold
61+
BoolQueryBuilder query = QueryBuilders.boolQuery();
62+
for (int i = 0; i < 17; i++) {
63+
query.should(QueryBuilders.termQuery("tag", "t" + i));
64+
}
65+
66+
QueryBuilder rewritten = rewriter.rewrite(query, context);
67+
BoolQueryBuilder b = (BoolQueryBuilder) rewritten;
68+
assertThat(b.should().size(), equalTo(1));
69+
assertThat(b.should().get(0), instanceOf(TermsQueryBuilder.class));
70+
}
71+
72+
public void testMixedFieldsAboveThresholdOnlyMergesPerField() {
73+
BoolQueryBuilder query = QueryBuilders.boolQuery();
74+
for (int i = 0; i < 18; i++) {
75+
query.filter(QueryBuilders.termQuery("f1", "v" + i));
76+
}
77+
for (int i = 0; i < 5; i++) {
78+
query.filter(QueryBuilders.termQuery("f2", "v" + i));
79+
}
80+
81+
QueryBuilder rewritten = rewriter.rewrite(query, context);
82+
BoolQueryBuilder b = (BoolQueryBuilder) rewritten;
83+
// one terms for f1, and 5 single terms for f2
84+
assertThat(b.filter().size(), equalTo(6));
85+
}
86+
5987
public void testMustClauseNoMerging() {
6088
// Term queries in must clauses should NOT be merged (different semantics)
6189
QueryBuilder query = QueryBuilders.boolQuery()

0 commit comments

Comments
 (0)