You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2022/08/24 03:44:07 UTC

[calcite] branch main updated: [CALCITE-5126] Implicit column alias for single-column UNNEST should work with any single-column UNNEST’s input

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

jhyde 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 e2f949d5d6 [CALCITE-5126] Implicit column alias for single-column UNNEST should work with any single-column UNNEST’s input
e2f949d5d6 is described below

commit e2f949d5d6cff79cbe8565bc3e85f437dee9fd7e
Author: dssysolyatin <dm...@gmail.com>
AuthorDate: Mon May 2 14:39:37 2022 +0300

    [CALCITE-5126] Implicit column alias for single-column UNNEST should work with any single-column UNNEST’s input
    
    Close apache/calcite#2789
---
 .../calcite/sql/validate/AliasNamespace.java       | 15 ++++++++
 .../calcite/sql/validate/SqlValidatorImpl.java     | 12 +++----
 .../apache/calcite/test/SqlToRelConverterTest.java |  4 +--
 .../org/apache/calcite/test/SqlValidatorTest.java  | 12 ++++++-
 .../apache/calcite/test/SqlToRelConverterTest.xml  |  2 +-
 core/src/test/resources/sql/unnest.iq              | 41 +++++++++++++++++-----
 6 files changed, 65 insertions(+), 21 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java
