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

github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new 209a0a2e86 fix: unnest struct field with an alias failed with internal 
error (#19698)
209a0a2e86 is described below

commit 209a0a2e86dda54e7dc00f5715ed1e35ea1af3cb
Author: Kumar Ujjawal <[email protected]>
AuthorDate: Fri Jan 9 21:27:58 2026 +0530

    fix: unnest struct field with an alias failed with internal error (#19698)
    
    ## Which issue does this PR close?
    
    <!--
    We generally require a GitHub issue to be filed for all bug fixes and
    enhancements and this helps us generate change logs for our releases.
    You can link an issue to this PR using the GitHub syntax. For example
    `Closes #123` indicates that this PR will close issue #123.
    -->
    
    - Closes #19689.
    
    ## Rationale for this change
    
    Unnesting with an alias resulted in an internal error it should just be
    ignored.
    
    
    <!--
    Why are you proposing this change? If this is already explained clearly
    in the issue then this section is not needed.
    Explaining clearly why changes are proposed helps reviewers understand
    your changes and offer better suggestions for fixes.
    -->
    
    ## What changes are included in this PR?
    
    - Added helper method to recognize when an alias wraps an unnest
    expression
    - Refined the condition for setting `transformed_root_exprs` to only
    apply to actual struct types (which return multiple expressions)
    - Updated slt test
    
    <!--
    There is no need to duplicate the description in the issue here but it
    is sometimes worth providing a summary of the individual changes in this
    PR.
    -->
    
    ## Are these changes tested?
    
    Yes
    
    <!--
    We typically require tests for all PRs in order to:
    1. Prevent the code from being accidentally broken by subsequent changes
    2. Serve as another way to document the expected behavior of the code
    
    If tests are not included in your PR, please explain why (for example,
    are they covered by existing tests)?
    -->
    
    ## Are there any user-facing changes?
    
    <!--
    If there are user-facing changes then we may require documentation to be
    updated before approving the PR.
    -->
    
    <!--
    If there are any breaking changes to public APIs, please add the `api
    change` label.
    -->
    
    ---------
    
    Co-authored-by: Andrew Lamb <[email protected]>
---
 datafusion/sql/src/utils.rs                   | 25 ++++++++++++++++++++--
 datafusion/sqllogictest/test_files/unnest.slt | 30 +++++++++++++++++++++++++--
 2 files changed, 51 insertions(+), 4 deletions(-)

diff --git a/datafusion/sql/src/utils.rs b/datafusion/sql/src/utils.rs
index af2e1c7942..43fb98e545 100644
--- a/datafusion/sql/src/utils.rs
+++ b/datafusion/sql/src/utils.rs
@@ -406,6 +406,24 @@ impl RecursiveUnnestRewriter<'_> {
             .collect()
     }
 
+    /// Check if the current expression is at the root level for struct unnest 
purposes.
+    /// This is true if:
+    /// 1. The expression IS the root expression, OR
+    /// 2. The root expression is an Alias wrapping this expression
+    ///
+    /// This allows `unnest(struct_col) AS alias` to work, where the alias is 
simply
+    /// ignored for struct unnest (matching DuckDB behavior).
+    fn is_at_struct_allowed_root(&self, expr: &Expr) -> bool {
+        if expr == self.root_expr {
+            return true;
+        }
+        // Allow struct unnest when root is an alias wrapping the unnest
+        if let Expr::Alias(Alias { expr: inner, .. }) = self.root_expr {
+            return inner.as_ref() == expr;
+        }
+        false
+    }
+
     fn transform(
         &mut self,
         level: usize,
@@ -566,7 +584,8 @@ impl TreeNodeRewriter for RecursiveUnnestRewriter<'_> {
                 // instead of unnest(struct_arr_col, depth = 2)
 
                 let unnest_recursion = unnest_stack.len();
-                let struct_allowed = (&expr == self.root_expr) && 
unnest_recursion == 1;
+                let struct_allowed =
+                    self.is_at_struct_allowed_root(&expr) && unnest_recursion 
== 1;
 
                 let mut transformed_exprs = self.transform(
                     unnest_recursion,
@@ -574,7 +593,9 @@ impl TreeNodeRewriter for RecursiveUnnestRewriter<'_> {
                     inner_expr,
                     struct_allowed,
                 )?;
-                if struct_allowed {
+                // Only set transformed_root_exprs for struct unnest (which 
returns multiple expressions).
+                // For list unnest (single expression), we let the normal 
rewrite handle the alias.
+                if struct_allowed && transformed_exprs.len() > 1 {
                     self.transformed_root_exprs = 
Some(transformed_exprs.clone());
                 }
                 return Ok(Transformed::new(
diff --git a/datafusion/sqllogictest/test_files/unnest.slt 
b/datafusion/sqllogictest/test_files/unnest.slt
index 352056adbf..f939cd0154 100644
--- a/datafusion/sqllogictest/test_files/unnest.slt
+++ b/datafusion/sqllogictest/test_files/unnest.slt
@@ -58,6 +58,20 @@ select unnest(struct(1,2,3));
 ----
 1 2 3
 
+## Basic unnest expression in select struct with alias (alias is ignored for 
struct unnest)
+query III
+select unnest(struct(1,2,3)) as ignored_alias;
+----
+1 2 3
+
+## Verify schema output for struct unnest with alias (alias is ignored)
+query TTT
+describe select unnest(struct(1,2,3)) as ignored_alias;
+----
+__unnest_placeholder(struct(Int64(1),Int64(2),Int64(3))).c0 Int64 YES
+__unnest_placeholder(struct(Int64(1),Int64(2),Int64(3))).c1 Int64 YES
+__unnest_placeholder(struct(Int64(1),Int64(2),Int64(3))).c2 Int64 YES
+
 ## Basic unnest list expression in from clause
 query I
 select * from unnest([1,2,3]);
@@ -798,9 +812,21 @@ NULL 1
 query error DataFusion error: Error during planning: Column in SELECT must be 
in GROUP BY or an aggregate function: While expanding wildcard, column 
"nested_unnest_table\.column1" must appear in the GROUP BY clause or must be 
part of an aggregate function, currently only 
"UNNEST\(nested_unnest_table\.column1\)\[c0\]" appears in the SELECT clause 
satisfies this requirement
 select unnest(column1) c1 from nested_unnest_table group by c1.c0;
 
-# TODO: this query should work. see issue: 
https://github.com/apache/datafusion/issues/12794
-query error DataFusion error: Internal error: Assertion failed: 
struct_allowed: unnest on struct can only be applied at the root level of 
select expression
+## Unnest struct with alias - alias is ignored (same as DuckDB behavior)
+## See: https://github.com/apache/datafusion/issues/12794
+query TT?
 select unnest(column1) c1 from nested_unnest_table
+----
+a b {c0: c}
+d e {c0: f}
+
+## Verify schema output for struct unnest with alias (alias is ignored)
+query TTT
+describe select unnest(column1) c1 from nested_unnest_table;
+----
+__unnest_placeholder(nested_unnest_table.column1).c0 Utf8 YES
+__unnest_placeholder(nested_unnest_table.column1).c1 Utf8 YES
+__unnest_placeholder(nested_unnest_table.column1).c2 Struct("c0": Utf8) YES
 
 query II??I??
 select unnest(column5), * from unnest_table;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to