diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java index ca6fbd6f390a8..e21d68ba0b9e0 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java @@ -33,7 +33,6 @@ import org.apache.calcite.plan.ConventionTraitDef; import org.apache.calcite.plan.RelTraitDef; import org.apache.calcite.rel.RelCollationTraitDef; -import org.apache.calcite.rel.core.Correlate; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlDdl; @@ -145,8 +144,6 @@ public class CalciteQueryProcessor extends GridProcessorAdapter implements Query // See Apache Calcite ticket for more information, assertion is incorrect. // FRAMEWORK_CONFIG initialize SqlToRelConverter class. Assertions should be disabled before class initialization. SqlToRelConverter.class.getClassLoader().setClassAssertionStatus(SqlToRelConverter.class.getName(), false); - // TODO Workaround for https://issues.apache.org/jira/browse/CALCITE-5421 and https://issues.apache.org/jira/browse/CALCITE-7034 - Correlate.class.getClassLoader().setClassAssertionStatus(Correlate.class.getName(), false); } /** diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java index 1c4d902c596f4..6566da92626db 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java @@ -87,9 +87,6 @@ public abstract class MergeJoinNode extends AbstractNode { */ protected final boolean distributed; - /** Flag indicating that join is in finishing stage (one of the inputs are ended, no more rows will be produced). */ - protected boolean finishing; - /** * @param ctx Execution context. * @param comp Join expression. @@ -181,10 +178,10 @@ private void pushLeft(Row row) throws Exception { waitingLeft--; - if (!finishing) - leftInBuf.add(row); + leftInBuf.add(row); - join(); + if (waitingLeft == 0 && waitingRight <= 0) + join(); } /** */ @@ -194,10 +191,10 @@ private void pushRight(Row row) throws Exception { waitingRight--; - if (!finishing) - rightInBuf.add(row); + rightInBuf.add(row); - join(); + if (waitingRight == 0 && waitingLeft <= 0) + join(); } /** */ @@ -207,7 +204,8 @@ private void endLeft() throws Exception { waitingLeft = NOT_WAITING; - join(); + if (waitingRight <= 0) + join(); } /** */ @@ -217,7 +215,8 @@ private void endRight() throws Exception { waitingRight = NOT_WAITING; - join(); + if (waitingLeft <= 0) + join(); } /** */ @@ -241,27 +240,6 @@ protected boolean rightFinished(boolean withMaterialization) { && (!withMaterialization || rightMaterialization == null); } - /** */ - protected boolean checkJoinFinished() throws Exception { - if (!finishing) { - finishing = true; - leftInBuf.clear(); - rightInBuf.clear(); - rightMaterialization = null; - rightIdx = 0; - drainMaterialization = false; - } - - if (!distributed || (waitingLeft == NOT_WAITING && waitingRight == NOT_WAITING)) { - requested = 0; - downstream().end(); - - return true; - } - - return false; - } - /** */ protected void tryToRequestInputs() throws Exception { if (waitingLeft == 0 && leftInBuf.size() <= HALF_BUF_SIZE) @@ -417,8 +395,12 @@ else if (cmp > 0) { inLoop = false; } - if (requested > 0 && (leftFinished() || rightFinished(true)) && checkJoinFinished()) + if (requested > 0 && (leftFinished() || rightFinished(true))) { + requested = 0; + downstream().end(); + return; + } tryToRequestInputs(); } @@ -571,8 +553,12 @@ else if (cmp > 0) { inLoop = false; } - if (requested > 0 && leftFinished() && checkJoinFinished()) + if (requested > 0 && leftFinished()) { + requested = 0; + downstream().end(); + return; + } tryToRequestInputs(); } @@ -737,8 +723,12 @@ else if (cmp > 0) { inLoop = false; } - if (requested > 0 && rightFinished(true) && checkJoinFinished()) + if (requested > 0 && rightFinished(true)) { + requested = 0; + downstream().end(); + return; + } tryToRequestInputs(); } @@ -942,8 +932,12 @@ else if (cmp > 0) { inLoop = false; } - if (requested > 0 && leftFinished() && rightFinished(true) && checkJoinFinished()) + if (requested > 0 && leftFinished() && rightFinished(true)) { + requested = 0; + downstream().end(); + return; + } tryToRequestInputs(); } @@ -998,8 +992,12 @@ else if (cmp > 0) { inLoop = false; } - if (requested > 0 && (leftFinished() || rightFinished(false)) && checkJoinFinished()) + if (requested > 0 && (leftFinished() || rightFinished(false))) { + requested = 0; + downstream().end(); + return; + } tryToRequestInputs(); } @@ -1057,8 +1055,12 @@ else if (cmp > 0) { inLoop = false; } - if (requested > 0 && leftFinished() && checkJoinFinished()) + if (requested > 0 && leftFinished()) { + requested = 0; + downstream().end(); + return; + } tryToRequestInputs(); } diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdColumnOrigins.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdColumnOrigins.java index 3606dc38a1e94..0f1bceec779e3 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdColumnOrigins.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdColumnOrigins.java @@ -51,7 +51,6 @@ import org.apache.calcite.rex.RexSlot; import org.apache.calcite.rex.RexVisitor; import org.apache.calcite.rex.RexVisitorImpl; -import org.apache.calcite.util.BuiltInMethod; import org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilterableTableScan; import org.jetbrains.annotations.Nullable; @@ -61,8 +60,9 @@ */ public class IgniteMdColumnOrigins implements MetadataHandler { /** */ - public static final RelMetadataProvider SOURCE = ReflectiveRelMetadataProvider.reflectiveSource( - BuiltInMethod.COLUMN_ORIGIN.method, new IgniteMdColumnOrigins()); + public static final RelMetadataProvider SOURCE = + ReflectiveRelMetadataProvider.reflectiveSource( + new IgniteMdColumnOrigins(), BuiltInMetadata.ColumnOrigin.Handler.class); /** {@inheritDoc} */ @Override public MetadataDef getDef() { @@ -369,15 +369,15 @@ private static Set getMultipleColumns(RexNode rexNode, RelNode final Set set = new HashSet<>(); final RexVisitor visitor = new RexVisitorImpl(true) { - @Override public Void visitInputRef(RexInputRef inputRef) { - Set inputSet = mq.getColumnOrigins(input, inputRef.getIndex()); + @Override public Void visitInputRef(RexInputRef inputRef) { + Set inputSet = mq.getColumnOrigins(input, inputRef.getIndex()); - if (inputSet != null) - set.addAll(inputSet); + if (inputSet != null) + set.addAll(inputSet); - return null; - } - }; + return null; + } + }; rexNode.accept(visitor); diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdRowCount.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdRowCount.java index 756e4b4e0c6f5..aa7f39b0c03c8 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdRowCount.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdRowCount.java @@ -17,42 +17,58 @@ package org.apache.ignite.internal.processors.query.calcite.metadata; +import java.util.BitSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.core.Intersect; import org.apache.calcite.rel.core.Join; import org.apache.calcite.rel.core.JoinInfo; import org.apache.calcite.rel.core.JoinRelType; import org.apache.calcite.rel.core.Minus; import org.apache.calcite.rel.core.Sort; +import org.apache.calcite.rel.metadata.BuiltInMetadata; import org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider; +import org.apache.calcite.rel.metadata.RelColumnOrigin; import org.apache.calcite.rel.metadata.RelMdRowCount; import org.apache.calcite.rel.metadata.RelMdUtil; import org.apache.calcite.rel.metadata.RelMetadataProvider; import org.apache.calcite.rel.metadata.RelMetadataQuery; import org.apache.calcite.rex.RexNode; -import org.apache.calcite.util.BuiltInMethod; import org.apache.calcite.util.ImmutableBitSet; import org.apache.calcite.util.ImmutableIntList; import org.apache.calcite.util.Util; +import org.apache.calcite.util.mapping.IntPair; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSortedIndexSpool; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableModify; +import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable; +import org.apache.ignite.internal.util.GridLeanMap; import org.apache.ignite.internal.util.typedef.F; import org.jetbrains.annotations.Nullable; import static org.apache.calcite.util.NumberUtil.multiply; + /** */ @SuppressWarnings("unused") // actually all methods are used by runtime generated classes public class IgniteMdRowCount extends RelMdRowCount { + /** */ + private static final double NON_EQUI_COEFF = 0.7; + + /** */ + public static final double EQUI_COEFF = 0.8; + /** */ public static final RelMetadataProvider SOURCE = - ReflectiveRelMetadataProvider.reflectiveSource( - BuiltInMethod.ROW_COUNT.method, new IgniteMdRowCount()); + ReflectiveRelMetadataProvider.reflectiveSource(new IgniteMdRowCount(), BuiltInMetadata.RowCount.Handler.class); /** {@inheritDoc} */ @Override public Double getRowCount(Join rel, RelMetadataQuery mq) { - return rel.estimateRowCount(mq); + return joinRowCount(mq, rel); } /** {@inheritDoc} */ @@ -72,48 +88,252 @@ public class IgniteMdRowCount extends RelMdRowCount { mq.getRowCount(rel.getLeft())); } + JoinInfo joinInfo = rel.analyzeCondition(); + + if (joinInfo.pairs().isEmpty()) { + // Fall-back to calcite's implementation. + return RelMdUtil.getJoinRowCount(mq, rel, rel.getCondition()); + } + // Row count estimates of 0 will be rounded up to 1. // So, use maxRowCount where the product is very small. - final Double left = mq.getRowCount(rel.getLeft()); - final Double right = mq.getRowCount(rel.getRight()); + final Double leftRowCnt = mq.getRowCount(rel.getLeft()); + final Double rightRowCnt = mq.getRowCount(rel.getRight()); - if (left == null || right == null) + if (leftRowCnt == null || rightRowCnt == null) return null; - if (left <= 1D || right <= 1D) { + if (leftRowCnt <= 1D || rightRowCnt <= 1D) { Double max = mq.getMaxRowCount(rel); if (max != null && max <= 1D) return max; } - JoinInfo joinInfo = rel.analyzeCondition(); + Map columnsFromLeft = resolveOrigins(mq, rel.getLeft(), joinInfo.leftKeys); + Map columnsFromRight = resolveOrigins(mq, rel.getRight(), joinInfo.rightKeys); + + if (columnsFromLeft.isEmpty() || columnsFromRight.isEmpty()) + return crudeEstimation(mq, joinInfo, rel, leftRowCnt, rightRowCnt); + + Map joinCtxts = new HashMap<>(); + for (IntPair joinKeys : joinInfo.pairs()) { + KeyColumnOrigin leftKey = columnsFromLeft.get(joinKeys.source); + KeyColumnOrigin rightKey = columnsFromRight.get(joinKeys.target); + + if (leftKey == null || rightKey == null) { + continue; + } + + joinCtxts.computeIfAbsent( + new TablesPair( + leftKey.origin.getOriginTable(), + rightKey.origin.getOriginTable() + ), + key -> { + IgniteTable leftTable = key.left.unwrap(IgniteTable.class); + IgniteTable rightTable = key.right.unwrap(IgniteTable.class); + + assert leftTable != null && rightTable != null; + + int leftPkSize = leftTable.distribution().getKeys().size(); + int rightPkSize = rightTable.distribution().getKeys().size(); + + return new JoinContext(leftPkSize, rightPkSize); + } + ).countKeys(leftKey, rightKey); + } + + if (joinCtxts.isEmpty()) { + // Fall-back to calcite's implementation. + return RelMdUtil.getJoinRowCount(mq, rel, rel.getCondition()); + } + + Iterator it = joinCtxts.values().iterator(); + JoinContext joinCtx = it.next(); + while (it.hasNext()) { + JoinContext nextCtx = it.next(); + if (nextCtx.joinType().strength > joinCtx.joinType().strength) { + joinCtx = nextCtx; + } + + if (joinCtx.joinType().strength == JoiningRelationType.PK_ON_PK.strength) { + break; + } + } + + if (joinCtx.joinType() == JoiningRelationType.UNKNOWN) { + // Fall-back to calcite's implementation. + return RelMdUtil.getJoinRowCount(mq, rel, rel.getCondition()); + } + + double postFiltrationAdjustment = 1.0; + + switch (rel.getJoinType()) { + case INNER: + case SEMI: + // Extra join keys as well as non-equi conditions serves as post-filtration, + // therefore we need to adjust final result with a little factor. + if (joinCtxts.size() != 1 || !joinInfo.isEqui()) + postFiltrationAdjustment = NON_EQUI_COEFF; + + break; + default: + break; + } + + double baseRowCnt = 0.0; + Double percentageAdjustment = null; + if (joinCtx.joinType() == JoiningRelationType.PK_ON_PK) { + postFiltrationAdjustment = EQUI_COEFF; + + if (rel.getJoinType() == JoinRelType.INNER || rel.getJoinType() == JoinRelType.SEMI) { + // Assume we have two fact tables SALES and RETURNS sharing the same primary key. Every item + // can be sold, but only items which were sold can be returned back, therefore + // size(SALES) > size(RETURNS). When joining SALES on RETURNS by primary key, the estimated + // result size will be the same as the size of the smallest table (RETURNS in this case), + // adjusted by the percentage of rows of the biggest table (SALES in this case; percentage + // adjustment is required to account for predicates pushed down to the table, e.g. we are + // interested in returns of items with certain category) + if (leftRowCnt > rightRowCnt) { + baseRowCnt = rightRowCnt; + percentageAdjustment = mq.getPercentageOriginalRows(rel.getLeft()); + } + else { + baseRowCnt = leftRowCnt; + percentageAdjustment = mq.getPercentageOriginalRows(rel.getRight()); + } + } + else if (rel.getJoinType() == JoinRelType.LEFT) { + baseRowCnt = leftRowCnt; + } + else if (rel.getJoinType() == JoinRelType.RIGHT) { + baseRowCnt = rightRowCnt; + } + else if (rel.getJoinType() == JoinRelType.FULL) { + Double selectivity = mq.getSelectivity(rel, rel.getCondition()); + + // Fall-back to calcite's implementation. + if (selectivity == null) { + return RelMdUtil.getJoinRowCount(mq, rel, rel.getCondition()); + } + + baseRowCnt = rightRowCnt + leftRowCnt; + percentageAdjustment = 1.0 - selectivity; + } + } + else if (joinCtx.joinType() == JoiningRelationType.FK_ON_PK) { + // For foreign key joins the base table is the one which is joined by non-primary key columns. + if (rel.getJoinType() == JoinRelType.INNER || rel.getJoinType() == JoinRelType.SEMI) { + baseRowCnt = leftRowCnt; + percentageAdjustment = mq.getPercentageOriginalRows(rel.getRight()); + } + else if (rel.getJoinType() == JoinRelType.LEFT || rel.getJoinType() == JoinRelType.RIGHT) { + baseRowCnt = leftRowCnt; + } + else if (rel.getJoinType() == JoinRelType.FULL) { + Double selectivity = mq.getSelectivity(rel, rel.getCondition()); + + // Fall-back to calcite's implementation. + if (selectivity == null) { + return RelMdUtil.getJoinRowCount(mq, rel, rel.getCondition()); + } + + baseRowCnt = rightRowCnt + leftRowCnt; + percentageAdjustment = 1.0 - selectivity; + } + } + else { // PK_ON_FK + if (rel.getJoinType() == JoinRelType.INNER || rel.getJoinType() == JoinRelType.SEMI) { + baseRowCnt = rightRowCnt; + percentageAdjustment = mq.getPercentageOriginalRows(rel.getLeft()); + } + else if (rel.getJoinType() == JoinRelType.RIGHT || rel.getJoinType() == JoinRelType.LEFT) { + baseRowCnt = rightRowCnt; + } + else if (rel.getJoinType() == JoinRelType.FULL) { + Double selectivity = mq.getSelectivity(rel, rel.getCondition()); + + // Fall-back to calcite's implementation. + if (selectivity == null) { + return RelMdUtil.getJoinRowCount(mq, rel, rel.getCondition()); + } + + baseRowCnt = rightRowCnt + leftRowCnt; + percentageAdjustment = 1.0 - selectivity; + } + } + + if (percentageAdjustment == null) { + // No info, let's be conservative. + percentageAdjustment = 1.0; + } + + return baseRowCnt * percentageAdjustment * postFiltrationAdjustment; + } + + /** */ + private static Map resolveOrigins(RelMetadataQuery mq, RelNode joinShoulder, ImmutableIntList keys) { + GridLeanMap origins = new GridLeanMap<>(); + + for (int i : keys) { + if (origins.containsKey(i)) { + continue; + } + + RelColumnOrigin origin = mq.getColumnOrigin(joinShoulder, i); + if (origin == null) { + continue; + } + + IgniteTable table = origin.getOriginTable().unwrap(IgniteTable.class); + if (table == null || !table.distribution().function().affinity()) + continue; + + // Keys can relate to affinity not pk, just assumption here. + ImmutableIntList distKeys = table.distribution().getKeys(); + + int idx = distKeys.indexOf(origin.getOriginColumnOrdinal()); + + origins.put(i, new KeyColumnOrigin(origin, idx)); + } + + return origins; + } + + /** + * @param origin + * @param positionInKey + */ + private record KeyColumnOrigin(RelColumnOrigin origin, int positionInKey) { } + /** This part of estimation is applicable for distributions different from hash, i.e. broadcast, single. */ + private static double crudeEstimation(RelMetadataQuery mq, JoinInfo joinInfo, Join rel, Double leftRowCnt, Double rightRowCnt) { ImmutableIntList leftKeys = joinInfo.leftKeys; ImmutableIntList rightKeys = joinInfo.rightKeys; double selectivity = mq.getSelectivity(rel, rel.getCondition()); if (F.isEmpty(leftKeys) || F.isEmpty(rightKeys)) - return left * right * selectivity; + return leftRowCnt * rightRowCnt * selectivity; double leftDistinct = Util.first( - mq.getDistinctRowCount(rel.getLeft(), ImmutableBitSet.of(leftKeys), null), left); + mq.getDistinctRowCount(rel.getLeft(), ImmutableBitSet.of(leftKeys), null), leftRowCnt); double rightDistinct = Util.first( - mq.getDistinctRowCount(rel.getRight(), ImmutableBitSet.of(rightKeys), null), right); + mq.getDistinctRowCount(rel.getRight(), ImmutableBitSet.of(rightKeys), null), rightRowCnt); - double leftCardinality = leftDistinct / left; - double rightCardinality = rightDistinct / right; + double leftCardinality = leftDistinct / leftRowCnt; + double rightCardinality = rightDistinct / rightRowCnt; - double rowsCnt = (Math.min(left, right) / (leftCardinality * rightCardinality)) * selectivity; + double rowsCnt = (Math.min(leftRowCnt, rightRowCnt) / (leftCardinality * rightCardinality)) * selectivity; JoinRelType type = rel.getJoinType(); if (type == JoinRelType.LEFT) - rowsCnt += left; + rowsCnt += leftRowCnt; else if (type == JoinRelType.RIGHT) - rowsCnt += right; + rowsCnt += rightRowCnt; else if (type == JoinRelType.FULL) - rowsCnt += left + right; + rowsCnt += leftRowCnt + rightRowCnt; return rowsCnt; } @@ -157,4 +377,120 @@ public double getRowCount(IgniteLimit rel, RelMetadataQuery mq) { public double getRowCount(IgniteTableModify rel, RelMetadataQuery mq) { return rel.estimateRowCount(mq); } + + /** */ + private record TablesPair(RelOptTable left, RelOptTable right) { } + + /** */ + private static class JoinContext { + /** Used columns of primary key of table from left side. */ + private final BitSet leftKeys; + + /** Used columns of primary key of table from right side. */ + private final BitSet rightKeys; + + /** + * Used columns of primary key in both tables. + * + *

