Skip to main content

datafusion_sql/unparser/
plan.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use super::{
19    Unparser,
20    ast::{
21        BuilderError, DerivedRelationBuilder, QueryBuilder, RelationBuilder,
22        SelectBuilder, TableRelationBuilder, TableWithJoinsBuilder,
23    },
24    rewrite::{
25        TableAliasRewriter, inject_column_aliases_into_subquery, normalize_union_schema,
26        rewrite_plan_for_sort_on_non_projected_fields,
27        subquery_alias_inner_query_and_columns,
28    },
29    utils::{
30        find_agg_node_within_select, find_unnest_node_within_select,
31        find_window_nodes_within_select, try_transform_to_simple_table_scan_with_filters,
32        unproject_sort_expr, unproject_unnest_expr,
33        unproject_unnest_expr_as_flatten_value, unproject_window_exprs,
34    },
35};
36use crate::unparser::extension_unparser::{
37    UnparseToStatementResult, UnparseWithinStatementResult,
38};
39use crate::unparser::utils::{find_unnest_node_until_relation, unproject_agg_exprs};
40use crate::unparser::{
41    ast::FlattenRelationBuilder, ast::UnnestRelationBuilder, rewrite::rewrite_qualify,
42};
43use crate::utils::UNNEST_PLACEHOLDER;
44use datafusion_common::{
45    Column, DFSchema, DataFusionError, Result, ScalarValue, TableReference,
46    assert_or_internal_err, internal_datafusion_err, internal_err, not_impl_err,
47    tree_node::{Transformed, TransformedResult, TreeNode, TreeNodeRecursion},
48    utils::combine_limit,
49};
50use datafusion_expr::expr::{OUTER_REFERENCE_COLUMN_PREFIX, UNNEST_COLUMN_PREFIX};
51use datafusion_expr::{
52    Aggregate, BinaryExpr, Distinct, Expr, FetchType, JoinConstraint, JoinType,
53    LogicalPlan, LogicalPlanBuilder, Operator, Projection, SkipType, Sort, SortExpr,
54    TableScan, Unnest, UserDefinedLogicalNode, Window, expr::Alias,
55};
56use sqlparser::ast::{self, Ident, OrderByKind, SetExpr, TableAliasColumnDef};
57use std::{sync::Arc, vec};
58
59/// Convert a DataFusion [`LogicalPlan`] to [`ast::Statement`]
60///
61/// This function is the opposite of [`SqlToRel::sql_statement_to_plan`] and can
62/// be used to, among other things, to convert `LogicalPlan`s to SQL strings.
63///
64/// # Errors
65///
66/// This function returns an error if the plan cannot be converted to SQL.
67///
68/// # See Also
69///
70/// * [`expr_to_sql`] for converting [`Expr`], a single expression to SQL
71///
72/// # Example
73/// ```
74/// use arrow::datatypes::{DataType, Field, Schema};
75/// use datafusion_expr::{col, logical_plan::table_scan};
76/// use datafusion_sql::unparser::plan_to_sql;
77/// let schema = Schema::new(vec![
78///     Field::new("id", DataType::Utf8, false),
79///     Field::new("value", DataType::Utf8, false),
80/// ]);
81/// // Scan 'table' and select columns 'id' and 'value'
82/// let plan = table_scan(Some("table"), &schema, None)
83///     .unwrap()
84///     .project(vec![col("id"), col("value")])
85///     .unwrap()
86///     .build()
87///     .unwrap();
88/// // convert to AST
89/// let sql = plan_to_sql(&plan).unwrap();
90/// // use the Display impl to convert to SQL text
91/// assert_eq!(
92///     sql.to_string(),
93///     "SELECT \"table\".id, \"table\".\"value\" FROM \"table\""
94/// )
95/// ```
96///
97/// [`SqlToRel::sql_statement_to_plan`]: crate::planner::SqlToRel::sql_statement_to_plan
98/// [`expr_to_sql`]: crate::unparser::expr_to_sql
99pub fn plan_to_sql(plan: &LogicalPlan) -> Result<ast::Statement> {
100    let unparser = Unparser::default();
101    unparser.plan_to_sql(plan)
102}
103
104/// Aggregate-expression scope for one rendered SELECT block.
105///
106/// When an aggregate's input is itself emitted as a derived subquery (a
107/// projection sits between the aggregate and its relation), the input columns
108/// are only reachable by that derived table's output names. Base-table
109/// qualifiers like `t.col` name a relation that is out of scope above the
110/// boundary, so emitting them produces SQL a strict engine rejects.
111///
112/// Every clause that renders an aggregate expression (SELECT / GROUP BY /
113/// HAVING / QUALIFY / ORDER BY) has to apply the same rule. Detect the
114/// boundary once here and reuse it, so the clauses can't drift apart (which is
115/// how earlier fixes left some clauses correct and others not).
116struct UnparserAggScope<'a> {
117    agg: &'a Aggregate,
118    /// `agg.input` renders as a derived projection, so out-of-scope qualifiers
119    /// must be stripped from expressions in this scope.
120    input_is_derived_projection: bool,
121}
122
123impl<'a> UnparserAggScope<'a> {
124    fn new(agg: &'a Aggregate) -> Self {
125        Self {
126            agg,
127            input_is_derived_projection: Unparser::contains_projection_before_relation(
128                agg.input.as_ref(),
129            ),
130        }
131    }
132
133    /// Prepare a projected column or predicate that still references the
134    /// aggregate by its output columns: unproject it back onto the aggregate
135    /// (and `windows`) expressions, then normalize it for this scope.
136    fn prepare(&self, expr: Expr, windows: Option<&[&Window]>) -> Result<Expr> {
137        self.normalize(unproject_agg_exprs(expr, self.agg, windows)?)
138    }
139
140    /// Normalize an expression that is already in aggregate form (group / aggr
141    /// exprs, or an unprojected sort expr): strip the qualifiers that fall out
142    /// of scope once the input is a derived projection. No-op otherwise.
143    fn normalize(&self, expr: Expr) -> Result<Expr> {
144        if self.input_is_derived_projection {
145            Unparser::strip_column_qualifiers_for_schema(
146                expr,
147                self.agg.input.schema().as_ref(),
148            )
149        } else {
150            Ok(expr)
151        }
152    }
153
154    /// Unproject a sort expression onto this aggregate, then normalize it so
155    /// ORDER BY uses the same scope as the other clauses.
156    fn prepare_sort_expr(
157        &self,
158        sort_expr: SortExpr,
159        input: &LogicalPlan,
160    ) -> Result<SortExpr> {
161        let mut sort_expr = unproject_sort_expr(sort_expr, Some(self.agg), input)?;
162        sort_expr.expr = self.normalize(sort_expr.expr)?;
163        Ok(sort_expr)
164    }
165}
166
167impl Unparser<'_> {
168    pub fn plan_to_sql(&self, plan: &LogicalPlan) -> Result<ast::Statement> {
169        let mut plan = normalize_union_schema(plan)?;
170        if !self.dialect.supports_qualify() {
171            plan = rewrite_qualify(plan)?;
172        }
173
174        match plan {
175            LogicalPlan::Projection(_)
176            | LogicalPlan::Filter(_)
177            | LogicalPlan::Window(_)
178            | LogicalPlan::Aggregate(_)
179            | LogicalPlan::Sort(_)
180            | LogicalPlan::Join(_)
181            | LogicalPlan::Repartition(_)
182            | LogicalPlan::Union(_)
183            | LogicalPlan::TableScan(_)
184            | LogicalPlan::EmptyRelation(_)
185            | LogicalPlan::Subquery(_)
186            | LogicalPlan::SubqueryAlias(_)
187            | LogicalPlan::Limit(_)
188            | LogicalPlan::Statement(_)
189            | LogicalPlan::Values(_)
190            | LogicalPlan::Distinct(_) => self.select_to_sql_statement(&plan),
191            LogicalPlan::Dml(_) => self.dml_to_sql(&plan),
192            LogicalPlan::Extension(extension) => {
193                self.extension_to_statement(extension.node.as_ref())
194            }
195            LogicalPlan::Explain(_)
196            | LogicalPlan::Analyze(_)
197            | LogicalPlan::Ddl(_)
198            | LogicalPlan::Copy(_)
199            | LogicalPlan::DescribeTable(_)
200            | LogicalPlan::RecursiveQuery(_)
201            | LogicalPlan::Unnest(_) => not_impl_err!("Unsupported plan: {plan:?}"),
202        }
203    }
204
205    /// Try to unparse a [UserDefinedLogicalNode] to a SQL statement.
206    /// If multiple unparsers are registered for the same [UserDefinedLogicalNode],
207    /// the first unparsing result will be returned.
208    fn extension_to_statement(
209        &self,
210        node: &dyn UserDefinedLogicalNode,
211    ) -> Result<ast::Statement> {
212        let mut statement = None;
213        for unparser in &self.extension_unparsers {
214            match unparser.unparse_to_statement(node, self)? {
215                UnparseToStatementResult::Modified(stmt) => {
216                    statement = Some(stmt);
217                    break;
218                }
219                UnparseToStatementResult::Unmodified => {}
220            }
221        }
222        if let Some(statement) = statement {
223            Ok(statement)
224        } else {
225            not_impl_err!("Unsupported extension node: {node:?}")
226        }
227    }
228
229    /// Try to unparse a [UserDefinedLogicalNode] to a SQL statement.
230    /// If multiple unparsers are registered for the same [UserDefinedLogicalNode],
231    /// the first unparser supporting the node will be used.
232    fn extension_to_sql(
233        &self,
234        node: &dyn UserDefinedLogicalNode,
235        query: &mut Option<&mut QueryBuilder>,
236        select: &mut Option<&mut SelectBuilder>,
237        relation: &mut Option<&mut RelationBuilder>,
238    ) -> Result<()> {
239        for unparser in &self.extension_unparsers {
240            match unparser.unparse(node, self, query, select, relation)? {
241                UnparseWithinStatementResult::Modified => return Ok(()),
242                UnparseWithinStatementResult::Unmodified => {}
243            }
244        }
245        not_impl_err!("Unsupported extension node: {node:?}")
246    }
247
248    fn select_to_sql_statement(&self, plan: &LogicalPlan) -> Result<ast::Statement> {
249        let mut query_builder = Some(QueryBuilder::default());
250
251        let body = self.select_to_sql_expr(plan, &mut query_builder)?;
252
253        let query = query_builder.unwrap().body(Box::new(body)).build()?;
254
255        Ok(ast::Statement::Query(Box::new(query)))
256    }
257
258    fn select_to_sql_expr(
259        &self,
260        plan: &LogicalPlan,
261        query: &mut Option<QueryBuilder>,
262    ) -> Result<SetExpr> {
263        let mut select_builder = SelectBuilder::default();
264        select_builder.push_from(TableWithJoinsBuilder::default());
265        let mut relation_builder = RelationBuilder::default();
266        self.select_to_sql_recursively(
267            plan,
268            query,
269            &mut select_builder,
270            &mut relation_builder,
271        )?;
272
273        // If we were able to construct a full body (i.e. UNION ALL), return it
274        if let Some(body) = query.as_mut().and_then(|q| q.take_body()) {
275            return Ok(*body);
276        }
277
278        // If no projection is set, add a wildcard projection to the select
279        // which will be translated to `SELECT *` in the SQL statement
280        if !select_builder.already_projected() {
281            select_builder.projection(vec![ast::SelectItem::Wildcard(
282                ast::WildcardAdditionalOptions::default(),
283            )]);
284        }
285
286        let mut twj = select_builder.pop_from().unwrap();
287        twj.relation(relation_builder);
288        select_builder.push_from(twj);
289
290        Ok(SetExpr::Select(Box::new(select_builder.build()?)))
291    }
292
293    /// Reconstructs a SELECT SQL statement from a logical plan by
294    /// unprojecting column expressions found in a [Projection] node. This
295    /// requires scanning the plan tree for relevant Aggregate and Window
296    /// nodes and matching column expressions to the appropriate agg or
297    /// window expressions.
298    ///
299    /// `fully_absorbed` reports whether the Projection arm was able to
300    /// absorb every `Sort`/`Limit` node between this Projection and the
301    /// Aggregate/Window into the current SELECT. When `false`, the
302    /// Aggregate/Window will end up in a derived subquery, so we fall
303    /// back to passthrough column references that resolve against that
304    /// subquery's output instead of unprojecting onto the original
305    /// aggregate expressions.
306    ///
307    /// Returns `true` if an Aggregate node was found and claimed for this
308    /// SELECT.
309    fn reconstruct_select_statement(
310        &self,
311        plan: &LogicalPlan,
312        p: &Projection,
313        select: &mut SelectBuilder,
314        fully_absorbed: bool,
315    ) -> Result<bool> {
316        let mut exprs = p.expr.clone();
317
318        // If an Unnest node is found within the select, find and unproject the unnest column
319        let flatten_alias = select.current_flatten_alias();
320        if let Some(unnest) = find_unnest_node_within_select(plan) {
321            if let Some(ref alias) = flatten_alias {
322                exprs = exprs
323                    .into_iter()
324                    .map(|e| unproject_unnest_expr_as_flatten_value(e, unnest, alias))
325                    .collect::<Result<Vec<_>>>()?;
326            } else {
327                exprs = exprs
328                    .into_iter()
329                    .map(|e| unproject_unnest_expr(e, unnest))
330                    .collect::<Result<Vec<_>>>()?;
331            }
332        };
333
334        // Rewrite column references that point to FLATTEN table aliases:
335        // in Snowflake, FLATTEN output is accessed via .VALUE, not the
336        // original column name.
337        if !select.flatten_table_aliases_empty() {
338            exprs = exprs
339                .into_iter()
340                .map(|e| {
341                    e.transform(|expr| {
342                        if let Expr::Column(ref col) = expr
343                            && let Some(ref relation) = col.relation
344                            && select.is_flatten_table_alias(relation.table())
345                        {
346                            return Ok(Transformed::yes(Expr::Column(Column::new(
347                                Some(relation.clone()),
348                                "VALUE",
349                            ))));
350                        }
351                        Ok(Transformed::no(expr))
352                    })
353                    .map(|t| t.data)
354                })
355                .collect::<Result<Vec<_>>>()?;
356        }
357
358        // When some Sort/Limit nodes between this Projection and the
359        // Aggregate/Window couldn't be absorbed into the current SELECT,
360        // the Aggregate/Window will live inside a derived subquery. In
361        // that case we use the passthrough projection path — column refs
362        // resolve against the derived subquery's output columns instead
363        // of being unprojected onto the original aggregate/window
364        // expressions.
365        let agg = if fully_absorbed {
366            find_agg_node_within_select(plan, true)
367        } else {
368            None
369        };
370        let window = if fully_absorbed {
371            find_window_nodes_within_select(plan, None, true)
372        } else {
373            None
374        };
375        match (agg, window) {
376            (Some(agg), window) => {
377                let window_option = window.as_deref();
378                let unparser_agg_scope = UnparserAggScope::new(agg);
379                let items = exprs
380                    .into_iter()
381                    .map(|proj_expr| {
382                        let unproj =
383                            unparser_agg_scope.prepare(proj_expr, window_option)?;
384                        self.select_item_to_sql(&unproj)
385                    })
386                    .collect::<Result<Vec<_>>>()?;
387
388                select.projection(items);
389                select.group_by(ast::GroupByExpr::Expressions(
390                    agg.group_expr
391                        .iter()
392                        .cloned()
393                        .map(|expr| {
394                            self.expr_to_sql(&unparser_agg_scope.normalize(expr)?)
395                        })
396                        .collect::<Result<Vec<_>>>()?,
397                    vec![],
398                ));
399                Ok(true)
400            }
401            (None, Some(window)) => {
402                let items = exprs
403                    .into_iter()
404                    .map(|proj_expr| {
405                        let unproj = unproject_window_exprs(proj_expr, &window)?;
406                        self.select_item_to_sql(&unproj)
407                    })
408                    .collect::<Result<Vec<_>>>()?;
409
410                select.projection(items);
411                Ok(false)
412            }
413            _ => {
414                let items = exprs
415                    .iter()
416                    .map(|e| {
417                        // After unproject_unnest_expr_as_flatten_value, an
418                        // internal UNNEST display-name alias may still wrap
419                        // the rewritten _unnest.VALUE column. Replace it
420                        // with the bare FLATTEN VALUE select item.
421                        if let Some(ref alias) = flatten_alias
422                            && Self::has_internal_unnest_alias(e)
423                        {
424                            return Ok(self.build_flatten_value_select_item(alias, None));
425                        }
426                        self.select_item_to_sql(e)
427                    })
428                    .collect::<Result<Vec<_>>>()?;
429                select.projection(items);
430                Ok(false)
431            }
432        }
433    }
434
435    fn contains_projection_before_relation(plan: &LogicalPlan) -> bool {
436        match plan {
437            LogicalPlan::Projection(_) => true,
438            LogicalPlan::TableScan(_)
439            | LogicalPlan::Subquery(_)
440            | LogicalPlan::SubqueryAlias(_)
441            | LogicalPlan::Join(_)
442            | LogicalPlan::EmptyRelation(_)
443            | LogicalPlan::Values(_) => false,
444            _ => {
445                let inputs = plan.inputs();
446                matches!(
447                    inputs.as_slice(),
448                    [input] if Self::contains_projection_before_relation(input)
449                )
450            }
451        }
452    }
453
454    fn contains_aggregate_before_relation(plan: &LogicalPlan) -> bool {
455        match plan {
456            LogicalPlan::Aggregate(_) => true,
457            LogicalPlan::TableScan(_)
458            | LogicalPlan::Subquery(_)
459            | LogicalPlan::SubqueryAlias(_)
460            | LogicalPlan::Join(_)
461            | LogicalPlan::EmptyRelation(_)
462            | LogicalPlan::Values(_) => false,
463            _ => {
464                let inputs = plan.inputs();
465                matches!(
466                    inputs.as_slice(),
467                    [input] if Self::contains_aggregate_before_relation(input)
468                )
469            }
470        }
471    }
472
473    /// Unproject a sort expression; normalize it when the sort is above an
474    /// aggregate, otherwise just unproject (no scope to normalize against).
475    fn unproject_sort_expr_in_scope(
476        sort_expr: SortExpr,
477        agg: Option<&Aggregate>,
478        input: &LogicalPlan,
479    ) -> Result<SortExpr> {
480        match agg {
481            Some(agg) => UnparserAggScope::new(agg).prepare_sort_expr(sort_expr, input),
482            None => unproject_sort_expr(sort_expr, None, input),
483        }
484    }
485
486    fn derive(
487        &self,
488        plan: &LogicalPlan,
489        relation: &mut RelationBuilder,
490        alias: Option<ast::TableAlias>,
491        lateral: bool,
492    ) -> Result<()> {
493        let mut derived_builder = DerivedRelationBuilder::default();
494        derived_builder.lateral(lateral).alias(alias).subquery({
495            let inner_statement = self.plan_to_sql(plan)?;
496            if let ast::Statement::Query(inner_query) = inner_statement {
497                inner_query
498            } else {
499                return internal_err!(
500                    "Subquery must be a Query, but found {inner_statement:?}"
501                );
502            }
503        });
504        relation.derived(derived_builder);
505
506        Ok(())
507    }
508
509    fn derive_with_dialect_alias(
510        &self,
511        alias: &str,
512        plan: &LogicalPlan,
513        relation: &mut RelationBuilder,
514        lateral: bool,
515        columns: Vec<Ident>,
516    ) -> Result<()> {
517        if self.dialect.requires_derived_table_alias() || !columns.is_empty() {
518            self.derive(
519                plan,
520                relation,
521                Some(self.new_table_alias(alias.to_string(), columns)),
522                lateral,
523            )
524        } else {
525            self.derive(plan, relation, None, lateral)
526        }
527    }
528
529    /// Projection unparsing when [`super::dialect::Dialect::unnest_as_lateral_flatten`] is enabled:
530    /// Snowflake-style `LATERAL FLATTEN` for unnest (not other dialect spellings).
531    ///
532    /// [`Self::peel_to_unnest_with_modifiers`] walks through any intermediate
533    /// Limit/Sort nodes (the optimizer can insert these between the Projection
534    /// and the Unnest), applies their modifiers to the query, and returns the
535    /// Unnest plus the [`LogicalPlan`] ref to recurse into. This bypasses the
536    /// normal Limit/Sort handlers which would wrap the subtree in a derived
537    /// subquery.
538    ///
539    /// SELECT rendering is delegated to [`Self::reconstruct_select_statement`],
540    /// which rewrites placeholder columns to `alias."VALUE"` via
541    /// [`unproject_unnest_expr_as_flatten_value`].
542    ///
543    /// Returns `Ok(true)` when this path fully handled the projection.
544    fn try_projection_unnest_as_lateral_flatten(
545        &self,
546        plan: &LogicalPlan,
547        p: &Projection,
548        query: &mut Option<QueryBuilder>,
549        select: &mut SelectBuilder,
550        relation: &mut RelationBuilder,
551        unnest_input_type: Option<&UnnestInputType>,
552    ) -> Result<bool> {
553        // unnest_as_lateral_flatten: Snowflake LATERAL FLATTEN
554        //
555        // Generate the alias up front so that peel_to_unnest_with_modifiers
556        // can rewrite ORDER BY placeholder columns to alias.VALUE.
557        if self.dialect.unnest_as_lateral_flatten() && unnest_input_type.is_some() {
558            let flatten_alias_name = if !select.already_projected() {
559                select.next_flatten_alias()
560            } else {
561                select
562                    .current_flatten_alias()
563                    .unwrap_or_else(|| select.next_flatten_alias())
564            };
565
566            if let Some((unnest, unnest_plan)) = self.peel_to_unnest_with_modifiers(
567                p.input.as_ref(),
568                query,
569                Some(&flatten_alias_name),
570            )? && let Some(mut flatten) =
571                self.try_unnest_to_lateral_flatten_sql(unnest)?
572            {
573                let inner_projection = Self::peel_to_inner_projection(
574                    unnest.input.as_ref(),
575                )
576                .ok_or_else(|| {
577                    internal_datafusion_err!(
578                        "Unnest input is not a Projection: {:?}",
579                        unnest.input
580                    )
581                })?;
582
583                flatten.alias(Some(ast::TableAlias {
584                    name: Ident::with_quote('"', &flatten_alias_name),
585                    columns: vec![],
586                    explicit: true,
587                    at: None,
588                }));
589
590                if !select.already_projected() {
591                    self.reconstruct_select_statement(plan, p, select, true)?;
592                }
593
594                if matches!(
595                    inner_projection.input.as_ref(),
596                    LogicalPlan::EmptyRelation(_)
597                ) {
598                    relation.flatten(flatten);
599                    self.select_to_sql_recursively(unnest_plan, query, select, relation)?;
600                    return Ok(true);
601                }
602
603                self.select_to_sql_recursively(unnest_plan, query, select, relation)?;
604
605                let flatten_factor = flatten.build().map_err(|e| {
606                    internal_datafusion_err!("Failed to build FLATTEN: {e}")
607                })?;
608                let cross_join = ast::Join {
609                    relation: flatten_factor,
610                    global: false,
611                    join_operator: ast::JoinOperator::CrossJoin(
612                        ast::JoinConstraint::None,
613                    ),
614                };
615                if let Some(mut from) = select.pop_from() {
616                    from.push_join(cross_join);
617                    select.push_from(from);
618                } else {
619                    let mut twj = TableWithJoinsBuilder::default();
620                    twj.push_join(cross_join);
621                    select.push_from(twj);
622                }
623
624                return Ok(true);
625            }
626        }
627
628        Ok(false)
629    }
630
631    fn project_window_output(
632        &self,
633        window_expr: &[Expr],
634        select: &mut SelectBuilder,
635        agg: Option<&Aggregate>,
636    ) -> Result<()> {
637        let mut items = if select.already_projected() {
638            select.pop_projections()
639        } else {
640            vec![ast::SelectItem::Wildcard(
641                ast::WildcardAdditionalOptions::default(),
642            )]
643        };
644
645        items.extend(
646            window_expr
647                .iter()
648                .map(|expr| {
649                    // No normalization: this agg branch is only reachable from a
650                    // hand-built plan. SQL wraps windows in a projection, which
651                    // reconstruct_select_statement handles (and normalizes).
652                    let expr = if let Some(agg) = agg {
653                        unproject_agg_exprs(expr.clone(), agg, None)?
654                    } else {
655                        expr.clone()
656                    };
657                    self.select_item_to_sql(&expr)
658                })
659                .collect::<Result<Vec<_>>>()?,
660        );
661        select.projection(items);
662
663        Ok(())
664    }
665
666    fn window_input_requires_derived_subquery(plan: &LogicalPlan) -> bool {
667        // These operators either produce a SELECT list or apply SQL clauses
668        // that are evaluated after window functions in a single SELECT block.
669        // Keep them below the Window node by emitting a derived table.
670        matches!(
671            plan,
672            LogicalPlan::Projection(_)
673                | LogicalPlan::Distinct(_)
674                | LogicalPlan::Limit(_)
675                | LogicalPlan::Sort(_)
676                | LogicalPlan::Union(_)
677        )
678    }
679
680    fn window_to_sql_with_derived_input(
681        &self,
682        window: &Window,
683        select: &mut SelectBuilder,
684        relation: &mut RelationBuilder,
685    ) -> Result<()> {
686        let input_alias = "derived_window_input";
687        self.derive(
688            window.input.as_ref(),
689            relation,
690            Some(self.new_table_alias(input_alias.to_string(), vec![])),
691            false,
692        )?;
693
694        let input_schema = window.input.schema();
695        let mut alias_rewriter = TableAliasRewriter {
696            table_schema: input_schema.as_ref(),
697            alias_name: TableReference::bare(input_alias),
698            rewrite_unqualified: true,
699        };
700        let window_expr = window
701            .window_expr
702            .iter()
703            .map(|expr| expr.clone().rewrite(&mut alias_rewriter).data())
704            .collect::<Result<Vec<_>>>()?;
705
706        self.project_window_output(&window_expr, select, None)
707    }
708
709    fn extract_join_input_table_scan_filters(
710        plan: &Arc<LogicalPlan>,
711        table_scan_filters: &mut Vec<Expr>,
712    ) -> Result<Arc<LogicalPlan>> {
713        match try_transform_to_simple_table_scan_with_filters(plan)? {
714            Some((plan, filters)) => {
715                table_scan_filters.extend(filters);
716                Ok(Arc::new(plan))
717            }
718            None => Ok(Arc::clone(plan)),
719        }
720    }
721
722    #[cfg_attr(feature = "recursive_protection", recursive::recursive)]
723    fn select_to_sql_recursively(
724        &self,
725        plan: &LogicalPlan,
726        query: &mut Option<QueryBuilder>,
727        select: &mut SelectBuilder,
728        relation: &mut RelationBuilder,
729    ) -> Result<()> {
730        match plan {
731            LogicalPlan::TableScan(scan) => {
732                if let Some(unparsed_table_scan) = self.unparse_table_scan_pushdown(
733                    plan,
734                    None,
735                    select.already_projected(),
736                )? {
737                    return self.select_to_sql_recursively(
738                        &unparsed_table_scan,
739                        query,
740                        select,
741                        relation,
742                    );
743                }
744                let mut builder = TableRelationBuilder::default();
745                let mut table_parts = vec![];
746                if let Some(catalog_name) = scan.table_name.catalog() {
747                    table_parts
748                        .push(self.new_ident_quoted_if_needs(catalog_name.to_string()));
749                }
750                if let Some(schema_name) = scan.table_name.schema() {
751                    table_parts
752                        .push(self.new_ident_quoted_if_needs(schema_name.to_string()));
753                }
754                table_parts.push(
755                    self.new_ident_quoted_if_needs(scan.table_name.table().to_string()),
756                );
757                builder.name(ast::ObjectName::from(table_parts));
758                relation.table(builder);
759
760                Ok(())
761            }
762            LogicalPlan::Projection(p) => {
763                if let Some(new_plan) = rewrite_plan_for_sort_on_non_projected_fields(p) {
764                    return self
765                        .select_to_sql_recursively(&new_plan, query, select, relation);
766                }
767
768                // Projection can be top-level plan for unnest relation.
769                // The projection generated by the `RecursiveUnnestRewriter`
770                // will have at least one expression referencing an unnest
771                // placeholder column.
772                let unnest_input_type: Option<UnnestInputType> =
773                    p.expr.iter().find_map(Self::find_unnest_placeholder);
774
775                // --- UNNEST table factor path (BigQuery, etc.) ---
776                // Only fires for a single bare-placeholder projection.
777                // Uses peel_to_unnest_with_modifiers (rather than matching
778                // p.input directly) to handle Limit/Sort between Projection
779                // and Unnest.
780                if self.dialect.unnest_as_table_factor()
781                    && p.expr.len() == 1
782                    && Self::is_bare_unnest_placeholder(&p.expr[0])
783                    && let Some((unnest, unnest_plan)) =
784                        self.peel_to_unnest_with_modifiers(p.input.as_ref(), query, None)?
785                    && let Some(unnest_relation) =
786                        self.try_unnest_to_table_factor_sql(unnest)?
787                {
788                    relation.unnest(unnest_relation);
789                    return self.select_to_sql_recursively(
790                        unnest_plan,
791                        query,
792                        select,
793                        relation,
794                    );
795                }
796
797                if self.try_projection_unnest_as_lateral_flatten(
798                    plan,
799                    p,
800                    query,
801                    select,
802                    relation,
803                    unnest_input_type.as_ref(),
804                )? {
805                    return Ok(());
806                }
807
808                // If it's a unnest projection, we should provide the table column alias
809                // to provide a column name for the unnest relation.
810                let columns = if unnest_input_type.is_some() {
811                    p.expr
812                        .iter()
813                        .map(|e| {
814                            self.new_ident_quoted_if_needs(e.schema_name().to_string())
815                        })
816                        .collect()
817                } else {
818                    vec![]
819                };
820                // Projection can be top-level plan for derived table
821                if select.already_projected() {
822                    return self.derive_with_dialect_alias(
823                        "derived_projection",
824                        plan,
825                        relation,
826                        unnest_input_type
827                            .filter(|t| matches!(t, UnnestInputType::OuterReference))
828                            .is_some(),
829                        columns,
830                    );
831                }
832                // For Snowflake FLATTEN: when the outer Projection has
833                // UNNEST(...) display-name columns (from SELECT * / SELECT
834                // UNNEST(...)), generate a flatten alias now so that
835                // reconstruct_select_statement and the downstream Unnest
836                // handler both use the same alias.
837                if self.dialect.unnest_as_lateral_flatten()
838                    && p.expr.iter().any(Self::has_internal_unnest_alias)
839                {
840                    select.next_flatten_alias();
841                }
842                // Pre-register FLATTEN table aliases from SubqueryAlias
843                // nodes in the plan tree so that
844                // reconstruct_select_statement can rewrite column
845                // references (e.g. a.col → a.VALUE) before the
846                // SubqueryAlias handler runs.
847                if self.dialect.unnest_as_lateral_flatten() {
848                    Self::collect_flatten_aliases(p.input.as_ref(), select);
849                }
850                // Walk down through consecutive Sort/Limit nodes, greedily
851                // absorbing what can be folded into the SELECT we're
852                // building around the Aggregate. A single SQL SELECT can
853                // carry at most one `ORDER BY` (applied before `LIMIT`),
854                // so the safe shape between us and the Aggregate is
855                // `Limit* Sort?` (outer→inner). We stop at the first node
856                // that would violate this; that node becomes the
857                // subquery boundary, and recursion (seeing
858                // `already_projected = true`) wraps it in a derived
859                // relation. If we walk all the way to a non-Sort/non-Limit
860                // terminator, the entire chain folds into one SELECT.
861                //
862                // Stacked Sorts with nothing between them collapse to the
863                // outermost — the same simplification `EnforceSorting`
864                // applies on the physical side — but only when no Limit
865                // has been absorbed since the previous Sort, since the
866                // inner Sort would otherwise be determining which rows
867                // the Limit keeps.
868                //
869                // The fold is collected here without touching `query`
870                // (apart from non-literal direct Limits, which don't
871                // depend on projection form). Once we know whether every
872                // Sort/Limit was absorbed we can pick the right
873                // projection form and emit `ORDER BY` with or without
874                // unprojection.
875                let mut cur = p.input.as_ref();
876                let mut absorbed_sort: Option<&Sort> = None;
877                let mut combined_skip: usize = 0;
878                let mut combined_fetch: Option<usize> = None;
879                let mut have_combined_limit = false;
880                let mut have_direct_limit = false;
881                let mut have_order_by = false;
882                loop {
883                    match cur {
884                        LogicalPlan::Limit(limit) => {
885                            if have_order_by {
886                                // Limit-below-Sort: `ORDER BY … LIMIT N`
887                                // would apply the sort first, but the
888                                // logical plan applies the Limit first.
889                                break;
890                            }
891                            let skip_lit = limit.get_skip_type()?;
892                            let fetch_lit = limit.get_fetch_type()?;
893                            match (skip_lit, fetch_lit) {
894                                (SkipType::Literal(s), FetchType::Literal(f)) => {
895                                    if have_direct_limit {
896                                        break;
897                                    }
898                                    if have_combined_limit {
899                                        // outer = already-accumulated;
900                                        // inner = this Limit. Same merge
901                                        // rule as the optimizer.
902                                        let (cs, cf) = combine_limit(
903                                            combined_skip,
904                                            combined_fetch,
905                                            s,
906                                            f,
907                                        );
908                                        combined_skip = cs;
909                                        combined_fetch = cf;
910                                    } else {
911                                        combined_skip = s;
912                                        combined_fetch = f;
913                                        have_combined_limit = true;
914                                    }
915                                }
916                                _ => {
917                                    if have_combined_limit || have_direct_limit {
918                                        // Cannot safely merge a
919                                        // non-literal Limit with a prior
920                                        // one; let recursion handle it.
921                                        break;
922                                    }
923                                    let Some(query_ref) = query.as_mut() else {
924                                        return internal_err!(
925                                            "Limit operator only valid in a statement context."
926                                        );
927                                    };
928                                    if let Some(fetch) = &limit.fetch {
929                                        query_ref.limit(Some(self.expr_to_sql(fetch)?));
930                                    }
931                                    if let Some(skip) = &limit.skip {
932                                        query_ref.offset(Some(ast::Offset {
933                                            rows: ast::OffsetRows::None,
934                                            value: self.expr_to_sql(skip)?,
935                                        }));
936                                    }
937                                    have_direct_limit = true;
938                                }
939                            }
940                            cur = limit.input.as_ref();
941                        }
942                        LogicalPlan::Sort(sort) if sort.fetch.is_some() => {
943                            // `Sort { fetch }` is logically
944                            // `Limit(fetch) -> Sort`. Try to absorb the
945                            // virtual Limit first; only if that succeeds
946                            // do we absorb the Sort. Otherwise we'd
947                            // silently drop the fetch.
948                            let fetch = sort.fetch.expect("guarded above");
949                            if have_order_by {
950                                // The virtual Limit would sit below an
951                                // already-absorbed outer Sort.
952                                break;
953                            }
954                            if have_direct_limit {
955                                // Cannot combine a literal fetch with a
956                                // non-literal direct Limit; let the
957                                // derived subquery preserve both.
958                                break;
959                            }
960                            if have_combined_limit {
961                                let (cs, cf) = combine_limit(
962                                    combined_skip,
963                                    combined_fetch,
964                                    0,
965                                    Some(fetch),
966                                );
967                                combined_skip = cs;
968                                combined_fetch = cf;
969                            } else {
970                                combined_skip = 0;
971                                combined_fetch = Some(fetch);
972                                have_combined_limit = true;
973                            }
974                            // Now the Sort itself. We know
975                            // `!have_order_by` from the check above.
976                            absorbed_sort = Some(sort);
977                            have_order_by = true;
978                            cur = sort.input.as_ref();
979                        }
980                        LogicalPlan::Sort(sort) => {
981                            // Sort without `fetch`.
982                            if have_order_by {
983                                // Outer Sort already absorbed; the inner
984                                // Sort is reordered by it and is
985                                // conventionally dropped, matching
986                                // `EnforceSorting` on the physical side.
987                                cur = sort.input.as_ref();
988                                continue;
989                            }
990                            absorbed_sort = Some(sort);
991                            have_order_by = true;
992                            cur = sort.input.as_ref();
993                        }
994                        _ => break,
995                    }
996                }
997
998                // `fully_absorbed` is the bottom-up algorithm's "walked
999                // all the way to the terminator without stopping": the
1000                // Aggregate/Window will live in the same SELECT as this
1001                // Projection, so we can unproject sort exprs and let
1002                // `reconstruct_select_statement` claim it.
1003                let fully_absorbed =
1004                    !matches!(cur, LogicalPlan::Limit(_) | LogicalPlan::Sort(_));
1005                let found_agg =
1006                    self.reconstruct_select_statement(plan, p, select, fully_absorbed)?;
1007
1008                // Whether to bother emitting the absorbed clauses: only
1009                // if there's an Aggregate either claimed in this SELECT
1010                // or about to live in a derived subquery below us. If
1011                // there's nothing aggregate-like to fold over, fall
1012                // through and let the normal recursion handle the
1013                // Projection's input.
1014                let agg_below =
1015                    !fully_absorbed && find_agg_node_within_select(plan, true).is_some();
1016                if !(found_agg || agg_below) {
1017                    return self.select_to_sql_recursively(
1018                        p.input.as_ref(),
1019                        query,
1020                        select,
1021                        relation,
1022                    );
1023                }
1024
1025                if let Some(sort) = absorbed_sort {
1026                    let Some(query_ref) = query.as_mut() else {
1027                        return internal_err!(
1028                            "Sort operator only valid in a statement context."
1029                        );
1030                    };
1031                    let sort_exprs: Vec<SortExpr> = if fully_absorbed {
1032                        let agg =
1033                            find_agg_node_within_select(plan, select.already_projected());
1034                        sort.expr
1035                            .iter()
1036                            .map(|sort_expr| {
1037                                Self::unproject_sort_expr_in_scope(
1038                                    sort_expr.clone(),
1039                                    agg,
1040                                    sort.input.as_ref(),
1041                                )
1042                            })
1043                            .collect::<Result<Vec<_>>>()?
1044                    } else {
1045                        sort.expr.clone()
1046                    };
1047                    query_ref.order_by(self.sorts_to_sql(&sort_exprs)?);
1048                }
1049                if have_combined_limit {
1050                    let Some(query_ref) = query.as_mut() else {
1051                        return internal_err!(
1052                            "Limit operator only valid in a statement context."
1053                        );
1054                    };
1055                    if let Some(fetch) = combined_fetch {
1056                        query_ref.limit(Some(ast::Expr::value(ast::Value::Number(
1057                            fetch.to_string(),
1058                            false,
1059                        ))));
1060                    }
1061                    if combined_skip > 0 {
1062                        query_ref.offset(Some(ast::Offset {
1063                            rows: ast::OffsetRows::None,
1064                            value: ast::Expr::value(ast::Value::Number(
1065                                combined_skip.to_string(),
1066                                false,
1067                            )),
1068                        }));
1069                    }
1070                }
1071
1072                self.select_to_sql_recursively(cur, query, select, relation)
1073            }
1074            LogicalPlan::Filter(filter) => {
1075                let window = find_window_nodes_within_select(
1076                    plan,
1077                    None,
1078                    select.already_projected(),
1079                );
1080                let agg = find_agg_node_within_select(plan, select.already_projected());
1081
1082                if let (Some(window), true) =
1083                    (window.as_deref(), self.dialect.supports_qualify())
1084                {
1085                    let mut unprojected =
1086                        unproject_window_exprs(filter.predicate.clone(), window)?;
1087                    if let Some(agg) = agg {
1088                        unprojected =
1089                            UnparserAggScope::new(agg).prepare(unprojected, None)?;
1090                    }
1091                    let filter_expr = self.expr_to_sql(&unprojected)?;
1092                    select.qualify(Some(filter_expr));
1093                } else if let Some(agg) = agg {
1094                    let unprojected = UnparserAggScope::new(agg)
1095                        .prepare(filter.predicate.clone(), None)?;
1096                    let filter_expr = self.expr_to_sql(&unprojected)?;
1097                    select.having(Some(filter_expr));
1098                } else {
1099                    let filter_expr = self.expr_to_sql(&filter.predicate)?;
1100                    select.selection(Some(filter_expr));
1101                }
1102
1103                self.select_to_sql_recursively(
1104                    filter.input.as_ref(),
1105                    query,
1106                    select,
1107                    relation,
1108                )
1109            }
1110            LogicalPlan::Limit(limit) => {
1111                // Limit can be top-level plan for derived table
1112                if select.already_projected() {
1113                    return self.derive_with_dialect_alias(
1114                        "derived_limit",
1115                        plan,
1116                        relation,
1117                        false,
1118                        vec![],
1119                    );
1120                }
1121                if let Some(fetch) = &limit.fetch {
1122                    let Some(query) = query.as_mut() else {
1123                        return internal_err!(
1124                            "Limit operator only valid in a statement context."
1125                        );
1126                    };
1127                    query.limit(Some(self.expr_to_sql(fetch)?));
1128                }
1129
1130                if let Some(skip) = &limit.skip {
1131                    let Some(query) = query.as_mut() else {
1132                        return internal_err!(
1133                            "Offset operator only valid in a statement context."
1134                        );
1135                    };
1136
1137                    query.offset(Some(ast::Offset {
1138                        rows: ast::OffsetRows::None,
1139                        value: self.expr_to_sql(skip)?,
1140                    }));
1141                }
1142
1143                self.select_to_sql_recursively(
1144                    limit.input.as_ref(),
1145                    query,
1146                    select,
1147                    relation,
1148                )
1149            }
1150            LogicalPlan::Sort(sort) => {
1151                // Sort can be top-level plan for derived table
1152                if select.already_projected() {
1153                    return self.derive_with_dialect_alias(
1154                        "derived_sort",
1155                        plan,
1156                        relation,
1157                        false,
1158                        vec![],
1159                    );
1160                }
1161
1162                let Some(query_ref) = query else {
1163                    return internal_err!(
1164                        "Sort operator only valid in a statement context."
1165                    );
1166                };
1167
1168                if let Some(fetch) = sort.fetch {
1169                    query_ref.limit(Some(ast::Expr::value(ast::Value::Number(
1170                        fetch.to_string(),
1171                        false,
1172                    ))));
1173                };
1174
1175                let agg = find_agg_node_within_select(plan, select.already_projected());
1176                // unproject sort expressions
1177                let sort_exprs: Vec<SortExpr> = sort
1178                    .expr
1179                    .iter()
1180                    .map(|sort_expr| {
1181                        Self::unproject_sort_expr_in_scope(
1182                            sort_expr.clone(),
1183                            agg,
1184                            sort.input.as_ref(),
1185                        )
1186                    })
1187                    .collect::<Result<Vec<_>>>()?;
1188
1189                query_ref.order_by(self.sorts_to_sql(&sort_exprs)?);
1190
1191                self.select_to_sql_recursively(
1192                    sort.input.as_ref(),
1193                    query,
1194                    select,
1195                    relation,
1196                )
1197            }
1198            LogicalPlan::Aggregate(agg) => {
1199                // Aggregation can be already handled in the projection case
1200                if !select.already_projected() {
1201                    let unparser_agg_scope = UnparserAggScope::new(agg);
1202                    // The query returns aggregate and group expressions. If that weren't the case,
1203                    // the aggregate would have been placed inside a projection, making the check above^ false
1204                    let exprs: Vec<_> = agg
1205                        .aggr_expr
1206                        .iter()
1207                        .chain(agg.group_expr.iter())
1208                        .cloned()
1209                        .map(|expr| {
1210                            self.select_item_to_sql(&unparser_agg_scope.normalize(expr)?)
1211                        })
1212                        .collect::<Result<Vec<_>>>()?;
1213                    select.projection(exprs);
1214
1215                    select.group_by(ast::GroupByExpr::Expressions(
1216                        agg.group_expr
1217                            .iter()
1218                            .cloned()
1219                            .map(|expr| {
1220                                self.expr_to_sql(&unparser_agg_scope.normalize(expr)?)
1221                            })
1222                            .collect::<Result<Vec<_>>>()?,
1223                        vec![],
1224                    ));
1225                } else if Self::contains_aggregate_before_relation(agg.input.as_ref()) {
1226                    return self.derive_with_dialect_alias(
1227                        "derived_aggregate",
1228                        agg.input.as_ref(),
1229                        relation,
1230                        false,
1231                        vec![],
1232                    );
1233                }
1234
1235                self.select_to_sql_recursively(
1236                    agg.input.as_ref(),
1237                    query,
1238                    select,
1239                    relation,
1240                )
1241            }
1242            LogicalPlan::Distinct(distinct) => {
1243                // Distinct can be top-level plan for derived table
1244                if select.already_projected() {
1245                    return self.derive_with_dialect_alias(
1246                        "derived_distinct",
1247                        plan,
1248                        relation,
1249                        false,
1250                        vec![],
1251                    );
1252                }
1253
1254                // If this distinct is the parent of a Union and we're in a query context,
1255                // then we need to unparse as a `UNION` rather than a `UNION ALL`.
1256                if let Distinct::All(input) = distinct
1257                    && matches!(input.as_ref(), LogicalPlan::Union(_))
1258                    && let Some(query_mut) = query.as_mut()
1259                {
1260                    query_mut.distinct_union();
1261                    return self.select_to_sql_recursively(
1262                        input.as_ref(),
1263                        query,
1264                        select,
1265                        relation,
1266                    );
1267                }
1268
1269                let (select_distinct, input) = match distinct {
1270                    Distinct::All(input) => (ast::Distinct::Distinct, input.as_ref()),
1271                    Distinct::On(on) => {
1272                        let exprs = on
1273                            .on_expr
1274                            .iter()
1275                            .map(|e| self.expr_to_sql(e))
1276                            .collect::<Result<Vec<_>>>()?;
1277                        let items = on
1278                            .select_expr
1279                            .iter()
1280                            .map(|e| self.select_item_to_sql(e))
1281                            .collect::<Result<Vec<_>>>()?;
1282                        if let Some(sort_expr) = &on.sort_expr {
1283                            if let Some(query_ref) = query {
1284                                query_ref.order_by(self.sorts_to_sql(sort_expr)?);
1285                            } else {
1286                                return internal_err!(
1287                                    "Sort operator only valid in a statement context."
1288                                );
1289                            }
1290                        }
1291                        select.projection(items);
1292                        (ast::Distinct::On(exprs), on.input.as_ref())
1293                    }
1294                };
1295                select.distinct(Some(select_distinct));
1296                self.select_to_sql_recursively(input, query, select, relation)
1297            }
1298            LogicalPlan::Join(join) => {
1299                let mut table_scan_filters = vec![];
1300                let (left_plan, right_plan) = match join.join_type {
1301                    JoinType::RightSemi | JoinType::RightAnti => {
1302                        (&join.right, &join.left)
1303                    }
1304                    _ => (&join.left, &join.right),
1305                };
1306                // If there's an outer projection plan, it will already set up the projection.
1307                // In that case, we don't need to worry about setting up the projection here.
1308                // The outer projection plan will handle projecting the correct columns.
1309                let already_projected = select.already_projected();
1310
1311                let left_plan = Self::extract_join_input_table_scan_filters(
1312                    left_plan,
1313                    &mut table_scan_filters,
1314                )?;
1315                let left_plan = if already_projected {
1316                    Self::unwrap_qualified_passthrough_join_projection(left_plan)
1317                } else {
1318                    left_plan
1319                };
1320
1321                self.select_to_sql_recursively(
1322                    left_plan.as_ref(),
1323                    query,
1324                    select,
1325                    relation,
1326                )?;
1327
1328                let left_projection: Option<Vec<ast::SelectItem>> = if !already_projected
1329                {
1330                    Some(select.pop_projections())
1331                } else {
1332                    None
1333                };
1334
1335                let right_plan = Self::extract_join_input_table_scan_filters(
1336                    right_plan,
1337                    &mut table_scan_filters,
1338                )?;
1339
1340                let mut right_relation = RelationBuilder::default();
1341                if already_projected
1342                    && let Some(nested_relation) = self
1343                        .qualified_passthrough_join_projection_to_nested_relation(
1344                            right_plan.as_ref(),
1345                            query,
1346                        )?
1347                {
1348                    right_relation = nested_relation;
1349                } else {
1350                    self.select_to_sql_recursively(
1351                        right_plan.as_ref(),
1352                        query,
1353                        select,
1354                        &mut right_relation,
1355                    )?;
1356                }
1357
1358                let (join_filters, where_filters) = Self::split_join_on_and_where_filters(
1359                    join.join_type,
1360                    &join.filter,
1361                    table_scan_filters,
1362                );
1363                for filter in where_filters {
1364                    let filter_expr = self.expr_to_sql(&filter)?;
1365                    select.selection(Some(filter_expr));
1366                }
1367
1368                let join_constraint = self.join_constraint_to_sql(
1369                    join.join_constraint,
1370                    &join.on,
1371                    join_filters.as_ref(),
1372                )?;
1373
1374                let right_projection: Option<Vec<ast::SelectItem>> = if !already_projected
1375                {
1376                    Some(select.pop_projections())
1377                } else {
1378                    None
1379                };
1380
1381                match join.join_type {
1382                    JoinType::LeftSemi
1383                    | JoinType::LeftAnti
1384                    | JoinType::LeftMark
1385                    | JoinType::RightSemi
1386                    | JoinType::RightAnti
1387                    | JoinType::RightMark => {
1388                        let mut query_builder = QueryBuilder::default();
1389                        let mut from = TableWithJoinsBuilder::default();
1390                        let mut exists_select: SelectBuilder = SelectBuilder::default();
1391                        from.relation(right_relation);
1392                        exists_select.push_from(from);
1393                        if let Some(filter) = &join.filter {
1394                            exists_select.selection(Some(self.expr_to_sql(filter)?));
1395                        }
1396                        for (left, right) in &join.on {
1397                            exists_select.selection(Some(
1398                                self.expr_to_sql(&left.clone().eq(right.clone()))?,
1399                            ));
1400                        }
1401                        exists_select.projection(vec![ast::SelectItem::UnnamedExpr(
1402                            ast::Expr::value(ast::Value::Number("1".to_string(), false)),
1403                        )]);
1404                        query_builder.body(Box::new(SetExpr::Select(Box::new(
1405                            exists_select.build()?,
1406                        ))));
1407
1408                        let negated = match join.join_type {
1409                            JoinType::LeftSemi
1410                            | JoinType::RightSemi
1411                            | JoinType::LeftMark
1412                            | JoinType::RightMark => false,
1413                            JoinType::LeftAnti | JoinType::RightAnti => true,
1414                            _ => unreachable!(),
1415                        };
1416                        let exists_expr = ast::Expr::Exists {
1417                            subquery: Box::new(query_builder.build()?),
1418                            negated,
1419                        };
1420
1421                        match join.join_type {
1422                            JoinType::LeftMark | JoinType::RightMark => {
1423                                let source_schema =
1424                                    if join.join_type == JoinType::LeftMark {
1425                                        right_plan.schema()
1426                                    } else {
1427                                        left_plan.schema()
1428                                    };
1429                                let (table_ref, _) = source_schema.qualified_field(0);
1430                                let column = self.col_to_sql(&Column::new(
1431                                    table_ref.cloned(),
1432                                    "mark",
1433                                ))?;
1434                                select.replace_mark(&column, &exists_expr);
1435                            }
1436                            _ => {
1437                                select.selection(Some(exists_expr));
1438                            }
1439                        }
1440                        if let Some(projection) = left_projection {
1441                            select.projection(projection);
1442                        }
1443                    }
1444                    JoinType::Inner
1445                    | JoinType::Left
1446                    | JoinType::Right
1447                    | JoinType::Full => {
1448                        let Ok(Some(relation)) = right_relation.build() else {
1449                            return internal_err!("Failed to build right relation");
1450                        };
1451                        let ast_join = ast::Join {
1452                            relation,
1453                            global: false,
1454                            join_operator: self
1455                                .join_operator_to_sql(join.join_type, join_constraint)?,
1456                        };
1457                        let mut from = select.pop_from().unwrap();
1458                        from.push_join(ast_join);
1459                        select.push_from(from);
1460                        if !already_projected {
1461                            let Some(left_projection) = left_projection else {
1462                                return internal_err!("Left projection is missing");
1463                            };
1464
1465                            let Some(right_projection) = right_projection else {
1466                                return internal_err!("Right projection is missing");
1467                            };
1468
1469                            let projection = left_projection
1470                                .into_iter()
1471                                .chain(right_projection)
1472                                .collect();
1473                            select.projection(projection);
1474                        }
1475                    }
1476                };
1477
1478                Ok(())
1479            }
1480            LogicalPlan::SubqueryAlias(plan_alias) => {
1481                let (plan, mut columns) =
1482                    subquery_alias_inner_query_and_columns(plan_alias);
1483                let unparsed_table_scan = self.unparse_table_scan_pushdown(
1484                    plan,
1485                    Some(plan_alias.alias.clone()),
1486                    select.already_projected(),
1487                )?;
1488
1489                // If the (possibly rewritten) inner plan builds its own
1490                // SELECT clauses (e.g. Aggregate adds GROUP BY, Window adds
1491                // OVER, etc.) and unparse_table_scan_pushdown couldn't reduce it,
1492                // we must emit a derived subquery: (SELECT ...) AS alias.
1493                // Without this, the recursive handler would merge those clauses
1494                // into the outer SELECT, losing the subquery structure entirely.
1495                if unparsed_table_scan.is_none() && Self::requires_derived_subquery(plan)
1496                {
1497                    // When the dialect does not support column aliases in
1498                    // table aliases (e.g. SQLite), inject the aliases into
1499                    // the inner projection before wrapping as a derived
1500                    // subquery.
1501                    if !columns.is_empty()
1502                        && !self.dialect.supports_column_alias_in_table_alias()
1503                    {
1504                        let Ok(rewritten_plan) =
1505                            inject_column_aliases_into_subquery(plan.clone(), columns)
1506                        else {
1507                            return internal_err!(
1508                                "Failed to transform SubqueryAlias plan"
1509                            );
1510                        };
1511                        return self.derive(
1512                            &rewritten_plan,
1513                            relation,
1514                            Some(self.new_table_alias(
1515                                plan_alias.alias.table().to_string(),
1516                                vec![],
1517                            )),
1518                            false,
1519                        );
1520                    }
1521                    return self.derive(
1522                        plan,
1523                        relation,
1524                        Some(self.new_table_alias(
1525                            plan_alias.alias.table().to_string(),
1526                            columns,
1527                        )),
1528                        false,
1529                    );
1530                }
1531
1532                // if the child plan is a TableScan with pushdown operations, we don't need to
1533                // create an additional subquery for it
1534                if !select.already_projected() && unparsed_table_scan.is_none() {
1535                    select.projection(vec![ast::SelectItem::Wildcard(
1536                        ast::WildcardAdditionalOptions::default(),
1537                    )]);
1538                }
1539                let plan = unparsed_table_scan.unwrap_or_else(|| plan.clone());
1540                if !columns.is_empty()
1541                    && !self.dialect.supports_column_alias_in_table_alias()
1542                {
1543                    // Instead of specifying column aliases as part of the outer table, inject them directly into the inner projection
1544                    let rewritten_plan =
1545                        match inject_column_aliases_into_subquery(plan, columns) {
1546                            Ok(p) => p,
1547                            Err(e) => {
1548                                return internal_err!(
1549                                    "Failed to transform SubqueryAlias plan: {e}"
1550                                );
1551                            }
1552                        };
1553
1554                    columns = vec![];
1555
1556                    self.select_to_sql_recursively(
1557                        &rewritten_plan,
1558                        query,
1559                        select,
1560                        relation,
1561                    )?;
1562                } else {
1563                    self.select_to_sql_recursively(&plan, query, select, relation)?;
1564                }
1565
1566                relation.alias(Some(
1567                    self.new_table_alias(plan_alias.alias.table().to_string(), columns),
1568                ));
1569
1570                // If this SubqueryAlias wraps a FLATTEN (Snowflake unnest),
1571                // register the alias so the outer Projection can rewrite
1572                // column references to use VALUE.
1573                if self.dialect.unnest_as_lateral_flatten()
1574                    && find_unnest_node_until_relation(plan_alias.input.as_ref())
1575                        .is_some()
1576                {
1577                    select.add_flatten_table_alias(plan_alias.alias.table().to_string());
1578                }
1579
1580                Ok(())
1581            }
1582            LogicalPlan::Union(union) => {
1583                // Covers cases where the UNION is a subquery and the projection is at the top level
1584                if select.already_projected() {
1585                    return self.derive_with_dialect_alias(
1586                        "derived_union",
1587                        plan,
1588                        relation,
1589                        false,
1590                        vec![],
1591                    );
1592                }
1593
1594                let input_exprs: Vec<SetExpr> = union
1595                    .inputs
1596                    .iter()
1597                    .map(|input| self.select_to_sql_expr(input, query))
1598                    .collect::<Result<Vec<_>>>()?;
1599
1600                assert_or_internal_err!(
1601                    input_exprs.len() >= 2,
1602                    "UNION operator requires at least 2 inputs"
1603                );
1604
1605                let set_quantifier =
1606                    if query.as_ref().is_some_and(|q| q.is_distinct_union()) {
1607                        // Setting the SetQuantifier to None will unparse as a `UNION`
1608                        // rather than a `UNION ALL`.
1609                        ast::SetQuantifier::None
1610                    } else {
1611                        ast::SetQuantifier::All
1612                    };
1613
1614                // Build the union expression tree bottom-up by reversing the order
1615                // note that we are also swapping left and right inputs because of the rev
1616                let union_expr = input_exprs
1617                    .into_iter()
1618                    .rev()
1619                    .reduce(|a, b| SetExpr::SetOperation {
1620                        op: ast::SetOperator::Union,
1621                        set_quantifier,
1622                        left: Box::new(b),
1623                        right: Box::new(a),
1624                    })
1625                    .unwrap();
1626
1627                let Some(query) = query.as_mut() else {
1628                    return internal_err!(
1629                        "UNION ALL operator only valid in a statement context"
1630                    );
1631                };
1632                query.body(Box::new(union_expr));
1633
1634                Ok(())
1635            }
1636            LogicalPlan::Window(window) => {
1637                // Window nodes are usually handled simultaneously with Projection
1638                // nodes, where projected columns are unprojected back into their
1639                // corresponding window expressions. Manually built plans can have
1640                // Window nodes without an enclosing Projection, so in that case
1641                // the Window node itself must contribute its output expressions.
1642                let project_window_output = !select.already_projected();
1643                if project_window_output
1644                    && Self::window_input_requires_derived_subquery(window.input.as_ref())
1645                {
1646                    return self
1647                        .window_to_sql_with_derived_input(window, select, relation);
1648                }
1649
1650                let agg = if project_window_output {
1651                    find_agg_node_within_select(plan, false)
1652                } else {
1653                    None
1654                };
1655
1656                self.select_to_sql_recursively(
1657                    window.input.as_ref(),
1658                    query,
1659                    select,
1660                    relation,
1661                )?;
1662
1663                if project_window_output {
1664                    self.project_window_output(&window.window_expr, select, agg)?;
1665                }
1666
1667                Ok(())
1668            }
1669            LogicalPlan::EmptyRelation(_) => {
1670                // An EmptyRelation could be behind an UNNEST node. If the dialect supports UNNEST as a table factor,
1671                // a TableRelationBuilder will be created for the UNNEST node first.
1672                if !relation.has_relation() {
1673                    relation.empty();
1674                }
1675                Ok(())
1676            }
1677            LogicalPlan::Extension(extension) => {
1678                if let Some(query) = query.as_mut() {
1679                    self.extension_to_sql(
1680                        extension.node.as_ref(),
1681                        &mut Some(query),
1682                        &mut Some(select),
1683                        &mut Some(relation),
1684                    )
1685                } else {
1686                    self.extension_to_sql(
1687                        extension.node.as_ref(),
1688                        &mut None,
1689                        &mut Some(select),
1690                        &mut Some(relation),
1691                    )
1692                }
1693            }
1694            LogicalPlan::Unnest(unnest) => {
1695                if !unnest.struct_type_columns.is_empty() {
1696                    if self.dialect.unnest_as_lateral_flatten() {
1697                        return not_impl_err!(
1698                            "Snowflake FLATTEN cannot unparse struct unnest: \
1699                             DataFusion expands struct fields into columns (horizontal), \
1700                             but Snowflake FLATTEN expands them into rows (vertical). \
1701                             Columns: {:?}",
1702                            unnest.struct_type_columns
1703                        );
1704                    }
1705                    return internal_err!(
1706                        "Struct type columns are not currently supported in UNNEST: {:?}",
1707                        unnest.struct_type_columns
1708                    );
1709                }
1710
1711                // For Snowflake FLATTEN: if the relation hasn't been set yet
1712                // (UNNEST was in SELECT clause, not FROM clause), set the FLATTEN
1713                // relation here so the FROM clause is emitted.
1714                if self.dialect.unnest_as_lateral_flatten()
1715                    && !relation.has_relation()
1716                    && let Some(mut flatten_relation) =
1717                        self.try_unnest_to_lateral_flatten_sql(unnest)?
1718                {
1719                    // Use the alias already generated by the Projection
1720                    // handler so SELECT items and the FLATTEN relation
1721                    // reference the same name.
1722                    if let Some(alias) = select.current_flatten_alias() {
1723                        flatten_relation.alias(Some(ast::TableAlias {
1724                            name: Ident::with_quote('"', &alias),
1725                            columns: vec![],
1726                            explicit: true,
1727                            at: None,
1728                        }));
1729                    }
1730                    relation.flatten(flatten_relation);
1731                }
1732
1733                // In the case of UNNEST, the Unnest node is followed by a duplicate Projection node that we should skip.
1734                // Otherwise, there will be a duplicate SELECT clause.
1735                // | Projection: table.col1, UNNEST(table.col2)
1736                // |   Unnest: UNNEST(table.col2)
1737                // |     Projection: table.col1, table.col2 AS UNNEST(table.col2)
1738                // |       Filter: table.col3 = Int64(3)
1739                // |         TableScan: table projection=None
1740                if let Some(p) = Self::peel_to_inner_projection(unnest.input.as_ref()) {
1741                    // Skip the inner Projection (synthetic rewriter node)
1742                    // and continue with its input.
1743                    self.select_to_sql_recursively(&p.input, query, select, relation)
1744                } else {
1745                    internal_err!("Unnest input is not a Projection: {unnest:?}")
1746                }
1747            }
1748            LogicalPlan::Subquery(subquery)
1749                if find_unnest_node_until_relation(subquery.subquery.as_ref())
1750                    .is_some() =>
1751            {
1752                if self.dialect.unnest_as_table_factor()
1753                    || self.dialect.unnest_as_lateral_flatten()
1754                {
1755                    self.select_to_sql_recursively(
1756                        subquery.subquery.as_ref(),
1757                        query,
1758                        select,
1759                        relation,
1760                    )
1761                } else {
1762                    self.derive_with_dialect_alias(
1763                        "derived_unnest",
1764                        subquery.subquery.as_ref(),
1765                        relation,
1766                        true,
1767                        vec![],
1768                    )
1769                }
1770            }
1771            _ => {
1772                not_impl_err!("Unsupported operator: {plan:?}")
1773            }
1774        }
1775    }
1776
1777    /// Walk through transparent nodes (SubqueryAlias) to find the inner
1778    /// Projection that feeds an Unnest node.
1779    ///
1780    /// The inner Projection is created atomically by the
1781    /// `RecursiveUnnestRewriter` and contains the array expression that the
1782    /// Unnest operates on. A `SubqueryAlias` (e.g. from a virtual/passthrough
1783    /// table) may wrap the Projection.
1784    fn peel_to_inner_projection(plan: &LogicalPlan) -> Option<&Projection> {
1785        match plan {
1786            LogicalPlan::Projection(p) => Some(p),
1787            LogicalPlan::SubqueryAlias(alias) => {
1788                Self::peel_to_inner_projection(alias.input.as_ref())
1789            }
1790            _ => None,
1791        }
1792    }
1793
1794    /// Walk through transparent nodes (Limit, Sort) between the outer
1795    /// Projection and the Unnest, applying their SQL modifiers (LIMIT,
1796    /// OFFSET, ORDER BY) to the query builder. Returns the `Unnest` node
1797    /// and a reference to the enclosing `LogicalPlan` for recursion, or
1798    /// `Ok(None)` if no Unnest is found.
1799    ///
1800    /// By processing Limit/Sort inline and then recursing into the Unnest
1801    /// plan directly, we bypass the normal Limit/Sort handlers which would
1802    /// create unwanted derived subqueries (since `already_projected` is
1803    /// set at the point this is called).
1804    fn peel_to_unnest_with_modifiers<'a>(
1805        &self,
1806        plan: &'a LogicalPlan,
1807        query: &mut Option<QueryBuilder>,
1808        flatten_alias: Option<&str>,
1809    ) -> Result<Option<(&'a Unnest, &'a LogicalPlan)>> {
1810        match plan {
1811            LogicalPlan::Unnest(unnest) => Ok(Some((unnest, plan))),
1812            LogicalPlan::Limit(limit) => {
1813                if let Some(fetch) = &limit.fetch
1814                    && let Some(q) = query.as_mut()
1815                {
1816                    q.limit(Some(self.expr_to_sql(fetch)?));
1817                }
1818                if let Some(skip) = &limit.skip
1819                    && let Some(q) = query.as_mut()
1820                {
1821                    q.offset(Some(ast::Offset {
1822                        rows: ast::OffsetRows::None,
1823                        value: self.expr_to_sql(skip)?,
1824                    }));
1825                }
1826                self.peel_to_unnest_with_modifiers(
1827                    limit.input.as_ref(),
1828                    query,
1829                    flatten_alias,
1830                )
1831            }
1832            LogicalPlan::Sort(sort) => {
1833                let Some(query_ref) = query.as_mut() else {
1834                    return internal_err!(
1835                        "Sort between Projection and Unnest requires a statement context."
1836                    );
1837                };
1838                if let Some(fetch) = sort.fetch {
1839                    query_ref.limit(Some(ast::Expr::value(ast::Value::Number(
1840                        fetch.to_string(),
1841                        false,
1842                    ))));
1843                }
1844                // When a flatten_alias is provided, rewrite
1845                // __unnest_placeholder(...) columns in sort expressions to
1846                // alias.VALUE so ORDER BY references the FLATTEN output.
1847                let unnest_node = match sort.input.as_ref() {
1848                    LogicalPlan::Unnest(u) => Some(u),
1849                    _ => find_unnest_node_within_select(sort.input.as_ref()),
1850                };
1851                let sort_exprs = if let Some(alias) = flatten_alias
1852                    && let Some(unnest) = unnest_node
1853                {
1854                    sort.expr
1855                        .iter()
1856                        .map(|s| {
1857                            let rewritten = unproject_unnest_expr_as_flatten_value(
1858                                s.expr.clone(),
1859                                unnest,
1860                                alias,
1861                            )?;
1862                            Ok(SortExpr {
1863                                expr: rewritten,
1864                                ..s.clone()
1865                            })
1866                        })
1867                        .collect::<Result<Vec<_>>>()?
1868                } else {
1869                    sort.expr.clone()
1870                };
1871                query_ref.order_by(self.sorts_to_sql(&sort_exprs)?);
1872                self.peel_to_unnest_with_modifiers(
1873                    sort.input.as_ref(),
1874                    query,
1875                    flatten_alias,
1876                )
1877            }
1878            _ => Ok(None),
1879        }
1880    }
1881
1882    /// Search an expression tree for an unnest placeholder column reference.
1883    ///
1884    /// Returns the [`UnnestInputType`] if any sub-expression is a column
1885    /// whose name starts with `__unnest_placeholder`. The placeholder may
1886    /// be at the top level (bare), inside a function call, or one of several
1887    /// expressions — this function finds it regardless.
1888    fn find_unnest_placeholder(expr: &Expr) -> Option<UnnestInputType> {
1889        let mut result = None;
1890        let _ = expr.apply(|e| {
1891            if let Some(t) = Self::classify_placeholder_column(e) {
1892                result = Some(t);
1893                return Ok(TreeNodeRecursion::Stop);
1894            }
1895            Ok(TreeNodeRecursion::Continue)
1896        });
1897        result
1898    }
1899
1900    /// Returns true if `expr` is a placeholder column, optionally wrapped
1901    /// in a single alias (the rewriter's internal `UNNEST(...)` name).
1902    /// Does NOT match when a user alias wraps the internal alias
1903    /// (e.g. `Alias("c1", Alias("UNNEST(...)", Column(placeholder)))`),
1904    /// so the table-factor path correctly falls through to
1905    /// `reconstruct_select_statement` which preserves user aliases.
1906    fn is_bare_unnest_placeholder(expr: &Expr) -> bool {
1907        // Peel at most one alias layer (the rewriter's internal name).
1908        let inner = match expr {
1909            Expr::Alias(Alias { expr, .. }) => expr.as_ref(),
1910            other => other,
1911        };
1912        Self::classify_placeholder_column(inner).is_some()
1913    }
1914
1915    /// If `expr` is a `Column` whose name starts with `__unnest_placeholder`,
1916    /// classify it as [`UnnestInputType::OuterReference`] or
1917    /// [`UnnestInputType::Scalar`].
1918    fn classify_placeholder_column(expr: &Expr) -> Option<UnnestInputType> {
1919        if let Expr::Column(Column { name, .. }) = expr
1920            && let Some(prefix) = name.strip_prefix(UNNEST_PLACEHOLDER)
1921        {
1922            if prefix.starts_with(&format!("({OUTER_REFERENCE_COLUMN_PREFIX}(")) {
1923                return Some(UnnestInputType::OuterReference);
1924            }
1925            return Some(UnnestInputType::Scalar);
1926        }
1927        None
1928    }
1929
1930    /// Check whether an expression carries an internal `UNNEST(...)` display
1931    /// name as its column name or outermost alias. After
1932    /// [`unproject_unnest_expr_as_flatten_value`] rewrites the placeholder
1933    /// column to `_unnest.VALUE`, the internal alias may still linger
1934    /// (e.g. `Alias("UNNEST(make_array(...))", Column("_unnest.VALUE"))`).
1935    /// Callers use this to replace the expression with a clean
1936    /// `_unnest."VALUE"` select item.
1937    fn has_internal_unnest_alias(expr: &Expr) -> bool {
1938        match expr {
1939            Expr::Column(col) => {
1940                col.name.starts_with(&format!("{UNNEST_COLUMN_PREFIX}("))
1941            }
1942            Expr::Alias(Alias { name, .. }) => {
1943                name.starts_with(&format!("{UNNEST_COLUMN_PREFIX}("))
1944            }
1945            _ => false,
1946        }
1947    }
1948
1949    /// Walk the plan tree and register any SubqueryAlias that wraps an
1950    /// unnest as a FLATTEN table alias on the SelectBuilder. This allows
1951    /// `reconstruct_select_statement` to rewrite column references (e.g.
1952    /// `a.col` → `a.VALUE`) before the SubqueryAlias handler runs.
1953    /// Returns true if a plan tree contains an Unnest node, searching
1954    /// through Projection, Subquery, and SubqueryAlias wrappers.
1955    fn contains_unnest(plan: &LogicalPlan) -> bool {
1956        match plan {
1957            LogicalPlan::Unnest(_) => true,
1958            LogicalPlan::Projection(p) => Self::contains_unnest(&p.input),
1959            LogicalPlan::Subquery(s) => Self::contains_unnest(&s.subquery),
1960            LogicalPlan::SubqueryAlias(a) => Self::contains_unnest(&a.input),
1961            _ => false,
1962        }
1963    }
1964
1965    fn collect_flatten_aliases(plan: &LogicalPlan, select: &mut SelectBuilder) {
1966        match plan {
1967            LogicalPlan::SubqueryAlias(alias)
1968                if Self::contains_unnest(alias.input.as_ref()) =>
1969            {
1970                select.add_flatten_table_alias(alias.alias.table().to_string());
1971            }
1972            LogicalPlan::Join(join) => {
1973                Self::collect_flatten_aliases(&join.left, select);
1974                Self::collect_flatten_aliases(&join.right, select);
1975            }
1976            _ => {}
1977        }
1978    }
1979
1980    fn try_unnest_to_table_factor_sql(
1981        &self,
1982        unnest: &Unnest,
1983    ) -> Result<Option<UnnestRelationBuilder>> {
1984        let mut unnest_relation = UnnestRelationBuilder::default();
1985        let LogicalPlan::Projection(projection) = unnest.input.as_ref() else {
1986            return Ok(None);
1987        };
1988
1989        if !matches!(projection.input.as_ref(), LogicalPlan::EmptyRelation(_)) {
1990            // It may be possible that UNNEST is used as a source for the query.
1991            // However, at this point, we don't yet know if it is just a single expression
1992            // from another source or if it's from UNNEST.
1993            //
1994            // Unnest(Projection(EmptyRelation)) denotes a case with `UNNEST([...])`,
1995            // which is normally safe to unnest as a table factor.
1996            // However, in the future, more comprehensive checks can be added here.
1997            return Ok(None);
1998        };
1999
2000        let exprs = projection
2001            .expr
2002            .iter()
2003            .map(|e| self.expr_to_sql(e))
2004            .collect::<Result<Vec<_>>>()?;
2005        unnest_relation.array_exprs(exprs);
2006
2007        Ok(Some(unnest_relation))
2008    }
2009
2010    /// Build a `SELECT alias."VALUE"` item for Snowflake FLATTEN output.
2011    fn build_flatten_value_select_item(
2012        &self,
2013        flatten_alias: &str,
2014        user_alias: Option<&str>,
2015    ) -> ast::SelectItem {
2016        let compound = ast::Expr::CompoundIdentifier(vec![
2017            self.new_ident_quoted_if_needs(flatten_alias.to_string()),
2018            Ident::with_quote('"', "VALUE"),
2019        ]);
2020        match user_alias {
2021            Some(alias) => ast::SelectItem::ExprWithAlias {
2022                expr: compound,
2023                alias: self.new_ident_quoted_if_needs(alias.to_string()),
2024            },
2025            None => ast::SelectItem::UnnamedExpr(compound),
2026        }
2027    }
2028
2029    /// Convert an `Unnest` logical plan node to a `LATERAL FLATTEN(INPUT => expr, ...)`
2030    /// table factor for Snowflake-style SQL output.
2031    fn try_unnest_to_lateral_flatten_sql(
2032        &self,
2033        unnest: &Unnest,
2034    ) -> Result<Option<FlattenRelationBuilder>> {
2035        let Some(projection) = Self::peel_to_inner_projection(unnest.input.as_ref())
2036        else {
2037            return Ok(None);
2038        };
2039
2040        // For now, handle the simple case of a single expression to flatten.
2041        // Multi-expression would require multiple LATERAL FLATTEN calls chained together.
2042        let Some(first_expr) = projection.expr.first() else {
2043            return Ok(None);
2044        };
2045
2046        let input_expr = self.expr_to_sql(first_expr)?;
2047
2048        let mut flatten = FlattenRelationBuilder::default();
2049        flatten.input_expr(input_expr);
2050        flatten.outer(unnest.options.preserve_nulls());
2051
2052        Ok(Some(flatten))
2053    }
2054
2055    fn is_scan_with_pushdown(scan: &TableScan) -> bool {
2056        scan.projection.is_some() || !scan.filters.is_empty() || scan.fetch.is_some()
2057    }
2058
2059    /// Returns true if a plan, when used as the direct child of a SubqueryAlias,
2060    /// must be emitted as a derived subquery `(SELECT ...) AS alias`.
2061    ///
2062    /// Plans like Aggregate or Window build their own SELECT clauses (GROUP BY,
2063    /// window functions).
2064    fn requires_derived_subquery(plan: &LogicalPlan) -> bool {
2065        matches!(
2066            plan,
2067            LogicalPlan::Aggregate(_)
2068                | LogicalPlan::Window(_)
2069                | LogicalPlan::Sort(_)
2070                | LogicalPlan::Limit(_)
2071                | LogicalPlan::Union(_)
2072        )
2073    }
2074
2075    fn is_qualified_passthrough_projection(projection: &Projection) -> bool {
2076        projection
2077            .expr
2078            .iter()
2079            .all(|expr| matches!(expr, Expr::Column(column) if column.relation.is_some()))
2080    }
2081
2082    fn unwrap_qualified_passthrough_join_projection(
2083        plan: Arc<LogicalPlan>,
2084    ) -> Arc<LogicalPlan> {
2085        if let LogicalPlan::Projection(projection) = plan.as_ref()
2086            && matches!(projection.input.as_ref(), LogicalPlan::Join(_))
2087            && Self::is_qualified_passthrough_projection(projection)
2088        {
2089            Arc::clone(&projection.input)
2090        } else {
2091            plan
2092        }
2093    }
2094
2095    fn qualified_passthrough_join_projection_to_nested_relation(
2096        &self,
2097        plan: &LogicalPlan,
2098        query: &mut Option<QueryBuilder>,
2099    ) -> Result<Option<RelationBuilder>> {
2100        let LogicalPlan::Projection(projection) = plan else {
2101            return Ok(None);
2102        };
2103        if !matches!(projection.input.as_ref(), LogicalPlan::Join(_))
2104            || !Self::is_qualified_passthrough_projection(projection)
2105        {
2106            return Ok(None);
2107        }
2108
2109        let original_query = query.clone();
2110        let mut nested_select = SelectBuilder::default();
2111        nested_select.push_from(TableWithJoinsBuilder::default());
2112        let mut nested_relation = RelationBuilder::default();
2113        self.select_to_sql_recursively(
2114            projection.input.as_ref(),
2115            query,
2116            &mut nested_select,
2117            &mut nested_relation,
2118        )?;
2119        if nested_select.has_selection() {
2120            *query = original_query;
2121            return Ok(None);
2122        }
2123
2124        let Some(mut nested_from) = nested_select.pop_from() else {
2125            return internal_err!("Failed to build nested join relation");
2126        };
2127        nested_from.relation(nested_relation);
2128        let Some(table_with_joins) = nested_from.build()? else {
2129            return internal_err!("Failed to build nested join relation");
2130        };
2131
2132        let mut relation = RelationBuilder::default();
2133        relation.nested_join(table_with_joins, None);
2134        Ok(Some(relation))
2135    }
2136
2137    /// Strip the table qualifier from every column in an expression that must
2138    /// resolve against an unnamed derived table's output columns rather than a
2139    /// deeper table alias that is out of scope at this nesting level.
2140    fn strip_column_qualifiers(expr: Expr) -> Result<Expr> {
2141        expr.transform(|e| match e {
2142            Expr::Column(mut column) => {
2143                column.relation = None;
2144                Ok(Transformed::yes(Expr::Column(column)))
2145            }
2146            other => Ok(Transformed::no(other)),
2147        })
2148        .data()
2149    }
2150
2151    fn strip_column_qualifiers_for_schema(expr: Expr, schema: &DFSchema) -> Result<Expr> {
2152        expr.transform(|e| match e {
2153            Expr::Column(mut column)
2154                if column.relation.is_some()
2155                    && schema.index_of_column(&column).is_ok() =>
2156            {
2157                column.relation = None;
2158                Ok(Transformed::yes(Expr::Column(column)))
2159            }
2160            other => Ok(Transformed::no(other)),
2161        })
2162        .data()
2163    }
2164
2165    /// Try to unparse a table scan with pushdown operations into a new subquery plan.
2166    /// If the table scan is without any pushdown operations, return None.
2167    fn unparse_table_scan_pushdown(
2168        &self,
2169        plan: &LogicalPlan,
2170        alias: Option<TableReference>,
2171        already_projected: bool,
2172    ) -> Result<Option<LogicalPlan>> {
2173        match plan {
2174            LogicalPlan::TableScan(table_scan) => {
2175                if !Self::is_scan_with_pushdown(table_scan) {
2176                    return Ok(None);
2177                }
2178                let table_schema = table_scan.source.schema();
2179                let filter_schema = DFSchema::try_from_qualified_schema(
2180                    table_scan.table_name.clone(),
2181                    table_schema.as_ref(),
2182                )?;
2183                let mut filter_alias_rewriter =
2184                    alias.as_ref().map(|alias_name| TableAliasRewriter {
2185                        table_schema: &filter_schema,
2186                        alias_name: alias_name.clone(),
2187                        rewrite_unqualified: true,
2188                    });
2189
2190                let mut builder = LogicalPlanBuilder::scan(
2191                    table_scan.table_name.clone(),
2192                    Arc::clone(&table_scan.source),
2193                    None,
2194                )?;
2195                // We will rebase the column references to the new alias if it exists.
2196                // If the projection or filters are empty, we will append alias to the table scan.
2197                //
2198                // Example:
2199                //   select t1.c1 from t1 where t1.c1 > 1 -> select a.c1 from t1 as a where a.c1 > 1
2200                if let Some(ref alias) = alias
2201                    && (table_scan.projection.is_some() || !table_scan.filters.is_empty())
2202                {
2203                    builder = builder.alias(alias.clone())?;
2204                }
2205
2206                // Avoid creating a duplicate Projection node, which would result in an additional subquery if a projection already exists.
2207                // For example, if the `optimize_projection` rule is applied, there will be a Projection node, and duplicate projection
2208                // information included in the TableScan node.
2209                if !already_projected && let Some(project_vec) = &table_scan.projection {
2210                    if project_vec.is_empty() {
2211                        builder = builder.project(self.empty_projection_fallback())?;
2212                    } else {
2213                        let project_columns = project_vec
2214                            .iter()
2215                            .cloned()
2216                            .map(|i| {
2217                                let schema = table_scan.source.schema();
2218                                let field = schema.field(i);
2219                                if alias.is_some() {
2220                                    Column::new(alias.clone(), field.name().clone())
2221                                } else {
2222                                    Column::new(
2223                                        Some(table_scan.table_name.clone()),
2224                                        field.name().clone(),
2225                                    )
2226                                }
2227                            })
2228                            .collect::<Vec<_>>();
2229                        builder = builder.project(project_columns)?;
2230                    };
2231                }
2232
2233                let filter_expr: Result<Option<Expr>> = table_scan
2234                    .filters
2235                    .iter()
2236                    .cloned()
2237                    .map(|expr| {
2238                        if let Some(ref mut rewriter) = filter_alias_rewriter {
2239                            expr.rewrite(rewriter).data()
2240                        } else {
2241                            Ok(expr)
2242                        }
2243                    })
2244                    .reduce(|acc, expr_result| {
2245                        acc.and_then(|acc_expr| {
2246                            expr_result.map(|expr| acc_expr.and(expr))
2247                        })
2248                    })
2249                    .transpose();
2250
2251                if let Some(filter) = filter_expr? {
2252                    builder = builder.filter(filter)?;
2253                }
2254
2255                if let Some(fetch) = table_scan.fetch {
2256                    builder = builder.limit(0, Some(fetch))?;
2257                }
2258
2259                // If the table scan has an alias but no projection or filters, it means no column references are rebased.
2260                // So we will append the alias to this subquery.
2261                // Example:
2262                //   select * from t1 limit 10 -> (select * from t1 limit 10) as a
2263                if let Some(alias) = alias
2264                    && table_scan.projection.is_none()
2265                    && table_scan.filters.is_empty()
2266                {
2267                    builder = builder.alias(alias)?;
2268                }
2269
2270                Ok(Some(builder.build()?))
2271            }
2272            LogicalPlan::SubqueryAlias(subquery_alias) => {
2273                let ret = self.unparse_table_scan_pushdown(
2274                    &subquery_alias.input,
2275                    Some(subquery_alias.alias.clone()),
2276                    already_projected,
2277                )?;
2278                if let Some(alias) = alias
2279                    && let Some(plan) = ret
2280                {
2281                    let plan = LogicalPlanBuilder::new(plan).alias(alias)?.build()?;
2282                    return Ok(Some(plan));
2283                }
2284                Ok(ret)
2285            }
2286            // SubqueryAlias could be rewritten to a plan with a projection as the top node by [rewrite::subquery_alias_inner_query_and_columns].
2287            // The inner table scan could be a scan with pushdown operations.
2288            LogicalPlan::Projection(projection) => {
2289                if let Some(plan) = self.unparse_table_scan_pushdown(
2290                    &projection.input,
2291                    alias.clone(),
2292                    already_projected,
2293                )? {
2294                    // The pushed-down scan alias is only in scope for the
2295                    // projection directly above the aliased table scan. `plan`
2296                    // is the result of pushing the alias further down: if it is
2297                    // itself a `Projection`, the input was another projection
2298                    // (e.g. common subexpression elimination stacked one), so
2299                    // this projection sits over a derived table rather than
2300                    // directly over the aliased scan, and the alias is out of
2301                    // scope here. Its qualified pass-through columns must then
2302                    // reference the derived table's output unqualified instead
2303                    // of being rebased to the alias. Build it directly so the
2304                    // unqualified columns are not re-normalized back to the
2305                    // alias. (Otherwise `plan` is the scan-derived plan and we
2306                    // fall through to rebase to the alias, correct one level
2307                    // above the scan.)
2308                    if alias.is_some() && matches!(plan, LogicalPlan::Projection(_)) {
2309                        let exprs = projection
2310                            .expr
2311                            .iter()
2312                            .cloned()
2313                            .map(Self::strip_column_qualifiers)
2314                            .collect::<Result<Vec<_>>>()?;
2315                        return Ok(Some(LogicalPlan::Projection(Projection::try_new(
2316                            exprs,
2317                            Arc::new(plan),
2318                        )?)));
2319                    }
2320
2321                    let exprs = if alias.is_some() {
2322                        let mut alias_rewriter =
2323                            alias.as_ref().map(|alias_name| TableAliasRewriter {
2324                                table_schema: plan.schema().as_ref(),
2325                                alias_name: alias_name.clone(),
2326                                rewrite_unqualified: false,
2327                            });
2328                        projection
2329                            .expr
2330                            .iter()
2331                            .cloned()
2332                            .map(|expr| {
2333                                if let Some(ref mut rewriter) = alias_rewriter {
2334                                    expr.rewrite(rewriter).data()
2335                                } else {
2336                                    Ok(expr)
2337                                }
2338                            })
2339                            .collect::<Result<Vec<_>>>()?
2340                    } else {
2341                        projection.expr.clone()
2342                    };
2343                    Ok(Some(
2344                        LogicalPlanBuilder::from(plan).project(exprs)?.build()?,
2345                    ))
2346                } else {
2347                    Ok(None)
2348                }
2349            }
2350            _ => Ok(None),
2351        }
2352    }
2353
2354    fn select_item_to_sql(&self, expr: &Expr) -> Result<ast::SelectItem> {
2355        match expr {
2356            Expr::Alias(Alias { expr, name, .. }) => {
2357                let inner = self.expr_to_sql(expr)?;
2358
2359                // Determine the alias name to use
2360                let col_name = if let Some(rewritten_name) =
2361                    self.dialect.col_alias_overrides(name)?
2362                {
2363                    rewritten_name.to_string()
2364                } else {
2365                    name.to_string()
2366                };
2367
2368                Ok(ast::SelectItem::ExprWithAlias {
2369                    expr: inner,
2370                    alias: self.new_ident_quoted_if_needs(col_name),
2371                })
2372            }
2373            _ => {
2374                let inner = self.expr_to_sql(expr)?;
2375
2376                Ok(ast::SelectItem::UnnamedExpr(inner))
2377            }
2378        }
2379    }
2380
2381    fn sorts_to_sql(&self, sort_exprs: &[SortExpr]) -> Result<OrderByKind> {
2382        Ok(OrderByKind::Expressions(
2383            sort_exprs
2384                .iter()
2385                .map(|sort_expr| self.sort_to_sql(sort_expr))
2386                .collect::<Result<Vec<_>>>()?,
2387        ))
2388    }
2389
2390    fn join_operator_to_sql(
2391        &self,
2392        join_type: JoinType,
2393        constraint: ast::JoinConstraint,
2394    ) -> Result<ast::JoinOperator> {
2395        Ok(match join_type {
2396            JoinType::Inner => match &constraint {
2397                ast::JoinConstraint::On(_)
2398                | ast::JoinConstraint::Using(_)
2399                | ast::JoinConstraint::Natural => ast::JoinOperator::Inner(constraint),
2400                ast::JoinConstraint::None => {
2401                    // Inner joins with no conditions or filters are not valid SQL in most systems,
2402                    // return a CROSS JOIN instead
2403                    ast::JoinOperator::CrossJoin(constraint)
2404                }
2405            },
2406            JoinType::Left => ast::JoinOperator::LeftOuter(constraint),
2407            JoinType::Right => ast::JoinOperator::RightOuter(constraint),
2408            JoinType::Full => ast::JoinOperator::FullOuter(constraint),
2409            JoinType::LeftAnti => ast::JoinOperator::LeftAnti(constraint),
2410            JoinType::LeftSemi => ast::JoinOperator::LeftSemi(constraint),
2411            JoinType::RightAnti => ast::JoinOperator::RightAnti(constraint),
2412            JoinType::RightSemi => ast::JoinOperator::RightSemi(constraint),
2413            JoinType::LeftMark | JoinType::RightMark => {
2414                unimplemented!("Unparsing of Mark join type")
2415            }
2416        })
2417    }
2418
2419    /// Convert the components of a USING clause to the USING AST. Returns
2420    /// 'None' if the conditions are not compatible with a USING expression,
2421    /// e.g. non-column expressions or non-matching names.
2422    fn join_using_to_sql(
2423        &self,
2424        join_conditions: &[(Expr, Expr)],
2425    ) -> Option<ast::JoinConstraint> {
2426        let mut object_names = Vec::with_capacity(join_conditions.len());
2427        for (left, right) in join_conditions {
2428            match (left, right) {
2429                (
2430                    Expr::Column(Column {
2431                        relation: _,
2432                        name: left_name,
2433                        spans: _,
2434                    }),
2435                    Expr::Column(Column {
2436                        relation: _,
2437                        name: right_name,
2438                        spans: _,
2439                    }),
2440                ) if left_name == right_name => {
2441                    // For example, if the join condition `t1.id = t2.id`
2442                    // this is represented as two columns like `[t1.id, t2.id]`
2443                    // This code forms `id` (without relation name)
2444                    let ident = self.new_ident_quoted_if_needs(left_name.to_string());
2445                    object_names.push(ast::ObjectName::from(vec![ident]));
2446                }
2447                // USING is only valid with matching column names; arbitrary expressions
2448                // are not allowed
2449                _ => return None,
2450            }
2451        }
2452        Some(ast::JoinConstraint::Using(object_names))
2453    }
2454
2455    /// Convert a join constraint and associated conditions and filter to a SQL AST node
2456    fn join_constraint_to_sql(
2457        &self,
2458        constraint: JoinConstraint,
2459        conditions: &[(Expr, Expr)],
2460        filter: Option<&Expr>,
2461    ) -> Result<ast::JoinConstraint> {
2462        match (constraint, conditions, filter) {
2463            // No constraints
2464            (JoinConstraint::On | JoinConstraint::Using, [], None) => {
2465                Ok(ast::JoinConstraint::None)
2466            }
2467
2468            (JoinConstraint::Using, conditions, None) => {
2469                match self.join_using_to_sql(conditions) {
2470                    Some(using) => Ok(using),
2471                    // As above, this should not be reachable from parsed SQL,
2472                    // but a user could create this; we "downgrade" to ON.
2473                    None => self.join_conditions_to_sql_on(conditions, None),
2474                }
2475            }
2476
2477            // Two cases here:
2478            // 1. Straightforward ON case, with possible equi-join conditions
2479            //    and additional filters
2480            // 2. USING with additional filters; we "downgrade" to ON, because
2481            //    you can't use USING with arbitrary filters. (This should not
2482            //    be accessible from parsed SQL, but may have been a
2483            //    custom-built JOIN by a user.)
2484            (JoinConstraint::On | JoinConstraint::Using, conditions, filter) => {
2485                self.join_conditions_to_sql_on(conditions, filter)
2486            }
2487        }
2488    }
2489
2490    // Convert a list of equi0join conditions and an optional filter to a SQL ON
2491    // AST node, with the equi-join conditions and the filter merged into a
2492    // single conditional expression
2493    fn join_conditions_to_sql_on(
2494        &self,
2495        join_conditions: &[(Expr, Expr)],
2496        filter: Option<&Expr>,
2497    ) -> Result<ast::JoinConstraint> {
2498        let mut condition = None;
2499        // AND the join conditions together to create the overall condition
2500        for (left, right) in join_conditions {
2501            // Parse left and right
2502            let l = self.expr_to_sql(left)?;
2503            let r = self.expr_to_sql(right)?;
2504            let e = self.binary_op_to_sql(l, r, ast::BinaryOperator::Eq);
2505            condition = match condition {
2506                Some(expr) => Some(self.and_op_to_sql(expr, e)),
2507                None => Some(e),
2508            };
2509        }
2510
2511        // Then AND the non-equijoin filter condition as well
2512        condition = match (condition, filter) {
2513            (Some(expr), Some(filter)) => {
2514                Some(self.and_op_to_sql(expr, self.expr_to_sql(filter)?))
2515            }
2516            (Some(expr), None) => Some(expr),
2517            (None, Some(filter)) => Some(self.expr_to_sql(filter)?),
2518            (None, None) => None,
2519        };
2520
2521        let constraint = match condition {
2522            Some(filter) => ast::JoinConstraint::On(filter),
2523            None => ast::JoinConstraint::None,
2524        };
2525
2526        Ok(constraint)
2527    }
2528
2529    fn and_op_to_sql(&self, lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
2530        self.binary_op_to_sql(lhs, rhs, ast::BinaryOperator::And)
2531    }
2532
2533    fn new_table_alias(&self, alias: String, columns: Vec<Ident>) -> ast::TableAlias {
2534        let columns = columns
2535            .into_iter()
2536            .map(|ident| TableAliasColumnDef {
2537                name: ident,
2538                data_type: None,
2539            })
2540            .collect();
2541        ast::TableAlias {
2542            name: self.new_ident_quoted_if_needs(alias),
2543            columns,
2544            explicit: true,
2545            at: None,
2546        }
2547    }
2548
2549    fn dml_to_sql(&self, plan: &LogicalPlan) -> Result<ast::Statement> {
2550        not_impl_err!("Unsupported plan: {plan:?}")
2551    }
2552
2553    /// Generates appropriate projection expression for empty projection lists.
2554    /// Returns an empty vec for dialects supporting empty select lists,
2555    /// or a dummy literal `1` for other dialects.
2556    fn empty_projection_fallback(&self) -> Vec<Expr> {
2557        if self.dialect.supports_empty_select_list() {
2558            Vec::new()
2559        } else {
2560            vec![Expr::Literal(ScalarValue::Int64(Some(1)), None)]
2561        }
2562    }
2563
2564    /// Decides where extracted table-scan filters belong in the unparsed SQL:
2565    /// in the `JOIN ON` clause or in `WHERE`.
2566    ///
2567    /// For inner joins the two are semantically equivalent, so filters go to
2568    /// `WHERE` (some dialects reject subqueries inside `JOIN ON`).
2569    /// For outer joins the filters are AND-folded into `ON` to preserve correctness.
2570    ///
2571    /// Returns `(on_filter, where_filters)`.
2572    fn split_join_on_and_where_filters(
2573        join_type: JoinType,
2574        join_filter: &Option<Expr>,
2575        table_scan_filters: Vec<Expr>,
2576    ) -> (Option<Expr>, Vec<Expr>) {
2577        if table_scan_filters.is_empty() {
2578            return (join_filter.clone(), vec![]);
2579        }
2580
2581        if join_type == JoinType::Inner {
2582            // ON and WHERE are equivalent for inner joins; prefer WHERE
2583            // because some dialects reject subqueries inside JOIN ON.
2584            return (join_filter.clone(), table_scan_filters);
2585        }
2586
2587        // Outer joins: fold table-scan filters into ON to preserve semantics.
2588        let combined = table_scan_filters.into_iter().reduce(|acc, filter| {
2589            Expr::BinaryExpr(BinaryExpr {
2590                left: Box::new(acc),
2591                op: Operator::And,
2592                right: Box::new(filter),
2593            })
2594        });
2595
2596        let on_filter = match (join_filter, combined) {
2597            (Some(jf), Some(c)) => Some(Expr::BinaryExpr(BinaryExpr {
2598                left: Box::new(jf.clone()),
2599                op: Operator::And,
2600                right: Box::new(c),
2601            })),
2602            (Some(jf), None) => Some(jf.clone()),
2603            (None, Some(c)) => Some(c),
2604            (None, None) => None,
2605        };
2606
2607        (on_filter, vec![])
2608    }
2609}
2610
2611impl From<BuilderError> for DataFusionError {
2612    fn from(e: BuilderError) -> Self {
2613        DataFusionError::External(Box::new(e))
2614    }
2615}
2616
2617/// The type of the input to the UNNEST table factor.
2618#[derive(Debug)]
2619enum UnnestInputType {
2620    /// The input is a column reference. It will be presented like `outer_ref(column_name)`.
2621    OuterReference,
2622    /// The input is a scalar value. It will be presented like a scalar array or struct.
2623    Scalar,
2624}