Index: lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java	(revision 1350386)
+++ lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java	(revision )
@@ -631,6 +631,15 @@
         assertNull(joinResults);
       } else {
         compareHits(r, joinR, results, joinResults);
+        TopDocs b = joinS.search(childJoinQuery, 10);
+        for (ScoreDoc hit : b.scoreDocs) {
+          Explanation explanation = joinS.explain(childJoinQuery, hit.doc);
+          Document document = joinS.doc(hit.doc - 1);
+          int childId = Integer.parseInt(document.get("childID"));
+          assertTrue(explanation.isMatch());
+          assertEquals(hit.score, explanation.getValue(), 0.0f);
+          assertEquals(String.format("Score based on child doc range from %d to %d", hit.doc - 1 - childId, hit.doc - 1), explanation.getDescription());
+        }
       }
 
       // Test joining in the opposite direction (parent to
Index: lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java	(revision 1350386)
+++ lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java	(revision )
@@ -26,6 +26,7 @@
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;       // javadocs
 import org.apache.lucene.index.Term;
+import org.apache.lucene.search.ComplexExplanation;
 import org.apache.lucene.search.DocIdSet;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Explanation;
@@ -179,7 +180,8 @@
       // acceptDocs when we score:
       final DocIdSet parents = parentsFilter.getDocIdSet(readerContext, null);
 
-      if (parents == null) {
+      if (parents == null
+          || parents.iterator().docID() == DocIdSetIterator.NO_MORE_DOCS) { // <-- means DocIdSet#EMPTY_DOCIDSET
         // No matches
         return null;
       }
@@ -191,11 +193,15 @@
     }
 
     @Override
-    public Explanation explain(AtomicReaderContext reader, int doc) throws IOException {
-      // TODO
-      throw new UnsupportedOperationException(getClass().getName() +
-                                              " cannot explain match on parent document");
+    public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
+      BlockJoinScorer scorer = (BlockJoinScorer) scorer(context, true, false, context.reader().getLiveDocs());
+      if (scorer != null) {
+        if (scorer.advance(doc) == doc) {
+          return scorer.explain(context.docBase);
-    }
+        }
+      }
+      return new ComplexExplanation(false, 0.0f, "Not a match");
+    }
 
     @Override
     public boolean scoresDocsOutOfOrder() {
@@ -209,6 +215,7 @@
     private final ScoreMode scoreMode;
     private final Bits acceptDocs;
     private int parentDoc = -1;
+    private int prevParentDoc;
     private float parentScore;
     private int nextChildDoc;
 
@@ -365,7 +372,7 @@
         return nextDoc();
       }
 
-      final int prevParentDoc = parentBits.prevSetBit(parentTarget-1);
+      prevParentDoc = parentBits.prevSetBit(parentTarget-1);
 
       //System.out.println("  rolled back to prevParentDoc=" + prevParentDoc + " vs parentDoc=" + parentDoc);
       assert prevParentDoc >= parentDoc;
@@ -383,6 +390,15 @@
       //System.out.println("  return nextParentDoc=" + nd);
       return nd;
     }
+
+    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 new ComplexExplanation(
+          true, score(), String.format("Score based on child doc range from %d to %d", start, end)
+      );
+    }
+
   }
 
   @Override
