Skip to content

Commit 0edf495

Browse files
authored
Merge pull request #1424 from b2ihealthcare/issue/SO-6491-nested-query-scoring
SO-6491: Resolve issue with tracking document scores in EsQueryBuilder
2 parents b7c2589 + aeb8a6f commit 0edf495

File tree

2 files changed

+93
-49
lines changed

2 files changed

+93
-49
lines changed

commons/com.b2international.index.tests/src/com/b2international/index/BoolQueryTest.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.b2international.index;
1717

1818
import static org.junit.Assert.assertEquals;
19+
import static org.junit.Assert.assertTrue;
1920

2021
import java.util.*;
2122
import java.util.function.BiFunction;
@@ -28,6 +29,7 @@
2829
import org.slf4j.LoggerFactory;
2930

3031
import com.b2international.index.Fixtures.Data;
32+
import com.b2international.index.Fixtures.ParentData;
3133
import com.b2international.index.admin.IndexAdmin;
3234
import com.b2international.index.es.query.EsQueryBuilder;
3335
import com.b2international.index.mapping.DocumentMapping;
@@ -46,7 +48,7 @@ public class BoolQueryTest extends BaseIndexTest {
4648

4749
@Override
4850
protected Collection<Class<?>> getTypes() {
49-
return List.of(Fixtures.Data.class);
51+
return List.of(Fixtures.Data.class, Fixtures.ParentData.class);
5052
}
5153

5254
@Test
@@ -159,6 +161,26 @@ public void mergeDisjunctTermFilterClausesOnCollection() throws Exception {
159161
assertEquals(BoolQueryBuilder.NAME, esQueryBuilder.getName());
160162
assertEquals(3, ((BoolQueryBuilder) esQueryBuilder).filter().size());
161163
}
164+
165+
@Test
166+
public void nestedScoringQuery() throws Exception {
167+
Expression actual = Expressions.bool()
168+
// This clause requires scoring
169+
.should(Expressions.nestedMatch("nestedData", Expressions.matchTextAll("nestedData.analyzedField", "multi term query")))
170+
// This one does not
171+
.should(Expressions.nestedMatch("nestedData", Expressions.exactMatch("nestedData.field2", "field2value")))
172+
.build();
173+
174+
IndexAdmin indexAdmin = index().admin();
175+
DocumentMapping mapping = indexAdmin.getIndexMapping().getMapping(ParentData.class);
176+
Map<String, Object> settings = indexAdmin.settings();
177+
178+
EsQueryBuilder esQueryBuilder = new EsQueryBuilder(mapping, settings, LOG);
179+
esQueryBuilder.build(actual);
180+
181+
// We are only interested in the value of the flag, not the actual query
182+
assertTrue(esQueryBuilder.needsScoring());
183+
}
162184

163185
private Expression generateDeepBooleanClause(int depth, BiFunction<ExpressionBuilder, Expression, ExpressionBuilder> boolClause) {
164186
ExpressionBuilder root = Expressions.bool();

commons/com.b2international.index/src/com/b2international/index/es/query/EsQueryBuilder.java

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public boolean needsScoring() {
7474
return needsScoring;
7575
}
7676

77+
public void enableScoring() {
78+
needsScoring = true;
79+
}
80+
7781
public QueryBuilder build(Expression expression) {
7882
checkNotNull(expression, "expression");
7983
visit(expression);
@@ -156,7 +160,7 @@ private void visit(ScriptScoreExpression expression) {
156160
visit(inner);
157161
final QueryBuilder innerQuery = deque.pop();
158162

159-
needsScoring = true;
163+
enableScoring();
160164
deque.push(QueryBuilders
161165
.functionScoreQuery(innerQuery, ScoreFunctionBuilders.scriptFunction(expression.toEsScript(mapping)))
162166
.boostMode(CombineFunction.REPLACE));
@@ -180,8 +184,8 @@ private void visit(BoolExpression bool) {
180184
// visit the item and immediately pop the deque item back
181185
final EsQueryBuilder innerQueryBuilder = new EsQueryBuilder(mapping, settings, log, path);
182186
innerQueryBuilder.visit(must);
183-
if (innerQueryBuilder.needsScoring) {
184-
needsScoring = innerQueryBuilder.needsScoring;
187+
if (innerQueryBuilder.needsScoring()) {
188+
enableScoring();
185189
query.must(innerQueryBuilder.deque.pop());
186190
} else {
187191
query.filter(innerQueryBuilder.deque.pop());
@@ -266,9 +270,17 @@ private void visit(NestedPredicate predicate) {
266270
final DocumentMapping nestedMapping = mapping.getNestedMapping(predicate.getField());
267271
final EsQueryBuilder nestedQueryBuilder = new EsQueryBuilder(nestedMapping, settings, log, nestedPath);
268272
nestedQueryBuilder.visit(predicate.getExpression());
269-
needsScoring = nestedQueryBuilder.needsScoring;
273+
274+
final ScoreMode scoreMode;
275+
if (nestedQueryBuilder.needsScoring()) {
276+
enableScoring();
277+
scoreMode = ScoreMode.Max;
278+
} else {
279+
scoreMode = ScoreMode.None;
280+
}
281+
270282
final QueryBuilder nestedQuery = nestedQueryBuilder.deque.pop();
271-
deque.push(QueryBuilders.nestedQuery(nestedPath, nestedQuery, nestedQueryBuilder.needsScoring ? ScoreMode.Max : ScoreMode.None));
283+
deque.push(QueryBuilders.nestedQuery(nestedPath, nestedQuery, scoreMode));
272284
}
273285

274286
private String toFieldPath(Predicate predicate) {
@@ -288,60 +300,70 @@ private void visit(TextPredicate predicate) {
288300
final String term = predicate.term();
289301
final MatchType type = predicate.type();
290302
final int minShouldMatch = predicate.minShouldMatch();
303+
final String searchAnalyzer = selectSearchAnalyzer(predicate);
291304

292305
QueryBuilder query;
293306

294-
String searchAnalyzer = selectSearchAnalyzer(predicate);
295307
switch (type) {
296-
case BOOLEAN_PREFIX:
297-
query = QueryBuilders.matchBoolPrefixQuery(field, term)
298-
.analyzer(searchAnalyzer)
299-
.operator(Operator.AND);
300-
break;
301-
case PHRASE:
302-
query = QueryBuilders.matchPhraseQuery(field, term)
303-
.analyzer(searchAnalyzer);
304-
break;
305-
case ALL:
306-
MatchQueryBuilder all = QueryBuilders.matchQuery(field, term)
307-
.analyzer(searchAnalyzer)
308-
.operator(Operator.AND);
309-
if (!Strings.isNullOrEmpty(predicate.fuzziness())) {
310-
all
311-
.fuzziness(predicate.fuzziness())
312-
.prefixLength(predicate.prefixLength())
313-
.maxExpansions(predicate.maxExpansions());
314-
}
315-
query = all;
316-
break;
317-
case ANY:
318-
MatchQueryBuilder any = QueryBuilders.matchQuery(field, term)
319-
.analyzer(searchAnalyzer)
320-
.operator(Operator.OR)
321-
.minimumShouldMatch(Integer.toString(minShouldMatch));
322-
if (!Strings.isNullOrEmpty(predicate.fuzziness())) {
323-
any
324-
.fuzziness(predicate.fuzziness())
308+
case BOOLEAN_PREFIX:
309+
query = QueryBuilders.matchBoolPrefixQuery(field, term)
310+
.analyzer(searchAnalyzer)
311+
.operator(Operator.AND);
312+
break;
313+
314+
case PHRASE:
315+
query = QueryBuilders.matchPhraseQuery(field, term)
316+
.analyzer(searchAnalyzer);
317+
break;
318+
319+
case ALL:
320+
MatchQueryBuilder all = QueryBuilders.matchQuery(field, term)
321+
.analyzer(searchAnalyzer)
322+
.operator(Operator.AND);
323+
324+
if (!Strings.isNullOrEmpty(predicate.fuzziness())) {
325+
all.fuzziness(predicate.fuzziness())
326+
.prefixLength(predicate.prefixLength())
327+
.maxExpansions(predicate.maxExpansions());
328+
}
329+
330+
query = all;
331+
break;
332+
333+
case ANY:
334+
MatchQueryBuilder any = QueryBuilders.matchQuery(field, term)
335+
.analyzer(searchAnalyzer)
336+
.operator(Operator.OR)
337+
.minimumShouldMatch(Integer.toString(minShouldMatch));
338+
339+
if (!Strings.isNullOrEmpty(predicate.fuzziness())) {
340+
any.fuzziness(predicate.fuzziness())
325341
.prefixLength(predicate.prefixLength())
326342
.maxExpansions(predicate.maxExpansions());
327-
}
328-
query = any;
329-
break;
330-
case PARSED:
331-
query = QueryBuilders.queryStringQuery(TextConstants.escape(term))
332-
.analyzer(searchAnalyzer)
333-
.field(field)
334-
.escape(false)
335-
.allowLeadingWildcard(true)
336-
.defaultOperator(Operator.AND);
337-
break;
338-
default: throw new UnsupportedOperationException("Unexpected text match type: " + type);
343+
}
344+
345+
query = any;
346+
break;
347+
348+
case PARSED:
349+
query = QueryBuilders.queryStringQuery(TextConstants.escape(term))
350+
.analyzer(searchAnalyzer)
351+
.field(field)
352+
.escape(false)
353+
.allowLeadingWildcard(true)
354+
.defaultOperator(Operator.AND);
355+
break;
356+
357+
default:
358+
throw new UnsupportedOperationException("Unexpected text match type: " + type);
339359
}
360+
340361
if (query == null) {
341362
query = MATCH_NONE;
342363
} else {
343-
needsScoring = true;
364+
enableScoring();
344365
}
366+
345367
deque.push(query);
346368
}
347369

0 commit comments

Comments
 (0)