Repository: calcite
Updated Branches:
  refs/heads/master 999021115 -> f55d10c14


[CALCITE-864] Correlation variable has incorrect row type if it is populated by 
right side of a Join

Also fixes [CALCITE-559], apparently a duplicate issue.


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/d80e26c0
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/d80e26c0
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/d80e26c0

Branch: refs/heads/master
Commit: d80e26c016babcb250e41b0abe94b8b0cfadbbe7
Parents: 9990211
Author: Julian Hyde <[email protected]>
Authored: Fri Jan 22 18:00:40 2016 -0800
Committer: Julian Hyde <[email protected]>
Committed: Tue Jan 26 11:53:24 2016 -0800

----------------------------------------------------------------------
 .../calcite/rel/type/RelDataTypeFactory.java    | 13 ++++
 .../calcite/sql/validate/SqlQualified.java      |  4 ++
 .../apache/calcite/sql2rel/RelDecorrelator.java |  7 +--
 .../calcite/sql2rel/SqlToRelConverter.java      | 64 ++++++++++++++-----
 .../java/org/apache/calcite/test/JdbcTest.java  | 22 ++++---
 .../calcite/test/SqlToRelConverterTest.java     | 15 +++++
 .../calcite/test/SqlToRelConverterTest.xml      | 27 +++++++-
 core/src/test/resources/sql/subquery.iq         | 66 ++++++++++++++++++++
 8 files changed, 186 insertions(+), 32 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/d80e26c0/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
index 35272c8..ab9b5bb 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
@@ -19,6 +19,7 @@ package org.apache.calcite.rel.type;
 import org.apache.calcite.sql.SqlCollation;
 import org.apache.calcite.sql.SqlIntervalQualifier;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.validate.SqlValidatorUtil;
 
 import java.nio.charset.Charset;
 import java.util.ArrayList;
@@ -418,6 +419,18 @@ public interface RelDataTypeFactory {
     }
 
     /**
+     * Makes sure that field names are unique.
+     */
+    public FieldInfoBuilder uniquify() {
+      final List<String> uniqueNames = SqlValidatorUtil.uniquify(names);
+      if (uniqueNames != names) {
+        names.clear();
+        names.addAll(uniqueNames);
+      }
+      return this;
+    }
+
+    /**
      * Creates a struct type with the current contents of this builder.
      */
     public RelDataType build() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/d80e26c0/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
index 4b99845..067c324 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
@@ -47,6 +47,10 @@ public class SqlQualified {
     this.identifier = identifier;
   }
 
+  @Override public String toString() {
+    return "{id: " + identifier.toString() + ", prefix: " + prefixLength + "}";
+  }
+
   public static SqlQualified create(SqlValidatorScope scope, int prefixLength,
       SqlValidatorNamespace namespace, SqlIdentifier identifier) {
     return new SqlQualified(scope, prefixLength, namespace, identifier);

http://git-wip-us.apache.org/repos/asf/calcite/blob/d80e26c0/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java 
b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
index ba196db..099bb9a 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -35,7 +35,6 @@ import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.core.Correlate;
 import org.apache.calcite.rel.core.CorrelationId;
-import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.RelFactories;
@@ -806,11 +805,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
 
   private RelNode getCorRel(Correlation corVar) {
     final RelNode r = cm.mapCorVarToCorRel.get(corVar.corr);
-    RelNode r2 = r.getInput(0);
-    if (r2 instanceof Join) {
-      r2 = r2.getInput(0);
-    }
-    return r2;
+    return r.getInput(0);
   }
 
   private void decorrelateInputWithValueGenerator(RelNode rel) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/d80e26c0/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
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 d9189a5..a85c79a 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -160,6 +160,7 @@ import org.apache.calcite.util.trace.CalciteTrace;
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -2246,8 +2247,8 @@ public class SqlToRelConverter {
         }
       }
 
-      RelDataTypeField field =
-          catalogReader.field(foundNs.getRowType(), originalFieldName);
+      final RelDataTypeField field = foundNs.getRowType().getFieldList()
+          .get(fieldAccess.getField().getIndex() - namespaceOffset);
       int pos = namespaceOffset + field.getIndex();
 
       assert field.getType()
