1use std::fmt;
5use std::fmt::Display;
6use std::fmt::Formatter;
7
8use crate::expr::BoundExpression;
9use crate::expr::BoundKind;
10use crate::expr::Expression;
11use crate::expr::root;
12use crate::scalar_fn::ChildName;
13
14pub enum DisplayFormat {
15 Compact,
16 Tree,
17}
18
19pub trait ExprDisplay: Display {
24 fn display_child(&self, index: usize) -> &dyn ExprDisplay;
26
27 fn display_children_count(&self) -> usize;
29}
30
31impl ExprDisplay for Expression {
32 fn display_child(&self, index: usize) -> &dyn ExprDisplay {
33 Expression::child(self, index)
34 }
35
36 fn display_children_count(&self) -> usize {
37 self.children().len()
38 }
39}
40
41impl ExprDisplay for BoundExpression {
42 fn display_child(&self, index: usize) -> &dyn ExprDisplay {
43 &self.children()[index]
44 }
45
46 fn display_children_count(&self) -> usize {
47 self.children().len()
48 }
49}
50
51trait DisplayTreeNode: Sized {
52 fn tree_children(&self) -> &[Self];
53
54 fn tree_child_name(&self, index: usize) -> ChildName;
55
56 fn fmt_tree_node(&self, f: &mut Formatter<'_>) -> fmt::Result;
57}
58
59impl DisplayTreeNode for Expression {
60 fn tree_children(&self) -> &[Self] {
61 Expression::children(self).as_slice()
62 }
63
64 fn tree_child_name(&self, index: usize) -> ChildName {
65 self.scalar_fn().signature().child_name(index)
66 }
67
68 fn fmt_tree_node(&self, f: &mut Formatter<'_>) -> fmt::Result {
69 Display::fmt(self.scalar_fn(), f)
70 }
71}
72
73impl DisplayTreeNode for BoundExpression {
74 fn tree_children(&self) -> &[Self] {
75 BoundExpression::children(self)
76 }
77
78 fn tree_child_name(&self, index: usize) -> ChildName {
79 match self.kind() {
80 BoundKind::Scalar { scalar_fn, .. } => scalar_fn.signature().child_name(index),
81 BoundKind::Root => unreachable!("the scope root has no children"),
82 }
83 }
84
85 fn fmt_tree_node(&self, f: &mut Formatter<'_>) -> fmt::Result {
86 match self.kind() {
87 BoundKind::Scalar { scalar_fn, .. } => Display::fmt(scalar_fn, f),
88 BoundKind::Root => Display::fmt(root().scalar_fn(), f),
89 }
90 }
91}
92
93struct NodeDisplay<'a, T>(&'a T);
94
95impl<T: DisplayTreeNode> Display for NodeDisplay<'_, T> {
96 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
97 self.0.fmt_tree_node(f)
98 }
99}
100
101pub struct DisplayTreeExpr<'a, T: ?Sized = Expression>(pub &'a T);
102
103impl<T: DisplayTreeNode> Display for DisplayTreeExpr<'_, T> {
104 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
105 pub use termtree::Tree;
106 fn make_tree<T: DisplayTreeNode>(expr: &T) -> Tree<String> {
107 let child_trees = expr
108 .tree_children()
109 .iter()
110 .enumerate()
111 .map(|(index, child)| {
112 let child_tree = make_tree(child);
113 Tree::new(format!(
114 "{}: {}",
115 expr.tree_child_name(index),
116 child_tree.root
117 ))
118 .with_leaves(child_tree.leaves)
119 })
120 .collect::<Vec<_>>();
121
122 Tree::new(NodeDisplay(expr).to_string()).with_leaves(child_trees)
123 }
124
125 write!(f, "{}", make_tree(self.0))
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use crate::dtype::DType;
132 use crate::dtype::Nullability;
133 use crate::dtype::PType;
134 use crate::expr::and;
135 use crate::expr::between;
136 use crate::expr::cast;
137 use crate::expr::eq;
138 use crate::expr::get_item;
139 use crate::expr::gt;
140 use crate::expr::lit;
141 use crate::expr::not;
142 use crate::expr::pack;
143 use crate::expr::root;
144 use crate::expr::select;
145 use crate::expr::select_exclude;
146 use crate::scalar_fn::fns::between::BetweenOptions;
147 use crate::scalar_fn::fns::between::StrictComparison;
148
149 #[test]
150 fn tree_display_getitem() {
151 let expr = get_item("x", root());
152 println!("{}", expr.display_tree());
153 }
154
155 #[test]
156 fn tree_display_binary() {
157 let expr = gt(get_item("x", root()), lit(5));
158 println!("{}", expr.display_tree());
159 }
160
161 #[test]
162 fn test_child_names_debug() {
163 let binary_expr = gt(get_item("x", root()), lit(10));
165 println!("Binary expr tree:\n{}", binary_expr.display_tree());
166
167 let between_expr = between(
168 get_item("score", root()),
169 lit(0),
170 lit(100),
171 BetweenOptions {
172 lower_strict: StrictComparison::NonStrict,
173 upper_strict: StrictComparison::NonStrict,
174 },
175 );
176 println!("Between expr tree:\n{}", between_expr.display_tree());
177 }
178
179 #[test]
180 fn test_display_tree_root() {
181 use insta::assert_snapshot;
182 let root_expr = root();
183 assert_snapshot!(root_expr.display_tree().to_string(), @"vortex.root()");
184 }
185
186 #[test]
187 fn test_display_tree_literal() {
188 use insta::assert_snapshot;
189 let lit_expr = lit(42);
190 assert_snapshot!(lit_expr.display_tree().to_string(), @"vortex.literal(42i32)");
191 }
192
193 #[test]
194 fn test_display_tree_get_item() {
195 use insta::assert_snapshot;
196 let get_item_expr = get_item("my_field", root());
197 assert_snapshot!(get_item_expr.display_tree().to_string(), @r"
198 vortex.get_item(my_field)
199 └── input: vortex.root()
200 ");
201 }
202
203 #[test]
204 fn test_display_tree_binary() {
205 use insta::assert_snapshot;
206 let binary_expr = gt(get_item("x", root()), lit(10));
207 assert_snapshot!(binary_expr.display_tree().to_string(), @r"
208 vortex.binary(>)
209 ├── lhs: vortex.get_item(x)
210 │ └── input: vortex.root()
211 └── rhs: vortex.literal(10i32)
212 ");
213 }
214
215 #[test]
216 fn test_display_tree_complex_binary() {
217 use insta::assert_snapshot;
218 let complex_binary = and(
219 eq(get_item("name", root()), lit("alice")),
220 gt(get_item("age", root()), lit(18)),
221 );
222 assert_snapshot!(complex_binary.display_tree().to_string(), @r#"
223 vortex.binary(and)
224 ├── lhs: vortex.binary(=)
225 │ ├── lhs: vortex.get_item(name)
226 │ │ └── input: vortex.root()
227 │ └── rhs: vortex.literal("alice")
228 └── rhs: vortex.binary(>)
229 ├── lhs: vortex.get_item(age)
230 │ └── input: vortex.root()
231 └── rhs: vortex.literal(18i32)
232 "#);
233 }
234
235 #[test]
236 fn test_display_tree_select() {
237 use insta::assert_snapshot;
238 let select_expr = select(["name", "age"], root());
239 assert_snapshot!(select_expr.display_tree().to_string(), @r"
240 vortex.select({name, age})
241 └── child: vortex.root()
242 ");
243 }
244
245 #[test]
246 fn test_display_tree_select_exclude() {
247 use insta::assert_snapshot;
248 let select_exclude_expr = select_exclude(["internal_id", "metadata"], root());
249 assert_snapshot!(select_exclude_expr.display_tree().to_string(), @r"
250 vortex.select(~{internal_id, metadata})
251 └── child: vortex.root()
252 ");
253 }
254
255 #[test]
256 fn test_display_tree_cast() {
257 use insta::assert_snapshot;
258 let cast_expr = cast(
259 get_item("value", root()),
260 DType::Primitive(PType::I64, Nullability::NonNullable),
261 );
262 assert_snapshot!(cast_expr.display_tree().to_string(), @r"
263 vortex.cast(i64)
264 └── input: vortex.get_item(value)
265 └── input: vortex.root()
266 ");
267 }
268
269 #[test]
270 fn test_display_tree_not() {
271 use insta::assert_snapshot;
272 let not_expr = not(eq(get_item("active", root()), lit(true)));
273 assert_snapshot!(not_expr.display_tree().to_string(), @r"
274 vortex.not()
275 └── input: vortex.binary(=)
276 ├── lhs: vortex.get_item(active)
277 │ └── input: vortex.root()
278 └── rhs: vortex.literal(true)
279 ");
280 }
281
282 #[test]
283 fn test_display_tree_between() {
284 use insta::assert_snapshot;
285 let between_expr = between(
286 get_item("score", root()),
287 lit(0),
288 lit(100),
289 BetweenOptions {
290 lower_strict: StrictComparison::NonStrict,
291 upper_strict: StrictComparison::NonStrict,
292 },
293 );
294 assert_snapshot!(between_expr.display_tree().to_string(), @r"
295 vortex.between(lower_strict: <=, upper_strict: <=)
296 ├── array: vortex.get_item(score)
297 │ └── input: vortex.root()
298 ├── lower: vortex.literal(0i32)
299 └── upper: vortex.literal(100i32)
300 ");
301 }
302
303 #[test]
304 fn test_display_tree_nested() {
305 use insta::assert_snapshot;
306 let nested_expr = select(
307 ["result"],
308 cast(
309 between(
310 get_item("score", root()),
311 lit(50),
312 lit(100),
313 BetweenOptions {
314 lower_strict: StrictComparison::Strict,
315 upper_strict: StrictComparison::NonStrict,
316 },
317 ),
318 DType::Bool(Nullability::NonNullable),
319 ),
320 );
321 assert_snapshot!(nested_expr.display_tree().to_string(), @r"
322 vortex.select({result})
323 └── child: vortex.cast(bool)
324 └── input: vortex.between(lower_strict: <, upper_strict: <=)
325 ├── array: vortex.get_item(score)
326 │ └── input: vortex.root()
327 ├── lower: vortex.literal(50i32)
328 └── upper: vortex.literal(100i32)
329 ");
330 }
331
332 #[test]
333 fn test_display_tree_pack() {
334 use insta::assert_snapshot;
335 let select_from_pack_expr = select(
336 ["fizz", "buzz"],
337 pack(
338 [
339 ("fizz", root()),
340 ("bar", lit(5)),
341 ("buzz", eq(lit(42), get_item("answer", root()))),
342 ],
343 Nullability::Nullable,
344 ),
345 );
346 assert_snapshot!(select_from_pack_expr.display_tree().to_string(), @r"
347 vortex.select({fizz, buzz})
348 └── child: vortex.pack(names: [fizz, bar, buzz], nullability: Nullable)
349 ├── fizz: vortex.root()
350 ├── bar: vortex.literal(5i32)
351 └── buzz: vortex.binary(=)
352 ├── lhs: vortex.literal(42i32)
353 └── rhs: vortex.get_item(answer)
354 └── input: vortex.root()
355 ");
356 }
357}