This is an automated email from the ASF dual-hosted git repository.
mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 2f096ff2ec [CALCITE-6776] Multiple expanded `IS NOT DISTINCT FROM`
cannot be collapsed back
2f096ff2ec is described below
commit 2f096ff2eccd98065227c812af09ea949fb719dd
Author: Lino Rosa <[email protected]>
AuthorDate: Thu Jan 9 09:25:15 2025 -0500
[CALCITE-6776] Multiple expanded `IS NOT DISTINCT FROM` cannot be collapsed
back
---
.../java/org/apache/calcite/plan/RelOptUtil.java | 37 +--
.../org/apache/calcite/plan/RelOptUtilTest.java | 255 +++++++++++++++++++++
2 files changed, 275 insertions(+), 17 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 64c8e9c069..0c73d0c00e 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -1748,32 +1748,35 @@ public abstract class RelOptUtil {
* and
* {@link #splitJoinCondition(List, List, RexNode, List, List, List, List)}.
*
- * <p>If the given expr <code>call</code> is an expanded version of
+ * <p>If the given expr <code>rexCall</code> contains an expanded version of
* {@code IS NOT DISTINCT FROM} function call, collapses it and return a
* {@code IS NOT DISTINCT FROM} function call.
*
* <p>For example: {@code t1.key IS NOT DISTINCT FROM t2.key}
- * can rewritten in expanded form as
+ * can be rewritten in expanded form as
* {@code t1.key = t2.key OR (t1.key IS NULL AND t2.key IS NULL)}.
*
- * @param call Function expression to try collapsing
+ * @param rexCall Function expression to try collapsing
* @param rexBuilder {@link RexBuilder} instance to create new {@link
RexCall} instances.
- * @return If the given function is an expanded IS NOT DISTINCT FROM
function call,
- * return a IS NOT DISTINCT FROM function call. Otherwise return the
input
- * function call as it is.
+ * @return A function where all IS NOT DISTINCT FROM are collapsed.
*/
- public static RexCall collapseExpandedIsNotDistinctFromExpr(final RexCall
call,
+ public static RexCall collapseExpandedIsNotDistinctFromExpr(final RexCall
rexCall,
final RexBuilder rexBuilder) {
- switch (call.getKind()) {
- case OR:
- return doCollapseExpandedIsNotDistinctFromOrExpr(call, rexBuilder);
-
- case CASE:
- return doCollapseExpandedIsNotDistinctFromCaseExpr(call, rexBuilder);
-
- default:
- return call;
- }
+ final RexShuttle shuttle = new RexShuttle() {
+ @Override public RexNode visitCall(RexCall call) {
+ RexCall recursivelyExpanded = (RexCall) super.visitCall(call);
+
+ switch (recursivelyExpanded.getKind()) {
+ case OR:
+ return
doCollapseExpandedIsNotDistinctFromOrExpr(recursivelyExpanded, rexBuilder);
+ case CASE:
+ return
doCollapseExpandedIsNotDistinctFromCaseExpr(recursivelyExpanded, rexBuilder);
+ default:
+ return recursivelyExpanded;
+ }
+ }
+ };
+ return (RexCall) rexCall.accept(shuttle);
}
private static RexCall doCollapseExpandedIsNotDistinctFromOrExpr(final
RexCall call,
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 95a33027c5..ab058c55a2 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
@@ -34,6 +34,7 @@ import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.schema.SchemaPlus;
@@ -420,6 +421,260 @@ class RelOptUtilTest {
assertThat(actRightKeys, is(expRightKeys));
}
+ /**
+ * Test that {@link
RelOptUtil#collapseExpandedIsNotDistinctFromExpr(RexCall, RexBuilder)}
+ * collapses an expanded version of IS NOT DISTINCT using OR.
+ */
+ @Test void testCollapseExpandedIsNotDistinctFromUsingOr() {
+ final RexBuilder rexBuilder = relBuilder.getRexBuilder();
+
+ final RexNode leftEmpNo =
+ RexInputRef.of(empScan.getRowType().getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+ final RexNode rightEmpNo =
+ RexInputRef.of(empRow.getFieldCount() +
deptRow.getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+
+ // OR(AND(IS NULL($0), IS NULL($7)), IS TRUE(=($0, $7)))
+ RexNode expanded = relBuilder.isNotDistinctFrom(leftEmpNo, rightEmpNo);
+
+ // IS NOT DISTINCT FROM($0, $7)
+ RexNode collapsed =
+ RelOptUtil.collapseExpandedIsNotDistinctFromExpr((RexCall) expanded,
rexBuilder);
+
+ RexNode expected =
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
leftEmpNo, rightEmpNo);
+
+ assertThat(collapsed, is(expected));
+ }
+
+ /**
+ * Test that {@link
RelOptUtil#collapseExpandedIsNotDistinctFromExpr(RexCall, RexBuilder)}
+ * collapses an expanded version of IS NOT DISTINCT using CASE.
+ */
+ @Test void testCollapseExpandedIsNotDistinctFromUsingCase() {
+ final RexBuilder rexBuilder = relBuilder.getRexBuilder();
+
+ final RexNode leftEmpNo =
+ RexInputRef.of(empScan.getRowType().getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+ final RexNode rightEmpNo =
+ RexInputRef.of(empRow.getFieldCount() +
deptRow.getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+
+ // CASE(IS NULL($0), IS NULL($7), IS NULL($7), IS NULL($0), =($0, $7))
+ RexNode expanded =
+ relBuilder.call(SqlStdOperatorTable.CASE, relBuilder.isNull(leftEmpNo),
+ relBuilder.isNull(rightEmpNo),
+ relBuilder.isNull(rightEmpNo),
+ relBuilder.isNull(leftEmpNo),
+ relBuilder.equals(leftEmpNo, rightEmpNo));
+
+ // IS NOT DISTINCT FROM($0, $7)
+ RexNode collapsed =
+ RelOptUtil.collapseExpandedIsNotDistinctFromExpr((RexCall) expanded,
rexBuilder);
+
+ RexNode expected =
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
leftEmpNo, rightEmpNo);
+
+ assertThat(collapsed, is(expected));
+ }
+
+ /**
+ * Test that {@link
RelOptUtil#collapseExpandedIsNotDistinctFromExpr(RexCall, RexBuilder)}
+ * collapses an expression with expanded versions of IS NOT DISTINCT using
OR and CASE.
+ */
+ @Test void testCollapseExpandedIsNotDistinctFromUsingOrAndCase() {
+ final RexBuilder rexBuilder = relBuilder.getRexBuilder();
+
+ final RexNode leftEmpNo =
+ RexInputRef.of(empScan.getRowType().getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+ final RexNode rightEmpNo =
+ RexInputRef.of(empRow.getFieldCount() +
deptRow.getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+
+ final RexNode leftDeptNo =
+ RexInputRef.of(empScan.getRowType().getFieldNames().indexOf("DEPTNO"),
+ empDeptJoinRelFields);
+ final RexNode rightDeptNo =
+ RexInputRef.of(empRow.getFieldCount() +
deptRow.getFieldNames().indexOf("DEPTNO"),
+ empDeptJoinRelFields);
+
+ // An IS NOT DISTINCT FROM expanded in "CASE" shape
+ // CASE(IS NULL($0), IS NULL($7), IS NULL($7), IS NULL($0), =($0, $7))
+ RexNode expandedCase =
+ relBuilder.call(SqlStdOperatorTable.CASE, relBuilder.isNull(leftEmpNo),
+ relBuilder.isNull(rightEmpNo),
+ relBuilder.isNull(rightEmpNo),
+ relBuilder.isNull(leftEmpNo),
+ relBuilder.equals(leftEmpNo, rightEmpNo));
+
+ // An IS NOT DISTINCT FROM expanded in "OR" shape
+ // OR(AND(IS NULL($7), IS NULL($8)), =($7, $8))
+ RexNode expandedOr =
+ relBuilder.call(
+ SqlStdOperatorTable.OR, relBuilder.call(SqlStdOperatorTable.AND,
+ relBuilder.isNull(leftDeptNo),
+ relBuilder.isNull(rightDeptNo)),
+ relBuilder.call(SqlStdOperatorTable.EQUALS, leftDeptNo,
rightDeptNo));
+
+ // AND(
+ // OR(AND(IS NULL($7), IS NULL($8)), =($7, $8)),
+ // CASE(IS NULL($0), IS NULL($7), IS NULL($7), IS NULL($0), =($0, $7))
+ // )
+ RexNode expanded = relBuilder.and(expandedOr, expandedCase);
+
+ // AND(IS NOT DISTINCT FROM($7, $8), IS NOT DISTINCT FROM($0, $7))
+ RexNode collapsed =
+ RelOptUtil.collapseExpandedIsNotDistinctFromExpr((RexCall) expanded,
rexBuilder);
+
+ RexNode expected =
+ rexBuilder.makeCall(
+ // Expected is nullable because `expandedCase` is nullable
+
relBuilder.getTypeFactory().createTypeWithNullability(expanded.getType(), true),
+ SqlStdOperatorTable.AND,
+ ImmutableList.of(
+ rexBuilder.makeCall(
+ SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+ leftEmpNo,
+ rightEmpNo),
+ rexBuilder.makeCall(
+ SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+ leftDeptNo,
+ rightDeptNo)));
+
+ assertThat(collapsed, is(expected));
+ }
+
+ /**
+ * Test that {@link
RelOptUtil#collapseExpandedIsNotDistinctFromExpr(RexCall, RexBuilder)}
+ * recursively collapses expanded versions of IS NOT DISTINCT.
+ */
+ @Test void testCollapseExpandedIsNotDistinctFromRecursively() {
+ final RexBuilder rexBuilder = relBuilder.getRexBuilder();
+
+ final RexNode leftEmpNo =
+ RexInputRef.of(empScan.getRowType().getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+ final RexNode rightEmpNo =
+ RexInputRef.of(empRow.getFieldCount() +
deptRow.getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+
+ // OR(
+ // AND(
+ // IS NULL(OR(AND(IS NULL($0), IS NULL($7)), IS TRUE(=($0, $7)))),
+ // IS NULL(OR(AND(IS NULL($0), IS NULL($7)), IS TRUE(=($0, $7))))),
+ // IS TRUE(=(
+ // OR(AND(IS NULL($0), IS NULL($7)), IS TRUE(=($0, $7))),
+ // OR(AND(IS NULL($0), IS NULL($7)), IS TRUE(=($0, $7)))
+ // )
+ // )
+ // )
+ RexNode expanded =
+ relBuilder.isNotDistinctFrom(
+ relBuilder.isNotDistinctFrom(leftEmpNo, rightEmpNo),
+ relBuilder.isNotDistinctFrom(leftEmpNo, rightEmpNo));
+
+ // IS NOT DISTINCT FROM(IS NOT DISTINCT FROM($0, $7), IS NOT DISTINCT
FROM($0, $7))
+ RexNode collapsed =
+ RelOptUtil.collapseExpandedIsNotDistinctFromExpr((RexCall) expanded,
rexBuilder);
+
+ RexNode expected =
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+ leftEmpNo,
+ rightEmpNo),
+
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+ leftEmpNo,
+ rightEmpNo));
+
+ assertThat(collapsed, is(expected));
+ }
+
+ /**
+ * Test that {@link
RelOptUtil#collapseExpandedIsNotDistinctFromExpr(RexCall, RexBuilder)}
+ * will collapse IS NOT DISTINCT FROM nested within RexNodes.
+ */
+ @Test void testCollapseExpandedIsNotDistinctFromInsideRexNode() {
+ final RexBuilder rexBuilder = relBuilder.getRexBuilder();
+
+ final RexNode leftEmpNo =
+ RexInputRef.of(empScan.getRowType().getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+ final RexNode rightEmpNo =
+ RexInputRef.of(empRow.getFieldCount() +
deptRow.getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+
+ RexNode expandedIsNotDistinctFrom =
relBuilder.isNotDistinctFrom(leftEmpNo, rightEmpNo);
+ // NULLIF(
+ // NOT(OR(AND(IS NULL($0), IS NULL($7)), IS TRUE(=($0, $7)))),
+ // IS NOT NULL(OR(AND(IS NULL($0), IS NULL($7)), IS TRUE(=($0, $7))))
+ // )
+ RexNode expanded =
+ relBuilder.call(SqlStdOperatorTable.NULLIF,
+ relBuilder.not(expandedIsNotDistinctFrom),
+ relBuilder.isNotNull(expandedIsNotDistinctFrom));
+
+ // NULLIF(NOT(IS NOT DISTINCT FROM($0, $7)), IS NOT NULL(IS NOT DISTINCT
FROM($0, $7)))
+ RexNode collapsed =
+ RelOptUtil.collapseExpandedIsNotDistinctFromExpr((RexCall) expanded,
rexBuilder);
+
+ RexNode collapsedIsNotDistinctFrom =
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
leftEmpNo, rightEmpNo);
+ RexNode expected =
+ rexBuilder.makeCall(
+ SqlStdOperatorTable.NULLIF,
+ relBuilder.not(collapsedIsNotDistinctFrom),
+ relBuilder.isNotNull(collapsedIsNotDistinctFrom));
+
+ assertThat(collapsed, is(expected));
+ }
+
+ /**
+ * Test that {@link
RelOptUtil#collapseExpandedIsNotDistinctFromExpr(RexCall, RexBuilder)}
+ * can handle collapsing IS NOT DISTINCT FROM composed of other RexNodes.
+ */
+ @Test void testCollapseExpandedIsNotDistinctFromOnContainingRexNodes() {
+ final RexBuilder rexBuilder = relBuilder.getRexBuilder();
+
+ final RexNode leftEmpNo =
+ RexInputRef.of(empScan.getRowType().getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+ final RexNode rightEmpNo =
+ RexInputRef.of(empRow.getFieldCount() +
deptRow.getFieldNames().indexOf("EMPNO"),
+ empDeptJoinRelFields);
+
+ final RexNode leftDeptNo =
+ RexInputRef.of(empScan.getRowType().getFieldNames().indexOf("DEPTNO"),
+ empDeptJoinRelFields);
+ final RexNode rightDeptNo =
+ RexInputRef.of(empRow.getFieldCount() +
deptRow.getFieldNames().indexOf("DEPTNO"),
+ empDeptJoinRelFields);
+
+ // OR(
+ // AND(IS NULL(NULLIF($0, $7)), IS NULL(NULLIF($7, $8))),
+ // IS TRUE(=(NULLIF($0, $7), NULLIF($7, $8)))
+ // )
+ RexNode expanded =
+ relBuilder.isNotDistinctFrom(
+ relBuilder.call(SqlStdOperatorTable.NULLIF, leftEmpNo, rightEmpNo),
+ relBuilder.call(SqlStdOperatorTable.NULLIF, leftDeptNo,
rightDeptNo));
+
+ // IS NOT DISTINCT FROM(NULLIF($0, $7), NULLIF($7, $8))
+ RexNode collapsed =
+ RelOptUtil.collapseExpandedIsNotDistinctFromExpr((RexCall) expanded,
rexBuilder);
+
+ RexNode expected =
+ rexBuilder.makeCall(
+ SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+ rexBuilder.makeCall(SqlStdOperatorTable.NULLIF, leftEmpNo,
rightEmpNo),
+ rexBuilder.makeCall(SqlStdOperatorTable.NULLIF, leftDeptNo,
rightDeptNo));
+
+ assertThat(collapsed, is(expected));
+ }
+
/**
* Tests {@link
RelOptUtil#pushDownJoinConditions(org.apache.calcite.rel.core.Join, RelBuilder)}
* where the join condition contains a complex expression.