Index: lucene/join/src/java/org/apache/lucene/search/join/ScoreMode.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lucene/join/src/java/org/apache/lucene/search/join/ScoreMode.java	(revision )
+++ lucene/join/src/java/org/apache/lucene/search/join/ScoreMode.java	(revision )
@@ -0,0 +1,45 @@
+package org.apache.lucene.search.join;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * How to aggregate multiple child hit scores into a single parent score.
+ */
+public enum ScoreMode {
+
+  /**
+   * Do no scoring.
+   */
+  None,
+
+  /**
+   * Parent hit's score is the average of all child scores.
+   */
+  Avg,
+
+  /**
+   * Parent hit's score is the max of all child scores.
+   */
+  Max,
+
+  /**
+   * Parent hit's score is the sum of all child scores.
+   */
+  Total
+
+}
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 1340056)
+++ lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java	(revision )
@@ -95,21 +95,21 @@
 
     // Search for product
     Query joinQuery =
-        JoinUtil.createJoinQuery(idField, false, toField, new TermQuery(new Term("name", "name2")), indexSearcher);
+        JoinUtil.createJoinQuery(idField, false, toField, new TermQuery(new Term("name", "name2")), indexSearcher, ScoreMode.None);
 
     TopDocs result = indexSearcher.search(joinQuery, 10);
     assertEquals(2, result.totalHits);
     assertEquals(4, result.scoreDocs[0].doc);
     assertEquals(5, result.scoreDocs[1].doc);
 
-    joinQuery = JoinUtil.createJoinQuery(idField, false, toField, new TermQuery(new Term("name", "name1")), indexSearcher);
+    joinQuery = JoinUtil.createJoinQuery(idField, false, toField, new TermQuery(new Term("name", "name1")), indexSearcher, ScoreMode.None);
     result = indexSearcher.search(joinQuery, 10);
     assertEquals(2, result.totalHits);
     assertEquals(1, result.scoreDocs[0].doc);
     assertEquals(2, result.scoreDocs[1].doc);
 
     // Search for offer
-    joinQuery = JoinUtil.createJoinQuery(toField, false, idField, new TermQuery(new Term("id", "5")), indexSearcher);
+    joinQuery = JoinUtil.createJoinQuery(toField, false, idField, new TermQuery(new Term("id", "5")), indexSearcher, ScoreMode.None);
     result = indexSearcher.search(joinQuery, 10);
     assertEquals(1, result.totalHits);
     assertEquals(3, result.scoreDocs[0].doc);
@@ -118,6 +118,96 @@
     dir.close();
   }
 
