vortex_array/expr/
display.rs

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