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