+  public void testSimpleWithScoring() throws Exception {
+    final String idField = "id";
+    final String toField = "movieId";
+
+    Directory dir = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(
+        random(),
+        dir,
+        newIndexWriterConfig(TEST_VERSION_CURRENT,
+            new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
+
+    // 0
+    Document doc = new Document();
+    doc.add(new Field("description", "A random movie", TextField.TYPE_STORED));
+    doc.add(new Field("name", "Movie 1", TextField.TYPE_STORED));
+    doc.add(new Field(idField, "1", TextField.TYPE_STORED));
+    w.addDocument(doc);
+
+    // 1
+    doc = new Document();
+    doc.add(new Field("subtitle", "The first subtitle of this movie", TextField.TYPE_STORED));
+    doc.add(new Field(idField, "2", TextField.TYPE_STORED));
+    doc.add(new Field(toField, "1", TextField.TYPE_STORED));
+    w.addDocument(doc);
+
+    // 2
+    doc = new Document();
+    doc.add(new Field("subtitle", "random subtitle; random event movie", TextField.TYPE_STORED));
+    doc.add(new Field(idField, "3", TextField.TYPE_STORED));
+    doc.add(new Field(toField, "1", TextField.TYPE_STORED));
+    w.addDocument(doc);
+
+    // 3
+    doc = new Document();
+    doc.add(new Field("description", "A second random movie", TextField.TYPE_STORED));
+    doc.add(new Field("name", "Movie 2", TextField.TYPE_STORED));
+    doc.add(new Field(idField, "4", TextField.TYPE_STORED));
+    w.addDocument(doc);
+    w.commit();
+
+    // 4
+    doc = new Document();
+    doc.add(new Field("subtitle", "a very random event happened during christmas night", TextField.TYPE_STORED));
+    doc.add(new Field(idField, "5", TextField.TYPE_STORED));
+    doc.add(new Field(toField, "4", TextField.TYPE_STORED));
+    w.addDocument(doc);
+
+    // 5
+    doc = new Document();
+    doc.add(new Field("subtitle", "movie end movie test 123 test 123 random", TextField.TYPE_STORED));
+    doc.add(new Field(idField, "6", TextField.TYPE_STORED));
+    doc.add(new Field(toField, "4", TextField.TYPE_STORED));
+    w.addDocument(doc);
+
+    IndexSearcher indexSearcher = new IndexSearcher(w.getReader());
+    w.close();
+
+    // Search for movie via subtitle
+    Query joinQuery =
+        JoinUtil.createJoinQuery(toField, false, idField, new TermQuery(new Term("subtitle", "random")), indexSearcher, ScoreMode.Max);
+    TopDocs result = indexSearcher.search(joinQuery, 10);
+    assertEquals(2, result.totalHits);
+    assertEquals(0, result.scoreDocs[0].doc);
+    assertEquals(3, result.scoreDocs[1].doc);
+
+    // Score mode max.
+    joinQuery = JoinUtil.createJoinQuery(toField, false, idField, new TermQuery(new Term("subtitle", "movie")), indexSearcher, ScoreMode.Max);
+    result = indexSearcher.search(joinQuery, 10);
+    assertEquals(2, result.totalHits);
+    assertEquals(3, result.scoreDocs[0].doc);
+    assertEquals(0, result.scoreDocs[1].doc);
+
+    // Score mode total
+    joinQuery = JoinUtil.createJoinQuery(toField, false, idField, new TermQuery(new Term("subtitle", "movie")), indexSearcher, ScoreMode.Total);
+    result = indexSearcher.search(joinQuery, 10);
+    assertEquals(2, result.totalHits);
+    assertEquals(0, result.scoreDocs[0].doc);
+    assertEquals(3, result.scoreDocs[1].doc);
+
+    //Score mode avg
+    joinQuery = JoinUtil.createJoinQuery(toField, false, idField, new TermQuery(new Term("subtitle", "movie")), indexSearcher, ScoreMode.Avg);
+    result = indexSearcher.search(joinQuery, 10);
+    assertEquals(2, result.totalHits);
+    assertEquals(3, result.scoreDocs[0].doc);
+    assertEquals(0, result.scoreDocs[1].doc);
+
+    indexSearcher.getIndexReader().close();
+    dir.close();
+  }
+
   @Test
   public void testSingleValueRandomJoin() throws Exception {
     int maxIndexIter = _TestUtil.nextInt(random(), 6, 12);
@@ -160,15 +250,20 @@
         String randomValue = context.randomUniqueValues[r];
         FixedBitSet expectedResult = createExpectedResult(randomValue, from, indexSearcher.getIndexReader(), context);
 
-        Query actualQuery = new TermQuery(new Term("value", randomValue));
+        final Query actualQuery = new TermQuery(new Term("value", randomValue));
         if (VERBOSE) {
           System.out.println("actualQuery=" + actualQuery);
         }
-        Query joinQuery;
+        final ScoreMode scoreMode = ScoreMode.values()[random().nextInt(ScoreMode.values().length)];
+        if (VERBOSE) {
+          System.out.println("scoreMode=" + scoreMode);
+        }
+
+        final Query joinQuery;
         if (from) {
-          joinQuery = JoinUtil.createJoinQuery("from", multipleValuesPerDocument, "to", actualQuery, indexSearcher);
+          joinQuery = JoinUtil.createJoinQuery("from", multipleValuesPerDocument, "to", actualQuery, indexSearcher, scoreMode);
         } else {
-          joinQuery = JoinUtil.createJoinQuery("to", multipleValuesPerDocument, "from", actualQuery, indexSearcher);
+          joinQuery = JoinUtil.createJoinQuery("to", multipleValuesPerDocument, "from", actualQuery, indexSearcher, scoreMode);
         }
         if (VERBOSE) {
           System.out.println("joinQuery=" + joinQuery);
@@ -176,23 +271,27 @@
 
         // Need to know all documents that have matches. TopDocs doesn't give me that and then I'd be also testing TopDocsCollector...
         final FixedBitSet actualResult = new FixedBitSet(indexSearcher.getIndexReader().maxDoc());
+        final TopScoreDocCollector topScoreDocCollector = TopScoreDocCollector.create(10, false);
         indexSearcher.search(joinQuery, new Collector() {
 
           int docBase;
 
           public void collect(int doc) throws IOException {
             actualResult.set(doc + docBase);
+            topScoreDocCollector.collect(doc);
           }
 
           public void setNextReader(AtomicReaderContext context) throws IOException {
             docBase = context.docBase;
+            topScoreDocCollector.setNextReader(context);
           }
 
           public void setScorer(Scorer scorer) throws IOException {
+            topScoreDocCollector.setScorer(scorer);
           }
 
           public boolean acceptsDocsOutOfOrder() {
-            return true;
+            return topScoreDocCollector.acceptsDocsOutOfOrder();
           }
         });
 
@@ -210,6 +309,10 @@
         }
 
         assertEquals(expectedResult, actualResult);
+
+        // TODO: TopDocs expectedTopDocs = createExpectedTopDocs();
+        TopDocs actualTopDocs = topScoreDocCollector.topDocs();
+        assertEquals(actualResult.cardinality(), actualTopDocs.totalHits);
       }
       topLevelReader.close();
       dir.close();
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 1340056)
+++ lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java	(revision )
@@ -17,29 +17,21 @@
  * limitations under the License.
  */
 
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
-
 import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;       // javadocs
+import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.Term;
-import org.apache.lucene.search.DocIdSet;
-import org.apache.lucene.search.DocIdSetIterator;
-import org.apache.lucene.search.Explanation;
-import org.apache.lucene.search.Filter;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.Scorer.ChildScorer;
-import org.apache.lucene.search.Weight;
+import org.apache.lucene.search.*;
 import org.apache.lucene.search.grouping.TopGroups;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.FixedBitSet;
 
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
 /**
  * This query requires that you index
  * children and parent docs as a single block, using the
@@ -84,21 +76,6 @@
  */
 
 public class ToParentBlockJoinQuery extends Query {
-
-  /** How to aggregate multiple child hit scores into a
-   *  single parent score. */
-  public static enum ScoreMode {
-    /** Do no scoring. */
-    None,
-    /** Parent hit's score is the average of all child
-        scores. */
-    Avg,
-    /** Parent hit's score is the max of all child
-        scores. */
-    Max,
-    /** Parent hit's score is the sum of all child
-        scores. */
-    Total};
 
   private final Filter parentsFilter;
   private final Query childQuery;
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 )
+++ lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java	(revision )
@@ -0,0 +1,171 @@
+package org.apache.lucene.search.join;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.index.AtomicReaderContext;
+import org.apache.lucene.index.DocsEnum;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.*;
+import org.apache.lucene.util.Bits;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefHash;
+
+import java.io.IOException;
+
+class TermsIncludingScoreQuery extends Query {
+
+  final String field;
+  final BytesRefHash terms;
+  final float[] scores;
+  final int[] ords;
+
+  TermsIncludingScoreQuery(String field, BytesRefHash terms, float[] scores) {
+    this.field = field;
+    this.terms = terms;
+    this.scores = scores;
+    this.ords = terms.sort(BytesRef.getUTF8SortedAsUnicodeComparator());
+  }
+
+  public String toString(String string) {
+    return "TermsIncludingScoreQuery{field=" + field + '}';
+  }
+
+  public static TermsIncludingScoreQuery create(String field, BytesRefHash terms, float[] scores, boolean multipleValuesPerDocument) {
+    if (multipleValuesPerDocument) {
+      throw new UnsupportedOperationException("Multiple values not yet supported");
+    } else {
+      return new SV(field, terms, scores);
+    }
+  }
+
+  static class SV extends TermsIncludingScoreQuery {
+
+    SV(String field, BytesRefHash terms, float[] scores) {
+      super(field, terms, scores);
+    }
+
+    @Override
+    public Weight createWeight(IndexSearcher searcher) throws IOException {
+      return new Weight() {
+
+        TermsEnum segmentTermsEnum;
+
+        public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
+          SVInnerScorer scorer = (SVInnerScorer) scorer(context, true, false, context.reader().getLiveDocs());
+          if (scorer != null) {
+            if (scorer.advance(doc) == doc) {
+              return scorer.explain();
+            }
+          }
+          return new ComplexExplanation(false, 0.0f, "Not a match");
+        }
+
+        public Query getQuery() {
+          return SV.this;
+        }
+
+        public float getValueForNormalization() throws IOException {
+          return 0.0f;
+        }
+
+        public void normalize(float norm, float topLevelBoost) {
+        }
+
+        public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder, boolean topScorer, Bits acceptDocs) throws IOException {
+          Terms terms = context.reader().terms(field);
+          if (terms == null) {
+            return null;
+          }
+
+          segmentTermsEnum = terms.iterator(segmentTermsEnum);
+          return new SVInnerScorer(this, acceptDocs, segmentTermsEnum);
+        }
+      };
+    }
+
+  }
+
+  // This impl assumes that the 'join' values are used uniquely per doc per field.
+  class SVInnerScorer extends Scorer {
+
+    private final BytesRef spare = new BytesRef();
+    private final Bits acceptDocs;
+    private final TermsEnum termsEnum;
+
+    private DocsEnum docsEnum;
+
+    private int upto;
+    private int currentUptoForScore;
+
+    SVInnerScorer(Weight weight, Bits acceptDocs, TermsEnum termsEnum) {
+      super(weight);
+      this.acceptDocs = acceptDocs;
+      this.termsEnum = termsEnum;
+    }
+
+    public float score() throws IOException {
+      return scores[ords[currentUptoForScore]];
+    }
+
+    public Explanation explain() throws IOException {
+      return new ComplexExplanation(true, score(), "Score based on join value " + termsEnum.term().utf8ToString());
+    }
+
+    public int docID() {
+      return docsEnum != null ? docsEnum.docID() : DocIdSetIterator.NO_MORE_DOCS;
+    }
+
+    public int nextDoc() throws IOException {
+      if (docsEnum != null) {
+        int docId = docsEnum.nextDoc();
+        if (docId == DocIdSetIterator.NO_MORE_DOCS) {
+          docsEnum = null;
+        } else {
+          return docId;
+        }
+      }
+
+      do {
+        if (upto == terms.size()) {
+          return DocIdSetIterator.NO_MORE_DOCS;
+        }
+
+        currentUptoForScore = upto;
+        TermsEnum.SeekStatus status = termsEnum.seekCeil(terms.get(ords[upto++], spare), true);
+        if (status == TermsEnum.SeekStatus.FOUND) {
+          docsEnum = termsEnum.docs(acceptDocs, null, false);
+        }
+      } while (docsEnum == null);
+
+      return docsEnum.nextDoc();
+    }
+
+    public int advance(int target) throws IOException {
+      if (docsEnum == null) {
+        int docId = nextDoc();
+        if (docId >= target) {
+          return docId;
+        }
+      }
+
+      return docsEnum.advance(target);
+    }
+  }
+
+}
Index: lucene/join/src/java/org/apache/lucene/search/join/TermsWithScoreCollector.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lucene/join/src/java/org/apache/lucene/search/join/TermsWithScoreCollector.java	(revision )
+++ lucene/join/src/java/org/apache/lucene/search/join/TermsWithScoreCollector.java	(revision )
@@ -0,0 +1,170 @@
+package org.apache.lucene.search.join;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.index.AtomicReaderContext;
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefHash;
+
+import java.io.IOException;
+
+abstract class TermsWithScoreCollector extends Collector {
+
+  private final static int INITIAL_GROW_FACTOR = 2;
+
+  final String field;
+  final BytesRefHash collectorTerms = new BytesRefHash();
+  final ScoreMode scoreMode;
+
+  Scorer scorer;
+  int growFactor = INITIAL_GROW_FACTOR;
+  float[] scores = new float[256];
+
+  TermsWithScoreCollector(String field, ScoreMode scoreMode) {
+    this.field = field;
+    this.scoreMode = scoreMode;
+  }
+
+  public BytesRefHash getCollectorTerms() {
+    return collectorTerms;
+  }
+
+  public float[] getScoresPerTerm() {
+    return scores;
+  }
+
+  public void setScorer(Scorer scorer) throws IOException {
+    this.scorer = scorer;
+  }
+
+  public boolean acceptsDocsOutOfOrder() {
+    return true;
+  }
+
+  /**
+   * Chooses the right {@link TermsWithScoreCollector} implementation.
+   *
+   * @param field                     The field to collect terms for
+   * @param multipleValuesPerDocument Whether the field to collect terms for has multiple values per document.
+   * @return a {@link TermsWithScoreCollector} instance
+   */
+  static TermsWithScoreCollector create(String field, boolean multipleValuesPerDocument, ScoreMode scoreMode) {
+    if (multipleValuesPerDocument) {
+      throw new UnsupportedOperationException("Multiple values per doc not yet supported");
+    } else {
+      switch (scoreMode) {
+        case Avg:
+          return new SVAvg(field);
+        default:
+          return new SV(field, scoreMode);
+      }
+    }
+  }
+
+  // impl that works with single value per document
+  static class SV extends TermsWithScoreCollector {
+
+    final BytesRef spare = new BytesRef();
+    FieldCache.DocTerms fromDocTerms;
+
+    SV(String field, ScoreMode scoreMode) {
+      super(field, scoreMode);
+    }
+
+    public void collect(int doc) throws IOException {
+      int ord = collectorTerms.add(fromDocTerms.getTerm(doc, spare));
+      if (ord < 0) {
+        ord = -ord - 1;
+      } else {
+        if (ord >= scores.length) {
+          scores = ArrayUtil.grow(scores, scores.length * growFactor);
+          growFactor++;
+        }
+      }
+
+      float current = scorer.score();
+      float existing = scores[ord];
+      if (Float.compare(existing, 0.0f) == 0) {
+        scores[ord] = current;
+      } else {
+        switch (scoreMode) {
+          case Total:
+            scores[ord] = scores[ord] + current;
+            break;
+          case Max:
+            if (current > existing) {
+              scores[ord] = current;
+            }
+        }
+      }
+    }
+
+    public void setNextReader(AtomicReaderContext context) throws IOException {
+      fromDocTerms = FieldCache.DEFAULT.getTerms(context.reader(), field);
+    }
+  }
+
+  static class SVAvg extends SV {
+
+    int[] ordScores = new int[256];
+
+    SVAvg(String field) {
+      super(field, ScoreMode.Avg);
+    }
+
+    @Override
+    public void collect(int doc) throws IOException {
+      int ord = collectorTerms.add(fromDocTerms.getTerm(doc, spare));
+      if (ord < 0) {
+        ord = -ord - 1;
+      } else {
+        if (ord >= scores.length) {
+          scores = ArrayUtil.grow(scores, scores.length * growFactor);
+          ordScores = ArrayUtil.grow(ordScores, ordScores.length * growFactor);
+          growFactor++;
+        }
+      }
+
+      float current = scorer.score();
+      float existing = scores[ord];
+      if (Float.compare(existing, 0.0f) == 0) {
+        scores[ord] = current;
+        ordScores[ord] = 1;
+      } else {
+        scores[ord] = scores[ord] + current;
+        ordScores[ord]++;
+      }
+    }
+
+    @Override
+    public float[] getScoresPerTerm() {
+      if (ordScores != null) {
+        for (int i = 0; i < ordScores.length; i++) {
+          scores[i] = scores[i] / ordScores[i];
+        }
+        ordScores = null;
+      }
+      return scores;
+    }
+  }
+
+}
Index: lucene/join/src/java/org/apache/lucene/search/join/JoinUtil.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lucene/join/src/java/org/apache/lucene/search/join/JoinUtil.java	(revision 1340056)
+++ lucene/join/src/java/org/apache/lucene/search/join/JoinUtil.java	(revision )
@@ -44,18 +44,32 @@
    * @param toField                   The to field to join to
    * @param fromQuery                 The query to match documents on the from side
    * @param fromSearcher              The searcher that executed the specified fromQuery
