1use 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 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 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}