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