+   * @param scoreMode                 Instructs how scores from the fromQuery are mapped to the returned query
    * @return a {@link Query} instance that can be used to join documents based on the
    *         terms in the from and to field
    * @throws IOException If I/O related errors occur
+   * @throws UnsupportedOperationException If the score mode isn't supported
    */
   public static Query createJoinQuery(String fromField,
                                       boolean multipleValuesPerDocument,
                                       String toField,
                                       Query fromQuery,
-                                      IndexSearcher fromSearcher) throws IOException {
+                                      IndexSearcher fromSearcher,
+                                      ScoreMode scoreMode) throws IOException {
+    switch (scoreMode) {
+      case None:
-    TermsCollector termsCollector = TermsCollector.create(fromField, multipleValuesPerDocument);
-    fromSearcher.search(fromQuery, termsCollector);
-    return new TermsQuery(toField, termsCollector.getCollectorTerms());
+        TermsCollector termsCollector = TermsCollector.create(fromField, multipleValuesPerDocument);
+        fromSearcher.search(fromQuery, termsCollector);
+        return new TermsQuery(toField, termsCollector.getCollectorTerms());
+      case Total:
+      case Max:
+      case Avg:
+        TermsWithScoreCollector termsWithScoreCollector = TermsWithScoreCollector.create(fromField, multipleValuesPerDocument, scoreMode);
+        fromSearcher.search(fromQuery, termsWithScoreCollector);
+        return TermsIncludingScoreQuery.create(toField, termsWithScoreCollector.getCollectorTerms(), termsWithScoreCollector.getScoresPerTerm(), multipleValuesPerDocument);
+      default:
+        throw new UnsupportedOperationException(String.format("Score mode %s isn't supported.", scoreMode));
+    }
   }
 
 }
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 1340056)
+++ lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java	(revision )
@@ -96,7 +96,7 @@
 
     // Wrap the child document query to 'join' any matches
     // up to corresponding parent:
