diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 43b13b17ddd..213832145ff 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -103,6 +103,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 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 5aaca1af153..aacda2d80fa 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 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 5e614ea41f3..1c80bf3bb8c 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 @@ -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; @@ -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, boost); + } return new W(this, toQuery.createWeight(searcher, false, 1f)); } @@ -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(); 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 537b2244aee..d7e0ae88438 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 @@ -104,7 +104,7 @@ public final class JoinUtil { termsWithScoreCollector = GenericTermsCollector.createCollectorSV(svFunction, scoreMode); } - return createJoinQuery(multipleValuesPerDocument, toField, fromQuery, fromSearcher, scoreMode, termsWithScoreCollector); + return createJoinQuery(multipleValuesPerDocument, toField, fromQuery, fromField, fromSearcher, scoreMode, termsWithScoreCollector); } /** @@ -362,7 +362,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) { @@ -379,25 +379,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)); @@ -507,7 +508,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; @@ -516,7 +518,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()); } } 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 70c28d58f9f..3130ae65db8 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; @@ -276,6 +278,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; @@ -290,7 +293,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 && 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 28d3044788c..cd3beaf646a 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; @@ -40,48 +39,36 @@ import org.apache.lucene.util.FixedBitSet; 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; + private final ScoreMode scoreMode; + private final String toField; + private final boolean multipleValuesPerDocument; + private final BytesRefHash terms; + private final float[] scores; + private final int[] ords; - TermsIncludingScoreQuery(String field, boolean multipleValuesPerDocument, BytesRefHash terms, float[] scores, Query originalQuery) { - this.field = field; + // 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 @@ -91,21 +78,25 @@ 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, float boost) throws IOException { + 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, boost); + } return new Weight(TermsIncludingScoreQuery.this) { @Override @@ -113,7 +104,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(); @@ -133,7 +124,7 @@ class TermsIncludingScoreQuery extends Query { @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; } @@ -151,7 +142,7 @@ class TermsIncludingScoreQuery extends Query { }; } - + class SVInOrderScorer extends Scorer { final DocIdSetIterator matchingDocsIterator; @@ -238,14 +229,4 @@ 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()+" "); - out.println(" score="+scores[ords[i]]); - out.println(""); - } - } } 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 63561c39e3b..3ff0a5c9ab3 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 { 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 72b6bf5beb1..c95e14479f3 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; @@ -922,6 +923,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 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 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 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 { 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 0d9801e6541..38c111a4072 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 @@ -158,6 +158,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{