vortex_array/expr/expression.rs
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::cell::Cell;
5use std::fmt;
6use std::fmt::Debug;
7use std::fmt::Display;
8use std::fmt::Formatter;
9use std::hash::Hash;
10use std::sync::Arc;
11
12use itertools::Itertools;
13use vortex_error::VortexExpect;
14use vortex_error::VortexResult;
15use vortex_error::vortex_ensure;
16
17use crate::dtype::DType;
18use crate::expr::display::DisplayTreeExpr;
19use crate::expr::traversal::TraversalOrder;
20use crate::expr::traversal::pre_order_visit_down;
21use crate::scalar_fn::ScalarFnRef;
22use crate::scalar_fn::ScalarFnVTable;
23
24/// An empty child slice, returned by [`Expression::children`] for childless variants.
25const NO_CHILDREN: &[Expression] = &[];
26
27/// A node in a Vortex expression tree.
28///
29/// Most nodes are a scalar function applied to child expressions. [`Expression::Root`] is the scope
30/// itself: a language primitive rather than a registered function, because its dtype comes from the
31/// scope rather than from children and it is not executable. A [`ScalarFnVTable`] can answer neither
32/// of those, so `Root` is a variant instead.
33#[derive(Clone, Debug, PartialEq, Eq, Hash)]
34pub enum Expression {
35 /// A scalar function applied to child expressions.
36 Scalar {
37 /// The scalar fn for this node.
38 scalar_fn: ScalarFnRef,
39 /// Any children of this expression.
40 children: Arc<Vec<Expression>>,
41 },
42 /// The full scope of the expression evaluation.
43 Root,
44}
45
46impl Expression {
47 /// Create a new expression node from a scalar_fn expression and its children.
48 pub fn try_new(
49 scalar_fn: ScalarFnRef,
50 children: impl IntoIterator<Item = Expression>,
51 ) -> VortexResult<Self> {
52 let children = Vec::from_iter(children);
53
54 vortex_ensure!(
55 scalar_fn.signature().arity().matches(children.len()),
56 "Expression arity mismatch: expected {} children but got {}",
57 scalar_fn.signature().arity(),
58 children.len()
59 );
60
61 Ok(Self::Scalar {
62 scalar_fn,
63 children: children.into(),
64 })
65 }
66
67 /// Whether this expression is the scope root.
68 pub fn is_root(&self) -> bool {
69 matches!(self, Self::Root)
70 }
71
72 /// Returns the scalar fn for this expression, or `None` if it is not a scalar node.
73 pub fn as_scalar(&self) -> Option<&ScalarFnRef> {
74 match self {
75 Self::Scalar { scalar_fn, .. } => Some(scalar_fn),
76 Self::Root => None,
77 }
78 }
79
80 /// Whether this expression's scalar fn is of the given vtable type.
81 pub fn is<V: ScalarFnVTable>(&self) -> bool {
82 self.as_scalar().is_some_and(|sf| sf.is::<V>())
83 }
84
85 /// The typed options for this expression if its scalar fn matches the given vtable type.
86 pub fn as_opt<V: ScalarFnVTable>(&self) -> Option<&V::Options> {
87 self.as_scalar().and_then(|sf| sf.as_opt::<V>())
88 }
89
90 /// The typed options for this expression.
91 ///
92 /// # Panics
93 ///
94 /// Panics if the vtable type does not match.
95 pub fn as_<V: ScalarFnVTable>(&self) -> &V::Options {
96 self.as_opt::<V>()
97 .vortex_expect("Expression options type mismatch")
98 }
99
100 /// Returns the children of this expression.
101 pub fn children(&self) -> &[Expression] {
102 match self {
103 Self::Scalar { children, .. } => children.as_slice(),
104 Self::Root => NO_CHILDREN,
105 }
106 }
107
108 /// Returns the n'th child of this expression.
109 pub fn child(&self, n: usize) -> &Expression {
110 &self.children()[n]
111 }
112
113 /// Replace the children of this expression with the provided new children.
114 pub fn with_children(
115 self,
116 children: impl IntoIterator<Item = Expression>,
117 ) -> VortexResult<Self> {
118 let children = Vec::from_iter(children);
119 match &self {
120 Self::Root => {
121 vortex_ensure!(
122 children.is_empty(),
123 "Expression arity mismatch: root expects 0 children but got {}",
124 children.len()
125 );
126 Ok(Self::Root)
127 }
128 Self::Scalar { scalar_fn, .. } => {
129 vortex_ensure!(
130 scalar_fn.signature().arity().matches(children.len()),
131 "Expression arity mismatch: expected {} children but got {}",
132 scalar_fn.signature().arity(),
133 children.len()
134 );
135 Ok(Self::Scalar {
136 scalar_fn: scalar_fn.clone(),
137 children: children.into(),
138 })
139 }
140 }
141 }
142
143 /// Computes the return dtype of this expression given the input dtype.
144 pub fn return_dtype(&self, scope: &DType) -> VortexResult<DType> {
145 match self {
146 Self::Root => Ok(scope.clone()),
147 Self::Scalar {
148 scalar_fn,
149 children,
150 } => {
151 let dtypes: Vec<_> = children
152 .iter()
153 .map(|c| c.return_dtype(scope))
154 .try_collect()?;
155 scalar_fn.return_dtype(&dtypes)
156 }
157 }
158 }
159
160 /// Returns a new expression representing the validity mask output of this expression.
161 ///
162 /// The returned expression evaluates to a non-nullable boolean array.
163 pub fn validity(&self) -> VortexResult<Expression> {
164 match self {
165 // The scope is exactly as valid as itself.
166 Self::Root => Ok(Self::Root),
167 Self::Scalar { scalar_fn, .. } => scalar_fn.validity(self),
168 }
169 }
170
171 /// Format the expression as a compact string.
172 ///
173 /// Since this is a recursive formatter, it is exposed on the public Expression type.
174 /// See fmt_data that is only implemented on the vtable trait.
175 pub fn fmt_sql(&self, f: &mut Formatter<'_>) -> fmt::Result {
176 match self {
177 Self::Root => write!(f, "$"),
178 Self::Scalar { scalar_fn, .. } => scalar_fn.fmt_sql(self, f),
179 }
180 }
181
182 /// Display the expression as a formatted tree structure.
183 ///
184 /// This provides a hierarchical view of the expression that shows the relationships
185 /// between parent and child expressions, making complex nested expressions easier
186 /// to understand and debug.
187 ///
188 /// # Example
189 ///
190 /// ```rust
191 /// # use vortex_array::dtype::{DType, Nullability, PType};
192 /// # use vortex_array::scalar_fn::fns::like::{Like, LikeOptions};
193 /// # use vortex_array::scalar_fn::ScalarFnVTableExt;
194 /// # use vortex_array::expr::{and, cast, eq, get_item, gt, lit, not, root, select};
195 /// // Build a complex nested expression
196 /// let complex_expr = select(
197 /// ["result"],
198 /// and(
199 /// not(eq(get_item("status", root()), lit("inactive"))),
200 /// and(
201 /// Like.new_expr(LikeOptions::default(), [get_item("name", root()), lit("%admin%")]),
202 /// gt(
203 /// cast(get_item("score", root()), DType::Primitive(PType::F64, Nullability::NonNullable)),
204 /// lit(75.0)
205 /// )
206 /// )
207 /// )
208 /// );
209 ///
210 /// println!("{}", complex_expr.display_tree());
211 /// ```
212 ///
213 /// This produces output like:
214 ///
215 /// ```text
216 /// Select(include): {result}
217 /// └── Binary(and)
218 /// ├── lhs: Not
219 /// │ └── Binary(=)
220 /// │ ├── lhs: GetItem(status)
221 /// │ │ └── Root
222 /// │ └── rhs: Literal(value: "inactive", dtype: utf8)
223 /// └── rhs: Binary(and)
224 /// ├── lhs: Like
225 /// │ ├── child: GetItem(name)
226 /// │ │ └── Root
227 /// │ └── pattern: Literal(value: "%admin%", dtype: utf8)
228 /// └── rhs: Binary(>)
229 /// ├── lhs: Cast(target: f64)
230 /// │ └── GetItem(score)
231 /// │ └── Root
232 /// └── rhs: Literal(value: 75f64, dtype: f64)
233 /// ```
234 pub fn display_tree(&self) -> impl Display {
235 DisplayTreeExpr(self)
236 }
237
238 /// Returns true if this expression contains expression E inside.
239 ///
240 /// # Example
241 ///
242 /// ```rust
243 /// # use vortex_array::scalar_fn::fns::literal::Literal;
244 /// # use vortex_array::expr::{eq, lit, root};
245 /// let expression = &eq(root(), lit(3u64));
246 /// assert!(expression.contains::<Literal>().unwrap());
247 /// let expression = root();
248 /// assert!(!expression.contains::<Literal>().unwrap());
249 /// ```
250 pub fn contains<E: ScalarFnVTable>(&self) -> VortexResult<bool> {
251 let mut contains = false;
252 pre_order_visit_down(self, |node| {
253 if node.is::<E>() {
254 contains = true;
255 return Ok(TraversalOrder::Stop);
256 }
257 Ok(TraversalOrder::Continue)
258 })?;
259 Ok(contains)
260 }
261}
262
263/// The default display implementation for expressions uses the 'SQL'-style format.
264impl Display for Expression {
265 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
266 self.fmt_sql(f)
267 }
268}
269
270// Switch to iterative cleanup after this many recursive drops.
271const MAX_DROP_DEPTH: u32 = 32;
272
273thread_local! {
274 static DROP_DEPTH: Cell<u32> = const { Cell::new(0) };
275}
276
277// Increments the thread-local drop depth until the guard is dropped.
278struct DropDepthGuard;
279
280impl DropDepthGuard {
281 fn enter() -> Option<Self> {
282 DROP_DEPTH.with(|depth| {
283 let current = depth.get();
284 (current < MAX_DROP_DEPTH).then(|| {
285 depth.set(current + 1);
286 Self
287 })
288 })
289 }
290}
291
292impl Drop for DropDepthGuard {
293 fn drop(&mut self) {
294 DROP_DEPTH.with(|depth| depth.set(depth.get() - 1));
295 }
296}
297
298impl Drop for Expression {
299 fn drop(&mut self) {
300 let Self::Scalar { children, .. } = self else {
301 return;
302 };
303 let Some(children) = Arc::get_mut(children) else {
304 return;
305 };
306 if children.is_empty() {
307 return;
308 }
309
310 let mut children_to_drop = std::mem::take(children);
311
312 match DropDepthGuard::enter() {
313 Some(_guard) => drop(children_to_drop),
314 None => {
315 while let Some(mut child) = children_to_drop.pop() {
316 if let Self::Scalar { children, .. } = &mut child
317 && let Some(expr_children) = Arc::get_mut(children)
318 {
319 children_to_drop.append(expr_children);
320 }
321 }
322 }
323 }
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use std::thread;
330
331 use super::*;
332 use crate::expr::lit;
333 use crate::expr::not;
334
335 fn deep_expression(depth: usize) -> Expression {
336 let mut expr = lit(true);
337 for _ in 0..depth {
338 expr = not(expr);
339 }
340 expr
341 }
342
343 #[test]
344 fn deep_expression_drops_within_a_small_stack() -> VortexResult<()> {
345 const DEPTH: usize = 100_000;
346 const STACK_SIZE: usize = 256 * 1024;
347
348 let dropper = thread::Builder::new()
349 .stack_size(STACK_SIZE)
350 .spawn(|| drop(deep_expression(DEPTH)))?;
351
352 assert!(
353 dropper.join().is_ok(),
354 "dropping a tree of depth {DEPTH} exhausted a {STACK_SIZE} byte stack"
355 );
356
357 Ok(())
358 }
359
360 #[test]
361 fn shallow_expression_keeps_shared_children() {
362 let expr = not(lit(true));
363 let shared = expr.clone();
364
365 drop(expr);
366
367 assert_eq!(shared.children().len(), 1);
368 }
369}