-    ToParentBlockJoinQuery childJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, ToParentBlockJoinQuery.ScoreMode.Avg);
+    ToParentBlockJoinQuery childJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, ScoreMode.Avg);
 
     // Combine the parent and nested child queries into a single query for a candidate
     BooleanQuery fullQuery = new BooleanQuery();
@@ -198,7 +198,7 @@
       
     // Wrap the child document query to 'join' any matches
     // up to corresponding parent:
-    ToParentBlockJoinQuery childJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, ToParentBlockJoinQuery.ScoreMode.Avg);
+    ToParentBlockJoinQuery childJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, ScoreMode.Avg);
       
     assertEquals("no filter - both passed", 2, s.search(childJoinQuery, 10).totalHits);
 
@@ -259,7 +259,7 @@
     w.close();
     IndexSearcher s = newSearcher(r);
     
-    ToParentBlockJoinQuery q = new ToParentBlockJoinQuery(new MatchAllDocsQuery(), new QueryWrapperFilter(new MatchAllDocsQuery()), ToParentBlockJoinQuery.ScoreMode.Avg);
+    ToParentBlockJoinQuery q = new ToParentBlockJoinQuery(new MatchAllDocsQuery(), new QueryWrapperFilter(new MatchAllDocsQuery()), ScoreMode.Avg);
     s.search(q, 10);
     BooleanQuery bq = new BooleanQuery();
     bq.setBoost(2f); // we boost the BQ
