Skip to content

Commit b12ee52

Browse files
Ensure stability of clause order for DisjunctionMaxQuery toString (#13944)
Co-authored-by: Laurent <[email protected]>
1 parent 5c08015 commit b12ee52

File tree

3 files changed

+39
-18
lines changed

3 files changed

+39
-18
lines changed

lucene/CHANGES.txt

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Bug Fixes
7878
* GITHUB#13884: Remove broken .toArray from Long/CharObjectHashMap entirely. (Pan Guixin)
7979
* GITHUB#12686: Added support for highlighting IndexOrDocValuesQuery. (Prudhvi Godithi)
8080
* GITHUB#13927: Fix StoredFieldsConsumer finish. (linfn)
81+
* GITHUB#13944: Ensure deterministic order of clauses for `DisjunctionMaxQuery#toString`. (Laurent Jakubina)
8182

8283
Build
8384
---------------------

lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java

+16-18
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Iterator;
2424
import java.util.List;
2525
import java.util.Objects;
26+
import java.util.stream.Collectors;
2627
import org.apache.lucene.index.LeafReaderContext;
2728

2829
/**
@@ -44,6 +45,7 @@ public final class DisjunctionMaxQuery extends Query implements Iterable<Query>
4445

4546
/* The subqueries */
4647
private final Multiset<Query> disjuncts = new Multiset<>();
48+
private final List<Query> orderedQueries; // used for toString()
4749

4850
/* Multiple of the non-max disjunct scores added into our final score. Non-zero values support tie-breaking. */
4951
private final float tieBreakerMultiplier;
@@ -65,6 +67,7 @@ public DisjunctionMaxQuery(Collection<Query> disjuncts, float tieBreakerMultipli
6567
}
6668
this.tieBreakerMultiplier = tieBreakerMultiplier;
6769
this.disjuncts.addAll(disjuncts);
70+
this.orderedQueries = new ArrayList<>(disjuncts); // order from the caller
6871
}
6972

7073
/**
@@ -295,24 +298,19 @@ public void visit(QueryVisitor visitor) {
295298
*/
296299
@Override
297300
public String toString(String field) {
298-
StringBuilder buffer = new StringBuilder();
299-
buffer.append("(");
300-
Iterator<Query> it = disjuncts.iterator();
301-
for (int i = 0; it.hasNext(); i++) {
302-
Query subquery = it.next();
303-
if (subquery instanceof BooleanQuery) { // wrap sub-bools in parens
304-
buffer.append("(");
305-
buffer.append(subquery.toString(field));
306-
buffer.append(")");
307-
} else buffer.append(subquery.toString(field));
308-
if (i != disjuncts.size() - 1) buffer.append(" | ");
309-
}
310-
buffer.append(")");
311-
if (tieBreakerMultiplier != 0.0f) {
312-
buffer.append("~");
313-
buffer.append(tieBreakerMultiplier);
314-
}
315-
return buffer.toString();
301+
return this.orderedQueries.stream()
302+
.map(
303+
subquery -> {
304+
if (subquery instanceof BooleanQuery) { // wrap sub-bools in parens
305+
return "(" + subquery.toString(field) + ")";
306+
}
307+
return subquery.toString(field);
308+
})
309+
.collect(
310+
Collectors.joining(
311+
" | ",
312+
"(",
313+
")" + ((tieBreakerMultiplier != 0.0f) ? "~" + tieBreakerMultiplier : "")));
316314
}
317315

318316
/**

lucene/core/src/test/org/apache/lucene/search/TestDisjunctionMaxQuery.java

+22
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Collections;
2626
import java.util.List;
2727
import java.util.Locale;
28+
import java.util.stream.Collectors;
2829
import org.apache.lucene.analysis.standard.StandardAnalyzer;
2930
import org.apache.lucene.document.Document;
3031
import org.apache.lucene.document.Field;
@@ -489,6 +490,27 @@ public void testDisjunctOrderAndEquals() throws Exception {
489490
assertEquals(q1, q2);
490491
}
491492

493+
/* Inspired from TestIntervals.testIntervalDisjunctionToStringStability */
494+
public void testToStringOrderMatters() {
495+
final int clauseNbr =
496+
random().nextInt(22) + 4; // ensure a reasonably large minimum number of clauses
497+
final String[] terms = new String[clauseNbr];
498+
for (int i = 0; i < clauseNbr; i++) {
499+
terms[i] = Character.toString((char) ('a' + i));
500+
}
501+
502+
final String expected =
503+
Arrays.stream(terms)
504+
.map((term) -> "test:" + term)
505+
.collect(Collectors.joining(" | ", "(", ")~1.0"));
506+
507+
DisjunctionMaxQuery source =
508+
new DisjunctionMaxQuery(
509+
Arrays.stream(terms).map((term) -> tq("test", term)).toList(), 1.0f);
510+
511+
assertEquals(expected, source.toString(""));
512+
}
513+
492514
public void testRandomTopDocs() throws Exception {
493515
doTestRandomTopDocs(2, 0.05f, 0.05f);
494516
doTestRandomTopDocs(2, 1.0f, 0.05f);

0 commit comments

Comments
 (0)