15
15
*/
16
16
package com .b2international .index .query ;
17
17
18
- import static com .google .common .collect .Lists .newArrayList ;
19
-
18
+ import java .util .ArrayList ;
20
19
import java .util .Collection ;
21
20
import java .util .List ;
22
21
import java .util .Set ;
32
31
*/
33
32
public abstract class AbstractExpressionBuilder <B extends AbstractExpressionBuilder <B >>{
34
33
35
- protected final List <Expression > mustClauses = newArrayList ( );
36
- protected final List <Expression > mustNotClauses = newArrayList ( );
37
- protected final List <Expression > shouldClauses = newArrayList ( );
38
- protected final List <Expression > filterClauses = newArrayList ( );
34
+ protected final List <Expression > mustClauses = new ArrayList <>( 1 );
35
+ protected final List <Expression > mustNotClauses = new ArrayList <>( 1 );
36
+ protected final List <Expression > shouldClauses = new ArrayList <>( 3 );
37
+ protected final List <Expression > filterClauses = new ArrayList <>( 3 );
39
38
protected int minShouldMatch = 1 ;
40
39
41
40
protected AbstractExpressionBuilder () {}
@@ -102,9 +101,15 @@ public Expression build() {
102
101
return shouldClauses .get (0 );
103
102
}
104
103
105
- final BoolExpression be = new BoolExpression (mustClauses , mustNotClauses , shouldClauses , filterClauses );
106
- be .setMinShouldMatch (minShouldMatch );
107
- return be ;
104
+ // if after the optimization the resulting bool clauses are empty, then return a MatchNone expression
105
+ if (mustClauses .isEmpty () && mustNotClauses .isEmpty () && shouldClauses .isEmpty () && filterClauses .isEmpty ()) {
106
+ return Expressions .matchNone ();
107
+ } else {
108
+ // otherwise create the bool expression as usual
109
+ final BoolExpression be = new BoolExpression (mustClauses , mustNotClauses , shouldClauses , filterClauses );
110
+ be .setMinShouldMatch (minShouldMatch );
111
+ return be ;
112
+ }
108
113
}
109
114
}
110
115
@@ -133,11 +138,50 @@ protected final void flattenShoulds() {
133
138
134
139
protected final void mergeTermFilters () {
135
140
// check each mustNot and should clause list and merge term/terms queries into a single terms query, targeting the same field
136
- // XXX merging must/filter queries will change the boolean logic from AND to OR which leads to incorrect results
137
141
mergeTermFilters (mustNotClauses );
138
142
mergeTermFilters (shouldClauses );
143
+ // XXX merging must/filter queries will change the boolean logic from AND to OR which leads to incorrect results
144
+ // instead calculate the intersection of the values and use that for the expressions
145
+ reduceTermFilters (mustClauses );
146
+ reduceTermFilters (filterClauses );
139
147
}
140
148
149
+ private void reduceTermFilters (List <Expression > clauses ) {
150
+ Multimap <String , Expression > termExpressionsByField = HashMultimap .create ();
151
+ for (Expression expression : List .copyOf (clauses )) {
152
+ if (expression instanceof SingleArgumentPredicate <?>) {
153
+ termExpressionsByField .put (((SingleArgumentPredicate <?>) expression ).getField (), expression );
154
+ } else if (expression instanceof SetPredicate <?>) {
155
+ termExpressionsByField .put (((SetPredicate <?>) expression ).getField (), expression );
156
+ }
157
+ }
158
+
159
+ for (String field : Set .copyOf (termExpressionsByField .keySet ())) {
160
+ Collection <Expression > termExpressions = termExpressionsByField .removeAll (field );
161
+ if (termExpressions .size () > 1 ) {
162
+ Set <Object > values = null ;
163
+ for (Expression expression : termExpressions ) {
164
+ if (values != null && values .isEmpty ()) {
165
+ break ;
166
+ }
167
+ Set <Object > expressionValues ;
168
+ if (expression instanceof SingleArgumentPredicate <?>) {
169
+ expressionValues = Set .of (((SingleArgumentPredicate <?>) expression ).getArgument ());
170
+ } else if (expression instanceof SetPredicate <?>) {
171
+ expressionValues = Set .copyOf (((SetPredicate <?>) expression ).values ());
172
+ } else {
173
+ throw new IllegalStateException ("Invalid clause detected when processing term/terms clauses: " + expression );
174
+ }
175
+ values = values == null ? expressionValues : Sets .intersection (values , expressionValues );
176
+ }
177
+ // remove all matching clauses first
178
+ clauses .removeAll (termExpressions );
179
+ // add the new merged expression
180
+ clauses .add (Expressions .matchAnyObject (field , values ));
181
+ }
182
+ }
183
+ }
184
+
141
185
private void mergeTermFilters (List <Expression > clauses ) {
142
186
Multimap <String , Expression > termExpressionsByField = HashMultimap .create ();
143
187
for (Expression expression : List .copyOf (clauses )) {
@@ -159,9 +203,10 @@ private void mergeTermFilters(List<Expression> clauses) {
159
203
values .addAll (((SetPredicate <?>) expression ).values ());
160
204
}
161
205
}
162
- // replace all clauses with a single expression
163
- clauses .add (Expressions .matchAnyObject (field , values ));
206
+ // remove all matching clauses first
164
207
clauses .removeAll (termExpressions );
208
+ // add the new merged expression
209
+ clauses .add (Expressions .matchAnyObject (field , values ));
165
210
}
166
211
}
167
212
}
0 commit comments