yonatan-sevenai commented on code in PR #21593:
URL: https://github.com/apache/datafusion/pull/21593#discussion_r3100818826


##########
datafusion/sql/src/unparser/plan.rs:
##########
@@ -376,29 +406,112 @@ impl Unparser<'_> {
                         .select_to_sql_recursively(&new_plan, query, select, 
relation);
                 }
 
-                // Projection can be top-level plan for unnest relation
-                // The projection generated by the `RecursiveUnnestRewriter` 
from a UNNEST relation will have
-                // only one expression, which is the placeholder column 
generated by the rewriter.
-                let unnest_input_type = if p.expr.len() == 1 {
-                    Self::check_unnest_placeholder_with_outer_ref(&p.expr[0])
-                } else {
-                    None
-                };
+                // Projection can be top-level plan for unnest relation.
+                // The projection generated by the `RecursiveUnnestRewriter`
+                // will have at least one expression referencing an unnest
+                // placeholder column.
+                let unnest_input_type: Option<UnnestInputType> =
+                    p.expr.iter().find_map(Self::find_unnest_placeholder);
+
+                // --- UNNEST table factor path (BigQuery, etc.) ---
+                // Only fires for a single bare-placeholder projection.
+                // Uses peel_to_unnest_with_modifiers (rather than matching
+                // p.input directly) to handle Limit/Sort between Projection
+                // and Unnest.
                 if self.dialect.unnest_as_table_factor()
-                    && unnest_input_type.is_some()
-                    && let LogicalPlan::Unnest(unnest) = &p.input.as_ref()
+                    && p.expr.len() == 1
+                    && Self::is_bare_unnest_placeholder(&p.expr[0])
+                    && let Some((unnest, unnest_plan)) =
+                        self.peel_to_unnest_with_modifiers(p.input.as_ref(), 
query)?
                     && let Some(unnest_relation) =
                         self.try_unnest_to_table_factor_sql(unnest)?
                 {
                     relation.unnest(unnest_relation);
                     return self.select_to_sql_recursively(
-                        p.input.as_ref(),
+                        unnest_plan,
                         query,
                         select,
                         relation,
                     );
                 }
 
+                // --- Snowflake LATERAL FLATTEN path ---
+                // `peel_to_unnest_with_modifiers` walks through any
+                // intermediate Limit/Sort nodes (the optimizer can insert
+                // these between the Projection and the Unnest), applies
+                // their modifiers to the query, and returns the Unnest +
+                // the LogicalPlan ref to recurse into. This bypasses the
+                // normal Limit/Sort handlers which would wrap the subtree
+                // in a derived subquery.
+                // SELECT rendering is delegated to
+                // `reconstruct_select_statement`, which rewrites
+                // placeholder columns to `"_unnest"."VALUE"` via
+                // `unproject_unnest_expr_as_flatten_value` — this works
+                // for bare, wrapped, and multi-expression projections.
+                if self.dialect.unnest_as_lateral_flatten()
+                    && unnest_input_type.is_some()
+                    && let Some((unnest, unnest_plan)) =
+                        self.peel_to_unnest_with_modifiers(p.input.as_ref(), 
query)?
+                    && let Some(flatten) =
+                        self.try_unnest_to_lateral_flatten_sql(unnest)?
+                {
+                    let inner_projection =
+                        Self::peel_to_inner_projection(unnest.input.as_ref())
+                            .ok_or_else(|| {
+                                DataFusionError::Internal(format!(
+                                    "Unnest input is not a Projection: {:?}",
+                                    unnest.input
+                                ))
+                            })?;
+
+                    // An outer plan (e.g. a wrapping Projection) may have
+                    // already set SELECT columns; only set them once.
+                    if !select.already_projected() {
+                        self.reconstruct_select_statement(plan, p, select)?;
+                    }
+
+                    if matches!(
+                        inner_projection.input.as_ref(),
+                        LogicalPlan::EmptyRelation(_)
+                    ) {
+                        // Inline array (e.g. UNNEST([1,2,3])):
+                        // FLATTEN is the sole FROM source.
+                        relation.flatten(flatten);
+                        return self.select_to_sql_recursively(
+                            unnest_plan,
+                            query,
+                            select,
+                            relation,
+                        );
+                    }
+
+                    // Non-empty source (table, subquery, etc.):
+                    // recurse to set the primary FROM, then attach FLATTEN
+                    // as a CROSS JOIN.
+                    self.select_to_sql_recursively(unnest_plan, query, select, 
relation)?;
+
+                    let flatten_factor = flatten.build().map_err(|e| {
+                        DataFusionError::Internal(format!("Failed to build 
FLATTEN: {e}"))

Review Comment:
   Thanks!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to