From d1c8612f77c6e6e00bfb89a81d8a21dcd2b47e6e Mon Sep 17 00:00:00 2001 From: Takaaki Nakama Date: Sun, 24 May 2026 01:36:58 +0900 Subject: [PATCH] [CALCITE-7547] BIG_QUERY conformance should allow field access on UNNEST(array_of_struct) AS alias Under SqlConformanceEnum.BIG_QUERY, SELECT i.name FROM t, UNNEST(t.items) AS i where items is ARRAY> failed validation with "Column 'NAME' not found in table 'I'" because allowAliasUnnestItems() returned false. With the flag off, SqlUnnestOperator#inferReturnType flattens the ROW element type into individual columns instead of keeping it as a single struct-typed column the alias can wrap, and AliasNamespace cannot remap the flattened result back under the alias. BigQuery itself supports this access pattern, so extend the existing PRESTO case in SqlConformanceEnum#allowAliasUnnestItems() to also cover BIG_QUERY. This complements CALCITE-7546, which removes the downstream NPE in SqlToRelConverter#convertUnnest for the 2-operand AS(UNNEST, alias) form when the flag is enabled. --- .../calcite/sql/validate/SqlConformance.java | 1 + .../sql/validate/SqlConformanceEnum.java | 1 + .../calcite/test/SqlToRelConverterTest.java | 26 ++++++++++++++ .../apache/calcite/test/SqlValidatorTest.java | 31 +++++++++++++++++ .../calcite/test/SqlToRelConverterTest.xml | 34 +++++++++++++++++++ 5 files changed, 93 insertions(+) diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java index fe902759db16..fcd3b46641f6 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java @@ -375,6 +375,7 @@ default boolean isColonFieldAccessAllowed() { * fields of T if T is a STRUCT type. * *

Among the built-in conformance levels, true in + * {@link SqlConformanceEnum#BIG_QUERY}, * {@link SqlConformanceEnum#PRESTO}; * false otherwise. */ diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java index 047f05981a16..f0f258171b3d 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java @@ -435,6 +435,7 @@ public enum SqlConformanceEnum implements SqlConformance { @Override public boolean allowAliasUnnestItems() { switch (this) { + case BIG_QUERY: case PRESTO: return true; default: 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 ebad0e9450a5..1fe413761df6 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java @@ -1939,6 +1939,32 @@ public static void checkActualAndReferenceFiles() { sql(sql).withConformance(SqlConformanceEnum.PRESTO).ok(); } + /** + * Test case for + * [CALCITE-7547] + * BIG_QUERY conformance should allow field access on + * UNNEST(array_of_struct) AS alias. + */ + @Test void testAliasUnnestArrayPlanWithSingleColumnBigQuery() { + final String sql = "select d.deptno, employee.empno\n" + + "from dept_nested_expanded as d,\n" + + " UNNEST(d.employees) as t(employee)"; + sql(sql).withConformance(SqlConformanceEnum.BIG_QUERY).ok(); + } + + /** + * Test case for + * [CALCITE-7547] + * BIG_QUERY conformance should allow field access on + * UNNEST(array_of_struct) AS alias. + */ + @Test void testAliasUnnestArrayPlanWithDoubleColumnBigQuery() { + final String sql = "select d.deptno, e, k.empno\n" + + "from dept_nested_expanded as d CROSS JOIN\n" + + " UNNEST(d.admins, d.employees) as t(e, k)"; + sql(sql).withConformance(SqlConformanceEnum.BIG_QUERY).ok(); + } + @Test void testArrayOfRecord() { sql("select employees[1].detail.skills[2+3].desc from dept_nested").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 5198762c106b..9a080efbf862 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -8816,6 +8816,37 @@ void testGroupExpressionEquivalenceParams() { + "\\('EMPNO', 'ENAME', 'DETAIL'\\), whereas alias list has 1 columns"); } + /** + * Test case for + * [CALCITE-7547] + * BIG_QUERY conformance should allow field access on + * UNNEST(array_of_struct) AS alias. + */ + @Test void testAliasUnnestMultipleArraysBigQuery() { + // for accessing a field in STRUCT type unnested from array + sql("select e.ENAME\n" + + "from dept_nested_expanded as d CROSS JOIN\n" + + " UNNEST(d.employees) as t(e)") + .withConformance(SqlConformanceEnum.BIG_QUERY).columnType("VARCHAR(10) NOT NULL"); + + // for unnesting multiple arrays at the same time + sql("select d.deptno, e, k.empno, l.\"unit\", l.\"X\" * l.\"Y\"\n" + + "from dept_nested_expanded as d CROSS JOIN\n" + + " UNNEST(d.admins, d.employees, d.offices) as t(e, k, l)") + .withConformance(SqlConformanceEnum.BIG_QUERY).ok(); + + // Make sure validation fails properly given illegal select items + sql("select d.deptno, ^e^.some_column, k.empno\n" + + "from dept_nested_expanded as d CROSS JOIN\n" + + " UNNEST(d.admins, d.employees) as t(e, k)") + .withConformance(SqlConformanceEnum.BIG_QUERY) + .fails("Table 'E' not found"); + sql("select d.deptno, e.detail, ^unknown^.detail\n" + + "from dept_nested_expanded as d CROSS JOIN\n" + + " UNNEST(d.employees) as t(e)") + .withConformance(SqlConformanceEnum.BIG_QUERY).fails("Incompatible types"); + } + @Test void testUnnestArray() { sql("select*from unnest(array[1])") .columnType("INTEGER NOT NULL"); 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 56f169629ae6..5ee7180efa75 100644 --- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml @@ -341,6 +341,23 @@ LogicalProject(DEPTNO=[$0], E=[$5], EMPNO=[$6.EMPNO]) + + + + + + + + @@ -358,6 +375,23 @@ LogicalProject(DEPTNO=[$0], EMPNO=[$5.EMPNO]) + + + + + + + +