@@ -493,15 +493,15 @@
       }
 
       final int x = random().nextInt(4);
-      final ToParentBlockJoinQuery.ScoreMode agg;
+      final ScoreMode agg;
       if (x == 0) {
-        agg = ToParentBlockJoinQuery.ScoreMode.None;
+        agg = ScoreMode.None;
       } else if (x == 1) {
-        agg = ToParentBlockJoinQuery.ScoreMode.Max;
+        agg = ScoreMode.Max;
       } else if (x == 2) {
-        agg = ToParentBlockJoinQuery.ScoreMode.Total;
+        agg = ScoreMode.Total;
       } else {
-        agg = ToParentBlockJoinQuery.ScoreMode.Avg;
+        agg = ScoreMode.Avg;
       }
 
       final ToParentBlockJoinQuery childJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, agg);
@@ -584,7 +584,7 @@
 
       final boolean trackScores;
       final boolean trackMaxScore;
-      if (agg == ToParentBlockJoinQuery.ScoreMode.None) {
+      if (agg == ScoreMode.None) {
         trackScores = false;
         trackMaxScore = false;
       } else {
@@ -881,8 +881,8 @@
 
     // Wrap the child document query to 'join' any matches
     // up to corresponding parent:
-    ToParentBlockJoinQuery childJobJoinQuery = new ToParentBlockJoinQuery(childJobQuery, parentsFilter, ToParentBlockJoinQuery.ScoreMode.Avg);
-    ToParentBlockJoinQuery childQualificationJoinQuery = new ToParentBlockJoinQuery(childQualificationQuery, parentsFilter, ToParentBlockJoinQuery.ScoreMode.Avg);
+    ToParentBlockJoinQuery childJobJoinQuery = new ToParentBlockJoinQuery(childJobQuery, parentsFilter, ScoreMode.Avg);
+    ToParentBlockJoinQuery childQualificationJoinQuery = new ToParentBlockJoinQuery(childQualificationQuery, parentsFilter, ScoreMode.Avg);
 
     // Combine the parent and nested child queries into a single query for a candidate
     BooleanQuery fullQuery = new BooleanQuery();
@@ -952,7 +952,7 @@
                             new QueryWrapperFilter(
                               new TermQuery(new Term("parent", "1"))));
 
-    ToParentBlockJoinQuery q = new ToParentBlockJoinQuery(tq, parentFilter, ToParentBlockJoinQuery.ScoreMode.Avg);
+    ToParentBlockJoinQuery q = new ToParentBlockJoinQuery(tq, parentFilter, ScoreMode.Avg);
     Weight weight = s.createNormalizedWeight(q);
     DocIdSetIterator disi = weight.scorer(s.getIndexReader().getTopReaderContext().leaves()[0], true, true, null);
     assertEquals(1, disi.advance(1));
@@ -986,7 +986,7 @@
                             new QueryWrapperFilter(
                               new TermQuery(new Term("isparent", "yes"))));
 
-    ToParentBlockJoinQuery q = new ToParentBlockJoinQuery(tq, parentFilter, ToParentBlockJoinQuery.ScoreMode.Avg);
+    ToParentBlockJoinQuery q = new ToParentBlockJoinQuery(tq, parentFilter, ScoreMode.Avg);
     Weight weight = s.createNormalizedWeight(q);
     DocIdSetIterator disi = weight.scorer(s.getIndexReader().getTopReaderContext().leaves()[0], true, true, null);
     assertEquals(2, disi.advance(0));
