From bad7b32a74dbabde0a3187ddf96451393cace778 Mon Sep 17 00:00:00 2001 From: Connor Tsui Date: Tue, 28 Jul 2026 10:25:26 -0400 Subject: [PATCH] adjust strict docs Signed-off-by: Connor Tsui --- vortex-array/src/scalar_fn/fns/list_length.rs | 2 ++ vortex-array/src/scalar_fn/fns/list_sum.rs | 3 +++ vortex-array/src/scalar_fn/vtable.rs | 21 ++++++++++++++----- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/vortex-array/src/scalar_fn/fns/list_length.rs b/vortex-array/src/scalar_fn/fns/list_length.rs index e1ce38353cc..415a65416db 100644 --- a/vortex-array/src/scalar_fn/fns/list_length.rs +++ b/vortex-array/src/scalar_fn/fns/list_length.rs @@ -109,6 +109,8 @@ impl ScalarFnVTable for ListLength { } fn is_strict(&self, _options: &Self::Options) -> bool { + // A null list has a null length, and the length of a valid list is a non-null value + // determined by that list alone, with `return_dtype` carrying over the input nullability. true } diff --git a/vortex-array/src/scalar_fn/fns/list_sum.rs b/vortex-array/src/scalar_fn/fns/list_sum.rs index 78ea4dfd1eb..95b8ddbb55a 100644 --- a/vortex-array/src/scalar_fn/fns/list_sum.rs +++ b/vortex-array/src/scalar_fn/fns/list_sum.rs @@ -121,6 +121,9 @@ impl ScalarFnVTable for ListSum { } fn is_strict(&self, _options: &Self::Options) -> bool { + // A null list sums to null, which is all that strictness requires. Element nulls are part + // of the list value rather than row-level nulls, and a valid list may still sum to null + // (empty, all-null, or overflow) since strictness is only one-directional. true } diff --git a/vortex-array/src/scalar_fn/vtable.rs b/vortex-array/src/scalar_fn/vtable.rs index 41f1ea25fc7..938466179a3 100644 --- a/vortex-array/src/scalar_fn/vtable.rs +++ b/vortex-array/src/scalar_fn/vtable.rs @@ -194,6 +194,8 @@ pub trait ScalarFnVTable: 'static + Sized + Clone + Send + Sync { /// Returns whether this expression itself is strict. /// + /// ## `STRICT` semantics + /// /// Strict has the same value-level meaning as PostgreSQL `STRICT`: if any input value at row /// `i` is null, the output value at row `i` is null. Formally, `f` is strict iff at each row, /// for some total function `g` over non-null values, @@ -203,8 +205,8 @@ pub trait ScalarFnVTable: 'static + Sized + Clone + Send + Sync { /// f(v1, .., vk) = g(v1, .., vk) otherwise /// ``` /// - /// That is, any null input yields a null output, and non-null outputs depend only on the (all - /// non-null) inputs at that row. Lifted columnwise, this is equivalent to the mask-hoisting + /// That is, any null input yields a null output, and the output at a fully non-null row depends + /// only on the inputs at that row. Lifted columnwise, this is equivalent to the mask-hoisting /// law: for any argument `aj = mask(aj', m)`, /// /// ```text @@ -217,10 +219,17 @@ pub trait ScalarFnVTable: 'static + Sized + Clone + Send + Sync { /// strict because, for example, `false AND null = false`. /// /// Two consequences that optimizations rely on: - /// 1. Output validity is precomputable as the `AND` of the input validities, so - /// `valid(f(a1, .., ak)) = valid(a1) ∧ .. ∧ valid(ak)`. + /// 1. Any null input slot nulls the output slot, so the output validity is bounded above by the + /// `AND` of the input validities: `valid(f(a1, .., ak)) ⊆ valid(a1) ∧ .. ∧ valid(ak)`. /// 2. Values behind null slots are irrelevant, so kernels may compute `g` densely over all - /// lanes (including garbage) and apply validity afterwards. + /// lanes (including garbage) and intersect the input validities afterwards. + /// + /// Strictness is one-directional: `g` may itself produce null, as `list_sum` does for a valid + /// but empty list. This is weaker than the validity-intersection property (Arrow's + /// `NullHandling::INTERSECTION`), where the output validity always *equals* the `AND` of the + /// input validities. Most strict functions satisfy that stronger property and can advertise it + /// via [`ScalarFnVTable::validity`], but optimizations must not assume it from strictness + /// alone. /// /// 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 @@ -229,6 +238,8 @@ pub trait ScalarFnVTable: 'static + Sized + Clone + Send + Sync { /// /// Nullary functions are vacuously strict because they have no input values. /// + /// ## Returns + /// /// Conservatively defaults to `false` (non-strict). /// /// This method only checks the expression itself, not its children.