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>;