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 /// Format the expression as a compact string.
161 ///
162 /// Since this is a recursive formatter, it is exposed on the public Expression type.
163 /// See fmt_data that is only implemented on the vtable trait.
164 pub fn fmt_sql(&self, f: &mut Formatter<'_>) -> fmt::Result {
165 match self {
166 Self::Root => write!(f, "$"),
167 Self::Scalar { scalar_fn, .. } => scalar_fn.fmt_sql(self, f),
168 }
169 }
170
171 /// Display the expression as a formatted tree structure.
172 ///
173 /// This provides a hierarchical view of the expression that shows the relationships
174 /// between parent and child expressions, making complex nested expressions easier
175 /// to understand and debug.
176 ///
177 /// # Example
178 ///
179 /// ```rust
180 /// # use vortex_array::dtype::{DType, Nullability, PType};
181 /// # use vortex_array::scalar_fn::fns::like::{Like, LikeOptions};
182 /// # use vortex_array::scalar_fn::ScalarFnVTableExt;
183 /// # use vortex_array::expr::{and, cast, eq, get_item, gt, lit, not, root, select};
184 /// // Build a complex nested expression
185 /// let complex_expr = select(
186 /// ["result"],
187 /// and(
188 /// not(eq(get_item("status", root()), lit("inactive"))),
189 /// and(
190 /// Like.new_expr(LikeOptions::default(), [get_item("name", root()), lit("%admin%")]),
191 /// gt(
192 /// cast(get_item("score", root()), DType::Primitive(PType::F64, Nullability::NonNullable)),
193 /// lit(75.0)
194 /// )
195 /// )
196 /// )
197 /// );
198 ///
199 /// println!("{}", complex_expr.display_tree());
200 /// ```
201 ///
202 /// This produces output like:
203 ///
204 /// ```text
205 /// Select(include): {result}
206 /// └── Binary(and)
207 /// ├── lhs: Not
208 /// │ └── Binary(=)
209 /// │ ├── lhs: GetItem(status)
210 /// │ │ └── Root
211 /// │ └── rhs: Literal(value: "inactive", dtype: utf8)
212 /// └── rhs: Binary(and)
213 /// ├── lhs: Like
214 /// │ ├── child: GetItem(name)
215 /// │ │ └── Root
216 /// │ └── pattern: Literal(value: "%admin%", dtype: utf8)
217 /// └── rhs: Binary(>)
218 /// ├── lhs: Cast(target: f64)
219 /// │ └── GetItem(score)
220 /// │ └── Root
221 /// └── rhs: Literal(value: 75f64, dtype: f64)
222 /// ```
223 pub fn display_tree(&self) -> impl Display {
224 DisplayTreeExpr(self)
225 }
226
227 /// Returns true if this expression contains expression E inside.
228 ///
229 /// # Example
230 ///
231 /// ```rust
232 /// # use vortex_array::scalar_fn::fns::literal::Literal;
233 /// # use vortex_array::expr::{eq, lit, root};
234 /// let expression = &eq(root(), lit(3u64));
235 /// assert!(expression.contains::<Literal>().unwrap());
236 /// let expression = root();
237 /// assert!(!expression.contains::<Literal>().unwrap());
238 /// ```
239 pub fn contains<E: ScalarFnVTable>(&self) -> VortexResult<bool> {
240 let mut contains = false;
241 pre_order_visit_down(self, |node| {
242 if node.is::<E>() {
243 contains = true;
244 return Ok(TraversalOrder::Stop);
245 }
246 Ok(TraversalOrder::Continue)
247 })?;
248 Ok(contains)
249 }
250}
251
252/// The default display implementation for expressions uses the 'SQL'-style format.
253impl Display for Expression {
254 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
255 self.fmt_sql(f)
256 }
257}
258
259// Switch to iterative cleanup after this many recursive drops.
260const MAX_DROP_DEPTH: u32 = 32;
261
262thread_local! {
263 static DROP_DEPTH: Cell<u32> = const { Cell::new(0) };
264}
265
266// Increments the thread-local drop depth until the guard is dropped.
267struct DropDepthGuard;
268
269impl DropDepthGuard {
270 fn enter() -> Option<Self> {
271 DROP_DEPTH.with(|depth| {
272 let current = depth.get();
273 (current < MAX_DROP_DEPTH).then(|| {
274 depth.set(current + 1);
275 Self
276 })
277 })
278 }
279}
280
281impl Drop for DropDepthGuard {
282 fn drop(&mut self) {
283 DROP_DEPTH.with(|depth| depth.set(depth.get() - 1));
284 }
285}
286
287impl Drop for Expression {
288 fn drop(&mut self) {
289 let Self::Scalar { children, .. } = self else {
290 return;
291 };
292 let Some(children) = Arc::get_mut(children) else {
293 return;
294 };
295 if children.is_empty() {
296 return;
297 }
298
299 let mut children_to_drop = std::mem::take(children);
300
301 match DropDepthGuard::enter() {
302 Some(_guard) => drop(children_to_drop),
303 None => {
304 while let Some(mut child) = children_to_drop.pop() {
305 if let Self::Scalar { children, .. } = &mut child
306 && let Some(expr_children) = Arc::get_mut(children)
307 {
308 children_to_drop.append(expr_children);
309 }
310 }
311 }
312 }
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use std::thread;
319
320 use super::*;
321 use crate::expr::lit;
322 use crate::expr::not;
323
324 fn deep_expression(depth: usize) -> Expression {
325 let mut expr = lit(true);
326 for _ in 0..depth {
327 expr = not(expr);
328 }
329 expr
330 }
331
332 #[test]
333 fn deep_expression_drops_within_a_small_stack() -> VortexResult<()> {
334 const DEPTH: usize = 100_000;
335 const STACK_SIZE: usize = 256 * 1024;
336
337 let dropper = thread::Builder::new()
338 .stack_size(STACK_SIZE)
339 .spawn(|| drop(deep_expression(DEPTH)))?;
340
341 assert!(
342 dropper.join().is_ok(),
343 "dropping a tree of depth {DEPTH} exhausted a {STACK_SIZE} byte stack"
344 );
345
346 Ok(())
347 }
348
349 #[test]
350 fn shallow_expression_keeps_shared_children() {
351 let expr = not(lit(true));
352 let shared = expr.clone();
353
354 drop(expr);
355
356 assert_eq!(shared.children().len(), 1);
357 }
358}