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

xiong 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 86b34f7ba8 [CALCITE-5138] Join on condition generates wrong plan when 
the condition is sub-query
86b34f7ba8 is described below

commit 86b34f7ba81cc85aec4589f8fa4dcbf503ad8677
Author: NobiGo <[email protected]>
AuthorDate: Sun May 8 10:45:01 2022 +0800

    [CALCITE-5138] Join on condition generates wrong plan when the condition is 
sub-query
---
 .../calcite/sql/validate/SqlValidatorImpl.java     |  13 +-
 .../apache/calcite/sql2rel/SqlToRelConverter.java  |  23 +++-
 .../apache/calcite/test/SqlToRelConverterTest.java |   9 ++
 .../apache/calcite/test/SqlToRelConverterTest.xml  |  21 +++
 core/src/test/resources/sql/conditions.iq          | 153 +++++++++++++++++++++
 5 files changed, 216 insertions(+), 3 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index 01c13c94c0..2978f1a700 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -4341,11 +4341,22 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     condition.validate(this, scope);
 
     final RelDataType type = deriveType(scope, condition);
-    if (!SqlTypeUtil.inBooleanFamily(type)) {
+    if (!isReturnBooleanType(type)) {
       throw newValidationError(condition, RESOURCE.condMustBeBoolean(clause));
     }
   }
 
