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::Display;
5use std::fmt::Formatter;
6use std::ops::Deref;
7
8use crate::expr::Expression;
9use crate::scalar_fn::ScalarFnRef;
10
11pub enum DisplayFormat {
12    Compact,
13    Tree,
14}
15
16pub struct DisplayTreeExpr<'a>(pub &'a Expression);
17
18impl Display for DisplayTreeExpr<'_> {
19    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
20        pub use termtree::Tree;
21        fn make_tree(expr: &Expression) -> Result<Tree<String>, std::fmt::Error> {
22            let scalar_fn: &ScalarFnRef = expr.deref();
23            let node_name = format!("{}", scalar_fn);
24
25            // Get child names for display purposes
26            let child_names = (0..expr.children().len()).map(|i| expr.signature().child_name(i));
27            let children = expr.children();
28
29            let child_trees: Result<Vec<Tree<String>>, std::fmt::Error> = children
30                .iter()
31                .zip(child_names)
32                .map(|(child, name)| {
33                    let child_tree = make_tree(child)?;
34                    Ok::<Tree<String>, std::fmt::Error>(
35                        Tree::new(format!("{}: {}", name, child_tree.root))
36                            .with_leaves(child_tree.leaves),
37                    )
38                })
39                .collect();
40
41            Ok(Tree::new(node_name).with_leaves(child_trees?))
42        }
43
44        write!(f, "{}", make_tree(self.0)?)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use crate::dtype::DType;
51    use crate::dtype::Nullability;
52    use crate::dtype::PType;
53    use crate::expr::and;
54    use crate::expr::between;
55    use crate::expr::cast;
56    use crate::expr::eq;
57    use crate::expr::get_item;
58    use crate::expr::gt;
59    use crate::expr::lit;
60    use crate::expr::not;
61    use crate::expr::pack;
62    use crate::expr::root;
63    use crate::expr::select;
64    use crate::expr::select_exclude;
65    use crate::scalar_fn::fns::between::BetweenOptions;
66    use crate::scalar_fn::fns::between::StrictComparison;
67
68    #[test]
69    fn tree_display_getitem() {
70        let expr = get_item("x", root());
71        println!("{}", expr.display_tree());
72    }
73
74    #[test]
75    fn tree_display_binary() {
76        let expr = gt(get_item("x", root()), lit(5));
77        println!("{}", expr.display_tree());
78    }
79
80    #[test]
81    fn test_child_names_debug() {
82        // Simple test to debug child names display
83        let binary_expr = gt(get_item("x", root()), lit(10));
84        println!("Binary expr tree:\n{}", binary_expr.display_tree());
85
86        let between_expr = between(
87            get_item("score", root()),
88            lit(0),
89            lit(100),
90            BetweenOptions {
91                lower_strict: StrictComparison::NonStrict,
92                upper_strict: StrictComparison::NonStrict,
93            },
94        );
95        println!("Between expr tree:\n{}", between_expr.display_tree());
96    }
97
98    #[test]
99    fn test_display_tree_root() {
100        use insta::assert_snapshot;
101        let root_expr = root();
102        assert_snapshot!(root_expr.display_tree().to_string(), @"vortex.root()");
103    }
104
105    #[test]
106    fn test_display_tree_literal() {
107        use insta::assert_snapshot;
108        let lit_expr = lit(42);
109        assert_snapshot!(lit_expr.display_tree().to_string(), @"vortex.literal(42i32)");
110    }
111
112    #[test]
113    fn test_display_tree_get_item() {
114        use insta::assert_snapshot;
115        let get_item_expr = get_item("my_field", root());
116        assert_snapshot!(get_item_expr.display_tree().to_string(), @r"
117        vortex.get_item(my_field)
118        └── input: vortex.root()
119        ");
120    }
121
122    #[test]
123    fn test_display_tree_binary() {
124        use insta::assert_snapshot;
125        let binary_expr = gt(get_item("x", root()), lit(10));
126        assert_snapshot!(binary_expr.display_tree().to_string(), @r"
127        vortex.binary(>)
128        ├── lhs: vortex.get_item(x)
129        │   └── input: vortex.root()
130        └── rhs: vortex.literal(10i32)
131        ");
132    }
133
134    #[test]
135    fn test_display_tree_complex_binary() {
136        use insta::assert_snapshot;
137        let complex_binary = and(
138            eq(get_item("name", root()), lit("alice")),
139            gt(get_item("age", root()), lit(18)),
140        );
141        assert_snapshot!(complex_binary.display_tree().to_string(), @r#"
142        vortex.binary(and)
143        ├── lhs: vortex.binary(=)
144        │   ├── lhs: vortex.get_item(name)
145        │   │   └── input: vortex.root()
146        │   └── rhs: vortex.literal("alice")
147        └── rhs: vortex.binary(>)
148            ├── lhs: vortex.get_item(age)
149            │   └── input: vortex.root()
150            └── rhs: vortex.literal(18i32)
151        "#);
152    }
153
154    #[test]
155    fn test_display_tree_select() {
156        use insta::assert_snapshot;
157        let select_expr = select(["name", "age"], root());
158        assert_snapshot!(select_expr.display_tree().to_string(), @r"
159        vortex.select({name, age})
160        └── child: vortex.root()
161        ");
162    }
163
164    #[test]
165    fn test_display_tree_select_exclude() {
166        use insta::assert_snapshot;
167        let select_exclude_expr = select_exclude(["internal_id", "metadata"], root());
168        assert_snapshot!(select_exclude_expr.display_tree().to_string(), @r"
169        vortex.select(~{internal_id, metadata})
170        └── child: vortex.root()
171        ");
172    }
173
174    #[test]
175    fn test_display_tree_cast() {
176        use insta::assert_snapshot;
177        let cast_expr = cast(
178            get_item("value", root()),
179            DType::Primitive(PType::I64, Nullability::NonNullable),
180        );
181        assert_snapshot!(cast_expr.display_tree().to_string(), @r"
182        vortex.cast(i64)
183        └── input: vortex.get_item(value)
184            └── input: vortex.root()
185        ");
186    }
187
188    #[test]
189    fn test_display_tree_not() {
190        use insta::assert_snapshot;
191        let not_expr = not(eq(get_item("active", root()), lit(true)));
192        assert_snapshot!(not_expr.display_tree().to_string(), @r"
193        vortex.not()
194        └── input: vortex.binary(=)
195            ├── lhs: vortex.get_item(active)
196            │   └── input: vortex.root()
197            └── rhs: vortex.literal(true)
198        ");
199    }
200
201    #[test]
202    fn test_display_tree_between() {
203        use insta::assert_snapshot;
204        let between_expr = between(
205            get_item("score", root()),
206            lit(0),
207            lit(100),
208            BetweenOptions {
209                lower_strict: StrictComparison::NonStrict,
210                upper_strict: StrictComparison::NonStrict,
211            },
212        );
213        assert_snapshot!(between_expr.display_tree().to_string(), @r"
214        vortex.between(lower_strict: <=, upper_strict: <=)
215        ├── array: vortex.get_item(score)
216        │   └── input: vortex.root()
217        ├── lower: vortex.literal(0i32)
218        └── upper: vortex.literal(100i32)
219        ");
220    }
221
222    #[test]
223    fn test_display_tree_nested() {
224        use insta::assert_snapshot;
225        let nested_expr = select(
226            ["result"],
227            cast(
228                between(
229                    get_item("score", root()),
230                    lit(50),
231                    lit(100),
232                    BetweenOptions {
233                        lower_strict: StrictComparison::Strict,
234                        upper_strict: StrictComparison::NonStrict,
235                    },
236                ),
237                DType::Bool(Nullability::NonNullable),
238            ),
239        );
240        assert_snapshot!(nested_expr.display_tree().to_string(), @r"
241        vortex.select({result})
242        └── child: vortex.cast(bool)
243            └── input: vortex.between(lower_strict: <, upper_strict: <=)
244                ├── array: vortex.get_item(score)
245                │   └── input: vortex.root()
246                ├── lower: vortex.literal(50i32)
247                └── upper: vortex.literal(100i32)
248        ");
249    }
250
251    #[test]
252    fn test_display_tree_pack() {
253        use insta::assert_snapshot;
254        let select_from_pack_expr = select(
255            ["fizz", "buzz"],
256            pack(
257                [
258                    ("fizz", root()),
259                    ("bar", lit(5)),
260                    ("buzz", eq(lit(42), get_item("answer", root()))),
261                ],
262                Nullability::Nullable,
263            ),
264        );
265        assert_snapshot!(select_from_pack_expr.display_tree().to_string(), @r"
266        vortex.select({fizz, buzz})
267        └── child: vortex.pack(names: [fizz, bar, buzz], nullability: Nullable)
268            ├── fizz: vortex.root()
269            ├── bar: vortex.literal(5i32)
270            └── buzz: vortex.binary(=)
271                ├── lhs: vortex.literal(42i32)
272                └── rhs: vortex.get_item(answer)
273                    └── input: vortex.root()
274        ");
275    }
276}