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);
 

Reply via email to