From 90061e2f7036f21824e26cff19307b152afa7d48 Mon Sep 17 00:00:00 2001 From: Takaaki Nakama Date: Sun, 24 May 2026 00:19:55 +0900 Subject: [PATCH 1/2] [CALCITE-7546] NullPointerException in SqlToRelConverter for UNNEST(array) AS alias under conformance with allowAliasUnnestItems=true Under a SqlConformance where allowAliasUnnestItems() is true (SqlConformanceEnum.PRESTO and user conformances overriding the flag), the AS branch in convertFrom passes fieldNames=null to convertUnnest when the AS clause omits a column list. The PRESTO-only branch then called requireNonNull(fieldNames, "fieldNames") and threw NPE. Fall back to default item aliases derived from SqlUtil#deriveAliasFromOrdinal, matching the names that SqlUnnestOperator#inferReturnType uses during validation. This keeps the relational row type aligned with the validator's underlying namespace so both struct and scalar element types resolve correctly. --- .../calcite/sql2rel/SqlToRelConverter.java | 13 ++++++- .../calcite/test/SqlToRelConverterTest.java | 20 +++++++++++ .../calcite/test/SqlToRelConverterTest.xml | 34 +++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) 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 4622176a0733..0f6538bf03e8 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -2677,10 +2677,21 @@ private void convertUnnest(Blackboard bb, SqlCall call, @Nullable List f RelNode uncollect; try { if (validator().config().conformance().allowAliasUnnestItems()) { + // Without an AS column list, mirror SqlUnnestOperator#inferReturnType + // so Uncollect's row type stays aligned with the validator. + List itemAliases; + if (fieldNames != null) { + itemAliases = fieldNames; + } else { + itemAliases = new ArrayList<>(nodes.size()); + for (int i = 0; i < nodes.size(); i++) { + itemAliases.add(SqlUtil.deriveAliasFromOrdinal(i)); + } + } uncollect = relBuilder .push(child) .project(exprs) - .uncollect(requireNonNull(fieldNames, "fieldNames"), operator.withOrdinality) + .uncollect(itemAliases, operator.withOrdinality) .build(); } else { // REVIEW danny 2020-04-26: should we unify the normal field aliases and 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..e55232bae2e7 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,26 @@ public static void checkActualAndReferenceFiles() { sql(sql).withConformance(SqlConformanceEnum.PRESTO).ok(); } + /** + * Test case for + * [CALCITE-7546] + * NullPointerException in SqlToRelConverter for UNNEST(array) AS alias under + * conformance with allowAliasUnnestItems=true. + */ + @Test void testAliasUnnestArrayPlanWithoutColumnList() { + final String sql = "select d.deptno, e.empno\n" + + "from dept_nested_expanded as d,\n" + + " UNNEST(d.employees) as e"; + sql(sql).withConformance(SqlConformanceEnum.PRESTO).ok(); + } + + @Test void testAliasUnnestScalarArrayPlanWithoutColumnList() { + final String sql = "select d.deptno, a\n" + + "from dept_nested_expanded as d,\n" + + " UNNEST(d.admins) as a"; + sql(sql).withConformance(SqlConformanceEnum.PRESTO).ok(); + } + @Test void testArrayOfRecord() { sql("select employees[1].detail.skills[2+3].desc from dept_nested").ok(); } 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..91ad9221a8a1 100644 --- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml @@ -362,6 +362,40 @@ from dept_nested_expanded as d, UNNEST(d.employees) as t(employee)]]> + + + + + + + + + + + + + + + + Date: Sun, 24 May 2026 16:04:56 +0900 Subject: [PATCH 2/2] [CALCITE-7546] Add test for UNNEST(ARRAY literal) AS alias from issue reproduction Add testAliasUnnestArrayLiteralPlanWithoutColumnList using the exact SQL from the issue ("SELECT t FROM UNNEST(ARRAY[1, 2, 3]) AS t"), which exercises the same convertUnnest fallback as the column-reference cases but through a standalone (non-correlated) Uncollect tree. Verified that the new test reproduces "NullPointerException: fieldNames" against the pre-fix code and passes with the fix. --- .../apache/calcite/test/SqlToRelConverterTest.java | 13 +++++++++++++ .../apache/calcite/test/SqlToRelConverterTest.xml | 14 ++++++++++++++ 2 files changed, 27 insertions(+) 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 e55232bae2e7..c43a524f889f 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java @@ -1959,6 +1959,19 @@ public static void checkActualAndReferenceFiles() { sql(sql).withConformance(SqlConformanceEnum.PRESTO).ok(); } + /** + * Test case for + * [CALCITE-7546] + * NullPointerException in SqlToRelConverter for UNNEST(array) AS alias under + * conformance with allowAliasUnnestItems=true, using the exact array + * literal reproduction from the issue. + */ + @Test void testAliasUnnestArrayLiteralPlanWithoutColumnList() { + final String sql = "select t\n" + + "from UNNEST(ARRAY[1, 2, 3]) as t"; + sql(sql).withConformance(SqlConformanceEnum.PRESTO).ok(); + } + @Test void testArrayOfRecord() { sql("select employees[1].detail.skills[2+3].desc from dept_nested").ok(); } 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 91ad9221a8a1..9cd1e4c6c9e4 100644 --- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml @@ -325,6 +325,20 @@ LogicalProject(A=[$0], B=[$1], C=[$2], DEPTNO=[$3], NAME=[$4]) LogicalProject(A=[$2], B=[$1], C=[$0]) LogicalValues(tuples=[[{ 1, 2, 3 }]]) LogicalTableScan(table=[[CATALOG, SALES, DEPT]]) +]]> + + + + + + + +