This is an automated email from the ASF dual-hosted git repository.

chunwei 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 300f5200fe [CALCITE-5195] ArrayIndexOutOfBoundsException when 
inferring more equal conditions from join condition for semi join
300f5200fe is described below

commit 300f5200fe50870da418366613df1cd09921b1b6
Author: chunwei.lcw <[email protected]>
AuthorDate: Wed Jun 22 11:41:13 2022 +0800

    [CALCITE-5195] ArrayIndexOutOfBoundsException when inferring more equal 
conditions from join condition for semi join
---
 .../apache/calcite/rel/rules/FilterJoinRule.java   | 53 ++++++-------
 .../org/apache/calcite/test/RelOptRulesTest.java   | 82 +++++++++++++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 86 ++++++++++++++++++++++
 3 files changed, 196 insertions(+), 25 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
index 0df08263a2..e699a9e1ee 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
@@ -138,7 +138,9 @@ public abstract class FilterJoinRule<C extends 
FilterJoinRule.Config>
       }
     }
 
-    joinFilters = inferJoinEqualConditions(joinFilters, join);
+    if (joinType != JoinRelType.FULL) {
+      joinFilters = inferJoinEqualConditions(joinFilters, join);
+    }
 
     // Try to push down filters in ON clause. A ON clause filter can only be
     // pushed down if it does not affect the non-matching set, i.e. it is
