Skip to main content

polyglot_sql/
traversal.rs

1//! Tree traversal utilities for SQL expression ASTs.
2//!
3//! This module provides read-only traversal, search, and transformation utilities
4//! for the [`Expression`] tree produced by the parser. Because Rust's ownership
5//! model does not allow parent pointers inside the AST, parent information is
6//! tracked externally via [`TreeContext`] (built on demand).
7//!
8//! # Traversal
9//!
10//! Two iterator types are provided:
11//! - [`DfsIter`] -- depth-first (pre-order) traversal using a stack. Visits a node
12//!   before its children. Good for top-down analysis and early termination.
13//! - [`BfsIter`] -- breadth-first (level-order) traversal using a queue. Visits all
14//!   nodes at depth N before any node at depth N+1. Good for level-aware analysis.
15//!
16//! Both are available through the [`ExpressionWalk`] trait methods [`dfs`](ExpressionWalk::dfs)
17//! and [`bfs`](ExpressionWalk::bfs).
18//!
19//! # Searching
20//!
21//! The [`ExpressionWalk`] trait also provides convenience methods for finding expressions:
22//! [`find`](ExpressionWalk::find), [`find_all`](ExpressionWalk::find_all),
23//! [`contains`](ExpressionWalk::contains), and [`count`](ExpressionWalk::count).
24//! Common predicates are available as free functions: [`is_column`], [`is_literal`],
25//! [`is_function`], [`is_aggregate`], [`is_window_function`], [`is_subquery`], and
26//! [`is_select`].
27//!
28//! # Transformation
29//!
30//! [`transform_all`] rewrites every expression visited by the read-only walkers,
31//! bottom-up, using the generated child visitor and an explicit stack.
32//! [`transform`] and [`transform_map`] retain the selective child traversal of
33//! [`transform_recursive`](crate::dialects::transform_recursive) for compatibility.
34//! The [`ExpressionWalk::transform_owned`] method provides the same capability as
35//! an owned method on `Expression`.
36//!
37//! Based on traversal patterns from `sqlglot/expressions.py`.
38
39#![cfg_attr(
40    not(any(feature = "ast-tools", feature = "generate", feature = "semantic")),
41    allow(dead_code)
42)]
43
44use crate::expressions::{Expression, TableRef};
45use std::collections::{HashMap, VecDeque};
46
47/// Unique identifier for expression nodes during traversal
48pub type NodeId = usize;
49
50/// Information about a node's parent relationship
51#[derive(Debug, Clone)]
52pub struct ParentInfo {
53    /// The NodeId of the parent (None for root)
54    pub parent_id: Option<NodeId>,
55    /// Which argument/field in the parent this node occupies
56    pub arg_key: String,
57    /// Index if the node is part of a list (e.g., expressions in SELECT)
58    pub index: Option<usize>,
59}
60
61/// External parent-tracking context for an expression tree.
62///
63/// Since Rust's ownership model does not allow intrusive parent pointers in the AST,
64/// `TreeContext` provides an on-demand side-table that maps each node (identified by
65/// a [`NodeId`]) to its [`ParentInfo`] (parent node, field name, and list index).
66///
67/// Build a context from any expression root with [`TreeContext::build`], then query
68/// parent relationships with [`get`](TreeContext::get), ancestry chains with
69/// [`ancestors_of`](TreeContext::ancestors_of), or tree depth with
70/// [`depth_of`](TreeContext::depth_of).
71///
72/// This is useful when analysis requires upward navigation (e.g., determining whether
73/// a column reference appears inside a WHERE clause or a JOIN condition).
74#[derive(Debug, Default)]
75pub struct TreeContext {
76    /// Map from NodeId to parent information
77    nodes: HashMap<NodeId, ParentInfo>,
78    /// Counter for generating NodeIds
79    next_id: NodeId,
80    /// Stack for tracking current path during traversal
81    path: Vec<(NodeId, String, Option<usize>)>,
82}
83
84impl TreeContext {
85    /// Create a new empty tree context
86    pub fn new() -> Self {
87        Self::default()
88    }
89
90    /// Build context from an expression tree
91    pub fn build(root: &Expression) -> Self {
92        let mut ctx = Self::new();
93        ctx.visit_expr(root);
94        ctx
95    }
96
97    /// Visit an expression and record parent information
98    fn visit_expr(&mut self, expr: &Expression) -> NodeId {
99        let id = self.next_id;
100        self.next_id += 1;
101
102        // Record parent info based on current path
103        let parent_info = if let Some((parent_id, arg_key, index)) = self.path.last() {
104            ParentInfo {
105                parent_id: Some(*parent_id),
106                arg_key: arg_key.clone(),
107                index: *index,
108            }
109        } else {
110            ParentInfo {
111                parent_id: None,
112                arg_key: String::new(),
113                index: None,
114            }
115        };
116        self.nodes.insert(id, parent_info);
117
118        crate::ast_children::for_each_child(expr, |child_path, child| {
119            let (key, index) = child_location(child_path);
120            self.path.push((id, key, index));
121            self.visit_expr(child);
122            self.path.pop();
123        });
124
125        id
126    }
127
128    /// Get parent info for a node
129    pub fn get(&self, id: NodeId) -> Option<&ParentInfo> {
130        self.nodes.get(&id)
131    }
132
133    /// Get the depth of a node (0 for root)
134    pub fn depth_of(&self, id: NodeId) -> usize {
135        let mut depth = 0;
136        let mut current = id;
137        while let Some(info) = self.nodes.get(&current) {
138            if let Some(parent_id) = info.parent_id {
139                depth += 1;
140                current = parent_id;
141            } else {
142                break;
143            }
144        }
145        depth
146    }
147
148    /// Get ancestors of a node (parent, grandparent, etc.)
149    pub fn ancestors_of(&self, id: NodeId) -> Vec<NodeId> {
150        let mut ancestors = Vec::new();
151        let mut current = id;
152        while let Some(info) = self.nodes.get(&current) {
153            if let Some(parent_id) = info.parent_id {
154                ancestors.push(parent_id);
155                current = parent_id;
156            } else {
157                break;
158            }
159        }
160        ancestors
161    }
162}
163
164fn child_location(path: &[crate::ast_children::ChildPathSegment]) -> (String, Option<usize>) {
165    use crate::ast_children::ChildPathSegment;
166
167    let mut key = String::new();
168    let mut index = None;
169    for (position, segment) in path.iter().enumerate() {
170        match segment {
171            ChildPathSegment::Field(field) => {
172                if !key.is_empty() {
173                    key.push('.');
174                }
175                key.push_str(field);
176            }
177            ChildPathSegment::Index(value) if position + 1 == path.len() => {
178                index = Some(*value);
179            }
180            ChildPathSegment::Index(value) => {
181                key.push('[');
182                key.push_str(&value.to_string());
183                key.push(']');
184            }
185        }
186    }
187    (key, index)
188}
189
190/// Pre-order depth-first iterator over an expression tree.
191///
192/// Visits each node before its children, using a stack-based approach. This means
193/// the root is yielded first, followed by the entire left subtree (recursively),
194/// then the right subtree. For a binary expression `a + b`, the iteration order
195/// is: `Add`, `a`, `b`.
196///
197/// Created via [`ExpressionWalk::dfs`] or [`DfsIter::new`].
198pub struct DfsIter<'a> {
199    stack: Vec<&'a Expression>,
200}
201
202impl<'a> DfsIter<'a> {
203    /// Create a new DFS iterator starting from the given expression
204    pub fn new(root: &'a Expression) -> Self {
205        Self { stack: vec![root] }
206    }
207}
208
209impl<'a> Iterator for DfsIter<'a> {
210    type Item = &'a Expression;
211
212    fn next(&mut self) -> Option<Self::Item> {
213        let expr = self.stack.pop()?;
214
215        let child_start = self.stack.len();
216        crate::ast_children::for_each_child_untracked(expr, |child| self.stack.push(child));
217        self.stack[child_start..].reverse();
218
219        Some(expr)
220    }
221}
222
223/// Level-order breadth-first iterator over an expression tree.
224///
225/// Visits all nodes at depth N before any node at depth N+1, using a queue-based
226/// approach. For a tree `(a + b) = c`, the iteration order is: `Eq` (depth 0),
227/// `Add`, `c` (depth 1), `a`, `b` (depth 2).
228///
229/// Created via [`ExpressionWalk::bfs`] or [`BfsIter::new`].
230pub struct BfsIter<'a> {
231    queue: VecDeque<&'a Expression>,
232}
233
234impl<'a> BfsIter<'a> {
235    /// Create a new BFS iterator starting from the given expression
236    pub fn new(root: &'a Expression) -> Self {
237        let mut queue = VecDeque::new();
238        queue.push_back(root);
239        Self { queue }
240    }
241}
242
243impl<'a> Iterator for BfsIter<'a> {
244    type Item = &'a Expression;
245
246    fn next(&mut self) -> Option<Self::Item> {
247        let expr = self.queue.pop_front()?;
248
249        crate::ast_children::for_each_child_untracked(expr, |child| self.queue.push_back(child));
250
251        Some(expr)
252    }
253}
254
255/// Extension trait that adds traversal and search methods to [`Expression`].
256///
257/// This trait is implemented for `Expression` and provides a fluent API for
258/// iterating, searching, measuring, and transforming expression trees without
259/// needing to import the iterator types directly.
260pub trait ExpressionWalk {
261    /// Returns a depth-first (pre-order) iterator over this expression and all descendants.
262    ///
263    /// The root node is yielded first, then its children are visited recursively
264    /// from left to right.
265    fn dfs(&self) -> DfsIter<'_>;
266
267    /// Returns a breadth-first (level-order) iterator over this expression and all descendants.
268    ///
269    /// All nodes at depth N are yielded before any node at depth N+1.
270    fn bfs(&self) -> BfsIter<'_>;
271
272    /// Finds the first expression matching `predicate` in depth-first order.
273    ///
274    /// Returns `None` if no descendant (including this node) matches.
275    fn find<F>(&self, predicate: F) -> Option<&Expression>
276    where
277        F: Fn(&Expression) -> bool;
278
279    /// Collects all expressions matching `predicate` in depth-first order.
280    ///
281    /// Returns an empty vector if no descendants match.
282    fn find_all<F>(&self, predicate: F) -> Vec<&Expression>
283    where
284        F: Fn(&Expression) -> bool;
285
286    /// Returns `true` if this node or any descendant matches `predicate`.
287    fn contains<F>(&self, predicate: F) -> bool
288    where
289        F: Fn(&Expression) -> bool;
290
291    /// Counts how many nodes (including this one) match `predicate`.
292    fn count<F>(&self, predicate: F) -> usize
293    where
294        F: Fn(&Expression) -> bool;
295
296    /// Returns direct child expressions of this node.
297    ///
298    /// Collects all single-child fields and list-child fields into a flat vector
299    /// of references. Leaf nodes return an empty vector.
300    fn children(&self) -> Vec<&Expression>;
301
302    /// Returns the maximum depth of the expression tree rooted at this node.
303    ///
304    /// A leaf node has depth 0, a node whose deepest child is a leaf has depth 1, etc.
305    fn tree_depth(&self) -> usize;
306
307    /// Transforms this expression tree bottom-up using the given function (owned variant).
308    ///
309    /// Uses the selective legacy traversal of [`transform`]. For complete child
310    /// coverage, use [`transform_all`]. Visited children are transformed first.
311    /// Return `Ok(None)` from `fun` to replace a node with `NULL`.
312    /// Return `Ok(Some(expr))` to substitute the node with `expr`.
313    #[cfg(any(
314        feature = "transpile",
315        feature = "ast-tools",
316        feature = "generate",
317        feature = "semantic"
318    ))]
319    fn transform_owned<F>(self, fun: F) -> crate::Result<Expression>
320    where
321        F: Fn(Expression) -> crate::Result<Option<Expression>>,
322        Self: Sized;
323}
324
325impl ExpressionWalk for Expression {
326    fn dfs(&self) -> DfsIter<'_> {
327        DfsIter::new(self)
328    }
329
330    fn bfs(&self) -> BfsIter<'_> {
331        BfsIter::new(self)
332    }
333
334    fn find<F>(&self, predicate: F) -> Option<&Expression>
335    where
336        F: Fn(&Expression) -> bool,
337    {
338        self.dfs().find(|e| predicate(e))
339    }
340
341    fn find_all<F>(&self, predicate: F) -> Vec<&Expression>
342    where
343        F: Fn(&Expression) -> bool,
344    {
345        self.dfs().filter(|e| predicate(e)).collect()
346    }
347
348    fn contains<F>(&self, predicate: F) -> bool
349    where
350        F: Fn(&Expression) -> bool,
351    {
352        self.dfs().any(|e| predicate(e))
353    }
354
355    fn count<F>(&self, predicate: F) -> usize
356    where
357        F: Fn(&Expression) -> bool,
358    {
359        self.dfs().filter(|e| predicate(e)).count()
360    }
361
362    fn children(&self) -> Vec<&Expression> {
363        let mut result: Vec<&Expression> = Vec::new();
364        crate::ast_children::for_each_child(self, |_, child| result.push(child));
365        result
366    }
367
368    fn tree_depth(&self) -> usize {
369        let mut max_depth = 0usize;
370        let mut stack = vec![(self, 0usize)];
371        while let Some((node, depth)) = stack.pop() {
372            max_depth = max_depth.max(depth);
373            crate::ast_children::for_each_child(node, |_, child| {
374                stack.push((child, depth + 1));
375            });
376        }
377        max_depth
378    }
379
380    #[cfg(any(
381        feature = "transpile",
382        feature = "ast-tools",
383        feature = "generate",
384        feature = "semantic"
385    ))]
386    fn transform_owned<F>(self, fun: F) -> crate::Result<Expression>
387    where
388        F: Fn(Expression) -> crate::Result<Option<Expression>>,
389    {
390        transform(self, &fun)
391    }
392}
393
394/// Transforms an expression tree bottom-up, with optional node removal.
395///
396/// Uses the legacy dialect traversal to transform children before their parent.
397/// This traversal is selective: it skips some typed functions and syntax fields.
398/// Use [`transform_all`] for complete expression traversal.
399/// If `fun` returns `Ok(None)`, the node is replaced with an `Expression::Null`.
400/// If `fun` returns `Ok(Some(expr))`, the node is replaced with `expr`.
401///
402/// This is the primary transformation entry point when callers need the ability to
403/// "delete" nodes by returning `None`.
404///
405/// # Example
406///
407/// ```rust,ignore
408/// use polyglot_sql::traversal::transform;
409///
410/// // Remove all Paren wrapper nodes from a tree
411/// let result = transform(expr, &|e| match e {
412///     Expression::Paren(p) => Ok(Some(p.this)),
413///     other => Ok(Some(other)),
414/// })?;
415/// ```
416#[cfg(any(
417    feature = "transpile",
418    feature = "ast-tools",
419    feature = "generate",
420    feature = "semantic"
421))]
422pub fn transform<F>(expr: Expression, fun: &F) -> crate::Result<Expression>
423where
424    F: Fn(Expression) -> crate::Result<Option<Expression>>,
425{
426    crate::dialects::transform_recursive(expr, &|e| match fun(e)? {
427        Some(transformed) => Ok(transformed),
428        None => Ok(Expression::Null(crate::expressions::Null)),
429    })
430}
431
432/// Transforms an expression tree bottom-up using the legacy dialect traversal.
433///
434/// Child traversal is selective; use [`transform_all`] to visit every expression,
435/// including arguments of all typed function nodes.
436///
437/// Like [`transform`], but `fun` returns an `Expression` directly rather than
438/// `Option<Expression>`, so nodes cannot be deleted. This is a convenience wrapper
439/// for the common case where every node is mapped to exactly one output node.
440///
441/// # Example
442///
443/// ```rust,ignore
444/// use polyglot_sql::traversal::transform_map;
445///
446/// // Uppercase all column names in a tree
447/// let result = transform_map(expr, &|e| match e {
448///     Expression::Column(mut c) => {
449///         c.name.name = c.name.name.to_uppercase();
450///         Ok(Expression::Column(c))
451///     }
452///     other => Ok(other),
453/// })?;
454/// ```
455#[cfg(any(
456    feature = "transpile",
457    feature = "ast-tools",
458    feature = "generate",
459    feature = "semantic"
460))]
461pub fn transform_map<F>(expr: Expression, fun: &F) -> crate::Result<Expression>
462where
463    F: Fn(Expression) -> crate::Result<Expression>,
464{
465    crate::dialects::transform_recursive(expr, fun)
466}
467
468/// Transforms every expression node bottom-up using the generated child visitor.
469///
470/// Visits the same nodes as [`ExpressionWalk::dfs`], including every typed function
471/// argument and expressions nested in clauses. Each original node is transformed
472/// once, after its children, in their stored order. Replacement nodes are not
473/// revisited, and callback errors propagate immediately.
474///
475/// Like the read-only walkers, this visits actual `Expression` fields. Embedded
476/// structs such as `Update.table` and `Select.joins` are not synthesized as extra
477/// `Table` or `Join` nodes; their expression children are still visited. A callback
478/// can edit those structs when it receives their containing expression.
479///
480/// Traversal uses an explicit stack and does not clone subtrees. Inferred types and
481/// other fields marked as AST metadata are preserved without visiting them.
482#[cfg(any(
483    feature = "transpile",
484    feature = "ast-tools",
485    feature = "generate",
486    feature = "semantic"
487))]
488pub fn transform_all<F>(expr: Expression, fun: &F) -> crate::Result<Expression>
489where
490    F: Fn(Expression) -> crate::Result<Expression>,
491{
492    transform_with_dispatch(expr, fun, &|_| true, &|node, fun| fun(node))
493}
494
495// Shared stack engine. Dialect rewrites retain their selective dispatch and
496// wrapper semantics; general AST rewrites use the generated visitor everywhere.
497#[cfg(any(
498    feature = "transpile",
499    feature = "ast-tools",
500    feature = "generate",
501    feature = "semantic"
502))]
503pub(crate) fn transform_with_dispatch<F, P, G>(
504    expr: Expression,
505    transform_fn: &F,
506    uses_generated_dispatch: &P,
507    fallback: &G,
508) -> crate::Result<Expression>
509where
510    F: Fn(Expression) -> crate::Result<Expression>,
511    P: Fn(&Expression) -> bool,
512    G: Fn(Expression, &F) -> crate::Result<Expression>,
513{
514    enum Task {
515        Visit(Expression),
516        Finish {
517            shell: Expression,
518            child_count: usize,
519        },
520    }
521
522    let mut tasks = vec![Task::Visit(expr)];
523    let mut results = Vec::new();
524
525    while let Some(task) = tasks.pop() {
526        match task {
527            Task::Visit(mut expression) => {
528                if !uses_generated_dispatch(&expression) {
529                    results.push(fallback(expression, transform_fn)?);
530                    continue;
531                }
532
533                let mut children = Vec::new();
534                crate::ast_children::for_each_child_mut(&mut expression, |child| {
535                    children.push(std::mem::replace(
536                        child,
537                        Expression::Null(crate::expressions::Null),
538                    ));
539                });
540                let child_count = children.len();
541                tasks.push(Task::Finish {
542                    shell: expression,
543                    child_count,
544                });
545                for child in children.into_iter().rev() {
546                    tasks.push(Task::Visit(child));
547                }
548            }
549            Task::Finish {
550                mut shell,
551                child_count,
552            } => {
553                if results.len() < child_count {
554                    return Err(crate::error::Error::Internal(
555                        "transform result stack underflow".to_string(),
556                    ));
557                }
558                let transformed_children = results.split_off(results.len() - child_count);
559                let mut transformed_children = transformed_children.into_iter();
560                crate::ast_children::for_each_child_mut(&mut shell, |child| {
561                    *child = transformed_children
562                        .next()
563                        .expect("validated transform child count");
564                });
565                if transformed_children.next().is_some() {
566                    return Err(crate::error::Error::Internal(
567                        "transform child restoration mismatch".to_string(),
568                    ));
569                }
570                results.push(transform_fn(shell)?);
571            }
572        }
573    }
574
575    match results.len() {
576        1 => Ok(results.pop().expect("single transform result")),
577        _ => Err(crate::error::Error::Internal(
578            "unexpected transform result stack size".to_string(),
579        )),
580    }
581}
582
583// ---------------------------------------------------------------------------
584// Common expression predicates
585// ---------------------------------------------------------------------------
586// These free functions are intended for use with the search methods on
587// `ExpressionWalk` (e.g., `expr.find(is_column)`, `expr.contains(is_aggregate)`).
588
589/// Returns `true` if `expr` is a column reference ([`Expression::Column`]).
590pub fn is_column(expr: &Expression) -> bool {
591    matches!(expr, Expression::Column(_))
592}
593
594/// Returns `true` if `expr` is a literal value (number, string, boolean, or NULL).
595pub fn is_literal(expr: &Expression) -> bool {
596    matches!(
597        expr,
598        Expression::Literal(_) | Expression::Boolean(_) | Expression::Null(_)
599    )
600}
601
602/// Returns `true` if `expr` is a function call (regular or aggregate).
603pub fn is_function(expr: &Expression) -> bool {
604    matches!(
605        expr,
606        Expression::Function(_) | Expression::AggregateFunction(_) | Expression::Hierarchy(_)
607    )
608}
609
610/// Returns `true` if `expr` is a subquery ([`Expression::Subquery`]).
611pub fn is_subquery(expr: &Expression) -> bool {
612    matches!(expr, Expression::Subquery(_))
613}
614
615/// Returns `true` if `expr` is a SELECT statement ([`Expression::Select`]).
616pub fn is_select(expr: &Expression) -> bool {
617    matches!(expr, Expression::Select(_))
618}
619
620/// Returns `true` if `expr` is an aggregate function.
621///
622/// This is the canonical aggregate classifier used throughout the crate. Window-only
623/// functions and scalar helper functions with aggregate-like names are intentionally
624/// excluded.
625pub fn is_aggregate(expr: &Expression) -> bool {
626    matches!(
627        expr,
628        Expression::AggregateFunction(_)
629            | Expression::Count(_)
630            | Expression::Sum(_)
631            | Expression::Avg(_)
632            | Expression::Min(_)
633            | Expression::Max(_)
634            | Expression::GroupConcat(_)
635            | Expression::StringAgg(_)
636            | Expression::ListAgg(_)
637            | Expression::ArrayAgg(_)
638            | Expression::CountIf(_)
639            | Expression::SumIf(_)
640            | Expression::Stddev(_)
641            | Expression::StddevPop(_)
642            | Expression::StddevSamp(_)
643            | Expression::Variance(_)
644            | Expression::VarPop(_)
645            | Expression::VarSamp(_)
646            | Expression::Median(_)
647            | Expression::Mode(_)
648            | Expression::First(_)
649            | Expression::Last(_)
650            | Expression::AnyValue(_)
651            | Expression::ApproxDistinct(_)
652            | Expression::ApproxCountDistinct(_)
653            | Expression::ApproxPercentile(_)
654            | Expression::Percentile(_)
655            | Expression::PercentileCont(_)
656            | Expression::PercentileDisc(_)
657            | Expression::LogicalAnd(_)
658            | Expression::LogicalOr(_)
659            | Expression::Skewness(_)
660            | Expression::ArrayConcatAgg(_)
661            | Expression::ArrayUniqueAgg(_)
662            | Expression::BoolXorAgg(_)
663            | Expression::BitwiseAndAgg(_)
664            | Expression::BitwiseOrAgg(_)
665            | Expression::BitwiseXorAgg(_)
666            | Expression::JsonArrayAgg(_)
667            | Expression::JsonObjectAgg(_)
668            | Expression::JSONArrayAgg(_)
669            | Expression::JSONObjectAgg(_)
670            | Expression::JSONBObjectAgg(_)
671            | Expression::ParameterizedAgg(_)
672            | Expression::ArgMax(_)
673            | Expression::ArgMin(_)
674            | Expression::ApproxTopK(_)
675            | Expression::ApproxTopKAccumulate(_)
676            | Expression::ApproxTopKCombine(_)
677            | Expression::ApproxTopSum(_)
678            | Expression::ApproxQuantiles(_)
679            | Expression::Grouping(_)
680            | Expression::GroupingId(_)
681            | Expression::AnonymousAggFunc(_)
682            | Expression::CombinedAggFunc(_)
683            | Expression::CombinedParameterizedAgg(_)
684            | Expression::HashAgg(_)
685            | Expression::Hll(_)
686            | Expression::Minhash(_)
687            | Expression::ObjectAgg(_)
688            | Expression::AIAgg(_)
689            | Expression::Quantile(_)
690            | Expression::ApproxQuantile(_)
691            | Expression::Corr(_)
692            | Expression::CovarPop(_)
693            | Expression::CovarSamp(_)
694            | Expression::RegrValx(_)
695            | Expression::RegrValy(_)
696            | Expression::RegrAvgx(_)
697            | Expression::RegrAvgy(_)
698            | Expression::RegrCount(_)
699            | Expression::RegrIntercept(_)
700            | Expression::RegrR2(_)
701            | Expression::RegrSxx(_)
702            | Expression::RegrSxy(_)
703            | Expression::RegrSyy(_)
704            | Expression::RegrSlope(_)
705    )
706}
707
708/// Returns `true` if `expr` is a window function ([`Expression::WindowFunction`]).
709pub fn is_window_function(expr: &Expression) -> bool {
710    matches!(expr, Expression::WindowFunction(_))
711}
712
713/// Collects all column references ([`Expression::Column`]) from the expression tree.
714///
715/// Performs a depth-first search and returns references to every column node found.
716pub fn get_columns(expr: &Expression) -> Vec<&Expression> {
717    expr.find_all(is_column)
718}
719
720/// Collects all table references ([`Expression::Table`]) from the expression tree.
721///
722/// Performs a depth-first search and returns references to every table node found.
723///
724/// Note: DML target tables (`Insert.table`, `Update.table`, `Delete.table`) are
725/// stored as `TableRef` struct fields, not as `Expression::Table` nodes, so they
726/// are not reachable via tree traversal. Use [`get_all_tables`] to include those.
727pub fn get_tables(expr: &Expression) -> Vec<&Expression> {
728    expr.find_all(|e| matches!(e, Expression::Table(_)))
729}
730
731/// Collects **all** referenced tables from the expression tree, including DML
732/// target tables that are stored as `TableRef` struct fields and are therefore
733/// not reachable through normal tree traversal.
734///
735/// Returns owned `Expression::Table` values. This is the comprehensive version
736/// of [`get_tables`] — use it when you need to discover every table referenced
737/// in a statement, including inside CTE bodies containing INSERT/UPDATE/DELETE.
738pub fn get_all_tables(expr: &Expression) -> Vec<Expression> {
739    use std::collections::HashSet;
740
741    let mut seen = HashSet::new();
742    let mut result = Vec::new();
743
744    // First: collect all Expression::Table nodes found via DFS.
745    for node in expr.dfs() {
746        if let Expression::Table(t) = node {
747            let qname = table_ref_qualified_name(t);
748            if seen.insert(qname) {
749                result.push(node.clone());
750            }
751        }
752
753        // Also extract DML target TableRef fields not reachable via iter_children.
754        let refs: Vec<&TableRef> = match node {
755            Expression::Insert(ins) => vec![&ins.table],
756            Expression::Update(upd) => {
757                let mut v = vec![&upd.table];
758                v.extend(upd.extra_tables.iter());
759                v
760            }
761            Expression::Delete(del) => {
762                let mut v = vec![&del.table];
763                v.extend(del.using.iter());
764                v
765            }
766            _ => continue,
767        };
768        for tref in refs {
769            if tref.name.name.is_empty() {
770                continue;
771            }
772            let qname = table_ref_qualified_name(tref);
773            if seen.insert(qname) {
774                result.push(Expression::Table(Box::new(tref.clone())));
775            }
776        }
777    }
778
779    result
780}
781
782/// Build a qualified name string from a TableRef for deduplication purposes.
783fn table_ref_qualified_name(t: &TableRef) -> String {
784    let mut name = String::new();
785    if let Some(ref cat) = t.catalog {
786        name.push_str(&cat.name);
787        name.push('.');
788    }
789    if let Some(ref schema) = t.schema {
790        name.push_str(&schema.name);
791        name.push('.');
792    }
793    name.push_str(&t.name.name);
794    name
795}
796
797/// Extracts the underlying [`Expression::Table`] from a MERGE field that may
798/// be a bare `Table`, an `Alias` wrapping a `Table`, or an `Identifier`.
799/// Returns `None` if the expression doesn't contain a recognisable table.
800fn unwrap_merge_table(expr: &Expression) -> Option<&Expression> {
801    match expr {
802        Expression::Table(_) => Some(expr),
803        Expression::Alias(alias) => match &alias.this {
804            Expression::Table(_) => Some(&alias.this),
805            _ => None,
806        },
807        _ => None,
808    }
809}
810
811/// Returns the target table of a MERGE statement (the `Merge.this` field),
812/// unwrapping any alias wrapper to yield the underlying [`Expression::Table`].
813///
814/// Returns `None` if `expr` is not a `Merge` or the target isn't a recognisable table.
815pub fn get_merge_target(expr: &Expression) -> Option<&Expression> {
816    match expr {
817        Expression::Merge(m) => unwrap_merge_table(&m.this),
818        _ => None,
819    }
820}
821
822/// Returns the source table of a MERGE statement (the `Merge.using` field),
823/// unwrapping any alias wrapper to yield the underlying [`Expression::Table`].
824///
825/// Returns `None` if `expr` is not a `Merge`, the source isn't a recognisable
826/// table (e.g. it's a subquery), or the source is otherwise unresolvable.
827pub fn get_merge_source(expr: &Expression) -> Option<&Expression> {
828    match expr {
829        Expression::Merge(m) => unwrap_merge_table(&m.using),
830        _ => None,
831    }
832}
833
834/// Returns `true` if the expression tree contains any aggregate function calls.
835pub fn contains_aggregate(expr: &Expression) -> bool {
836    expr.contains(is_aggregate)
837}
838
839/// Returns `true` if the expression tree contains any window function calls.
840pub fn contains_window_function(expr: &Expression) -> bool {
841    expr.contains(is_window_function)
842}
843
844/// Returns `true` if the expression tree contains any subquery nodes.
845pub fn contains_subquery(expr: &Expression) -> bool {
846    expr.contains(is_subquery)
847}
848
849// ---------------------------------------------------------------------------
850// Extended type predicates
851// ---------------------------------------------------------------------------
852
853/// Macro for generating simple type-predicate functions.
854macro_rules! is_type {
855    ($name:ident, $($variant:pat),+ $(,)?) => {
856        /// Returns `true` if `expr` matches the expected AST variant(s).
857        pub fn $name(expr: &Expression) -> bool {
858            matches!(expr, $($variant)|+)
859        }
860    };
861}
862
863// Query
864is_type!(is_insert, Expression::Insert(_));
865is_type!(is_update, Expression::Update(_));
866is_type!(is_delete, Expression::Delete(_));
867is_type!(is_merge, Expression::Merge(_));
868is_type!(is_union, Expression::Union(_));
869is_type!(is_intersect, Expression::Intersect(_));
870is_type!(is_except, Expression::Except(_));
871
872// Identifiers & literals
873is_type!(is_boolean, Expression::Boolean(_));
874is_type!(is_null_literal, Expression::Null(_));
875is_type!(is_star, Expression::Star(_));
876is_type!(is_identifier, Expression::Identifier(_));
877is_type!(is_table, Expression::Table(_));
878
879// Comparison
880is_type!(is_eq, Expression::Eq(_));
881is_type!(is_neq, Expression::Neq(_));
882is_type!(is_lt, Expression::Lt(_));
883is_type!(is_lte, Expression::Lte(_));
884is_type!(is_gt, Expression::Gt(_));
885is_type!(is_gte, Expression::Gte(_));
886is_type!(is_like, Expression::Like(_));
887is_type!(is_ilike, Expression::ILike(_));
888
889// Arithmetic
890is_type!(is_add, Expression::Add(_));
891is_type!(is_sub, Expression::Sub(_));
892is_type!(is_mul, Expression::Mul(_));
893is_type!(is_div, Expression::Div(_));
894is_type!(is_mod, Expression::Mod(_));
895is_type!(is_concat, Expression::Concat(_));
896
897// Logical
898is_type!(is_and, Expression::And(_));
899is_type!(is_or, Expression::Or(_));
900is_type!(is_not, Expression::Not(_));
901
902// Predicates
903is_type!(is_in, Expression::In(_));
904is_type!(is_between, Expression::Between(_));
905is_type!(is_is_null, Expression::IsNull(_));
906is_type!(is_exists, Expression::Exists(_));
907
908// Functions
909is_type!(is_count, Expression::Count(_));
910is_type!(is_sum, Expression::Sum(_));
911is_type!(is_avg, Expression::Avg(_));
912is_type!(is_min_func, Expression::Min(_));
913is_type!(is_max_func, Expression::Max(_));
914is_type!(is_coalesce, Expression::Coalesce(_));
915is_type!(is_null_if, Expression::NullIf(_));
916is_type!(is_cast, Expression::Cast(_));
917is_type!(is_try_cast, Expression::TryCast(_));
918is_type!(is_safe_cast, Expression::SafeCast(_));
919is_type!(is_case, Expression::Case(_));
920
921// Clauses
922is_type!(is_from, Expression::From(_));
923is_type!(is_join, Expression::Join(_));
924is_type!(is_where, Expression::Where(_));
925is_type!(is_group_by, Expression::GroupBy(_));
926is_type!(is_having, Expression::Having(_));
927is_type!(is_order_by, Expression::OrderBy(_));
928is_type!(is_limit, Expression::Limit(_));
929is_type!(is_offset, Expression::Offset(_));
930is_type!(is_with, Expression::With(_));
931is_type!(is_cte, Expression::Cte(_));
932is_type!(is_alias, Expression::Alias(_));
933is_type!(is_paren, Expression::Paren(_));
934is_type!(is_ordered, Expression::Ordered(_));
935
936// DDL
937is_type!(is_create_table, Expression::CreateTable(_));
938is_type!(is_drop_table, Expression::DropTable(_));
939is_type!(is_alter_table, Expression::AlterTable(_));
940is_type!(is_create_index, Expression::CreateIndex(_));
941is_type!(is_drop_index, Expression::DropIndex(_));
942is_type!(is_create_view, Expression::CreateView(_));
943is_type!(is_drop_view, Expression::DropView(_));
944
945// ---------------------------------------------------------------------------
946// Composite predicates
947// ---------------------------------------------------------------------------
948
949/// Returns `true` if `expr` is a query statement (SELECT, INSERT, UPDATE, DELETE, or MERGE).
950pub fn is_query(expr: &Expression) -> bool {
951    matches!(
952        expr,
953        Expression::Select(_)
954            | Expression::Insert(_)
955            | Expression::Update(_)
956            | Expression::Delete(_)
957            | Expression::Merge(_)
958    )
959}
960
961/// Returns `true` if `expr` is a set operation (UNION, INTERSECT, or EXCEPT).
962pub fn is_set_operation(expr: &Expression) -> bool {
963    matches!(
964        expr,
965        Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_)
966    )
967}
968
969/// Returns `true` if `expr` is a comparison operator.
970pub fn is_comparison(expr: &Expression) -> bool {
971    matches!(
972        expr,
973        Expression::Eq(_)
974            | Expression::Neq(_)
975            | Expression::Lt(_)
976            | Expression::Lte(_)
977            | Expression::Gt(_)
978            | Expression::Gte(_)
979            | Expression::Like(_)
980            | Expression::ILike(_)
981    )
982}
983
984/// Returns `true` if `expr` is an arithmetic operator.
985pub fn is_arithmetic(expr: &Expression) -> bool {
986    matches!(
987        expr,
988        Expression::Add(_)
989            | Expression::Sub(_)
990            | Expression::Mul(_)
991            | Expression::Div(_)
992            | Expression::Mod(_)
993    )
994}
995
996/// Returns `true` if `expr` is a logical operator (AND, OR, NOT).
997pub fn is_logical(expr: &Expression) -> bool {
998    matches!(
999        expr,
1000        Expression::And(_) | Expression::Or(_) | Expression::Not(_)
1001    )
1002}
1003
1004/// Returns `true` if `expr` is a DDL statement.
1005pub fn is_ddl(expr: &Expression) -> bool {
1006    matches!(
1007        expr,
1008        Expression::CreateTable(_)
1009            | Expression::DropTable(_)
1010            | Expression::Undrop(_)
1011            | Expression::AlterTable(_)
1012            | Expression::CreateIndex(_)
1013            | Expression::DropIndex(_)
1014            | Expression::CreateView(_)
1015            | Expression::DropView(_)
1016            | Expression::AlterView(_)
1017            | Expression::CreateSchema(_)
1018            | Expression::DropSchema(_)
1019            | Expression::CreateDatabase(_)
1020            | Expression::DropDatabase(_)
1021            | Expression::CreateFunction(_)
1022            | Expression::DropFunction(_)
1023            | Expression::CreateProcedure(_)
1024            | Expression::DropProcedure(_)
1025            | Expression::CreateSequence(_)
1026            | Expression::CreateSynonym(_)
1027            | Expression::DropSequence(_)
1028            | Expression::AlterSequence(_)
1029            | Expression::CreateTrigger(_)
1030            | Expression::DropTrigger(_)
1031            | Expression::CreateType(_)
1032            | Expression::DropType(_)
1033    )
1034}
1035
1036/// Find the parent of `target` within the tree rooted at `root`.
1037///
1038/// Uses pointer identity ([`std::ptr::eq`]) — `target` must be a reference
1039/// obtained from the same tree (e.g., via [`ExpressionWalk::find`] or DFS iteration).
1040///
1041/// Returns `None` if `target` is the root itself or is not found in the tree.
1042pub fn find_parent<'a>(root: &'a Expression, target: &Expression) -> Option<&'a Expression> {
1043    fn search<'a>(node: &'a Expression, target: *const Expression) -> Option<&'a Expression> {
1044        let mut result = None;
1045        crate::ast_children::for_each_child(node, |_, child| {
1046            if result.is_none() {
1047                if std::ptr::eq(child, target) {
1048                    result = Some(node);
1049                } else {
1050                    result = search(child, target);
1051                }
1052            }
1053        });
1054        result
1055    }
1056
1057    search(root, target as *const Expression)
1058}
1059
1060/// Find the first ancestor of `target` matching `predicate`, walking from
1061/// parent toward root.
1062///
1063/// Uses pointer identity for target lookup. Returns `None` if no ancestor
1064/// matches or `target` is not found in the tree.
1065pub fn find_ancestor<'a, F>(
1066    root: &'a Expression,
1067    target: &Expression,
1068    predicate: F,
1069) -> Option<&'a Expression>
1070where
1071    F: Fn(&Expression) -> bool,
1072{
1073    // Build path from root to target
1074    fn build_path<'a>(
1075        node: &'a Expression,
1076        target: *const Expression,
1077        path: &mut Vec<&'a Expression>,
1078    ) -> bool {
1079        if std::ptr::eq(node, target) {
1080            return true;
1081        }
1082        path.push(node);
1083        let mut found = false;
1084        crate::ast_children::for_each_child(node, |_, child| {
1085            if !found {
1086                found = build_path(child, target, path);
1087            }
1088        });
1089        if found {
1090            return true;
1091        }
1092        path.pop();
1093        false
1094    }
1095
1096    let mut path = Vec::new();
1097    if !build_path(root, target as *const Expression, &mut path) {
1098        return None;
1099    }
1100
1101    // Walk path in reverse (parent first, then grandparent, etc.)
1102    for ancestor in path.iter().rev() {
1103        if predicate(ancestor) {
1104            return Some(ancestor);
1105        }
1106    }
1107    None
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112    use super::*;
1113    use crate::expressions::{BinaryOp, Column, Identifier, LikeOp, Literal, TableRef};
1114
1115    fn make_column(name: &str) -> Expression {
1116        Expression::boxed_column(Column {
1117            name: Identifier {
1118                name: name.to_string(),
1119                quoted: false,
1120                trailing_comments: vec![],
1121                span: None,
1122            },
1123            table: None,
1124            join_mark: false,
1125            trailing_comments: vec![],
1126            span: None,
1127            inferred_type: None,
1128        })
1129    }
1130
1131    fn make_literal(value: i64) -> Expression {
1132        Expression::Literal(Box::new(Literal::Number(value.to_string())))
1133    }
1134
1135    #[test]
1136    fn test_dfs_simple() {
1137        let left = make_column("a");
1138        let right = make_literal(1);
1139        let expr = Expression::Eq(Box::new(BinaryOp {
1140            left,
1141            right,
1142            left_comments: vec![],
1143            operator_comments: vec![],
1144            trailing_comments: vec![],
1145            inferred_type: None,
1146        }));
1147
1148        let nodes: Vec<_> = expr.dfs().collect();
1149        assert_eq!(nodes.len(), 3); // Eq, Column, Literal
1150        assert!(matches!(nodes[0], Expression::Eq(_)));
1151        assert!(matches!(nodes[1], Expression::Column(_)));
1152        assert!(matches!(nodes[2], Expression::Literal(_)));
1153    }
1154
1155    #[test]
1156    fn test_find() {
1157        let left = make_column("a");
1158        let right = make_literal(1);
1159        let expr = Expression::Eq(Box::new(BinaryOp {
1160            left,
1161            right,
1162            left_comments: vec![],
1163            operator_comments: vec![],
1164            trailing_comments: vec![],
1165            inferred_type: None,
1166        }));
1167
1168        let column = expr.find(is_column);
1169        assert!(column.is_some());
1170        assert!(matches!(column.unwrap(), Expression::Column(_)));
1171
1172        let literal = expr.find(is_literal);
1173        assert!(literal.is_some());
1174        assert!(matches!(literal.unwrap(), Expression::Literal(_)));
1175    }
1176
1177    #[test]
1178    fn test_find_all() {
1179        let col1 = make_column("a");
1180        let col2 = make_column("b");
1181        let expr = Expression::And(Box::new(BinaryOp {
1182            left: col1,
1183            right: col2,
1184            left_comments: vec![],
1185            operator_comments: vec![],
1186            trailing_comments: vec![],
1187            inferred_type: None,
1188        }));
1189
1190        let columns = expr.find_all(is_column);
1191        assert_eq!(columns.len(), 2);
1192    }
1193
1194    #[test]
1195    fn test_contains() {
1196        let col = make_column("a");
1197        let lit = make_literal(1);
1198        let expr = Expression::Eq(Box::new(BinaryOp {
1199            left: col,
1200            right: lit,
1201            left_comments: vec![],
1202            operator_comments: vec![],
1203            trailing_comments: vec![],
1204            inferred_type: None,
1205        }));
1206
1207        assert!(expr.contains(is_column));
1208        assert!(expr.contains(is_literal));
1209        assert!(!expr.contains(is_subquery));
1210    }
1211
1212    #[test]
1213    fn test_count() {
1214        let col1 = make_column("a");
1215        let col2 = make_column("b");
1216        let lit = make_literal(1);
1217
1218        let inner = Expression::Add(Box::new(BinaryOp {
1219            left: col2,
1220            right: lit,
1221            left_comments: vec![],
1222            operator_comments: vec![],
1223            trailing_comments: vec![],
1224            inferred_type: None,
1225        }));
1226
1227        let expr = Expression::Eq(Box::new(BinaryOp {
1228            left: col1,
1229            right: inner,
1230            left_comments: vec![],
1231            operator_comments: vec![],
1232            trailing_comments: vec![],
1233            inferred_type: None,
1234        }));
1235
1236        assert_eq!(expr.count(is_column), 2);
1237        assert_eq!(expr.count(is_literal), 1);
1238    }
1239
1240    #[test]
1241    fn test_tree_depth() {
1242        // Single node
1243        let lit = make_literal(1);
1244        assert_eq!(lit.tree_depth(), 0);
1245
1246        // One level
1247        let col = make_column("a");
1248        let expr = Expression::Eq(Box::new(BinaryOp {
1249            left: col,
1250            right: lit.clone(),
1251            left_comments: vec![],
1252            operator_comments: vec![],
1253            trailing_comments: vec![],
1254            inferred_type: None,
1255        }));
1256        assert_eq!(expr.tree_depth(), 1);
1257
1258        // Two levels
1259        let inner = Expression::Add(Box::new(BinaryOp {
1260            left: make_column("b"),
1261            right: lit,
1262            left_comments: vec![],
1263            operator_comments: vec![],
1264            trailing_comments: vec![],
1265            inferred_type: None,
1266        }));
1267        let outer = Expression::Eq(Box::new(BinaryOp {
1268            left: make_column("a"),
1269            right: inner,
1270            left_comments: vec![],
1271            operator_comments: vec![],
1272            trailing_comments: vec![],
1273            inferred_type: None,
1274        }));
1275        assert_eq!(outer.tree_depth(), 2);
1276    }
1277
1278    #[test]
1279    fn test_tree_context() {
1280        let col = make_column("a");
1281        let lit = make_literal(1);
1282        let expr = Expression::Eq(Box::new(BinaryOp {
1283            left: col,
1284            right: lit,
1285            left_comments: vec![],
1286            operator_comments: vec![],
1287            trailing_comments: vec![],
1288            inferred_type: None,
1289        }));
1290
1291        let ctx = TreeContext::build(&expr);
1292
1293        // Root has no parent
1294        let root_info = ctx.get(0).unwrap();
1295        assert!(root_info.parent_id.is_none());
1296
1297        // Children have root as parent
1298        let left_info = ctx.get(1).unwrap();
1299        assert_eq!(left_info.parent_id, Some(0));
1300        assert_eq!(left_info.arg_key, "left");
1301
1302        let right_info = ctx.get(2).unwrap();
1303        assert_eq!(right_info.parent_id, Some(0));
1304        assert_eq!(right_info.arg_key, "right");
1305    }
1306
1307    // -- Step 8: transform / transform_map tests --
1308
1309    #[test]
1310    fn test_transform_rename_columns() {
1311        let ast = crate::parser::Parser::parse_sql("SELECT a, b FROM t").unwrap();
1312        let expr = ast[0].clone();
1313        let result = super::transform_map(expr, &|e| {
1314            if let Expression::Column(ref c) = e {
1315                if c.name.name == "a" {
1316                    return Ok(Expression::boxed_column(Column {
1317                        name: Identifier::new("alpha"),
1318                        table: c.table.clone(),
1319                        join_mark: false,
1320                        trailing_comments: vec![],
1321                        span: None,
1322                        inferred_type: None,
1323                    }));
1324                }
1325            }
1326            Ok(e)
1327        })
1328        .unwrap();
1329        let sql = crate::generator::Generator::sql(&result).unwrap();
1330        assert!(sql.contains("alpha"), "Expected 'alpha' in: {}", sql);
1331        assert!(sql.contains("b"), "Expected 'b' in: {}", sql);
1332    }
1333
1334    #[test]
1335    fn test_transform_noop() {
1336        let ast = crate::parser::Parser::parse_sql("SELECT 1 + 2").unwrap();
1337        let expr = ast[0].clone();
1338        let result = super::transform_map(expr.clone(), &|e| Ok(e)).unwrap();
1339        let sql1 = crate::generator::Generator::sql(&expr).unwrap();
1340        let sql2 = crate::generator::Generator::sql(&result).unwrap();
1341        assert_eq!(sql1, sql2);
1342    }
1343
1344    #[test]
1345    fn test_transform_nested() {
1346        let ast = crate::parser::Parser::parse_sql("SELECT a + b FROM t").unwrap();
1347        let expr = ast[0].clone();
1348        let result = super::transform_map(expr, &|e| {
1349            if let Expression::Column(ref c) = e {
1350                return Ok(Expression::Literal(Box::new(Literal::Number(
1351                    if c.name.name == "a" { "1" } else { "2" }.to_string(),
1352                ))));
1353            }
1354            Ok(e)
1355        })
1356        .unwrap();
1357        let sql = crate::generator::Generator::sql(&result).unwrap();
1358        assert_eq!(sql, "SELECT 1 + 2 FROM t");
1359    }
1360
1361    #[test]
1362    fn test_transform_error() {
1363        let ast = crate::parser::Parser::parse_sql("SELECT a FROM t").unwrap();
1364        let expr = ast[0].clone();
1365        let result = super::transform_map(expr, &|e| {
1366            if let Expression::Column(ref c) = e {
1367                if c.name.name == "a" {
1368                    return Err(crate::error::Error::parse("test error", 0, 0, 0, 0));
1369                }
1370            }
1371            Ok(e)
1372        });
1373        assert!(result.is_err());
1374    }
1375
1376    #[test]
1377    fn test_transform_owned_trait() {
1378        let ast = crate::parser::Parser::parse_sql("SELECT x FROM t").unwrap();
1379        let expr = ast[0].clone();
1380        let result = expr.transform_owned(|e| Ok(Some(e))).unwrap();
1381        let sql = crate::generator::Generator::sql(&result).unwrap();
1382        assert_eq!(sql, "SELECT x FROM t");
1383    }
1384
1385    // -- children() tests --
1386
1387    #[test]
1388    fn test_children_leaf() {
1389        let lit = make_literal(1);
1390        assert_eq!(lit.children().len(), 0);
1391    }
1392
1393    #[test]
1394    fn test_children_binary_op() {
1395        let left = make_column("a");
1396        let right = make_literal(1);
1397        let expr = Expression::Eq(Box::new(BinaryOp {
1398            left,
1399            right,
1400            left_comments: vec![],
1401            operator_comments: vec![],
1402            trailing_comments: vec![],
1403            inferred_type: None,
1404        }));
1405        let children = expr.children();
1406        assert_eq!(children.len(), 2);
1407        assert!(matches!(children[0], Expression::Column(_)));
1408        assert!(matches!(children[1], Expression::Literal(_)));
1409    }
1410
1411    #[test]
1412    fn test_children_select() {
1413        let ast = crate::parser::Parser::parse_sql("SELECT a, b FROM t").unwrap();
1414        let expr = &ast[0];
1415        let children = expr.children();
1416        // Should include select list items (a, b)
1417        assert!(children.len() >= 2);
1418    }
1419
1420    #[test]
1421    fn test_children_follow_ast_field_order() {
1422        let ast = crate::parser::Parser::parse_sql("SELECT a, b FROM t").unwrap();
1423        let children = ast[0].children();
1424
1425        assert!(matches!(children[0], Expression::Column(column) if column.name.name == "a"));
1426        assert!(matches!(children[1], Expression::Column(column) if column.name.name == "b"));
1427        assert!(matches!(children[2], Expression::Table(table) if table.name.name == "t"));
1428    }
1429
1430    #[test]
1431    fn test_traversal_covers_previously_omitted_expression_fields() {
1432        let like = Expression::Like(Box::new(LikeOp {
1433            left: make_column("name"),
1434            right: Expression::Literal(Box::new(Literal::String("x%".to_string()))),
1435            escape: Some(Expression::Literal(Box::new(Literal::String(
1436                "!".to_string(),
1437            )))),
1438            quantifier: None,
1439            inferred_type: None,
1440        }));
1441        let nodes: Vec<_> = like.dfs().collect();
1442        assert_eq!(nodes.len(), 4);
1443        assert!(matches!(
1444            nodes[3],
1445            Expression::Literal(literal)
1446                if matches!(literal.as_ref(), Literal::String(value) if value == "!")
1447        ));
1448
1449        let mut table = TableRef::new("events");
1450        table.hints.push(make_column("table_hint"));
1451        table.identifier_func = Some(Box::new(Expression::identifier("dynamic_table")));
1452        let table = Expression::Table(Box::new(table));
1453        let children = table.children();
1454        assert_eq!(children.len(), 2);
1455        assert!(
1456            matches!(children[0], Expression::Column(column) if column.name.name == "table_hint")
1457        );
1458        assert!(
1459            matches!(children[1], Expression::Identifier(identifier) if identifier.name == "dynamic_table")
1460        );
1461    }
1462
1463    #[test]
1464    fn test_children_select_includes_from_and_join_sources() {
1465        let ast = crate::parser::Parser::parse_sql(
1466            "SELECT u.id FROM users u JOIN orders o ON u.id = o.user_id",
1467        )
1468        .unwrap();
1469        let expr = &ast[0];
1470        let children = expr.children();
1471
1472        let table_names: Vec<&str> = children
1473            .iter()
1474            .filter_map(|e| match e {
1475                Expression::Table(t) => Some(t.name.name.as_str()),
1476                _ => None,
1477            })
1478            .collect();
1479
1480        assert!(table_names.contains(&"users"));
1481        assert!(table_names.contains(&"orders"));
1482    }
1483
1484    #[test]
1485    fn test_get_tables_includes_insert_query_sources() {
1486        let ast = crate::parser::Parser::parse_sql(
1487            "INSERT INTO dst (id) SELECT s.id FROM src s JOIN dim d ON s.id = d.id",
1488        )
1489        .unwrap();
1490        let expr = &ast[0];
1491        let tables = get_tables(expr);
1492        let names: Vec<&str> = tables
1493            .iter()
1494            .filter_map(|e| match e {
1495                Expression::Table(t) => Some(t.name.name.as_str()),
1496                _ => None,
1497            })
1498            .collect();
1499
1500        assert!(names.contains(&"src"));
1501        assert!(names.contains(&"dim"));
1502    }
1503
1504    // -- find_parent() tests --
1505
1506    #[test]
1507    fn test_find_parent_binary() {
1508        let left = make_column("a");
1509        let right = make_literal(1);
1510        let expr = Expression::Eq(Box::new(BinaryOp {
1511            left,
1512            right,
1513            left_comments: vec![],
1514            operator_comments: vec![],
1515            trailing_comments: vec![],
1516            inferred_type: None,
1517        }));
1518
1519        // Find the column child and get its parent
1520        let col = expr.find(is_column).unwrap();
1521        let parent = super::find_parent(&expr, col);
1522        assert!(parent.is_some());
1523        assert!(matches!(parent.unwrap(), Expression::Eq(_)));
1524    }
1525
1526    #[test]
1527    fn test_find_parent_root_has_none() {
1528        let lit = make_literal(1);
1529        let parent = super::find_parent(&lit, &lit);
1530        assert!(parent.is_none());
1531    }
1532
1533    // -- find_ancestor() tests --
1534
1535    #[test]
1536    fn test_find_ancestor_select() {
1537        let ast = crate::parser::Parser::parse_sql("SELECT a FROM t WHERE a > 1").unwrap();
1538        let expr = &ast[0];
1539
1540        // Find a column inside the WHERE clause
1541        let where_col = expr.dfs().find(|e| {
1542            if let Expression::Column(c) = e {
1543                c.name.name == "a"
1544            } else {
1545                false
1546            }
1547        });
1548        assert!(where_col.is_some());
1549
1550        // Find Select ancestor of that column
1551        let ancestor = super::find_ancestor(expr, where_col.unwrap(), is_select);
1552        assert!(ancestor.is_some());
1553        assert!(matches!(ancestor.unwrap(), Expression::Select(_)));
1554    }
1555
1556    #[test]
1557    fn test_find_ancestor_no_match() {
1558        let left = make_column("a");
1559        let right = make_literal(1);
1560        let expr = Expression::Eq(Box::new(BinaryOp {
1561            left,
1562            right,
1563            left_comments: vec![],
1564            operator_comments: vec![],
1565            trailing_comments: vec![],
1566            inferred_type: None,
1567        }));
1568
1569        let col = expr.find(is_column).unwrap();
1570        let ancestor = super::find_ancestor(&expr, col, is_select);
1571        assert!(ancestor.is_none());
1572    }
1573
1574    #[test]
1575    fn test_ancestors() {
1576        let col = make_column("a");
1577        let lit = make_literal(1);
1578        let inner = Expression::Add(Box::new(BinaryOp {
1579            left: col,
1580            right: lit,
1581            left_comments: vec![],
1582            operator_comments: vec![],
1583            trailing_comments: vec![],
1584            inferred_type: None,
1585        }));
1586        let outer = Expression::Eq(Box::new(BinaryOp {
1587            left: make_column("b"),
1588            right: inner,
1589            left_comments: vec![],
1590            operator_comments: vec![],
1591            trailing_comments: vec![],
1592            inferred_type: None,
1593        }));
1594
1595        let ctx = TreeContext::build(&outer);
1596
1597        // The inner Add's left child (column "a") should have ancestors
1598        // Node 0: Eq
1599        // Node 1: Column "b" (left of Eq)
1600        // Node 2: Add (right of Eq)
1601        // Node 3: Column "a" (left of Add)
1602        // Node 4: Literal (right of Add)
1603
1604        let ancestors = ctx.ancestors_of(3);
1605        assert_eq!(ancestors, vec![2, 0]); // Add, then Eq
1606    }
1607
1608    #[test]
1609    fn test_get_merge_target_and_source() {
1610        let dialect = crate::Dialect::get(crate::dialects::DialectType::Generic);
1611
1612        // MERGE with aliased target and source tables
1613        let sql = "MERGE INTO orders o USING customers c ON o.customer_id = c.id WHEN MATCHED THEN UPDATE SET amount = amount + 100";
1614        let exprs = dialect.parse(sql).unwrap();
1615        let expr = &exprs[0];
1616
1617        assert!(is_merge(expr));
1618        assert!(is_query(expr));
1619
1620        let target = get_merge_target(expr).expect("should find target table");
1621        assert!(matches!(target, Expression::Table(_)));
1622        if let Expression::Table(t) = target {
1623            assert_eq!(t.name.name, "orders");
1624        }
1625
1626        let source = get_merge_source(expr).expect("should find source table");
1627        assert!(matches!(source, Expression::Table(_)));
1628        if let Expression::Table(t) = source {
1629            assert_eq!(t.name.name, "customers");
1630        }
1631    }
1632
1633    #[test]
1634    fn test_get_merge_source_subquery_returns_none() {
1635        let dialect = crate::Dialect::get(crate::dialects::DialectType::Generic);
1636
1637        // MERGE with subquery source — get_merge_source should return None
1638        let sql = "MERGE INTO orders o USING (SELECT * FROM customers) c ON o.customer_id = c.id WHEN MATCHED THEN DELETE";
1639        let exprs = dialect.parse(sql).unwrap();
1640        let expr = &exprs[0];
1641
1642        assert!(get_merge_target(expr).is_some());
1643        assert!(get_merge_source(expr).is_none());
1644    }
1645
1646    #[test]
1647    fn test_get_merge_on_non_merge_returns_none() {
1648        let dialect = crate::Dialect::get(crate::dialects::DialectType::Generic);
1649        let exprs = dialect.parse("SELECT 1").unwrap();
1650        assert!(get_merge_target(&exprs[0]).is_none());
1651        assert!(get_merge_source(&exprs[0]).is_none());
1652    }
1653
1654    #[test]
1655    fn test_get_tables_finds_tables_inside_in_subquery() {
1656        let dialect = crate::Dialect::get(crate::dialects::DialectType::Generic);
1657        let sql = "SELECT id, name FROM customers WHERE id IN (SELECT customer_id FROM orders WHERE amount > 1000)";
1658        let exprs = dialect.parse(sql).unwrap();
1659        let tables = get_tables(&exprs[0]);
1660        let names: Vec<&str> = tables
1661            .iter()
1662            .filter_map(|e| {
1663                if let Expression::Table(t) = e {
1664                    Some(t.name.name.as_str())
1665                } else {
1666                    None
1667                }
1668            })
1669            .collect();
1670        assert!(names.contains(&"customers"), "should find outer table");
1671        assert!(names.contains(&"orders"), "should find subquery table");
1672    }
1673
1674    #[test]
1675    fn test_get_tables_finds_tables_inside_exists_subquery() {
1676        let dialect = crate::Dialect::get(crate::dialects::DialectType::Generic);
1677        let sql = "SELECT * FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id)";
1678        let exprs = dialect.parse(sql).unwrap();
1679        let tables = get_tables(&exprs[0]);
1680        let names: Vec<&str> = tables
1681            .iter()
1682            .filter_map(|e| {
1683                if let Expression::Table(t) = e {
1684                    Some(t.name.name.as_str())
1685                } else {
1686                    None
1687                }
1688            })
1689            .collect();
1690        assert!(names.contains(&"customers"), "should find outer table");
1691        assert!(
1692            names.contains(&"orders"),
1693            "should find EXISTS subquery table"
1694        );
1695    }
1696
1697    #[test]
1698    fn test_get_tables_finds_tables_in_correlated_subquery() {
1699        let dialect = crate::Dialect::get(crate::dialects::DialectType::TSQL);
1700        let sql = "SELECT id, name FROM customers WHERE id IN (SELECT customer_id FROM orders WHERE amount > 1000)";
1701        let exprs = dialect.parse(sql).unwrap();
1702        let tables = get_tables(&exprs[0]);
1703        let names: Vec<&str> = tables
1704            .iter()
1705            .filter_map(|e| {
1706                if let Expression::Table(t) = e {
1707                    Some(t.name.name.as_str())
1708                } else {
1709                    None
1710                }
1711            })
1712            .collect();
1713        assert!(
1714            names.contains(&"customers"),
1715            "TSQL: should find outer table"
1716        );
1717        assert!(
1718            names.contains(&"orders"),
1719            "TSQL: should find subquery table"
1720        );
1721    }
1722}