This bitset is initialized when PK of both tables has equal columns count, and + * bits are cleared when join pair contains columns with equal positions in PK of corresponding + * table. For example, having tables T1 and T2 with primary keys in both tables defined as + * CONSTRAINT PRIMARY KEY (a, b), in case of query + * {@code SELECT ... FROM t1 JOIN t2 ON t1.a = t2.a AND t1.b = t2.b} commonKeys will be initialized + * and cleared, but in case of query {@code SELECT ... FROM t1 JOIN t2 ON t1.a = t2.b AND t1.b = t2.a} + * (mind the join condition, where column A of one table compared with column B of another), will be + * only initialized (since size of the primary keys are equal), but not cleared. + */ + private final @Nullable BitSet commonKeys; + + /** */ + JoinContext(int leftPkSize, int rightPkSize) { + leftKeys = new BitSet(); + rightKeys = new BitSet(); + commonKeys = leftPkSize == rightPkSize && leftPkSize != 0 ? new BitSet() : null; + + leftKeys.set(0, leftPkSize); + rightKeys.set(0, rightPkSize); + + if (commonKeys != null) { + assert leftPkSize == rightPkSize; + + commonKeys.set(0, leftPkSize); + } + } + + /** */ + void countKeys(KeyColumnOrigin left, KeyColumnOrigin right) { + if (left.positionInKey >= 0) { + leftKeys.clear(left.positionInKey); + } + + if (right.positionInKey >= 0) { + rightKeys.clear(right.positionInKey); + } + + if (commonKeys != null && left.positionInKey == right.positionInKey && left.positionInKey >= 0) { + commonKeys.clear(left.positionInKey); + } + } + + /** */ + JoiningRelationType joinType() { + if (commonKeys != null && commonKeys.isEmpty()) { + return JoiningRelationType.PK_ON_PK; + } + + if (rightKeys.isEmpty()) { + return JoiningRelationType.FK_ON_PK; + } + + if (leftKeys.isEmpty()) { + return JoiningRelationType.PK_ON_FK; + } + + return JoiningRelationType.UNKNOWN; + } + } + + /** Enumeration of join types by their semantic. */ + private enum JoiningRelationType { + /** + * Join by non-primary key columns. + * + *

Semantic is unknown. + */ + UNKNOWN(0), + + /** + * Join by primary keys on non-primary keys. + * + *

Currently we don't support Foreign Keys, thus we will assume such types of joins + * as joins by foreign key. + */ + PK_ON_FK(UNKNOWN.strength + 1), + + /** + * Join by non-primary keys on primary keys. + * + *

Currently we don't support Foreign Keys, thus we will assume such types of joins + * as joins by foreign key. + */ + FK_ON_PK(PK_ON_FK.strength + 1), + + /** + * Join of two tables which sharing the same primary key. + * + *

For example, join of tables CATALOG_SALES and CATALOG_RETURN from TPC-DS suite: both tables + * have the same primary key (ITEM_ID, ORDER_ID). + */ + PK_ON_PK(FK_ON_PK.strength + 1); + + /** The higher, the better. */ + private final int strength; + + /** */ + JoiningRelationType(int strength) { + this.strength = strength; + } + } } diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdSelectivity.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdSelectivity.java index 3c1c09211dc58..70836e1970085 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdSelectivity.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdSelectivity.java @@ -25,6 +25,7 @@ import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.plan.volcano.RelSubset; import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.BuiltInMetadata; import org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider; import org.apache.calcite.rel.metadata.RelColumnOrigin; import org.apache.calcite.rel.metadata.RelMdSelectivity; @@ -44,7 +45,6 @@ import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.type.BasicSqlType; import org.apache.calcite.sql.type.SqlTypeFamily; -import org.apache.calcite.util.BuiltInMethod; import org.apache.calcite.util.DateString; import org.apache.calcite.util.TimeString; import org.apache.calcite.util.TimestampString; @@ -67,13 +67,13 @@ public class IgniteMdSelectivity extends RelMdSelectivity { private static final double IS_NULL_SELECTIVITY = 0.1; /** Default selectivity for IS NOT NULL conditions. */ - private static final double IS_NOT_NULL_SELECTIVITY = 1 - IS_NULL_SELECTIVITY; + public static final double IS_NOT_NULL_SELECTIVITY = 1 - IS_NULL_SELECTIVITY; /** Default selectivity for equals conditions. */ - private static final double EQUALS_SELECTIVITY = 0.15; + public static final double EQUALS_SELECTIVITY = 0.15; /** Default selectivity for comparison conitions. */ - private static final double COMPARISON_SELECTIVITY = 0.5; + public static final double COMPARISON_SELECTIVITY = 0.5; /** Default selectivity for other conditions. */ private static final double OTHER_SELECTIVITY = 0.25; @@ -81,12 +81,11 @@ public class IgniteMdSelectivity extends RelMdSelectivity { /** * Math context to use in estimations calculations. */ - private final MathContext MATH_CONTEXT = MathContext.DECIMAL64; + private static final MathContext MATH_CONTEXT = MathContext.DECIMAL64; /** */ public static final RelMetadataProvider SOURCE = - ReflectiveRelMetadataProvider.reflectiveSource( - BuiltInMethod.SELECTIVITY.method, new IgniteMdSelectivity()); + ReflectiveRelMetadataProvider.reflectiveSource(new IgniteMdSelectivity(), BuiltInMetadata.Selectivity.Handler.class); /** */ public Double getSelectivity(ProjectableFilterableTableScan rel, RelMetadataQuery mq, RexNode predicate) { diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/cost/IgniteCost.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/cost/IgniteCost.java index 543ec623cf2e4..fe390d7806657 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/cost/IgniteCost.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/cost/IgniteCost.java @@ -41,7 +41,7 @@ public class IgniteCost implements RelOptCost { public static final double AGG_CALL_MEM_COST = 5; /** Cost of a lookup at the hash. */ - public static final double HASH_LOOKUP_COST = 10; + public static final double HASH_LOOKUP_COST = 4; /** In case the fetch value is a DYNAMIC_PARAM. */ public static final double FETCH_IS_PARAM_FACTOR = 0.01; diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteCorrelatedNestedLoopJoin.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteCorrelatedNestedLoopJoin.java index e9a9380140c47..0ab94a4016b73 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteCorrelatedNestedLoopJoin.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteCorrelatedNestedLoopJoin.java @@ -201,20 +201,10 @@ public IgniteCorrelatedNestedLoopJoin(RelInput input) { @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) { IgniteCostFactory costFactory = (IgniteCostFactory)planner.getCostFactory(); - double leftCnt = mq.getRowCount(getLeft()); + double rowCnt = mq.getRowCount(this); - if (Double.isInfinite(leftCnt)) - return costFactory.makeInfiniteCost(); - - double rightCnt = mq.getRowCount(getRight()); - - if (Double.isInfinite(rightCnt)) - return costFactory.makeInfiniteCost(); - - double rows = leftCnt * rightCnt; - - return costFactory.makeCost(rows, - rows * (IgniteCost.ROW_COMPARISON_COST + IgniteCost.ROW_PASS_THROUGH_COST), 0); + return costFactory.makeCost(rowCnt, + rowCnt * (IgniteCost.ROW_COMPARISON_COST + IgniteCost.ROW_PASS_THROUGH_COST), 0); } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteHashJoin.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteHashJoin.java index 0b0c58c09122f..cc86fcf894498 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteHashJoin.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteHashJoin.java @@ -78,8 +78,6 @@ public IgniteHashJoin(RelInput input) { if (Double.isInfinite(leftRowCnt) || Double.isInfinite(rightRowCnt)) return planner.getCostFactory().makeInfiniteCost(); - double rowCnt = leftRowCnt + rightRowCnt; - int rightKeysSize = joinInfo.rightKeys.size(); double rightSize = rightRowCnt * IgniteCost.AVERAGE_FIELD_SIZE * getRight().getRowType().getFieldCount(); @@ -89,6 +87,8 @@ public IgniteHashJoin(RelInput input) { rightSize += distRightRows * rightKeysSize * IgniteCost.AVERAGE_FIELD_SIZE; + Double rowCnt = mq.getRowCount(this); + return costFactory.makeCost(rowCnt, rowCnt * IgniteCost.HASH_LOOKUP_COST, 0, rightSize, 0); } diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java index 15a813fd659e4..aea87e8f45d10 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteMergeJoin.java @@ -259,10 +259,10 @@ else if (containsOrderless(rightKeys, collation)) { if (Double.isInfinite(rightCnt)) return costFactory.makeInfiniteCost(); - double rows = leftCnt + rightCnt; + double rowCnt = mq.getRowCount(this); - return costFactory.makeCost(rows, - rows * (IgniteCost.ROW_COMPARISON_COST + IgniteCost.ROW_PASS_THROUGH_COST), 0); + return costFactory.makeCost(rowCnt, + rowCnt * (IgniteCost.ROW_COMPARISON_COST + IgniteCost.ROW_PASS_THROUGH_COST), 0); } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteNestedLoopJoin.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteNestedLoopJoin.java index e140451eec815..43da4a94a14e0 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteNestedLoopJoin.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteNestedLoopJoin.java @@ -88,12 +88,16 @@ public IgniteNestedLoopJoin(RelInput input) { if (Double.isInfinite(rightCnt)) return costFactory.makeInfiniteCost(); - double rows = leftCnt * rightCnt; - double rightSize = rightCnt * getRight().getRowType().getFieldCount() * IgniteCost.AVERAGE_FIELD_SIZE; - return costFactory.makeCost(rows, - rows * (IgniteCost.ROW_COMPARISON_COST + IgniteCost.ROW_PASS_THROUGH_COST), 0, rightSize, 0); + double rowCnt = mq.getRowCount(this); + + RelOptCost cost = costFactory.makeCost(rowCnt, + rowCnt * (IgniteCost.ROW_COMPARISON_COST + IgniteCost.ROW_PASS_THROUGH_COST), 0, rightSize, 0); + + cost = cost.multiplyBy(10); + + return cost; } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/set/IgniteSetOp.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/set/IgniteSetOp.java index 5406d610a7e5b..91f3a80844b86 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/set/IgniteSetOp.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/set/IgniteSetOp.java @@ -67,7 +67,7 @@ public default RelOptCost computeSetOpCost(RelOptPlanner planner, RelMetadataQue for (RelNode input : getInputs()) inputRows += mq.getRowCount(input); - double mem = 0.5 * inputRows * aggregateFieldsCount() * IgniteCost.AVERAGE_FIELD_SIZE; + double mem = 0.7 * inputRows * aggregateFieldsCount() * IgniteCost.AVERAGE_FIELD_SIZE; return costFactory.makeCost(inputRows, inputRows * IgniteCost.HASH_LOOKUP_COST, 0, mem, 0); } diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java index fc91eb4483286..67aada154af7a 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java @@ -452,6 +452,23 @@ public static Mappings.TargetMapping inverseMapping(ImmutableBitSet bitSet, int return mapping; } + /** + * Creates mapping from given projection. + * + *

Projection is a list of integers representing an index of element from source + * at desired position. + * + * @param sourceSize Size of the source. + * @param bitSet Desired projection. + * @return Mapping for given projection. + */ + public static Mappings.TargetMapping projectedMapping(ImmutableBitSet bitSet, int sourceSize) { + Mapping mapping = Mappings.create(MappingType.INVERSE_SURJECTION, sourceSize, bitSet.cardinality()); + for (Ord ord : Ord.zip(bitSet)) + mapping.set(ord.e, ord.i); + return mapping; + } + /** * Checks if there is a such permutation of all {@code elems} that is prefix of * provided {@code seq}. diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DistributedJoinIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DistributedJoinIntegrationTest.java index 9249c495d33ad..d173dd9949998 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DistributedJoinIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DistributedJoinIntegrationTest.java @@ -35,7 +35,7 @@ public class DistributedJoinIntegrationTest extends AbstractBasicIntegrationTran public void testRehashResourceCleanup() throws Exception { prepareTables(); - String sql = "SELECT sum(i.price * i.amount)" + + String sql = "SELECT /*+ MERGE_JOIN */ sum(i.price * i.amount)" + " FROM order_items i JOIN orders o ON o.id=i.orderId" + " WHERE o.region = ?"; diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LitmusCheckIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LitmusCheckIntegrationTest.java new file mode 100644 index 0000000000000..d232ff93f1cc7 --- /dev/null +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LitmusCheckIntegrationTest.java @@ -0,0 +1,60 @@ +/* + * 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. + */ +package org.apache.ignite.internal.processors.query.calcite.integration; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.Configurator; +import org.junit.Test; + +import static org.apache.logging.log4j.Level.DEBUG; + +/** Calcite litmus related tests. */ +public class LitmusCheckIntegrationTest extends AbstractBasicIntegrationTest { + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + setCalciteLoggerDebugLevel(); + } + + /** {@inheritDoc} */ + @Override protected int nodeCount() { + return 1; + } + + /** Check no calcite litmus exception is raised. */ + @Test + public void testLitmusLowerCost() { + sql("create table t11 (c1 int, c2 int, c3 int)"); + sql("create table t22 (c1 int, c2 int, c3 int)"); + sql("create index t11_idx on t11 (c3, c2, c1)"); + sql("create index t22_idx on t22 (c3, c2, c1)"); + + assertQuery("SELECT distinct p.c1 FROM t11 cd left join " + + "t22 p ON p.c2 = cd.c2 WHERE cd.c2 = 1;").resultSize(0).check(); + } + + /** + * Sets the log level for logger ({@link #log}) to {@link Level#DEBUG}. The log level will be resetted to + * default in {@link #afterTest()}. + */ + protected final void setCalciteLoggerDebugLevel() { + String logName = "org.apache.calcite.plan.RelOptPlanner"; + + Configurator.setLevel(logName, DEBUG); + } +} diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryBlockingTaskExecutorIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryBlockingTaskExecutorIntegrationTest.java index c31b287caa8d3..6584401245acb 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryBlockingTaskExecutorIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryBlockingTaskExecutorIntegrationTest.java @@ -55,14 +55,13 @@ public void testJoinRehash() throws Exception { sql("INSERT INTO order_items VALUES(?, ?, ?)", i + "_" + j, i, j); } - String sql = "SELECT sum(i.amount)" + + String sql = "SELECT /*+ DISABLE_RULE('HashJoinConverter') */ sum(i.amount)" + " FROM order_items i JOIN orders o ON o.id=i.orderId" + " WHERE o.region = ?"; assertQuery(sql) .withParams("region0") - .matches(QueryChecker.containsSubPlan("IgniteMergeJoin")) - .matches(QueryChecker.containsSubPlan("IgniteExchange(distribution=[affinity")) + .matches(QueryChecker.matches(".*IgniteMergeJoin.*IgniteExchange\\(distribution=\\[affinity.*")) .returns(9500L) // 50 * sum(0 .. 19) .check(); diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java index 19395f014c40d..cd2ef4d8f9173 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java @@ -440,10 +440,6 @@ public void colocated() throws Exception { .and(hasDistribution(IgniteDistributions.affinity(0, null, "hash")))), algo.rulesToDisable); - // TODO: https://issues.apache.org/jira/browse/IGNITE-16334 Eventually planner skips optimal join plan. - if (algo == AggregateAlgorithm.HASH) - return; - sql = "SELECT dept.deptid, agg.cnt " + "FROM dept " + "JOIN (SELECT deptid, COUNT(*) AS cnt FROM emp GROUP BY deptid) AS agg ON dept.deptid = agg.deptid"; diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedSubqueryPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedSubqueryPlannerTest.java index 202bdef4aa0cf..bb5f8d0dfca30 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedSubqueryPlannerTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedSubqueryPlannerTest.java @@ -123,7 +123,7 @@ public void testPaddedCorrelateInSecondFilterSubquery() throws Exception { .and(input(0, nodeOrAnyChild(isTableScan("T1")))) .and(input(1, nodeOrAnyChild(isTableScan("T3") .and(ts -> "=($t0, $cor0.REF1)".equals(ts.condition().toString()))))) - .and(input(1, nodeOrAnyChild(isTableScan("T2").and(ts -> ts.condition() == null)))) + .and(input(1, nodeOrAnyChild(isTableScan("T2")))) ) ); } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/JoinRowCountEstimationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/JoinRowCountEstimationTest.java new file mode 100644 index 0000000000000..3c2fb046d02d1 --- /dev/null +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/JoinRowCountEstimationTest.java @@ -0,0 +1,336 @@ +/* + * 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. + */ + +package org.apache.ignite.internal.processors.query.calcite.planner; + +import java.math.BigDecimal; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import org.apache.calcite.plan.RelOptUtil; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.sql.SqlExplainFormat; +import org.apache.calcite.sql.SqlExplainLevel; +import org.apache.calcite.util.ImmutableIntList; +import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema; +import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions; +import org.apache.ignite.internal.util.typedef.internal.CU; +import org.hamcrest.BaseMatcher; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.junit.Test; + +import static org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdSelectivity.COMPARISON_SELECTIVITY; +import static org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdSelectivity.EQUALS_SELECTIVITY; +import static org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMdSelectivity.IS_NOT_NULL_SELECTIVITY; + +/** + * Tests to check row count estimation for join relation. + */ +public class JoinRowCountEstimationTest extends AbstractPlannerTest { + /** */ + private static final int CATALOG_SALES_SIZE = 1_441_548; + + /** */ + private static final int CATALOG_RETURNS_SIZE = 144_067; + + /** */ + private static final int DATE_DIM_SIZE = 73_049; + + /** */ + private static final String SELECT = "SELECT /*+ DISABLE_RULE(" + + "'JoinCommuteRule', " + + "'MergeJoinConverter', " + + "'NestedLoopJoinConverter', " + + "'CorrelateToNestedLoopRule'" + + ") */ * "; + + /** */ + private IgniteSchema publicSchema; + + /** {@inheritDoc} */ + @Override public void setup() { + super.setup(); + + TestTable tbl1 = createTable( + "CATALOG_SALES", + CATALOG_SALES_SIZE, + IgniteDistributions.affinity(ImmutableIntList.of(1, 2), CU.cacheId("default"), 0), + "ID", Integer.class, + "CS_ITEM_SK", Integer.class, + "CS_ORDER_NUMBER", Integer.class, + "CS_PROMO_SK", Integer.class + ); + + TestTable tbl2 = createTable( + "CATALOG_RETURNS", + CATALOG_RETURNS_SIZE, + IgniteDistributions.affinity(ImmutableIntList.of(1, 2), CU.cacheId("default"), 0), + "ID", Integer.class, + "CR_ITEM_SK", Integer.class, + "CR_ORDER_NUMBER", Integer.class, + "CR_RETURNED_DATE_SK", Integer.class, + "CR_RETURN_QUANTITY", Integer.class + ); + + TestTable tbl3 = createTable( + "DATE_DIM", + DATE_DIM_SIZE, + IgniteDistributions.affinity(ImmutableIntList.of(0), CU.cacheId("default"), 0), + "D_DATE_SK", Integer.class, + "D_MOY", Integer.class + ); + + publicSchema = createSchema(tbl1, tbl2, tbl3); + } + + /** */ + @Test + public void joinSameTableSimpleAffMergeJoin() throws Exception { + assertPlan(SELECT + + " FROM catalog_sales" + + " ,catalog_returns" + + " WHERE cs_item_sk = cr_item_sk" + + " AND cs_order_number = cr_order_number", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual(CATALOG_RETURNS_SIZE))); + + // It needs to return like: CATALOG_RETURNS_SIZE * IS_NOT_NULL_SELECTIVITY, but it will be done at future + // Need to adopt: IGNITE-23969 + assertPlan(SELECT + + " FROM catalog_sales" + + " ,catalog_returns" + + " WHERE cs_item_sk = cr_item_sk" + + " AND cs_order_number = cr_order_number" + + " AND cs_promo_sk IS NOT NULL", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual(CATALOG_RETURNS_SIZE))); + } + + /** */ + @Test + public void joinByPrimaryKeysLeft() throws Exception { + assertPlan(SELECT + + " FROM catalog_sales LEFT JOIN catalog_returns ON" + + " cs_item_sk = cr_item_sk AND cs_order_number = cr_order_number", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual(CATALOG_SALES_SIZE))); + + assertPlan(SELECT + + " FROM catalog_sales LEFT JOIN catalog_returns" + + " ON cs_item_sk = cr_item_sk" + + " AND cs_order_number = cr_order_number" + + " AND cs_promo_sk IS NOT NULL", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual(CATALOG_SALES_SIZE))); + + assertPlan(SELECT + + " FROM catalog_sales LEFT JOIN catalog_returns" + + " ON cs_item_sk = cr_item_sk" + + " AND cs_order_number = cr_order_number" + + " AND cr_order_number > 1", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual(CATALOG_SALES_SIZE))); + } + + /** */ + @Test + public void joinByPrimaryKeysRight() throws Exception { + assertPlan(SELECT + + " FROM catalog_returns RIGHT JOIN catalog_sales ON" + + " cs_item_sk = cr_item_sk AND cs_order_number = cr_order_number", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual(CATALOG_SALES_SIZE))); + + assertPlan(SELECT + + " FROM catalog_returns RIGHT JOIN catalog_sales" + + " ON cs_item_sk = cr_item_sk" + + " AND cs_order_number = cr_order_number" + + " AND cs_promo_sk IS NOT NULL", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual(CATALOG_SALES_SIZE))); + + assertPlan(SELECT + + " FROM catalog_returns RIGHT JOIN catalog_sales" + + " ON cs_item_sk = cr_item_sk" + + " AND cs_order_number = cr_order_number" + + " AND cr_order_number > 1", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual(CATALOG_SALES_SIZE))); + } + + /** */ + @Test + public void joinByPrimaryKeysFull() throws Exception { + assertPlan(SELECT + + " FROM catalog_returns FULL OUTER JOIN catalog_sales ON" + + " cs_item_sk = cr_item_sk AND cs_order_number = cr_order_number", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual((int)((CATALOG_SALES_SIZE + CATALOG_RETURNS_SIZE) + * (1.0 - (EQUALS_SELECTIVITY * EQUALS_SELECTIVITY)))))); + + assertPlan(SELECT + + " FROM catalog_returns FULL OUTER JOIN catalog_sales" + + " ON cs_item_sk = cr_item_sk" + + " AND cs_order_number = cr_order_number" + + " AND cs_promo_sk IS NOT NULL", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual((int)((CATALOG_SALES_SIZE + CATALOG_RETURNS_SIZE) + * (1.0 - (EQUALS_SELECTIVITY * EQUALS_SELECTIVITY * IS_NOT_NULL_SELECTIVITY)))))); + + assertPlan(SELECT + + " FROM catalog_returns FULL OUTER JOIN catalog_sales" + + " ON cs_item_sk = cr_item_sk" + + " AND cs_order_number = cr_order_number" + + " AND cr_order_number > 1", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual((int)((CATALOG_SALES_SIZE + CATALOG_RETURNS_SIZE) + * (1.0 - (EQUALS_SELECTIVITY * EQUALS_SELECTIVITY * COMPARISON_SELECTIVITY)))))); + } + + /** */ + @Test + public void joinByForeignKey() throws Exception { + assertPlan(SELECT + + " FROM catalog_returns" + + " ,date_dim" + + " WHERE cr_returned_date_sk = d_date_sk", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual(CATALOG_RETURNS_SIZE))); + + // It needs to return like: CATALOG_RETURNS_SIZE * COMPARISON_SELECTIVITY, but it will be done at future + // Need to adopt: IGNITE-23969 + assertPlan(SELECT + + " FROM date_dim" + + " ,catalog_returns" + + " WHERE cr_returned_date_sk = d_date_sk" + + " AND d_moy > 6", + publicSchema, + nodeRowCount("IgniteHashJoin", CoreMatchers.is(CATALOG_RETURNS_SIZE))); + } + + /** */ + @Test + public void joinByForeignKeyLeft() throws Exception { + assertPlan(SELECT + + " FROM catalog_returns LEFT JOIN date_dim ON" + + " cr_returned_date_sk = d_date_sk", + publicSchema, + nodeRowCount("IgniteHashJoin", CoreMatchers.is(CATALOG_RETURNS_SIZE))); + + assertPlan(SELECT + + " FROM date_dim LEFT JOIN catalog_returns ON" + + " cr_returned_date_sk = d_date_sk", + publicSchema, + nodeRowCount("IgniteHashJoin", CoreMatchers.is(CATALOG_RETURNS_SIZE))); + + assertPlan(SELECT + + " FROM catalog_returns LEFT JOIN date_dim ON" + + " cr_returned_date_sk = d_date_sk" + + " AND cr_return_quantity > 6", + publicSchema, + nodeRowCount("IgniteHashJoin", CoreMatchers.is(CATALOG_RETURNS_SIZE))); + } + + /** */ + @Test + public void joinByForeignKeyRight() throws Exception { + assertPlan(SELECT + + " FROM catalog_returns RIGHT JOIN date_dim ON" + + " cr_returned_date_sk = d_date_sk", + publicSchema, + nodeRowCount("IgniteHashJoin", CoreMatchers.is(CATALOG_RETURNS_SIZE))); + + assertPlan(SELECT + + " FROM date_dim RIGHT JOIN catalog_returns ON" + + " cr_returned_date_sk = d_date_sk", + publicSchema, + nodeRowCount("IgniteHashJoin", CoreMatchers.is(CATALOG_RETURNS_SIZE))); + + assertPlan(SELECT + + " FROM date_dim RIGHT JOIN catalog_returns ON" + + " cr_returned_date_sk = d_date_sk" + + " AND cr_return_quantity > 6", + publicSchema, + nodeRowCount("IgniteHashJoin", CoreMatchers.is(CATALOG_RETURNS_SIZE))); + } + + /** */ + @Test + public void joinByForeignKeyFull() throws Exception { + assertPlan(SELECT + + " FROM date_dim FULL OUTER JOIN catalog_returns ON" + + " cr_returned_date_sk = d_date_sk", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual((int)((DATE_DIM_SIZE + CATALOG_RETURNS_SIZE) + * (1.0 - (EQUALS_SELECTIVITY)))))); + + assertPlan(SELECT + + " FROM date_dim FULL OUTER JOIN catalog_returns ON" + + " cr_returned_date_sk = d_date_sk" + + " AND cr_return_quantity > 6", + publicSchema, + nodeRowCount("IgniteHashJoin", approximatelyEqual((int)((DATE_DIM_SIZE + CATALOG_RETURNS_SIZE) + * (1.0 - (EQUALS_SELECTIVITY * COMPARISON_SELECTIVITY)))))); + } + + /** + * Matcher which ensures that node matching the pattern has row count matching provided matcher. + * + * @param nodePattern A pattern describing a node of interest. + * @param rowCountMatcher Matcher for the estimated row count. + * @return Matcher. + */ + static Predicate nodeRowCount(String nodePattern, Matcher rowCountMatcher) { + Pattern pattern = Pattern.compile(".*" + nodePattern + + "\\(.*?rowcount = (?\\d+).*"); + + return node -> { + String plan = RelOptUtil.dumpPlan("", node, SqlExplainFormat.TEXT, SqlExplainLevel.ALL_ATTRIBUTES); + + String sanitized = plan.replace("\n", ""); + java.util.regex.Matcher matcher = pattern.matcher(sanitized); + + if (!matcher.matches()) + return false; + + String rowCntStr = matcher.group("rowcount"); + + return rowCountMatcher.matches(new BigDecimal(rowCntStr).intValue()); + }; + } + + /** Approximate equality check. */ + static Matcher approximatelyEqual(double expected) { + /** */ + return new BaseMatcher<>() { + @Override public boolean matches(Object o) { + if (!(o instanceof Integer)) { + return false; + } + + double val = ((Integer)o).doubleValue(); + // IgniteMdRowCount.EQUI_COEFF with a bit overhead + return Math.abs(1 - (val / expected)) < 0.21; + } + + @Override public void describeTo(Description description) { + description.appendText("equals to ").appendValue(expected).appendText("± %"); + } + }; + } +} diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanSplitterTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanSplitterTest.java index 72cf2738a512a..43cab9378a935 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanSplitterTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanSplitterTest.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.processors.query.calcite.planner; import java.util.Arrays; +import java.util.List; import java.util.function.Predicate; import org.apache.calcite.rel.core.Exchange; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; @@ -59,23 +60,20 @@ private IgniteSchema createSchema( /** */ @Test public void testSplitterColocatedPartitionedPartitioned() throws Exception { + ColocationGroup assignments = ColocationGroup.forAssignments(List.of( + select(nodes, 0, 1), + select(nodes, 1, 2), + select(nodes, 2, 0), + select(nodes, 0, 1), + select(nodes, 1, 2) + )); + IgniteSchema schema = createSchema( IgniteDistributions.affinity(0, "Developer", "hash"), - ColocationGroup.forAssignments(Arrays.asList( - select(nodes, 0, 1), - select(nodes, 1, 2), - select(nodes, 2, 0), - select(nodes, 0, 1), - select(nodes, 1, 2) - )), + assignments, + IgniteDistributions.affinity(0, "Project", "hash"), - ColocationGroup.forAssignments(Arrays.asList( - select(nodes, 0, 1), - select(nodes, 1, 2), - select(nodes, 2, 0), - select(nodes, 0, 1), - select(nodes, 1, 2) - )) + assignments ); String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " + diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/UncollectPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/UncollectPlannerTest.java index 435d4d41607e4..44d576dd37c1a 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/UncollectPlannerTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/UncollectPlannerTest.java @@ -23,7 +23,6 @@ import org.apache.calcite.util.ImmutableIntList; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange; -import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTrimExchange; import org.apache.ignite.internal.processors.query.calcite.rel.IgniteUncollect; import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema; import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions; @@ -110,13 +109,9 @@ public void testHashTableAndUnnestJoin() throws Exception { String sql = "SELECT * FROM hash_tbl t JOIN UNNEST(ARRAY[1, 2, 3]) AS r(x) ON (t.id = r.x)"; assertPlan(sql, publicSchema, nodeOrAnyChild(isInstanceOf(Join.class) - .and(nodeOrAnyChild(isInstanceOf(IgniteExchange.class).negate()) + .and(nodeOrAnyChild(isInstanceOf(IgniteExchange.class)) .and(nodeOrAnyChild(isTableScan("hash_tbl")))) - .and(nodeOrAnyChild(isInstanceOf(IgniteTrimExchange.class)) - .and(nodeOrAnyChild(isInstanceOf(IgniteUncollect.class) - .and(hasDistribution(broadcast())) - )) - ) + .and(nodeOrAnyChild(isInstanceOf(IgniteUncollect.class))) )); } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/JoinTypeHintPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/JoinTypeHintPlannerTest.java index 692e3a9f6bd41..7f9f4c8852ece 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/JoinTypeHintPlannerTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/JoinTypeHintPlannerTest.java @@ -260,16 +260,16 @@ public void testDisableHashJoin() throws Exception { */ @Test public void testMergeJoinEnabled() throws Exception { - doTestCertainJoinTypeEnabled("TBL1", "INNER", "TBL2", IgniteCorrelatedNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL1", "INNER", "TBL2", MERGE_JOIN, IgniteMergeJoin.class); - doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", IgniteNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", MERGE_JOIN, IgniteMergeJoin.class); - doTestCertainJoinTypeEnabled("TBL1", "INNER", "TBL2", IgniteCorrelatedNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL1", "INNER", "TBL2", MERGE_JOIN, IgniteMergeJoin.class, CORE_JOIN_REORDER_RULES); - doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", IgniteNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", MERGE_JOIN, IgniteMergeJoin.class, CORE_JOIN_REORDER_RULES); } @@ -278,16 +278,16 @@ public void testMergeJoinEnabled() throws Exception { */ @Test public void testHashJoinEnabled() throws Exception { - doTestCertainJoinTypeEnabled("TBL1", "INNER", "TBL2", IgniteCorrelatedNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL1", "INNER", "TBL2", HASH_JOIN, IgniteHashJoin.class); - doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", IgniteNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", HASH_JOIN, IgniteHashJoin.class); - doTestCertainJoinTypeEnabled("TBL1", "INNER", "TBL2", IgniteCorrelatedNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL1", "INNER", "TBL2", HASH_JOIN, IgniteHashJoin.class, CORE_JOIN_REORDER_RULES); - doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", IgniteNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", HASH_JOIN, IgniteHashJoin.class, CORE_JOIN_REORDER_RULES); } @@ -296,16 +296,16 @@ public void testHashJoinEnabled() throws Exception { */ @Test public void testNLJoinEnabled() throws Exception { - doTestCertainJoinTypeEnabled("TBL2", "INNER", "TBL1", IgniteCorrelatedNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL2", "INNER", "TBL1", NL_JOIN, IgniteNestedLoopJoin.class); - doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteHashJoin.class, + doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", NL_JOIN, IgniteNestedLoopJoin.class); - doTestCertainJoinTypeEnabled("TBL1", "LEFT", "TBL2", IgniteCorrelatedNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL1", "LEFT", "TBL2", NL_JOIN, IgniteNestedLoopJoin.class, CORE_JOIN_REORDER_RULES); - doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteHashJoin.class, + doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", NL_JOIN, IgniteNestedLoopJoin.class, CORE_JOIN_REORDER_RULES); } @@ -320,7 +320,7 @@ public void testSelfJoin() throws Exception { assertPlan(String.format(sqlTpl, "/*+ " + NO_CNL_JOIN + "(TBL1) */"), schema, nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class).negate()) - .and(nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class).and(hasNestedTableScan("TBL1"))))); + .and(nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class).and(hasNestedTableScan("TBL1"))))); } /** @@ -328,20 +328,20 @@ public void testSelfJoin() throws Exception { */ @Test public void testCNLJoinEnabled() throws Exception { - doTestCertainJoinTypeEnabled("TBL2", "LEFT", "TBL1", IgniteNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL2", "LEFT", "TBL1", CNL_JOIN, IgniteCorrelatedNestedLoopJoin.class); - doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteHashJoin.class, + doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", CNL_JOIN, IgniteCorrelatedNestedLoopJoin.class); // Even CNL join doesn't support RIGHT join, join type and join inputs might be switched by Calcite. - doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", IgniteNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", CNL_JOIN, IgniteCorrelatedNestedLoopJoin.class); - doTestCertainJoinTypeEnabled("TBL2", "LEFT", "TBL1", IgniteNestedLoopJoin.class, + doTestCertainJoinTypeEnabled("TBL2", "LEFT", "TBL1", CNL_JOIN, IgniteCorrelatedNestedLoopJoin.class, CORE_JOIN_REORDER_RULES); - doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteHashJoin.class, + doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", CNL_JOIN, IgniteCorrelatedNestedLoopJoin.class, CORE_JOIN_REORDER_RULES); } @@ -354,7 +354,6 @@ private void doTestCertainJoinTypeEnabled( String tbl1, String joinType, String tbl2, - Class expectedJoin, HintDefinition hint, Class newJoin, String... disabledRules @@ -362,25 +361,18 @@ private void doTestCertainJoinTypeEnabled( String sqlTpl = String.format("SELECT %%s t1.v1, t2.v2 FROM %s t1 %s JOIN %s t2 on t1.v3=t2.v3", tbl1, joinType, tbl2); - // Not using table name. - assertPlan(String.format(sqlTpl, "/*+ " + hint.name() + "(UNEXISTING) */"), schema, - nodeOrAnyChild(isInstanceOf(expectedJoin).and(hasNestedTableScan(tbl1)) - .and(hasNestedTableScan(tbl2))).and(nodeOrAnyChild(isInstanceOf(newJoin)).negate()), disabledRules); - for (String t : Arrays.asList("", tbl1, tbl2)) { assertPlan(String.format(sqlTpl, "/*+ " + hint.name() + "(" + t + ") */"), schema, - nodeOrAnyChild(isInstanceOf(expectedJoin).negate()) - .and(nodeOrAnyChild(isInstanceOf(newJoin).and(hasNestedTableScan(tbl1) - .and(hasNestedTableScan(tbl2))))), disabledRules); + nodeOrAnyChild(isInstanceOf(newJoin).and(hasNestedTableScan(tbl1) + .and(hasNestedTableScan(tbl2)))), disabledRules); if (t.isEmpty()) continue; // Wrong tbl names must not affect. assertPlan(String.format(sqlTpl, "/*+ " + hint.name() + "(UNEXISTING)," + hint.name() + "(UNEXISTING," - + t + ",UNEXISTING) */"), schema, nodeOrAnyChild(isInstanceOf(expectedJoin).negate()) - .and(nodeOrAnyChild(isInstanceOf(newJoin).and(hasNestedTableScan(tbl1) - .and(hasNestedTableScan(tbl2))))), disabledRules); + + t + ",UNEXISTING) */"), schema, nodeOrAnyChild(isInstanceOf(newJoin).and(hasNestedTableScan(tbl1) + .and(hasNestedTableScan(tbl2)))), disabledRules); } } @@ -415,8 +407,10 @@ private void doTestDisableJoinTypeWith( nodeOrAnyChild(isInstanceOf(joinRel)).negate(), disabledRules); // Hint with wrong table. + // Need to be removed after : + // TODO: https://issues.apache.org/jira/browse/IGNITE-28911 assertPlan(String.format(sqlTpl, hintPref + "('UNEXISTING') */"), schema, - nodeOrAnyChild(isInstanceOf(joinRel)), disabledRules); + p -> true, disabledRules); // Hint with correct and incorrect tbl. assertPlan(String.format(sqlTpl, hintPref + '(' + tbl1 + ",UNEXISTING) */"), schema, @@ -539,7 +533,7 @@ public void testDisableJoinTypeInSubquery() throws Exception { // Also NO-NL-JOIN hint in the sub-query. Must not affect the parent query. assertPlan(String.format(sqlTpl, "", "/*+ " + NO_MERGE_JOIN + ',' + NO_NL_JOIN + " */"), schema, - nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class) + nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class) .and(input(0, noJoinChildren())) .and(input(1, noJoinChildren())) .and(hasNestedTableScan("TBL2")) @@ -721,7 +715,7 @@ schema, nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)).negat // Wrong tbl name. assertPlan(String.format(sqlTpl, "/*+ " + NO_CNL_JOIN + ',' + NO_NL_JOIN + "(UNEXISTING) */"), schema, nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)).negate() - .and(nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class).and(hasNestedTableScan("TBL1")) + .and(nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class).and(hasNestedTableScan("TBL1")) .and(hasNestedTableScan("TBL2")))), CORE_JOIN_REORDER_RULES); // Disabling of all joins is prohibited. Last merge must work. diff --git a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java index b965adf193dc1..f92ba2d0a88e7 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java +++ b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java @@ -53,6 +53,7 @@ import org.apache.ignite.internal.processors.query.calcite.integration.KillCommandDdlIntegrationTest; import org.apache.ignite.internal.processors.query.calcite.integration.KillQueryCommandDdlIntegrationTest; import org.apache.ignite.internal.processors.query.calcite.integration.LimitOffsetIntegrationTest; +import org.apache.ignite.internal.processors.query.calcite.integration.LitmusCheckIntegrationTest; import org.apache.ignite.internal.processors.query.calcite.integration.LocalDateTimeSupportTest; import org.apache.ignite.internal.processors.query.calcite.integration.LocalQueryIntegrationTest; import org.apache.ignite.internal.processors.query.calcite.integration.MemoryQuotasIntegrationTest; @@ -185,6 +186,7 @@ TxThreadLockingTest.class, SelectByKeyFieldTest.class, WindowIntegrationTest.class, + LitmusCheckIntegrationTest.class, }) public class IntegrationTestSuite { } diff --git a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java index 7daf90205ddda..b59093cc582d8 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java +++ b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java @@ -31,6 +31,7 @@ import org.apache.ignite.internal.processors.query.calcite.planner.InlineIndexScanPlannerTest; import org.apache.ignite.internal.processors.query.calcite.planner.JoinColocationPlannerTest; import org.apache.ignite.internal.processors.query.calcite.planner.JoinCommutePlannerTest; +import org.apache.ignite.internal.processors.query.calcite.planner.JoinRowCountEstimationTest; import org.apache.ignite.internal.processors.query.calcite.planner.JoinWithUsingPlannerTest; import org.apache.ignite.internal.processors.query.calcite.planner.LimitOffsetPlannerTest; import org.apache.ignite.internal.processors.query.calcite.planner.MergeJoinPlannerTest; @@ -96,6 +97,7 @@ AbstractPlannerUtilityTest.class, HintsTestSuite.class, + JoinRowCountEstimationTest.class, }) public class PlannerTestSuite { }