Skip to content

Commit 5fe4b7f

Browse files
authored
Merge branch 'main' into iterative-articulation-points
2 parents d2d9ea4 + 7c587af commit 5fe4b7f

5 files changed

Lines changed: 73 additions & 25 deletions

File tree

lucene/CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,8 @@ Improvements
388388

389389
* GITHUB#16144: Clarify LeafCollector batch collection scoring contract. (Costin Leau)
390390

391+
* GITHUB#16229: BooleanWeight.explain prints failing optional clauses when query fails due to insufficient SHOULD matches. (Jakub Slowinski)
392+
391393
* GITHUB#16239: GraphTokenStreamFiniteStrings#articulationPoints now uses an iterative
392394
approach instead of recursion, removing the previous MAX_RECURSION_LEVEL of 1000. (Jakub Slowinski)
393395

lucene/core/src/java/org/apache/lucene/codecs/lucene103/blocktree/Lucene103BlockTreeTermsWriter.java

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -775,18 +775,18 @@ private PendingBlock writeBlock(
775775

776776
assert StringHelper.startsWith(term.termBytes, prefix) : term + " prefix=" + prefix;
777777
BlockTermState state = term.state;
778-
final int suffix = term.termBytes.length - prefixLength;
778+
final int suffixLength = term.termBytes.length - prefixLength;
779779
// if (DEBUG2) {
780-
// BytesRef suffixBytes = new BytesRef(suffix);
781-
// System.arraycopy(term.termBytes, prefixLength, suffixBytes.bytes, 0, suffix);
782-
// suffixBytes.length = suffix;
783-
// System.out.println(" write term suffix=" +
780+
// BytesRef suffixBytes = new BytesRef(suffixLength);
781+
// System.arraycopy(term.termBytes, prefixLength, suffixBytes.bytes, 0, suffixLength);
782+
// suffixBytes.length = suffixLength;
783+
// System.out.println(" write term suffixLength=" +
784784
// ToStringUtils.bytesRefToString(suffixBytes));
785785
// }
786786

787-
// For leaf block we write suffix straight
788-
suffixLengthsWriter.writeVInt(suffix);
789-
suffixWriter.append(term.termBytes, prefixLength, suffix);
787+
// For leaf block we write suffixLength straight
788+
suffixLengthsWriter.writeVInt(suffixLength);
789+
suffixWriter.append(term.termBytes, prefixLength, suffixLength);
790790
assert floorLeadLabel == -1 || (term.termBytes[prefixLength] & 0xff) >= floorLeadLabel;
791791

792792
// Write term stats, to separate byte[] blob:
@@ -809,12 +809,12 @@ private PendingBlock writeBlock(
809809

810810
assert StringHelper.startsWith(term.termBytes, prefix) : term + " prefix=" + prefix;
811811
BlockTermState state = term.state;
812-
final int suffix = term.termBytes.length - prefixLength;
812+
final int suffixLength = term.termBytes.length - prefixLength;
813813
// if (DEBUG2) {
814-
// BytesRef suffixBytes = new BytesRef(suffix);
815-
// System.arraycopy(term.termBytes, prefixLength, suffixBytes.bytes, 0, suffix);
816-
// suffixBytes.length = suffix;
817-
// System.out.println(" write term suffix=" +
814+
// BytesRef suffixBytes = new BytesRef(suffixLength);
815+
// System.arraycopy(term.termBytes, prefixLength, suffixBytes.bytes, 0, suffixLength);
816+
// suffixBytes.length = suffixLength;
817+
// System.out.println(" write term suffixLength=" +
818818
// ToStringUtils.bytesRefToString(suffixBytes));
819819
// }
820820

@@ -823,8 +823,8 @@ private PendingBlock writeBlock(
823823
// it's a prefix term. Terms cannot be larger than ~32 KB
824824
// so we won't run out of bits:
825825

826-
suffixLengthsWriter.writeVInt(suffix << 1);
827-
suffixWriter.append(term.termBytes, prefixLength, suffix);
826+
suffixLengthsWriter.writeVInt(suffixLength << 1);
827+
suffixWriter.append(term.termBytes, prefixLength, suffixLength);
828828

829829
// Write term stats, to separate byte[] blob:
830830
statsWriter.add(state.docFreq, state.totalTermFreq);
@@ -843,21 +843,22 @@ private PendingBlock writeBlock(
843843
} else {
844844
PendingBlock block = (PendingBlock) ent;
845845
assert StringHelper.startsWith(block.prefix, prefix);
846-
final int suffix = block.prefix.length - prefixLength;
846+
final int suffixLength = block.prefix.length - prefixLength;
847847
assert StringHelper.startsWith(block.prefix, prefix);
848848

849-
assert suffix > 0;
849+
assert suffixLength > 0;
850850

851851
// For non-leaf block we borrow 1 bit to record
852852
// if entry is term or sub-block:f
853-
suffixLengthsWriter.writeVInt((suffix << 1) | 1);
854-
suffixWriter.append(block.prefix.bytes, prefixLength, suffix);
853+
suffixLengthsWriter.writeVInt((suffixLength << 1) | 1);
854+
suffixWriter.append(block.prefix.bytes, prefixLength, suffixLength);
855855

856856
// if (DEBUG2) {
857-
// BytesRef suffixBytes = new BytesRef(suffix);
858-
// System.arraycopy(block.prefix.bytes, prefixLength, suffixBytes.bytes, 0, suffix);
859-
// suffixBytes.length = suffix;
860-
// System.out.println(" write sub-block suffix=" +
857+
// BytesRef suffixBytes = new BytesRef(suffixLength);
858+
// System.arraycopy(block.prefix.bytes, prefixLength, suffixBytes.bytes, 0,
859+
// suffixLength);
860+
// suffixBytes.length = suffixLength;
861+
// System.out.println(" write sub-block suffixLength=" +
861862
// ToStringUtils.bytesRefToString(suffixBytes) + " subFP=" + block.fp + " subCode=" +
862863
// (startFP-block.fp) + " floor=" + block.isFloor);
863864
// }

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ protected static class WeightedBooleanClause {
6565
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
6666
final int minShouldMatch = query.getMinimumNumberShouldMatch();
6767
List<Explanation> subs = new ArrayList<>();
68+
List<Explanation> failingOptionals = new ArrayList<>();
6869
boolean fail = false;
6970
int matchCount = 0;
7071
int shouldMatchCount = 0;
@@ -97,16 +98,25 @@ public Explanation explain(LeafReaderContext context, int doc) throws IOExceptio
9798
subs.add(
9899
Explanation.noMatch("no match on required clause (" + c.query().toString() + ")", e));
99100
fail = true;
101+
} else if (c.occur() == Occur.SHOULD) {
102+
failingOptionals.add(
103+
Explanation.noMatch("no match on optional clause (" + c.query().toString() + ")", e));
100104
}
101105
}
102106
if (fail) {
103107
return Explanation.noMatch(
104108
"Failure to meet condition(s) of required/prohibited clause(s)", subs);
105109
} else if (matchCount == 0) {
110+
subs.addAll(failingOptionals);
106111
return Explanation.noMatch("No matching clauses", subs);
107112
} else if (shouldMatchCount < minShouldMatch) {
113+
subs.addAll(failingOptionals);
108114
return Explanation.noMatch(
109-
"Failure to match minimum number of optional clauses: " + minShouldMatch, subs);
115+
"Failure to match minimum number of optional clauses: "
116+
+ minShouldMatch
117+
+ ", matched: "
118+
+ shouldMatchCount,
119+
subs);
110120
} else {
111121
// Replicating the same floating-point errors as the scorer does is quite
112122
// complex (essentially because of how ReqOptSumScorer casts intermediate

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1395,7 +1395,7 @@ public void testClauseSetsImmutability() throws Exception {
13951395
bqBuilder.add(new TermQuery(d), Occur.MUST_NOT);
13961396
bqBuilder.add(new TermQuery(d), Occur.MUST_NOT);
13971397
BooleanQuery bq = bqBuilder.build();
1398-
// should and must are not dedupliacated
1398+
// should and must are not deduplicated
13991399
assertEquals(2, bq.getClauses(Occur.SHOULD).size());
14001400
assertEquals(2, bq.getClauses(Occur.MUST).size());
14011401
// filter and must not are deduplicated

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,39 @@ public void testBQ22() throws Exception {
145145

146146
bqtest(new BoostQuery(query, 0), new int[] {0, 1, 2, 3});
147147
}
148+
149+
public void testExplainFailingOptionalClauses() throws Exception {
150+
// minShouldMatch not satisfied, show failing SHOULDs.
151+
BooleanQuery msm =
152+
new BooleanQuery.Builder()
153+
.add(new TermQuery(new Term(FIELD, "w1")), Occur.SHOULD)
154+
.add(new TermQuery(new Term(FIELD, "xx")), Occur.SHOULD)
155+
.add(new TermQuery(new Term(FIELD, "zz")), Occur.SHOULD)
156+
.setMinimumNumberShouldMatch(2)
157+
.build();
158+
String msmExpl = searcher.explain(msm, 0).toString();
159+
assertTrue(
160+
msmExpl.contains("Failure to match minimum number of optional clauses: 2, matched: 1"));
161+
assertTrue(msmExpl.contains("no match on optional clause (field:xx)"));
162+
assertTrue(msmExpl.contains("no match on optional clause (field:zz)"));
163+
assertFalse(msmExpl.contains("no match on optional clause (field:w1)"));
164+
165+
// nothing matches in disjunction, show failing SHOULDs.
166+
BooleanQuery disj =
167+
new BooleanQuery.Builder()
168+
.add(new TermQuery(new Term(FIELD, "xx")), Occur.SHOULD)
169+
.add(new TermQuery(new Term(FIELD, "zz")), Occur.SHOULD)
170+
.build();
171+
String disjExpl = searcher.explain(disj, 0).toString();
172+
assertTrue(disjExpl.contains("no match on optional clause (field:xx)"));
173+
assertTrue(disjExpl.contains("no match on optional clause (field:zz)"));
174+
175+
// MUST clause fails, failing SHOULDs are not shown.
176+
BooleanQuery mustFail =
177+
new BooleanQuery.Builder()
178+
.add(new TermQuery(new Term(FIELD, "missing")), Occur.MUST)
179+
.add(new TermQuery(new Term(FIELD, "xx")), Occur.SHOULD)
180+
.build();
181+
assertFalse(searcher.explain(mustFail, 0).toString().contains("no match on optional clause"));
182+
}
148183
}

0 commit comments

Comments
 (0)