You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mv...@apache.org on 2017/05/23 09:01:42 UTC

lucene-solr:branch_6x: LUCENE-7810: Fix equals() and hashCode() methods of several join queries.

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_6x d9a2e27c4 -> e30fbd68d


LUCENE-7810: Fix equals() and hashCode() methods of several join queries.


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/e30fbd68
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/e30fbd68
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/e30fbd68

Branch: refs/heads/branch_6x
Commit: e30fbd68df9fd10418c1ffcfb4b6742e3cfd4713
Parents: d9a2e27
Author: Martijn van Groningen <ma...@gmail.com>
Authored: Mon May 22 17:56:08 2017 +0200
Committer: Martijn van Groningen <mv...@apache.org>
Committed: Tue May 23 10:53:04 2017 +0200

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   5 +
 .../lucene/search/join/GlobalOrdinalsQuery.java |   6 +-
 .../join/GlobalOrdinalsWithScoreQuery.java      |  18 +-
 .../org/apache/lucene/search/join/JoinUtil.java |  39 +--
 .../join/PointInSetIncludingScoreQuery.java     |  10 +-
 .../search/join/TermsIncludingScoreQuery.java   | 107 +++-----
 .../apache/lucene/search/join/TermsQuery.java   |  39 +--
 .../apache/lucene/search/join/TestJoinUtil.java | 246 +++++++++++++++++++
 .../search/join/TestScoreJoinQPNoScore.java     |   9 +
 9 files changed, 369 insertions(+), 110 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e30fbd68/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index cf3b150..92cce81 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -11,6 +11,11 @@ Other
   from methods that don't declare them ("sneaky throw" hack). (Robert Muir,
   Uwe Schindler, Dawid Weiss)  
 
+Bug Fixes
+
+* LUCENE-7810: Fix equals() and hashCode() methods of several join queries.
+  (Hossman, Adrien Grand, Martijn van Groningen)
+
 ======================= Lucene 6.6.0 =======================
 
 New Features

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e30fbd68/lucene/join/src/java/org/apache/lucene/search/join/GlobalOrdinalsQuery.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/GlobalOrdinalsQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/GlobalOrdinalsQuery.java
index 00c7ae0..89ea942 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/GlobalOrdinalsQuery.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/GlobalOrdinalsQuery.java
@@ -20,7 +20,6 @@ import java.io.IOException;
 import java.util.Set;
 
 import org.apache.lucene.index.DocValues;
-import org.apache.lucene.index.IndexReaderContext;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.MultiDocValues;
 import org.apache.lucene.index.SortedDocValues;
