This is an automated email from the ASF dual-hosted git repository.

zabetak 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 7b8b2b9604 [CALCITE-4972] Subfields of array columns containing 
structs are not qualified in getFieldOrigins
7b8b2b9604 is described below

commit 7b8b2b96041d0cf7bf69cae336659087739fa495
Author: Mark Grey <mgthesec...@spotify.com>
AuthorDate: Fri Jan 7 16:22:10 2022 -0500

    [CALCITE-4972] Subfields of array columns containing structs are not 
qualified in getFieldOrigins
    
    Close apache/calcite#2683
---
 .../calcite/sql/validate/SqlValidatorImpl.java     | 16 +++++++++++++
 .../calcite/sql/validate/UnnestNamespace.java      | 27 ++++++++++++++++++++++
 .../org/apache/calcite/test/SqlValidatorTest.java  | 13 +++++++++++
 3 files changed, 56 insertions(+)

diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index c53803e8fd..cd272f649b 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -6145,6 +6145,13 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
             scope.fullyQualify((SqlIdentifier) selectItem);
         SqlValidatorNamespace namespace = requireNonNull(qualified.namespace,
             () -> "namespace for " + qualified);
+        if (namespace.isWrapperFor(AliasNamespace.class)) {
+          AliasNamespace aliasNs = namespace.unwrap(AliasNamespace.class);
+          SqlNode aliased = requireNonNull(aliasNs.getNode(), () ->
+              "sqlNode for aliasNs " + aliasNs);
+          namespace = getNamespaceOrThrow(stripAs(aliased));
+        }
+
         final SqlValidatorTable table = namespace.getTable();
         if (table == null) {
           return null;
@@ -6152,7 +6159,16 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
         final List<String> origin =
             new ArrayList<>(table.getQualifiedName());
         for (String name : qualified.suffix()) {
+          if (namespace.isWrapperFor(UnnestNamespace.class)) {
+            // If identifier is drawn from a repeated subrecord via unnest, 
add name of array field
+            UnnestNamespace unnestNamespace = 
namespace.unwrap(UnnestNamespace.class);
+            final SqlQualified columnUnnestedFrom = 
unnestNamespace.getColumnUnnestedFrom(name);
+            if (columnUnnestedFrom != null) {
+              origin.addAll(columnUnnestedFrom.suffix());
+            }
+          }
           namespace = namespace.lookupChild(name);
+
           if (namespace == null) {
             return null;
           }
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/UnnestNamespace.java 
b/core/src/main/java/org/apache/calcite/sql/validate/UnnestNamespace.java
index 531ba53a90..f3e732d993 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/UnnestNamespace.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/UnnestNamespace.java
@@ -64,6 +64,33 @@ class UnnestNamespace extends AbstractNamespace {
     return null;
   }
 
+  /**
+   * Given a field name from SelectScope, find the column in this
+   * UnnestNamespace it originates from.
+   *
+   * @param queryFieldName Name of column
+   * @return A SqlQualified if subfield comes from this unnest, null if not 
found
+   */
+  @Nullable SqlQualified getColumnUnnestedFrom(String queryFieldName) {
+    for (SqlNode operand : unnest.getOperandList()) {
+      // Ignore operands that are inline ARRAY[] literals
+      if (operand instanceof SqlIdentifier) {
+        final SqlIdentifier id = (SqlIdentifier) operand;
+        final SqlQualified qualified = this.scope.fullyQualify(id);
+        RelDataType dataType = 
this.scope.resolveColumn(qualified.suffix().get(0), id);
+        if (dataType != null) {
+          RelDataType repeatedEntryType = dataType.getComponentType();
+          if (repeatedEntryType != null
+              && repeatedEntryType.isStruct()
+              && repeatedEntryType.getFieldNames().contains(queryFieldName)) {
+            return qualified;
+          }
+        }
+      }
+    }
+    return null;
+  }
+
   @Override protected RelDataType validateImpl(RelDataType targetRowType) {
     // Validate the call and its arguments, and infer the return type.
     validator.validateCall(unnest, scope);
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 00dd5e5d7b..be16adca82 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -9210,6 +9210,19 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
                 + " CATALOG.SALES.EMP.HIREDATE,"
                 + " null,"
                 + " null}"));
+
+    sql("select e.empno from dept_nested, unnest(employees) as e")
+        .assertFieldOrigin(
+            is("{CATALOG.SALES.DEPT_NESTED.EMPLOYEES.EMPNO}"));
+
+    sql("select * from UNNEST(ARRAY['a', 'b'])")
+        .assertFieldOrigin(is("{null}"));
+
+    sql("select * from UNNEST(ARRAY['a', 'b'], ARRAY['d', 'e'])")
+        .assertFieldOrigin(is("{null, null}"));
+
+    sql("select dpt.skill.desc from dept_nested as dpt")
+        .assertFieldOrigin(is("{CATALOG.SALES.DEPT_NESTED.SKILL.DESC}"));
   }
 
   @Test void testBrackets() {

Reply via email to