+  private boolean isReturnBooleanType(RelDataType relDataType) {
+    if (relDataType instanceof RelRecordType) {
+      RelRecordType recordType = (RelRecordType) relDataType;
+      Preconditions.checkState(recordType.getFieldList().size() == 1,
+          "sub-query as condition must return only one column");
+      RelDataTypeField recordField = recordType.getFieldList().get(0);
+      return SqlTypeUtil.inBooleanFamily(recordField.getType());
+    }
+    return SqlTypeUtil.inBooleanFamily(relDataType);
+  }
+
   protected void validateHavingClause(SqlSelect select) {
     // HAVING is validated in the scope after groups have been created.
     // For example, in "SELECT empno FROM emp WHERE empno = 10 GROUP BY
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java 
b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index c22ecc5a2d..91d223c62b 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -3049,7 +3049,9 @@ public class SqlToRelConverter {
         rightRel,
         condition,
         convertJoinType(join.getJoinType()));
-    bb.setRoot(joinRel, false);
+    relBuilder.push(joinRel);
+    final RelNode newProjectRel = 
relBuilder.project(relBuilder.fields()).build();
+    bb.setRoot(newProjectRel, false);
   }
 
   private RexNode convertNaturalCondition(
@@ -3113,6 +3115,13 @@ public class SqlToRelConverter {
         : bb.reRegister(rightRel);
     bb.setRoot(ImmutableList.of(leftRel, newRightRel));
     RexNode conditionExp =  bb.convertExpression(condition);
+    if (conditionExp instanceof RexInputRef && newRightRel != rightRel) {
+      int leftFieldCount = leftRel.getRowType().getFieldCount();
+      List<RelDataTypeField> rightFieldList = 
newRightRel.getRowType().getFieldList();
+      int rightFieldCount = newRightRel.getRowType().getFieldCount();
+      conditionExp = 
rexBuilder.makeInputRef(rightFieldList.get(rightFieldCount - 1).getType(),
+          leftFieldCount + rightFieldCount - 1);
+    }
     return Pair.of(conditionExp, newRightRel);
   }
 
@@ -4763,7 +4772,17 @@ public class SqlToRelConverter {
       List<RegisterArgs> registerCopy = registered;
       registered = new ArrayList<>();
       for (RegisterArgs reg: registerCopy) {
-        register(reg.rel, reg.joinType, reg.leftKeys);
+        RelNode relNode = reg.rel;
+        relBuilder.push(relNode);
+        final RelMetadataQuery mq = relBuilder.getCluster().getMetadataQuery();
+        final Boolean unique = mq.areColumnsUnique(relBuilder.peek(),
+            ImmutableBitSet.of());
+        if (unique == null || !unique) {
+          relBuilder.aggregate(relBuilder.groupKey(),
+              relBuilder.aggregateCall(SqlStdOperatorTable.SINGLE_VALUE,
+                  relBuilder.field(0)));
+        }
+        register(relBuilder.build(), reg.joinType, reg.leftKeys);
       }
       return requireNonNull(this.root, "root");
     }
diff --git 
a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index a7e974fa9a..880846127c 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -4419,6 +4419,15 @@ class SqlToRelConverterTest extends SqlToRelTestBase {
     sql(sql).withTrim(true).ok();
   }
 
+  @Test void testJoinWithOnConditionQuery() {
+    String sql = ""
+        + "SELECT emp.deptno, emp.sal\n"
+        + "FROM dept\n"
+        + "JOIN emp\n"
+        + "ON (SELECT AVG(emp.sal) > 0 FROM emp)";
+    sql(sql).ok();
+  }
+
   @Test void testJoinExpandAndDecorrelation() {
     String sql = ""
         + "SELECT emp.deptno, emp.sal\n"
diff --git 
a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml 
b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 407f03ab4f..3cf56aa333 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -3400,6 +3400,27 @@ LogicalProject(EXPR$0=[COALESCE(ROW($4, $5, $6), 
ROW($14, $15, $16)).X])
       LogicalTableScan(table=[[CATALOG, CUSTOMER, CONTACT_PEEK]])
     LogicalProject(CONTACTNO=[$0], FNAME=[$1], LNAME=[$2], EMAIL=[$3], 
X=[$4.X], Y=[$4.Y], unit=[$4.unit], M=[$5.M], A=[$5.SUB.A], B=[$5.SUB.B])
       LogicalTableScan(table=[[CATALOG, CUSTOMER, CONTACT_PEEK]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testJoinWithOnConditionQuery">
+    <Resource name="sql">
+      <![CDATA[SELECT emp.deptno, emp.sal
+FROM dept
+         LEFT JOIN emp ON (SELECT AVG(emp.sal) > 0 FROM emp)]]>
+    </Resource>
+    <Resource name="plan">
+      <![CDATA[
+LogicalProject(DEPTNO=[$9], SAL=[$7])
+  LogicalJoin(condition=[$11], joinType=[inner])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalJoin(condition=[true], joinType=[left])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalAggregate(group=[{}], agg#0=[SINGLE_VALUE($0)])
+        LogicalProject(EXPR$0=[>($0, 0)])
+          LogicalAggregate(group=[{}], agg#0=[AVG($0)])
+            LogicalProject(SAL=[$5])
+              LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
   </TestCase>
diff --git a/core/src/test/resources/sql/conditions.iq 
b/core/src/test/resources/sql/conditions.iq
index f83ea1e1d7..96a6bf4ebe 100644
--- a/core/src/test/resources/sql/conditions.iq
+++ b/core/src/test/resources/sql/conditions.iq
@@ -357,4 +357,157 @@ EnumerableCalc(expr#0..7=[{inputs}], 
expr#8=[CAST($t7):INTEGER], expr#9=[25], ex
   EnumerableTableScan(table=[[scott, EMP]])
 !plan
 
+# [CALCITE-5138] Join on condition generates wrong plan when the condition is 
sub-query
+
+# Bad: more than one value returned by sub-query
+select empno
+from emp as r
+         left join dept as s on (select empno > 0 from emp);
+more than one value in agg SINGLE_VALUE
+!error
+
+# Bad: more than one value returned by sub-query
+select empno
+from emp as r
+         left join dept as s on (select true from emp);
+more than one value in agg SINGLE_VALUE
+!error
+
+# Bad: more than one column returned by sub-query
+select empno
+from emp as r
+         left join dept as s on (select true,true from emp);
+sub-query as condition must return only one column
+!error
+
+# Bad: column returned by sub-query is not a conditon
+select empno
+from emp as r
+         left join dept as s on (select empno from emp);
+ON clause must be a condition
+!error
+
+# sub-query return true
+select empno
+from emp as r
+         left join dept as s on (select count(true) > 0 from emp)
+where empno = 7369;
++-------+
+| EMPNO |
++-------+
+|  7369 |
+|  7369 |
+|  7369 |
+|  7369 |
++-------+
+(4 rows)
+
+!ok
+
+EnumerableCalc(expr#0..2=[{inputs}], EMPNO=[$t0])
+  EnumerableNestedLoopJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], expr#8=[CAST($t0):INTEGER NOT NULL], 
expr#9=[7369], expr#10=[=($t8, $t9)], EMPNO=[$t0], $condition=[$t10])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableCalc(expr#0..1=[{inputs}], proj#0..1=[{exprs}], $condition=[$t1])
+      EnumerableNestedLoopJoin(condition=[true], joinType=[left])
+        EnumerableCalc(expr#0..2=[{inputs}], DEPTNO=[$t0])
+          EnumerableTableScan(table=[[scott, DEPT]])
+        EnumerableAggregate(group=[{}], agg#0=[SINGLE_VALUE($0)])
+          EnumerableCalc(expr#0=[{inputs}], expr#1=[0], expr#2=[>($t0, $t1)], 
EXPR$0=[$t2])
+            EnumerableAggregate(group=[{}], agg#0=[COUNT()])
+              EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# sub-query return true with Equal condition
+select r.empno, s.deptno
+from emp as r
+         left join dept as s on (select count(true) > 0 from emp) and s.deptno 
= r.deptno;
+
++-------+--------+
+| EMPNO | DEPTNO |
++-------+--------+
+|  7369 |     20 |
+|  7499 |     30 |
+|  7521 |     30 |
+|  7566 |     20 |
+|  7654 |     30 |
+|  7698 |     30 |
+|  7782 |     10 |
+|  7788 |     20 |
+|  7839 |     10 |
+|  7844 |     30 |
+|  7876 |     20 |
+|  7900 |     30 |
+|  7902 |     20 |
+|  7934 |     10 |
++-------+--------+
+(14 rows)
+
+!ok
+
+# sub-query return false
+select r.empno, s.deptno
+from emp as r
+         left join dept as s on (select count(job) > 0 from bonus);
+
++-------+--------+
+| EMPNO | DEPTNO |
++-------+--------+
+|  7369 |        |
+|  7499 |        |
+|  7521 |        |
+|  7566 |        |
+|  7654 |        |
+|  7698 |        |
+|  7782 |        |
+|  7788 |        |
+|  7839 |        |
+|  7844 |        |
+|  7876 |        |
+|  7900 |        |
+|  7902 |        |
+|  7934 |        |
++-------+--------+
+(14 rows)
+
+!ok
+
+# ON condition is a column
+select *
+from dept
+         LEFT JOIN (SELECT COUNT(*) > 0 AS "EXPR$0"
+                    FROM emp) AS "t0" ON "EXPR$0";
++--------+------------+----------+--------+
+| DEPTNO | DNAME      | LOC      | EXPR$0 |
++--------+------------+----------+--------+
+|     10 | ACCOUNTING | NEW YORK | true   |
+|     20 | RESEARCH   | DALLAS   | true   |
+|     30 | SALES      | CHICAGO  | true   |
+|     40 | OPERATIONS | BOSTON   | true   |
++--------+------------+----------+--------+
+(4 rows)
+
+!ok
+
+# As above, but complicated
+SELECT emp.empno
+FROM emp
+         LEFT JOIN (select *
+                    from dept
+                             LEFT JOIN (SELECT COUNT(*) > 0 AS "EXPR$0"
+                                        FROM emp) AS "t0" ON TRUE) as "dt0*" 
ON "EXPR$0"
+where emp.empno = 7902;
++-------+
+| EMPNO |
++-------+
+|  7902 |
+|  7902 |
+|  7902 |
+|  7902 |
++-------+
+(4 rows)
+
+!ok
+
+
 # End conditions.iq

Reply via email to