diff --git a/vortex-array/src/scalar_fn/vtable.rs b/vortex-array/src/scalar_fn/vtable.rs index 9e825d27ce3..d27bcfc2403 100644 --- a/vortex-array/src/scalar_fn/vtable.rs +++ b/vortex-array/src/scalar_fn/vtable.rs @@ -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. /// - /// ```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