Skip to main content

vortex_array/expr/
display.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt;
5use std::fmt::Display;
6use std::fmt::Formatter;
7
8use vortex_utils::tree::TreeDisplayAdapter;
9use vortex_utils::tree::write_branch_tree;
10
11use crate::expr::BoundExpression;
12use crate::expr::Expression;
13use crate::scalar_fn::ChildName;
14
15pub enum DisplayFormat {
16    Compact,
17    Tree,
18}
19
20/// Read-only expression-tree interface used by scalar functions for SQL-style formatting.
21///
22/// Both [`Expression`] and [`BoundExpression`] implement this interface, allowing scalar
23/// functions to format either representation without converting between them.
24pub trait ExprDisplay: Display {
25    /// Return the child at `index`.
26    fn display_child(&self, index: usize) -> &dyn ExprDisplay;
27
28    /// Return the number of children in this node.
29    fn display_children_count(&self) -> usize;
30}
31
32impl ExprDisplay for Expression {
33    fn display_child(&self, index: usize) -> &dyn ExprDisplay {
34        Expression::child(self, index)
35    }
36
37    fn display_children_count(&self) -> usize {
38        self.children().len()
39    }
40}
41
42impl ExprDisplay for BoundExpression {
43    fn display_child(&self, index: usize) -> &dyn ExprDisplay {
44        &self.children()[index]
45    }
46
47    fn display_children_count(&self) -> usize {
48        self.children().len()
49    }
50}
51
52trait DisplayTreeNode: Sized {
53    fn tree_children(&self) -> &[Self];
54
55    fn tree_child_name(&self, index: usize) -> ChildName;
56
57    fn fmt_tree_node(&self, f: &mut Formatter<'_>) -> fmt::Result;
58}
59
60/// Tree-display label for the scope root.
61const ROOT_DISPLAY: &str = "vortex.root()";
62
63impl DisplayTreeNode for Expression {
64    fn tree_children(&self) -> &[Self] {
65        Expression::children(self)
66    }
67
68    fn tree_child_name(&self, index: usize) -> ChildName {
69        match self {
70            Expression::Scalar { scalar_fn, .. } => scalar_fn.signature().child_name(index),
71            Expression::Root => unreachable!("the scope root has no children"),
72        }
73    }
74
75    fn fmt_tree_node(&self, f: &mut Formatter<'_>) -> fmt::Result {
76        match self {
77            Expression::Scalar { scalar_fn, .. } => Display::fmt(scalar_fn, f),
78            Expression::Root => write!(f, "{ROOT_DISPLAY}"),
79        }
80    }
81}
82
83impl DisplayTreeNode for BoundExpression {
84    fn tree_children(&self) -> &[Self] {
85        BoundExpression::children(self)
86    }
87
88    fn tree_child_name(&self, index: usize) -> ChildName {
89        match self {
90            BoundExpression::Scalar { scalar_fn, .. } => scalar_fn.signature().child_name(index),
91            BoundExpression::Root { .. } => unreachable!("the scope root has no children"),
92        }
93    }
94
95    fn fmt_tree_node(&self, f: &mut Formatter<'_>) -> fmt::Result {
96        match self {
97            BoundExpression::Scalar { scalar_fn, .. } => Display::fmt(scalar_fn, f),
98            BoundExpression::Root { .. } => write!(f, "{ROOT_DISPLAY}"),
99        }
100    }
101}
102
103pub struct DisplayTreeExpr<'a, T: ?Sized = Expression>(pub &'a T);
104
105impl<T: DisplayTreeNode> TreeDisplayAdapter for DisplayTreeExpr<'_, T> {
106    type Context = ();
107    type Node = T;
108
109    fn write_node(
110        &self,
111        node: &Self::Node,
112        _context: &Self::Context,
113        formatter: &mut Formatter<'_>,
114    ) -> fmt::Result {
115        node.fmt_tree_node(formatter)
116    }
117
118    fn visit_children(
119        &self,
120        node: &Self::Node,
121        visit: &mut dyn FnMut(&str, &Self::Node, bool) -> fmt::Result,
122    ) -> fmt::Result {
123        let children = node.tree_children();
124        for (index, child) in children.iter().enumerate() {
125            let child_name = node.tree_child_name(index);
126            visit(child_name.as_ref(), child, index + 1 == children.len())?;
127        }
128        Ok(())
129    }
130}
131
132impl<T: DisplayTreeNode> Display for DisplayTreeExpr<'_, T> {
133    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
134        write_branch_tree(self, self.0, &mut (), f)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use crate::dtype::DType;
141    use crate::dtype::Nullability;
142    use crate::dtype::PType;
143    use crate::expr::and;
144    use crate::expr::between;
145    use crate::expr::cast;
146    use crate::expr::eq;
147    use crate::expr::get_item;
148    use crate::expr::gt;
149    use crate::expr::lit;
150    use crate::expr::not;
151    use crate::expr::pack;
152    use crate::expr::root;
153    use crate::expr::select;
154    use crate::expr::select_exclude;
155    use crate::scalar_fn::fns::between::BetweenOptions;
156    use crate::scalar_fn::fns::between::StrictComparison;
157
158    #[test]
159    fn tree_display_getitem() {
160        let expr = get_item("x", root());
161        println!("{}", expr.display_tree());
162    }
163
164    #[test]
165    fn tree_display_binary() {
166        let expr = gt(get_item("x", root()), lit(5));
167        println!("{}", expr.display_tree());
168    }
169
170    #[test]
171    fn test_child_names_debug() {
172        // Simple test to debug child names display
173        let binary_expr = gt(get_item("x", root()), lit(10));
174        println!("Binary expr tree:\n{}", binary_expr.display_tree());
175
176        let between_expr = between(
177            get_item("score", root()),
178            lit(0),
179            lit(100),
180            BetweenOptions {
181                lower_strict: StrictComparison::NonStrict,
182                upper_strict: StrictComparison::NonStrict,
183            },
184        );
185        println!("Between expr tree:\n{}", between_expr.display_tree());
186    }
187
188    #[test]
189    fn test_display_tree_root() {
190        use insta::assert_snapshot;
191        let root_expr = root();
192        assert_snapshot!(root_expr.display_tree().to_string(), @"vortex.root()");
193    }
194
195    #[test]
196    fn test_display_tree_literal() {
197        use insta::assert_snapshot;
198        let lit_expr = lit(42);
199        assert_snapshot!(lit_expr.display_tree().to_string(), @"vortex.literal(42i32)");
200    }
201
202    #[test]
203    fn test_display_tree_get_item() {
204        use insta::assert_snapshot;
205        let get_item_expr = get_item("my_field", root());
206        assert_snapshot!(get_item_expr.display_tree().to_string(), @r"
207        vortex.get_item(my_field)
208        └── input: vortex.root()
209        ");
210    }
211
212    #[test]
213    fn test_display_tree_binary() {
214        use insta::assert_snapshot;
215        let binary_expr = gt(get_item("x", root()), lit(10));
216        assert_snapshot!(binary_expr.display_tree().to_string(), @r"
217        vortex.binary(>)
218        ├── lhs: vortex.get_item(x)
219        │   └── input: vortex.root()
220        └── rhs: vortex.literal(10i32)
221        ");
222    }
223
224    #[test]
225    fn test_display_tree_complex_binary() {
226        use insta::assert_snapshot;
227        let complex_binary = and(
228            eq(get_item("name", root()), lit("alice")),
229            gt(get_item("age", root()), lit(18)),
230        );
231        assert_snapshot!(complex_binary.display_tree().to_string(), @r#"
232        vortex.binary(and)
233        ├── lhs: vortex.binary(=)
234        │   ├── lhs: vortex.get_item(name)
235        │   │   └── input: vortex.root()
236        │   └── rhs: vortex.literal("alice")
237        └── rhs: vortex.binary(>)
238            ├── lhs: vortex.get_item(age)
239            │   └── input: vortex.root()
240            └── rhs: vortex.literal(18i32)
241        "#);
242    }
243
244    #[test]
245    fn test_display_tree_select() {
246        use insta::assert_snapshot;
247        let select_expr = select(["name", "age"], root());
248        assert_snapshot!(select_expr.display_tree().to_string(), @r"
249        vortex.select({name, age})
250        └── child: vortex.root()
251        ");
252    }
253
254    #[test]
255    fn test_display_tree_select_exclude() {
256        use insta::assert_snapshot;
257        let select_exclude_expr = select_exclude(["internal_id", "metadata"], root());
258        assert_snapshot!(select_exclude_expr.display_tree().to_string(), @r"
259        vortex.select(~{internal_id, metadata})
260        └── child: vortex.root()
261        ");
262    }
263
264    #[test]
265    fn test_display_tree_cast() {
266        use insta::assert_snapshot;
267        let cast_expr = cast(
268            get_item("value", root()),
269            DType::Primitive(PType::I64, Nullability::NonNullable),
270        );
271        assert_snapshot!(cast_expr.display_tree().to_string(), @r"
272        vortex.cast(i64)
273        └── input: vortex.get_item(value)
274            └── input: vortex.root()
275        ");
276    }
277
278    #[test]
279    fn test_display_tree_not() {
280        use insta::assert_snapshot;
281        let not_expr = not(eq(get_item("active", root()), lit(true)));
282        assert_snapshot!(not_expr.display_tree().to_string(), @r"
283        vortex.not()
284        └── input: vortex.binary(=)
285            ├── lhs: vortex.get_item(active)
286            │   └── input: vortex.root()
287            └── rhs: vortex.literal(true)
288        ");
289    }
290
291    #[test]
292    fn test_display_tree_between() {
293        use insta::assert_snapshot;
294        let between_expr = between(
295            get_item("score", root()),
296            lit(0),
297            lit(100),
298            BetweenOptions {
299                lower_strict: StrictComparison::NonStrict,
300                upper_strict: StrictComparison::NonStrict,
301            },
302        );
303        assert_snapshot!(between_expr.display_tree().to_string(), @r"
304        vortex.between(lower_strict: <=, upper_strict: <=)
305        ├── array: vortex.get_item(score)
306        │   └── input: vortex.root()
307        ├── lower: vortex.literal(0i32)
308        └── upper: vortex.literal(100i32)
309        ");
310    }
311
312    #[test]
313    fn test_display_tree_nested() {
314        use insta::assert_snapshot;
315        let nested_expr = select(
316            ["result"],
317            cast(
318                between(
319                    get_item("score", root()),
320                    lit(50),
321                    lit(100),
322                    BetweenOptions {
323                        lower_strict: StrictComparison::Strict,
324                        upper_strict: StrictComparison::NonStrict,
325                    },
326                ),
327                DType::Bool(Nullability::NonNullable),
328            ),
329        );
330        assert_snapshot!(nested_expr.display_tree().to_string(), @r"
331        vortex.select({result})
332        └── child: vortex.cast(bool)
333            └── input: vortex.between(lower_strict: <, upper_strict: <=)
334                ├── array: vortex.get_item(score)
335                │   └── input: vortex.root()
336                ├── lower: vortex.literal(50i32)
337                └── upper: vortex.literal(100i32)
338        ");
339    }
340
341    #[test]
342    fn test_display_tree_pack() {
343        use insta::assert_snapshot;
344        let select_from_pack_expr = select(
345            ["fizz", "buzz"],
346            pack(
347                [
348                    ("fizz", root()),
349                    ("bar", lit(5)),
350                    ("buzz", eq(lit(42), get_item("answer", root()))),
351                ],
352                Nullability::Nullable,
353            ),
354        );
355        assert_snapshot!(select_from_pack_expr.display_tree().to_string(), @r"
356        vortex.select({fizz, buzz})
357        └── child: vortex.pack(names: [fizz, bar, buzz], nullability: Nullable)
358            ├── fizz: vortex.root()
359            ├── bar: vortex.literal(5i32)
360            └── buzz: vortex.binary(=)
361                ├── lhs: vortex.literal(42i32)
362                └── rhs: vortex.get_item(answer)
363                    └── input: vortex.root()
364        ");
365    }
366}