@@ -38,13 +38,12 @@ use crate::unparser::extension_unparser::{
3838} ;
3939use crate :: unparser:: utils:: { find_unnest_node_until_relation, unproject_agg_exprs} ;
4040use crate :: unparser:: {
41- ast:: FLATTEN_DEFAULT_ALIAS , ast:: FlattenRelationBuilder , ast:: UnnestRelationBuilder ,
42- rewrite:: rewrite_qualify,
41+ ast:: FlattenRelationBuilder , ast:: UnnestRelationBuilder , rewrite:: rewrite_qualify,
4342} ;
4443use crate :: utils:: UNNEST_PLACEHOLDER ;
4544use datafusion_common:: {
4645 Column , DataFusionError , Result , ScalarValue , TableReference , assert_or_internal_err,
47- internal_err, not_impl_err,
46+ internal_datafusion_err , internal_err, not_impl_err,
4847 tree_node:: { TransformedResult , TreeNode , TreeNodeRecursion } ,
4948} ;
5049use datafusion_expr:: expr:: { OUTER_REFERENCE_COLUMN_PREFIX , UNNEST_COLUMN_PREFIX } ;
@@ -239,17 +238,12 @@ impl Unparser<'_> {
239238 let mut exprs = p. expr . clone ( ) ;
240239
241240 // If an Unnest node is found within the select, find and unproject the unnest column
241+ let flatten_alias = select. current_flatten_alias ( ) ;
242242 if let Some ( unnest) = find_unnest_node_within_select ( plan) {
243- if self . dialect . unnest_as_lateral_flatten ( ) {
243+ if let Some ( ref alias ) = flatten_alias {
244244 exprs = exprs
245245 . into_iter ( )
246- . map ( |e| {
247- unproject_unnest_expr_as_flatten_value (
248- e,
249- unnest,
250- FLATTEN_DEFAULT_ALIAS ,
251- )
252- } )
246+ . map ( |e| unproject_unnest_expr_as_flatten_value ( e, unnest, alias) )
253247 . collect :: < Result < Vec < _ > > > ( ) ?;
254248 } else {
255249 exprs = exprs
@@ -294,19 +288,17 @@ impl Unparser<'_> {
294288 select. projection ( items) ;
295289 }
296290 _ => {
297- let use_flatten = self . dialect . unnest_as_lateral_flatten ( ) ;
298291 let items = exprs
299292 . iter ( )
300293 . map ( |e| {
301294 // After unproject_unnest_expr_as_flatten_value, an
302295 // internal UNNEST display-name alias may still wrap
303296 // the rewritten _unnest.VALUE column. Replace it
304297 // with the bare FLATTEN VALUE select item.
305- if use_flatten && Self :: has_internal_unnest_alias ( e) {
306- return Ok ( self . build_flatten_value_select_item (
307- FLATTEN_DEFAULT_ALIAS ,
308- None ,
309- ) ) ;
298+ if let Some ( ref alias) = flatten_alias
299+ && Self :: has_internal_unnest_alias ( e)
300+ {
301+ return Ok ( self . build_flatten_value_select_item ( alias, None ) ) ;
310302 }
311303 self . select_item_to_sql ( e)
312304 } )
@@ -360,6 +352,105 @@ impl Unparser<'_> {
360352 }
361353 }
362354
355+ /// Projection unparsing when [`super::dialect::Dialect::unnest_as_lateral_flatten`] is enabled:
356+ /// Snowflake-style `LATERAL FLATTEN` for unnest (not other dialect spellings).
357+ ///
358+ /// [`Self::peel_to_unnest_with_modifiers`] walks through any intermediate
359+ /// Limit/Sort nodes (the optimizer can insert these between the Projection
360+ /// and the Unnest), applies their modifiers to the query, and returns the
361+ /// Unnest plus the [`LogicalPlan`] ref to recurse into. This bypasses the
362+ /// normal Limit/Sort handlers which would wrap the subtree in a derived
363+ /// subquery.
364+ ///
365+ /// SELECT rendering is delegated to [`Self::reconstruct_select_statement`],
366+ /// which rewrites placeholder columns to `alias."VALUE"` via
367+ /// [`unproject_unnest_expr_as_flatten_value`].
368+ ///
369+ /// Returns `Ok(true)` when this path fully handled the projection.
370+ fn try_projection_unnest_as_lateral_flatten (
371+ & self ,
372+ plan : & LogicalPlan ,
373+ p : & Projection ,
374+ query : & mut Option < QueryBuilder > ,
375+ select : & mut SelectBuilder ,
376+ relation : & mut RelationBuilder ,
377+ unnest_input_type : Option < & UnnestInputType > ,
378+ ) -> Result < bool > {
379+ // unnest_as_lateral_flatten: Snowflake LATERAL FLATTEN
380+ if self . dialect . unnest_as_lateral_flatten ( )
381+ && unnest_input_type. is_some ( )
382+ && let Some ( ( unnest, unnest_plan) ) =
383+ self . peel_to_unnest_with_modifiers ( p. input . as_ref ( ) , query) ?
384+ && let Some ( mut flatten) = self . try_unnest_to_lateral_flatten_sql ( unnest) ?
385+ {
386+ let inner_projection = Self :: peel_to_inner_projection ( unnest. input . as_ref ( ) )
387+ . ok_or_else ( || {
388+ internal_datafusion_err ! (
389+ "Unnest input is not a Projection: {:?}" ,
390+ unnest. input
391+ )
392+ } ) ?;
393+
394+ // Generate a unique alias for this FLATTEN so that
395+ // multiple unnests in the same query don't collide.
396+ // When the SELECT was already built by an outer Projection
397+ // (already_projected), it already called
398+ // next_flatten_alias(), so we reuse that alias.
399+ if !select. already_projected ( ) {
400+ let flatten_alias_name = select. next_flatten_alias ( ) ;
401+ flatten. alias ( Some ( ast:: TableAlias {
402+ name : Ident :: with_quote ( '"' , & flatten_alias_name) ,
403+ columns : vec ! [ ] ,
404+ explicit : true ,
405+ } ) ) ;
406+ self . reconstruct_select_statement ( plan, p, select) ?;
407+ } else if let Some ( alias) = select. current_flatten_alias ( ) {
408+ flatten. alias ( Some ( ast:: TableAlias {
409+ name : Ident :: with_quote ( '"' , & alias) ,
410+ columns : vec ! [ ] ,
411+ explicit : true ,
412+ } ) ) ;
413+ }
414+
415+ if matches ! (
416+ inner_projection. input. as_ref( ) ,
417+ LogicalPlan :: EmptyRelation ( _)
418+ ) {
419+ // Inline array (e.g. UNNEST([1,2,3])):
420+ // FLATTEN is the sole FROM source.
421+ relation. flatten ( flatten) ;
422+ self . select_to_sql_recursively ( unnest_plan, query, select, relation) ?;
423+ return Ok ( true ) ;
424+ }
425+
426+ // Non-empty source (table, subquery, etc.):
427+ // recurse to set the primary FROM, then attach FLATTEN
428+ // as a CROSS JOIN.
429+ self . select_to_sql_recursively ( unnest_plan, query, select, relation) ?;
430+
431+ let flatten_factor = flatten
432+ . build ( )
433+ . map_err ( |e| internal_datafusion_err ! ( "Failed to build FLATTEN: {e}" ) ) ?;
434+ let cross_join = ast:: Join {
435+ relation : flatten_factor,
436+ global : false ,
437+ join_operator : ast:: JoinOperator :: CrossJoin ( ast:: JoinConstraint :: None ) ,
438+ } ;
439+ if let Some ( mut from) = select. pop_from ( ) {
440+ from. push_join ( cross_join) ;
441+ select. push_from ( from) ;
442+ } else {
443+ let mut twj = TableWithJoinsBuilder :: default ( ) ;
444+ twj. push_join ( cross_join) ;
445+ select. push_from ( twj) ;
446+ }
447+
448+ return Ok ( true ) ;
449+ }
450+
451+ Ok ( false )
452+ }
453+
363454 #[ cfg_attr( feature = "recursive_protection" , recursive:: recursive) ]
364455 fn select_to_sql_recursively (
365456 & self ,
@@ -435,80 +526,14 @@ impl Unparser<'_> {
435526 ) ;
436527 }
437528
438- // --- Snowflake LATERAL FLATTEN path ---
439- // `peel_to_unnest_with_modifiers` walks through any
440- // intermediate Limit/Sort nodes (the optimizer can insert
441- // these between the Projection and the Unnest), applies
442- // their modifiers to the query, and returns the Unnest +
443- // the LogicalPlan ref to recurse into. This bypasses the
444- // normal Limit/Sort handlers which would wrap the subtree
445- // in a derived subquery.
446- // SELECT rendering is delegated to
447- // `reconstruct_select_statement`, which rewrites
448- // placeholder columns to `"_unnest"."VALUE"` via
449- // `unproject_unnest_expr_as_flatten_value` — this works
450- // for bare, wrapped, and multi-expression projections.
451- if self . dialect . unnest_as_lateral_flatten ( )
452- && unnest_input_type. is_some ( )
453- && let Some ( ( unnest, unnest_plan) ) =
454- self . peel_to_unnest_with_modifiers ( p. input . as_ref ( ) , query) ?
455- && let Some ( flatten) =
456- self . try_unnest_to_lateral_flatten_sql ( unnest) ?
457- {
458- let inner_projection =
459- Self :: peel_to_inner_projection ( unnest. input . as_ref ( ) )
460- . ok_or_else ( || {
461- DataFusionError :: Internal ( format ! (
462- "Unnest input is not a Projection: {:?}" ,
463- unnest. input
464- ) )
465- } ) ?;
466-
467- // An outer plan (e.g. a wrapping Projection) may have
468- // already set SELECT columns; only set them once.
469- if !select. already_projected ( ) {
470- self . reconstruct_select_statement ( plan, p, select) ?;
471- }
472-
473- if matches ! (
474- inner_projection. input. as_ref( ) ,
475- LogicalPlan :: EmptyRelation ( _)
476- ) {
477- // Inline array (e.g. UNNEST([1,2,3])):
478- // FLATTEN is the sole FROM source.
479- relation. flatten ( flatten) ;
480- return self . select_to_sql_recursively (
481- unnest_plan,
482- query,
483- select,
484- relation,
485- ) ;
486- }
487-
488- // Non-empty source (table, subquery, etc.):
489- // recurse to set the primary FROM, then attach FLATTEN
490- // as a CROSS JOIN.
491- self . select_to_sql_recursively ( unnest_plan, query, select, relation) ?;
492-
493- let flatten_factor = flatten. build ( ) . map_err ( |e| {
494- DataFusionError :: Internal ( format ! ( "Failed to build FLATTEN: {e}" ) )
495- } ) ?;
496- let cross_join = ast:: Join {
497- relation : flatten_factor,
498- global : false ,
499- join_operator : ast:: JoinOperator :: CrossJoin (
500- ast:: JoinConstraint :: None ,
501- ) ,
502- } ;
503- if let Some ( mut from) = select. pop_from ( ) {
504- from. push_join ( cross_join) ;
505- select. push_from ( from) ;
506- } else {
507- let mut twj = TableWithJoinsBuilder :: default ( ) ;
508- twj. push_join ( cross_join) ;
509- select. push_from ( twj) ;
510- }
511-
529+ if self . try_projection_unnest_as_lateral_flatten (
530+ plan,
531+ p,
532+ query,
533+ select,
534+ relation,
535+ unnest_input_type. as_ref ( ) ,
536+ ) ? {
512537 return Ok ( ( ) ) ;
513538 }
514539
@@ -536,6 +561,16 @@ impl Unparser<'_> {
536561 columns,
537562 ) ;
538563 }
564+ // For Snowflake FLATTEN: when the outer Projection has
565+ // UNNEST(...) display-name columns (from SELECT * / SELECT
566+ // UNNEST(...)), generate a flatten alias now so that
567+ // reconstruct_select_statement and the downstream Unnest
568+ // handler both use the same alias.
569+ if self . dialect . unnest_as_lateral_flatten ( )
570+ && p. expr . iter ( ) . any ( Self :: has_internal_unnest_alias)
571+ {
572+ select. next_flatten_alias ( ) ;
573+ }
539574 self . reconstruct_select_statement ( plan, p, select) ?;
540575 self . select_to_sql_recursively ( p. input . as_ref ( ) , query, select, relation)
541576 }
@@ -1135,9 +1170,19 @@ impl Unparser<'_> {
11351170 // relation here so the FROM clause is emitted.
11361171 if self . dialect . unnest_as_lateral_flatten ( )
11371172 && !relation. has_relation ( )
1138- && let Some ( flatten_relation) =
1173+ && let Some ( mut flatten_relation) =
11391174 self . try_unnest_to_lateral_flatten_sql ( unnest) ?
11401175 {
1176+ // Use the alias already generated by the Projection
1177+ // handler so SELECT items and the FLATTEN relation
1178+ // reference the same name.
1179+ if let Some ( alias) = select. current_flatten_alias ( ) {
1180+ flatten_relation. alias ( Some ( ast:: TableAlias {
1181+ name : Ident :: with_quote ( '"' , & alias) ,
1182+ columns : vec ! [ ] ,
1183+ explicit : true ,
1184+ } ) ) ;
1185+ }
11411186 relation. flatten ( flatten_relation) ;
11421187 }
11431188
0 commit comments