@@ -45,7 +45,7 @@ use crate::utils::UNNEST_PLACEHOLDER;
4545use datafusion_common:: {
4646 Column , DataFusionError , Result , ScalarValue , TableReference , assert_or_internal_err,
4747 internal_err, not_impl_err,
48- tree_node:: { TransformedResult , TreeNode } ,
48+ tree_node:: { TransformedResult , TreeNode , TreeNodeRecursion } ,
4949} ;
5050use datafusion_expr:: expr:: { OUTER_REFERENCE_COLUMN_PREFIX , UNNEST_COLUMN_PREFIX } ;
5151use datafusion_expr:: {
@@ -420,17 +420,33 @@ impl Unparser<'_> {
420420 . select_to_sql_recursively ( & new_plan, query, select, relation) ;
421421 }
422422
423- // Projection can be top-level plan for unnest relation
423+ // Projection can be top-level plan for unnest relation.
424424 // The projection generated by the `RecursiveUnnestRewriter` from a UNNEST relation will have
425425 // only one expression, which is the placeholder column generated by the rewriter.
426- let unnest_input_type = if p. expr . len ( ) == 1 {
427- Self :: check_unnest_placeholder_with_outer_ref ( & p. expr [ 0 ] )
426+ //
427+ // Two cases:
428+ // - "bare": the expression IS the placeholder (+ aliases):
429+ // `__unnest_placeholder(...) AS item`
430+ // - "wrapped": the placeholder is inside a function call:
431+ // `json_as_text(__unnest_placeholder(...), 'type') AS target_type`
432+ let ( unnest_input_type, placeholder_is_bare) = if p. expr . len ( ) == 1 {
433+ if let Some ( t) =
434+ Self :: check_unnest_placeholder_with_outer_ref ( & p. expr [ 0 ] )
435+ {
436+ ( Some ( t) , true )
437+ } else if let Some ( t) =
438+ Self :: find_unnest_placeholder_in_expr ( & p. expr [ 0 ] )
439+ {
440+ ( Some ( t) , false )
441+ } else {
442+ ( None , false )
443+ }
428444 } else {
429- None
445+ ( None , false )
430446 } ;
431447 // Extract the outermost user alias (e.g. "c1" from `UNNEST(...) AS c1`).
432448 // Internal aliases like "UNNEST(...)" are not user aliases.
433- let user_alias = if unnest_input_type. is_some ( ) {
449+ let user_alias = if placeholder_is_bare && unnest_input_type. is_some ( ) {
434450 Self :: extract_unnest_user_alias ( & p. expr [ 0 ] )
435451 } else {
436452 None
@@ -472,11 +488,15 @@ impl Unparser<'_> {
472488 relation. flatten ( flatten_relation) ;
473489
474490 if !select. already_projected ( ) {
475- let value_expr = self . build_flatten_value_select_item (
476- & alias_name,
477- user_alias. as_deref ( ) ,
478- ) ;
479- select. projection ( vec ! [ value_expr] ) ;
491+ if placeholder_is_bare {
492+ let value_expr = self . build_flatten_value_select_item (
493+ & alias_name,
494+ user_alias. as_deref ( ) ,
495+ ) ;
496+ select. projection ( vec ! [ value_expr] ) ;
497+ } else {
498+ self . reconstruct_select_statement ( plan, p, select) ?;
499+ }
480500 }
481501
482502 return self . select_to_sql_recursively (
@@ -509,11 +529,15 @@ impl Unparser<'_> {
509529 flatten. outer ( unnest. options . preserve_nulls ) ;
510530
511531 if !select. already_projected ( ) {
512- let value_expr = self . build_flatten_value_select_item (
513- & alias_name,
514- user_alias. as_deref ( ) ,
515- ) ;
516- select. projection ( vec ! [ value_expr] ) ;
532+ if placeholder_is_bare {
533+ let value_expr = self . build_flatten_value_select_item (
534+ & alias_name,
535+ user_alias. as_deref ( ) ,
536+ ) ;
537+ select. projection ( vec ! [ value_expr] ) ;
538+ } else {
539+ self . reconstruct_select_statement ( plan, p, select) ?;
540+ }
517541 }
518542
519543 // Recurse into the Unnest → inner source to set the primary
@@ -1356,6 +1380,39 @@ impl Unparser<'_> {
13561380 None
13571381 }
13581382
1383+ /// Recursively search the expression tree for an unnest placeholder column.
1384+ ///
1385+ /// Unlike [`Self::check_unnest_placeholder_with_outer_ref`] which only
1386+ /// matches when the placeholder IS the expression (modulo aliases), this
1387+ /// function finds placeholders buried inside function calls or other
1388+ /// transformations. For example:
1389+ ///
1390+ /// ```text
1391+ /// Alias("target_type",
1392+ /// json_as_text(
1393+ /// Column("__unnest_placeholder(...)"), ← found here
1394+ /// Literal("type")))
1395+ /// ```
1396+ fn find_unnest_placeholder_in_expr ( expr : & Expr ) -> Option < UnnestInputType > {
1397+ let mut result = None ;
1398+ let _ = expr. apply ( |e| {
1399+ if let Expr :: Column ( Column { name, .. } ) = e
1400+ && let Some ( prefix) = name. strip_prefix ( UNNEST_PLACEHOLDER )
1401+ {
1402+ result = if prefix
1403+ . starts_with ( & format ! ( "({OUTER_REFERENCE_COLUMN_PREFIX}(" ) )
1404+ {
1405+ Some ( UnnestInputType :: OuterReference )
1406+ } else {
1407+ Some ( UnnestInputType :: Scalar )
1408+ } ;
1409+ return Ok ( TreeNodeRecursion :: Stop ) ;
1410+ }
1411+ Ok ( TreeNodeRecursion :: Continue )
1412+ } ) ;
1413+ result
1414+ }
1415+
13591416 /// Extract the outermost user-provided alias from an unnest expression.
13601417 /// Returns `None` if the outermost alias is DataFusion's internal display
13611418 /// name (e.g. `UNNEST(make_array(...))`), or if there is no alias at all.
0 commit comments