@@ -249,10 +251,10 @@ public abstract class FilterJoinRule<C extends 
FilterJoinRule.Config>
    */
   protected List<RexNode> inferJoinEqualConditions(List<RexNode> rexNodes, 
Join join) {
     final List<RexNode> result = new ArrayList<>(rexNodes.size());
-    final List<Set<Integer>> equalSets = splitEqualSets(rexNodes, result);
+    final List<Set<RexInputRef>> equalSets = splitEqualSets(rexNodes, result);
 
     boolean needOptimize = false;
-    for (Set<Integer> set : equalSets) {
+    for (Set<RexInputRef> set : equalSets) {
       if (set.size() > 2) {
         needOptimize = true;
         break;
@@ -274,8 +276,8 @@ public abstract class FilterJoinRule<C extends 
FilterJoinRule.Config>
    * @param leftNodes where the conditions not feasible for equal sets are put
    * @return the equal sets
    */
-  private List<Set<Integer>> splitEqualSets(List<RexNode> rexNodes, 
List<RexNode> leftNodes) {
-    final List<Set<Integer>> equalSets = new ArrayList<>();
+  private List<Set<RexInputRef>> splitEqualSets(List<RexNode> rexNodes, 
List<RexNode> leftNodes) {
+    final List<Set<RexInputRef>> equalSets = new ArrayList<>();
     for (RexNode rexNode : rexNodes) {
       if (rexNode.isA(SqlKind.EQUALS)) {
         final RexNode op1 = ((RexCall) rexNode).getOperands().get(0);
@@ -283,9 +285,9 @@ public abstract class FilterJoinRule<C extends 
FilterJoinRule.Config>
         if (op1 instanceof RexInputRef && op2 instanceof RexInputRef) {
           final RexInputRef in1 = (RexInputRef) op1;
           final RexInputRef in2 = (RexInputRef) op2;
-          Set<Integer> set = null;
-          for (Set<Integer> s : equalSets) {
-            if (s.contains(in1.getIndex()) || s.contains(in2.getIndex())) {
+          Set<RexInputRef> set = null;
+          for (Set<RexInputRef> s : equalSets) {
+            if (s.contains(in1) || s.contains(in2)) {
               set = s;
               break;
             }
@@ -295,8 +297,8 @@ public abstract class FilterJoinRule<C extends 
FilterJoinRule.Config>
             set = new LinkedHashSet<>();
             equalSets.add(set);
           }
-          set.add(in1.getIndex());
-          set.add(in2.getIndex());
+          set.add(in1);
+          set.add(in2);
         } else {
           leftNodes.add(rexNode);
         }
@@ -315,18 +317,19 @@ public abstract class FilterJoinRule<C extends 
FilterJoinRule.Config>
    * @param equalSets the equal sets
    * @return the newly constructed conditions from equal sets
    */
-  private List<RexNode> constructConditionFromEqualSets(Join join, 
List<Set<Integer>> equalSets) {
+  private List<RexNode> constructConditionFromEqualSets(Join join,
+      List<Set<RexInputRef>> equalSets) {
     final RexBuilder rexBuilder = join.getCluster().getRexBuilder();
     final List<RexNode> result = new ArrayList<>();
-    final int leftRowCount = join.getLeft().getRowType().getFieldCount();
-    for (Set<Integer> set : equalSets) {
-      final List<Integer> leftSet = new ArrayList<>();
-      final List<Integer> rightSet = new ArrayList<>();
-      for (int i : set) {
-        if (i < leftRowCount) {
-          leftSet.add(i);
+    final int leftFieldCount = join.getLeft().getRowType().getFieldCount();
+    for (Set<RexInputRef> set : equalSets) {
+      final List<RexInputRef> leftSet = new ArrayList<>();
+      final List<RexInputRef> rightSet = new ArrayList<>();
+      for (RexInputRef ref : set) {
+        if (ref.getIndex() < leftFieldCount) {
+          leftSet.add(ref);
         } else {
-          rightSet.add(i);
+          rightSet.add(ref);
         }
       }
       // Add left side conditions.
@@ -334,8 +337,8 @@ public abstract class FilterJoinRule<C extends 
FilterJoinRule.Config>
         for (int i = 1; i < leftSet.size(); ++i) {
           result.add(
               rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
-                  rexBuilder.makeInputRef(join, leftSet.get(0)),
-                  rexBuilder.makeInputRef(join, leftSet.get(i))));
+                  leftSet.get(0),
+                  leftSet.get(i)));
         }
       }
       // Add right side conditions.
@@ -343,16 +346,16 @@ public abstract class FilterJoinRule<C extends 
FilterJoinRule.Config>
         for (int i = 1; i < rightSet.size(); ++i) {
           result.add(
               rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
-                  rexBuilder.makeInputRef(join, rightSet.get(0)),
-                  rexBuilder.makeInputRef(join, rightSet.get(i))));
+                  rightSet.get(0),
+                  rightSet.get(i)));
         }
       }
       // Only need one equal condition for each equal set.
       if (leftSet.size() > 0 && rightSet.size() > 0) {
         result.add(
             rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
-                rexBuilder.makeInputRef(join, leftSet.get(0)),
-                rexBuilder.makeInputRef(join, rightSet.get(0))));
+                leftSet.get(0),
+                rightSet.get(0)));
       }
     }
 
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 127662f4a7..b305f8c930 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -2216,6 +2216,88 @@ class RelOptRulesTest extends RelOptTestBase {
         .check();
   }
 
+  /** Similar to {@link #testJoinConditionPushdown1()} but full join
+   * from which more equality conditions can not be inferred. */
+  @Test void testJoinConditionPushdown3() {
+    final String sql = "select *\n"
+        + "from emp e full join dept d\n"
+        + "on e.deptno = d.deptno and e.empno = d.deptno";
+    sql(sql)
+        .withRule(CoreRules.FILTER_INTO_JOIN,
+            CoreRules.JOIN_CONDITION_PUSH,
+            CoreRules.PROJECT_MERGE,
+            CoreRules.FILTER_PROJECT_TRANSPOSE)
+        .checkUnchanged();
+  }
+
+  /** Similar to {@link #testJoinConditionPushdown1()} but semi join
+   * from which more equality conditions can be inferred. */
+  @Test void testJoinConditionPushdown4() {
+    final Function<RelBuilder, RelNode> relFn = b -> {
+      RelNode left = b.scan("EMP")
+          .project(
+              b.field("DEPTNO"),
+              b.field("ENAME"))
+          .build();
+      RelNode right = b.scan("DEPT")
+          .project(
+              b.field("DEPTNO"),
+              b.field("DNAME"))
+          .build();
+
+      b.push(left).push(right);
+
+      RexInputRef ref1 = b.field(2, 0, "DEPTNO");
+      RexInputRef ref2 = b.field(2, 1, "DEPTNO");
+      RexInputRef ref3 = b.field(2, 1, "DNAME");
+
+      RexCall cond1 = (RexCall) b.equals(ref1, ref2);
+      RexCall cond2 = (RexCall) b.equals(ref1, ref3);
+
+      RexNode cond = b.and(cond1, cond2);
+      return b.semiJoin(cond)
+          .project(b.field(0))
+          .build();
+    };
+
+    relFn(relFn)
+        .withRule(
+            CoreRules.JOIN_PUSH_EXPRESSIONS,
+            CoreRules.JOIN_CONDITION_PUSH,
+            CoreRules.SEMI_JOIN_PROJECT_TRANSPOSE,
+            CoreRules.JOIN_REDUCE_EXPRESSIONS,
+            CoreRules.FILTER_REDUCE_EXPRESSIONS)
+        .check();
+  }
+
+  /** Similar to {@link #testJoinConditionPushdown1()} but left join
+   * from which more equality conditions can not be inferred. */
+  @Test void testJoinConditionPushdown5() {
+    final String sql = "select *\n"
+        + "from emp e left join dept d\n"
+        + "on e.deptno = d.deptno and e.empno = d.deptno";
+    sql(sql)
+        .withRule(CoreRules.FILTER_INTO_JOIN,
+            CoreRules.JOIN_CONDITION_PUSH,
+            CoreRules.PROJECT_MERGE,
+            CoreRules.FILTER_PROJECT_TRANSPOSE)
+        .checkUnchanged();
+  }
+
+  /** Similar to {@link #testJoinConditionPushdown1()} but right join
+   * from which more equality conditions can be inferred. */
+  @Test void testJoinConditionPushdown6() {
+    final String sql = "select *\n"
+        + "from emp e right join dept d\n"
+        + "on e.deptno = d.deptno and e.empno = d.deptno";
+    sql(sql)
+        .withRule(CoreRules.FILTER_INTO_JOIN,
+            CoreRules.JOIN_CONDITION_PUSH,
+            CoreRules.PROJECT_MERGE,
+            CoreRules.FILTER_PROJECT_TRANSPOSE)
+        .check();
+  }
+
   /** Tests that filters are combined if they are identical. */
   @Test void testMergeFilter() {
     final String sql = "select name from (\n"
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 2e72d43700..87e5489f57 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -4537,6 +4537,92 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], 
MGR=[$3], HIREDATE=[$4], SAL=[$
     LogicalFilter(condition=[=($7, $0)])
       LogicalTableScan(table=[[CATALOG, SALES, EMP]])
     LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testJoinConditionPushdown3">
+    <Resource name="sql">
+      <![CDATA[select *
+from emp e full join dept d
+on e.deptno = d.deptno and e.empno = d.deptno
+]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[$9], NAME=[$10])
+  LogicalJoin(condition=[AND(=($7, $9), =($0, $9))], joinType=[full])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testJoinConditionPushdown4">
+    <Resource name="sql">
+      <![CDATA[select *
+from emp e semi join dept d
+on e.deptno = d.deptno and e.empno = d.deptno
+]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0])
+  LogicalJoin(condition=[AND(=($0, $2), =($0, $3))], joinType=[semi])
+    LogicalProject(DEPTNO=[$7], ENAME=[$1])
+      LogicalTableScan(table=[[scott, EMP]])
+    LogicalProject(DEPTNO=[$0], DNAME=[$1])
+      LogicalTableScan(table=[[scott, DEPT]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0])
+  LogicalProject(DEPTNO=[$7], ENAME=[$1])
+    LogicalJoin(condition=[=($7, $8)], joinType=[semi])
+      LogicalTableScan(table=[[scott, EMP]])
+      LogicalFilter(condition=[=($0, $1)])
+        LogicalProject(DEPTNO=[$0], DNAME=[$1])
+          LogicalTableScan(table=[[scott, DEPT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testJoinConditionPushdown5">
+    <Resource name="sql">
+      <![CDATA[select *
+from emp e left join dept d
+on e.deptno = d.deptno and e.empno = d.deptno
+]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[$9], NAME=[$10])
+  LogicalJoin(condition=[AND(=($7, $9), =($0, $9))], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testJoinConditionPushdown6">
+    <Resource name="sql">
+      <![CDATA[select *
+from emp e right join dept d
+on e.deptno = d.deptno and e.empno = d.deptno
+]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[$9], NAME=[$10])
+  LogicalJoin(condition=[AND(=($7, $9), =($0, $9))], joinType=[right])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[$9], NAME=[$10])
+  LogicalJoin(condition=[=($7, $9)], joinType=[right])
+    LogicalFilter(condition=[=($7, $0)])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
     </Resource>
   </TestCase>

Reply via email to