This is an automated email from the ASF dual-hosted git repository.
laurent pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/master by this push:
new c25b29c [CALCITE-2803] ProjectTransposeJoinRule messes INDF
expressions
c25b29c is described below
commit c25b29c0c1cae3a57bf91ddfd9f6a0622ca974a4
Author: Laurent Goujon <[email protected]>
AuthorDate: Wed Jan 23 14:32:00 2019 -0800
[CALCITE-2803] ProjectTransposeJoinRule messes INDF expressions
ProjectTransposeJoinRule does not identify expanded versions of IS NOT
DISTINCT FROM expressions in the join filter, and might push them below
the join operator in a way which makes impossible for
RelOptUtil#splitJoinCondition() to identify the INDF condition and mis-
categorize the join as not being an equi-join.
Fix the issue by collapsing INDF expressions before invoking PushProjector.
Also add support for expanded form CASE(WHEN A IS NULL THEN B IS NULL
WHEN B IS NULL THEN A IS NULL ELSE A = B).
---
.../java/org/apache/calcite/plan/RelOptUtil.java | 95 +++++++++++++++++++---
.../rel/rules/ProjectJoinTransposeRule.java | 24 +++++-
.../org/apache/calcite/plan/RelOptUtilTest.java | 54 +++++++++++-
.../org/apache/calcite/test/RelOptRulesTest.java | 10 +++
.../org/apache/calcite/test/RelOptRulesTest.xml | 23 ++++++
5 files changed, 190 insertions(+), 16 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index 809dd27..15733d9 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -1474,6 +1474,8 @@ public abstract class RelOptUtil {
}
/**
+ * Collapse an expanded version of IS NOT DISTINCT FROM expression
+ *
* Helper method for
* {@link #splitJoinCondition(RexBuilder, int, RexNode, List, List, List,
List)} and
* {@link #splitJoinCondition(List, List, RexNode, List, List, List, List)}.
@@ -1492,8 +1494,22 @@ public abstract class RelOptUtil {
* return a IS NOT DISTINCT FROM function call. Otherwise return the
input
* function call as it is.
*/
- private static RexCall collapseExpandedIsNotDistinctFromExpr(final RexCall
call,
+ public static RexCall collapseExpandedIsNotDistinctFromExpr(final RexCall
call,
final RexBuilder rexBuilder) {
+ switch (call.getKind()) {
+ case OR:
+ return doCollapseExpandedIsNotDistinctFromOrExpr(call, rexBuilder);
+
+ case CASE:
+ return doCollapseExpandedIsNotDistinctFromCaseExpr(call, rexBuilder);
+
+ default:
+ return call;
+ }
+ }
+
+ private static RexCall doCollapseExpandedIsNotDistinctFromOrExpr(final
RexCall call,
+ final RexBuilder rexBuilder) {
if (call.getKind() != SqlKind.OR || call.getOperands().size() != 2) {
return call;
}
@@ -1508,13 +1524,24 @@ public abstract class RelOptUtil {
RexCall opEqCall = (RexCall) op0;
RexCall opNullEqCall = (RexCall) op1;
+ // Swapping the operands if necessary
if (opEqCall.getKind() == SqlKind.AND
- && opNullEqCall.getKind() == SqlKind.EQUALS) {
+ && (opNullEqCall.getKind() == SqlKind.EQUALS
+ || opNullEqCall.getKind() == SqlKind.IS_TRUE)) {
RexCall temp = opEqCall;
opEqCall = opNullEqCall;
opNullEqCall = temp;
}
+ // Check if EQUALS is actually wrapped in IS TRUE expression
+ if (opEqCall.getKind() == SqlKind.IS_TRUE) {
+ RexNode tmp = opEqCall.getOperands().get(0);
+ if (!(tmp instanceof RexCall)) {
+ return call;
+ }
+ opEqCall = (RexCall) tmp;
+ }
+
if (opNullEqCall.getKind() != SqlKind.AND
|| opNullEqCall.getOperands().size() != 2
|| opEqCall.getKind() != SqlKind.EQUALS) {
@@ -1527,18 +1554,62 @@ public abstract class RelOptUtil {
|| op11.getKind() != SqlKind.IS_NULL) {
return call;
}
- final RexNode isNullInput0 = ((RexCall) op10).getOperands().get(0);
- final RexNode isNullInput1 = ((RexCall) op11).getOperands().get(0);
- final String isNullInput0Digest = isNullInput0.toString();
- final String isNullInput1Digest = isNullInput1.toString();
- final String equalsInput0Digest = opEqCall.getOperands().get(0).toString();
- final String equalsInput1Digest = opEqCall.getOperands().get(1).toString();
+ return doCollapseExpandedIsNotDistinctFrom(rexBuilder, call, (RexCall)
op10, (RexCall) op11,
+ opEqCall);
+ }
+
+ private static RexCall doCollapseExpandedIsNotDistinctFromCaseExpr(final
RexCall call,
+ final RexBuilder rexBuilder) {
+ if (call.getKind() != SqlKind.CASE || call.getOperands().size() != 5) {
+ return call;
+ }
+
+ final RexNode op0 = call.getOperands().get(0);
+ final RexNode op1 = call.getOperands().get(1);
+ final RexNode op2 = call.getOperands().get(2);
+ final RexNode op3 = call.getOperands().get(3);
+ final RexNode op4 = call.getOperands().get(4);
+
+ if (!(op0 instanceof RexCall) || !(op1 instanceof RexCall) || !(op2
instanceof RexCall)
+ || !(op3 instanceof RexCall) || !(op4 instanceof RexCall)) {
+ return call;
+ }
+
+ RexCall ifCall = (RexCall) op0;
+ RexCall thenCall = (RexCall) op1;
+ RexCall elseIfCall = (RexCall) op2;
+ RexCall elseIfThenCall = (RexCall) op3;
+ RexCall elseCall = (RexCall) op4;
+
+ if (ifCall.getKind() != SqlKind.IS_NULL
+ || thenCall.getKind() != SqlKind.IS_NULL
+ || elseIfCall.getKind() != SqlKind.IS_NULL
+ || elseIfThenCall.getKind() != SqlKind.IS_NULL
+ || elseCall.getKind() != SqlKind.EQUALS) {
+ return call;
+ }
+
+ if (!ifCall.equals(elseIfThenCall)
+ || !thenCall.equals(elseIfCall)) {
+ return call;
+ }
+
+ return doCollapseExpandedIsNotDistinctFrom(rexBuilder, call, ifCall,
elseIfCall, elseCall);
+ }
+
+ private static RexCall doCollapseExpandedIsNotDistinctFrom(final RexBuilder
rexBuilder,
+ final RexCall call, RexCall ifNull0Call, RexCall ifNull1Call, RexCall
equalsCall) {
+ final RexNode isNullInput0 = ifNull0Call.getOperands().get(0);
+ final RexNode isNullInput1 = ifNull1Call.getOperands().get(0);
+
+ final RexNode equalsInput0 = RexUtil
+ .removeNullabilityCast(rexBuilder.getTypeFactory(),
equalsCall.getOperands().get(0));
+ final RexNode equalsInput1 = RexUtil
+ .removeNullabilityCast(rexBuilder.getTypeFactory(),
equalsCall.getOperands().get(1));
- if ((isNullInput0Digest.equals(equalsInput0Digest)
- && isNullInput1Digest.equals(equalsInput1Digest))
- || (isNullInput1Digest.equals(equalsInput0Digest)
- && isNullInput0Digest.equals(equalsInput1Digest))) {
+ if ((isNullInput0.equals(equalsInput0) &&
isNullInput1.equals(equalsInput1))
+ || (isNullInput1.equals(equalsInput0) &&
isNullInput0.equals(equalsInput1))) {
return (RexCall)
rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
ImmutableList.of(isNullInput0, isNullInput1));
}
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java
index 52d22cc..feb01b6 100644
---
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java
+++
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java
@@ -18,14 +18,17 @@ package org.apache.calcite.rel.rules;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.RelFactories;
import org.apache.calcite.rel.core.SemiJoin;
import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexOver;
+import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.tools.RelBuilderFactory;
import java.util.ArrayList;
@@ -77,6 +80,21 @@ public class ProjectJoinTransposeRule extends RelOptRule {
if (join instanceof SemiJoin) {
return; // TODO: support SemiJoin
}
+
+ // Normalize the join condition so we don't end up misidentified expanded
+ // form of IS NOT DISTINCT FROM as PushProject also visit the filter
condition
+ // and push down expressions.
+ RexNode joinFilter = join.getCondition().accept(new RexShuttle() {
+ @Override public RexNode visitCall(RexCall rexCall) {
+ final RexNode node = super.visitCall(rexCall);
+ if (!(node instanceof RexCall)) {
+ return node;
+ }
+ return RelOptUtil.collapseExpandedIsNotDistinctFromExpr((RexCall) node,
+ call.builder().getRexBuilder());
+ }
+ });
+
// locate all fields referenced in the projection and join condition;
// determine which inputs are referenced in the projection and
// join condition; if all fields are being referenced and there are no
@@ -84,7 +102,7 @@ public class ProjectJoinTransposeRule extends RelOptRule {
PushProjector pushProject =
new PushProjector(
origProj,
- join.getCondition(),
+ joinFilter,
join,
preserveExprCondition,
call.builder());
@@ -108,7 +126,7 @@ public class ProjectJoinTransposeRule extends RelOptRule {
// convert the join condition to reference the projected columns
RexNode newJoinFilter = null;
int[] adjustments = pushProject.getAdjustments();
- if (join.getCondition() != null) {
+ if (joinFilter != null) {
List<RelDataTypeField> projJoinFieldList = new ArrayList<>();
projJoinFieldList.addAll(
join.getSystemFieldList());
@@ -118,7 +136,7 @@ public class ProjectJoinTransposeRule extends RelOptRule {
rightProjRel.getRowType().getFieldList());
newJoinFilter =
pushProject.convertRefsAndExprs(
- join.getCondition(),
+ joinFilter,
projJoinFieldList,
adjustments);
}
diff --git a/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
b/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
index 664b418..2752034 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
@@ -188,6 +188,58 @@ public class RelOptUtilTest {
REL_BUILDER.literal(true));
}
+ /**
+ * Test {@link RelOptUtil#splitJoinCondition(RelNode, RelNode, RexNode,
List, List, List)}
+ * where the join condition contains an expanded version of IS NOT DISTINCT
using CASE
+ */
+ @Test public void testSplitJoinConditionExpandedIsNotDistinctFromUsingCase()
{
+ int leftJoinIndex =
EMP_SCAN.getRowType().getFieldNames().indexOf("DEPTNO");
+ int rightJoinIndex = DEPT_ROW.getFieldNames().indexOf("DEPTNO");
+
+ RexInputRef leftKeyInputRef = RexInputRef.of(leftJoinIndex,
EMP_DEPT_JOIN_REL_FIELDS);
+ RexInputRef rightKeyInputRef =
+ RexInputRef.of(EMP_ROW.getFieldCount() + rightJoinIndex,
EMP_DEPT_JOIN_REL_FIELDS);
+ RexNode joinCond = RelOptUtil.isDistinctFrom(
+ REL_BUILDER.getRexBuilder(),
+ leftKeyInputRef,
+ rightKeyInputRef,
+ true);
+
+
+ splitJoinConditionHelper(
+ joinCond,
+ Collections.singletonList(leftJoinIndex),
+ Collections.singletonList(rightJoinIndex),
+ Collections.singletonList(false),
+ REL_BUILDER.literal(true));
+ }
+
+ /**
+ * Test {@link RelOptUtil#splitJoinCondition(RelNode, RelNode, RexNode,
List, List, List)}
+ * where the join condition contains an expanded version of IS NOT DISTINCT
using CASE
+ */
+ @Test public void
testSplitJoinConditionExpandedIsNotDistinctFromUsingCase2() {
+ int leftJoinIndex =
EMP_SCAN.getRowType().getFieldNames().indexOf("DEPTNO");
+ int rightJoinIndex = DEPT_ROW.getFieldNames().indexOf("DEPTNO");
+
+ RexInputRef leftKeyInputRef = RexInputRef.of(leftJoinIndex,
EMP_DEPT_JOIN_REL_FIELDS);
+ RexInputRef rightKeyInputRef =
+ RexInputRef.of(EMP_ROW.getFieldCount() + rightJoinIndex,
EMP_DEPT_JOIN_REL_FIELDS);
+ RexNode joinCond = REL_BUILDER.call(SqlStdOperatorTable.CASE,
+ REL_BUILDER.call(SqlStdOperatorTable.IS_NULL, leftKeyInputRef),
+ REL_BUILDER.call(SqlStdOperatorTable.IS_NULL, rightKeyInputRef),
+ REL_BUILDER.call(SqlStdOperatorTable.IS_NULL, rightKeyInputRef),
+ REL_BUILDER.call(SqlStdOperatorTable.IS_NULL, leftKeyInputRef),
+ REL_BUILDER.call(SqlStdOperatorTable.EQUALS, leftKeyInputRef,
rightKeyInputRef));
+
+ splitJoinConditionHelper(
+ joinCond,
+ Collections.singletonList(leftJoinIndex),
+ Collections.singletonList(rightJoinIndex),
+ Collections.singletonList(false),
+ REL_BUILDER.literal(true));
+ }
+
private static void splitJoinConditionHelper(RexNode joinCond, List<Integer>
expLeftKeys,
List<Integer> expRightKeys, List<Boolean> expFilterNulls, RexNode
expRemaining) {
List<Integer> actLeftKeys = new ArrayList<>();
@@ -197,7 +249,7 @@ public class RelOptUtilTest {
RexNode actRemaining = RelOptUtil.splitJoinCondition(EMP_SCAN, DEPT_SCAN,
joinCond, actLeftKeys,
actRightKeys, actFilterNulls);
- assertEquals(expRemaining.toString(), actRemaining.toString());
+ assertEquals(expRemaining, actRemaining);
assertEquals(expFilterNulls, actFilterNulls);
assertEquals(expLeftKeys, actLeftKeys);
assertEquals(expRightKeys, actRightKeys);
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 2516f72..055af52 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -5183,6 +5183,16 @@ public class RelOptRulesTest extends RelOptTestBase {
+ "from sales.dept group by name";
sql(sql).with(program).check();
}
+
+ /** Test case for
+ * <a href="https://issues.apache.org/jira/browse/CALCITE-2803">[CALCITE-2803]
+ * Identify expanded IS NOT DISTINCT FROM expression when pushing project
past join</a>.
+ */
+ @Test public void testPushProjectWithIsNotDistinctFromPastJoin() {
+ checkPlanning(ProjectJoinTransposeRule.INSTANCE,
+ "select e.sal + b.comm from emp e inner join bonus b "
+ + "on e.ename IS NOT DISTINCT FROM b.ename and e.deptno = 10");
+ }
}
// End RelOptRulesTest.java
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 e90f1b0..ad9d200 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -10139,4 +10139,27 @@ LogicalProject(NAME=[$0], EXPR$1=[CAST(POWER(/(-($1,
/(*($2, $2), $3)), $3), 0.5
]]>
</Resource>
</TestCase>
+ <TestCase name="testPushProjectWithIsNotDistinctFromPastJoin">
+ <Resource name="sql">
+ <![CDATA[select e.sal + b.comm from emp e inner join bonus b on
e.ename IS NOT DISTINCT FROM b.ename]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(EXPR$0=[+($5, $12)])
+ LogicalJoin(condition=[AND(OR(AND(IS NULL($1), IS NULL($9)), IS TRUE(=($1,
$9))), =($7, 10))], joinType=[inner])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalTableScan(table=[[CATALOG, SALES, BONUS]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EXPR$0=[+($1, $4)])
+ LogicalJoin(condition=[AND(IS NOT DISTINCT FROM($0, $3), $2)],
joinType=[inner])
+ LogicalProject(ENAME=[$1], SAL=[$5], ==[=($7, 10)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(ENAME=[$0], COMM=[$3])
+ LogicalTableScan(table=[[CATALOG, SALES, BONUS]])
+]]>
+ </Resource>
+ </TestCase>
</Root>