Skip to main content

fsqlite_func/
scalar.rs

1//! Scalar (row-level) function trait.
2//!
3//! Scalar functions compute a single output value from zero or more input
4//! values. They are stateless across rows: each invocation is independent.
5//!
6//! This trait is **open** (user-implementable), unlike the sealed pager/btree
7//! traits. Extension authors implement `ScalarFunction` to register custom
8//! SQL functions.
9//!
10//! # Send + Sync
11//!
12//! Scalar functions may be shared across threads via `Arc` for use by
13//! concurrent query executors. Implementations must be thread-safe.
14//!
15//! # Cx Exception
16//!
17//! `invoke` does **not** take `&Cx` because deterministic scalar functions
18//! are pure computations (§9 cross-cutting rule: "Pure computation
19//! exceptions: deterministic ScalarFunction::invoke without I/O need not
20//! take Cx").
21#![allow(clippy::unnecessary_literal_bound)]
22
23use fsqlite_error::Result;
24use fsqlite_types::SqliteValue;
25
26use crate::{FunctionArity, collation::CollationFunction};
27
28/// A scalar (row-level) SQL function.
29///
30/// Scalar functions are invoked once per row and return a single value.
31/// They are stored in the [`FunctionRegistry`](crate::FunctionRegistry) as
32/// `Arc<dyn ScalarFunction>`.
33///
34/// # Error Handling
35///
36/// - Return [`FrankenError::FunctionError`](fsqlite_error::FrankenError::FunctionError)
37///   for domain errors (e.g. `abs(i64::MIN)`).
38/// - Return [`FrankenError::TooBig`](fsqlite_error::FrankenError::TooBig)
39///   if the result exceeds `SQLITE_MAX_LENGTH`.
40/// SQLite's JSON subtype tag (`'J'` = 74), attached to TEXT/BLOB values
41/// produced by JSON functions so downstream JSON constructors embed them as
42/// parsed JSON rather than quoting them as ordinary strings.
43pub const JSON_SUBTYPE: u32 = 74;
44
45pub trait ScalarFunction: Send + Sync {
46    /// Execute this function on the given arguments.
47    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue>;
48
49    /// Execute this function with knowledge of each argument's value subtype
50    /// (e.g. [`JSON_SUBTYPE`] for values returned by JSON functions).
51    ///
52    /// The default ignores subtypes and delegates to [`Self::invoke`]. JSON
53    /// constructors (`json_object`, `json_array`, the JSON mutators) override
54    /// this so a JSON-subtyped TEXT argument is embedded as a JSON value
55    /// instead of being quoted as a plain string — matching C SQLite, which
56    /// keys this behaviour off `sqlite3_value_subtype()`.
57    fn invoke_with_arg_subtypes(
58        &self,
59        args: &[SqliteValue],
60        _arg_subtypes: &[u32],
61    ) -> Result<SqliteValue> {
62        self.invoke(args)
63    }
64
65    /// Whether this function consumes the SQL collation selected from its
66    /// arguments (for example built-in `nullif`, scalar `min`, and scalar
67    /// `max`). Custom functions default to collation-opaque semantics.
68    fn consumes_argument_collation(&self) -> bool {
69        false
70    }
71
72    /// Invoke with the selected SQL collation, when this implementation
73    /// advertises [`Self::consumes_argument_collation`].
74    ///
75    /// The default deliberately ignores the collation so a custom function
76    /// that happens to replace a collation-consuming built-in keeps its own
77    /// semantics.
78    fn invoke_with_collation(
79        &self,
80        args: &[SqliteValue],
81        _collation: Option<&dyn CollationFunction>,
82    ) -> Result<SqliteValue> {
83        self.invoke(args)
84    }
85
86    /// The subtype this function tags onto its result value, if any.
87    ///
88    /// Returns [`JSON_SUBTYPE`] for functions whose result is JSON text so the
89    /// engine can propagate the tag to the destination register. Defaults to
90    /// `None` (no subtype).
91    fn result_subtype(&self) -> Option<u32> {
92        None
93    }
94
95    /// Whether this function is deterministic (same inputs → same output).
96    ///
97    /// Deterministic functions enable constant folding and other query
98    /// planner optimizations. Defaults to `true`.
99    fn is_deterministic(&self) -> bool {
100        true
101    }
102
103    /// The number of arguments this function accepts.
104    ///
105    /// `-1` means variadic (any number of arguments).
106    fn num_args(&self) -> i32;
107
108    /// Minimum accepted argument count for a variadic function.
109    ///
110    /// The default is zero. Fixed-arity functions are matched directly from
111    /// [`Self::num_args`] and do not consult this method.
112    fn min_args(&self) -> i32 {
113        0
114    }
115
116    /// Maximum accepted argument count for a variadic function.
117    ///
118    /// The default is unbounded. Fixed-arity functions are matched directly
119    /// from [`Self::num_args`] and do not consult this method.
120    fn max_args(&self) -> Option<i32> {
121        None
122    }
123
124    /// Return the complete SQL-visible arity contract in one metadata call.
125    ///
126    /// Registries use this method exactly once before publishing a function,
127    /// so a reentrant or stateful [`Self::num_args`] implementation cannot
128    /// produce a key and bounds from different observations. Implementations
129    /// with dynamically-computed metadata may override this method directly.
130    fn arity(&self) -> FunctionArity {
131        let declared = self.num_args();
132        FunctionArity::from_declared_args(declared, || (self.min_args(), self.max_args()))
133    }
134
135    /// The function name, used in error messages and EXPLAIN output.
136    fn name(&self) -> &str;
137}
138
139#[cfg(test)]
140mod tests {
141    use std::sync::Arc;
142
143    use fsqlite_error::FrankenError;
144
145    use super::*;
146
147    // -- Mock: add_one(x) -> x + 1 --
148
149    struct AddOne;
150
151    impl ScalarFunction for AddOne {
152        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
153            match &args[0] {
154                SqliteValue::Integer(i) => Ok(SqliteValue::Integer(i + 1)),
155                SqliteValue::Float(f) => Ok(SqliteValue::Float(f + 1.0)),
156                SqliteValue::Null => Ok(SqliteValue::Null),
157                SqliteValue::Text(s) => {
158                    let n: i64 = s.parse().unwrap_or(0);
159                    Ok(SqliteValue::Integer(n + 1))
160                }
161                SqliteValue::Blob(_) => Ok(SqliteValue::Integer(1)),
162            }
163        }
164
165        fn num_args(&self) -> i32 {
166            1
167        }
168
169        fn name(&self) -> &str {
170            "add_one"
171        }
172    }
173
174    // -- Mock: non-deterministic --
175
176    struct NonDeterministic;
177
178    impl ScalarFunction for NonDeterministic {
179        fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
180            Ok(SqliteValue::Integer(42))
181        }
182
183        fn is_deterministic(&self) -> bool {
184            false
185        }
186
187        fn num_args(&self) -> i32 {
188            0
189        }
190
191        fn name(&self) -> &str {
192            "random_ish"
193        }
194    }
195
196    // -- Mock: variadic concat --
197
198    struct Concat;
199
200    impl ScalarFunction for Concat {
201        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
202            let mut result = String::new();
203            for arg in args {
204                result.push_str(&arg.to_text());
205            }
206            Ok(SqliteValue::Text(result.into()))
207        }
208
209        fn num_args(&self) -> i32 {
210            -1
211        }
212
213        fn name(&self) -> &str {
214            "concat"
215        }
216    }
217
218    // -- Mock: domain error --
219
220    struct SafeAbs;
221
222    impl ScalarFunction for SafeAbs {
223        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
224            match &args[0] {
225                SqliteValue::Integer(i) => {
226                    if *i == i64::MIN {
227                        return Err(FrankenError::function_error("abs(i64::MIN) would overflow"));
228                    }
229                    Ok(SqliteValue::Integer(i.abs()))
230                }
231                _ => Ok(args[0].clone()),
232            }
233        }
234
235        fn num_args(&self) -> i32 {
236            1
237        }
238
239        fn name(&self) -> &str {
240            "safe_abs"
241        }
242    }
243
244    // -- Mock: too-big error --
245
246    struct BigResult;
247
248    impl ScalarFunction for BigResult {
249        fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
250            Err(FrankenError::TooBig)
251        }
252
253        fn num_args(&self) -> i32 {
254            0
255        }
256
257        fn name(&self) -> &str {
258            "big_result"
259        }
260    }
261
262    // -- Tests --
263
264    #[test]
265    fn test_scalar_function_invoke_basic() {
266        let f = AddOne;
267        // Integer
268        assert_eq!(
269            f.invoke(&[SqliteValue::Integer(41)]).unwrap(),
270            SqliteValue::Integer(42)
271        );
272        // Float
273        assert_eq!(
274            f.invoke(&[SqliteValue::Float(1.5)]).unwrap(),
275            SqliteValue::Float(2.5)
276        );
277        // Null
278        assert!(f.invoke(&[SqliteValue::Null]).unwrap().is_null());
279        // Text (numeric coercion)
280        assert_eq!(
281            f.invoke(&[SqliteValue::Text("99".into())]).unwrap(),
282            SqliteValue::Integer(100)
283        );
284    }
285
286    #[test]
287    fn test_scalar_function_deterministic_flag() {
288        let det = AddOne;
289        assert!(det.is_deterministic());
290
291        let non_det = NonDeterministic;
292        assert!(!non_det.is_deterministic());
293    }
294
295    #[test]
296    fn test_scalar_function_variadic() {
297        let f = Concat;
298        assert_eq!(f.num_args(), -1);
299        assert_eq!(f.min_args(), 0);
300        assert_eq!(f.max_args(), None);
301        assert!(f.arity().accepts(0));
302        assert!(f.arity().accepts(3));
303
304        // 0 args
305        assert_eq!(f.invoke(&[]).unwrap(), SqliteValue::Text("".into()));
306
307        // 1 arg
308        assert_eq!(
309            f.invoke(&[SqliteValue::Text("hello".into())]).unwrap(),
310            SqliteValue::Text("hello".into())
311        );
312
313        // many args
314        assert_eq!(
315            f.invoke(&[
316                SqliteValue::Text("a".into()),
317                SqliteValue::Text("b".into()),
318                SqliteValue::Text("c".into()),
319            ])
320            .unwrap(),
321            SqliteValue::Text("abc".into())
322        );
323    }
324
325    #[test]
326    fn test_scalar_function_error_domain() {
327        let f = SafeAbs;
328        let err = f.invoke(&[SqliteValue::Integer(i64::MIN)]).unwrap_err();
329        assert!(
330            matches!(err, FrankenError::FunctionError(ref msg) if msg.contains("overflow")),
331            "expected FunctionError, got {err:?}"
332        );
333    }
334
335    #[test]
336    fn test_scalar_function_too_big_error() {
337        let f = BigResult;
338        let err = f.invoke(&[]).unwrap_err();
339        assert!(matches!(err, FrankenError::TooBig));
340    }
341
342    #[test]
343    fn test_scalar_send_sync() {
344        fn assert_send_sync<T: Send + Sync>() {}
345        assert_send_sync::<AddOne>();
346
347        // Can be stored in Arc
348        let f: Arc<dyn ScalarFunction> = Arc::new(AddOne);
349        let f2 = Arc::clone(&f);
350        let handle = std::thread::spawn(move || f2.invoke(&[SqliteValue::Integer(0)]));
351        let _ = f.invoke(&[SqliteValue::Integer(1)]);
352        handle.join().unwrap().unwrap();
353    }
354}