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::expr::ScalarFn;
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: &ScalarFn = 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 vortex_dtype::DType;
51    use vortex_dtype::Nullability;
52    use vortex_dtype::PType;
53
54    use crate::compute::BetweenOptions;
55    use crate::compute::StrictComparison;
56    use crate::expr::exprs::between::between;
57    use crate::expr::exprs::binary::and;
58    use crate::expr::exprs::binary::eq;
59    use crate::expr::exprs::binary::gt;
60    use crate::expr::exprs::cast::cast;
61    use crate::expr::exprs::get_item::get_item;
62    use crate::expr::exprs::literal::lit;
63    use crate::expr::exprs::not::not;
64    use crate::expr::exprs::pack::pack;
65    use crate::expr::exprs::root::root;
66    use crate::expr::exprs::select::select;
67    use crate::expr::exprs::select::select_exclude;
68
69    #[test]
70    fn tree_display_getitem() {
71        let expr = get_item("x", root());
72        println!("{}", expr.display_tree());
73    }
74
75    #[test]
76    fn tree_display_binary() {
77        let expr = gt(get_item("x", root()), lit(5));
78        println!("{}", expr.display_tree());
79    }
80
81    #[test]
82    fn test_child_names_debug() {
83        // Simple test to debug child names display
84        let binary_expr = gt(get_item("x", root()), lit(10));
85        println!("Binary expr tree:\n{}", binary_expr.display_tree());
86
87        let between_expr = between(
88            get_item("score", root()),
89            lit(0),
90            lit(100),
91            BetweenOptions {
92                lower_strict: StrictComparison::NonStrict,
93                upper_strict: StrictComparison::NonStrict,
94            },
95        );
96        println!("Between expr tree:\n{}", between_expr.display_tree());
97    }
98
99    #[test]
100    fn test_display_tree_root() {
101        use insta::assert_snapshot;
102        let root_expr = root();
103        assert_snapshot!(root_expr.display_tree().to_string(), @"vortex.root()");
104    }
105
106    #[test]
107    fn test_display_tree_literal() {
108        use insta::assert_snapshot;
109        let lit_expr = lit(42);
110        assert_snapshot!(lit_expr.display_tree().to_string(), @"vortex.literal(42i32)");
111    }
112
113    #[test]
114    fn test_display_tree_get_item() {
115        use insta::assert_snapshot;
116        let get_item_expr = get_item("my_field", root());
117        assert_snapshot!(get_item_expr.display_tree().to_string(), @r"
118        vortex.get_item(my_field)
119        └── input: vortex.root()
120        ");
121    }
122
123    #[test]
124    fn test_display_tree_binary() {
125        use insta::assert_snapshot;
126        let binary_expr = gt(get_item("x", root()), lit(10));
127        assert_snapshot!(binary_expr.display_tree().to_string(), @r"
128        vortex.binary(>)
129        ├── lhs: vortex.get_item(x)
130        │   └── input: vortex.root()
131        └── rhs: vortex.literal(10i32)
132        ");
133    }
134
135    #[test]
136    fn test_display_tree_complex_binary() {
137        use insta::assert_snapshot;
138        let complex_binary = and(
139            eq(get_item("name", root()), lit("alice")),
140            gt(get_item("age", root()), lit(18)),
141        );
142        assert_snapshot!(complex_binary.display_tree().to_string(), @r#"
143        vortex.binary(and)
144        ├── lhs: vortex.binary(=)
145        │   ├── lhs: vortex.get_item(name)
146        │   │   └── input: vortex.root()
147        │   └── rhs: vortex.literal("alice")
148        └── rhs: vortex.binary(>)
149            ├── lhs: vortex.get_item(age)
150            │   └── input: vortex.root()
151            └── rhs: vortex.literal(18i32)
152        "#);
153    }
154
155    #[test]
156    fn test_display_tree_select() {
157        use insta::assert_snapshot;
158        let select_expr = select(["name", "age"], root());
159        assert_snapshot!(select_expr.display_tree().to_string(), @r"
160        vortex.select({name, age})
161        └── child: vortex.root()
162        ");
163    }
164
165    #[test]
166    fn test_display_tree_select_exclude() {
167        use insta::assert_snapshot;
168        let select_exclude_expr = select_exclude(["internal_id", "metadata"], root());
169        assert_snapshot!(select_exclude_expr.display_tree().to_string(), @r"
170        vortex.select(~{internal_id, metadata})
171        └── child: vortex.root()
172        ");
173    }
174
175    #[test]
176    fn test_display_tree_cast() {
177        use insta::assert_snapshot;
178        let cast_expr = cast(
179            get_item("value", root()),
180            DType::Primitive(PType::I64, Nullability::NonNullable),
181        );
182        assert_snapshot!(cast_expr.display_tree().to_string(), @r"
183        vortex.cast(i64)
184        └── input: vortex.get_item(value)
185            └── input: vortex.root()
186        ");
187    }
188
189    #[test]
190    fn test_display_tree_not() {
191        use insta::assert_snapshot;
192        let not_expr = not(eq(get_item("active", root()), lit(true)));
193        assert_snapshot!(not_expr.display_tree().to_string(), @r"
194        vortex.not()
195        └── input: vortex.binary(=)
196            ├── lhs: vortex.get_item(active)
197            │   └── input: vortex.root()
198            └── rhs: vortex.literal(true)
199        ");
200    }
201
202    #[test]
203    fn test_display_tree_between() {
204        use insta::assert_snapshot;
205        let between_expr = between(
206            get_item("score", root()),
207            lit(0),
208            lit(100),
209            BetweenOptions {
210                lower_strict: StrictComparison::NonStrict,
211                upper_strict: StrictComparison::NonStrict,
212            },
213        );
214        assert_snapshot!(between_expr.display_tree().to_string(), @r"
215        vortex.between(lower_strict: <=, upper_strict: <=)
216        ├── array: vortex.get_item(score)
217        │   └── input: vortex.root()
218        ├── lower: vortex.literal(0i32)
219        └── upper: vortex.literal(100i32)
220        ");
221    }
222
223    #[test]
224    fn test_display_tree_nested() {
225        use insta::assert_snapshot;
226        let nested_expr = select(
227            ["result"],
228            cast(
229                between(
230                    get_item("score", root()),
231                    lit(50),
232                    lit(100),
233                    BetweenOptions {
234                        lower_strict: StrictComparison::Strict,
235                        upper_strict: StrictComparison::NonStrict,
236                    },
237                ),
238                DType::Bool(Nullability::NonNullable),
239            ),
240        );
241        assert_snapshot!(nested_expr.display_tree().to_string(), @r"
242        vortex.select({result})
243        └── child: vortex.cast(bool)
244            └── input: vortex.between(lower_strict: <, upper_strict: <=)
245                ├── array: vortex.get_item(score)
246                │   └── input: vortex.root()
247                ├── lower: vortex.literal(50i32)
248                └── upper: vortex.literal(100i32)
249        ");
250    }
251
252    #[test]
253    fn test_display_tree_pack() {
254        use insta::assert_snapshot;
255        let select_from_pack_expr = select(
256            ["fizz", "buzz"],
257            pack(
258                [
259                    ("fizz", root()),
260                    ("bar", lit(5)),
261                    ("buzz", eq(lit(42), get_item("answer", root()))),
262                ],
263                Nullability::Nullable,
264            ),
265        );
266        assert_snapshot!(select_from_pack_expr.display_tree().to_string(), @r"
267        vortex.select({fizz, buzz})
268        └── child: vortex.pack(names: [fizz, bar, buzz], nullability: ?)
269            ├── fizz: vortex.root()
270            ├── bar: vortex.literal(5i32)
271            └── buzz: vortex.binary(=)
272                ├── lhs: vortex.literal(42i32)
273                └── rhs: vortex.get_item(answer)
274                    └── input: vortex.root()
275        ");
276    }
277}