From 1bda4056148209d41e4e717920317c97866b3ef1 Mon Sep 17 00:00:00 2001 From: Sean Broeder Date: Thu, 17 Sep 2026 08:30:17 -0700 Subject: [PATCH] [CALCITE-7791] UNNEST of a single-field ROW array loses the field name when aliased Extend AliasNamespace's CALCITE-5779 COLLECTION_TABLE check to also cover UNNEST when the array's component type is a struct --- .../calcite/sql/validate/AliasNamespace.java | 17 +++++++++++++++++ .../apache/calcite/test/SqlValidatorTest.java | 14 ++++++++++++++ 2 files changed, 31 insertions(+) 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 99f84154e77..fbd21c5936a 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 @@ -111,6 +111,7 @@ protected AliasNamespace( rowType.getFieldList().get(0).getType()) .build(); aliasedType = node.getKind() == SqlKind.COLLECTION_TABLE + || (node.getKind() == SqlKind.UNNEST && isUnnestOfStructArray((SqlCall) node)) ? new SingleColumnAliasRelDataType(rowType, singleColumnAlias) : singleColumnAlias; // If the sub-query is UNNEST with ordinality // and the sub-query has two columns: data column, ordinality column @@ -162,6 +163,22 @@ protected AliasNamespace( } } + /** + * Returns whether an UNNEST call's array operand has a struct element type. + * + *

{@link SqlUnnestOperator#inferReturnType} produces a 1-field row for + * both struct arrays and scalar arrays. Only the structs have a real field + * name worth preserving. + */ + private boolean isUnnestOfStructArray(SqlCall unnestCall) { + if (unnestCall.operandCount() != 1) { + return false; + } + final RelDataType operandType = validator.getValidatedNodeType(unnestCall.operand(0)); + final RelDataType componentType = operandType.getComponentType(); + return componentType != null && componentType.isStruct(); + } + private static String getString(RelDataType rowType) { StringBuilder buf = new StringBuilder(); buf.append("("); 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 3915e5723cb..b95ca8cca24 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -1201,6 +1201,20 @@ void testDyadicCollateOperator() { }); } + /** Test case for + * [CALCITE-7791] + * UNNEST of a single-field ROW array loses the field name when aliased. */ + @Test void testUnnestSingleFieldRow() { + sql("select d.a\n" + + "from unnest(cast(array[row(1)] as row(a integer) array)) as d") + .columnType("INTEGER NOT NULL"); + // The array's real field name ("A") survives star-expansion too, not just + // the synthetic alias ("D"). + sql("select *\n" + + "from unnest(cast(array[row(1)] as row(a integer) array)) as d") + .type("RecordType(INTEGER NOT NULL A) NOT NULL"); + } + @Test void testOverlay() { expr("overlay('ABCdef' placing 'abc' from 1)").ok(); expr("overlay('ABCdef' placing 'abc' from 1 for 3)").ok();