Skip to main content

vortex_array/scalar_fn/
vtable.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::borrow::Cow;
5use std::fmt;
6use std::fmt::Debug;
7use std::fmt::Display;
8use std::fmt::Formatter;
9use std::hash::Hash;
10
11use arcref::ArcRef;
12use vortex_error::VortexExpect;
13use vortex_error::VortexResult;
14use vortex_error::vortex_bail;
15use vortex_error::vortex_err;
16use vortex_session::VortexSession;
17
18use crate::ArrayRef;
19use crate::ExecutionCtx;
20use crate::IntoArray;
21use crate::arrays::ScalarFn;
22use crate::arrays::ScalarFnArray;
23use crate::dtype::DType;
24use crate::expr::BoundExpression;
25use crate::expr::Expression;
26use crate::expr::display::ExprDisplay;
27use crate::scalar_fn::ScalarFnId;
28use crate::scalar_fn::ScalarFnRef;
29use crate::scalar_fn::TypedScalarFnInstance;
30
31/// This trait defines the interface for scalar function vtables, including methods for
32/// serialization, deserialization, validation, child naming, return type computation,
33/// and evaluation.
34///
35/// This trait is non-object safe and allows the implementer to make use of associated types
36/// for improved type safety, while allowing Vortex to enforce runtime checks on the inputs and
37/// outputs of each function.
38///
39/// The [`ScalarFnVTable`] trait should be implemented for a struct that holds global data across
40/// all instances of the expression. In almost all cases, this struct will be an empty unit
41/// struct, since most expressions do not require any global state.
42pub trait ScalarFnVTable: 'static + Sized + Clone + Send + Sync {
43    /// Options for this expression.
44    type Options: 'static + Send + Sync + Clone + Debug + Display + PartialEq + Eq + Hash;
45
46    /// Returns the ID of the scalar function vtable.
47    fn id(&self) -> ScalarFnId;
48
49    /// Serialize the options for this expression.
50    ///
51    /// Should return `Ok(None)` if the expression is not serializable, and `Ok(vec![])` if it is
52    /// serializable but has no metadata.
53    fn serialize(&self, options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
54        _ = options;
55        Ok(None)
56    }
57
58    /// Deserialize the options of this expression.
59    fn deserialize(
60        &self,
61        _metadata: &[u8],
62        _session: &VortexSession,
63    ) -> VortexResult<Self::Options> {
64        vortex_bail!("Expression {} is not deserializable", self.id());
65    }
66
67    /// Returns the arity of this expression.
68    fn arity(&self, options: &Self::Options) -> Arity;
69
70    /// Returns the name of the nth child of the expr.
71    fn child_name(&self, options: &Self::Options, child_idx: usize) -> ChildName;
72
73    /// Format an expression tree in a human-readable SQL-style format.
74    ///
75    /// The expression may be either an [`Expression`] or a
76    /// [`bound expression`](crate::expr::BoundExpression).
77    fn fmt_sql(
78        &self,
79        options: &Self::Options,
80        expr: &dyn ExprDisplay,
81        f: &mut Formatter<'_>,
82    ) -> fmt::Result {
83        write!(f, "{}(", self.id())?;
84        let nchildren = expr.display_children_count();
85        for i in 0..nchildren {
86            Display::fmt(expr.display_child(i), f)?;
87            if i + 1 < nchildren {
88                write!(f, ", ")?;
89            }
90        }
91        let opts = format!("{}", options);
92        if !opts.is_empty() {
93            write!(f, ", opts={}", opts)?;
94        }
95        write!(f, ")")
96    }
97
98    /// Compute the return [`DType`] of the expression if evaluated over the given input types.
99    ///
100    /// # Preconditions
101    ///
102    /// The length of `args` must match the [`Arity`] of this function. Callers are responsible
103    /// for validating this (e.g., [`Expression::try_new`] checks arity at construction time).
104    /// Implementations may assume correct arity and will panic or return nonsensical results if
105    /// violated.
106    ///
107    /// [`Expression::try_new`]: crate::expr::Expression::try_new
108    fn return_dtype(&self, options: &Self::Options, args: &[DType]) -> VortexResult<DType>;
109
110    /// Execute the expression over the input arguments.
111    ///
112    /// Implementations are encouraged to check their inputs for constant arrays to perform
113    /// more optimized execution.
114    ///
115    /// If the input arguments cannot be directly used for execution (for example, an expression
116    /// may require canonical input arrays), then the implementation should perform a single
117    /// child execution and return a new [`crate::arrays::ScalarFnArray`] wrapping up the new child.
118    ///
119    /// This provides maximum opportunities for array-level optimizations using execute_parent
120    /// kernels.
121    fn execute(
122        &self,
123        options: &Self::Options,
124        args: &dyn ExecutionArgs,
125        ctx: &mut ExecutionCtx,
126    ) -> VortexResult<ArrayRef>;
127
128    /// Implement an abstract reduction rule over a tree of scalar functions.
129    ///
130    /// The [`ReduceNode`] can be used to traverse children, inspect their types, and
131    /// construct the result via [`ReduceNode::new_node`]. The rule is generic over the node
132    /// type and is instantiated once per reducible tree kind (expressions and arrays).
133    ///
134    /// Return `Ok(None)` if no reduction is possible.
135    fn reduce<T: ReduceNode>(&self, options: &Self::Options, node: &T) -> VortexResult<Option<T>> {
136        _ = options;
137        _ = node;
138        Ok(None)
139    }
140
141    /// Simplify the expression if possible.
142    fn simplify(
143        &self,
144        options: &Self::Options,
145        expr: &Expression,
146        ctx: &dyn SimplifyCtx,
147    ) -> VortexResult<Option<Expression>> {
148        _ = options;
149        _ = expr;
150        _ = ctx;
151        Ok(None)
152    }
153
154    /// Simplify the expression if possible, without type information.
155    fn simplify_untyped(
156        &self,
157        options: &Self::Options,
158        expr: &Expression,
159    ) -> VortexResult<Option<Expression>> {
160        _ = options;
161        _ = expr;
162        Ok(None)
163    }
164
165    /// Returns an expression that evaluates to the validity of the result of this expression.
166    ///
167    /// If a validity expression cannot be constructed, returns `None` and the expression will
168    /// be evaluated as normal before extracting the validity mask from the result.
169    ///
170    /// This is essentially a specialized form of a `reduce_parent`
171    fn validity(
172        &self,
173        options: &Self::Options,
174        expression: &Expression,
175    ) -> VortexResult<Option<Expression>> {
176        _ = (options, expression);
177        Ok(None)
178    }
179
180    /// Returns whether this scalar function is strict.
181    ///
182    /// A strict function returns null for a row when any argument is null for that row. This
183    /// matches [PostgreSQL's `STRICT` convention](https://www.postgresql.org/docs/current/sql-createfunction.html)
184    /// for null propagation.
185    ///
186    /// Return `true` only when this holds for every argument. `add` is strict, but Kleene `AND`
187    /// is not because `false AND null` returns `false`. `is_null` is also not strict.
188    ///
189    /// Strictness does not require valid inputs to produce a valid output. For example,
190    /// [`crate::expr::list_sum`] returns null for a valid empty list. Implement
191    /// [`ScalarFnVTable::validity`] only when the output validity can be derived without
192    /// evaluation.
193    ///
194    /// [`ScalarFnVTable::return_dtype`] must return a nullable output dtype when any input dtype is
195    /// nullable. A `cast` that forces a non-nullable output dtype is therefore not strict.
196    ///
197    /// This property applies only to the scalar function, not its child expressions. Nullary
198    /// functions are vacuously strict. The default is conservatively `false`.
199    fn is_strict(&self, options: &Self::Options) -> bool {
200        _ = options;
201        false
202    }
203
204    /// Returns whether this scalar function can never raise a semantic error.
205    ///
206    /// Return `true` only when a well-typed call cannot error because of its values. `checked_add`
207    /// is fallible on integer overflow, and integer division is fallible when its divisor is zero.
208    /// A null result is not an error: [`crate::expr::list_sum`] is infallible for an empty list.
209    ///
210    /// Ignore incidental execution errors, such as canonicalization failures, allocation errors,
211    /// and encoding mismatches. They are not part of the function's semantics.
212    ///
213    /// Returning `true` permits optimizations that evaluate the function over values that no input
214    /// row references. Dictionary push-down, for example, evaluates every dictionary value, so a
215    /// fallible function could error on a value that row-wise evaluation would never reach.
216    ///
217    /// This applies only to the scalar function, not its child expressions, and only to inputs
218    /// accepted by [`ScalarFnVTable::return_dtype`]. The default is conservatively `false`.
219    fn is_infallible(&self, options: &Self::Options) -> bool {
220        _ = options;
221        false
222    }
223}
224
225/// A node used for implementing abstract reduction rules over a tree of scalar functions.
226///
227/// Reduction rules are generic over the node type, so a rule is written once and monomorphized
228/// per reducible tree kind: [`ExpressionReduceNode`] for expression trees and
229/// [`ArrayReduceNode`] for array trees. Nodes borrow from the tree being reduced, making
230/// traversal allocation-free, while nodes produced by [`ReduceNode::new_node`] own their
231/// freshly-built subtrees.
232pub trait ReduceNode: Clone {
233    /// Return the data type of this node.
234    fn node_dtype(&self) -> VortexResult<DType>;
235
236    /// Return this node's scalar function if it is indeed a scalar fn.
237    fn scalar_fn(&self) -> Option<&ScalarFnRef>;
238
239    /// Descend to the child of this node.
240    fn child(&self, idx: usize) -> Self;
241
242    /// Returns the number of children of this node.
243    fn child_count(&self) -> usize;
244
245    /// Create a new node from the given scalar function and children, inheriting this node's
246    /// reduction context (e.g. the expression scope, or the array row count).
247    fn new_node(&self, scalar_fn: ScalarFnRef, children: &[Self]) -> VortexResult<Self>;
248}
249
250/// A [`ReduceNode`] over an expression tree, typed within a scope.
251#[derive(Clone)]
252pub struct ExpressionReduceNode<'a> {
253    expression: Cow<'a, Expression>,
254    scope: &'a DType,
255}
256
257impl<'a> ExpressionReduceNode<'a> {
258    /// Creates a node borrowing the given expression and scope.
259    pub fn new(expression: &'a Expression, scope: &'a DType) -> Self {
260        Self {
261            expression: Cow::Borrowed(expression),
262            scope,
263        }
264    }
265
266    /// Returns the expression backing this node.
267    pub fn expression(&self) -> &Expression {
268        &self.expression
269    }
270
271    /// Consumes this node and returns the backing expression.
272    pub fn into_expression(self) -> Expression {
273        self.expression.into_owned()
274    }
275}
276
277impl ReduceNode for ExpressionReduceNode<'_> {
278    fn node_dtype(&self) -> VortexResult<DType> {
279        self.expression.return_dtype(self.scope)
280    }
281
282    fn scalar_fn(&self) -> Option<&ScalarFnRef> {
283        self.expression.as_scalar()
284    }
285
286    fn child(&self, idx: usize) -> Self {
287        let expression = match &self.expression {
288            Cow::Borrowed(expression) => Cow::Borrowed(expression.child(idx)),
289            Cow::Owned(expression) => Cow::Owned(expression.child(idx).clone()),
290        };
291        Self {
292            expression,
293            scope: self.scope,
294        }
295    }
296
297    fn child_count(&self) -> usize {
298        self.expression.children().len()
299    }
300
301    fn new_node(&self, scalar_fn: ScalarFnRef, children: &[Self]) -> VortexResult<Self> {
302        let expression = Expression::try_new(
303            scalar_fn,
304            children
305                .iter()
306                .map(|c| c.expression.as_ref().clone())
307                .collect::<Vec<_>>(),
308        )?;
309        Ok(Self {
310            expression: Cow::Owned(expression),
311            scope: self.scope,
312        })
313    }
314}
315
316/// A [`ReduceNode`] over an array tree.
317#[derive(Clone)]
318pub struct ArrayReduceNode<'a> {
319    array: Cow<'a, ArrayRef>,
320}
321
322impl<'a> ArrayReduceNode<'a> {
323    /// Creates a node borrowing the given array.
324    pub fn new(array: &'a ArrayRef) -> Self {
325        Self {
326            array: Cow::Borrowed(array),
327        }
328    }
329
330    /// Returns the array backing this node.
331    pub fn array(&self) -> &ArrayRef {
332        &self.array
333    }
334
335    /// Consumes this node and returns the backing array.
336    pub fn into_array(self) -> ArrayRef {
337        self.array.into_owned()
338    }
339}
340
341impl ReduceNode for ArrayReduceNode<'_> {
342    fn node_dtype(&self) -> VortexResult<DType> {
343        Ok(self.array.dtype().clone())
344    }
345
346    fn scalar_fn(&self) -> Option<&ScalarFnRef> {
347        self.array
348            .as_opt::<ScalarFn>()
349            .map(|a| a.data().scalar_fn())
350    }
351
352    fn child(&self, idx: usize) -> Self {
353        let array = match &self.array {
354            Cow::Borrowed(array) => Cow::Borrowed(
355                array
356                    .children_iter()
357                    .nth(idx)
358                    .vortex_expect("child idx out of bounds"),
359            ),
360            Cow::Owned(array) => Cow::Owned(
361                array
362                    .nth_child(idx)
363                    .vortex_expect("child idx out of bounds"),
364            ),
365        };
366        Self { array }
367    }
368
369    fn child_count(&self) -> usize {
370        self.array.nchildren()
371    }
372
373    fn new_node(&self, scalar_fn: ScalarFnRef, children: &[Self]) -> VortexResult<Self> {
374        let array = ScalarFnArray::try_new_with_len(
375            scalar_fn,
376            children.iter().map(|c| c.array.as_ref().clone()).collect(),
377            self.array.len(),
378        )?;
379        Ok(Self {
380            array: Cow::Owned(array.into_array()),
381        })
382    }
383}
384
385/// The arity (number of arguments) of a function.
386#[derive(Clone, Copy, Debug, PartialEq, Eq)]
387pub enum Arity {
388    Exact(usize),
389    Variadic { min: usize, max: Option<usize> },
390}
391
392impl Display for Arity {
393    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
394        match self {
395            Arity::Exact(n) => write!(f, "{}", n),
396            Arity::Variadic { min, max } => match max {
397                Some(max) if min == max => write!(f, "{}", min),
398                Some(max) => write!(f, "{}..{}", min, max),
399                None => write!(f, "{}+", min),
400            },
401        }
402    }
403}
404
405impl Arity {
406    /// Whether the given argument count matches this arity.
407    pub fn matches(&self, arg_count: usize) -> bool {
408        match self {
409            Arity::Exact(m) => *m == arg_count,
410            Arity::Variadic { min, max } => {
411                if arg_count < *min {
412                    return false;
413                }
414                if let Some(max) = max
415                    && arg_count > *max
416                {
417                    return false;
418                }
419                true
420            }
421        }
422    }
423}
424
425/// Context for simplification.
426///
427/// Used to lazily compute input data types where simplification requires them.
428pub trait SimplifyCtx {
429    /// Get the data type of the given expression.
430    fn return_dtype(&self, expr: &Expression) -> VortexResult<DType>;
431}
432
433/// Arguments for expression execution.
434pub trait ExecutionArgs {
435    /// Returns the input array at the given index.
436    fn get(&self, index: usize) -> VortexResult<ArrayRef>;
437
438    /// Returns the number of inputs.
439    fn num_inputs(&self) -> usize;
440
441    /// Returns the row count of the execution scope.
442    fn row_count(&self) -> usize;
443}
444
445/// A concrete [`ExecutionArgs`] backed by a `Vec<ArrayRef>`.
446pub struct VecExecutionArgs {
447    inputs: Vec<ArrayRef>,
448    row_count: usize,
449}
450
451impl VecExecutionArgs {
452    /// Create a new `VecExecutionArgs`.
453    pub fn new(inputs: Vec<ArrayRef>, row_count: usize) -> Self {
454        Self { inputs, row_count }
455    }
456}
457
458impl ExecutionArgs for VecExecutionArgs {
459    fn get(&self, index: usize) -> VortexResult<ArrayRef> {
460        self.inputs.get(index).cloned().ok_or_else(|| {
461            vortex_err!(
462                "Input index {} out of bounds (num_inputs={})",
463                index,
464                self.inputs.len()
465            )
466        })
467    }
468
469    fn num_inputs(&self) -> usize {
470        self.inputs.len()
471    }
472
473    fn row_count(&self) -> usize {
474        self.row_count
475    }
476}
477
478#[derive(Clone, Debug, PartialEq, Eq, Hash)]
479pub struct EmptyOptions;
480impl Display for EmptyOptions {
481    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
482        write!(f, "")
483    }
484}
485
486/// Factory functions for vtables.
487pub trait ScalarFnVTableExt: ScalarFnVTable {
488    /// Bind this vtable with the given options into a [`ScalarFnRef`].
489    fn bind(&self, options: Self::Options) -> ScalarFnRef {
490        TypedScalarFnInstance::new(self.clone(), options).erased()
491    }
492
493    /// Create a new expression with this vtable and the given options and children.
494    fn new_expr(
495        &self,
496        options: Self::Options,
497        children: impl IntoIterator<Item = Expression>,
498    ) -> Expression {
499        Self::try_new_expr(self, options, children).vortex_expect("Failed to create expression")
500    }
501
502    /// Try to create a new expression with this vtable and the given options and children.
503    fn try_new_expr(
504        &self,
505        options: Self::Options,
506        children: impl IntoIterator<Item = Expression>,
507    ) -> VortexResult<Expression> {
508        Expression::try_new(self.bind(options), children)
509    }
510
511    /// Try to create a bound expression with this vtable, the given options, and bound children.
512    fn try_new_bound_expr(
513        &self,
514        options: Self::Options,
515        children: impl IntoIterator<Item = BoundExpression>,
516    ) -> VortexResult<BoundExpression> {
517        BoundExpression::try_new(self.bind(options), children)
518    }
519}
520impl<V: ScalarFnVTable> ScalarFnVTableExt for V {}
521
522/// A reference to the name of a child expression.
523pub type ChildName = ArcRef<str>;