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::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}