llkv_plan/
traversal.rs

1//! Generic iterative traversal utilities for AST-like structures.
2//!
3//! This module provides postorder traversal patterns that avoid stack overflow
4//! on deeply nested expression trees. The approach uses explicit work and result
5//! stacks instead of recursion, which is critical for handling complex SQL queries
6//! (e.g., 55,000+ line files with deeply nested WHERE clauses).
7//!
8//! # Two Approaches
9//!
10//! ## 1. Trait-Based Traversal (for borrowed trees)
11//!
12//! Use [`Traversable`] and [`traverse_postorder`] when you have a tree of borrowed
13//! nodes and want to compute a result without consuming the tree.
14//!
15//! ## 2. Manual Frame Pattern (for owned/consumed trees)
16//!
17//! When transforming owned trees (consuming input, producing output), use the
18//! manual Frame pattern with these helpers:
19//!
20//! - Define a `Frame` enum with `Enter(InputNode)` and `Exit*` variants
21//! - Use `Vec<Frame>` as work_stack, `Vec<OutputNode>` as result_stack
22//! - Process frames in a loop, building results in postorder
23//!
24//! See existing implementations in:
25//! - `llkv-sql/src/sql_engine.rs`: `translate_condition_with_context`
26//! - `llkv-executor/src/translation/expression.rs`: `translate_predicate_with`
27//!
28//! # Example (Trait-Based)
29//!
30//! ```
31//! use llkv_plan::traversal::{Traversable, traverse_postorder};
32//! use llkv_result::Result as LlkvResult;
33//!
34//! enum MyExpr {
35//!     Add(Box<MyExpr>, Box<MyExpr>),
36//!     Value(i32),
37//! }
38//!
39//! impl Traversable for MyExpr {
40//!     type Output = i32;
41//!
42//!     fn visit_children(&self) -> LlkvResult<Vec<&Self>> {
43//!         match self {
44//!             MyExpr::Add(left, right) => Ok(vec![left.as_ref(), right.as_ref()]),
45//!             MyExpr::Value(_) => Ok(vec![]),
46//!         }
47//!     }
48//!
49//!     fn construct(&self, children: Vec<i32>) -> LlkvResult<i32> {
50//!         match self {
51//!             MyExpr::Add(_, _) => Ok(children[0] + children[1]),
52//!             MyExpr::Value(v) => Ok(*v),
53//!         }
54//!     }
55//! }
56//!
57//! let expr = MyExpr::Add(
58//!     Box::new(MyExpr::Value(2)),
59//!     Box::new(MyExpr::Value(3)),
60//! );
61//! assert_eq!(traverse_postorder(&expr).unwrap(), 5);
62//! ```
63//!
64//! # Example (TransformFrame Pattern)
65//!
66//! ```
67//! use llkv_plan::TransformFrame;
68//!
69//! #[derive(Clone)]
70//! enum InputExpr {
71//!     Binary { left: Box<InputExpr>, op: BinaryOp, right: Box<InputExpr> },
72//!     Unary { op: UnaryOp, expr: Box<InputExpr> },
73//!     Value(i32),
74//! }
75//!
76//! #[derive(Clone)]
77//! enum BinaryOp { Add, Subtract }
78//!
79//! #[derive(Clone)]
80//! enum UnaryOp { Negate }
81//!
82//! enum OutputExpr {
83//!     Binary { left: Box<OutputExpr>, op: BinaryOp, right: Box<OutputExpr> },
84//!     Unary { op: UnaryOp, expr: Box<OutputExpr> },
85//!     Value(i32),
86//! }
87//!
88//! enum ExprExitContext {
89//!     Binary(BinaryOp),
90//!     Unary,
91//! }
92//!
93//! type ExprFrame<'a> = TransformFrame<'a, InputExpr, OutputExpr, ExprExitContext>;
94//!
95//! fn transform_expr(root_expr: &InputExpr) -> OutputExpr {
96//!     let mut work_stack = vec![ExprFrame::Enter(root_expr)];
97//!     let mut result_stack = Vec::new();
98//!
99//!     while let Some(frame) = work_stack.pop() {
100//!         match frame {
101//!             ExprFrame::Enter(node) => match node {
102//!                 InputExpr::Binary { left, op, right } => {
103//!                     work_stack.push(ExprFrame::Exit(ExprExitContext::Binary(op.clone())));
104//!                     work_stack.push(ExprFrame::Enter(right));
105//!                     work_stack.push(ExprFrame::Enter(left));
106//!                 }
107//!                 InputExpr::Unary { op: _, expr } => {
108//!                     work_stack.push(ExprFrame::Exit(ExprExitContext::Unary));
109//!                     work_stack.push(ExprFrame::Enter(expr));
110//!                 }
111//!                 InputExpr::Value(v) => {
112//!                     work_stack.push(ExprFrame::Leaf(OutputExpr::Value(*v)));
113//!                 }
114//!             },
115//!             ExprFrame::Exit(exit_context) => match exit_context {
116//!                 ExprExitContext::Binary(op) => {
117//!                     let right = result_stack.pop().unwrap();
118//!                     let left = result_stack.pop().unwrap();
119//!                     result_stack.push(OutputExpr::Binary {
120//!                         left: Box::new(left),
121//!                         op,
122//!                         right: Box::new(right),
123//!                     });
124//!                 }
125//!                 ExprExitContext::Unary => {
126//!                     let expr = result_stack.pop().unwrap();
127//!                     result_stack.push(OutputExpr::Unary {
128//!                         op: UnaryOp::Negate,
129//!                         expr: Box::new(expr),
130//!                     });
131//!                 }
132//!             },
133//!             ExprFrame::Leaf(output) => {
134//!                 result_stack.push(output);
135//!             }
136//!         }
137//!     }
138//!
139//!     result_stack.pop().unwrap()
140//! }
141//! # transform_expr(&InputExpr::Value(42));
142//! ```
143
144use llkv_result::Result as LlkvResult;
145
146/// Internal frame type used by [`traverse_postorder`] for borrowed tree traversal.
147///
148/// This is distinct from [`TransformFrame`] because it's used internally by the
149/// trait-based traversal function and tracks both the node reference and expected
150/// child count for the Exit variant.
151///
152/// Not exported - users should use [`TransformFrame`] for custom traversals.
153enum TraversalFrame<'a, T> {
154    Enter(&'a T),
155    Exit(&'a T, usize), // node and expected child count
156}
157
158/// A frame in the traversal work stack for tree transformations.
159///
160/// Used in manual iterative traversals that consume input nodes and produce
161/// transformed output nodes. This enum provides the common structure while
162/// allowing implementations to define custom exit variants for operation-specific
163/// context (e.g., which binary operator triggered the exit).
164///
165/// The pattern avoids stack overflow on deeply nested trees (50k+ nodes) by using
166/// explicit work and result stacks instead of recursion.
167///
168/// # Type Parameters
169/// * `Input` - The input node type being consumed during traversal
170/// * `Output` - The result type being built on the result stack
171/// * `ExitContext` - Custom enum carrying operation-specific exit state
172///
173/// # Traversal Pattern
174///
175/// ```rust
176/// use llkv_plan::TransformFrame;
177/// # use llkv_result::Result;
178/// # type SqlExpr = String;
179/// # type OutputExpr = i32;
180/// # #[derive(Clone)]
181/// # enum BinaryOp { Add, Subtract }
182///
183/// // Define operation-specific exit variants
184/// enum ScalarExitContext {
185///     BinaryOp { op: BinaryOp },
186///     UnaryMinus,
187/// }
188///
189/// type ScalarTransformFrame<'a> = TransformFrame<'a, SqlExpr, OutputExpr, ScalarExitContext>;
190///
191/// fn transform_scalar(expr: &SqlExpr) -> Result<OutputExpr> {
192///     let mut work_stack: Vec<ScalarTransformFrame> = vec![ScalarTransformFrame::Enter(expr)];
193///     let mut result_stack: Vec<OutputExpr> = Vec::new();
194///
195///     while let Some(frame) = work_stack.pop() {
196///         match frame {
197///             ScalarTransformFrame::Enter(node) => {
198///                 // Decompose node, push exit frame, then push children
199/// #               work_stack.push(ScalarTransformFrame::Leaf(42));
200///             }
201///             ScalarTransformFrame::Exit(ScalarExitContext::BinaryOp { op }) => {
202///                 // Pop children from result_stack, combine, push result
203/// #               let right = result_stack.pop().unwrap();
204/// #               let left = result_stack.pop().unwrap();
205/// #               result_stack.push(left + right);
206///             }
207///             ScalarTransformFrame::Exit(ScalarExitContext::UnaryMinus) => {
208///                 // Pop single child, transform, push result
209/// #               let val = result_stack.pop().unwrap();
210/// #               result_stack.push(-val);
211///             }
212///             ScalarTransformFrame::Leaf(output) => {
213///                 result_stack.push(output);
214///             }
215///         }
216///     }
217///
218///     Ok(result_stack.pop().unwrap())
219/// }
220/// # transform_scalar(&"test".to_string()).unwrap();
221/// ```
222///
223/// # See Also
224///
225/// Concrete implementations using this pattern:
226/// - `llkv-sql/src/sql_engine.rs`: `translate_condition_with_context`, `translate_scalar`
227/// - `llkv-executor/src/translation/expression.rs`: `translate_predicate_with`
228pub enum TransformFrame<'a, Input, Output, ExitContext> {
229    /// Begin visiting an input node (descend to children).
230    Enter(&'a Input),
231
232    /// Complete processing of a node (combine child results).
233    ///
234    /// The `ExitContext` carries operation-specific state needed to combine
235    /// child results into the parent node's result. For example, in arithmetic
236    /// expression evaluation, this might carry which operator to apply.
237    Exit(ExitContext),
238
239    /// A leaf node that has been fully transformed to output.
240    ///
241    /// Used when a node can be immediately converted without further traversal
242    /// (e.g., literals, pre-resolved identifiers).
243    Leaf(Output),
244}
245
246/// Trait for types that support iterative postorder traversal.
247///
248/// Implementors define:
249/// 1. How to decompose a node into children (in `visit_children`)
250/// 2. How to reconstruct a result from child results (in `construct`)
251///
252/// The traversal guarantees postorder: children are fully processed before
253/// their parent's `construct` is called.
254pub trait Traversable: Sized {
255    /// The result type produced by traversing this tree.
256    type Output;
257
258    /// Visit each child of this node.
259    ///
260    /// Called when first encountering a node. Implementations should:
261    /// - Return an iterator over child references
262    /// - Return an empty iterator for leaf nodes
263    ///
264    /// # Returns
265    /// An iterator over references to child nodes, or an error if the node is malformed.
266    fn visit_children(&self) -> LlkvResult<Vec<&Self>>;
267
268    /// Reconstruct this node's result from processed children.
269    ///
270    /// Called after all children have been processed. The `children` vec
271    /// contains child results in the same order they were visited.
272    ///
273    /// # Arguments
274    /// * `children` - Results from child nodes, in visit order
275    ///
276    /// # Returns
277    /// The reconstructed result for this node.
278    fn construct(&self, children: Vec<Self::Output>) -> LlkvResult<Self::Output>;
279}
280
281/// Performs iterative postorder traversal of a tree structure.
282///
283/// This function avoids stack overflow by using explicit work and result stacks
284/// instead of recursion. It's designed for deeply nested ASTs common in complex
285/// SQL queries.
286///
287/// # Type Parameters
288/// * `T` - The node type (must implement [`Traversable`])
289///
290/// # Arguments
291/// * `root` - The root node to traverse
292///
293/// # Returns
294/// The result produced by traversing the entire tree.
295///
296/// # Errors
297/// Returns errors from `visit_children` or `construct` implementations, or if the
298/// traversal encounters an inconsistent state (e.g., result stack underflow).
299///
300/// # Performance
301/// Stack usage is O(tree depth), memory usage is O(tree depth + node count).
302/// For very deep trees (50k+ nesting), thread stack size should be >= 16MB.
303pub fn traverse_postorder<T>(root: &T) -> LlkvResult<T::Output>
304where
305    T: Traversable,
306{
307    let mut work_stack: Vec<TraversalFrame<T>> = vec![TraversalFrame::Enter(root)];
308    let mut result_stack: Vec<T::Output> = Vec::new();
309
310    while let Some(frame) = work_stack.pop() {
311        match frame {
312            TraversalFrame::Enter(node) => {
313                let children = node.visit_children()?;
314                let child_count = children.len();
315
316                // Schedule exit frame first
317                work_stack.push(TraversalFrame::Exit(node, child_count));
318
319                // Then push children in reverse order for correct postorder
320                for child in children.into_iter().rev() {
321                    work_stack.push(TraversalFrame::Enter(child));
322                }
323            }
324            TraversalFrame::Exit(node, child_count) => {
325                // Collect child results from result stack
326                if result_stack.len() < child_count {
327                    return Err(llkv_result::Error::Internal(
328                        "traverse_postorder: result stack underflow".into(),
329                    ));
330                }
331
332                let start = result_stack.len() - child_count;
333                let child_results: Vec<T::Output> = result_stack.drain(start..).collect();
334
335                // Process this node with its children's results
336                let output = node.construct(child_results)?;
337                result_stack.push(output);
338            }
339        }
340    }
341
342    // Should have exactly one result
343    if result_stack.len() != 1 {
344        return Err(llkv_result::Error::Internal(format!(
345            "traverse_postorder: expected 1 result, got {}",
346            result_stack.len()
347        )));
348    }
349
350    result_stack.pop().ok_or_else(|| {
351        llkv_result::Error::Internal("traverse_postorder: empty result stack".into())
352    })
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358
359    // Simple expression tree for testing
360    #[derive(Debug, Clone, PartialEq)]
361    enum TestExpr {
362        Add(Box<TestExpr>, Box<TestExpr>),
363        Mul(Box<TestExpr>, Box<TestExpr>),
364        Value(i32),
365    }
366
367    impl Traversable for TestExpr {
368        type Output = i32;
369
370        fn visit_children(&self) -> LlkvResult<Vec<&Self>> {
371            match self {
372                TestExpr::Add(left, right) | TestExpr::Mul(left, right) => {
373                    Ok(vec![left.as_ref(), right.as_ref()])
374                }
375                TestExpr::Value(_) => Ok(vec![]),
376            }
377        }
378
379        fn construct(&self, children: Vec<i32>) -> LlkvResult<i32> {
380            match self {
381                TestExpr::Add(_, _) => Ok(children[0] + children[1]),
382                TestExpr::Mul(_, _) => Ok(children[0] * children[1]),
383                TestExpr::Value(v) => Ok(*v),
384            }
385        }
386    }
387
388    #[test]
389    fn test_simple_evaluation() {
390        // 2 + 3 = 5
391        let expr = TestExpr::Add(Box::new(TestExpr::Value(2)), Box::new(TestExpr::Value(3)));
392        assert_eq!(traverse_postorder(&expr).unwrap(), 5);
393    }
394
395    #[test]
396    fn test_nested_evaluation() {
397        // (2 + 3) * 4 = 20
398        let expr = TestExpr::Mul(
399            Box::new(TestExpr::Add(
400                Box::new(TestExpr::Value(2)),
401                Box::new(TestExpr::Value(3)),
402            )),
403            Box::new(TestExpr::Value(4)),
404        );
405        assert_eq!(traverse_postorder(&expr).unwrap(), 20);
406    }
407
408    #[test]
409    fn test_deeply_nested() {
410        // Build a deeply nested expression: 1 + 1 + 1 + ... (1000 times)
411        let mut expr = TestExpr::Value(1);
412        for _ in 0..1000 {
413            expr = TestExpr::Add(Box::new(expr), Box::new(TestExpr::Value(1)));
414        }
415        assert_eq!(traverse_postorder(&expr).unwrap(), 1001);
416    }
417}