Skip to main content

fsqlite_func/
lib.rs

1//! Built-in SQL function and extension trait surfaces.
2//!
3//! This crate defines open, user-implementable traits for:
4//! - scalar, aggregate, and window functions
5//! - virtual table modules/cursors
6//! - collation callbacks
7//! - authorizer callbacks
8//!
9//! It also provides a small in-memory [`FunctionRegistry`] for registering and
10//! resolving scalar/aggregate/window functions by `(name, num_args)` key with
11//! variadic fallback.
12#![allow(clippy::unnecessary_literal_bound)]
13
14use std::any::Any;
15use std::collections::HashMap;
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::{Arc, OnceLock};
18
19use fsqlite_error::FrankenError;
20use fsqlite_types::SqliteValue;
21use tracing::debug;
22
23// ── Function evaluation metrics (bd-2wt.1) ─────────────────────────────────
24
25/// Total number of scalar function calls across all statements.
26static FSQLITE_FUNC_CALLS_TOTAL: AtomicU64 = AtomicU64::new(0);
27/// Cumulative function evaluation duration in microseconds.
28static FSQLITE_FUNC_EVAL_DURATION_US_TOTAL: AtomicU64 = AtomicU64::new(0);
29
30/// Snapshot of function evaluation metrics.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct FuncMetricsSnapshot {
33    /// Total scalar function calls.
34    pub calls_total: u64,
35    /// Cumulative evaluation duration in microseconds.
36    pub eval_duration_us_total: u64,
37}
38
39/// Read a point-in-time snapshot of function evaluation metrics.
40#[must_use]
41pub fn func_metrics_snapshot() -> FuncMetricsSnapshot {
42    FuncMetricsSnapshot {
43        calls_total: FSQLITE_FUNC_CALLS_TOTAL.load(Ordering::Relaxed),
44        eval_duration_us_total: FSQLITE_FUNC_EVAL_DURATION_US_TOTAL.load(Ordering::Relaxed),
45    }
46}
47
48/// Reset function metrics to zero (tests/diagnostics).
49pub fn reset_func_metrics() {
50    FSQLITE_FUNC_CALLS_TOTAL.store(0, Ordering::Relaxed);
51    FSQLITE_FUNC_EVAL_DURATION_US_TOTAL.store(0, Ordering::Relaxed);
52}
53
54/// Record a function call for metrics (called from VDBE engine).
55pub fn record_func_call(duration_us: u64) {
56    FSQLITE_FUNC_CALLS_TOTAL.fetch_add(1, Ordering::Relaxed);
57    FSQLITE_FUNC_EVAL_DURATION_US_TOTAL.fetch_add(duration_us, Ordering::Relaxed);
58}
59
60/// Record a function call count only, without timing (fast path).
61pub fn record_func_call_count_only() {
62    FSQLITE_FUNC_CALLS_TOTAL.fetch_add(1, Ordering::Relaxed);
63}
64
65// ── UDF registration metrics (bd-2wt.3) ────────────────────────────────
66
67/// Total number of UDF registrations.
68static FSQLITE_UDF_REGISTERED: AtomicU64 = AtomicU64::new(0);
69
70/// Record a UDF registration event.
71pub fn record_udf_registered() {
72    FSQLITE_UDF_REGISTERED.fetch_add(1, Ordering::Relaxed);
73}
74
75/// Current count of UDF registrations.
76#[must_use]
77pub fn udf_registered_count() -> u64 {
78    FSQLITE_UDF_REGISTERED.load(Ordering::Relaxed)
79}
80
81/// Reset UDF registration counter (tests/diagnostics).
82pub fn reset_udf_metrics() {
83    FSQLITE_UDF_REGISTERED.store(0, Ordering::Relaxed);
84}
85
86pub mod agg_builtins;
87pub mod aggregate;
88pub mod authorizer;
89pub mod builtins;
90pub mod collation;
91pub mod datetime;
92pub mod math;
93pub mod scalar;
94pub mod vtab;
95pub mod window;
96pub mod window_builtins;
97
98pub use agg_builtins::register_aggregate_builtins;
99pub use aggregate::{AggregateAdapter, AggregateFunction};
100pub use authorizer::{AuthAction, AuthResult, Authorizer, AuthorizerAction, AuthorizerDecision};
101pub use builtins::{
102    ChangeTrackingState, case_sensitive_like_active, get_last_changes, get_last_insert_rowid,
103    get_total_changes, register_builtins, reset_total_changes, set_case_sensitive_like,
104    set_change_tracking_state, set_last_changes, set_last_insert_rowid, sqlite_compile_options,
105    sqlite_compileoption_used,
106};
107pub use collation::{
108    BinaryCollation, CollationAnnotation, CollationFunction, CollationRegistry, CollationSource,
109    NoCaseCollation, RtrimCollation, resolve_collation,
110};
111pub use datetime::register_datetime_builtins;
112pub use math::register_math_builtins;
113pub use scalar::{JSON_SUBTYPE, ScalarFunction};
114pub use vtab::{
115    ColumnContext, ConstraintOp, IndexConstraint, IndexConstraintUsage, IndexInfo, IndexOrderBy,
116    VirtualTable, VirtualTableCursor,
117};
118pub use window::{WindowAdapter, WindowFunction};
119pub use window_builtins::register_window_builtins;
120
121/// Top-level function family exposed by the runtime registry.
122#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
123pub enum BuiltinFunctionFamily {
124    Scalar,
125    Aggregate,
126    Window,
127}
128
129impl BuiltinFunctionFamily {
130    #[must_use]
131    pub const fn label(self) -> &'static str {
132        match self {
133            Self::Scalar => "scalar",
134            Self::Aggregate => "aggregate",
135            Self::Window => "window",
136        }
137    }
138}
139
140/// Track-E built-in function class used for parity closure accounting.
141#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
142pub enum BuiltinFunctionClass {
143    CoreScalar,
144    MathScalar,
145    DateTimeScalar,
146    Aggregate,
147    Window,
148}
149
150impl BuiltinFunctionClass {
151    #[must_use]
152    pub const fn label(self) -> &'static str {
153        match self {
154            Self::CoreScalar => "core_scalar",
155            Self::MathScalar => "math_scalar",
156            Self::DateTimeScalar => "datetime_scalar",
157            Self::Aggregate => "aggregate",
158            Self::Window => "window",
159        }
160    }
161}
162
163/// Runtime-authoritative description of one built-in function registration.
164#[derive(Debug, Clone, PartialEq, Eq)]
165pub struct BuiltinFunctionSurfaceEntry {
166    /// Lowercase SQL function name as exposed by the runtime registry.
167    pub name: String,
168    /// Declared arity, or `-1` for variadic registrations.
169    pub num_args: i32,
170    /// Top-level function family.
171    pub family: BuiltinFunctionFamily,
172    /// Track-E parity classification bucket.
173    pub class: BuiltinFunctionClass,
174    /// Whether this entry is an alternate spelling over another runtime entry.
175    pub is_alias: bool,
176    /// Canonical parity surface identifier for this function family.
177    pub surface_id: &'static str,
178}
179
180const CORE_FUNCTION_SURFACE_ID: &str = "SURF-FUNC-CORE-011";
181const WINDOW_FUNCTION_SURFACE_ID: &str = "SURF-FUNC-WINDOW-012";
182
183/// Return the runtime-authoritative built-in function surface inventory.
184///
185/// The inventory is derived from the actual registration path in this crate
186/// rather than from harness-side matrices so Track E docs and future parity
187/// checks can reuse one stable source of truth.
188#[must_use]
189pub fn builtin_function_surface_inventory() -> &'static [BuiltinFunctionSurfaceEntry] {
190    static INVENTORY: OnceLock<Vec<BuiltinFunctionSurfaceEntry>> = OnceLock::new();
191    INVENTORY
192        .get_or_init(|| {
193            let mut registry = FunctionRegistry::new();
194            register_builtins(&mut registry);
195            register_window_builtins(&mut registry);
196
197            let mut entries = Vec::with_capacity(
198                registry.scalars.len() + registry.aggregates.len() + registry.windows.len(),
199            );
200            extend_builtin_surface_entries(
201                &mut entries,
202                BuiltinFunctionFamily::Scalar,
203                registry.scalars.keys(),
204            );
205            extend_builtin_surface_entries(
206                &mut entries,
207                BuiltinFunctionFamily::Aggregate,
208                registry.aggregates.keys(),
209            );
210            extend_builtin_surface_entries(
211                &mut entries,
212                BuiltinFunctionFamily::Window,
213                registry.windows.keys(),
214            );
215            entries.sort_by(|left, right| {
216                (left.family, left.class, &left.name, left.num_args).cmp(&(
217                    right.family,
218                    right.class,
219                    &right.name,
220                    right.num_args,
221                ))
222            });
223            entries
224        })
225        .as_slice()
226}
227
228/// Type-erased aggregate function object used by the registry.
229pub type ErasedAggregateFunction = dyn AggregateFunction<State = Box<dyn Any + Send>>;
230
231/// Type-erased window function object used by the registry.
232pub type ErasedWindowFunction = dyn WindowFunction<State = Box<dyn Any + Send>>;
233
234/// Composite lookup key for functions: `(UPPERCASE name, num_args)`.
235///
236/// `-1` for `num_args` means variadic (any number of arguments).
237/// Names are stored as uppercase ASCII for case-insensitive matching.
238#[derive(Debug, Clone, Hash, Eq, PartialEq)]
239pub struct FunctionKey {
240    /// Function name, stored as uppercase ASCII.
241    pub name: String,
242    /// Expected argument count, or `-1` for variadic.
243    pub num_args: i32,
244}
245
246impl FunctionKey {
247    /// Create a new function key with the name canonicalized to uppercase.
248    #[must_use]
249    pub fn new(name: &str, num_args: i32) -> Self {
250        Self {
251            name: canonical_name(name),
252            num_args,
253        }
254    }
255}
256
257/// Registry for scalar, aggregate, and window functions, keyed by
258/// `(name, num_args)`.
259///
260/// Lookup strategy (§9.5):
261/// 1. Exact match on `(UPPERCASE_NAME, num_args)`.
262/// 2. Fallback to an arity-compatible variadic version `(UPPERCASE_NAME, -1)`.
263/// 3. Known scalar name with incompatible arity returns a function that raises
264///    SQLite's "wrong number of arguments" error when invoked.
265/// 4. `None` if neither found (caller should raise "no such function").
266#[derive(Default)]
267pub struct FunctionRegistry {
268    scalars: HashMap<FunctionKey, Arc<dyn ScalarFunction>>,
269    aggregates: HashMap<FunctionKey, Arc<ErasedAggregateFunction>>,
270    windows: HashMap<FunctionKey, Arc<ErasedWindowFunction>>,
271}
272
273struct WrongArgCountScalarFunction {
274    display_name: String,
275}
276
277fn wrong_arg_count_message(display_name: &str) -> String {
278    format!("wrong number of arguments to function {display_name}()")
279}
280
281fn wrong_arg_display_name(canonical: &str) -> String {
282    canonical.to_ascii_lowercase()
283}
284
285impl WrongArgCountScalarFunction {
286    fn new(canonical: &str) -> Self {
287        Self {
288            display_name: wrong_arg_display_name(canonical),
289        }
290    }
291
292    fn message(&self) -> String {
293        wrong_arg_count_message(&self.display_name)
294    }
295}
296
297impl ScalarFunction for WrongArgCountScalarFunction {
298    fn invoke(&self, _args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
299        Err(FrankenError::function_error(self.message()))
300    }
301
302    fn num_args(&self) -> i32 {
303        -1
304    }
305
306    fn name(&self) -> &str {
307        &self.display_name
308    }
309}
310
311struct WrongArgCountAggregateFunction {
312    display_name: String,
313}
314
315impl WrongArgCountAggregateFunction {
316    fn new(canonical: &str) -> Self {
317        Self {
318            display_name: wrong_arg_display_name(canonical),
319        }
320    }
321
322    fn message(&self) -> String {
323        wrong_arg_count_message(&self.display_name)
324    }
325}
326
327impl AggregateFunction for WrongArgCountAggregateFunction {
328    type State = ();
329
330    fn initial_state(&self) -> Self::State {}
331
332    fn step(&self, _state: &mut Self::State, _args: &[SqliteValue]) -> fsqlite_error::Result<()> {
333        Err(FrankenError::function_error(self.message()))
334    }
335
336    fn finalize(&self, _state: Self::State) -> fsqlite_error::Result<SqliteValue> {
337        Err(FrankenError::function_error(self.message()))
338    }
339
340    fn num_args(&self) -> i32 {
341        -1
342    }
343
344    fn name(&self) -> &str {
345        &self.display_name
346    }
347}
348
349struct WrongArgCountWindowFunction {
350    display_name: String,
351}
352
353impl WrongArgCountWindowFunction {
354    fn new(canonical: &str) -> Self {
355        Self {
356            display_name: wrong_arg_display_name(canonical),
357        }
358    }
359
360    fn message(&self) -> String {
361        wrong_arg_count_message(&self.display_name)
362    }
363}
364
365impl WindowFunction for WrongArgCountWindowFunction {
366    type State = ();
367
368    fn initial_state(&self) -> Self::State {}
369
370    fn step(&self, _state: &mut Self::State, _args: &[SqliteValue]) -> fsqlite_error::Result<()> {
371        Err(FrankenError::function_error(self.message()))
372    }
373
374    fn inverse(
375        &self,
376        _state: &mut Self::State,
377        _args: &[SqliteValue],
378    ) -> fsqlite_error::Result<()> {
379        Err(FrankenError::function_error(self.message()))
380    }
381
382    fn value(&self, _state: &Self::State) -> fsqlite_error::Result<SqliteValue> {
383        Err(FrankenError::function_error(self.message()))
384    }
385
386    fn finalize(&self, _state: Self::State) -> fsqlite_error::Result<SqliteValue> {
387        Err(FrankenError::function_error(self.message()))
388    }
389
390    fn num_args(&self) -> i32 {
391        -1
392    }
393
394    fn name(&self) -> &str {
395        &self.display_name
396    }
397}
398
399impl FunctionRegistry {
400    /// Create an empty registry.
401    #[must_use]
402    pub fn new() -> Self {
403        Self::default()
404    }
405
406    /// Create a mutable clone of a registry from an `Arc` reference.
407    ///
408    /// This is used by the UDF registration API to produce a new registry
409    /// containing the existing functions plus the newly registered UDF.
410    #[must_use]
411    pub fn clone_from_arc(arc: &Arc<Self>) -> Self {
412        Self {
413            scalars: arc.scalars.clone(),
414            aggregates: arc.aggregates.clone(),
415            windows: arc.windows.clone(),
416        }
417    }
418
419    /// Register a scalar function, keyed by `(name, num_args)`.
420    ///
421    /// Overwrites any existing function with the same key. Returns the
422    /// previous function if one existed.
423    pub fn register_scalar<F>(&mut self, function: F) -> Option<Arc<dyn ScalarFunction>>
424    where
425        F: ScalarFunction + 'static,
426    {
427        let key = FunctionKey::new(function.name(), function.num_args());
428        self.scalars.insert(key, Arc::new(function))
429    }
430
431    /// Register an aggregate function using the type-erased adapter.
432    ///
433    /// Overwrites any existing function with the same `(name, num_args)` key.
434    pub fn register_aggregate<F>(&mut self, function: F) -> Option<Arc<ErasedAggregateFunction>>
435    where
436        F: AggregateFunction + 'static,
437        F::State: 'static,
438    {
439        let key = FunctionKey::new(function.name(), function.num_args());
440        self.aggregates
441            .insert(key, Arc::new(AggregateAdapter::new(function)))
442    }
443
444    /// Register a window function using the type-erased adapter.
445    ///
446    /// Overwrites any existing function with the same `(name, num_args)` key.
447    pub fn register_window<F>(&mut self, function: F) -> Option<Arc<ErasedWindowFunction>>
448    where
449        F: WindowFunction + 'static,
450        F::State: 'static,
451    {
452        let key = FunctionKey::new(function.name(), function.num_args());
453        self.windows
454            .insert(key, Arc::new(WindowAdapter::new(function)))
455    }
456
457    /// Look up a scalar function by `(name, num_args)`.
458    ///
459    /// Tries exact match first, then falls back to an arity-compatible
460    /// variadic version `(name, -1)` if no exact match exists.
461    #[must_use]
462    pub fn find_scalar(&self, name: &str, num_args: i32) -> Option<Arc<dyn ScalarFunction>> {
463        let canon = canonical_name(name);
464        self.find_scalar_precanonical(&canon, num_args)
465    }
466
467    /// Look up a scalar function by already-uppercased name (avoids allocation).
468    ///
469    /// Used by the VDBE engine where `P4::FuncName` values are already
470    /// canonicalized by codegen.
471    #[must_use]
472    pub fn find_scalar_precanonical(
473        &self,
474        canonical: &str,
475        num_args: i32,
476    ) -> Option<Arc<dyn ScalarFunction>> {
477        let exact = FunctionKey {
478            name: canonical.to_owned(),
479            num_args,
480        };
481        if let Some(f) = self.scalars.get(&exact) {
482            debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "exact", "registry lookup");
483            return Some(Arc::clone(f));
484        }
485        let variadic = FunctionKey {
486            name: canonical.to_owned(),
487            num_args: -1,
488        };
489        if let Some(function) = self.scalars.get(&variadic) {
490            if function.accepts_arg_count(num_args) {
491                debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "variadic", "registry lookup");
492                return Some(Arc::clone(function));
493            }
494            debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "wrong_arity", "registry lookup");
495            return Some(Arc::new(WrongArgCountScalarFunction::new(canonical)));
496        }
497        if self.scalars.keys().any(|key| key.name == canonical) {
498            debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "wrong_arity", "registry lookup");
499            return Some(Arc::new(WrongArgCountScalarFunction::new(canonical)));
500        }
501        debug!(
502            name = %canonical,
503            arity = num_args,
504            kind = "scalar",
505            hit = "miss",
506            "registry lookup"
507        );
508        None
509    }
510
511    /// Look up an aggregate function by `(name, num_args)`.
512    ///
513    /// Tries exact match first, then falls back to variadic `(name, -1)`.
514    #[must_use]
515    pub fn find_aggregate(
516        &self,
517        name: &str,
518        num_args: i32,
519    ) -> Option<Arc<ErasedAggregateFunction>> {
520        let canon = canonical_name(name);
521        self.find_aggregate_precanonical(&canon, num_args)
522    }
523
524    /// Look up an aggregate function by already-uppercased name (avoids allocation).
525    #[must_use]
526    pub fn find_aggregate_precanonical(
527        &self,
528        canonical: &str,
529        num_args: i32,
530    ) -> Option<Arc<ErasedAggregateFunction>> {
531        let exact = FunctionKey {
532            name: canonical.to_owned(),
533            num_args,
534        };
535        if let Some(f) = self.aggregates.get(&exact) {
536            debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "exact", "registry lookup");
537            return Some(Arc::clone(f));
538        }
539        let variadic = FunctionKey {
540            name: canonical.to_owned(),
541            num_args: -1,
542        };
543        if let Some(function) = self.aggregates.get(&variadic) {
544            if function.accepts_arg_count(num_args) {
545                debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "variadic", "registry lookup");
546                return Some(Arc::clone(function));
547            }
548            debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "wrong_arity", "registry lookup");
549            return Some(Arc::new(AggregateAdapter::new(
550                WrongArgCountAggregateFunction::new(canonical),
551            )));
552        }
553        if self.aggregates.keys().any(|key| key.name == canonical) {
554            debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "wrong_arity", "registry lookup");
555            return Some(Arc::new(AggregateAdapter::new(
556                WrongArgCountAggregateFunction::new(canonical),
557            )));
558        }
559        debug!(
560            name = %canonical,
561            arity = num_args,
562            kind = "aggregate",
563            hit = "miss",
564            "registry lookup"
565        );
566        None
567    }
568
569    /// Look up a window function by `(name, num_args)`.
570    ///
571    /// Tries exact match first, then falls back to variadic `(name, -1)`.
572    #[must_use]
573    pub fn find_window(&self, name: &str, num_args: i32) -> Option<Arc<ErasedWindowFunction>> {
574        let canon = canonical_name(name);
575        let exact = FunctionKey {
576            name: canon.clone(),
577            num_args,
578        };
579        if let Some(f) = self.windows.get(&exact) {
580            debug!(name = %canon, arity = num_args, kind = "window", hit = "exact", "registry lookup");
581            return Some(Arc::clone(f));
582        }
583        let variadic = FunctionKey {
584            name: canon.clone(),
585            num_args: -1,
586        };
587        if let Some(function) = self.windows.get(&variadic) {
588            if function.accepts_arg_count(num_args) {
589                debug!(name = %canon, arity = num_args, kind = "window", hit = "variadic", "registry lookup");
590                return Some(Arc::clone(function));
591            }
592            debug!(name = %canon, arity = num_args, kind = "window", hit = "wrong_arity", "registry lookup");
593            return Some(Arc::new(WindowAdapter::new(
594                WrongArgCountWindowFunction::new(&canon),
595            )));
596        }
597        if self.windows.keys().any(|key| key.name == canon) {
598            debug!(name = %canon, arity = num_args, kind = "window", hit = "wrong_arity", "registry lookup");
599            return Some(Arc::new(WindowAdapter::new(
600                WrongArgCountWindowFunction::new(&canon),
601            )));
602        }
603        debug!(
604            name = %canon,
605            arity = num_args,
606            kind = "window",
607            hit = "miss",
608            "registry lookup"
609        );
610        None
611    }
612
613    /// Whether the registry contains any scalar function with this name
614    /// (any arg count).
615    #[must_use]
616    pub fn contains_scalar(&self, name: &str) -> bool {
617        let canon = canonical_name(name);
618        self.scalars.keys().any(|k| k.name == canon)
619    }
620
621    /// Whether the registry contains any aggregate function with this name
622    /// (any arg count).
623    #[must_use]
624    pub fn contains_aggregate(&self, name: &str) -> bool {
625        let canon = canonical_name(name);
626        self.aggregates.keys().any(|k| k.name == canon)
627    }
628
629    /// Whether the registry contains any window function with this name
630    /// (any arg count).
631    #[must_use]
632    pub fn contains_window(&self, name: &str) -> bool {
633        let canon = canonical_name(name);
634        self.windows.keys().any(|k| k.name == canon)
635    }
636
637    /// Return whether a known window function accepts the SQL-visible arity.
638    ///
639    /// `None` means the name is not registered as a window function at all.
640    /// This is useful for callers that may execute optimized window paths
641    /// without invoking the returned function's `step()` method, where the
642    /// wrong-arity sentinel from `find_window` would otherwise be bypassed.
643    #[must_use]
644    pub fn window_accepts_arg_count(&self, name: &str, num_args: i32) -> Option<bool> {
645        let canon = canonical_name(name);
646        let exact = FunctionKey {
647            name: canon.clone(),
648            num_args,
649        };
650        if let Some(function) = self.windows.get(&exact) {
651            return Some(function.accepts_arg_count(num_args));
652        }
653
654        let variadic = FunctionKey {
655            name: canon.clone(),
656            num_args: -1,
657        };
658        if let Some(function) = self.windows.get(&variadic) {
659            return Some(function.accepts_arg_count(num_args));
660        }
661
662        self.windows
663            .keys()
664            .any(|key| key.name == canon)
665            .then_some(false)
666    }
667
668    /// Return deduplicated lowercase names of all registered aggregate functions.
669    ///
670    /// Used by the codegen thread-local to recognize custom aggregate UDFs.
671    #[must_use]
672    pub fn aggregate_names_lowercase(&self) -> Vec<String> {
673        let mut names: Vec<String> = self
674            .aggregates
675            .keys()
676            .map(|k| k.name.to_ascii_lowercase())
677            .collect();
678        names.sort();
679        names.dedup();
680        names
681    }
682}
683
684fn extend_builtin_surface_entries<'a>(
685    entries: &mut Vec<BuiltinFunctionSurfaceEntry>,
686    family: BuiltinFunctionFamily,
687    keys: impl Iterator<Item = &'a FunctionKey>,
688) {
689    for key in keys {
690        let name = key.name.to_ascii_lowercase();
691        let class = builtin_function_class(&name, family);
692        entries.push(BuiltinFunctionSurfaceEntry {
693            is_alias: builtin_function_alias_flag(&name, family),
694            surface_id: builtin_function_surface_id(family),
695            name,
696            num_args: key.num_args,
697            family,
698            class,
699        });
700    }
701}
702
703fn builtin_function_class(name: &str, family: BuiltinFunctionFamily) -> BuiltinFunctionClass {
704    match family {
705        BuiltinFunctionFamily::Aggregate => BuiltinFunctionClass::Aggregate,
706        BuiltinFunctionFamily::Window => BuiltinFunctionClass::Window,
707        BuiltinFunctionFamily::Scalar => {
708            if matches!(
709                name,
710                "acos"
711                    | "acosh"
712                    | "asin"
713                    | "asinh"
714                    | "atan"
715                    | "atan2"
716                    | "atanh"
717                    | "ceil"
718                    | "ceiling"
719                    | "cos"
720                    | "cosh"
721                    | "degrees"
722                    | "exp"
723                    | "floor"
724                    | "ln"
725                    | "log"
726                    | "log10"
727                    | "log2"
728                    | "mod"
729                    | "pi"
730                    | "pow"
731                    | "power"
732                    | "radians"
733                    | "sin"
734                    | "sinh"
735                    | "sqrt"
736                    | "tan"
737                    | "tanh"
738                    | "trunc"
739            ) {
740                BuiltinFunctionClass::MathScalar
741            } else if matches!(
742                name,
743                "date" | "datetime" | "julianday" | "strftime" | "time" | "timediff" | "unixepoch"
744            ) {
745                BuiltinFunctionClass::DateTimeScalar
746            } else {
747                BuiltinFunctionClass::CoreScalar
748            }
749        }
750    }
751}
752
753fn builtin_function_alias_flag(name: &str, family: BuiltinFunctionFamily) -> bool {
754    match family {
755        BuiltinFunctionFamily::Scalar => {
756            matches!(name, "ceiling" | "if" | "power" | "printf" | "substring")
757        }
758        BuiltinFunctionFamily::Aggregate | BuiltinFunctionFamily::Window => name == "string_agg",
759    }
760}
761
762const fn builtin_function_surface_id(family: BuiltinFunctionFamily) -> &'static str {
763    match family {
764        BuiltinFunctionFamily::Window => WINDOW_FUNCTION_SURFACE_ID,
765        BuiltinFunctionFamily::Scalar | BuiltinFunctionFamily::Aggregate => {
766            CORE_FUNCTION_SURFACE_ID
767        }
768    }
769}
770
771fn canonical_name(name: &str) -> String {
772    name.trim().to_ascii_uppercase()
773}
774
775#[cfg(test)]
776mod tests {
777    use std::collections::BTreeSet;
778
779    use fsqlite_types::SqliteValue;
780
781    use super::*;
782
783    fn runtime_registry_surface_keys() -> BTreeSet<(BuiltinFunctionFamily, String, i32)> {
784        let mut registry = FunctionRegistry::new();
785        register_builtins(&mut registry);
786        register_window_builtins(&mut registry);
787
788        let scalar_keys = registry
789            .scalars
790            .keys()
791            .map(|key| {
792                (
793                    BuiltinFunctionFamily::Scalar,
794                    key.name.to_ascii_lowercase(),
795                    key.num_args,
796                )
797            })
798            .collect::<BTreeSet<_>>();
799        let aggregate_keys = registry
800            .aggregates
801            .keys()
802            .map(|key| {
803                (
804                    BuiltinFunctionFamily::Aggregate,
805                    key.name.to_ascii_lowercase(),
806                    key.num_args,
807                )
808            })
809            .collect::<BTreeSet<_>>();
810        let window_keys = registry
811            .windows
812            .keys()
813            .map(|key| {
814                (
815                    BuiltinFunctionFamily::Window,
816                    key.name.to_ascii_lowercase(),
817                    key.num_args,
818                )
819            })
820            .collect::<BTreeSet<_>>();
821
822        scalar_keys
823            .into_iter()
824            .chain(aggregate_keys)
825            .chain(window_keys)
826            .collect()
827    }
828
829    fn inventory_surface_keys() -> BTreeSet<(BuiltinFunctionFamily, String, i32)> {
830        builtin_function_surface_inventory()
831            .iter()
832            .map(|entry| (entry.family, entry.name.clone(), entry.num_args))
833            .collect()
834    }
835
836    fn find_surface_entry(
837        family: BuiltinFunctionFamily,
838        name: &str,
839        num_args: i32,
840    ) -> &'static BuiltinFunctionSurfaceEntry {
841        builtin_function_surface_inventory()
842            .iter()
843            .find(|entry| {
844                entry.family == family && entry.name == name && entry.num_args == num_args
845            })
846            .unwrap_or_else(|| {
847                unreachable!(
848                    "missing builtin surface entry: family={} name={} arity={}",
849                    family.label(),
850                    name,
851                    num_args
852                )
853            })
854    }
855
856    #[test]
857    fn test_builtin_function_surface_inventory_matches_live_registry() {
858        let inventory = builtin_function_surface_inventory();
859        let inventory_keys = inventory_surface_keys();
860        let runtime_keys = runtime_registry_surface_keys();
861
862        assert_eq!(
863            inventory.len(),
864            inventory_keys.len(),
865            "inventory must not contain duplicate family/name/arity tuples"
866        );
867        assert_eq!(
868            inventory_keys, runtime_keys,
869            "inventory must exactly match the live registration path"
870        );
871        assert!(
872            inventory.windows(2).all(|entries| {
873                (
874                    entries[0].family,
875                    entries[0].class,
876                    &entries[0].name,
877                    entries[0].num_args,
878                ) <= (
879                    entries[1].family,
880                    entries[1].class,
881                    &entries[1].name,
882                    entries[1].num_args,
883                )
884            }),
885            "inventory must stay deterministically sorted"
886        );
887    }
888
889    #[test]
890    fn test_builtin_function_surface_inventory_classifies_representative_entries() {
891        let abs = find_surface_entry(BuiltinFunctionFamily::Scalar, "abs", 1);
892        assert_eq!(abs.class, BuiltinFunctionClass::CoreScalar);
893        assert!(!abs.is_alias);
894        assert_eq!(abs.surface_id, CORE_FUNCTION_SURFACE_ID);
895
896        let date = find_surface_entry(BuiltinFunctionFamily::Scalar, "date", -1);
897        assert_eq!(date.class, BuiltinFunctionClass::DateTimeScalar);
898        assert!(!date.is_alias);
899        assert_eq!(date.surface_id, CORE_FUNCTION_SURFACE_ID);
900
901        let power = find_surface_entry(BuiltinFunctionFamily::Scalar, "power", 2);
902        assert_eq!(power.class, BuiltinFunctionClass::MathScalar);
903        assert!(power.is_alias);
904        assert_eq!(power.surface_id, CORE_FUNCTION_SURFACE_ID);
905
906        let count = find_surface_entry(BuiltinFunctionFamily::Aggregate, "count", 0);
907        assert_eq!(count.class, BuiltinFunctionClass::Aggregate);
908        assert!(!count.is_alias);
909        assert_eq!(count.surface_id, CORE_FUNCTION_SURFACE_ID);
910
911        let row_number = find_surface_entry(BuiltinFunctionFamily::Window, "row_number", 0);
912        assert_eq!(row_number.class, BuiltinFunctionClass::Window);
913        assert!(!row_number.is_alias);
914        assert_eq!(row_number.surface_id, WINDOW_FUNCTION_SURFACE_ID);
915
916        let string_agg_window = find_surface_entry(BuiltinFunctionFamily::Window, "string_agg", 2);
917        assert_eq!(string_agg_window.class, BuiltinFunctionClass::Window);
918        assert!(string_agg_window.is_alias);
919        assert_eq!(string_agg_window.surface_id, WINDOW_FUNCTION_SURFACE_ID);
920    }
921
922    // -- Mock: double(x) -> x * 2, fixed 1-arg --
923
924    struct Double;
925
926    impl ScalarFunction for Double {
927        fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
928            Ok(SqliteValue::Integer(args[0].to_integer() * 2))
929        }
930
931        fn num_args(&self) -> i32 {
932            1
933        }
934
935        fn name(&self) -> &str {
936            "double"
937        }
938    }
939
940    // -- Mock: variadic concat --
941
942    struct VariadicConcat;
943
944    impl ScalarFunction for VariadicConcat {
945        fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
946            let mut out = String::new();
947            for a in args {
948                out.push_str(&a.to_text());
949            }
950            Ok(SqliteValue::Text(out.into()))
951        }
952
953        fn num_args(&self) -> i32 {
954            -1
955        }
956
957        fn min_args(&self) -> i32 {
958            1
959        }
960
961        fn max_args(&self) -> Option<i32> {
962            Some(3)
963        }
964
965        fn name(&self) -> &str {
966            "my_func"
967        }
968    }
969
970    // -- Mock: fixed 2-arg version of same name --
971
972    struct TwoArgFunc;
973
974    impl ScalarFunction for TwoArgFunc {
975        fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
976            Ok(SqliteValue::Integer(
977                args[0].to_integer() + args[1].to_integer(),
978            ))
979        }
980
981        fn num_args(&self) -> i32 {
982            2
983        }
984
985        fn name(&self) -> &str {
986            "my_func"
987        }
988    }
989
990    fn assert_wrong_arg_count(
991        function: &dyn ScalarFunction,
992        args: &[SqliteValue],
993        expected_name: &str,
994    ) {
995        let err = function.invoke(args).expect_err("wrong arity should fail");
996        let expected = format!("wrong number of arguments to function {expected_name}()");
997        assert!(
998            matches!(&err, FrankenError::FunctionError(message) if message == &expected),
999            "expected {expected:?}, got {err:?}"
1000        );
1001    }
1002
1003    fn assert_wrong_arg_count_aggregate(
1004        function: &ErasedAggregateFunction,
1005        args: &[SqliteValue],
1006        expected_name: &str,
1007    ) {
1008        let mut state = function.initial_state();
1009        let err = function
1010            .step(&mut state, args)
1011            .expect_err("wrong aggregate arity should fail");
1012        let expected = format!("wrong number of arguments to function {expected_name}()");
1013        assert!(
1014            matches!(&err, FrankenError::FunctionError(message) if message == &expected),
1015            "expected {expected:?}, got {err:?}"
1016        );
1017    }
1018
1019    fn assert_wrong_arg_count_window(
1020        function: &ErasedWindowFunction,
1021        args: &[SqliteValue],
1022        expected_name: &str,
1023    ) {
1024        let mut state = function.initial_state();
1025        let err = function
1026            .step(&mut state, args)
1027            .expect_err("wrong window arity should fail");
1028        let expected = format!("wrong number of arguments to function {expected_name}()");
1029        assert!(
1030            matches!(&err, FrankenError::FunctionError(message) if message == &expected),
1031            "expected {expected:?}, got {err:?}"
1032        );
1033    }
1034
1035    struct Product;
1036
1037    impl AggregateFunction for Product {
1038        type State = i64;
1039
1040        fn initial_state(&self) -> Self::State {
1041            1
1042        }
1043
1044        fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> fsqlite_error::Result<()> {
1045            *state *= args[0].to_integer();
1046            Ok(())
1047        }
1048
1049        fn finalize(&self, state: Self::State) -> fsqlite_error::Result<SqliteValue> {
1050            Ok(SqliteValue::Integer(state))
1051        }
1052
1053        fn num_args(&self) -> i32 {
1054            1
1055        }
1056
1057        fn name(&self) -> &str {
1058            "product"
1059        }
1060    }
1061
1062    struct MovingSum;
1063
1064    impl WindowFunction for MovingSum {
1065        type State = i64;
1066
1067        fn initial_state(&self) -> Self::State {
1068            0
1069        }
1070
1071        fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> fsqlite_error::Result<()> {
1072            *state += args[0].to_integer();
1073            Ok(())
1074        }
1075
1076        fn inverse(
1077            &self,
1078            state: &mut Self::State,
1079            args: &[SqliteValue],
1080        ) -> fsqlite_error::Result<()> {
1081            *state -= args[0].to_integer();
1082            Ok(())
1083        }
1084
1085        fn value(&self, state: &Self::State) -> fsqlite_error::Result<SqliteValue> {
1086            Ok(SqliteValue::Integer(*state))
1087        }
1088
1089        fn finalize(&self, state: Self::State) -> fsqlite_error::Result<SqliteValue> {
1090            Ok(SqliteValue::Integer(state))
1091        }
1092
1093        fn num_args(&self) -> i32 {
1094            1
1095        }
1096
1097        fn name(&self) -> &str {
1098            "moving_sum"
1099        }
1100    }
1101
1102    #[test]
1103    fn test_registry_register_scalar() {
1104        let mut registry = FunctionRegistry::new();
1105        let previous = registry.register_scalar(Double);
1106        assert!(previous.is_none());
1107        assert!(registry.contains_scalar("double"));
1108        assert!(registry.contains_scalar("DOUBLE"));
1109        let f = registry
1110            .find_scalar(" Double ", 1)
1111            .expect("double registered");
1112        assert_eq!(
1113            f.invoke(&[SqliteValue::Integer(21)])
1114                .expect("invoke succeeds"),
1115            SqliteValue::Integer(42)
1116        );
1117    }
1118
1119    #[test]
1120    fn test_registry_case_insensitive_lookup() {
1121        let mut registry = FunctionRegistry::new();
1122        registry.register_scalar(Double);
1123
1124        // Register as "double", look up as "DOUBLE", "Double", " double "
1125        assert!(registry.find_scalar("DOUBLE", 1).is_some());
1126        assert!(registry.find_scalar("Double", 1).is_some());
1127        assert!(registry.find_scalar(" double ", 1).is_some());
1128    }
1129
1130    #[test]
1131    fn test_registry_overwrite() {
1132        let mut registry = FunctionRegistry::new();
1133
1134        // Register first version
1135        let prev = registry.register_scalar(Double);
1136        assert!(prev.is_none());
1137
1138        // Register second version with same (name, num_args) — overwrites
1139        let prev = registry.register_scalar(Double);
1140        assert!(prev.is_some());
1141
1142        // Still works
1143        let f = registry.find_scalar("double", 1).unwrap();
1144        assert_eq!(
1145            f.invoke(&[SqliteValue::Integer(5)]).unwrap(),
1146            SqliteValue::Integer(10)
1147        );
1148    }
1149
1150    #[test]
1151    fn test_registry_variadic_fallback() {
1152        let mut registry = FunctionRegistry::new();
1153
1154        // Register only the variadic version (num_args = -1)
1155        registry.register_scalar(VariadicConcat);
1156
1157        let too_few = registry
1158            .find_scalar("my_func", 0)
1159            .expect("known function with bad arity returns erroring scalar");
1160        assert_wrong_arg_count(too_few.as_ref(), &[], "my_func");
1161
1162        // Look up with specific arg count — no exact match, falls back to variadic
1163        let f = registry
1164            .find_scalar("my_func", 3)
1165            .expect("variadic fallback");
1166        assert_eq!(
1167            f.invoke(&[
1168                SqliteValue::Text("a".into()),
1169                SqliteValue::Text("b".into()),
1170                SqliteValue::Text("c".into()),
1171            ])
1172            .unwrap(),
1173            SqliteValue::Text("abc".into())
1174        );
1175        let too_many = registry
1176            .find_scalar("my_func", 4)
1177            .expect("known function with bad arity returns erroring scalar");
1178        assert_wrong_arg_count(
1179            too_many.as_ref(),
1180            &[
1181                SqliteValue::Null,
1182                SqliteValue::Null,
1183                SqliteValue::Null,
1184                SqliteValue::Null,
1185            ],
1186            "my_func",
1187        );
1188    }
1189
1190    #[test]
1191    fn test_registry_exact_wrong_arity_returns_function_error() {
1192        let mut registry = FunctionRegistry::new();
1193        registry.register_scalar(Double);
1194
1195        let f = registry
1196            .find_scalar("double", 2)
1197            .expect("known function with wrong arity returns erroring scalar");
1198        assert_wrong_arg_count(
1199            f.as_ref(),
1200            &[SqliteValue::Integer(1), SqliteValue::Integer(2)],
1201            "double",
1202        );
1203    }
1204
1205    #[test]
1206    fn test_registry_exact_match_over_variadic() {
1207        let mut registry = FunctionRegistry::new();
1208
1209        // Register both variadic (num_args=-1) and exact 2-arg version
1210        registry.register_scalar(VariadicConcat);
1211        registry.register_scalar(TwoArgFunc);
1212
1213        // Look up with num_args=2 — exact match wins over variadic
1214        let f = registry
1215            .find_scalar("my_func", 2)
1216            .expect("exact match found");
1217        assert_eq!(
1218            f.invoke(&[SqliteValue::Integer(10), SqliteValue::Integer(32)])
1219                .unwrap(),
1220            SqliteValue::Integer(42)
1221        );
1222
1223        // Look up with num_args=3 — no exact match, falls back to variadic
1224        let f = registry
1225            .find_scalar("my_func", 3)
1226            .expect("variadic fallback");
1227        assert_eq!(f.num_args(), -1);
1228    }
1229
1230    #[test]
1231    fn test_registry_not_found_returns_none() {
1232        let registry = FunctionRegistry::new();
1233        assert!(registry.find_scalar("nonexistent", 1).is_none());
1234        assert!(registry.find_aggregate("nonexistent", 1).is_none());
1235        assert!(registry.find_window("nonexistent", 1).is_none());
1236    }
1237
1238    #[test]
1239    fn test_registry_register_and_resolve_aggregate() {
1240        let mut registry = FunctionRegistry::new();
1241        let previous = registry.register_aggregate(Product);
1242        assert!(previous.is_none());
1243        assert!(registry.contains_aggregate("product"));
1244        let f = registry
1245            .find_aggregate("PRODUCT", 1)
1246            .expect("product aggregate registered");
1247
1248        let mut state = f.initial_state();
1249        f.step(&mut state, &[SqliteValue::Integer(2)])
1250            .expect("step 1");
1251        f.step(&mut state, &[SqliteValue::Integer(3)])
1252            .expect("step 2");
1253        f.step(&mut state, &[SqliteValue::Integer(7)])
1254            .expect("step 3");
1255
1256        assert_eq!(
1257            f.finalize(state).expect("finalize succeeds"),
1258            SqliteValue::Integer(42)
1259        );
1260    }
1261
1262    #[test]
1263    fn test_registry_aggregate_type_erased() {
1264        let mut registry = FunctionRegistry::new();
1265        registry.register_aggregate(Product);
1266
1267        // Round-trip through type-erased registry
1268        let f = registry
1269            .find_aggregate("product", 1)
1270            .expect("product found");
1271        let mut state = f.initial_state();
1272        f.step(&mut state, &[SqliteValue::Integer(6)]).unwrap();
1273        f.step(&mut state, &[SqliteValue::Integer(7)]).unwrap();
1274        assert_eq!(f.finalize(state).unwrap(), SqliteValue::Integer(42));
1275        assert_eq!(f.name(), "product");
1276    }
1277
1278    #[test]
1279    fn test_registry_aggregate_wrong_arity_returns_function_error() {
1280        let mut registry = FunctionRegistry::new();
1281        registry.register_aggregate(Product);
1282
1283        let f = registry
1284            .find_aggregate("product", 0)
1285            .expect("known aggregate with wrong arity returns erroring aggregate");
1286        assert_wrong_arg_count_aggregate(f.as_ref(), &[], "product");
1287    }
1288
1289    #[test]
1290    fn test_registry_register_and_resolve_window() {
1291        let mut registry = FunctionRegistry::new();
1292        let previous = registry.register_window(MovingSum);
1293        assert!(previous.is_none());
1294        assert!(registry.contains_window("moving_sum"));
1295        let f = registry
1296            .find_window("MOVING_SUM", 1)
1297            .expect("moving_sum window registered");
1298
1299        let mut state = f.initial_state();
1300        f.step(&mut state, &[SqliteValue::Integer(10)])
1301            .expect("step 1");
1302        f.step(&mut state, &[SqliteValue::Integer(20)])
1303            .expect("step 2");
1304        f.step(&mut state, &[SqliteValue::Integer(30)])
1305            .expect("step 3");
1306        assert_eq!(f.value(&state).expect("value"), SqliteValue::Integer(60));
1307
1308        f.inverse(&mut state, &[SqliteValue::Integer(10)])
1309            .expect("inverse 1");
1310        f.step(&mut state, &[SqliteValue::Integer(40)])
1311            .expect("step 4");
1312        assert_eq!(f.value(&state).expect("value"), SqliteValue::Integer(90));
1313    }
1314
1315    #[test]
1316    fn test_registry_window_wrong_arity_returns_function_error() {
1317        let mut registry = FunctionRegistry::new();
1318        registry.register_window(MovingSum);
1319
1320        let f = registry
1321            .find_window("moving_sum", 0)
1322            .expect("known window with wrong arity returns erroring window");
1323        assert_wrong_arg_count_window(f.as_ref(), &[], "moving_sum");
1324    }
1325
1326    #[test]
1327    fn test_registry_window_accepts_arg_count_reports_known_bad_arity() {
1328        let mut registry = FunctionRegistry::new();
1329        registry.register_window(MovingSum);
1330
1331        assert_eq!(
1332            registry.window_accepts_arg_count("moving_sum", 1),
1333            Some(true)
1334        );
1335        assert_eq!(
1336            registry.window_accepts_arg_count("moving_sum", 0),
1337            Some(false)
1338        );
1339        assert_eq!(registry.window_accepts_arg_count("missing_window", 1), None);
1340    }
1341
1342    #[test]
1343    fn test_registry_window_type_erased() {
1344        let mut registry = FunctionRegistry::new();
1345        registry.register_window(MovingSum);
1346
1347        let f = registry
1348            .find_window("moving_sum", 1)
1349            .expect("moving_sum found");
1350
1351        // Full lifecycle: initial_state -> step -> inverse -> value -> finalize
1352        let mut state = f.initial_state();
1353        f.step(&mut state, &[SqliteValue::Integer(100)]).unwrap();
1354        assert_eq!(f.value(&state).unwrap(), SqliteValue::Integer(100));
1355
1356        f.inverse(&mut state, &[SqliteValue::Integer(100)]).unwrap();
1357        assert_eq!(f.value(&state).unwrap(), SqliteValue::Integer(0));
1358
1359        f.step(&mut state, &[SqliteValue::Integer(42)]).unwrap();
1360        assert_eq!(f.finalize(state).unwrap(), SqliteValue::Integer(42));
1361    }
1362
1363    #[test]
1364    fn test_function_key_equality() {
1365        let k1 = FunctionKey::new("ABS", 1);
1366        let k2 = FunctionKey::new("abs", 1);
1367        let k3 = FunctionKey::new("ABS", 2);
1368
1369        assert_eq!(k1, k2, "case-insensitive equality");
1370        assert_ne!(k1, k3, "different num_args");
1371    }
1372
1373    // ── E2E: bd-1dc9 ────────────────────────────────────────────────────
1374
1375    #[test]
1376    fn test_e2e_custom_collation_in_order_by() {
1377        use collation::{BinaryCollation, CollationFunction, NoCaseCollation, RtrimCollation};
1378
1379        // Simulate ORDER BY with a custom reverse-alphabetical collation.
1380        struct ReverseAlpha;
1381
1382        impl CollationFunction for ReverseAlpha {
1383            fn name(&self) -> &str {
1384                "REVERSE_ALPHA"
1385            }
1386
1387            fn compare(&self, left: &[u8], right: &[u8]) -> std::cmp::Ordering {
1388                // Reverse of BINARY
1389                right.cmp(left)
1390            }
1391        }
1392
1393        let coll = ReverseAlpha;
1394        let mut data: Vec<&[u8]> = vec![b"banana", b"apple", b"cherry", b"date"];
1395        data.sort_by(|a, b| coll.compare(a, b));
1396
1397        // Reverse alphabetical: date > cherry > banana > apple
1398        let expected: Vec<&[u8]> = vec![b"date", b"cherry", b"banana", b"apple"];
1399        assert_eq!(data, expected);
1400        assert_eq!(coll.name(), "REVERSE_ALPHA");
1401
1402        // Verify built-in collations are usable as trait objects.
1403        let collations: Vec<Box<dyn CollationFunction>> = vec![
1404            Box::new(BinaryCollation),
1405            Box::new(NoCaseCollation),
1406            Box::new(RtrimCollation),
1407            Box::new(ReverseAlpha),
1408        ];
1409        assert_eq!(collations.len(), 4);
1410
1411        // Sort with BINARY: normal alphabetical
1412        let mut binary_sorted = data.clone();
1413        binary_sorted.sort_by(|a, b| collations[0].compare(a, b));
1414        assert_eq!(binary_sorted[0], b"apple");
1415    }
1416
1417    #[test]
1418    fn test_e2e_authorizer_sandboxing() {
1419        use authorizer::{AuthAction, AuthResult, Authorizer};
1420
1421        // Authorizer that denies INSERT/UPDATE/DELETE but allows SELECT.
1422        struct SelectOnlyAuthorizer;
1423
1424        impl Authorizer for SelectOnlyAuthorizer {
1425            fn authorize(
1426                &self,
1427                action: AuthAction,
1428                _arg1: Option<&str>,
1429                arg2: Option<&str>,
1430                _db_name: Option<&str>,
1431                _trigger: Option<&str>,
1432            ) -> AuthResult {
1433                match action {
1434                    AuthAction::Select | AuthAction::Read => {
1435                        // Ignore the "secret" column (replaced with NULL)
1436                        if action == AuthAction::Read && arg2 == Some("secret") {
1437                            return AuthResult::Ignore;
1438                        }
1439                        AuthResult::Ok
1440                    }
1441                    AuthAction::Insert | AuthAction::Update | AuthAction::Delete => {
1442                        AuthResult::Deny
1443                    }
1444                    _ => AuthResult::Ok,
1445                }
1446            }
1447        }
1448
1449        let auth = SelectOnlyAuthorizer;
1450
1451        // SELECT is allowed at compile time.
1452        assert_eq!(
1453            auth.authorize(AuthAction::Select, None, None, Some("main"), None),
1454            AuthResult::Ok,
1455            "SELECT must be allowed"
1456        );
1457
1458        // INSERT is denied at compile time.
1459        assert_eq!(
1460            auth.authorize(AuthAction::Insert, Some("users"), None, Some("main"), None),
1461            AuthResult::Deny,
1462            "INSERT must be denied (compile-time auth error)"
1463        );
1464
1465        // UPDATE is denied.
1466        assert_eq!(
1467            auth.authorize(
1468                AuthAction::Update,
1469                Some("users"),
1470                Some("email"),
1471                Some("main"),
1472                None
1473            ),
1474            AuthResult::Deny,
1475        );
1476
1477        // DELETE is denied.
1478        assert_eq!(
1479            auth.authorize(AuthAction::Delete, Some("users"), None, Some("main"), None),
1480            AuthResult::Deny,
1481        );
1482
1483        // Read on "secret" column returns Ignore (nullify).
1484        assert_eq!(
1485            auth.authorize(
1486                AuthAction::Read,
1487                Some("users"),
1488                Some("secret"),
1489                Some("main"),
1490                None
1491            ),
1492            AuthResult::Ignore,
1493            "Ignore must nullify column"
1494        );
1495
1496        // Read on normal column is allowed.
1497        assert_eq!(
1498            auth.authorize(
1499                AuthAction::Read,
1500                Some("users"),
1501                Some("name"),
1502                Some("main"),
1503                None
1504            ),
1505            AuthResult::Ok,
1506        );
1507    }
1508
1509    #[test]
1510    fn test_e2e_function_registry_resolution() {
1511        // Register abs(1 arg) and a variadic version, then test resolution.
1512        struct Abs1;
1513
1514        impl ScalarFunction for Abs1 {
1515            fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
1516                Ok(SqliteValue::Integer(args[0].to_integer().abs()))
1517            }
1518
1519            fn num_args(&self) -> i32 {
1520                1
1521            }
1522
1523            fn name(&self) -> &str {
1524                "abs"
1525            }
1526        }
1527
1528        struct AbsVariadic;
1529
1530        impl ScalarFunction for AbsVariadic {
1531            fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
1532                // Variadic: return sum of absolute values
1533                let sum: i64 = args.iter().map(|a| a.to_integer().abs()).sum();
1534                Ok(SqliteValue::Integer(sum))
1535            }
1536
1537            fn num_args(&self) -> i32 {
1538                -1
1539            }
1540
1541            fn name(&self) -> &str {
1542                "abs"
1543            }
1544        }
1545
1546        let mut registry = FunctionRegistry::new();
1547        registry.register_scalar(Abs1);
1548        registry.register_scalar(AbsVariadic);
1549
1550        // SELECT abs(-5) should use 1-arg version.
1551        let f = registry.find_scalar("abs", 1).expect("abs(1) found");
1552        assert_eq!(f.num_args(), 1, "exact 1-arg match");
1553        assert_eq!(
1554            f.invoke(&[SqliteValue::Integer(-5)]).unwrap(),
1555            SqliteValue::Integer(5)
1556        );
1557
1558        // SELECT abs(-5, -3) should fall through to variadic.
1559        let f = registry.find_scalar("abs", 2).expect("abs variadic found");
1560        assert_eq!(f.num_args(), -1, "variadic fallback for 2 args");
1561        assert_eq!(
1562            f.invoke(&[SqliteValue::Integer(-5), SqliteValue::Integer(-3)])
1563                .unwrap(),
1564            SqliteValue::Integer(8)
1565        );
1566
1567        // Nonexistent function returns None.
1568        assert!(registry.find_scalar("nonexistent", 1).is_none());
1569    }
1570
1571    #[test]
1572    fn test_authorizer_called_at_compile_time() {
1573        use authorizer::{AuthAction, AuthResult, Authorizer};
1574        use std::sync::Mutex;
1575
1576        // Track every authorize call to verify compile-time invocation pattern.
1577        struct TrackingAuthorizer {
1578            calls: Mutex<Vec<AuthAction>>,
1579        }
1580
1581        impl TrackingAuthorizer {
1582            fn new() -> Self {
1583                Self {
1584                    calls: Mutex::new(Vec::new()),
1585                }
1586            }
1587        }
1588
1589        impl Authorizer for TrackingAuthorizer {
1590            fn authorize(
1591                &self,
1592                action: AuthAction,
1593                _arg1: Option<&str>,
1594                _arg2: Option<&str>,
1595                _db_name: Option<&str>,
1596                _trigger: Option<&str>,
1597            ) -> AuthResult {
1598                self.calls.lock().unwrap().push(action);
1599                AuthResult::Ok
1600            }
1601        }
1602
1603        let auth = TrackingAuthorizer::new();
1604
1605        // Simulate compile-time authorization for:
1606        // `SELECT name, email FROM users WHERE id = ?`
1607        //
1608        // The authorizer is called during prepare(), NOT during step().
1609        // Expected calls:
1610        //   1. Select (the statement type)
1611        //   2. Read(users, name)
1612        //   3. Read(users, email)
1613        //   4. Read(users, id)    -- WHERE clause column
1614
1615        // Phase 1: prepare (compile time) — authorizer is called
1616        auth.authorize(AuthAction::Select, None, None, Some("main"), None);
1617        auth.authorize(
1618            AuthAction::Read,
1619            Some("users"),
1620            Some("name"),
1621            Some("main"),
1622            None,
1623        );
1624        auth.authorize(
1625            AuthAction::Read,
1626            Some("users"),
1627            Some("email"),
1628            Some("main"),
1629            None,
1630        );
1631        auth.authorize(
1632            AuthAction::Read,
1633            Some("users"),
1634            Some("id"),
1635            Some("main"),
1636            None,
1637        );
1638
1639        let calls = auth.calls.lock().unwrap();
1640        assert_eq!(calls.len(), 4, "authorizer called 4 times during prepare");
1641        assert_eq!(calls[0], AuthAction::Select);
1642        assert_eq!(calls[1], AuthAction::Read);
1643        assert_eq!(calls[2], AuthAction::Read);
1644        assert_eq!(calls[3], AuthAction::Read);
1645        drop(calls);
1646
1647        // Phase 2: step (execution) — authorizer is NOT called again
1648        // (In a real implementation, step() would not invoke authorize.)
1649        // We simply verify no additional calls were recorded.
1650        let calls_after = auth.calls.lock().unwrap();
1651        assert_eq!(
1652            calls_after.len(),
1653            4,
1654            "authorizer must NOT be called during step/execution"
1655        );
1656        drop(calls_after);
1657    }
1658}