@@ -194,68 +194,43 @@ pub trait ScalarFnVTable: 'static + Sized + Clone + Send + Sync {
194194
195195 /// Returns whether this scalar function is strict.
196196 ///
197- /// A strict function propagates nulls: at each row, a null in any argument always produces a
198- /// null result:
197+ /// A strict function returns null for a row when any argument is null for that row. This
198+ /// matches [PostgreSQL's `STRICT` convention](https://www.postgresql.org/docs/current/sql-createfunction.html)
199+ /// for null propagation.
199200 ///
200- /// ```text
201- /// vj = NULL for some j => f(v1, .., vk) = NULL
202- /// ```
201+ /// Return `true` only when this holds for every argument. `add` is strict, but Kleene `AND`
202+ /// is not because `false AND null` returns `false`. `is_null` is also not strict.
203203 ///
204- /// Strictness does not guarantee that non-null inputs produce a non-null result. For example,
205- /// `list_sum` returns null for a valid but empty list. Therefore, strictness only bounds the
206- /// output validity:
204+ /// Strictness does not require valid inputs to produce a valid output. For example,
205+ /// [`crate::expr::list_sum`] returns null for a valid empty list. Implement
206+ /// [`ScalarFnVTable::validity`] only when the output validity can be derived without
207+ /// evaluation.
207208 ///
208- /// ```text
209- /// valid(f(a1, .., ak)) ⊆ valid(a1) ∧ .. ∧ valid(ak)
210- /// ```
209+ /// [`ScalarFnVTable::return_dtype`] must return a nullable output dtype when any input dtype is
210+ /// nullable. A `cast` that forces a non-nullable output dtype is therefore not strict.
211211 ///
212- /// Many strict functions satisfy the stronger equality, where output validity is exactly the
213- /// intersection of the input validities. They can advertise that through
214- /// [`ScalarFnVTable::validity`], but callers must not infer it from strictness alone.
215- ///
216- /// Optimizations use strictness to move a mask through the function:
217- ///
218- /// ```text
219- /// f(a1, .., mask(aj', m), .., ak) == mask(f(a1, .., aj', .., ak), m)
220- /// ```
221- ///
222- /// The per-argument requirement matters when, for example, pushing a function through
223- /// dictionary codes while leaving its other arguments unmasked. Kleene `AND` and `OR` are not
224- /// strict even though a mask can be moved when applied to all arguments together:
225- /// `false AND null = false`.
226- ///
227- /// Returning `true` also requires [`ScalarFnVTable::return_dtype`] to propagate nullability:
228- /// if any input dtype is nullable, the output dtype must be nullable. For example, `cast` is
229- /// value-strict but can pin a non-nullable output dtype through its options, so it must return
230- /// `false`.
231- ///
232- /// This method describes only the scalar function, not its child expressions. Nullary
212+ /// This property applies only to the scalar function, not its child expressions. Nullary
233213 /// functions are vacuously strict. The default is conservatively `false`.
234214 fn is_strict ( & self , options : & Self :: Options ) -> bool {
235215 _ = options;
236216 false
237217 }
238218
239- /// Returns whether this expression is semantically fallible. Conservatively defaults to
240- /// `true`.
219+ /// Returns whether this scalar function can raise a semantic error.
241220 ///
242- /// An expression is semantically fallible if there exists a set of well-typed inputs that
243- /// causes the expression to produce an error as part of its _defined behavior_. For example,
244- /// `checked_add` is fallible because integer overflow is a domain error, and division is
245- /// fallible because of division by zero.
221+ /// Return `true` if a well-typed call can error because of its values. `checked_add` is
222+ /// fallible on integer overflow, and integer division is fallible when its divisor is zero.
223+ /// A null result is not an error: [`crate::expr::list_sum`] is infallible for an empty list.
246224 ///
247- /// This does **not** include execution errors that are incidental to the implementation, such
248- /// as canonicalization failures, memory allocation errors, or encoding mismatches. Those can
249- /// happen to any expression and are not what this method captures.
225+ /// Exclude incidental execution errors, such as canonicalization failures, allocation errors,
226+ /// and encoding mismatches. They are not part of the function's semantics.
250227 ///
251- /// This property is used by optimizations that speculatively evaluate an expression over values
252- /// that may not appear in the actual input. For example, pushing a scalar function down to a
253- /// dictionary's values array is only safe when the function is infallible or all values are
254- /// referenced, since a fallible function might error on a value left unreferenced after
255- /// slicing that would never be encountered during normal evaluation.
228+ /// Returning `false` permits optimizations that evaluate the function over values that no input
229+ /// row references. Dictionary push-down, for example, evaluates every dictionary value, so a
230+ /// fallible function could error on a value that row-wise evaluation would never reach.
256231 ///
257- /// Note: this is only applicable to expressions that pass type-checking via
258- /// [`ScalarFnVTable::return_dtype`].
232+ /// This applies only to the scalar function, not its child expressions, and only to inputs
233+ /// accepted by [`ScalarFnVTable::return_dtype`]. The default is conservatively `true` .
259234 fn is_fallible ( & self , options : & Self :: Options ) -> bool {
260235 _ = options;
261236 true
0 commit comments