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 test_display_tree_root() {
160 use insta::assert_snapshot;
161 let root_expr = root();
162 assert_snapshot!(root_expr.display_tree().to_string(), @"vortex.root()");
163 }
164
165 #[test]
166 fn test_display_tree_literal() {
167 use insta::assert_snapshot;
168 let lit_expr = lit(42);
169 assert_snapshot!(lit_expr.display_tree().to_string(), @"vortex.literal(42i32)");
170 }
171
172 #[test]
173 fn test_display_tree_get_item() {
174 use insta::assert_snapshot;
175 let get_item_expr = get_item("my_field", root());
176 assert_snapshot!(get_item_expr.display_tree().to_string(), @r"
177 vortex.get_item(my_field)
178 └── input: vortex.root()
179 ");
180 }
181
182 #[test]
183 fn test_display_tree_binary() {
184 use insta::assert_snapshot;
185 let binary_expr = gt(get_item("x", root()), lit(10));
186 assert_snapshot!(binary_expr.display_tree().to_string(), @r"
187 vortex.binary(>)
188 ├── lhs: vortex.get_item(x)
189 │ └── input: vortex.root()
190 └── rhs: vortex.literal(10i32)
191 ");
192 }
193
194 #[test]
195 fn test_display_tree_complex_binary() {
196 use insta::assert_snapshot;
197 let complex_binary = and(
198 eq(get_item("name", root()), lit("alice")),
199 gt(get_item("age", root()), lit(18)),
200 );
201 assert_snapshot!(complex_binary.display_tree().to_string(), @r#"
202 vortex.binary(and)
203 ├── lhs: vortex.binary(=)
204 │ ├── lhs: vortex.get_item(name)
205 │ │ └── input: vortex.root()
206 │ └── rhs: vortex.literal("alice")
207 └── rhs: vortex.binary(>)
208 ├── lhs: vortex.get_item(age)
209 │ └── input: vortex.root()
210 └── rhs: vortex.literal(18i32)
211 "#);
212 }
213
214 #[test]
215 fn test_display_tree_select() {
216 use insta::assert_snapshot;
217 let select_expr = select(["name", "age"], root());
218 assert_snapshot!(select_expr.display_tree().to_string(), @r"
219 vortex.select({name, age})
220 └── child: vortex.root()
221 ");
222 }
223
224 #[test]
225 fn test_display_tree_select_exclude() {
226 use insta::assert_snapshot;
227 let select_exclude_expr = select_exclude(["internal_id", "metadata"], root());
228 assert_snapshot!(select_exclude_expr.display_tree().to_string(), @r"
229 vortex.select(~{internal_id, metadata})
230 └── child: vortex.root()
231 ");
232 }
233
234 #[test]
235 fn test_display_tree_cast() {
236 use insta::assert_snapshot;
237 let cast_expr = cast(
238 get_item("value", root()),
239 DType::Primitive(PType::I64, Nullability::NonNullable),
240 );
241 assert_snapshot!(cast_expr.display_tree().to_string(), @r"
242 vortex.cast(i64)
243 └── input: vortex.get_item(value)
244 └── input: vortex.root()
245 ");
246 }
247
248 #[test]
249 fn test_display_tree_not() {
250 use insta::assert_snapshot;
251 let not_expr = not(eq(get_item("active", root()), lit(true)));
252 assert_snapshot!(not_expr.display_tree().to_string(), @r"
253 vortex.not()
254 └── input: vortex.binary(=)
255 ├── lhs: vortex.get_item(active)
256 │ └── input: vortex.root()
257 └── rhs: vortex.literal(true)
258 ");
259 }
260
261 #[test]
262 fn test_display_tree_between() {
263 use insta::assert_snapshot;
264 let between_expr = between(
265 get_item("score", root()),
266 lit(0),
267 lit(100),
268 BetweenOptions {
269 lower_strict: StrictComparison::NonStrict,
270 upper_strict: StrictComparison::NonStrict,
271 },
272 );
273 assert_snapshot!(between_expr.display_tree().to_string(), @r"
274 vortex.between(lower_strict: <=, upper_strict: <=)
275 ├── array: vortex.get_item(score)
276 │ └── input: vortex.root()
277 ├── lower: vortex.literal(0i32)
278 └── upper: vortex.literal(100i32)
279 ");
280 }
281
282 #[test]
283 fn test_display_tree_nested() {
284 use insta::assert_snapshot;
285 let nested_expr = select(
286 ["result"],
287 cast(
288 between(
289 get_item("score", root()),
290 lit(50),
291 lit(100),
292 BetweenOptions {
293 lower_strict: StrictComparison::Strict,
294 upper_strict: StrictComparison::NonStrict,
295 },
296 ),
297 DType::Bool(Nullability::NonNullable),
298 ),
299 );
300 assert_snapshot!(nested_expr.display_tree().to_string(), @r"
301 vortex.select({result})
302 └── child: vortex.cast(bool)
303 └── input: vortex.between(lower_strict: <, upper_strict: <=)
304 ├── array: vortex.get_item(score)
305 │ └── input: vortex.root()
306 ├── lower: vortex.literal(50i32)
307 └── upper: vortex.literal(100i32)
308 ");
309 }
310
311 #[test]
312 fn test_display_tree_pack() {
313 use insta::assert_snapshot;
314 let select_from_pack_expr = select(
315 ["fizz", "buzz"],
316 pack(
317 [
318 ("fizz", root()),
319 ("bar", lit(5)),
320 ("buzz", eq(lit(42), get_item("answer", root()))),
321 ],
322 Nullability::Nullable,
323 ),
324 );
325 assert_snapshot!(select_from_pack_expr.display_tree().to_string(), @r"
326 vortex.select({fizz, buzz})
327 └── child: vortex.pack(names: [fizz, bar, buzz], nullability: Nullable)
328 ├── fizz: vortex.root()
329 ├── bar: vortex.literal(5i32)
330 └── buzz: vortex.binary(=)
331 ├── lhs: vortex.literal(42i32)
332 └── rhs: vortex.get_item(answer)
333 └── input: vortex.root()
334 ");
335 }
336}