llkv_expr/
format.rs

1//! Formatting utilities for expression and predicate trees.
2
3use std::fmt::Display;
4use std::ops::Bound;
5
6use crate::expr::{Filter, Operator};
7use crate::{Expr, Literal, ScalarExpr};
8
9impl<'a, F> Expr<'a, F>
10where
11    F: Display + Copy,
12{
13    /// Render a predicate expression as a human-readable string.
14    pub fn format_display(&self) -> String {
15        use Expr::*;
16
17        // Iterative postorder traversal using work/result stack pattern.
18        // This uses a two-pass approach: first collect nodes in postorder, then format them.
19        // This avoids stack overflow on deeply nested expressions (50k+ nodes).
20        let mut traverse_stack = Vec::new();
21        let mut postorder = Vec::new();
22        traverse_stack.push(self);
23
24        while let Some(node) = traverse_stack.pop() {
25            postorder.push(node);
26            match node {
27                And(children) | Or(children) => {
28                    for child in children {
29                        traverse_stack.push(child);
30                    }
31                }
32                Not(inner) => traverse_stack.push(inner),
33                Pred(_)
34                | Compare { .. }
35                | InList { .. }
36                | IsNull { .. }
37                | Literal(_)
38                | Exists(_) => {}
39            }
40        }
41
42        let mut result_stack: Vec<String> = Vec::new();
43        for node in postorder.into_iter().rev() {
44            match node {
45                And(children) => {
46                    if children.is_empty() {
47                        result_stack.push("TRUE".to_string());
48                    } else {
49                        let mut parts = Vec::with_capacity(children.len());
50                        for _ in 0..children.len() {
51                            parts.push(result_stack.pop().unwrap_or_default());
52                        }
53                        parts.reverse();
54                        result_stack.push(parts.join(" AND "));
55                    }
56                }
57                Or(children) => {
58                    if children.is_empty() {
59                        result_stack.push("FALSE".to_string());
60                    } else {
61                        let mut parts = Vec::with_capacity(children.len());
62                        for _ in 0..children.len() {
63                            parts.push(result_stack.pop().unwrap_or_default());
64                        }
65                        parts.reverse();
66                        result_stack.push(parts.join(" OR "));
67                    }
68                }
69                Not(_) => {
70                    let inner = result_stack.pop().unwrap_or_default();
71                    result_stack.push(format!("NOT ({inner})"));
72                }
73                Pred(filter) => {
74                    result_stack.push(format_filter(filter));
75                }
76                Compare { left, op, right } => {
77                    result_stack.push(format!(
78                        "{} {} {}",
79                        left.format_display(),
80                        op.as_str(),
81                        right.format_display()
82                    ));
83                }
84                InList {
85                    expr,
86                    list,
87                    negated,
88                } => {
89                    let expr_str = expr.format_display();
90                    let mut parts = Vec::with_capacity(list.len());
91                    for value in list {
92                        parts.push(value.format_display());
93                    }
94                    let keyword = if *negated { "NOT IN" } else { "IN" };
95                    result_stack.push(format!("{} {} ({})", expr_str, keyword, parts.join(", ")));
96                }
97                IsNull { expr, negated } => {
98                    let expr_str = expr.format_display();
99                    let keyword = if *negated { "IS NOT NULL" } else { "IS NULL" };
100                    result_stack.push(format!("{} {}", expr_str, keyword));
101                }
102                Literal(value) => {
103                    result_stack.push(if *value {
104                        "TRUE".to_string()
105                    } else {
106                        "FALSE".to_string()
107                    });
108                }
109                Exists(_) => {
110                    result_stack.push("EXISTS(...)".to_string());
111                }
112            }
113        }
114
115        result_stack.pop().unwrap_or_default()
116    }
117}
118
119impl<F> ScalarExpr<F>
120where
121    F: Display + Copy,
122{
123    /// Render a scalar expression as a human-readable string.
124    pub fn format_display(&self) -> String {
125        use ScalarExpr::*;
126
127        let mut traverse_stack = Vec::new();
128        let mut postorder = Vec::new();
129        traverse_stack.push(self);
130
131        while let Some(node) = traverse_stack.pop() {
132            postorder.push(node);
133            match node {
134                Column(_) | Literal(_) => {}
135                Binary { left, right, .. } | Compare { left, right, .. } => {
136                    traverse_stack.push(right);
137                    traverse_stack.push(left);
138                }
139                Not(inner) => traverse_stack.push(inner),
140                IsNull { expr, .. } => traverse_stack.push(expr),
141                Aggregate(_) => {}
142                GetField { base, .. } => traverse_stack.push(base),
143                Cast { expr, .. } => traverse_stack.push(expr),
144                Case {
145                    operand,
146                    branches,
147                    else_expr,
148                } => {
149                    if let Some(op) = operand {
150                        traverse_stack.push(op);
151                    }
152                    if let Some(else_expr) = else_expr {
153                        traverse_stack.push(else_expr);
154                    }
155                    for (when_expr, then_expr) in branches {
156                        traverse_stack.push(then_expr);
157                        traverse_stack.push(when_expr);
158                    }
159                }
160                Coalesce(items) => {
161                    for item in items {
162                        traverse_stack.push(item);
163                    }
164                }
165                Random => {}
166                ScalarSubquery(_) => {}
167            }
168        }
169
170        let mut result_stack: Vec<String> = Vec::new();
171        for node in postorder.into_iter().rev() {
172            match node {
173                Column(fid) => result_stack.push(format!("col#{}", fid)),
174                Literal(lit) => result_stack.push(lit.format_display()),
175                Aggregate(_agg) => result_stack.push("AGG".to_string()),
176                GetField { field_name, .. } => {
177                    let base = result_stack.pop().unwrap_or_default();
178                    result_stack.push(format!("{base}.{field_name}"));
179                }
180                Cast { data_type, .. } => {
181                    let value = result_stack.pop().unwrap_or_default();
182                    result_stack.push(format!("CAST({value} AS {data_type:?})"));
183                }
184                Binary { op, .. } => {
185                    let right = result_stack.pop().unwrap_or_default();
186                    let left = result_stack.pop().unwrap_or_default();
187                    result_stack.push(format!("({} {} {})", left, op.as_str(), right));
188                }
189                Compare { op, .. } => {
190                    let right = result_stack.pop().unwrap_or_default();
191                    let left = result_stack.pop().unwrap_or_default();
192                    result_stack.push(format!("({} {} {})", left, op.as_str(), right));
193                }
194                Not(_) => {
195                    let operand = result_stack.pop().unwrap_or_default();
196                    result_stack.push(format!("(NOT {})", operand));
197                }
198                IsNull { negated, .. } => {
199                    let operand = result_stack.pop().unwrap_or_default();
200                    if *negated {
201                        result_stack.push(format!("({operand} IS NOT NULL)"));
202                    } else {
203                        result_stack.push(format!("({operand} IS NULL)"));
204                    }
205                }
206                Case {
207                    branches,
208                    else_expr,
209                    ..
210                } => {
211                    let mut parts = Vec::with_capacity(branches.len() + 2);
212                    for _ in 0..branches.len() {
213                        let then_str = result_stack.pop().unwrap_or_default();
214                        let when_str = result_stack.pop().unwrap_or_default();
215                        parts.push(format!("WHEN {when_str} THEN {then_str}"));
216                    }
217                    if let Some(_else_expr) = else_expr {
218                        parts.push(format!("ELSE {}", result_stack.pop().unwrap_or_default()));
219                    }
220
221                    parts.push("END".to_string());
222                    let mut output = parts.join(" ");
223                    output.insert_str(0, "CASE ");
224                    result_stack.push(output);
225                }
226                Coalesce(items) => {
227                    let mut args = Vec::with_capacity(items.len());
228                    for _ in 0..items.len() {
229                        args.push(result_stack.pop().unwrap_or_default());
230                    }
231                    args.reverse();
232                    result_stack.push(format!("COALESCE({})", args.join(", ")));
233                }
234                Random => {
235                    result_stack.push("RANDOM()".to_string());
236                }
237                ScalarSubquery(sub) => {
238                    result_stack.push(format!("(SCALAR_SUBQUERY#{})", sub.id.0));
239                }
240            }
241        }
242
243        result_stack.pop().unwrap_or_default()
244    }
245}
246
247fn format_filter<F: Display>(filter: &Filter<'_, F>) -> String {
248    format!("field#{} {}", filter.field_id, format_operator(&filter.op))
249}
250
251fn format_operator(op: &Operator<'_>) -> String {
252    match op {
253        Operator::Equals(lit) => format!("= {}", lit.format_display()),
254        Operator::Range { lower, upper } => format!(
255            "IN {} .. {}",
256            format_range_bound_lower(lower),
257            format_range_bound_upper(upper)
258        ),
259        Operator::GreaterThan(lit) => format!("> {}", lit.format_display()),
260        Operator::GreaterThanOrEquals(lit) => format!(">= {}", lit.format_display()),
261        Operator::LessThan(lit) => format!("< {}", lit.format_display()),
262        Operator::LessThanOrEquals(lit) => format!("<= {}", lit.format_display()),
263        Operator::In(values) => {
264            let rendered: Vec<String> = values.iter().map(|lit| lit.format_display()).collect();
265            format!("IN {{{}}}", rendered.join(", "))
266        }
267        Operator::StartsWith {
268            pattern,
269            case_sensitive,
270        } => format_pattern_op("STARTS WITH", pattern, *case_sensitive),
271        Operator::EndsWith {
272            pattern,
273            case_sensitive,
274        } => format_pattern_op("ENDS WITH", pattern, *case_sensitive),
275        Operator::Contains {
276            pattern,
277            case_sensitive,
278        } => format_pattern_op("CONTAINS", pattern, *case_sensitive),
279        Operator::IsNull => "IS NULL".to_string(),
280        Operator::IsNotNull => "IS NOT NULL".to_string(),
281    }
282}
283
284fn format_pattern_op(op_name: &str, pattern: &str, case_sensitive: bool) -> String {
285    let mut rendered = format!("{} \"{}\"", op_name, escape_string(pattern));
286    if !case_sensitive {
287        rendered.push_str(" (case-insensitive)");
288    }
289    rendered
290}
291
292fn format_range_bound_lower(bound: &Bound<Literal>) -> String {
293    match bound {
294        Bound::Unbounded => "-inf".to_string(),
295        Bound::Included(lit) => format!("[{}", lit.format_display()),
296        Bound::Excluded(lit) => format!("({}", lit.format_display()),
297    }
298}
299
300fn format_range_bound_upper(bound: &Bound<Literal>) -> String {
301    match bound {
302        Bound::Unbounded => "+inf".to_string(),
303        Bound::Included(lit) => format!("{}]", lit.format_display()),
304        Bound::Excluded(lit) => format!("{})", lit.format_display()),
305    }
306}
307
308fn escape_string(value: &str) -> String {
309    value.chars().flat_map(|c| c.escape_default()).collect()
310}