vortex_array/expr/expression.rs
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt;
5use std::fmt::Debug;
6use std::fmt::Display;
7use std::fmt::Formatter;
8use std::hash::Hash;
9use std::ops::Deref;
10use std::sync::Arc;
11
12use itertools::Itertools;
13use vortex_error::VortexResult;
14use vortex_error::vortex_ensure;
15
16use crate::dtype::DType;
17use crate::expr::display::DisplayTreeExpr;
18use crate::expr::traversal::TraversalOrder;
19use crate::expr::traversal::pre_order_visit_down;
20use crate::scalar_fn::ScalarFnRef;
21use crate::scalar_fn::ScalarFnVTable;
22use crate::scalar_fn::fns::root::Root;
23
24/// A node in a Vortex expression tree.
25///
26/// Expressions represent scalar computations that can be performed on data. Each
27/// expression consists of an encoding (vtable), heap-allocated metadata, and child expressions.
28#[derive(Clone, Debug, PartialEq, Eq, Hash)]
29pub struct Expression {
30 /// The scalar fn for this node.
31 scalar_fn: ScalarFnRef,
32 /// Any children of this expression.
33 children: Arc<Vec<Expression>>,
34}
35
36impl Deref for Expression {
37 type Target = ScalarFnRef;
38
39 fn deref(&self) -> &Self::Target {
40 &self.scalar_fn
41 }
42}
43
44impl Expression {
45 /// Create a new expression node from a scalar_fn expression and its children.
46 pub fn try_new(
47 scalar_fn: ScalarFnRef,
48 children: impl IntoIterator<Item = Expression>,
49 ) -> VortexResult<Self> {
50 let children = Vec::from_iter(children);
51
52 vortex_ensure!(
53 scalar_fn.signature().arity().matches(children.len()),
54 "Expression arity mismatch: expected {} children but got {}",
55 scalar_fn.signature().arity(),
56 children.len()
57 );
58
59 Ok(Self {
60 scalar_fn,
61 children: children.into(),
62 })
63 }
64
65 /// Returns the scalar fn vtable for this expression.
66 pub fn scalar_fn(&self) -> &ScalarFnRef {
67 &self.scalar_fn
68 }
69
70 /// Returns the children of this expression.
71 pub fn children(&self) -> &Arc<Vec<Expression>> {
72 &self.children
73 }
74
75 /// Returns the n'th child of this expression.
76 pub fn child(&self, n: usize) -> &Expression {
77 &self.children[n]
78 }
79
80 /// Replace the children of this expression with the provided new children.
81 pub fn with_children(
82 mut self,
83 children: impl IntoIterator<Item = Expression>,
84 ) -> VortexResult<Self> {
85 let children = Vec::from_iter(children);
86 vortex_ensure!(
87 self.signature().arity().matches(children.len()),
88 "Expression arity mismatch: expected {} children but got {}",
89 self.signature().arity(),
90 children.len()
91 );
92 self.children = Arc::new(children);
93 Ok(self)
94 }
95
96 /// Computes the return dtype of this expression given the input dtype.
97 pub fn return_dtype(&self, scope: &DType) -> VortexResult<DType> {
98 if self.is::<Root>() {
99 return Ok(scope.clone());
100 }
101
102 let dtypes: Vec<_> = self
103 .children
104 .iter()
105 .map(|c| c.return_dtype(scope))
106 .try_collect()?;
107 self.scalar_fn.return_dtype(&dtypes)
108 }
109
110 /// Returns a new expression representing the validity mask output of this expression.
111 ///
112 /// The returned expression evaluates to a non-nullable boolean array.
113 pub fn validity(&self) -> VortexResult<Expression> {
114 self.scalar_fn.validity(self)
115 }
116
117 /// Format the expression as a compact string.
118 ///
119 /// Since this is a recursive formatter, it is exposed on the public Expression type.
120 /// See fmt_data that is only implemented on the vtable trait.
121 pub fn fmt_sql(&self, f: &mut Formatter<'_>) -> fmt::Result {
122 self.scalar_fn.fmt_sql(self, f)
123 }
124
125 /// Display the expression as a formatted tree structure.
126 ///
127 /// This provides a hierarchical view of the expression that shows the relationships
128 /// between parent and child expressions, making complex nested expressions easier
129 /// to understand and debug.
130 ///
131 /// # Example
132 ///
133 /// ```rust
134 /// # use vortex_array::dtype::{DType, Nullability, PType};
135 /// # use vortex_array::scalar_fn::fns::like::{Like, LikeOptions};
136 /// # use vortex_array::scalar_fn::ScalarFnVTableExt;
137 /// # use vortex_array::expr::{and, cast, eq, get_item, gt, lit, not, root, select};
138 /// // Build a complex nested expression
139 /// let complex_expr = select(
140 /// ["result"],
141 /// and(
142 /// not(eq(get_item("status", root()), lit("inactive"))),
143 /// and(
144 /// Like.new_expr(LikeOptions::default(), [get_item("name", root()), lit("%admin%")]),
145 /// gt(
146 /// cast(get_item("score", root()), DType::Primitive(PType::F64, Nullability::NonNullable)),
147 /// lit(75.0)
148 /// )
149 /// )
150 /// )
151 /// );
152 ///
153 /// println!("{}", complex_expr.display_tree());
154 /// ```
155 ///
156 /// This produces output like:
157 ///
158 /// ```text
159 /// Select(include): {result}
160 /// └── Binary(and)
161 /// ├── lhs: Not
162 /// │ └── Binary(=)
163 /// │ ├── lhs: GetItem(status)
164 /// │ │ └── Root
165 /// │ └── rhs: Literal(value: "inactive", dtype: utf8)
166 /// └── rhs: Binary(and)
167 /// ├── lhs: Like
168 /// │ ├── child: GetItem(name)
169 /// │ │ └── Root
170 /// │ └── pattern: Literal(value: "%admin%", dtype: utf8)
171 /// └── rhs: Binary(>)
172 /// ├── lhs: Cast(target: f64)
173 /// │ └── GetItem(score)
174 /// │ └── Root
175 /// └── rhs: Literal(value: 75f64, dtype: f64)
176 /// ```
177 pub fn display_tree(&self) -> impl Display {
178 DisplayTreeExpr(self)
179 }
180
181 /// Returns true if this expression contains expression E inside.
182 ///
183 /// # Example
184 ///
185 /// ```rust
186 /// # use vortex_array::scalar_fn::fns::literal::Literal;
187 /// # use vortex_array::expr::{eq, lit, root};
188 /// let expression = &eq(root(), lit(3u64));
189 /// assert!(expression.contains::<Literal>().unwrap());
190 /// let expression = root();
191 /// assert!(!expression.contains::<Literal>().unwrap());
192 /// ```
193 pub fn contains<E: ScalarFnVTable>(&self) -> VortexResult<bool> {
194 let mut contains = false;
195 pre_order_visit_down(self, |node| {
196 if node.is::<E>() {
197 contains = true;
198 return Ok(TraversalOrder::Stop);
199 }
200 Ok(TraversalOrder::Continue)
201 })?;
202 Ok(contains)
203 }
204}
205
206/// The default display implementation for expressions uses the 'SQL'-style format.
207impl Display for Expression {
208 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
209 self.fmt_sql(f)
210 }
211}
212
213/// Iterative drop for expression to avoid stack overflows.
214impl Drop for Expression {
215 fn drop(&mut self) {
216 if let Some(children) = Arc::get_mut(&mut self.children) {
217 let mut children_to_drop = std::mem::take(children);
218
219 while let Some(mut child) = children_to_drop.pop() {
220 if let Some(expr_children) = Arc::get_mut(&mut child.children) {
221 children_to_drop.append(expr_children);
222 }
223 }
224 }
225 }
226}