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
26/// A scalar (row-level) SQL function.
27///
28/// Scalar functions are invoked once per row and return a single value.
29/// They are stored in the [`FunctionRegistry`](crate::FunctionRegistry) as
30/// `Arc<dyn ScalarFunction>`.
31///
32/// # Error Handling
33///
34/// - Return [`FrankenError::FunctionError`](fsqlite_error::FrankenError::FunctionError)
35///   for domain errors (e.g. `abs(i64::MIN)`).
36/// - Return [`FrankenError::TooBig`](fsqlite_error::FrankenError::TooBig)
37///   if the result exceeds `SQLITE_MAX_LENGTH`.
38/// SQLite's JSON subtype tag (`'J'` = 74), attached to TEXT/BLOB values
39/// produced by JSON functions so downstream JSON constructors embed them as
40/// parsed JSON rather than quoting them as ordinary strings.
41pub const JSON_SUBTYPE: u32 = 74;
42
43pub trait ScalarFunction: Send + Sync {
44    /// Execute this function on the given arguments.
45    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue>;
46
47    /// Execute this function with knowledge of each argument's value subtype
48    /// (e.g. [`JSON_SUBTYPE`] for values returned by JSON functions).
49    ///
50    /// The default ignores subtypes and delegates to [`Self::invoke`]. JSON
51    /// constructors (`json_object`, `json_array`, the JSON mutators) override
52    /// this so a JSON-subtyped TEXT argument is embedded as a JSON value
53    /// instead of being quoted as a plain string — matching C SQLite, which
54    /// keys this behaviour off `sqlite3_value_subtype()`.
55    fn invoke_with_arg_subtypes(
56        &self,
57        args: &[SqliteValue],
58        _arg_subtypes: &[u32],
59    ) -> Result<SqliteValue> {
60        self.invoke(args)
61    }
62
63    /// The subtype this function tags onto its result value, if any.
64    ///
65    /// Returns [`JSON_SUBTYPE`] for functions whose result is JSON text so the
66    /// engine can propagate the tag to the destination register. Defaults to
67    /// `None` (no subtype).
68    fn result_subtype(&self) -> Option<u32> {
69        None
70    }
71
72    /// Whether this function is deterministic (same inputs → same output).
73    ///
74    /// Deterministic functions enable constant folding and other query
75    /// planner optimizations. Defaults to `true`.
76    fn is_deterministic(&self) -> bool {
77        true
78    }
79
80    /// The number of arguments this function accepts.
81    ///
82    /// `-1` means variadic (any number of arguments).
83    fn num_args(&self) -> i32;
84
85    /// Minimum accepted argument count for variadic functions.
86    ///
87    /// Fixed-arity functions default to their exact arity. Variadic functions
88    /// default to accepting zero arguments unless an implementation tightens
89    /// the bound to match SQLite's function surface.
90    fn min_args(&self) -> i32 {
91        self.num_args().max(0)
92    }
93
94    /// Maximum accepted argument count, or `None` for unbounded variadic
95    /// functions.
96    fn max_args(&self) -> Option<i32> {
97        (self.num_args() >= 0).then(|| self.num_args())
98    }
99
100    /// Return whether this function accepts `num_args` arguments.
101    fn accepts_arg_count(&self, num_args: i32) -> bool {
102        num_args >= self.min_args() && self.max_args().is_none_or(|max| num_args <= max)
103    }
104
105    /// The function name, used in error messages and EXPLAIN output.
106    fn name(&self) -> &str;
107}
108
109#[cfg(test)]
110mod tests {
111    use std::sync::Arc;
112
113    use fsqlite_error::FrankenError;
114
115    use super::*;
116
117    // -- Mock: add_one(x) -> x + 1 --
118
119    struct AddOne;
120
121    impl ScalarFunction for AddOne {
122        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
123            match &args[0] {
124                SqliteValue::Integer(i) => Ok(SqliteValue::Integer(i + 1)),
125                SqliteValue::Float(f) => Ok(SqliteValue::Float(f + 1.0)),
126                SqliteValue::Null => Ok(SqliteValue::Null),
127                SqliteValue::Text(s) => {
128                    let n: i64 = s.parse().unwrap_or(0);
129                    Ok(SqliteValue::Integer(n + 1))
130                }
131                SqliteValue::Blob(_) => Ok(SqliteValue::Integer(1)),
132            }
133        }
134
135        fn num_args(&self) -> i32 {
136            1
137        }
138
139        fn name(&self) -> &str {
140            "add_one"
141        }
142    }
143
144    // -- Mock: non-deterministic --
145
146    struct NonDeterministic;
147
148    impl ScalarFunction for NonDeterministic {
149        fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
150            Ok(SqliteValue::Integer(42))
151        }
152
153        fn is_deterministic(&self) -> bool {
154            false
155        }
156
157        fn num_args(&self) -> i32 {
158            0
159        }
160
161        fn name(&self) -> &str {
162            "random_ish"
163        }
164    }
165
166    // -- Mock: variadic concat --
167
168    struct Concat;
169
170    impl ScalarFunction for Concat {
171        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
172            let mut result = String::new();
173            for arg in args {
174                result.push_str(&arg.to_text());
175            }
176            Ok(SqliteValue::Text(result.into()))
177        }
178
179        fn num_args(&self) -> i32 {
180            -1
181        }
182
183        fn name(&self) -> &str {
184            "concat"
185        }
186    }
187
188    // -- Mock: domain error --
189
190    struct SafeAbs;
191
192    impl ScalarFunction for SafeAbs {
193        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
194            match &args[0] {
195                SqliteValue::Integer(i) => {
196                    if *i == i64::MIN {
197                        return Err(FrankenError::function_error("abs(i64::MIN) would overflow"));
198                    }
199                    Ok(SqliteValue::Integer(i.abs()))
200                }
201                _ => Ok(args[0].clone()),
202            }
203        }
204
205        fn num_args(&self) -> i32 {
206            1
207        }
208
209        fn name(&self) -> &str {
210            "safe_abs"
211        }
212    }
213
214    // -- Mock: too-big error --
215
216    struct BigResult;
217
218    impl ScalarFunction for BigResult {
219        fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
220            Err(FrankenError::TooBig)
221        }
222
223        fn num_args(&self) -> i32 {
224            0
225        }
226
227        fn name(&self) -> &str {
228            "big_result"
229        }
230    }
231
232    // -- Tests --
233
234    #[test]
235    fn test_scalar_function_invoke_basic() {
236        let f = AddOne;
237        // Integer
238        assert_eq!(
239            f.invoke(&[SqliteValue::Integer(41)]).unwrap(),
240            SqliteValue::Integer(42)
241        );
242        // Float
243        assert_eq!(
244            f.invoke(&[SqliteValue::Float(1.5)]).unwrap(),
245            SqliteValue::Float(2.5)
246        );
247        // Null
248        assert!(f.invoke(&[SqliteValue::Null]).unwrap().is_null());
249        // Text (numeric coercion)
250        assert_eq!(
251            f.invoke(&[SqliteValue::Text("99".into())]).unwrap(),
252            SqliteValue::Integer(100)
253        );
254    }
255
256    #[test]
257    fn test_scalar_function_deterministic_flag() {
258        let det = AddOne;
259        assert!(det.is_deterministic());
260
261        let non_det = NonDeterministic;
262        assert!(!non_det.is_deterministic());
263    }
264
265    #[test]
266    fn test_scalar_function_variadic() {
267        let f = Concat;
268        assert_eq!(f.num_args(), -1);
269        assert_eq!(f.min_args(), 0);
270        assert_eq!(f.max_args(), None);
271        assert!(f.accepts_arg_count(0));
272        assert!(f.accepts_arg_count(3));
273
274        // 0 args
275        assert_eq!(f.invoke(&[]).unwrap(), SqliteValue::Text("".into()));
276
277        // 1 arg
278        assert_eq!(
279            f.invoke(&[SqliteValue::Text("hello".into())]).unwrap(),
280            SqliteValue::Text("hello".into())
281        );
282
283        // many args
284        assert_eq!(
285            f.invoke(&[
286                SqliteValue::Text("a".into()),
287                SqliteValue::Text("b".into()),
288                SqliteValue::Text("c".into()),
289            ])
290            .unwrap(),
291            SqliteValue::Text("abc".into())
292        );
293    }
294
295    #[test]
296    fn test_scalar_function_error_domain() {
297        let f = SafeAbs;
298        let err = f.invoke(&[SqliteValue::Integer(i64::MIN)]).unwrap_err();
299        assert!(
300            matches!(err, FrankenError::FunctionError(ref msg) if msg.contains("overflow")),
301            "expected FunctionError, got {err:?}"
302        );
303    }
304
305    #[test]
306    fn test_scalar_function_too_big_error() {
307        let f = BigResult;
308        let err = f.invoke(&[]).unwrap_err();
309        assert!(matches!(err, FrankenError::TooBig));
310    }
311
312    #[test]
313    fn test_scalar_send_sync() {
314        fn assert_send_sync<T: Send + Sync>() {}
315        assert_send_sync::<AddOne>();
316
317        // Can be stored in Arc
318        let f: Arc<dyn ScalarFunction> = Arc::new(AddOne);
319        let f2 = Arc::clone(&f);
320        let handle = std::thread::spawn(move || f2.invoke(&[SqliteValue::Integer(0)]));
321        let _ = f.invoke(&[SqliteValue::Integer(1)]);
322        handle.join().unwrap().unwrap();
323    }
324}