diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 3648914e142..4a53a8c9230 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -230,6 +230,8 @@ Optimizations --------------------- * SOLR-14975: Optimize CoreContainer.getAllCoreNames, getLoadedCoreNames and getCoreDescriptors. (Bruno Roustant) +* SOLR-15049: Optimize same-core, same-field joins in TopLevelJoinQuery (Jason Gerlowski) + Bug Fixes --------------------- * SOLR-14946: Fix responseHeader being returned in response when omitHeader=true and EmbeddedSolrServer is used diff --git a/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java index 5149b02d980..952a1eb246c 100644 --- a/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java +++ b/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java @@ -90,7 +90,7 @@ public class JoinQParserPlugin extends QParserPlugin { @Override Query makeFilter(QParser qparser, JoinQParserPlugin plugin) throws SyntaxError { final JoinParams jParams = parseJoin(qparser); - final JoinQuery q = new TopLevelJoinQuery(jParams.fromField, jParams.toField, jParams.fromCore, jParams.fromQuery); + final JoinQuery q = createTopLevelJoin(jParams); q.fromCoreOpenTime = jParams.fromCoreOpenTime; return q; } @@ -99,6 +99,18 @@ public class JoinQParserPlugin extends QParserPlugin { Query makeJoinDirectFromParams(JoinParams jParams) { return new TopLevelJoinQuery(jParams.fromField, jParams.toField, null, jParams.fromQuery); } + + private JoinQuery createTopLevelJoin(JoinParams jParams) { + if (isSelfJoin(jParams)) { + return new TopLevelJoinQuery.SelfJoin(jParams.fromField, jParams.fromQuery); + } + return new TopLevelJoinQuery(jParams.fromField, jParams.toField, jParams.fromCore, jParams.fromQuery); + } + + private boolean isSelfJoin(JoinParams jparams) { + return jparams.fromCore == null && + (jparams.fromField != null && jparams.fromField.equals(jparams.toField)); + } }, crossCollection { @Override diff --git a/solr/core/src/java/org/apache/solr/search/TopLevelJoinQuery.java b/solr/core/src/java/org/apache/solr/search/TopLevelJoinQuery.java index 428c229b0d2..8ae1d48d03f 100644 --- a/solr/core/src/java/org/apache/solr/search/TopLevelJoinQuery.java +++ b/solr/core/src/java/org/apache/solr/search/TopLevelJoinQuery.java @@ -162,7 +162,7 @@ public class TopLevelJoinQuery extends JoinQuery { return fromOrdBitSet; } - private BitsetBounds convertFromOrdinalsIntoToField(LongBitSet fromOrdBitSet, SortedSetDocValues fromDocValues, + protected BitsetBounds convertFromOrdinalsIntoToField(LongBitSet fromOrdBitSet, SortedSetDocValues fromDocValues, LongBitSet toOrdBitSet, SortedSetDocValues toDocValues) throws IOException { long fromOrdinal = 0; long firstToOrd = BitsetBounds.NO_MATCHES; @@ -208,7 +208,7 @@ public class TopLevelJoinQuery extends JoinQuery { return -(low + 1); // key not found. } - private static class BitsetBounds { + protected static class BitsetBounds { public static final long NO_MATCHES = -1L; public final long lower; public final long upper; @@ -218,4 +218,28 @@ public class TopLevelJoinQuery extends JoinQuery { this.upper = upper; } } + + /** + * A {@link TopLevelJoinQuery} implementation optimized for when 'from' and 'to' cores and fields match and no ordinal- + * conversion is necessary. + */ + static class SelfJoin extends TopLevelJoinQuery { + public SelfJoin(String joinField, Query subQuery) { + super(joinField, joinField, null, subQuery); + } + + protected BitsetBounds convertFromOrdinalsIntoToField(LongBitSet fromOrdBitSet, SortedSetDocValues fromDocValues, + LongBitSet toOrdBitSet, SortedSetDocValues toDocValues) throws IOException { + + // 'from' and 'to' ordinals are identical for self-joins. + toOrdBitSet.or(fromOrdBitSet); + + // Calculate boundary ords used for other optimizations + final long firstToOrd = toOrdBitSet.nextSetBit(0); + final long lastToOrd = toOrdBitSet.prevSetBit(toOrdBitSet.length() - 1); + return new BitsetBounds(firstToOrd, lastToOrd); + } + } } + +