diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 3ba5c082e27..ee23c3c4285 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -149,6 +149,9 @@ Other * SOLR-9109/SOLR-9121: Allow specification of a custom Ivy settings file via system property "ivysettings.xml". (Misha Dmitriev, Christine Poerschke, Uwe Schindler, Steve Rowe) +* LUCENE-7206: Improve the ToParentBlockJoinQuery's explain by including the explain + of the best matching child doc. (Ilya Kasnacheev, Jeff Evans via Martijn van Groningen) + Build * LUCENE-7292: Use '-release' instead of '-source/-target' during diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java index c7bc72ff526..e4ee7601bb9 100644 --- a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java +++ b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java @@ -17,8 +17,10 @@ package org.apache.lucene.search.join; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.Set; @@ -184,7 +186,7 @@ public class ToParentBlockJoinQuery extends Query { public Explanation explain(LeafReaderContext context, int doc) throws IOException { BlockJoinScorer scorer = (BlockJoinScorer) scorer(context); if (scorer != null && scorer.iterator().advance(doc) == doc) { - return scorer.explain(context.docBase); + return scorer.explain(context, childWeight); } return Explanation.noMatch("Not a match"); } @@ -436,10 +438,24 @@ public class ToParentBlockJoinQuery extends Query { return parentFreq; } - public Explanation explain(int docBase) throws IOException { - int start = docBase + prevParentDoc + 1; // +1 b/c prevParentDoc is previous parent doc - int end = docBase + parentDoc - 1; // -1 b/c parentDoc is parent doc - return Explanation.match(score(), String.format(Locale.ROOT, "Score based on child doc range from %d to %d", start, end) + public Explanation explain(LeafReaderContext context, Weight childWeight) throws IOException { + int start = context.docBase + prevParentDoc + 1; // +1 b/c prevParentDoc is previous parent doc + int end = context.docBase + parentDoc - 1; // -1 b/c parentDoc is parent doc + + Explanation bestChild = null; + int matches = 0; + for (int childDoc = start; childDoc <= end; childDoc++) { + Explanation child = childWeight.explain(context, childDoc - context.docBase); + if (child.isMatch()) { + matches++; + if (bestChild == null || child.getValue() > bestChild.getValue()) { + bestChild = child; + } + } + } + + return Explanation.match(score(), String.format(Locale.ROOT, + "Score based on %d child docs in range from %d to %d, best match:", matches, start, end), bestChild ); } diff --git a/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java b/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java index e11d207c44b..ab6aa5886d3 100644 --- a/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java +++ b/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java @@ -24,6 +24,8 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.document.Document; @@ -50,6 +52,7 @@ import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BoostQuery; +import org.apache.lucene.search.CheckHits; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.FieldDoc; @@ -231,6 +234,8 @@ public class TestBlockJoin extends LuceneTestCase { ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(Sort.RELEVANCE, 1, true, true); + CheckHits.checkHitCollector(random(), fullQuery.build(), "country", s, new int[] {2}); + s.search(fullQuery.build(), c); TopGroups results = c.getTopGroups(childJoinQuery, null, 0, 10, 0, true); @@ -869,7 +874,16 @@ public class TestBlockJoin extends LuceneTestCase { //System.out.println(" hit docID=" + hit.doc + " childId=" + childId + " parentId=" + document.get("parentID")); assertTrue(explanation.isMatch()); assertEquals(hit.score, explanation.getValue(), 0.0f); - assertEquals(String.format(Locale.ROOT, "Score based on child doc range from %d to %d", hit.doc - 1 - childId, hit.doc - 1), explanation.getDescription()); + Matcher m = Pattern.compile("Score based on ([0-9]+) child docs in range from ([0-9]+) to ([0-9]+), best match:").matcher(explanation.getDescription()); + assertTrue("Block Join description not matches", m.matches()); + assertTrue("Matched children not positive", Integer.parseInt(m.group(1)) > 0); + assertEquals("Wrong child range start", hit.doc - 1 - childId, Integer.parseInt(m.group(2))); + assertEquals("Wrong child range end", hit.doc - 1, Integer.parseInt(m.group(3))); + Explanation childWeightExplanation = explanation.getDetails()[0]; + if ("sum of:".equals(childWeightExplanation.getDescription())) { + childWeightExplanation = childWeightExplanation.getDetails()[0]; + } + assertTrue("Wrong child weight description", childWeightExplanation.getDescription().startsWith("weight(child")); } } diff --git a/lucene/test-framework/src/java/org/apache/lucene/search/CheckHits.java b/lucene/test-framework/src/java/org/apache/lucene/search/CheckHits.java index 4eacda743c8..f12960501f9 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/search/CheckHits.java +++ b/lucene/test-framework/src/java/org/apache/lucene/search/CheckHits.java @@ -346,6 +346,10 @@ public class CheckHits { if (expl.getDescription().endsWith("computed from:")) { return; // something more complicated. } + String descr = expl.getDescription().toLowerCase(Locale.ROOT); + if (descr.startsWith("score based on ") && descr.contains("child docs in range")) { + Assert.assertTrue("Child doc explanations are missing", detail.length > 0); + } if (detail.length > 0) { if (detail.length==1) { // simple containment, unless it's a freq of: (which lets a query explain how the freq is calculated), @@ -357,7 +361,6 @@ public class CheckHits { // - end with one of: "product of:", "sum of:", "max of:", or // - have "max plus times others" (where is float). float x = 0; - String descr = expl.getDescription().toLowerCase(Locale.ROOT); boolean productOf = descr.endsWith("product of:"); boolean sumOf = descr.endsWith("sum of:"); boolean maxOf = descr.endsWith("max of:");