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