Index: lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java	(revision 1363268)
+++ lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java	(revision )
@@ -99,9 +99,9 @@
       private TermsEnum segmentTermsEnum;
 
       public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-        SVInnerScorer scorer = (SVInnerScorer) scorer(context, true, false, context.reader().getLiveDocs());
+        SVInnerScorer scorer = (SVInnerScorer) scorer(context, false, false, context.reader().getLiveDocs());
         if (scorer != null) {
-          if (scorer.advance(doc) == doc) {
+          if (scorer.advanceForExplainOnly(doc) == doc) {
             return scorer.explain();
           }
         }
@@ -127,7 +127,13 @@
         }
 
         segmentTermsEnum = terms.iterator(segmentTermsEnum);
+        if (scoreDocsInOrder) {
-        if (multipleValuesPerDocument) {
+          if (multipleValuesPerDocument) {
+            return new SVInOrderScorer(this, acceptDocs, segmentTermsEnum, context.reader().maxDoc());
+          } else {
+            return new MVInOrderScorer(this, acceptDocs, segmentTermsEnum, context.reader().maxDoc());
+          }
+        } else if (multipleValuesPerDocument) {
           return new MVInnerScorer(this, acceptDocs, segmentTermsEnum, context.reader().maxDoc());
         } else {
           return new SVInnerScorer(this, acceptDocs, segmentTermsEnum);
@@ -192,6 +198,10 @@
     }
 
     public int advance(int target) throws IOException {
+      throw new UnsupportedOperationException("advance() isn't supported because doc ids are emitted out of order");
+    }
+
+    private int advanceForExplainOnly(int target) throws IOException {
       int docId;
       do {
         docId = nextDoc();
@@ -269,6 +279,92 @@
         } else {
           alreadyEmittedDocs.set(docId);
           return docId;
+        }
+      }
+    }
+  }
+
+  class SVInOrderScorer extends Scorer {
+
+    private final DocIdSetIterator matchingDocsIterator;
+    private final float[] scores;
+
+    int currentDoc = -1;
+
+    SVInOrderScorer(Weight weight, Bits acceptDocs, TermsEnum termsEnum, int maxDoc) throws IOException {
+      super(weight);
+      FixedBitSet matchingDocs = new FixedBitSet(maxDoc);
+      this.scores = new float[maxDoc];
+      fillDocsAndScores(matchingDocs, acceptDocs, termsEnum);
+      this.matchingDocsIterator = matchingDocs.iterator();
+    }
+
+    protected void fillDocsAndScores(FixedBitSet matchingDocs, Bits acceptDocs, TermsEnum termsEnum) throws IOException {
+      BytesRef spare = new BytesRef();
+      DocsEnum docsEnum = null;
+      for (int i = 0; i < terms.size(); i++) {
+        TermsEnum.SeekStatus status = termsEnum.seekCeil(terms.get(ords[i], spare), true);
+        if (status == TermsEnum.SeekStatus.FOUND) {
+          docsEnum = termsEnum.docs(acceptDocs, docsEnum, false);
+          float score = TermsIncludingScoreQuery.this.scores[ords[i]];
+          for (int doc = docsEnum.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = docsEnum.nextDoc()) {
+            matchingDocs.set(doc);
+            // In the case the same doc is also related to a another doc, a score might be overwritten. I think this
+            // can only happen in a many-to-many relation
+            scores[doc] = score;
+          }
+        }
+      }
+    }
+
+    public float score() throws IOException {
+      return scores[currentDoc];
+    }
+
+    public float freq() throws IOException {
+      return 1;
+    }
+
+    public int docID() {
+      return currentDoc;
+    }
+
+    public int nextDoc() throws IOException {
+      return currentDoc = matchingDocsIterator.nextDoc();
+    }
+
+    public int advance(int target) throws IOException {
+      return currentDoc = matchingDocsIterator.advance(target);
+    }
+  }
+
+  // This scorer deals with the fact that a document can have more than one score from multiple related documents.
+  class MVInOrderScorer extends SVInOrderScorer {
+
+    MVInOrderScorer(Weight weight, Bits acceptDocs, TermsEnum termsEnum, int maxDoc) throws IOException {
+      super(weight, acceptDocs, termsEnum, maxDoc);
+    }
+
+    @Override
+    protected void fillDocsAndScores(FixedBitSet matchingDocs, Bits acceptDocs, TermsEnum termsEnum) throws IOException {
+      BytesRef spare = new BytesRef();
+      DocsEnum docsEnum = null;
+      for (int i = 0; i < terms.size(); i++) {
+        TermsEnum.SeekStatus status = termsEnum.seekCeil(terms.get(ords[i], spare), true);
+        if (status == TermsEnum.SeekStatus.FOUND) {
+          docsEnum = termsEnum.docs(acceptDocs, docsEnum, false);
+          float score = TermsIncludingScoreQuery.this.scores[ords[i]];
+          for (int doc = docsEnum.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = docsEnum.nextDoc()) {
+            // I prefer this:
+//            if (scores[doc] < score) {
+//              scores[doc] = score;
+//            }
+            // But this behaves the same as MVInnerScorer and only then the tests will pass:
+            if (!matchingDocs.get(doc)) {
+              scores[doc] = score;
+              matchingDocs.set(doc);
+            }
+          }
         }
       }
     }
Index: lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java	(revision 1363268)
+++ lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java	(revision )
@@ -310,7 +310,7 @@
           }
 
           public boolean acceptsDocsOutOfOrder() {
-            return topScoreDocCollector.acceptsDocsOutOfOrder();
+            return TestJoinUtil.random().nextBoolean();
           }
         });
         // Asserting bit set...