index be293fc9c4..f3f7b343c3 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java
@@ -19,10 +19,13 @@ package org.apache.calcite.sql.validate;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlBasicCall;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlUnnestOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.Pair;
@@ -81,6 +84,7 @@ public class AliasNamespace extends AbstractNamespace {
     final RelDataType rowType = childNs.getRowTypeSansSystemColumns();
     final RelDataType aliasedType;
     if (operands.size() == 2) {
+      final SqlNode node = operands.get(0);
       // Alias is 'AS t' (no column list).
       // If the sub-query is UNNEST or VALUES,
       // and the sub-query has one column,
@@ -91,6 +95,17 @@ public class AliasNamespace extends AbstractNamespace {
             .add(((SqlIdentifier) operands.get(1)).getSimple(),
                 rowType.getFieldList().get(0).getType())
             .build();
+        // If the sub-query is UNNEST with ordinality
+        // and the sub-query has two columns: data column, ordinality column
+        // then the namespace's sole column is named after the alias.
+      } else if (node.getKind() == SqlKind.UNNEST && rowType.getFieldCount() == 2
+          && ((SqlUnnestOperator) ((SqlBasicCall) node).getOperator()).withOrdinality) {
+        aliasedType = validator.getTypeFactory().builder()
+            .kind(rowType.getStructKind())
+            .add(((SqlIdentifier) operands.get(1)).getSimple(),
+                rowType.getFieldList().get(0).getType())
+            .add(rowType.getFieldList().get(1))
+            .build();
       } else {
         aliasedType = rowType;
       }
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 1a060dea8f..c53803e8fd 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
@@ -2333,18 +2333,14 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         alias = String.valueOf(call.operand(1));
       }
       expr = call.operand(0);
-      final boolean needAlias = call.operandCount() > 2
+      final boolean needAliasNamespace = call.operandCount() > 2
           || expr.getKind() == SqlKind.VALUES
-          || expr.getKind() == SqlKind.UNNEST
-          && (((SqlCall) expr).operand(0).getKind()
-                  == SqlKind.ARRAY_VALUE_CONSTRUCTOR
-              || ((SqlCall) expr).operand(0).getKind()
-                  == SqlKind.MULTISET_VALUE_CONSTRUCTOR);
+          || expr.getKind() == SqlKind.UNNEST;
       newExpr =
           registerFrom(
               parentScope,
               usingScope,
-              !needAlias,
+              !needAliasNamespace,
               expr,
               enclosingNode,
               alias,
@@ -2357,7 +2353,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
       // If alias has a column list, introduce a namespace to translate
       // column names. We skipped registering it just now.
-      if (needAlias) {
+      if (needAliasNamespace) {
         registerNamespace(
             usingScope,
             alias,
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 ba4cd0c5c4..add989567b 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -3640,7 +3640,7 @@ class SqlToRelConverterTest extends SqlToRelTestBase {
   @Test void testDynamicSchemaUnnest() {
     final String sql = "select t1.c_nationkey, t3.fake_col3\n"
         + "from SALES.CUSTOMER as t1,\n"
-        + "lateral (select t2.\"$unnest\" as fake_col3\n"
+        + "lateral (select t2 as fake_col3\n"
         + "         from unnest(t1.fake_col) as t2) as t3";
     sql(sql).withDynamicTable().ok();
   }
@@ -3648,7 +3648,7 @@ class SqlToRelConverterTest extends SqlToRelTestBase {
   @Test void testStarDynamicSchemaUnnest() {
     final String sql = "select *\n"
         + "from SALES.CUSTOMER as t1,\n"
-        + "lateral (select t2.\"$unnest\" as fake_col3\n"
+        + "lateral (select t2 as fake_col3\n"
         + "         from unnest(t1.fake_col) as t2) as t3";
     sql(sql).withDynamicTable().ok();
   }
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index f3fb98d97a..c8d5fe6ec6 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -8295,6 +8295,8 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
         .type("RecordType(INTEGER NOT NULL EXPR$0, INTEGER NOT NULL ORDINALITY) NOT NULL");
     sql("select*from unnest(array[43.2e1, cast(null as decimal(4,2))]) with ordinality")
         .type("RecordType(DOUBLE EXPR$0, INTEGER NOT NULL ORDINALITY) NOT NULL");
+    sql("select * from unnest(array(select deptno from dept)) with ordinality as t")
+        .type("RecordType(INTEGER NOT NULL T, INTEGER NOT NULL ORDINALITY) NOT NULL");
     sql("select*from ^unnest(1) with ordinality^")
         .fails("(?s).*Cannot apply 'UNNEST' to arguments of type 'UNNEST.<INTEGER>.'.*");
     sql("select deptno\n"
@@ -8338,6 +8340,10 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
     // relation, that alias becomes the name of the column.
     sql("select fruit.* from UNNEST(array ['apple', 'banana']) as fruit")
         .type(expectedType);
+    sql("select fruit.* from UNNEST(array(select 'banana')) as fruit")
+        .type(expectedType);
+    sql("SELECT array(SELECT y + 1 FROM UNNEST(s.x) y) FROM (SELECT ARRAY[1,2,3] as x) s")
+        .ok();
 
     // The magic doesn't happen if the query is not an UNNEST.
     // In this case, the query is a SELECT.
@@ -8350,7 +8356,6 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
     sql("select * from UNNEST(array [('apple', 1), ('banana', 2)]) as fruit")
         .type("RecordType(CHAR(6) NOT NULL EXPR$0, INTEGER NOT NULL EXPR$1) "
             + "NOT NULL");
-
     // VALUES gets the same treatment as ARRAY. (Unlike PostgreSQL.)
     sql("select * from (values ('apple'), ('banana')) as fruit")
         .type("RecordType(CHAR(6) NOT NULL FRUIT) NOT NULL");
@@ -8358,6 +8363,11 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
     // UNNEST MULTISET gets the same treatment as UNNEST ARRAY.
     sql("select * from unnest(multiset [1, 2, 1]) as f")
         .type("RecordType(INTEGER NOT NULL F) NOT NULL");
+
+    // The magic doesn't happen if the UNNEST is used without AS operator.
+    sql("select * from (SELECT ARRAY['banana'] as fruits) as t, UNNEST(t.fruits)")
+        .type("RecordType(CHAR(6) NOT NULL ARRAY NOT NULL FRUITS, "
+            + "CHAR(6) NOT NULL EXPR$0) NOT NULL").ok();
   }
 
   @Test void testCorrelationJoin() {
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 2c4f5633e4..b64f9c33d5 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -6427,7 +6427,7 @@ unnest(t1.fake_col) as t2]]>
     </Resource>
     <Resource name="plan">
       <![CDATA[
-LogicalProject(**=[$1], $unnest=[$2])
+LogicalProject(**=[$1], T2=[$2])
   LogicalCorrelate(correlation=[$cor0], joinType=[inner], requiredColumns=[{0}])
     LogicalTableScan(table=[[CATALOG, SALES, CUSTOMER]])
     Uncollect
diff --git a/core/src/test/resources/sql/unnest.iq b/core/src/test/resources/sql/unnest.iq
index 8056742279..bf990ad463 100644
--- a/core/src/test/resources/sql/unnest.iq
+++ b/core/src/test/resources/sql/unnest.iq
@@ -105,6 +105,31 @@ from UNNEST(array ['apple', 'banana']) as "fruit";
 
 !ok
 
+select fruit
+from UNNEST(array(select 'banana')) as fruit;
++--------+
+| FRUIT  |
++--------+
+| banana |
++--------+
+(1 row)
+
+!ok
+
+#TODO: Remove if when [CALCITE-5127] will be fixed.
+!if (false) {
+SELECT ARRAY(SELECT y + 1 FROM UNNEST(s.x) y)
+FROM (SELECT ARRAY[1,2,3] as x) s;
++--------+
+| FRUIT  |
++--------+
+| banana |
++--------+
+(1 row)
+
+!ok
+!}
+
 # If UNNEST is not the direct child of the AS, aliasing doesn't happen.
 SELECT fruit.*
 FROM (
@@ -120,21 +145,19 @@ FROM (
 
 !ok
 
-# If UNNEST applies to a value that is not an array constructor,
-# aliasing doesn't happen. Thus the last column is 'EXPR$0', not 'z'.
 SELECT *
 FROM (
     SELECT x, collect(y) as ys
     FROM (VALUES (1, 1), (2, 2), (1, 3)) AS t (x, y)
     GROUP BY x) AS u,
   UNNEST(u.ys) AS z;
-+---+--------+--------+
-| X | YS     | EXPR$0 |
-+---+--------+--------+
-| 1 | [1, 3] |      1 |
-| 1 | [1, 3] |      3 |
-| 2 | [2]    |      2 |
-+---+--------+--------+
++---+--------+---+
+| X | YS     | Z |
++---+--------+---+
+| 1 | [1, 3] | 1 |
+| 1 | [1, 3] | 3 |
+| 2 | [2]    | 2 |
++---+--------+---+
 (3 rows)
 
 !ok