@@ -318,6 +318,7 @@ impl Unparser<'_> {
318318 if is_unnest_col {
319319 return Ok ( self . build_flatten_value_select_item (
320320 FLATTEN_DEFAULT_ALIAS ,
321+ None ,
321322 ) ) ;
322323 }
323324 }
@@ -423,10 +424,14 @@ impl Unparser<'_> {
423424 // The projection generated by the `RecursiveUnnestRewriter` from a UNNEST relation will have
424425 // only one expression, which is the placeholder column generated by the rewriter.
425426 let unnest_input_type = if p. expr . len ( ) == 1 {
426- Self :: check_unnest_placeholder_with_outer_ref (
427- & p. expr [ 0 ] ,
428- self . dialect . unnest_as_lateral_flatten ( ) ,
429- )
427+ Self :: check_unnest_placeholder_with_outer_ref ( & p. expr [ 0 ] )
428+ } else {
429+ None
430+ } ;
431+ // Extract the outermost user alias (e.g. "c1" from `UNNEST(...) AS c1`).
432+ // Internal aliases like "UNNEST(...)" are not user aliases.
433+ let user_alias = if unnest_input_type. is_some ( ) {
434+ Self :: extract_unnest_user_alias ( & p. expr [ 0 ] )
430435 } else {
431436 None
432437 } ;
@@ -466,8 +471,10 @@ impl Unparser<'_> {
466471 relation. flatten ( flatten_relation) ;
467472
468473 if !select. already_projected ( ) {
469- let value_expr =
470- self . build_flatten_value_select_item ( & alias_name) ;
474+ let value_expr = self . build_flatten_value_select_item (
475+ & alias_name,
476+ user_alias. as_deref ( ) ,
477+ ) ;
471478 select. projection ( vec ! [ value_expr] ) ;
472479 }
473480
@@ -501,8 +508,10 @@ impl Unparser<'_> {
501508 flatten. outer ( unnest. options . preserve_nulls ) ;
502509
503510 if !select. already_projected ( ) {
504- let value_expr =
505- self . build_flatten_value_select_item ( & alias_name) ;
511+ let value_expr = self . build_flatten_value_select_item (
512+ & alias_name,
513+ user_alias. as_deref ( ) ,
514+ ) ;
506515 select. projection ( vec ! [ value_expr] ) ;
507516 }
508517
@@ -540,6 +549,7 @@ impl Unparser<'_> {
540549 }
541550 if self . dialect . unnest_as_table_factor ( )
542551 && unnest_input_type. is_some ( )
552+ && user_alias. is_none ( ) // Skip if user alias present — fall through to reconstruct_select_statement which preserves aliases
543553 && let LogicalPlan :: Unnest ( unnest) = & p. input . as_ref ( )
544554 && let Some ( unnest_relation) =
545555 self . try_unnest_to_table_factor_sql ( unnest) ?
@@ -1241,28 +1251,14 @@ impl Unparser<'_> {
12411251 /// - If the column is not a placeholder column, return [None].
12421252 ///
12431253 /// `outer_ref` is the display result of [Expr::OuterReferenceColumn]
1244- /// When `deep_peel` is true, peels through multiple Alias layers to find
1245- /// the inner Column. This is needed for Snowflake FLATTEN where user aliases
1246- /// (e.g. `AS items`) add extra Alias wrappers around the placeholder column.
1247- fn check_unnest_placeholder_with_outer_ref (
1248- expr : & Expr ,
1249- deep_peel : bool ,
1250- ) -> Option < UnnestInputType > {
1251- let inner = match expr {
1252- Expr :: Alias ( Alias { expr, .. } ) => {
1253- if deep_peel {
1254- // Peel through all Alias layers
1255- let mut e = expr. as_ref ( ) ;
1256- while let Expr :: Alias ( Alias { expr : next, .. } ) = e {
1257- e = next. as_ref ( ) ;
1258- }
1259- e
1260- } else {
1261- expr. as_ref ( )
1262- }
1263- }
1264- _ => return None ,
1265- } ;
1254+ fn check_unnest_placeholder_with_outer_ref ( expr : & Expr ) -> Option < UnnestInputType > {
1255+ // Peel through all Alias layers to find the inner Column.
1256+ // The expression may have multiple aliases, e.g.:
1257+ // Alias("items", Alias("UNNEST(...)", Column("__unnest_placeholder(...)")))
1258+ let mut inner = expr;
1259+ while let Expr :: Alias ( Alias { expr, .. } ) = inner {
1260+ inner = expr. as_ref ( ) ;
1261+ }
12661262 if let Expr :: Column ( Column { name, .. } ) = inner
12671263 && let Some ( prefix) = name. strip_prefix ( UNNEST_PLACEHOLDER )
12681264 {
@@ -1274,6 +1270,19 @@ impl Unparser<'_> {
12741270 None
12751271 }
12761272
1273+ /// Extract the outermost user-provided alias from an unnest expression.
1274+ /// Returns `None` if the outermost alias is DataFusion's internal display
1275+ /// name (e.g. `UNNEST(make_array(...))`), or if there is no alias at all.
1276+ fn extract_unnest_user_alias ( expr : & Expr ) -> Option < String > {
1277+ if let Expr :: Alias ( Alias { name, .. } ) = expr {
1278+ // Internal aliases start with "UNNEST(" — user aliases don't.
1279+ if !name. starts_with ( & format ! ( "{UNNEST_COLUMN_PREFIX}(" ) ) {
1280+ return Some ( name. clone ( ) ) ;
1281+ }
1282+ }
1283+ None
1284+ }
1285+
12771286 fn try_unnest_to_table_factor_sql (
12781287 & self ,
12791288 unnest : & Unnest ,
@@ -1305,12 +1314,22 @@ impl Unparser<'_> {
13051314 }
13061315
13071316 /// Build a `SELECT alias."VALUE"` item for Snowflake FLATTEN output.
1308- fn build_flatten_value_select_item ( & self , alias_name : & str ) -> ast:: SelectItem {
1317+ fn build_flatten_value_select_item (
1318+ & self ,
1319+ flatten_alias : & str ,
1320+ user_alias : Option < & str > ,
1321+ ) -> ast:: SelectItem {
13091322 let compound = ast:: Expr :: CompoundIdentifier ( vec ! [
1310- self . new_ident_without_quote_style( alias_name . to_string( ) ) ,
1323+ self . new_ident_without_quote_style( flatten_alias . to_string( ) ) ,
13111324 Ident :: with_quote( '"' , "VALUE" ) ,
13121325 ] ) ;
1313- ast:: SelectItem :: UnnamedExpr ( compound)
1326+ match user_alias {
1327+ Some ( alias) => ast:: SelectItem :: ExprWithAlias {
1328+ expr : compound,
1329+ alias : self . new_ident_quoted_if_needs ( alias. to_string ( ) ) ,
1330+ } ,
1331+ None => ast:: SelectItem :: UnnamedExpr ( compound) ,
1332+ }
13141333 }
13151334
13161335 /// Convert an `Unnest` logical plan node to a `LATERAL FLATTEN(INPUT => expr, ...)`
0 commit comments