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