@@ -3275,21 +3276,26 @@ public class SqlToRelConverter {
     } else {
       qualified = SqlQualified.create(null, 1, null, identifier);
     }
-    final RexNode e0 = bb.lookupExp(qualified);
-    RexNode e = e0;
+    final Pair<RexNode, Map<String, Integer>> e0 = bb.lookupExp(qualified);
+    RexNode e = e0.left;
     for (String name : qualified.suffixTranslated()) {
-      final boolean caseSensitive = true; // name already fully-qualified
-      e = rexBuilder.makeFieldAccess(e, name, caseSensitive);
+      if (e == e0.left && e0.right != null) {
+        int i = e0.right.get(name);
+        e = rexBuilder.makeFieldAccess(e, i);
+      } else {
+        final boolean caseSensitive = true; // name already fully-qualified
+        e = rexBuilder.makeFieldAccess(e, name, caseSensitive);
+      }
     }
     if (e instanceof RexInputRef) {
       // adjust the type to account for nulls introduced by outer joins
       e = adjustInputRef(bb, (RexInputRef) e);
     }
 
-    if (e0 instanceof RexCorrelVariable) {
+    if (e0.left instanceof RexCorrelVariable) {
       assert e instanceof RexFieldAccess;
       final RexNode prev =
-          bb.mapCorrelateToRex.put(((RexCorrelVariable) e0).id,
+          bb.mapCorrelateToRex.put(((RexCorrelVariable) e0.left).id,
               (RexFieldAccess) e);
       assert prev == null;
     }
@@ -3891,14 +3897,14 @@ public class SqlToRelConverter {
      * @return a {@link RexFieldAccess} or {@link RexRangeRef}, or null if
      * not found
      */
-    RexNode lookupExp(SqlQualified qualified) {
+    Pair<RexNode, Map<String, Integer>> lookupExp(SqlQualified qualified) {
       if (nameToNodeMap != null && qualified.prefixLength == 1) {
         RexNode node = nameToNodeMap.get(qualified.identifier.names.get(0));
         if (node == null) {
           throw Util.newInternal("Unknown identifier '" + qualified.identifier
               + "' encountered while expanding expression");
         }
-        return node;
+        return Pair.of(node, null);
       }
       int[] offsets = {-1};
       final SqlValidatorScope[] ancestorScopes = {null};
@@ -3917,7 +3923,12 @@ public class SqlToRelConverter {
         int offset = offsets[0];
         final LookupContext rels =
             new LookupContext(this, inputs, systemFieldList.size());
-        return lookup(offset, rels);
+        final RexNode node = lookup(offset, rels);
+        if (node == null) {
+          return null;
+        } else {
+          return Pair.of(node, null);
+        }
       } else {
         // We're referencing a relational expression which has not been
         // converted yet. This occurs when from items are correlated,
@@ -3926,10 +3937,33 @@ public class SqlToRelConverter {
         assert isParent;
         DeferredLookup lookup =
             new DeferredLookup(this, qualified.identifier.names.get(0));
-        final CorrelationId correlName = cluster.createCorrel();
-        mapCorrelToDeferred.put(correlName, lookup);
-        final RelDataType rowType = foundNs.getRowType();
-        return rexBuilder.makeCorrel(rowType, correlName);
+        final CorrelationId correlId = cluster.createCorrel();
+        mapCorrelToDeferred.put(correlId, lookup);
+        if (offsets[0] < 0) {
+          return Pair.of(rexBuilder.makeCorrel(foundNs.getRowType(), correlId),
+              null);
+        } else {
+          final RelDataTypeFactory.FieldInfoBuilder builder =
+              typeFactory.builder();
+          final ListScope ancestorScope1 = (ListScope) ancestorScopes[0];
+          final ImmutableMap.Builder<String, Integer> fields =
+              ImmutableMap.builder();
+          int i = 0;
+          int offset = 0;
+          for (SqlValidatorNamespace c : ancestorScope1.getChildren()) {
+            builder.addAll(c.getRowType().getFieldList());
+            if (i == offsets[0]) {
+              for (RelDataTypeField field : c.getRowType().getFieldList()) {
+                fields.put(field.getName(), field.getIndex() + offset);
+              }
+            }
+            ++i;
+            offset += c.getRowType().getFieldCount();
+          }
+          final RexNode c =
+              rexBuilder.makeCorrel(builder.uniquify().build(), correlId);
+          return Pair.<RexNode, Map<String, Integer>>of(c, fields.build());
+        }
       }
     }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/d80e26c0/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java 
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 0e10877..0cc70ba 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -4581,19 +4581,21 @@ public class JdbcTest {
     }
   }
 
-  @Ignore("CALCITE-559 Correlated subquery will hit exception in Calcite")
-  @Test public void testJoinCorreScalarSubQ()
-      throws ClassNotFoundException, SQLException {
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-559";>[CALCITE-559]
+   * Correlated scalar subquery in WHERE gives error</a>. */
+  @Test public void testJoinCorrelatedScalarSubquery() throws SQLException {
+    final String sql = "select e.employee_id, d.department_id "
+        + " from employee e, department d "
+        + " where e.department_id = d.department_id "
+        + " and e.salary > (select avg(e2.salary) "
+        + "                 from employee e2 "
+        + "                 where e2.store_id = e.store_id)";
     CalciteAssert.that()
         .with(CalciteAssert.Config.FOODMART_CLONE)
         .with(Lex.JAVA)
-        .query("select e.employee_id, d.department_id "
-                + " from employee e, department d "
-                + " where e.department_id = d.department_id and "
-                + "       e.salary > (select avg(e2.salary) "
-                + "                       from employee e2 "
-                + " where e2.store_id = e.store_id)")
-        .returnsCount(0);
+        .query(sql)
+        .returnsCount(599);
   }
 
   @Test public void testLeftJoin() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/d80e26c0/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
----------------------------------------------------------------------
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 2e18dc7..f3cdf68 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -834,6 +834,21 @@ public class SqlToRelConverterTest extends 
SqlToRelTestBase {
     sql(sql).expand(false).convertsTo("${plan}");
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-864";>[CALCITE-864]
+   * Correlation variable has incorrect row type if it is populated by right
+   * side of a Join</a>. */
+  @Test public void testCorrelatedSubQueryInJoin() {
+    final String sql = "select *\n"
+        + "from emp as e\n"
+        + "join dept as d using (deptno)\n"
+        + "where d.name = (\n"
+        + "  select max(name)\n"
+        + "  from dept as d2\n"
+        + "  where d2.deptno = d.deptno)";
+    sql(sql).expand(false).convertsTo("${plan}");
+  }
+
   @Test public void testExists() {
     check(
         "select*from emp where exists (select 1 from dept where deptno=55)",

http://git-wip-us.apache.org/repos/asf/calcite/blob/d80e26c0/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
----------------------------------------------------------------------
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 a02eb60..84e1afe 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -117,6 +117,31 @@ LogicalAggregate(group=[{0}], EXPR$1=[SUM($1)], 
EXPR$2=[SUM(DISTINCT $1)], EXPR$
             <![CDATA[select deptno, sum(sal), sum(distinct sal), count(*) from 
emp group by deptno]]>
         </Resource>
     </TestCase>
+    <TestCase name="testCorrelatedSubQueryInJoin">
+        <Resource name="sql">
+            <![CDATA[select *
+from emp as e
+join dept as d using (deptno)
+where d.name = (
+  select max(name)
+  from dept as d2
+  where d2.deptno = d.deptno)]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[$9], NAME=[$10])
+  LogicalFilter(condition=[=($10, $SCALAR_QUERY({
+LogicalAggregate(group=[{}], EXPR$0=[MAX($0)])
+  LogicalProject(NAME=[$1])
+    LogicalFilter(condition=[=($0, $cor0.DEPTNO0)])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+}))], variablesSet=[[$cor0]])
+    LogicalJoin(condition=[=($7, $9)], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
     <TestCase name="testUnnest">
         <Resource name="plan">
             <![CDATA[
@@ -2859,7 +2884,7 @@ or exists (select deptno from emp where empno > 
dept.deptno + 5)]]>
             <![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=[OR(=($0, 1), EXISTS({
-LogicalFilter(condition=[>($0, +($cor0.DEPTNO, 5))])
+LogicalFilter(condition=[>($0, +($cor0.DEPTNO0, 5))])
   LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 }))], joinType=[left])
     LogicalTableScan(table=[[CATALOG, SALES, EMP]])

http://git-wip-us.apache.org/repos/asf/calcite/blob/d80e26c0/core/src/test/resources/sql/subquery.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/subquery.iq 
b/core/src/test/resources/sql/subquery.iq
index cfeb6e5..8f0eb70 100644
--- a/core/src/test/resources/sql/subquery.iq
+++ b/core/src/test/resources/sql/subquery.iq
@@ -319,4 +319,70 @@ where e.job not in (
 !plan
 !}
 
+# [CALCITE-864] Correlation variable has incorrect row type if it is populated
+# by right side of a Join
+select *
+from "scott".emp as e
+join "scott".dept as d using (deptno)
+where sal = (
+  select max(sal)
+  from "scott".emp as e2
+  join "scott".dept as d2 using (deptno)
+  where d2.deptno = d.deptno);
+ EMPNO | ENAME | JOB       | MGR  | HIREDATE   | SAL     | COMM | DEPTNO | 
DEPTNO0 | DNAME      | LOC
+-------+-------+-----------+------+------------+---------+------+--------+---------+------------+----------
+  7698 | BLAKE | MANAGER   | 7839 | 1981-01-05 | 2850.00 |      |     30 |     
 30 | SALES      | CHICAGO
+  7788 | SCOTT | ANALYST   | 7566 | 1987-04-19 | 3000.00 |      |     20 |     
 20 | RESEARCH   | DALLAS
+  7839 | KING  | PRESIDENT |      | 1981-11-17 | 5000.00 |      |     10 |     
 10 | ACCOUNTING | NEW YORK
+  7902 | FORD  | ANALYST   | 7566 | 1981-12-03 | 3000.00 |      |     20 |     
 20 | RESEARCH   | DALLAS
+(4 rows)
+
+!ok
+
+# Simpler test case for [CALCITE-864]
+select empno, ename, sal, e.deptno, loc
+from "scott".emp as e
+join "scott".dept as d using (deptno)
+where e.sal = (
+  select max(sal)
+  from "scott".emp as e2
+  where e2.deptno = e.deptno);
+ EMPNO | ENAME | SAL     | DEPTNO | LOC
+-------+-------+---------+--------+----------
+  7698 | BLAKE | 2850.00 |     30 | CHICAGO
+  7788 | SCOTT | 3000.00 |     20 | DALLAS
+  7839 | KING  | 5000.00 |     10 | NEW YORK
+  7902 | FORD  | 3000.00 |     20 | DALLAS
+(4 rows)
+
+!ok
+
+# Simpler test case for [CALCITE-864]
+select *
+from "scott".emp as e
+join "scott".dept as d using (deptno)
+where d.dname = (
+  select max(dname)
+  from "scott".dept as d2
+  where d2.deptno = d.deptno);
+ EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO | 
DEPTNO0 | DNAME      | LOC
+-------+--------+-----------+------+------------+---------+---------+--------+---------+------------+----------
+  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |         |     20 | 
     20 | RESEARCH   | DALLAS
+  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 | 
     30 | SALES      | CHICAGO
+  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 | 
     30 | SALES      | CHICAGO
+  7566 | JONES  | MANAGER   | 7839 | 1981-02-04 | 2975.00 |         |     20 | 
     20 | RESEARCH   | DALLAS
+  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 | 
     30 | SALES      | CHICAGO
+  7698 | BLAKE  | MANAGER   | 7839 | 1981-01-05 | 2850.00 |         |     30 | 
     30 | SALES      | CHICAGO
+  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |         |     10 | 
     10 | ACCOUNTING | NEW YORK
+  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |         |     20 | 
     20 | RESEARCH   | DALLAS
+  7839 | KING   | PRESIDENT |      | 1981-11-17 | 5000.00 |         |     10 | 
     10 | ACCOUNTING | NEW YORK
+  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 | 
     30 | SALES      | CHICAGO
+  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |         |     20 | 
     20 | RESEARCH   | DALLAS
+  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |         |     30 | 
     30 | SALES      | CHICAGO
+  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |         |     20 | 
     20 | RESEARCH   | DALLAS
+  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |         |     10 | 
     10 | ACCOUNTING | NEW YORK
+(14 rows)
+
+!ok
+
 # End subquery.iq

Reply via email to