Skip to main content

polyglot_sql/
scope.rs

1//! Scope Analysis Module
2//!
3//! This module provides scope analysis for SQL queries, enabling detection of
4//! correlated subqueries, column references, and scope relationships.
5//!
6//! Ported from sqlglot's optimizer/scope.py
7
8use crate::expressions::Expression;
9use crate::traversal::ExpressionWalk;
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, HashSet, VecDeque};
12#[cfg(feature = "bindings")]
13use ts_rs::TS;
14
15/// Type of scope in a SQL query
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[cfg_attr(feature = "bindings", derive(TS))]
18#[cfg_attr(feature = "bindings", ts(export))]
19pub enum ScopeType {
20    /// Root scope of the query
21    Root,
22    /// Subquery scope (e.g., WHERE x IN (SELECT ...))
23    Subquery,
24    /// Derived table scope (e.g., FROM (SELECT ...) AS t)
25    DerivedTable,
26    /// Common Table Expression scope
27    Cte,
28    /// Union/Intersect/Except scope
29    SetOperation,
30    /// User-Defined Table Function scope
31    Udtf,
32}
33
34/// Semantic kind of a source registered in a scope.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37pub enum SourceKind {
38    /// Root query or statement context
39    Root,
40    /// Physical table source
41    Table,
42    /// Derived table/subquery source
43    DerivedTable,
44    /// Common Table Expression source
45    Cte,
46    /// Virtual table source such as UNNEST / UDTF
47    Virtual,
48    /// Unresolved or synthetic fallback source
49    Unknown,
50}
51
52impl Default for SourceKind {
53    fn default() -> Self {
54        Self::Unknown
55    }
56}
57
58/// Unwrap query containers without creating a new lexical context.
59pub(crate) fn scope_query(expression: &Expression) -> &Expression {
60    match expression {
61        Expression::Cte(cte) => scope_query(&cte.this),
62        Expression::Subquery(subquery) => scope_query(&subquery.this),
63        Expression::Paren(paren) => scope_query(&paren.this),
64        Expression::Alias(alias) => scope_query(&alias.this),
65        Expression::Prepare(prepare) => scope_query(&prepare.statement),
66        Expression::CreateTable(create) if create.as_select.is_some() => {
67            scope_query(create.as_select.as_ref().unwrap())
68        }
69        _ => expression,
70    }
71}
72
73/// A lightweight resolution view containing only selected sources. CTE
74/// declarations remain available, but do not themselves cause ambiguity.
75pub(crate) fn selected_reference_scope(scope: &Scope) -> Scope {
76    let query = scope_query(&scope.expression);
77    let aliases: HashSet<_> = walk_in_scope(query, false)
78        .filter_map(|node| match node {
79            Expression::Table(table) => Some(
80                table
81                    .alias
82                    .as_ref()
83                    .unwrap_or(&table.name)
84                    .name
85                    .to_lowercase(),
86            ),
87            _ => None,
88        })
89        .collect();
90    let mut selected = Scope::new(query.clone());
91    selected.cte_sources = scope.cte_sources.clone();
92    selected.sources = scope
93        .sources
94        .iter()
95        .filter(|(name, source)| {
96            source.kind != SourceKind::Cte || aliases.contains(&name.to_lowercase())
97        })
98        .map(|(name, source)| (name.clone(), source.clone()))
99        .collect();
100    selected
101}
102
103/// Information about a source (table or subquery) in a scope
104#[derive(Debug, Clone)]
105pub struct SourceInfo {
106    /// The source expression (Table or subquery)
107    pub expression: Expression,
108    /// Whether this source is a scope (vs. a plain table)
109    pub is_scope: bool,
110    /// Semantic source kind for lineage consumers.
111    pub kind: SourceKind,
112    /// User-written alias, when it should be preserved separately from lineage name.
113    pub alias: Option<String>,
114    /// Canonical lineage source name, e.g. synthetic `_0` for virtual sources.
115    pub lineage_name: Option<String>,
116}
117
118impl SourceInfo {
119    pub fn new(expression: Expression, is_scope: bool, kind: SourceKind) -> Self {
120        Self {
121            expression,
122            is_scope,
123            kind,
124            alias: None,
125            lineage_name: None,
126        }
127    }
128
129    pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
130        self.alias = Some(alias.into());
131        self
132    }
133
134    pub fn with_lineage_name(mut self, lineage_name: impl Into<String>) -> Self {
135        self.lineage_name = Some(lineage_name.into());
136        self
137    }
138}
139
140/// A column reference found in a scope
141#[derive(Debug, Clone, PartialEq, Eq, Hash)]
142pub struct ColumnRef {
143    /// The table/alias qualifier (if any)
144    pub table: Option<String>,
145    /// The column name
146    pub name: String,
147}
148
149/// Represents a scope in a SQL query
150///
151/// A scope is the context of a SELECT statement and its sources.
152/// Scopes can be nested (subqueries, CTEs, derived tables) and form a tree.
153#[derive(Debug, Clone)]
154pub struct Scope {
155    /// The expression at the root of this scope
156    pub expression: Expression,
157
158    /// Type of this scope relative to its parent
159    pub scope_type: ScopeType,
160
161    /// Mapping of source names to their info
162    pub sources: HashMap<String, SourceInfo>,
163
164    /// Sources from LATERAL views (have access to preceding sources)
165    pub lateral_sources: HashMap<String, SourceInfo>,
166
167    /// CTE sources available to this scope
168    pub cte_sources: HashMap<String, SourceInfo>,
169
170    /// If this is a derived table or CTE with alias columns, this is that list
171    /// e.g., `SELECT * FROM (SELECT ...) AS y(col1, col2)` => ["col1", "col2"]
172    pub outer_columns: Vec<String>,
173
174    /// Whether this scope can potentially be correlated
175    /// (true for subqueries and UDTFs)
176    pub can_be_correlated: bool,
177
178    /// Child subquery scopes
179    pub subquery_scopes: Vec<Scope>,
180
181    /// Child derived table scopes
182    pub derived_table_scopes: Vec<Scope>,
183
184    /// Child CTE scopes
185    pub cte_scopes: Vec<Scope>,
186
187    /// Child UDTF (User Defined Table Function) scopes
188    pub udtf_scopes: Vec<Scope>,
189
190    /// Combined derived_table_scopes + udtf_scopes in definition order
191    pub table_scopes: Vec<Scope>,
192
193    /// Union/set operation scopes (left and right)
194    pub union_scopes: Vec<Scope>,
195
196    /// Cached columns
197    columns_cache: Option<Vec<ColumnRef>>,
198
199    /// Cached external columns
200    external_columns_cache: Option<Vec<ColumnRef>>,
201}
202
203impl Scope {
204    /// Create a new root scope
205    pub fn new(expression: Expression) -> Self {
206        Self {
207            expression,
208            scope_type: ScopeType::Root,
209            sources: HashMap::new(),
210            lateral_sources: HashMap::new(),
211            cte_sources: HashMap::new(),
212            outer_columns: Vec::new(),
213            can_be_correlated: false,
214            subquery_scopes: Vec::new(),
215            derived_table_scopes: Vec::new(),
216            cte_scopes: Vec::new(),
217            udtf_scopes: Vec::new(),
218            table_scopes: Vec::new(),
219            union_scopes: Vec::new(),
220            columns_cache: None,
221            external_columns_cache: None,
222        }
223    }
224
225    /// Create a child scope branching from this one
226    pub fn branch(&self, expression: Expression, scope_type: ScopeType) -> Self {
227        self.branch_with_options(expression, scope_type, None, None, None)
228    }
229
230    /// Create a child scope with additional options
231    pub fn branch_with_options(
232        &self,
233        expression: Expression,
234        scope_type: ScopeType,
235        sources: Option<HashMap<String, SourceInfo>>,
236        lateral_sources: Option<HashMap<String, SourceInfo>>,
237        outer_columns: Option<Vec<String>>,
238    ) -> Self {
239        let can_be_correlated = self.can_be_correlated
240            || scope_type == ScopeType::Subquery
241            || scope_type == ScopeType::Udtf;
242
243        Self {
244            expression,
245            scope_type,
246            sources: sources.unwrap_or_default(),
247            lateral_sources: lateral_sources.unwrap_or_default(),
248            cte_sources: self.cte_sources.clone(),
249            outer_columns: outer_columns.unwrap_or_default(),
250            can_be_correlated,
251            subquery_scopes: Vec::new(),
252            derived_table_scopes: Vec::new(),
253            cte_scopes: Vec::new(),
254            udtf_scopes: Vec::new(),
255            table_scopes: Vec::new(),
256            union_scopes: Vec::new(),
257            columns_cache: None,
258            external_columns_cache: None,
259        }
260    }
261
262    /// Clear all cached properties
263    pub fn clear_cache(&mut self) {
264        self.columns_cache = None;
265        self.external_columns_cache = None;
266    }
267
268    /// Add a source to this scope
269    pub fn add_source(&mut self, name: String, expression: Expression, is_scope: bool) {
270        let kind = if is_scope {
271            SourceKind::DerivedTable
272        } else {
273            SourceKind::Table
274        };
275        self.add_source_info(name, SourceInfo::new(expression, is_scope, kind));
276    }
277
278    /// Add a preconfigured source to this scope
279    pub fn add_source_info(&mut self, name: String, info: SourceInfo) {
280        self.sources.insert(name, info);
281        self.clear_cache();
282    }
283
284    /// Add a virtual source such as UNNEST / UDTF.
285    pub fn add_virtual_source(&mut self, alias: String, expression: Expression) {
286        let lineage_name = self.next_virtual_source_name();
287        let info = SourceInfo::new(expression, false, SourceKind::Virtual)
288            .with_alias(alias.clone())
289            .with_lineage_name(lineage_name);
290        self.add_source_info(alias, info);
291    }
292
293    fn next_virtual_source_name(&self) -> String {
294        let count = self
295            .sources
296            .values()
297            .filter(|source| source.kind == SourceKind::Virtual)
298            .count();
299        format!("_{}", count)
300    }
301
302    /// Add a lateral source to this scope
303    pub fn add_lateral_source(&mut self, name: String, expression: Expression, is_scope: bool) {
304        let kind = if is_scope {
305            SourceKind::DerivedTable
306        } else {
307            SourceKind::Table
308        };
309        let info = SourceInfo::new(expression.clone(), is_scope, kind);
310        self.sources.insert(name.clone(), info.clone());
311        self.lateral_sources.insert(name, info);
312        self.clear_cache();
313    }
314
315    /// Add a CTE source to this scope
316    pub fn add_cte_source(&mut self, name: String, expression: Expression) {
317        let info = SourceInfo::new(expression, true, SourceKind::Cte);
318        self.cte_sources.insert(name.clone(), info.clone());
319        self.sources.insert(name, info);
320        self.clear_cache();
321    }
322
323    /// Rename a source
324    pub fn rename_source(&mut self, old_name: &str, new_name: String) {
325        if let Some(source) = self.sources.remove(old_name) {
326            self.sources.insert(new_name, source);
327        }
328        self.clear_cache();
329    }
330
331    /// Remove a source
332    pub fn remove_source(&mut self, name: &str) {
333        self.sources.remove(name);
334        self.clear_cache();
335    }
336
337    /// Collect all column references in this scope
338    pub fn columns(&mut self) -> &[ColumnRef] {
339        if self.columns_cache.is_none() {
340            let mut columns = Vec::new();
341            collect_columns(&self.expression, &mut columns);
342            self.columns_cache = Some(columns);
343        }
344        self.columns_cache.as_ref().unwrap()
345    }
346
347    /// Collect projected output column names for this scope's query expression.
348    ///
349    /// This is intended for result schema style output columns (e.g. UNION
350    /// outputs), unlike [`Self::columns`], which returns raw referenced columns.
351    pub fn output_columns(&self) -> Vec<String> {
352        crate::ast_transforms::get_output_column_names(&self.expression)
353    }
354
355    /// Get all source names in this scope
356    pub fn source_names(&self) -> HashSet<String> {
357        let mut names: HashSet<String> = self.sources.keys().cloned().collect();
358        names.extend(self.cte_sources.keys().cloned());
359        names
360    }
361
362    /// Get columns that reference sources outside this scope
363    pub fn external_columns(&mut self) -> Vec<ColumnRef> {
364        if self.external_columns_cache.is_some() {
365            return self.external_columns_cache.clone().unwrap();
366        }
367
368        let source_names = self.source_names();
369        let columns = self.columns().to_vec();
370
371        let external: Vec<ColumnRef> = columns
372            .into_iter()
373            .filter(|col| {
374                // A column is external if it has a table qualifier that's not in our sources
375                match &col.table {
376                    Some(table) => !source_names.contains(table),
377                    None => false, // Unqualified columns might be local
378                }
379            })
380            .collect();
381
382        self.external_columns_cache = Some(external.clone());
383        external
384    }
385
386    /// Get columns that reference sources in this scope (not external)
387    pub fn local_columns(&mut self) -> Vec<ColumnRef> {
388        let external_set: HashSet<_> = self.external_columns().into_iter().collect();
389        let columns = self.columns().to_vec();
390
391        columns
392            .into_iter()
393            .filter(|col| !external_set.contains(col))
394            .collect()
395    }
396
397    /// Get unqualified columns (columns without table qualifier)
398    pub fn unqualified_columns(&mut self) -> Vec<ColumnRef> {
399        self.columns()
400            .iter()
401            .filter(|c| c.table.is_none())
402            .cloned()
403            .collect()
404    }
405
406    /// Get columns for a specific source
407    pub fn source_columns(&mut self, source_name: &str) -> Vec<ColumnRef> {
408        self.columns()
409            .iter()
410            .filter(|col| col.table.as_deref() == Some(source_name))
411            .cloned()
412            .collect()
413    }
414
415    /// Determine if this scope is a correlated subquery
416    ///
417    /// A subquery is correlated if:
418    /// 1. It can be correlated (is a subquery or UDTF), AND
419    /// 2. It references columns from outer scopes
420    pub fn is_correlated_subquery(&mut self) -> bool {
421        self.can_be_correlated && !self.external_columns().is_empty()
422    }
423
424    /// Check if this is a subquery scope
425    pub fn is_subquery(&self) -> bool {
426        self.scope_type == ScopeType::Subquery
427    }
428
429    /// Check if this is a derived table scope
430    pub fn is_derived_table(&self) -> bool {
431        self.scope_type == ScopeType::DerivedTable
432    }
433
434    /// Check if this is a CTE scope
435    pub fn is_cte(&self) -> bool {
436        self.scope_type == ScopeType::Cte
437    }
438
439    /// Check if this is the root scope
440    pub fn is_root(&self) -> bool {
441        self.scope_type == ScopeType::Root
442    }
443
444    /// Check if this is a UDTF scope
445    pub fn is_udtf(&self) -> bool {
446        self.scope_type == ScopeType::Udtf
447    }
448
449    /// Check if this is a union/set operation scope
450    pub fn is_union(&self) -> bool {
451        self.scope_type == ScopeType::SetOperation
452    }
453
454    /// Traverse all scopes in this tree (depth-first post-order)
455    pub fn traverse(&self) -> Vec<&Scope> {
456        let mut result = Vec::new();
457        self.traverse_impl(&mut result);
458        result
459    }
460
461    fn traverse_impl<'a>(&'a self, result: &mut Vec<&'a Scope>) {
462        // First traverse children
463        for scope in &self.cte_scopes {
464            scope.traverse_impl(result);
465        }
466        for scope in &self.union_scopes {
467            scope.traverse_impl(result);
468        }
469        for scope in &self.table_scopes {
470            scope.traverse_impl(result);
471        }
472        for scope in &self.subquery_scopes {
473            scope.traverse_impl(result);
474        }
475        // Then add self
476        result.push(self);
477    }
478
479    /// Count references to each scope in this tree
480    pub fn ref_count(&self) -> HashMap<usize, usize> {
481        let mut counts: HashMap<usize, usize> = HashMap::new();
482
483        for scope in self.traverse() {
484            for (_, source_info) in scope.sources.iter() {
485                if source_info.is_scope {
486                    let id = &source_info.expression as *const _ as usize;
487                    *counts.entry(id).or_insert(0) += 1;
488                }
489            }
490        }
491
492        counts
493    }
494}
495
496/// Collect all column references from an expression tree
497fn collect_columns(expr: &Expression, columns: &mut Vec<ColumnRef>) {
498    match expr {
499        Expression::Column(col) => {
500            columns.push(ColumnRef {
501                table: col.table.as_ref().map(|t| t.name.clone()),
502                name: col.name.name.clone(),
503            });
504        }
505        Expression::Select(select) => {
506            // Collect from SELECT expressions
507            for e in &select.expressions {
508                collect_columns(e, columns);
509            }
510            // Collect from JOIN ON / MATCH_CONDITION clauses
511            for join in &select.joins {
512                if let Some(on) = &join.on {
513                    collect_columns(on, columns);
514                }
515                if let Some(match_condition) = &join.match_condition {
516                    collect_columns(match_condition, columns);
517                }
518            }
519            // Collect from WHERE
520            if let Some(where_clause) = &select.where_clause {
521                collect_columns(&where_clause.this, columns);
522            }
523            // Collect from HAVING
524            if let Some(having) = &select.having {
525                collect_columns(&having.this, columns);
526            }
527            // Collect from ORDER BY
528            if let Some(order_by) = &select.order_by {
529                for ord in &order_by.expressions {
530                    collect_columns(&ord.this, columns);
531                }
532            }
533            // Collect from GROUP BY
534            if let Some(group_by) = &select.group_by {
535                for e in &group_by.expressions {
536                    collect_columns(e, columns);
537                }
538            }
539            // Note: We don't recurse into FROM/JOIN source subqueries here
540            // as those create their own scopes.
541        }
542        // Binary operations
543        Expression::And(bin)
544        | Expression::Or(bin)
545        | Expression::Add(bin)
546        | Expression::Sub(bin)
547        | Expression::Mul(bin)
548        | Expression::Div(bin)
549        | Expression::Mod(bin)
550        | Expression::Eq(bin)
551        | Expression::Neq(bin)
552        | Expression::Lt(bin)
553        | Expression::Lte(bin)
554        | Expression::Gt(bin)
555        | Expression::Gte(bin)
556        | Expression::BitwiseAnd(bin)
557        | Expression::BitwiseOr(bin)
558        | Expression::BitwiseXor(bin)
559        | Expression::Concat(bin) => {
560            collect_columns(&bin.left, columns);
561            collect_columns(&bin.right, columns);
562        }
563        // LIKE/ILIKE operations
564        Expression::Like(like) | Expression::ILike(like) => {
565            collect_columns(&like.left, columns);
566            collect_columns(&like.right, columns);
567            if let Some(escape) = &like.escape {
568                collect_columns(escape, columns);
569            }
570        }
571        // Unary operations
572        Expression::Not(un) | Expression::Neg(un) | Expression::BitwiseNot(un) => {
573            collect_columns(&un.this, columns);
574        }
575        Expression::Function(func) => {
576            for arg in &func.args {
577                collect_columns(arg, columns);
578            }
579        }
580        Expression::AggregateFunction(agg) => {
581            for arg in &agg.args {
582                collect_columns(arg, columns);
583            }
584        }
585        Expression::WindowFunction(wf) => {
586            collect_columns(&wf.this, columns);
587            for e in &wf.over.partition_by {
588                collect_columns(e, columns);
589            }
590            for e in &wf.over.order_by {
591                collect_columns(&e.this, columns);
592            }
593        }
594        Expression::Alias(alias) => {
595            collect_columns(&alias.this, columns);
596        }
597        Expression::Case(case) => {
598            if let Some(operand) = &case.operand {
599                collect_columns(operand, columns);
600            }
601            for (when_expr, then_expr) in &case.whens {
602                collect_columns(when_expr, columns);
603                collect_columns(then_expr, columns);
604            }
605            if let Some(else_clause) = &case.else_ {
606                collect_columns(else_clause, columns);
607            }
608        }
609        Expression::Paren(paren) => {
610            collect_columns(&paren.this, columns);
611        }
612        Expression::Ordered(ord) => {
613            collect_columns(&ord.this, columns);
614        }
615        Expression::In(in_expr) => {
616            collect_columns(&in_expr.this, columns);
617            for e in &in_expr.expressions {
618                collect_columns(e, columns);
619            }
620            // Note: in_expr.query is a subquery - creates its own scope
621        }
622        Expression::Between(between) => {
623            collect_columns(&between.this, columns);
624            collect_columns(&between.low, columns);
625            collect_columns(&between.high, columns);
626        }
627        Expression::IsNull(is_null) => {
628            collect_columns(&is_null.this, columns);
629        }
630        Expression::Cast(cast) => {
631            collect_columns(&cast.this, columns);
632        }
633        Expression::Extract(extract) => {
634            collect_columns(&extract.this, columns);
635        }
636        Expression::Exists(_) | Expression::Subquery(_) => {
637            // These create their own scopes - don't collect from here
638        }
639        Expression::Prepare(prepare) => {
640            collect_columns(&prepare.statement, columns);
641        }
642        _ => {
643            // For other expressions, we might need to add more cases
644        }
645    }
646}
647
648/// Build scope tree from an expression
649///
650/// This traverses the expression tree and builds a hierarchy of Scope objects
651/// that track sources and column references at each level.
652pub fn build_scope(expression: &Expression) -> Scope {
653    build_scope_with_ctes(expression, &HashMap::new())
654}
655
656/// Build a query scope with CTE definitions inherited from its lexical parent.
657pub(crate) fn build_scope_with_ctes(
658    expression: &Expression,
659    ctes: &HashMap<String, SourceInfo>,
660) -> Scope {
661    let mut root = Scope::new(expression.clone());
662    root.cte_sources = ctes.clone();
663    build_scope_impl(expression, &mut root);
664    root
665}
666
667fn build_scope_impl(expression: &Expression, current_scope: &mut Scope) {
668    match expression {
669        Expression::Prepare(prepare) => {
670            build_scope_impl(&prepare.statement, current_scope);
671        }
672        Expression::Select(select) => {
673            // Process CTEs first
674            if let Some(with) = &select.with {
675                process_ctes(with, current_scope);
676            }
677
678            // Process FROM clause
679            if let Some(from) = &select.from {
680                for table in &from.expressions {
681                    add_table_to_scope(table, current_scope);
682                }
683            }
684
685            // Process JOINs
686            for join in &select.joins {
687                add_table_to_scope(&join.this, current_scope);
688            }
689
690            // Process table-generating lateral views (Hive/Spark style UDTFs).
691            for lateral_view in &select.lateral_views {
692                add_lateral_view_to_scope(lateral_view, current_scope);
693            }
694
695            // Process subqueries in WHERE, SELECT expressions, etc.
696            collect_subqueries(expression, current_scope);
697        }
698        Expression::Union(union) => {
699            if let Some(with) = &union.with {
700                process_ctes(with, current_scope);
701            }
702
703            let mut left_scope = current_scope.branch(union.left.clone(), ScopeType::SetOperation);
704            build_scope_impl(&union.left, &mut left_scope);
705
706            let mut right_scope =
707                current_scope.branch(union.right.clone(), ScopeType::SetOperation);
708            build_scope_impl(&union.right, &mut right_scope);
709
710            current_scope.union_scopes.push(left_scope);
711            current_scope.union_scopes.push(right_scope);
712        }
713        Expression::Intersect(intersect) => {
714            if let Some(with) = &intersect.with {
715                process_ctes(with, current_scope);
716            }
717
718            let mut left_scope =
719                current_scope.branch(intersect.left.clone(), ScopeType::SetOperation);
720            build_scope_impl(&intersect.left, &mut left_scope);
721
722            let mut right_scope =
723                current_scope.branch(intersect.right.clone(), ScopeType::SetOperation);
724            build_scope_impl(&intersect.right, &mut right_scope);
725
726            current_scope.union_scopes.push(left_scope);
727            current_scope.union_scopes.push(right_scope);
728        }
729        Expression::Except(except) => {
730            if let Some(with) = &except.with {
731                process_ctes(with, current_scope);
732            }
733
734            let mut left_scope = current_scope.branch(except.left.clone(), ScopeType::SetOperation);
735            build_scope_impl(&except.left, &mut left_scope);
736
737            let mut right_scope =
738                current_scope.branch(except.right.clone(), ScopeType::SetOperation);
739            build_scope_impl(&except.right, &mut right_scope);
740
741            current_scope.union_scopes.push(left_scope);
742            current_scope.union_scopes.push(right_scope);
743        }
744        Expression::CreateTable(create) => {
745            // Handle CREATE TABLE ... AS [WITH ...] SELECT ...
746            // Process CTEs if present
747            if let Some(with) = &create.with_cte {
748                process_ctes(with, current_scope);
749            }
750            // Traverse the AS SELECT body
751            if let Some(as_select) = &create.as_select {
752                build_scope_impl(as_select, current_scope);
753            }
754        }
755        Expression::Subquery(subquery) => {
756            build_scope_impl(&subquery.this, current_scope);
757        }
758        Expression::Paren(paren) => {
759            build_scope_impl(&paren.this, current_scope);
760        }
761        _ => {}
762    }
763}
764
765fn process_ctes(with: &crate::expressions::With, current_scope: &mut Scope) {
766    for cte in &with.ctes {
767        let cte_name = cte.alias.name.clone();
768        let cte_expr = Expression::Cte(Box::new(cte.clone()));
769        let mut cte_scope = current_scope.branch(cte_expr.clone(), ScopeType::Cte);
770
771        if with.recursive && cte_body_self_references(cte) {
772            cte_scope.add_cte_source(cte_name.clone(), cte_expr.clone());
773        }
774
775        build_scope_impl(&cte.this, &mut cte_scope);
776        current_scope.add_cte_source(cte_name, cte_expr);
777        current_scope.cte_scopes.push(cte_scope);
778    }
779}
780
781fn cte_body_self_references(cte: &crate::expressions::Cte) -> bool {
782    let cte_name = cte.alias.name.as_str();
783    !cte.this
784        .find_all(|expr| match expr {
785            Expression::Table(table) if table.schema.is_none() && table.catalog.is_none() => {
786                table.name.name.eq_ignore_ascii_case(cte_name)
787            }
788            _ => false,
789        })
790        .is_empty()
791}
792
793fn add_table_to_scope(expr: &Expression, scope: &mut Scope) {
794    match expr {
795        Expression::Table(table) => {
796            let name = table
797                .alias
798                .as_ref()
799                .map(|a| a.name.clone())
800                .unwrap_or_else(|| table.name.name.clone());
801            let cte_source = if table.schema.is_none() && table.catalog.is_none() {
802                scope.cte_sources.get(&table.name.name).or_else(|| {
803                    scope
804                        .cte_sources
805                        .iter()
806                        .find(|(cte_name, _)| cte_name.eq_ignore_ascii_case(&table.name.name))
807                        .map(|(_, source)| source)
808                })
809            } else {
810                None
811            };
812
813            if let Some(source) = cte_source {
814                scope.add_source_info(name, source.clone());
815            } else {
816                let mut source = SourceInfo::new(expr.clone(), false, SourceKind::Table);
817                if let Some(alias) = &table.alias {
818                    source = source.with_alias(alias.name.clone());
819                }
820                scope.add_source_info(name, source);
821            }
822        }
823        Expression::Subquery(subquery) => {
824            let name = subquery
825                .alias
826                .as_ref()
827                .map(|a| a.name.clone())
828                .unwrap_or_default();
829
830            let mut derived_scope = scope.branch(subquery.this.clone(), ScopeType::DerivedTable);
831            build_scope_impl(&subquery.this, &mut derived_scope);
832
833            scope.add_source(name.clone(), expr.clone(), true);
834            scope.derived_table_scopes.push(derived_scope);
835        }
836        Expression::Unnest(unnest) => {
837            if let Some(alias) = &unnest.alias {
838                scope.add_virtual_source(alias.name.clone(), expr.clone());
839            }
840        }
841        Expression::Alias(alias) if matches!(&alias.this, Expression::Unnest(_)) => {
842            scope.add_virtual_source(alias.alias.name.clone(), expr.clone());
843        }
844        Expression::Alias(alias) if is_query_like_relation(&alias.this) => {
845            let outer_columns = alias
846                .column_aliases
847                .iter()
848                .map(|column| column.name.clone())
849                .collect::<Vec<_>>();
850            let mut derived_scope = scope.branch_with_options(
851                alias.this.clone(),
852                ScopeType::DerivedTable,
853                None,
854                None,
855                Some(outer_columns),
856            );
857            build_scope_impl(&alias.this, &mut derived_scope);
858
859            scope.add_source(alias.alias.name.clone(), expr.clone(), true);
860            scope.derived_table_scopes.push(derived_scope);
861        }
862        Expression::Lateral(lateral) => {
863            if let Some(alias) = &lateral.alias {
864                scope.add_virtual_source(alias.clone(), expr.clone());
865            }
866        }
867        Expression::LateralView(lateral_view) => {
868            add_lateral_view_to_scope(lateral_view, scope);
869        }
870        Expression::Pivot(pivot) => {
871            let name =
872                pivot_source_name(&pivot.this, pivot.alias.as_ref().map(|a| a.name.as_str()));
873            scope.add_source_info(
874                name,
875                SourceInfo::new(expr.clone(), false, SourceKind::DerivedTable),
876            );
877            add_pivot_inner_scope(&pivot.this, scope);
878        }
879        Expression::Unpivot(unpivot) => {
880            let name = pivot_source_name(
881                &unpivot.this,
882                unpivot.alias.as_ref().map(|a| a.name.as_str()),
883            );
884            scope.add_source_info(
885                name,
886                SourceInfo::new(expr.clone(), false, SourceKind::DerivedTable),
887            );
888            add_pivot_inner_scope(&unpivot.this, scope);
889        }
890        Expression::Paren(paren) => {
891            add_table_to_scope(&paren.this, scope);
892        }
893        _ => {}
894    }
895}
896
897fn is_query_like_relation(expr: &Expression) -> bool {
898    match expr {
899        Expression::Select(_)
900        | Expression::Subquery(_)
901        | Expression::Union(_)
902        | Expression::Intersect(_)
903        | Expression::Except(_) => true,
904        Expression::Paren(paren) => is_query_like_relation(&paren.this),
905        _ => false,
906    }
907}
908
909fn pivot_source_name(source: &Expression, explicit_alias: Option<&str>) -> String {
910    if let Some(alias) = explicit_alias {
911        return alias.to_string();
912    }
913
914    match source {
915        Expression::Table(table) => table
916            .alias
917            .as_ref()
918            .map(|alias| alias.name.clone())
919            .unwrap_or_else(|| table.name.name.clone()),
920        Expression::Subquery(subquery) => subquery
921            .alias
922            .as_ref()
923            .map(|alias| alias.name.clone())
924            .unwrap_or_else(|| "_0".to_string()),
925        Expression::Paren(paren) => pivot_source_name(&paren.this, explicit_alias),
926        _ => "_0".to_string(),
927    }
928}
929
930fn add_pivot_inner_scope(source: &Expression, scope: &mut Scope) {
931    match source {
932        Expression::Subquery(subquery) => {
933            let mut derived_scope = scope.branch(subquery.this.clone(), ScopeType::DerivedTable);
934            build_scope_impl(&subquery.this, &mut derived_scope);
935            scope.derived_table_scopes.push(derived_scope);
936        }
937        Expression::Paren(paren) => add_pivot_inner_scope(&paren.this, scope),
938        _ => {}
939    }
940}
941
942fn add_lateral_view_to_scope(lateral_view: &crate::expressions::LateralView, scope: &mut Scope) {
943    let alias = lateral_view
944        .table_alias
945        .as_ref()
946        .or_else(|| lateral_view.column_aliases.first())
947        .map(|alias| alias.name.clone());
948
949    if let Some(alias) = alias {
950        scope.add_virtual_source(
951            alias,
952            Expression::LateralView(Box::new(lateral_view.clone())),
953        );
954    }
955}
956
957fn collect_subqueries(expr: &Expression, parent_scope: &mut Scope) {
958    if matches!(expr, Expression::Select(_)) {
959        use crate::ast_children::ChildPathSegment::{Field, Index};
960
961        // Include JOIN predicates, ORDER BY and QUALIFY as well as projections
962        // and WHERE, but do not register FROM/JOIN queries again as scalar
963        // subqueries (including derived tables without aliases).
964        crate::ast_children::for_each_child(expr, |path, child| {
965            if !matches!(
966                path,
967                [Field("from" | "with" | "lateral_views"), ..]
968                    | [Field("joins"), Index(_), Field("this"), ..]
969            ) {
970                collect_subqueries_in_expr(child, parent_scope);
971            }
972        });
973    }
974}
975
976fn collect_subqueries_in_expr(expr: &Expression, parent_scope: &mut Scope) {
977    let mut seen = HashSet::new();
978    for node in walk_in_scope(expr, false) {
979        let query = match node {
980            Expression::Subquery(subquery) if subquery.alias.is_none() => Some(&subquery.this),
981            Expression::Exists(exists) => Some(&exists.this),
982            Expression::In(in_expr) => in_expr.query.as_ref(),
983            Expression::Any(quantified) | Expression::All(quantified) => Some(&quantified.subquery),
984            _ => None,
985        };
986
987        let Some(query) = query else {
988            continue;
989        };
990
991        let key = query as *const Expression as usize;
992        if !seen.insert(key) {
993            continue;
994        }
995
996        let mut sub_scope = parent_scope.branch(query.clone(), ScopeType::Subquery);
997        build_scope_impl(query, &mut sub_scope);
998        parent_scope.subquery_scopes.push(sub_scope);
999    }
1000}
1001
1002/// Walk within a scope, yielding expressions without crossing scope boundaries.
1003///
1004/// This iterator visits all nodes in the syntax tree, stopping at nodes that
1005/// start child scopes (CTEs, derived tables, subqueries in FROM/JOIN).
1006///
1007/// # Arguments
1008/// * `expression` - The expression to walk
1009/// * `bfs` - If true, uses breadth-first search; otherwise uses depth-first search
1010///
1011/// # Returns
1012/// An iterator over expressions within the scope
1013pub fn walk_in_scope<'a>(
1014    expression: &'a Expression,
1015    bfs: bool,
1016) -> impl Iterator<Item = &'a Expression> {
1017    WalkInScopeIter::new(expression, bfs)
1018}
1019
1020/// Iterator for walking within a scope
1021struct WalkInScopeIter<'a> {
1022    queue: VecDeque<&'a Expression>,
1023    bfs: bool,
1024}
1025
1026impl<'a> WalkInScopeIter<'a> {
1027    fn new(expression: &'a Expression, bfs: bool) -> Self {
1028        let mut queue = VecDeque::new();
1029        queue.push_back(expression);
1030        Self { queue, bfs }
1031    }
1032
1033    fn should_stop_at(&self, expr: &Expression, is_root: bool) -> bool {
1034        if is_root {
1035            return false;
1036        }
1037
1038        // Stop at CTE definitions
1039        if matches!(expr, Expression::Cte(_)) {
1040            return true;
1041        }
1042
1043        // Stop at subqueries that are derived tables (in FROM/JOIN)
1044        if let Expression::Subquery(subquery) = expr {
1045            if subquery.alias.is_some() {
1046                return true;
1047            }
1048        }
1049
1050        // Stop at standalone SELECT/UNION/etc that would be subqueries
1051        if matches!(
1052            expr,
1053            Expression::Select(_)
1054                | Expression::Union(_)
1055                | Expression::Intersect(_)
1056                | Expression::Except(_)
1057        ) {
1058            return true;
1059        }
1060
1061        false
1062    }
1063
1064    fn get_children(&self, expr: &'a Expression) -> Vec<&'a Expression> {
1065        let mut children = Vec::new();
1066
1067        match expr {
1068            Expression::Prepare(prepare) => {
1069                children.push(&prepare.statement);
1070            }
1071            Expression::Select(select) => {
1072                // Walk SELECT expressions
1073                for e in &select.expressions {
1074                    children.push(e);
1075                }
1076                // Walk FROM (but tables/subqueries create new scopes)
1077                if let Some(from) = &select.from {
1078                    for table in &from.expressions {
1079                        if !self.should_stop_at(table, false) {
1080                            children.push(table);
1081                        }
1082                    }
1083                }
1084                // Walk JOINs (but their sources create new scopes)
1085                for join in &select.joins {
1086                    if !self.should_stop_at(&join.this, false) {
1087                        children.push(&join.this);
1088                    }
1089                    if let Some(on) = &join.on {
1090                        children.push(on);
1091                    }
1092                    if let Some(condition) = &join.match_condition {
1093                        children.push(condition);
1094                    }
1095                }
1096                // Walk WHERE
1097                if let Some(where_clause) = &select.where_clause {
1098                    children.push(&where_clause.this);
1099                }
1100                // Walk GROUP BY
1101                if let Some(group_by) = &select.group_by {
1102                    for e in &group_by.expressions {
1103                        children.push(e);
1104                    }
1105                }
1106                // Walk HAVING
1107                if let Some(having) = &select.having {
1108                    children.push(&having.this);
1109                }
1110                if let Some(qualify) = &select.qualify {
1111                    children.push(&qualify.this);
1112                }
1113                // Walk ORDER BY
1114                if let Some(order_by) = &select.order_by {
1115                    for ord in &order_by.expressions {
1116                        children.push(&ord.this);
1117                    }
1118                }
1119                // Walk LIMIT
1120                if let Some(limit) = &select.limit {
1121                    children.push(&limit.this);
1122                }
1123                // Walk OFFSET
1124                if let Some(offset) = &select.offset {
1125                    children.push(&offset.this);
1126                }
1127            }
1128            Expression::And(bin)
1129            | Expression::Or(bin)
1130            | Expression::Add(bin)
1131            | Expression::Sub(bin)
1132            | Expression::Mul(bin)
1133            | Expression::Div(bin)
1134            | Expression::Mod(bin)
1135            | Expression::Eq(bin)
1136            | Expression::Neq(bin)
1137            | Expression::Lt(bin)
1138            | Expression::Lte(bin)
1139            | Expression::Gt(bin)
1140            | Expression::Gte(bin)
1141            | Expression::BitwiseAnd(bin)
1142            | Expression::BitwiseOr(bin)
1143            | Expression::BitwiseXor(bin)
1144            | Expression::Concat(bin) => {
1145                children.push(&bin.left);
1146                children.push(&bin.right);
1147            }
1148            Expression::Like(like) | Expression::ILike(like) => {
1149                children.push(&like.left);
1150                children.push(&like.right);
1151                if let Some(escape) = &like.escape {
1152                    children.push(escape);
1153                }
1154            }
1155            Expression::Not(un) | Expression::Neg(un) | Expression::BitwiseNot(un) => {
1156                children.push(&un.this);
1157            }
1158            Expression::Function(func) => {
1159                for arg in &func.args {
1160                    children.push(arg);
1161                }
1162            }
1163            Expression::AggregateFunction(agg) => {
1164                for arg in &agg.args {
1165                    children.push(arg);
1166                }
1167            }
1168            Expression::WindowFunction(wf) => {
1169                children.push(&wf.this);
1170                for e in &wf.over.partition_by {
1171                    children.push(e);
1172                }
1173                for e in &wf.over.order_by {
1174                    children.push(&e.this);
1175                }
1176            }
1177            Expression::Alias(alias) => {
1178                children.push(&alias.this);
1179            }
1180            Expression::Case(case) => {
1181                if let Some(operand) = &case.operand {
1182                    children.push(operand);
1183                }
1184                for (when_expr, then_expr) in &case.whens {
1185                    children.push(when_expr);
1186                    children.push(then_expr);
1187                }
1188                if let Some(else_clause) = &case.else_ {
1189                    children.push(else_clause);
1190                }
1191            }
1192            Expression::Paren(paren) => {
1193                children.push(&paren.this);
1194            }
1195            Expression::Ordered(ord) => {
1196                children.push(&ord.this);
1197            }
1198            Expression::In(in_expr) => {
1199                children.push(&in_expr.this);
1200                for e in &in_expr.expressions {
1201                    children.push(e);
1202                }
1203                // Note: in_expr.query creates a new scope - don't traverse
1204            }
1205            Expression::Between(between) => {
1206                children.push(&between.this);
1207                children.push(&between.low);
1208                children.push(&between.high);
1209            }
1210            Expression::IsNull(is_null) => {
1211                children.push(&is_null.this);
1212            }
1213            Expression::Cast(cast) => {
1214                children.push(&cast.this);
1215            }
1216            Expression::Extract(extract) => {
1217                children.push(&extract.this);
1218            }
1219            Expression::Coalesce(coalesce) => {
1220                for e in &coalesce.expressions {
1221                    children.push(e);
1222                }
1223            }
1224            Expression::NullIf(nullif) => {
1225                children.push(&nullif.this);
1226                children.push(&nullif.expression);
1227            }
1228            Expression::Table(_table) => {
1229                // Tables don't have child expressions to traverse within scope
1230                // (joins are handled at the Select level)
1231            }
1232            Expression::TryCatch(try_catch) => {
1233                for stmt in &try_catch.try_body {
1234                    children.push(stmt);
1235                }
1236                if let Some(catch_body) = &try_catch.catch_body {
1237                    for stmt in catch_body {
1238                        children.push(stmt);
1239                    }
1240                }
1241            }
1242            Expression::Column(_) | Expression::Literal(_) | Expression::Identifier(_) => {
1243                // Leaf nodes - no children
1244            }
1245            // Subqueries and Exists create new scopes - don't traverse into them
1246            Expression::Subquery(_) | Expression::Exists(_) => {}
1247            _ => {
1248                // Use the canonical AST traversal for other scalar expressions
1249                // (typed functions, field access, filters, etc.). Scope
1250                // boundaries are still enforced by should_stop_at.
1251                children.extend(expr.children());
1252            }
1253        }
1254
1255        children
1256    }
1257}
1258
1259impl<'a> Iterator for WalkInScopeIter<'a> {
1260    type Item = &'a Expression;
1261
1262    fn next(&mut self) -> Option<Self::Item> {
1263        let expr = if self.bfs {
1264            self.queue.pop_front()?
1265        } else {
1266            self.queue.pop_back()?
1267        };
1268
1269        // Get children that don't cross scope boundaries
1270        let children = self.get_children(expr);
1271
1272        if self.bfs {
1273            for child in children {
1274                if !self.should_stop_at(child, false) {
1275                    self.queue.push_back(child);
1276                }
1277            }
1278        } else {
1279            for child in children.into_iter().rev() {
1280                if !self.should_stop_at(child, false) {
1281                    self.queue.push_back(child);
1282                }
1283            }
1284        }
1285
1286        Some(expr)
1287    }
1288}
1289
1290/// Find the first expression matching the predicate within this scope.
1291///
1292/// This does NOT traverse into subscopes.
1293///
1294/// # Arguments
1295/// * `expression` - The root expression
1296/// * `predicate` - Function that returns true for matching expressions
1297/// * `bfs` - If true, uses breadth-first search; otherwise depth-first
1298///
1299/// # Returns
1300/// The first matching expression, or None
1301pub fn find_in_scope<'a, F>(
1302    expression: &'a Expression,
1303    predicate: F,
1304    bfs: bool,
1305) -> Option<&'a Expression>
1306where
1307    F: Fn(&Expression) -> bool,
1308{
1309    walk_in_scope(expression, bfs).find(|e| predicate(e))
1310}
1311
1312/// Find all expressions matching the predicate within this scope.
1313///
1314/// This does NOT traverse into subscopes.
1315///
1316/// # Arguments
1317/// * `expression` - The root expression
1318/// * `predicate` - Function that returns true for matching expressions
1319/// * `bfs` - If true, uses breadth-first search; otherwise depth-first
1320///
1321/// # Returns
1322/// A vector of matching expressions
1323pub fn find_all_in_scope<'a, F>(
1324    expression: &'a Expression,
1325    predicate: F,
1326    bfs: bool,
1327) -> Vec<&'a Expression>
1328where
1329    F: Fn(&Expression) -> bool,
1330{
1331    walk_in_scope(expression, bfs)
1332        .filter(|e| predicate(e))
1333        .collect()
1334}
1335
1336/// Traverse an expression by its "scopes".
1337///
1338/// Returns a list of all scopes in depth-first post-order.
1339///
1340/// # Arguments
1341/// * `expression` - The expression to traverse
1342///
1343/// # Returns
1344/// A vector of all scopes found
1345pub fn traverse_scope(expression: &Expression) -> Vec<Scope> {
1346    match expression {
1347        Expression::Select(_)
1348        | Expression::Union(_)
1349        | Expression::Intersect(_)
1350        | Expression::Except(_)
1351        | Expression::Prepare(_)
1352        | Expression::CreateTable(_) => {
1353            let root = build_scope(expression);
1354            root.traverse().into_iter().cloned().collect()
1355        }
1356        _ => Vec::new(),
1357    }
1358}
1359
1360#[cfg(test)]
1361mod tests {
1362    use super::*;
1363    use crate::parser::Parser;
1364
1365    fn parse_and_build_scope(sql: &str) -> Scope {
1366        let ast = Parser::parse_sql(sql).expect("Failed to parse SQL");
1367        build_scope(&ast[0])
1368    }
1369
1370    #[test]
1371    fn test_simple_select_scope() {
1372        let mut scope = parse_and_build_scope("SELECT a, b FROM t");
1373
1374        assert!(scope.is_root());
1375        assert!(!scope.can_be_correlated);
1376        assert!(scope.sources.contains_key("t"));
1377
1378        let columns = scope.columns();
1379        assert_eq!(columns.len(), 2);
1380    }
1381
1382    #[test]
1383    fn test_derived_table_scope() {
1384        let mut scope = parse_and_build_scope("SELECT x.a FROM (SELECT a FROM t) AS x");
1385
1386        assert!(scope.sources.contains_key("x"));
1387        assert_eq!(scope.derived_table_scopes.len(), 1);
1388
1389        let derived = &mut scope.derived_table_scopes[0];
1390        assert!(derived.is_derived_table());
1391        assert!(derived.sources.contains_key("t"));
1392    }
1393
1394    #[test]
1395    fn test_subquery_collection_respects_relation_boundaries() {
1396        let scope = parse_and_build_scope(
1397            "SELECT t.a FROM t JOIN (SELECT a FROM s) ON t.a = 1 \
1398             WHERE EXISTS (SELECT 1 FROM u) ORDER BY (SELECT MAX(a) FROM v)",
1399        );
1400        assert_eq!(scope.derived_table_scopes.len(), 1);
1401        assert_eq!(scope.subquery_scopes.len(), 2);
1402        let scope = parse_and_build_scope(
1403            "SELECT t.a FROM t JOIN s ON EXISTS (SELECT 1 FROM u WHERE u.a = t.a)",
1404        );
1405        assert_eq!(scope.subquery_scopes.len(), 1);
1406        assert!(scope.subquery_scopes[0].sources.contains_key("u"));
1407    }
1408
1409    #[test]
1410    fn test_non_correlated_subquery() {
1411        let mut scope = parse_and_build_scope("SELECT * FROM t WHERE EXISTS (SELECT b FROM s)");
1412
1413        assert_eq!(scope.subquery_scopes.len(), 1);
1414
1415        let subquery = &mut scope.subquery_scopes[0];
1416        assert!(subquery.is_subquery());
1417        assert!(subquery.can_be_correlated);
1418
1419        // The subquery references only 's', which is in its own sources
1420        assert!(subquery.sources.contains_key("s"));
1421        assert!(!subquery.is_correlated_subquery());
1422    }
1423
1424    #[test]
1425    fn test_correlated_subquery() {
1426        let mut scope =
1427            parse_and_build_scope("SELECT * FROM t WHERE EXISTS (SELECT b FROM s WHERE s.x = t.y)");
1428
1429        assert_eq!(scope.subquery_scopes.len(), 1);
1430
1431        let subquery = &mut scope.subquery_scopes[0];
1432        assert!(subquery.is_subquery());
1433        assert!(subquery.can_be_correlated);
1434
1435        // The subquery references 't.y' which is external
1436        let external = subquery.external_columns();
1437        assert!(!external.is_empty());
1438        assert!(external.iter().any(|c| c.table.as_deref() == Some("t")));
1439        assert!(subquery.is_correlated_subquery());
1440    }
1441
1442    #[test]
1443    fn test_cte_scope() {
1444        let scope = parse_and_build_scope("WITH cte AS (SELECT a FROM t) SELECT * FROM cte");
1445
1446        assert_eq!(scope.cte_scopes.len(), 1);
1447        assert!(scope.cte_sources.contains_key("cte"));
1448
1449        let cte = &scope.cte_scopes[0];
1450        assert!(cte.is_cte());
1451    }
1452
1453    #[test]
1454    fn test_multiple_sources() {
1455        let scope = parse_and_build_scope("SELECT t.a, s.b FROM t JOIN s ON t.id = s.id");
1456
1457        assert!(scope.sources.contains_key("t"));
1458        assert!(scope.sources.contains_key("s"));
1459        assert_eq!(scope.sources.len(), 2);
1460    }
1461
1462    #[test]
1463    fn test_aliased_table() {
1464        let scope = parse_and_build_scope("SELECT x.a FROM t AS x");
1465
1466        // Should be indexed by alias, not original name
1467        assert!(scope.sources.contains_key("x"));
1468        assert!(!scope.sources.contains_key("t"));
1469    }
1470
1471    #[test]
1472    fn test_local_columns() {
1473        let mut scope = parse_and_build_scope("SELECT t.a, t.b, s.c FROM t JOIN s ON t.id = s.id");
1474
1475        let local = scope.local_columns();
1476        // All columns are local since both t and s are in scope.
1477        // Includes JOIN ON references (t.id, s.id).
1478        assert_eq!(local.len(), 5);
1479        assert!(local.iter().all(|c| c.table.is_some()));
1480    }
1481
1482    #[test]
1483    fn test_columns_include_join_on_clause_references() {
1484        let mut scope = parse_and_build_scope(
1485            "SELECT o.total FROM orders o JOIN customers c ON c.id = o.customer_id",
1486        );
1487
1488        let cols: Vec<String> = scope
1489            .columns()
1490            .iter()
1491            .map(|c| match &c.table {
1492                Some(t) => format!("{}.{}", t, c.name),
1493                None => c.name.clone(),
1494            })
1495            .collect();
1496
1497        assert!(cols.contains(&"o.total".to_string()));
1498        assert!(cols.contains(&"c.id".to_string()));
1499        assert!(cols.contains(&"o.customer_id".to_string()));
1500    }
1501
1502    #[test]
1503    fn test_unqualified_columns() {
1504        let mut scope = parse_and_build_scope("SELECT a, b, t.c FROM t");
1505
1506        let unqualified = scope.unqualified_columns();
1507        // Only a and b are unqualified
1508        assert_eq!(unqualified.len(), 2);
1509        assert!(unqualified.iter().all(|c| c.table.is_none()));
1510    }
1511
1512    #[test]
1513    fn test_source_columns() {
1514        let mut scope = parse_and_build_scope("SELECT t.a, t.b, s.c FROM t JOIN s ON t.id = s.id");
1515
1516        let t_cols = scope.source_columns("t");
1517        // t.a, t.b, and t.id from JOIN condition
1518        assert!(t_cols.len() >= 2);
1519        assert!(t_cols.iter().all(|c| c.table.as_deref() == Some("t")));
1520
1521        let s_cols = scope.source_columns("s");
1522        // s.c and s.id from JOIN condition
1523        assert!(s_cols.len() >= 1);
1524        assert!(s_cols.iter().all(|c| c.table.as_deref() == Some("s")));
1525    }
1526
1527    #[test]
1528    fn test_rename_source() {
1529        let mut scope = parse_and_build_scope("SELECT a FROM t");
1530
1531        assert!(scope.sources.contains_key("t"));
1532        scope.rename_source("t", "new_name".to_string());
1533        assert!(!scope.sources.contains_key("t"));
1534        assert!(scope.sources.contains_key("new_name"));
1535    }
1536
1537    #[test]
1538    fn test_remove_source() {
1539        let mut scope = parse_and_build_scope("SELECT a FROM t");
1540
1541        assert!(scope.sources.contains_key("t"));
1542        scope.remove_source("t");
1543        assert!(!scope.sources.contains_key("t"));
1544    }
1545
1546    #[test]
1547    fn test_walk_in_scope() {
1548        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE a > 1").expect("Failed to parse");
1549        let expr = &ast[0];
1550
1551        // Walk should visit all expressions within the scope
1552        let walked: Vec<_> = walk_in_scope(expr, true).collect();
1553        assert!(!walked.is_empty());
1554
1555        // Should include the root SELECT
1556        assert!(walked.iter().any(|e| matches!(e, Expression::Select(_))));
1557        // Should include columns
1558        assert!(walked.iter().any(|e| matches!(e, Expression::Column(_))));
1559    }
1560
1561    #[test]
1562    fn test_find_in_scope() {
1563        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE a > 1").expect("Failed to parse");
1564        let expr = &ast[0];
1565
1566        // Find the first column
1567        let found = find_in_scope(expr, |e| matches!(e, Expression::Column(_)), true);
1568        assert!(found.is_some());
1569        assert!(matches!(found.unwrap(), Expression::Column(_)));
1570    }
1571
1572    #[test]
1573    fn test_find_all_in_scope() {
1574        let ast = Parser::parse_sql("SELECT a, b, c FROM t").expect("Failed to parse");
1575        let expr = &ast[0];
1576
1577        // Find all columns
1578        let found = find_all_in_scope(expr, |e| matches!(e, Expression::Column(_)), true);
1579        assert_eq!(found.len(), 3);
1580    }
1581
1582    #[test]
1583    fn test_traverse_scope() {
1584        let ast =
1585            Parser::parse_sql("SELECT a FROM (SELECT b FROM t) AS x").expect("Failed to parse");
1586        let expr = &ast[0];
1587
1588        let scopes = traverse_scope(expr);
1589        // traverse_scope returns all scopes via Scope::traverse
1590        // which includes derived table and root scopes
1591        assert!(!scopes.is_empty());
1592        // The root scope is always included
1593        assert!(scopes.iter().any(|s| s.is_root()));
1594    }
1595
1596    #[test]
1597    fn test_branch_with_options() {
1598        let ast = Parser::parse_sql("SELECT a FROM t").expect("Failed to parse");
1599        let scope = build_scope(&ast[0]);
1600
1601        let child = scope.branch_with_options(
1602            ast[0].clone(),
1603            ScopeType::Subquery, // Use Subquery to test can_be_correlated
1604            None,
1605            None,
1606            Some(vec!["col1".to_string(), "col2".to_string()]),
1607        );
1608
1609        assert_eq!(child.outer_columns, vec!["col1", "col2"]);
1610        assert!(child.can_be_correlated); // Subqueries are correlated
1611    }
1612
1613    #[test]
1614    fn test_is_udtf() {
1615        let ast = Parser::parse_sql("SELECT a FROM t").expect("Failed to parse");
1616        let scope = Scope::new(ast[0].clone());
1617        assert!(!scope.is_udtf());
1618
1619        let root = build_scope(&ast[0]);
1620        let udtf_scope = root.branch(ast[0].clone(), ScopeType::Udtf);
1621        assert!(udtf_scope.is_udtf());
1622    }
1623
1624    #[test]
1625    fn test_is_union() {
1626        let scope = parse_and_build_scope("SELECT a FROM t UNION SELECT b FROM s");
1627
1628        assert!(scope.is_root());
1629        assert_eq!(scope.union_scopes.len(), 2);
1630        // The children are set operation scopes
1631        assert!(scope.union_scopes[0].is_union());
1632        assert!(scope.union_scopes[1].is_union());
1633    }
1634
1635    #[test]
1636    fn test_union_output_columns() {
1637        let scope = parse_and_build_scope(
1638            "SELECT id, name FROM customers UNION ALL SELECT id, name FROM employees",
1639        );
1640        assert_eq!(scope.output_columns(), vec!["id", "name"]);
1641    }
1642
1643    #[test]
1644    fn test_clear_cache() {
1645        let mut scope = parse_and_build_scope("SELECT t.a FROM t");
1646
1647        // First call populates cache
1648        let _ = scope.columns();
1649        assert!(scope.columns_cache.is_some());
1650
1651        // Clear cache
1652        scope.clear_cache();
1653        assert!(scope.columns_cache.is_none());
1654        assert!(scope.external_columns_cache.is_none());
1655    }
1656
1657    #[test]
1658    fn test_scope_traverse() {
1659        let scope = parse_and_build_scope(
1660            "WITH cte AS (SELECT a FROM t) SELECT * FROM cte WHERE EXISTS (SELECT b FROM s)",
1661        );
1662
1663        let traversed = scope.traverse();
1664        // Should include: CTE scope, subquery scope, root scope
1665        assert!(traversed.len() >= 3);
1666    }
1667
1668    #[test]
1669    fn test_create_table_as_select_scope() {
1670        // Simple CTAS
1671        let scope = parse_and_build_scope("CREATE TABLE out_table AS SELECT 1 AS id FROM src");
1672        assert!(
1673            scope.sources.contains_key("src"),
1674            "CTAS scope should contain the FROM table"
1675        );
1676        assert!(
1677            !scope.sources.contains_key("out_table"),
1678            "CTAS target table should not be treated as a source"
1679        );
1680
1681        // CTAS with multiple FROM tables
1682        let scope = parse_and_build_scope(
1683            "CREATE TABLE out_table AS SELECT a.id FROM foo AS a JOIN bar AS b ON a.id = b.id",
1684        );
1685        assert!(scope.sources.contains_key("a"));
1686        assert!(scope.sources.contains_key("b"));
1687        assert!(
1688            !scope.sources.contains_key("out_table"),
1689            "CTAS target table should not be treated as a source"
1690        );
1691
1692        // CTAS with CTEs
1693        let scope = parse_and_build_scope(
1694            "CREATE TABLE out_table AS WITH cte AS (SELECT 1 AS id FROM src) SELECT * FROM cte",
1695        );
1696        assert!(
1697            scope.sources.contains_key("cte"),
1698            "CTAS with CTE should resolve CTE as source"
1699        );
1700        assert!(
1701            !scope.sources.contains_key("out_table"),
1702            "CTAS target table should not be treated as a source"
1703        );
1704        assert_eq!(scope.cte_scopes.len(), 1);
1705    }
1706
1707    #[test]
1708    fn test_create_table_as_select_traverse() {
1709        let ast = Parser::parse_sql("CREATE TABLE t AS SELECT a FROM src").unwrap();
1710        let scopes = traverse_scope(&ast[0]);
1711        assert!(
1712            !scopes.is_empty(),
1713            "traverse_scope should return scopes for CTAS"
1714        );
1715    }
1716}