From e2f949d5d6cff79cbe8565bc3e85f437dee9fd7e Mon Sep 17 00:00:00 2001 From: dssysolyatin Date: Mon, 2 May 2022 14:39:37 +0300 Subject: [PATCH] =?UTF-8?q?[CALCITE-5126]=20Implicit=20column=20alias=20fo?= =?UTF-8?q?r=20single-column=20UNNEST=20should=20work=20with=20any=20singl?= =?UTF-8?q?e-column=20UNNEST=E2=80=99s=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Close apache/calcite#2789 --- .../calcite/sql/validate/AliasNamespace.java | 15 +++++++ .../sql/validate/SqlValidatorImpl.java | 12 ++---- .../calcite/test/SqlToRelConverterTest.java | 4 +- .../apache/calcite/test/SqlValidatorTest.java | 12 +++++- .../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 be293fc9c450..f3f7b343c3df 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 @@ 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 @@ protected AliasNamespace( 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 @@ protected AliasNamespace( .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 1a060dea8f4c..c53803e8fdfe 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 @@ private SqlNode registerFrom( 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 @@ private SqlNode registerFrom( // 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 ba4cd0c5c48b..add989567b90 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 @@ void checkCorrelatedMapSubQuery(boolean expand) { @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 @@ void checkCorrelatedMapSubQuery(boolean expand) { @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 f3fb98d97a79..c8d5fe6ec65e 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 void _testGroupExpressionEquivalenceParams() { .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..'.*"); sql("select deptno\n" @@ -8338,6 +8340,10 @@ public void _testGroupExpressionEquivalenceParams() { // 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 void _testGroupExpressionEquivalenceParams() { 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 void _testGroupExpressionEquivalenceParams() { // 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 2c4f5633e420..b64f9c33d567 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]]>