Skip to main content

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}