@@ -51,13 +50,14 @@ final class GlobalOrdinalsQuery extends Query {
   // id of the context rather than the context itself in order not to hold references to index readers
   private final Object indexReaderContextId;
 
-  GlobalOrdinalsQuery(LongBitSet foundOrds, String joinField, MultiDocValues.OrdinalMap globalOrds, Query toQuery, Query fromQuery, IndexReaderContext context) {
+  GlobalOrdinalsQuery(LongBitSet foundOrds, String joinField, MultiDocValues.OrdinalMap globalOrds, Query toQuery,
+                      Query fromQuery, Object indexReaderContextId) {
     this.foundOrds = foundOrds;
     this.joinField = joinField;
     this.globalOrds = globalOrds;
     this.toQuery = toQuery;
     this.fromQuery = fromQuery;
-    this.indexReaderContextId = context.id();
+    this.indexReaderContextId = indexReaderContextId;
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e30fbd68/lucene/join/src/java/org/apache/lucene/search/join/GlobalOrdinalsWithScoreQuery.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/GlobalOrdinalsWithScoreQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/GlobalOrdinalsWithScoreQuery.java
index 7e54921..5b9ba88 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/GlobalOrdinalsWithScoreQuery.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/GlobalOrdinalsWithScoreQuery.java
@@ -17,7 +17,6 @@
 package org.apache.lucene.search.join;
 
 import org.apache.lucene.index.DocValues;
-import org.apache.lucene.index.IndexReaderContext;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.MultiDocValues;
 import org.apache.lucene.index.SortedDocValues;
@@ -45,21 +44,25 @@ final class GlobalOrdinalsWithScoreQuery extends Query {
   private final Query toQuery;
 
   // just for hashcode and equals:
+  private final ScoreMode scoreMode;
   private final Query fromQuery;
   private final int min;
   private final int max;
   // id of the context rather than the context itself in order not to hold references to index readers
   private final Object indexReaderContextId;
 
-  GlobalOrdinalsWithScoreQuery(GlobalOrdinalsWithScoreCollector collector, String joinField, MultiDocValues.OrdinalMap globalOrds, Query toQuery, Query fromQuery, int min, int max, IndexReaderContext context) {
+  GlobalOrdinalsWithScoreQuery(GlobalOrdinalsWithScoreCollector collector, ScoreMode scoreMode, String joinField,
+                               MultiDocValues.OrdinalMap globalOrds, Query toQuery, Query fromQuery, int min, int max,
+                               Object indexReaderContextId) {
     this.collector = collector;
     this.joinField = joinField;
     this.globalOrds = globalOrds;
     this.toQuery = toQuery;
+    this.scoreMode = scoreMode;
     this.fromQuery = fromQuery;
     this.min = min;
     this.max = max;
-    this.indexReaderContextId = context.id();
+    this.indexReaderContextId = indexReaderContextId;
   }
 
   @Override
@@ -67,6 +70,13 @@ final class GlobalOrdinalsWithScoreQuery extends Query {
     if (searcher.getTopReaderContext().id() != indexReaderContextId) {
       throw new IllegalStateException("Creating the weight against a different index reader than this query has been built for.");
     }
+    boolean doNoMinMax = min <= 0 && max == Integer.MAX_VALUE;
+    if (needsScores == false && doNoMinMax) {
+      // We don't need scores then quickly change the query to not uses the scores:
+      GlobalOrdinalsQuery globalOrdinalsQuery = new GlobalOrdinalsQuery(collector.collectedOrds, joinField, globalOrds,
+          toQuery, fromQuery, indexReaderContextId);
+      return globalOrdinalsQuery.createWeight(searcher, false);
+    }
     return new W(this, toQuery.createWeight(searcher, false));
   }
 
@@ -79,6 +89,7 @@ final class GlobalOrdinalsWithScoreQuery extends Query {
   private boolean equalsTo(GlobalOrdinalsWithScoreQuery other) {
     return min == other.min &&
            max == other.max &&
+           scoreMode.equals(other.scoreMode) &&
            joinField.equals(other.joinField) &&
            fromQuery.equals(other.fromQuery) &&
            toQuery.equals(other.toQuery) &&
@@ -88,6 +99,7 @@ final class GlobalOrdinalsWithScoreQuery extends Query {
   @Override
   public int hashCode() {
     int result = classHash();
+    result = 31 * result + scoreMode.hashCode();
     result = 31 * result + joinField.hashCode();
     result = 31 * result + toQuery.hashCode();
     result = 31 * result + fromQuery.hashCode();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e30fbd68/lucene/join/src/java/org/apache/lucene/search/join/JoinUtil.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/JoinUtil.java b/lucene/join/src/java/org/apache/lucene/search/join/JoinUtil.java
index bca5f61..c65649f 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/JoinUtil.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/JoinUtil.java
@@ -105,15 +105,15 @@ public final class JoinUtil {
       termsWithScoreCollector =  GenericTermsCollector.createCollectorSV(svFunction, scoreMode);
     }
     
-    return createJoinQuery(multipleValuesPerDocument, toField, fromQuery, fromSearcher, scoreMode,
+    return createJoinQuery(multipleValuesPerDocument, toField, fromQuery, fromField, fromSearcher, scoreMode,
         termsWithScoreCollector);
-    
+
   }
-  
+
   /**
    * @deprecated Because {@link LegacyNumericType} is deprecated, instead use {@link #createJoinQuery(String, boolean, String, Class, Query, IndexSearcher, ScoreMode)}
    *
-   * Method for query time joining for numeric fields. It supports multi- and single- values longs and ints. 
+   * Method for query time joining for numeric fields. It supports multi- and single- values longs and ints.
    * All considerations from {@link JoinUtil#createJoinQuery(String, boolean, String, Query, IndexSearcher, ScoreMode)} are applicable here too,
    * though memory consumption might be higher.
    * <p>
@@ -138,9 +138,9 @@ public final class JoinUtil {
       Query fromQuery,
       IndexSearcher fromSearcher,
       ScoreMode scoreMode) throws IOException {
-    
+
     final GenericTermsCollector termsCollector;
-     
+
     if (multipleValuesPerDocument) {
       Function<SortedSetDocValues> mvFunction = DocValuesTermsCollector.sortedNumericAsSortedSetDocValues(fromField,numericType);
       termsCollector = GenericTermsCollector.createCollectorMV(mvFunction, scoreMode);
@@ -148,10 +148,10 @@ public final class JoinUtil {
       Function<BinaryDocValues> svFunction = DocValuesTermsCollector.numericAsBinaryDocValues(fromField,numericType);
       termsCollector =  GenericTermsCollector.createCollectorSV(svFunction, scoreMode);
     }
-    
-    return createJoinQuery(multipleValuesPerDocument, toField, fromQuery, fromSearcher, scoreMode,
+
+    return createJoinQuery(multipleValuesPerDocument, toField, fromQuery, fromField, fromSearcher, scoreMode,
         termsCollector);
-    
+
   }
 
   /**
@@ -385,7 +385,7 @@ public final class JoinUtil {
     encoded.length = bytesPerDim;
 
     if (needsScore) {
-      return new PointInSetIncludingScoreQuery(fromQuery, multipleValuesPerDocument, toField, bytesPerDim, stream) {
+      return new PointInSetIncludingScoreQuery(scoreMode, fromQuery, multipleValuesPerDocument, toField, bytesPerDim, stream) {
 
         @Override
         protected String toString(byte[] value) {
@@ -402,25 +402,26 @@ public final class JoinUtil {
     }
   }
 
-  private static Query createJoinQuery(boolean multipleValuesPerDocument, String toField, Query fromQuery,
-      IndexSearcher fromSearcher, ScoreMode scoreMode, final GenericTermsCollector collector)
-          throws IOException {
+  private static Query createJoinQuery(boolean multipleValuesPerDocument, String toField, Query fromQuery, String fromField,
+      IndexSearcher fromSearcher, ScoreMode scoreMode, final GenericTermsCollector collector) throws IOException {
     
     fromSearcher.search(fromQuery, collector);
-    
     switch (scoreMode) {
       case None:
-        return new TermsQuery(toField, fromQuery, collector.getCollectedTerms());
+        return new TermsQuery(toField, collector.getCollectedTerms(), fromField, fromQuery, fromSearcher.getTopReaderContext().id());
       case Total:
       case Max:
       case Min:
       case Avg:
         return new TermsIncludingScoreQuery(
+            scoreMode,
             toField,
             multipleValuesPerDocument,
             collector.getCollectedTerms(),
             collector.getScoresPerTerm(),
-            fromQuery
+            fromField,
+            fromQuery,
+            fromSearcher.getTopReaderContext().id()
         );
       default:
         throw new IllegalArgumentException(String.format(Locale.ROOT, "Score mode %s isn't supported.", scoreMode));
@@ -530,7 +531,8 @@ public final class JoinUtil {
         if (min <= 0 && max == Integer.MAX_VALUE) {
           GlobalOrdinalsCollector globalOrdinalsCollector = new GlobalOrdinalsCollector(joinField, ordinalMap, valueCount);
           searcher.search(rewrittenFromQuery, globalOrdinalsCollector);
-          return new GlobalOrdinalsQuery(globalOrdinalsCollector.getCollectorOrdinals(), joinField, ordinalMap, rewrittenToQuery, rewrittenFromQuery, searcher.getTopReaderContext());
+          return new GlobalOrdinalsQuery(globalOrdinalsCollector.getCollectorOrdinals(), joinField, ordinalMap, rewrittenToQuery,
+              rewrittenFromQuery, searcher.getTopReaderContext().id());
         } else {
           globalOrdinalsWithScoreCollector = new GlobalOrdinalsWithScoreCollector.NoScore(joinField, ordinalMap, valueCount, min, max);
           break;
@@ -539,7 +541,8 @@ public final class JoinUtil {
         throw new IllegalArgumentException(String.format(Locale.ROOT, "Score mode %s isn't supported.", scoreMode));
     }
     searcher.search(rewrittenFromQuery, globalOrdinalsWithScoreCollector);
-    return new GlobalOrdinalsWithScoreQuery(globalOrdinalsWithScoreCollector, joinField, ordinalMap, rewrittenToQuery, rewrittenFromQuery, min, max, searcher.getTopReaderContext());
+    return new GlobalOrdinalsWithScoreQuery(globalOrdinalsWithScoreCollector, scoreMode, joinField, ordinalMap, rewrittenToQuery,
+        rewrittenFromQuery, min, max, searcher.getTopReaderContext().id());
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e30fbd68/lucene/join/src/java/org/apache/lucene/search/join/PointInSetIncludingScoreQuery.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/PointInSetIncludingScoreQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/PointInSetIncludingScoreQuery.java
index f80f1ae..861632b 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/PointInSetIncludingScoreQuery.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/PointInSetIncludingScoreQuery.java
@@ -66,6 +66,7 @@ abstract class PointInSetIncludingScoreQuery extends Query {
     }
   };
 
+  final ScoreMode scoreMode;
   final Query originalQuery;
   final boolean multipleValuesPerDocument;
   final PrefixCodedTerms sortedPackedPoints;
@@ -81,8 +82,9 @@ abstract class PointInSetIncludingScoreQuery extends Query {
 
   }
 
-  PointInSetIncludingScoreQuery(Query originalQuery, boolean multipleValuesPerDocument, String field, int bytesPerDim,
-                                Stream packedPoints) {
+  PointInSetIncludingScoreQuery(ScoreMode scoreMode, Query originalQuery, boolean multipleValuesPerDocument,
+                                String field, int bytesPerDim, Stream packedPoints) {
+    this.scoreMode = scoreMode;
     this.originalQuery = originalQuery;
     this.multipleValuesPerDocument = multipleValuesPerDocument;
     this.field = field;
@@ -287,6 +289,7 @@ abstract class PointInSetIncludingScoreQuery extends Query {
   @Override
   public final int hashCode() {
     int hash = classHash();
+    hash = 31 * hash + scoreMode.hashCode();
     hash = 31 * hash + field.hashCode();
     hash = 31 * hash + originalQuery.hashCode();
     hash = 31 * hash + sortedPackedPointsHashCode;
@@ -301,7 +304,8 @@ abstract class PointInSetIncludingScoreQuery extends Query {
   }
 
   private boolean equalsTo(PointInSetIncludingScoreQuery other) {
-    return other.field.equals(field) &&
+    return other.scoreMode.equals(scoreMode) &&
+           other.field.equals(field) &&
            other.originalQuery.equals(originalQuery) &&
            other.bytesPerDim == bytesPerDim &&
            other.sortedPackedPointsHashCode == sortedPackedPointsHashCode &&

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e30fbd68/lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java
index 94df35b..5bd36f5 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java
@@ -17,11 +17,10 @@
 package org.apache.lucene.search.join;
 
 import java.io.IOException;
-import java.io.PrintStream;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.Set;
 
-import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.PostingsEnum;
 import org.apache.lucene.index.Term;
@@ -41,48 +40,36 @@ import org.apache.lucene.util.LegacyNumericUtils;
 
 class TermsIncludingScoreQuery extends Query {
 
-  final String field;
-  final boolean multipleValuesPerDocument;
-  final BytesRefHash terms;
-  final float[] scores;
-  final int[] ords;
-  final Query originalQuery;
-  final Query unwrittenOriginalQuery;
-
-  TermsIncludingScoreQuery(String field, boolean multipleValuesPerDocument, BytesRefHash terms, float[] scores, Query originalQuery) {
-    this.field = field;
+  private final ScoreMode scoreMode;
+  private final String toField;
+  private final boolean multipleValuesPerDocument;
+  private final BytesRefHash terms;
+  private final float[] scores;
+  private final int[] ords;
+
+  // These fields are used for equals() and hashcode() only
+  private final Query fromQuery;
+  private final String fromField;
+  // id of the context rather than the context itself in order not to hold references to index readers
+  private final Object topReaderContextId;
+
+  TermsIncludingScoreQuery(ScoreMode scoreMode, String toField, boolean multipleValuesPerDocument, BytesRefHash terms, float[] scores,
+                           String fromField, Query fromQuery, Object indexReaderContextId) {
+    this.scoreMode = scoreMode;
+    this.toField = toField;
     this.multipleValuesPerDocument = multipleValuesPerDocument;
     this.terms = terms;
     this.scores = scores;
-    this.originalQuery = originalQuery;
     this.ords = terms.sort();
-    this.unwrittenOriginalQuery = originalQuery;
-  }
 
-  private TermsIncludingScoreQuery(String field, boolean multipleValuesPerDocument, BytesRefHash terms, float[] scores, int[] ords, Query originalQuery, Query unwrittenOriginalQuery) {
-    this.field = field;
-    this.multipleValuesPerDocument = multipleValuesPerDocument;
-    this.terms = terms;
-    this.scores = scores;
-    this.originalQuery = originalQuery;
-    this.ords = ords;
-    this.unwrittenOriginalQuery = unwrittenOriginalQuery;
+    this.fromField = fromField;
+    this.fromQuery = fromQuery;
+    this.topReaderContextId = indexReaderContextId;
   }
 
   @Override
   public String toString(String string) {
-    return String.format(Locale.ROOT, "TermsIncludingScoreQuery{field=%s;originalQuery=%s}", field, unwrittenOriginalQuery);
-  }
-
-  @Override
-  public Query rewrite(IndexReader reader) throws IOException {
-    final Query originalQueryRewrite = originalQuery.rewrite(reader);
-    if (originalQueryRewrite != originalQuery) {
-      return new TermsIncludingScoreQuery(field, multipleValuesPerDocument, terms, scores,
-          ords, originalQueryRewrite, originalQuery);
-    } else {
-      return super.rewrite(reader);
-    }
+    return String.format(Locale.ROOT, "TermsIncludingScoreQuery{field=%s;fromQuery=%s}", toField, fromQuery);
   }
 
   @Override
@@ -92,22 +79,26 @@ class TermsIncludingScoreQuery extends Query {
   }
   
   private boolean equalsTo(TermsIncludingScoreQuery other) {
-    return field.equals(other.field) &&
-           unwrittenOriginalQuery.equals(other.unwrittenOriginalQuery);
+    return Objects.equals(scoreMode, other.scoreMode) &&
+        Objects.equals(toField, other.toField) &&
+        Objects.equals(fromField, other.fromField) &&
+        Objects.equals(fromQuery, other.fromQuery) &&
+        Objects.equals(topReaderContextId, other.topReaderContextId);
   }
 
   @Override
   public int hashCode() {
-    final int prime = 31;
-    int result = classHash();
-    result += prime * field.hashCode();
-    result += prime * unwrittenOriginalQuery.hashCode();
-    return result;
+    return classHash() + Objects.hash(scoreMode, toField, fromField, fromQuery, topReaderContextId);
   }
 
   @Override
   public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
-    final Weight originalWeight = originalQuery.createWeight(searcher, needsScores);
+    if (needsScores == false) {
+      // We don't need scores then quickly change the query:
+      TermsQuery termsQuery = new TermsQuery(toField, terms, fromField, fromQuery, topReaderContextId);
+      return searcher.rewrite(termsQuery).createWeight(searcher, false);
+    }
+    final Weight fromWeight = fromQuery.createWeight(searcher, needsScores);
     return new Weight(TermsIncludingScoreQuery.this) {
 
       @Override
@@ -115,7 +106,7 @@ class TermsIncludingScoreQuery extends Query {
 
       @Override
       public Explanation explain(LeafReaderContext context, int doc) throws IOException {
-        Terms terms = context.reader().terms(field);
+        Terms terms = context.reader().terms(toField);
         if (terms != null) {
           TermsEnum segmentTermsEnum = terms.iterator();
           BytesRef spare = new BytesRef();
@@ -135,17 +126,17 @@ class TermsIncludingScoreQuery extends Query {
 
       @Override
       public float getValueForNormalization() throws IOException {
-        return originalWeight.getValueForNormalization();
+        return fromWeight.getValueForNormalization();
       }
 
       @Override
       public void normalize(float norm, float boost) {
-        originalWeight.normalize(norm, boost);
+        fromWeight.normalize(norm, boost);
       }
 
       @Override
       public Scorer scorer(LeafReaderContext context) throws IOException {
-        Terms terms = context.reader().terms(field);
+        Terms terms = context.reader().terms(toField);
         if (terms == null) {
           return null;
         }
@@ -163,7 +154,7 @@ class TermsIncludingScoreQuery extends Query {
 
     };
   }
-  
+
   class SVInOrderScorer extends Scorer {
 
     final DocIdSetIterator matchingDocsIterator;
@@ -249,23 +240,5 @@ class TermsIncludingScoreQuery extends Query {
       }
     }
   }
-  
-  void dump(PrintStream out){
-    out.println(field+":");
-    final BytesRef ref = new BytesRef();
-    for (int i = 0; i < terms.size(); i++) {
-      terms.get(ords[i], ref);
-      out.print(ref+" "+ref.utf8ToString()+" ");
-      try {
-        out.print(Long.toHexString(LegacyNumericUtils.prefixCodedToLong(ref))+"L");
-      } catch (Exception e) {
-        try {
-          out.print(Integer.toHexString(LegacyNumericUtils.prefixCodedToInt(ref))+"i");
-        } catch (Exception ee) {
-        }
-      }
-      out.println(" score="+scores[ords[i]]);
-      out.println("");
-    }
-  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e30fbd68/lucene/join/src/java/org/apache/lucene/search/join/TermsQuery.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/TermsQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/TermsQuery.java
index 63561c3..3ff0a5c 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/TermsQuery.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/TermsQuery.java
@@ -16,6 +16,9 @@
  */
 package org.apache.lucene.search.join;
 
+import java.io.IOException;
+import java.util.Objects;
+
 import org.apache.lucene.index.FilteredTermsEnum;
 import org.apache.lucene.index.Terms;
 import org.apache.lucene.index.TermsEnum;
@@ -25,8 +28,6 @@ import org.apache.lucene.util.AttributeSource;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.BytesRefHash;
 
-import java.io.IOException;
-
 /**
  * A query that has an array of terms from a specific field. This query will match documents have one or more terms in
  * the specified field that match with the terms specified in the array.
@@ -37,17 +38,25 @@ class TermsQuery extends MultiTermQuery {
 
   private final BytesRefHash terms;
   private final int[] ords;
-  private final Query fromQuery; // Used for equals() only
+
+  // These fields are used for equals() and hashcode() only
+  private final String fromField;
+  private final Query fromQuery;
+  // id of the context rather than the context itself in order not to hold references to index readers
+  private final Object indexReaderContextId;
 
   /**
-   * @param field The field that should contain terms that are specified in the previous parameter
-   * @param terms The terms that matching documents should have. The terms must be sorted by natural order.
+   * @param toField               The field that should contain terms that are specified in the next parameter.
+   * @param terms                 The terms that matching documents should have. The terms must be sorted by natural order.
+   * @param indexReaderContextId  Refers to the top level index reader used to create the set of terms in the previous parameter.
    */
-  TermsQuery(String field, Query fromQuery, BytesRefHash terms) {
-    super(field);
-    this.fromQuery = fromQuery;
+  TermsQuery(String toField, BytesRefHash terms, String fromField, Query fromQuery, Object indexReaderContextId) {
+    super(toField);
     this.terms = terms;
     ords = terms.sort();
+    this.fromField = fromField;
+    this.fromQuery = fromQuery;
+    this.indexReaderContextId = indexReaderContextId;
   }
 
   @Override
@@ -63,6 +72,7 @@ class TermsQuery extends MultiTermQuery {
   public String toString(String string) {
     return "TermsQuery{" +
         "field=" + field +
+        "fromQuery=" + fromQuery.toString(field) +
         '}';
   }
 
@@ -77,18 +87,15 @@ class TermsQuery extends MultiTermQuery {
     }
 
     TermsQuery other = (TermsQuery) obj;
-    if (!fromQuery.equals(other.fromQuery)) {
-      return false;
-    }
-    return true;
+    return Objects.equals(field, other.field) &&
+        Objects.equals(fromField, other.fromField) &&
+        Objects.equals(fromQuery, other.fromQuery) &&
+        Objects.equals(indexReaderContextId, other.indexReaderContextId);
   }
 
   @Override
   public int hashCode() {
-    final int prime = 31;
-    int result = super.hashCode();
-    result += prime * fromQuery.hashCode();
-    return result;
+    return classHash() + Objects.hash(field, fromField, fromQuery, indexReaderContextId);
   }
 
   static class SeekingTermSetTermsEnum extends FilteredTermsEnum {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e30fbd68/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java b/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
index 9452847..e089cd5 100644
--- a/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
+++ b/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -934,6 +935,251 @@ public class TestJoinUtil extends LuceneTestCase {
     dir.close();
   }
 
+  public void testEquals() throws Exception {
+    final int numDocs = atLeast(random(), 50);
+    try (final Directory dir = newDirectory()) {
+      try (final RandomIndexWriter w = new RandomIndexWriter(random(), dir,
+          newIndexWriterConfig(new MockAnalyzer(random()))
+              .setMergePolicy(newLogMergePolicy()))) {
+        boolean multiValued = random().nextBoolean();
+        String joinField = multiValued ? "mvField" : "svField";
+        for (int id = 0; id < numDocs; id++) {
+          Document doc = new Document();
+          doc.add(new TextField("id", "" + id, Field.Store.NO));
+          doc.add(new TextField("name", "name" + (id % 7), Field.Store.NO));
+          if (multiValued) {
+            int numValues = 1 + random().nextInt(2);
+            for (int i = 0; i < numValues; i++) {
+              doc.add(new SortedSetDocValuesField(joinField, new BytesRef("" + random().nextInt(13))));
+            }
+          } else {
+            doc.add(new SortedDocValuesField(joinField, new BytesRef("" + random().nextInt(13))));
+          }
+          w.addDocument(doc);
+        }
+
+        Set<ScoreMode> scoreModes = EnumSet.allOf(ScoreMode.class);
+        ScoreMode scoreMode1 = RandomPicks.randomFrom(random(), scoreModes);
+        scoreModes.remove(scoreMode1);
+        ScoreMode scoreMode2 = RandomPicks.randomFrom(random(), scoreModes);
+
+        final Query x;
+        try (IndexReader r = w.getReader()) {
+          IndexSearcher indexSearcher = new IndexSearcher(r);
+          x = JoinUtil.createJoinQuery(joinField, multiValued, joinField,
+              new TermQuery(new Term("name", "name5")),
+              indexSearcher, scoreMode1);
+          assertEquals("identical calls to createJoinQuery",
+              x, JoinUtil.createJoinQuery(joinField, multiValued, joinField,
+                  new TermQuery(new Term("name", "name5")),
+                  indexSearcher, scoreMode1));
+
+          assertFalse("score mode (" + scoreMode1 + " != " + scoreMode2 + "), but queries are equal",
+              x.equals(JoinUtil.createJoinQuery(joinField, multiValued, joinField,
+                  new TermQuery(new Term("name", "name5")),
+                  indexSearcher, scoreMode2)));
+
+
+          assertFalse("from fields (joinField != \"other_field\") but queries equals",
+              x.equals(JoinUtil.createJoinQuery(joinField, multiValued, "other_field",
+                  new TermQuery(new Term("name", "name5")),
+                  indexSearcher, scoreMode1)));
+
+          assertFalse("from fields (\"other_field\" != joinField) but queries equals",
+              x.equals(JoinUtil.createJoinQuery("other_field", multiValued, joinField,
+                  new TermQuery(new Term("name", "name5")),
+                  indexSearcher, scoreMode1)));
+
+          assertFalse("fromQuery (name:name5 != name:name6) but queries equals",
+              x.equals(JoinUtil.createJoinQuery("other_field", multiValued, joinField,
+                  new TermQuery(new Term("name", "name6")),
+                  indexSearcher, scoreMode1)));
+        }
+
+        for (int i = 0; i < 13; i++) {
+          Document doc = new Document();
+          doc.add(new TextField("id", "new_id" , Field.Store.NO));
+          doc.add(new TextField("name", "name5", Field.Store.NO));
+          if (multiValued) {
+            int numValues = 1 + random().nextInt(2);
+            for (int j = 0; j < numValues; j++) {
+              doc.add(new SortedSetDocValuesField(joinField, new BytesRef("" + i)));
+            }
+          } else {
+            doc.add(new SortedDocValuesField(joinField, new BytesRef("" + i)));
+          }
+          w.addDocument(doc);
+        }
+        try (IndexReader r = w.getReader()) {
+          IndexSearcher indexSearcher = new IndexSearcher(r);
+          assertFalse("Query shouldn't be equal, because different index readers ",
+              x.equals(JoinUtil.createJoinQuery(joinField, multiValued, joinField,
+                  new TermQuery(new Term("name", "name5")),
+                  indexSearcher, scoreMode1)));
+        }
+      }
+    }
+  }
+
+  public void testEquals_globalOrdinalsJoin() throws Exception {
+    final int numDocs = atLeast(random(), 50);
+    try (final Directory dir = newDirectory()) {
+      try (final RandomIndexWriter w = new RandomIndexWriter(random(), dir,
+          newIndexWriterConfig(new MockAnalyzer(random()))
+              .setMergePolicy(newLogMergePolicy()))) {
+        String joinField = "field";
+        for (int id = 0; id < numDocs; id++) {
+          Document doc = new Document();
+          doc.add(new TextField("id", "" + id, Field.Store.NO));
+          doc.add(new TextField("name", "name" + (id % 7), Field.Store.NO));
+          doc.add(new SortedDocValuesField(joinField, new BytesRef("" + random().nextInt(13))));
+          w.addDocument(doc);
+        }
+
+        Set<ScoreMode> scoreModes = EnumSet.allOf(ScoreMode.class);
+        ScoreMode scoreMode1 = RandomPicks.randomFrom(random(), scoreModes);
+        scoreModes.remove(scoreMode1);
+        ScoreMode scoreMode2 = RandomPicks.randomFrom(random(), scoreModes);
+
+        final Query x;
+        try (IndexReader r = w.getReader()) {
+          SortedDocValues[] values = new SortedDocValues[r.leaves().size()];
+          for (int i = 0; i < values.length; i++) {
+            LeafReader leafReader =  r.leaves().get(i).reader();
+            values[i] = DocValues.getSorted(leafReader, joinField);
+          }
+          MultiDocValues.OrdinalMap ordinalMap = MultiDocValues.OrdinalMap.build(
+              null, values, PackedInts.DEFAULT
+          );
+          IndexSearcher indexSearcher = new IndexSearcher(r);
+          x = JoinUtil.createJoinQuery(joinField, new TermQuery(new Term("name", "name5")), new MatchAllDocsQuery(),
+              indexSearcher, scoreMode1, ordinalMap);
+          assertEquals("identical calls to createJoinQuery",
+              x, JoinUtil.createJoinQuery(joinField, new TermQuery(new Term("name", "name5")), new MatchAllDocsQuery(),
+                  indexSearcher, scoreMode1, ordinalMap));
+
+          assertFalse("score mode (" + scoreMode1 + " != " + scoreMode2 + "), but queries are equal",
+              x.equals(JoinUtil.createJoinQuery(joinField, new TermQuery(new Term("name", "name5")), new MatchAllDocsQuery(),
+                  indexSearcher, scoreMode2, ordinalMap)));
+          assertFalse("fromQuery (name:name5 != name:name6) but queries equals",
+              x.equals(JoinUtil.createJoinQuery(joinField, new TermQuery(new Term("name", "name6")), new MatchAllDocsQuery(),
+                  indexSearcher, scoreMode1, ordinalMap)));
+        }
+
+        for (int i = 0; i < 13; i++) {
+          Document doc = new Document();
+          doc.add(new TextField("id", "new_id" , Field.Store.NO));
+          doc.add(new TextField("name", "name5", Field.Store.NO));
+          doc.add(new SortedDocValuesField(joinField, new BytesRef("" + i)));
+          w.addDocument(doc);
+        }
+        try (IndexReader r = w.getReader()) {
+          SortedDocValues[] values = new SortedDocValues[r.leaves().size()];
+          for (int i = 0; i < values.length; i++) {
+            LeafReader leafReader =  r.leaves().get(i).reader();
+            values[i] = DocValues.getSorted(leafReader, joinField);
+          }
+          MultiDocValues.OrdinalMap ordinalMap = MultiDocValues.OrdinalMap.build(
+              null, values, PackedInts.DEFAULT
+          );
+          IndexSearcher indexSearcher = new IndexSearcher(r);
+          assertFalse("Query shouldn't be equal, because different index readers ",
+              x.equals(JoinUtil.createJoinQuery(joinField, new TermQuery(new Term("name", "name5")), new MatchAllDocsQuery(),
+                  indexSearcher, scoreMode1, ordinalMap)));
+        }
+      }
+    }
+  }
+
+  public void testEquals_numericJoin() throws Exception {
+    final int numDocs = atLeast(random(), 50);
+    try (final Directory dir = newDirectory()) {
+      try (final RandomIndexWriter w = new RandomIndexWriter(random(), dir,
+          newIndexWriterConfig(new MockAnalyzer(random()))
+              .setMergePolicy(newLogMergePolicy()))) {
+        boolean multiValued = random().nextBoolean();
+        String joinField = multiValued ? "mvField" : "svField";
+        for (int id = 0; id < numDocs; id++) {
+          Document doc = new Document();
+          doc.add(new TextField("id", "" + id, Field.Store.NO));
+          doc.add(new TextField("name", "name" + (id % 7), Field.Store.NO));
+          if (multiValued) {
+            int numValues = 1 + random().nextInt(2);
+            for (int i = 0; i < numValues; i++) {
+              doc.add(new IntPoint(joinField, random().nextInt(13)));
+              doc.add(new SortedNumericDocValuesField(joinField, random().nextInt(13)));
+            }
+          } else {
+            doc.add(new IntPoint(joinField, random().nextInt(13)));
+            doc.add(new NumericDocValuesField(joinField, random().nextInt(13)));
+          }
+          w.addDocument(doc);
+        }
+
+        Set<ScoreMode> scoreModes = EnumSet.allOf(ScoreMode.class);
+        ScoreMode scoreMode1 = scoreModes.toArray(new ScoreMode[0])[random().nextInt(scoreModes.size())];
+        scoreModes.remove(scoreMode1);
+        ScoreMode scoreMode2 = scoreModes.toArray(new ScoreMode[0])[random().nextInt(scoreModes.size())];
+
+        final Query x;
+        try (IndexReader r = w.getReader()) {
+          IndexSearcher indexSearcher = new IndexSearcher(r);
+          x = JoinUtil.createJoinQuery(joinField, multiValued, joinField,
+              Integer.class, new TermQuery(new Term("name", "name5")),
+              indexSearcher, scoreMode1);
+          assertEquals("identical calls to createJoinQuery",
+              x, JoinUtil.createJoinQuery(joinField, multiValued, joinField,
+                  Integer.class, new TermQuery(new Term("name", "name5")),
+                  indexSearcher, scoreMode1));
+
+          assertFalse("score mode (" + scoreMode1 + " != " + scoreMode2 + "), but queries are equal",
+              x.equals(JoinUtil.createJoinQuery(joinField, multiValued, joinField,
+                  Integer.class, new TermQuery(new Term("name", "name5")),
+                  indexSearcher, scoreMode2)));
+
+          assertFalse("from fields (joinField != \"other_field\") but queries equals",
+              x.equals(JoinUtil.createJoinQuery(joinField, multiValued, "other_field",
+                  Integer.class, new TermQuery(new Term("name", "name5")),
+                  indexSearcher, scoreMode1)));
+
+          assertFalse("from fields (\"other_field\" != joinField) but queries equals",
+              x.equals(JoinUtil.createJoinQuery("other_field", multiValued, joinField,
+                  Integer.class, new TermQuery(new Term("name", "name5")),
+                  indexSearcher, scoreMode1)));
+
+          assertFalse("fromQuery (name:name5 != name:name6) but queries equals",
+              x.equals(JoinUtil.createJoinQuery("other_field", multiValued, joinField,
+                  Integer.class, new TermQuery(new Term("name", "name6")),
+                  indexSearcher, scoreMode1)));
+        }
+
+        for (int i = 0; i < 13; i++) {
+          Document doc = new Document();
+          doc.add(new TextField("id", "new_id" , Field.Store.NO));
+          doc.add(new TextField("name", "name5", Field.Store.NO));
+          if (multiValued) {
+            int numValues = 1 + random().nextInt(2);
+            for (int j = 0; j < numValues; j++) {
+              doc.add(new SortedNumericDocValuesField(joinField, i));
+              doc.add(new IntPoint(joinField, i));
+            }
+          } else {
+            doc.add(new NumericDocValuesField(joinField, i));
+            doc.add(new IntPoint(joinField, i));
+          }
+          w.addDocument(doc);
+        }
+        try (IndexReader r = w.getReader()) {
+          IndexSearcher indexSearcher = new IndexSearcher(r);
+          assertFalse("Query shouldn't be equal, because different index readers ",
+              x.equals(JoinUtil.createJoinQuery(joinField, multiValued, joinField,
+                  Integer.class, new TermQuery(new Term("name", "name5")),
+                  indexSearcher, scoreMode1)));
+        }
+      }
+    }
+  }
+
   @Test
   @Slow
   public void testSingleValueRandomJoin() throws Exception {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e30fbd68/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPNoScore.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPNoScore.java b/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPNoScore.java
index e36f37b..0ee7bdb 100644
--- a/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPNoScore.java
+++ b/solr/core/src/test/org/apache/solr/search/join/TestScoreJoinQPNoScore.java
@@ -199,6 +199,15 @@ public class TestScoreJoinQPNoScore extends SolrTestCaseJ4 {
 
   }
 
+  public void testNotEquals() throws SyntaxError, IOException{
+    try (SolrQueryRequest req = req("*:*")) {
+      Query x = QParser.getParser("{!join from=dept_id_s to=dept_ss score=none}text_t:develop", req).getQuery();
+      Query y = QParser.getParser("{!join from=dept_ss to=dept_ss score=none}text_t:develop", req).getQuery();
+      assertFalse("diff from fields produce equal queries",
+                  x.equals(y));
+    }
+  }
+    
   public void testJoinQueryType() throws SyntaxError, IOException{
     SolrQueryRequest req = null;
     try{