This is an automated email from the ASF dual-hosted git repository.
mihaibudiu 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 3809e7cdb7 [CALCITE-7614] UNNEST of an unqualified struct-rooted array
path fails validation: "Column 's.s' not found"
3809e7cdb7 is described below
commit 3809e7cdb75cc48e388a9f9a4e590e643540f3eb
Author: Takaaki Nakama <[email protected]>
AuthorDate: Wed Jun 24 09:58:21 2026 +0900
[CALCITE-7614] UNNEST of an unqualified struct-rooted array path fails
validation: "Column 's.s' not found"
When the operand of UNNEST is an unqualified identifier whose leading
component is a PEEK_FIELDS struct column (e.g. UNNEST(s.arr) where s is a
PEEK_FIELDS struct containing array field arr), validation failed with
"Column 's.s' not found in table 't'", while the table-qualified form
UNNEST(r.s.arr) worked.
While DelegatingScope.fullyQualify resolves the qualifying table for the
unqualified operand, that resolution re-enters validation of the UNNEST
namespace, which rewrites the very same operand identifier's names in
place to the fully-qualified form. fullyQualify then re-qualified the
already-qualified identifier, duplicating the struct-column segment and
producing the doubled "s.s".
Fix fullyQualify to qualify a copy of the identifier taken before the
re-entrant resolution, so the PEEK_FIELDS branch always works from the
original names.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-authored-by: Cursor <[email protected]>
---
.../org/apache/calcite/sql/validate/DelegatingScope.java | 7 ++++++-
.../java/org/apache/calcite/sql/test/SqlAdvisorTest.java | 1 +
.../java/org/apache/calcite/test/SqlValidatorTest.java | 15 +++++++++++++++
.../java/org/apache/calcite/test/catalog/Fixture.java | 7 +++++++
.../calcite/test/catalog/MockCatalogReaderSimple.java | 12 ++++++++++++
5 files changed, 41 insertions(+), 1 deletion(-)
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
index 5153d3160e..16a0e3a53b 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
@@ -329,6 +329,9 @@ protected void addColumnNames(
final ResolvedImpl resolved = new ResolvedImpl();
int size = identifier.names.size();
int i = size - 1;
+ // Snapshot: resolution below may rewrite identifier's names in place
[CALCITE-7614]
+ final SqlIdentifier originalIdentifier =
+ (SqlIdentifier) identifier.clone(identifier.getParserPosition());
for (; i > 0; i--) {
final SqlIdentifier prefix = identifier.getComponent(0, i);
resolved.clear();
@@ -388,7 +391,9 @@ protected void addColumnNames(
fromNs = resolve.namespace;
fromPath = resolve.path;
fromRowType = resolve.rowType();
- identifier = identifier
+ // Qualify the original (pre-resolution) names; see the comment
+ // on originalIdentifier above ([CALCITE-7614]).
+ identifier = originalIdentifier
.setName(0, columnName)
.add(0, tableName2, SqlParserPos.ZERO);
++i;
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
index fad2778a38..01488fcc4b 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
@@ -100,6 +100,7 @@ class SqlAdvisorTest extends SqlValidatorTestCase {
"TABLE(CATALOG.SALES.DOUBLE_PK)",
"TABLE(CATALOG.SALES.DEPT_NESTED)",
"TABLE(CATALOG.SALES.DEPT_NESTED_EXPANDED)",
+ "TABLE(CATALOG.SALES.DEPT_NESTED_PEEK)",
"TABLE(CATALOG.SALES.BONUS)",
"TABLE(CATALOG.SALES.ORDERS)",
"TABLE(CATALOG.SALES.SALGRADE)",
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 f85c6c75bf..0cf1966f99 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -9486,6 +9486,21 @@ void testGroupExpressionEquivalenceParams() {
sql(sql3).fails("Table 'D' not found");
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7614">[CALCITE-7614]
+ * UNNEST of an array field in a PEEK_FIELDS struct column fails when the
table
+ * is not qualified</a>. */
+ @Test void testUnnestPeekFieldsArrayColumn() {
+ // Table-qualified form already works.
+ sql("select * from dept_nested_peek as r CROSS JOIN UNNEST(r.s.arr) as
x").ok();
+ // Unqualified form must work too (used to fail with
+ // "Column 'S.S' not found in table 'DEPT_NESTED_PEEK'").
+ sql("select * from dept_nested_peek CROSS JOIN UNNEST(s.arr) as x").ok();
+ // Comma-join variants, for parity with testUnnestArrayColumn.
+ sql("select * from dept_nested_peek as r, UNNEST(r.s.arr) as x").ok();
+ sql("select * from dept_nested_peek, UNNEST(s.arr) as x").ok();
+ }
+
@Test void testUnnestWithOrdinality() {
sql("select*from unnest(array[1, 2]) with ordinality")
.type("RecordType(INTEGER NOT NULL EXPR$0, INTEGER NOT NULL
ORDINALITY) NOT NULL");
diff --git a/testkit/src/main/java/org/apache/calcite/test/catalog/Fixture.java
b/testkit/src/main/java/org/apache/calcite/test/catalog/Fixture.java
index be6d20179f..d067e6bb48 100644
--- a/testkit/src/main/java/org/apache/calcite/test/catalog/Fixture.java
+++ b/testkit/src/main/java/org/apache/calcite/test/catalog/Fixture.java
@@ -136,6 +136,13 @@ final class Fixture extends AbstractFixture {
final RelDataType varchar5ArrayType = array(varchar5Type);
final RelDataType intArrayArrayType = array(intArrayType);
final RelDataType varchar5ArrayArrayType = array(varchar5ArrayType);
+ // A "peek" struct that contains an array field, e.g. Row(ARR varchar(5)
array)
+ // with StructKind.PEEK_FIELDS so that "ARR" can be referenced without the
+ // struct-column prefix.
+ final RelDataType peekArrayType = typeFactory.builder()
+ .add("ARR", varchar5ArrayType)
+ .kind(StructKind.PEEK_FIELDS)
+ .build();
final RelDataType intMultisetType = typeFactory.createMultisetType(intType,
-1);
final RelDataType varchar5MultisetType =
typeFactory.createMultisetType(varchar5Type, -1);
final RelDataType intMultisetArrayType = array(intMultisetType);
diff --git
a/testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReaderSimple.java
b/testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReaderSimple.java
index 8723266ea0..1f7be11410 100644
---
a/testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReaderSimple.java
+++
b/testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReaderSimple.java
@@ -177,6 +177,14 @@ private void registerTableDeptNestedExpanded(MockSchema
salesSchema, Fixture fix
registerTable(deptNestedExpandedTable);
}
+ private void registerTableDeptNestedPeek(MockSchema salesSchema, Fixture
fixture) {
+ MockTable deptNestedPeekTable =
+ MockTable.create(this, salesSchema, "DEPT_NESTED_PEEK", false, 4);
+ deptNestedPeekTable.addColumn("DEPTNO", fixture.intType, true);
+ deptNestedPeekTable.addColumn("S", fixture.peekArrayType);
+ registerTable(deptNestedPeekTable);
+ }
+
private void registerTableBonus(MockSchema salesSchema, Fixture fixture) {
MockTable bonusTable =
MockTable.create(this, salesSchema, "BONUS", false, 0);
@@ -512,6 +520,10 @@ private void registerTableDoublePK(MockSchema salesSchema,
Fixture fixture) {
// Register "DEPT_NESTED_EXPANDED" table.
registerTableDeptNestedExpanded(salesSchema, fixture);
+ // Register "DEPT_NESTED_PEEK" table, which has a PEEK_FIELDS struct column
+ // "S" containing an array field "ARR".
+ registerTableDeptNestedPeek(salesSchema, fixture);
+
// Register "BONUS" table.
registerTableBonus(salesSchema, fixture);