Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 22 additions & 48 deletions vortex-array/src/scalar_fn/vtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,68 +194,42 @@ pub trait ScalarFnVTable: 'static + Sized + Clone + Send + Sync {

/// Returns whether this scalar function is strict.
///
/// A strict function propagates nulls: at each row, a null in any argument always produces a
/// null result:
/// A strict function returns null for a row when any argument is null for that row. This
/// matches PostgreSQL's `STRICT` convention for null propagation.
Comment on lines +197 to +198

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we link?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

best I can find is https://www.postgresql.org/docs/current/sql-createfunction.html and that description is honestly not great.

///
/// ```text
/// vj = NULL for some j => f(v1, .., vk) = NULL
/// ```
/// Return `true` only when this holds for every argument. `add` is strict, but Kleene `AND`
/// is not because `false AND null` returns `false`. `is_null` is also not strict.
///
/// Strictness does not guarantee that non-null inputs produce a non-null result. For example,
/// `list_sum` returns null for a valid but empty list. Therefore, strictness only bounds the
/// output validity:
/// Strictness does not require valid inputs to produce a valid output. For example,
/// [`crate::expr::list_sum`] returns null for a valid empty list. Implement
/// [`ScalarFnVTable::validity`] only when the output validity can be derived without
/// evaluation.
///
/// ```text
/// valid(f(a1, .., ak)) ⊆ valid(a1) ∧ .. ∧ valid(ak)
/// ```
/// [`ScalarFnVTable::return_dtype`] must return a nullable output dtype when any input dtype is
/// nullable. A `cast` that forces a non-nullable output dtype is therefore not strict.
///
/// Many strict functions satisfy the stronger equality, where output validity is exactly the
/// intersection of the input validities. They can advertise that through
/// [`ScalarFnVTable::validity`], but callers must not infer it from strictness alone.
///
/// Optimizations use strictness to move a mask through the function:
///
/// ```text
/// f(a1, .., mask(aj', m), .., ak) == mask(f(a1, .., aj', .., ak), m)
/// ```
///
/// The per-argument requirement matters when, for example, pushing a function through
/// dictionary codes while leaving its other arguments unmasked. Kleene `AND` and `OR` are not
/// strict even though a mask can be moved when applied to all arguments together:
/// `false AND null = false`.
///
/// Returning `true` also requires [`ScalarFnVTable::return_dtype`] to propagate nullability:
/// if any input dtype is nullable, the output dtype must be nullable. For example, `cast` is
/// value-strict but can pin a non-nullable output dtype through its options, so it must return
/// `false`.
///
/// This method describes only the scalar function, not its child expressions. Nullary
/// This property applies only to the scalar function, not its child expressions. Nullary
/// functions are vacuously strict. The default is conservatively `false`.
fn is_strict(&self, options: &Self::Options) -> bool {
_ = options;
false
}

/// Returns whether this expression is semantically fallible. Conservatively defaults to
/// `true`.
/// Returns whether this scalar function can raise a semantic error.
///
/// An expression is semantically fallible if there exists a set of well-typed inputs that
/// causes the expression to produce an error as part of its _defined behavior_. For example,
/// `checked_add` is fallible because integer overflow is a domain error, and division is
/// fallible because of division by zero.
/// Return `true` if a well-typed call can error because of its values. `checked_add` is
/// fallible on integer overflow, and integer division is fallible when its divisor is zero.
/// A null result is not an error: [`crate::expr::list_sum`] is infallible for an empty list.
///
/// This does **not** include execution errors that are incidental to the implementation, such
/// as canonicalization failures, memory allocation errors, or encoding mismatches. Those can
/// happen to any expression and are not what this method captures.
/// Exclude incidental execution errors, such as canonicalization failures, allocation errors,
/// and encoding mismatches. They are not part of the function's semantics.
///
/// This property is used by optimizations that speculatively evaluate an expression over values
/// that may not appear in the actual input. For example, pushing a scalar function down to a
/// dictionary's values array is only safe when the function is infallible or all values are
/// referenced, since a fallible function might error on a value left unreferenced after
/// slicing that would never be encountered during normal evaluation.
/// Returning `false` permits speculative evaluation. Dictionary push-down evaluates every
/// dictionary value, including values that no input row references. A fallible function could
/// therefore error on a value that normal evaluation (without pushdown) would never reach.
///
/// Note: this is only applicable to expressions that pass type-checking via
/// [`ScalarFnVTable::return_dtype`].
/// This applies only to the scalar function, not its child expressions, and only to inputs
/// accepted by [`ScalarFnVTable::return_dtype`]. The default is conservatively `true`.
fn is_fallible(&self, options: &Self::Options) -> bool {
_ = options;
true
Expand Down
Loading