You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2021/04/15 04:11:13 UTC
[calcite] 05/08: [CALCITE-4515] Do not generate the new join tree
from commute/associate rules if there are "always TRUE" conditions
(Vladimir Ozerov)
This is an automated email from the ASF dual-hosted git repository.
jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git
commit 93c305031fb6c152c118390647f2b9b0979109c2
Author: devozerov <pp...@gmail.com>
AuthorDate: Fri Feb 26 14:38:30 2021 +0300
[CALCITE-4515] Do not generate the new join tree from commute/associate rules if there are "always TRUE" conditions (Vladimir Ozerov)
---
.../calcite/rel/rules/JoinAssociateRule.java | 44 +++++--
.../apache/calcite/rel/rules/JoinCommuteRule.java | 19 ++-
.../org/apache/calcite/test/RelOptRulesTest.java | 138 +++++++++++++++++++++
.../org/apache/calcite/test/RelOptRulesTest.xml | 67 +++++++++-
4 files changed, 253 insertions(+), 15 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/JoinAssociateRule.java b/core/src/main/java/org/apache/calcite/rel/rules/JoinAssociateRule.java
index a4eb516..d65dfa3 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/JoinAssociateRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/JoinAssociateRule.java
@@ -19,7 +19,6 @@ package org.apache.calcite.rel.rules;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelRule;
-import org.apache.calcite.plan.volcano.RelSubset;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.JoinRelType;
@@ -28,6 +27,7 @@ import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexPermuteInputsShuttle;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBeans;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.mapping.Mappings;
@@ -68,7 +68,7 @@ public class JoinAssociateRule
final Join bottomJoin = call.rel(1);
final RelNode relA = bottomJoin.getLeft();
final RelNode relB = bottomJoin.getRight();
- final RelSubset relC = call.rel(2);
+ final RelNode relC = call.rel(2);
final RelOptCluster cluster = topJoin.getCluster();
final RexBuilder rexBuilder = cluster.getRexBuilder();
@@ -120,6 +120,11 @@ public class JoinAssociateRule
JoinPushThroughJoinRule.split(bottomJoin.getCondition(), aBitSet, top,
bottom);
+ final boolean allowAlwaysTrueCondition = config.isAllowAlwaysTrueCondition();
+ if (!allowAlwaysTrueCondition && (top.isEmpty() || bottom.isEmpty())) {
+ return;
+ }
+
// Mapping for moving conditions from topJoin or bottomJoin to
// newBottomJoin.
// target: | B | C |
@@ -132,16 +137,23 @@ public class JoinAssociateRule
final List<RexNode> newBottomList =
new RexPermuteInputsShuttle(bottomMapping, relB, relC)
.visitList(bottom);
- RexNode newBottomCondition =
- RexUtil.composeConjunction(rexBuilder, newBottomList);
- final Join newBottomJoin =
- bottomJoin.copy(bottomJoin.getTraitSet(), newBottomCondition, relB,
- relC, JoinRelType.INNER, false);
+ RexNode newBottomCondition = RexUtil.composeConjunction(rexBuilder, newBottomList);
+ if (!allowAlwaysTrueCondition && newBottomCondition.isAlwaysTrue()) {
+ return;
+ }
// Condition for newTopJoin consists of pieces from bottomJoin and topJoin.
// Field ordinals do not need to be changed.
RexNode newTopCondition = RexUtil.composeConjunction(rexBuilder, top);
+ if (!allowAlwaysTrueCondition && newTopCondition.isAlwaysTrue()) {
+ return;
+ }
+
+ final Join newBottomJoin =
+ bottomJoin.copy(bottomJoin.getTraitSet(), newBottomCondition, relB,
+ relC, JoinRelType.INNER, false);
+
@SuppressWarnings("SuspiciousNameCombination")
final Join newTopJoin =
topJoin.copy(topJoin.getTraitSet(), newTopCondition, relA,
@@ -153,19 +165,29 @@ public class JoinAssociateRule
/** Rule configuration. */
public interface Config extends RelRule.Config {
Config DEFAULT = EMPTY.as(Config.class)
- .withOperandFor(Join.class, RelSubset.class);
+ .withOperandFor(Join.class);
@Override default JoinAssociateRule toRule() {
return new JoinAssociateRule(this);
}
+ /**
+ * Whether to emit the new join tree if the new top or bottom join has a condition which
+ * is always {@code TRUE}.
+ */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(true)
+ boolean isAllowAlwaysTrueCondition();
+
+ /** Sets {@link #isAllowAlwaysTrueCondition()}. */
+ Config withAllowAlwaysTrueCondition(boolean allowAlwaysTrueCondition);
+
/** Defines an operand tree for the given classes. */
- default Config withOperandFor(Class<? extends Join> joinClass,
- Class<? extends RelSubset> relSubsetClass) {
+ default Config withOperandFor(Class<? extends Join> joinClass) {
return withOperandSupplier(b0 ->
b0.operand(joinClass).inputs(
b1 -> b1.operand(joinClass).anyInputs(),
- b2 -> b2.operand(relSubsetClass).anyInputs()))
+ b2 -> b2.operand(RelNode.class).anyInputs()))
.as(Config.class);
}
}
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/JoinCommuteRule.java b/core/src/main/java/org/apache/calcite/rel/rules/JoinCommuteRule.java
index ee4752e..c38be27 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/JoinCommuteRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/JoinCommuteRule.java
@@ -139,7 +139,13 @@ public class JoinCommuteRule
@Override public boolean matches(RelOptRuleCall call) {
Join join = call.rel(0);
// SEMI and ANTI join cannot be swapped.
- return join.getJoinType().projectsRight();
+ if (!join.getJoinType().projectsRight()) {
+ return false;
+ }
+
+ // Suppress join with "true" condition (that is, cartesian joins).
+ return config.isAllowAlwaysTrueCondition()
+ || !join.getCondition().isAlwaysTrue();
}
@Override public void onMatch(final RelOptRuleCall call) {
@@ -241,12 +247,21 @@ public class JoinCommuteRule
.as(Config.class);
}
- /** Whether to swap outer joins. */
+ /** Whether to swap outer joins; default false. */
@ImmutableBeans.Property
@ImmutableBeans.BooleanDefault(false)
boolean isSwapOuter();
/** Sets {@link #isSwapOuter()}. */
Config withSwapOuter(boolean swapOuter);
+
+ /** Whether to emit the new join tree if the join condition is {@code TRUE}
+ * (that is, cartesian joins); default true. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(true)
+ boolean isAllowAlwaysTrueCondition();
+
+ /** Sets {@link #isAllowAlwaysTrueCondition()}. */
+ Config withAllowAlwaysTrueCondition(boolean allowAlwaysTrueCondition);
}
}
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index 449914b..6ef31a5 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -71,6 +71,8 @@ import org.apache.calcite.rel.rules.DateRangeRules;
import org.apache.calcite.rel.rules.FilterJoinRule;
import org.apache.calcite.rel.rules.FilterMultiJoinMergeRule;
import org.apache.calcite.rel.rules.FilterProjectTransposeRule;
+import org.apache.calcite.rel.rules.JoinAssociateRule;
+import org.apache.calcite.rel.rules.JoinCommuteRule;
import org.apache.calcite.rel.rules.MultiJoin;
import org.apache.calcite.rel.rules.ProjectCorrelateTransposeRule;
import org.apache.calcite.rel.rules.ProjectFilterTransposeRule;
@@ -6981,4 +6983,140 @@ class RelOptRulesTest extends RelOptTestBase {
.withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
.checkUnchanged();
}
+
+ @Test void testJoinCommuteRuleWithAlwaysTrueConditionAllowed() {
+ checkJoinCommuteRuleWithAlwaysTrueConditionDisallowed(true);
+ }
+
+ @Test void testJoinCommuteRuleWithAlwaysTrueConditionDisallowed() {
+ checkJoinCommuteRuleWithAlwaysTrueConditionDisallowed(false);
+ }
+
+ private void checkJoinCommuteRuleWithAlwaysTrueConditionDisallowed(boolean allowAlwaysTrue) {
+ final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
+
+ RelNode left = relBuilder.scan("EMP").build();
+ RelNode right = relBuilder.scan("DEPT").build();
+
+ RelNode relNode = relBuilder.push(left)
+ .push(right)
+ .join(JoinRelType.INNER,
+ relBuilder.literal(true))
+ .build();
+
+ JoinCommuteRule.Config ruleConfig = JoinCommuteRule.Config.DEFAULT;
+ if (!allowAlwaysTrue) {
+ ruleConfig = ruleConfig.withAllowAlwaysTrueCondition(false);
+ }
+
+ HepProgram program = new HepProgramBuilder()
+ .addMatchLimit(1)
+ .addRuleInstance(ruleConfig.toRule())
+ .build();
+
+ HepPlanner hepPlanner = new HepPlanner(program);
+ hepPlanner.setRoot(relNode);
+ RelNode output = hepPlanner.findBestExp();
+
+ final String planAfter = NL + RelOptUtil.toString(output);
+ final DiffRepository diffRepos = getDiffRepos();
+ diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
+ SqlToRelTestBase.assertValid(output);
+ }
+
+ @Test void testJoinAssociateRuleWithBottomAlwaysTrueConditionAllowed() {
+ checkJoinAssociateRuleWithBottomAlwaysTrueCondition(true);
+ }
+
+ @Test void testJoinAssociateRuleWithBottomAlwaysTrueConditionDisallowed() {
+ checkJoinAssociateRuleWithBottomAlwaysTrueCondition(false);
+ }
+
+ private void checkJoinAssociateRuleWithBottomAlwaysTrueCondition(boolean allowAlwaysTrue) {
+ final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
+
+ RelNode bottomLeft = relBuilder.scan("EMP").build();
+ RelNode bottomRight = relBuilder.scan("DEPT").build();
+ RelNode top = relBuilder.scan("BONUS").build();
+
+ RelNode relNode = relBuilder.push(bottomLeft)
+ .push(bottomRight)
+ .join(JoinRelType.INNER,
+ relBuilder.call(SqlStdOperatorTable.EQUALS,
+ relBuilder.field(2, 0, "DEPTNO"),
+ relBuilder.field(2, 1, "DEPTNO")))
+ .push(top)
+ .join(JoinRelType.INNER,
+ relBuilder.call(SqlStdOperatorTable.EQUALS,
+ relBuilder.field(2, 0, "JOB"),
+ relBuilder.field(2, 1, "JOB")))
+ .build();
+
+ JoinAssociateRule.Config ruleConfig = JoinAssociateRule.Config.DEFAULT;
+ if (!allowAlwaysTrue) {
+ ruleConfig = ruleConfig.withAllowAlwaysTrueCondition(false);
+ }
+
+ HepProgram program = new HepProgramBuilder()
+ .addMatchLimit(1)
+ .addMatchOrder(HepMatchOrder.TOP_DOWN)
+ .addRuleInstance(ruleConfig.toRule())
+ .build();
+
+ HepPlanner hepPlanner = new HepPlanner(program);
+ hepPlanner.setRoot(relNode);
+ RelNode output = hepPlanner.findBestExp();
+
+ final String planAfter = NL + RelOptUtil.toString(output);
+ final DiffRepository diffRepos = getDiffRepos();
+ diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
+ SqlToRelTestBase.assertValid(output);
+ }
+
+ @Test void testJoinAssociateRuleWithTopAlwaysTrueConditionAllowed() {
+ checkJoinAssociateRuleWithTopAlwaysTrueCondition(true);
+ }
+
+ @Test void testJoinAssociateRuleWithTopAlwaysTrueConditionDisallowed() {
+ checkJoinAssociateRuleWithTopAlwaysTrueCondition(false);
+ }
+
+ private void checkJoinAssociateRuleWithTopAlwaysTrueCondition(boolean allowAlwaysTrue) {
+ final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
+
+ RelNode bottomLeft = relBuilder.scan("EMP").build();
+ RelNode bottomRight = relBuilder.scan("BONUS").build();
+ RelNode top = relBuilder.scan("DEPT").build();
+
+ RelNode relNode = relBuilder.push(bottomLeft)
+ .push(bottomRight)
+ .join(JoinRelType.INNER,
+ relBuilder.literal(true))
+ .push(top)
+ .join(JoinRelType.INNER,
+ relBuilder.call(SqlStdOperatorTable.EQUALS,
+ relBuilder.field(2, 0, "DEPTNO"),
+ relBuilder.field(2, 1, "DEPTNO")))
+ .build();
+
+ JoinAssociateRule.Config ruleConfig = JoinAssociateRule.Config.DEFAULT;
+ if (!allowAlwaysTrue) {
+ ruleConfig = ruleConfig.withAllowAlwaysTrueCondition(false);
+ }
+
+ HepProgram program = new HepProgramBuilder()
+ .addMatchLimit(1)
+ .addMatchOrder(HepMatchOrder.TOP_DOWN)
+ .addRuleInstance(ruleConfig.toRule())
+ .build();
+
+ HepPlanner hepPlanner = new HepPlanner(program);
+ hepPlanner.setRoot(relNode);
+ RelNode output = hepPlanner.findBestExp();
+
+ final String planAfter = NL + RelOptUtil.toString(output);
+ final DiffRepository diffRepos = getDiffRepos();
+ diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
+ SqlToRelTestBase.assertValid(output);
+ }
}
diff --git a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 8346c92..3dc6ad2 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -3718,6 +3718,69 @@ LogicalIntersect(all=[true])
]]>
</Resource>
</TestCase>
+ <TestCase name="testJoinAssociateRuleWithBottomAlwaysTrueConditionAllowed">
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalJoin(condition=[AND(=($2, $12), =($7, $8))], joinType=[inner])
+ LogicalTableScan(table=[[scott, EMP]])
+ LogicalJoin(condition=[true], joinType=[inner])
+ LogicalTableScan(table=[[scott, DEPT]])
+ LogicalTableScan(table=[[scott, BONUS]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testJoinAssociateRuleWithBottomAlwaysTrueConditionDisallowed">
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalJoin(condition=[=($2, $12)], joinType=[inner])
+ LogicalJoin(condition=[=($7, $8)], joinType=[inner])
+ LogicalTableScan(table=[[scott, EMP]])
+ LogicalTableScan(table=[[scott, DEPT]])
+ LogicalTableScan(table=[[scott, BONUS]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testJoinAssociateRuleWithTopAlwaysTrueConditionAllowed">
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalJoin(condition=[=($7, $12)], joinType=[inner])
+ LogicalTableScan(table=[[scott, EMP]])
+ LogicalJoin(condition=[true], joinType=[inner])
+ LogicalTableScan(table=[[scott, BONUS]])
+ LogicalTableScan(table=[[scott, DEPT]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testJoinAssociateRuleWithTopAlwaysTrueConditionDisallowed">
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalJoin(condition=[=($7, $12)], joinType=[inner])
+ LogicalJoin(condition=[true], joinType=[inner])
+ LogicalTableScan(table=[[scott, EMP]])
+ LogicalTableScan(table=[[scott, BONUS]])
+ LogicalTableScan(table=[[scott, DEPT]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testJoinCommuteRuleWithAlwaysTrueConditionAllowed">
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EMPNO=[$3], ENAME=[$4], JOB=[$5], MGR=[$6], HIREDATE=[$7], SAL=[$8], COMM=[$9], DEPTNO=[$10], DEPTNO0=[$0], DNAME=[$1], LOC=[$2])
+ LogicalJoin(condition=[true], joinType=[inner])
+ LogicalTableScan(table=[[scott, DEPT]])
+ LogicalTableScan(table=[[scott, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testJoinCommuteRuleWithAlwaysTrueConditionDisallowed">
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalJoin(condition=[true], joinType=[inner])
+ LogicalTableScan(table=[[scott, EMP]])
+ LogicalTableScan(table=[[scott, DEPT]])
+]]>
+ </Resource>
+ </TestCase>
<TestCase name="testJoinProjectTransposeWindow">
<Resource name="sql">
<![CDATA[select *
@@ -8227,7 +8290,7 @@ LogicalAggregate(group=[{}], EXPR$0=[SUM($0)])
</TestCase>
<TestCase name="testPushAggregateThroughJoin7">
<Resource name="sql">
- <![CDATA[select any_value(B.sal)
+ <![CDATA[select any_value(distinct B.sal)
from sales.emp as A
join (select distinct sal from sales.emp) as B
on A.sal=B.sal
@@ -8255,7 +8318,7 @@ LogicalAggregate(group=[{}], EXPR$0=[ANY_VALUE($1)])
</TestCase>
<TestCase name="testPushAggregateThroughJoin8">
<Resource name="sql">
- <![CDATA[select single_value(B.sal)
+ <![CDATA[select single_value(distinct B.sal)
from sales.emp as A
join (select distinct sal from sales.emp) as B
on A.sal=B.sal