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,
105    set_statement_text_encoding, sqlite_compile_options, sqlite_compileoption_used,
106    statement_text_encoding,
107};
108pub use collation::{
109    BinaryCollation, CollationAnnotation, CollationFunction, CollationRegistry, CollationSource,
110    NoCaseCollation, RtrimCollation, resolve_collation,
111};
112pub use datetime::register_datetime_builtins;
113pub use math::register_math_builtins;
114pub use scalar::{JSON_SUBTYPE, ScalarFunction};
115pub use vtab::{
116    ColumnContext, ConstraintOp, IndexConstraint, IndexConstraintUsage, IndexInfo, IndexOrderBy,
117    VirtualTable, VirtualTableCursor,
118};
119pub use window::{WindowAdapter, WindowFunction};
120pub use window_builtins::register_window_builtins;
121
122/// Top-level function family exposed by the runtime registry.
123#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
124pub enum BuiltinFunctionFamily {
125    Scalar,
126    Aggregate,
127    Window,
128}
129
130impl BuiltinFunctionFamily {
131    #[must_use]
132    pub const fn label(self) -> &'static str {
133        match self {
134            Self::Scalar => "scalar",
135            Self::Aggregate => "aggregate",
136            Self::Window => "window",
137        }
138    }
139}
140
141/// Track-E built-in function class used for parity closure accounting.
142#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
143pub enum BuiltinFunctionClass {
144    CoreScalar,
145    MathScalar,
146    DateTimeScalar,
147    Aggregate,
148    Window,
149}
150
151impl BuiltinFunctionClass {
152    #[must_use]
153    pub const fn label(self) -> &'static str {
154        match self {
155            Self::CoreScalar => "core_scalar",
156            Self::MathScalar => "math_scalar",
157            Self::DateTimeScalar => "datetime_scalar",
158            Self::Aggregate => "aggregate",
159            Self::Window => "window",
160        }
161    }
162}
163
164/// Runtime-authoritative description of one built-in function registration.
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct BuiltinFunctionSurfaceEntry {
167    /// Lowercase SQL function name as exposed by the runtime registry.
168    pub name: String,
169    /// Declared arity, or `-1` for variadic registrations.
170    pub num_args: i32,
171    /// Top-level function family.
172    pub family: BuiltinFunctionFamily,
173    /// Track-E parity classification bucket.
174    pub class: BuiltinFunctionClass,
175    /// Whether this entry is an alternate spelling over another runtime entry.
176    pub is_alias: bool,
177    /// Canonical parity surface identifier for this function family.
178    pub surface_id: &'static str,
179}
180
181const CORE_FUNCTION_SURFACE_ID: &str = "SURF-FUNC-CORE-011";
182const WINDOW_FUNCTION_SURFACE_ID: &str = "SURF-FUNC-WINDOW-012";
183
184/// Return the runtime-authoritative built-in function surface inventory.
185///
186/// The inventory is derived from the actual registration path in this crate
187/// rather than from harness-side matrices so Track E docs and future parity
188/// checks can reuse one stable source of truth.
189#[must_use]
190pub fn builtin_function_surface_inventory() -> &'static [BuiltinFunctionSurfaceEntry] {
191    static INVENTORY: OnceLock<Vec<BuiltinFunctionSurfaceEntry>> = OnceLock::new();
192    INVENTORY
193        .get_or_init(|| {
194            let mut registry = FunctionRegistry::new();
195            register_builtins(&mut registry);
196            register_window_builtins(&mut registry);
197
198            let mut entries = Vec::with_capacity(
199                registry.scalars.len() + registry.aggregates.len() + registry.windows.len(),
200            );
201            extend_builtin_surface_entries(
202                &mut entries,
203                BuiltinFunctionFamily::Scalar,
204                registry.scalars.keys(),
205            );
206            extend_builtin_surface_entries(
207                &mut entries,
208                BuiltinFunctionFamily::Aggregate,
209                registry.aggregates.keys(),
210            );
211            extend_builtin_surface_entries(
212                &mut entries,
213                BuiltinFunctionFamily::Window,
214                registry.windows.keys(),
215            );
216            entries.sort_by(|left, right| {
217                (left.family, left.class, &left.name, left.num_args).cmp(&(
218                    right.family,
219                    right.class,
220                    &right.name,
221                    right.num_args,
222                ))
223            });
224            entries
225        })
226        .as_slice()
227}
228
229/// Type-erased aggregate function object used by the registry.
230pub type ErasedAggregateFunction = dyn AggregateFunction<State = Box<dyn Any + Send>>;
231
232/// Type-erased window function object used by the registry.
233pub type ErasedWindowFunction = dyn WindowFunction<State = Box<dyn Any + Send>>;
234
235/// Composite lookup key for functions: `(UPPERCASE name, num_args)`.
236///
237/// `-1` for `num_args` means variadic (any number of arguments).
238/// Names are stored as uppercase ASCII for case-insensitive matching.
239#[derive(Debug, Clone, Hash, Eq, PartialEq)]
240pub struct FunctionKey {
241    /// Function name, stored as uppercase ASCII.
242    name: String,
243    /// Expected argument count, or `-1` for variadic.
244    num_args: i32,
245}
246
247impl FunctionKey {
248    /// Create a new function key with the name canonicalized to uppercase.
249    #[must_use]
250    pub fn new(name: &str, num_args: i32) -> Self {
251        assert_valid_declared_args(num_args);
252        Self {
253            name: canonical_name(name),
254            num_args,
255        }
256    }
257}
258
259fn assert_valid_declared_args(num_args: i32) {
260    assert!(
261        num_args >= -1,
262        "function argument count must be -1 or non-negative"
263    );
264}
265
266/// Immutable SQL-visible argument-count contract for a registered function.
267///
268/// Construct this once from user metadata and publish it alongside the
269/// function object. Runtime lookup uses only this value, never re-entering
270/// user-defined metadata callbacks.
271#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
272#[allow(clippy::struct_field_names)]
273pub struct FunctionArity {
274    declared_args: i32,
275    min_args: i32,
276    max_args: Option<i32>,
277}
278
279impl FunctionArity {
280    /// Construct an exact-arity contract.
281    #[must_use]
282    pub fn exact(num_args: i32) -> Self {
283        assert!(num_args >= 0, "exact function arity must be non-negative");
284        Self {
285            declared_args: num_args,
286            min_args: num_args,
287            max_args: Some(num_args),
288        }
289    }
290
291    /// Construct a variadic contract with inclusive argument-count bounds.
292    #[must_use]
293    pub fn variadic(min_args: i32, max_args: Option<i32>) -> Self {
294        assert!(min_args >= 0, "minimum function arity must be non-negative");
295        assert!(
296            max_args.is_none_or(|max| max >= min_args),
297            "maximum function arity must not be below its minimum"
298        );
299        Self {
300            declared_args: -1,
301            min_args,
302            max_args,
303        }
304    }
305
306    /// Registry key arity (`-1` for a variadic contract).
307    #[must_use]
308    pub const fn declared_args(self) -> i32 {
309        self.declared_args
310    }
311
312    /// Minimum accepted SQL-visible argument count.
313    #[must_use]
314    pub const fn min_args(self) -> i32 {
315        self.min_args
316    }
317
318    /// Maximum accepted SQL-visible argument count, or `None` when unbounded.
319    #[must_use]
320    pub const fn max_args(self) -> Option<i32> {
321        self.max_args
322    }
323
324    /// Whether this contract accepts `num_args` SQL-visible arguments.
325    #[must_use]
326    pub fn accepts(self, num_args: i32) -> bool {
327        num_args >= self.min_args && self.max_args.is_none_or(|max| num_args <= max)
328    }
329
330    pub(crate) fn from_declared_args(
331        declared_args: i32,
332        variadic_bounds: impl FnOnce() -> (i32, Option<i32>),
333    ) -> Self {
334        assert_valid_declared_args(declared_args);
335        if declared_args == -1 {
336            let (min_args, max_args) = variadic_bounds();
337            Self::variadic(min_args, max_args)
338        } else {
339            Self::exact(declared_args)
340        }
341    }
342}
343
344/// Kind of application-defined function selected for one SQL call.
345///
346/// Application registrations share one namespace across scalar, aggregate,
347/// and window functions. A window registration is also callable through the
348/// ordinary aggregate form, but remains distinguishable here so callers can
349/// validate whether an `OVER` clause is legal.
350#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
351pub enum ApplicationFunctionKind {
352    /// A scalar function evaluated once per input row.
353    Scalar,
354    /// An ordinary aggregate function evaluated once per group.
355    Aggregate,
356    /// A window function, callable both as an aggregate and with `OVER`.
357    Window,
358}
359
360impl ApplicationFunctionKind {
361    /// Lowercase SQL-facing label used in misuse diagnostics.
362    #[must_use]
363    pub const fn label(self) -> &'static str {
364        match self {
365            Self::Scalar => "scalar",
366            Self::Aggregate => "aggregate",
367            Self::Window => "window",
368        }
369    }
370
371    /// Whether the selected registration has an ordinary aggregate call form.
372    #[must_use]
373    pub const fn is_aggregate_callable(self) -> bool {
374        matches!(self, Self::Aggregate | Self::Window)
375    }
376
377    /// Whether the selected registration may be used with `OVER`.
378    #[must_use]
379    pub const fn is_window_callable(self) -> bool {
380        matches!(self, Self::Window)
381    }
382}
383
384/// Frozen application-overload resolution for one SQL-visible call arity.
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub struct ApplicationFunctionResolution {
387    kind: ApplicationFunctionKind,
388    arity: FunctionArity,
389}
390
391impl ApplicationFunctionResolution {
392    /// Selected function kind.
393    #[must_use]
394    pub const fn kind(self) -> ApplicationFunctionKind {
395        self.kind
396    }
397
398    /// Frozen arity contract belonging to the selected registration.
399    #[must_use]
400    pub const fn arity(self) -> FunctionArity {
401        self.arity
402    }
403}
404
405fn assert_key_matches_arity(key: &FunctionKey, arity: FunctionArity) {
406    assert_eq!(
407        key.num_args,
408        arity.declared_args(),
409        "function key and frozen arity contract must have the same declared argument count"
410    );
411}
412
413/// Frozen policy governing use of a scalar function in schema-maintained
414/// expressions such as indexes, generated columns, and CHECK constraints.
415///
416/// This metadata belongs to the registry entry, not the open
417/// [`ScalarFunction`] trait object. Consequently a user-defined function can
418/// neither re-enter registration nor contradict the explicit deterministic /
419/// non-deterministic API while rows are being evaluated.
420#[derive(Debug, Clone, Copy, PartialEq, Eq)]
421pub enum ScalarSchemaSafety {
422    /// Every invocation is stable for the lifetime of the schema.
423    Always,
424    /// No invocation is permitted in a schema-maintained expression.
425    Never,
426    /// The sealed built-in date/time classifier must inspect evaluated values.
427    DateTimeConditional,
428}
429
430impl ScalarSchemaSafety {
431    const fn from_deterministic(deterministic: bool) -> Self {
432        if deterministic {
433            Self::Always
434        } else {
435            Self::Never
436        }
437    }
438}
439
440/// Frozen policy governing whether a scalar call is constant for one query.
441///
442/// This is distinct from [`ScalarSchemaSafety`]: SQLite's slow-changing
443/// built-ins are stable for one statement and can therefore be factored out of
444/// inner loops, but they are not safe in schema-maintained expressions. The
445/// registry derives this metadata from public deterministic registration APIs;
446/// only sealed, crate-private built-in registration paths can publish
447/// [`Self::SlowChanging`].
448#[derive(Debug, Clone, Copy, PartialEq, Eq)]
449pub enum ScalarQueryConstancy {
450    /// With identical arguments, the function is stable across statements.
451    Constant,
452    /// The function is stable during one query but may change between them.
453    SlowChanging,
454    /// The function may produce a different result on every invocation.
455    Volatile,
456}
457
458impl ScalarQueryConstancy {
459    const fn from_deterministic(deterministic: bool) -> Self {
460        if deterministic {
461            Self::Constant
462        } else {
463            Self::Volatile
464        }
465    }
466
467    /// Whether the function is stable for the duration of one query.
468    #[must_use]
469    pub const fn is_query_constant(self) -> bool {
470        matches!(self, Self::Constant | Self::SlowChanging)
471    }
472}
473
474/// One scalar implementation and the immutable metadata selected for a call.
475///
476/// Keeping these values together prevents execution paths from resolving the
477/// function, schema-safety policy, query-constancy policy, and
478/// argument-collation contract through separate registry probes that could
479/// disagree or repeat canonicalization.
480#[derive(Clone)]
481pub struct ResolvedScalarFunction {
482    function: Arc<dyn ScalarFunction>,
483    schema_safety: ScalarSchemaSafety,
484    query_constancy: ScalarQueryConstancy,
485    consumes_argument_collation: bool,
486}
487
488impl ResolvedScalarFunction {
489    /// Clone the selected function object for invocation outside a registry
490    /// borrow or lock.
491    #[must_use]
492    pub fn function(&self) -> Arc<dyn ScalarFunction> {
493        Arc::clone(&self.function)
494    }
495
496    /// Frozen schema-safety policy belonging to the selected registration.
497    #[must_use]
498    pub const fn schema_safety(&self) -> ScalarSchemaSafety {
499        self.schema_safety
500    }
501
502    /// Frozen query-constancy policy belonging to the selected registration.
503    #[must_use]
504    pub const fn query_constancy(&self) -> ScalarQueryConstancy {
505        self.query_constancy
506    }
507
508    /// Frozen argument-collation contract belonging to the selected entry.
509    #[must_use]
510    pub const fn consumes_argument_collation(&self) -> bool {
511        self.consumes_argument_collation
512    }
513}
514
515enum ApplicationFunction {
516    Scalar {
517        function: Arc<dyn ScalarFunction>,
518        arity: FunctionArity,
519        schema_safety: ScalarSchemaSafety,
520        query_constancy: ScalarQueryConstancy,
521        consumes_argument_collation: bool,
522    },
523    Aggregate {
524        function: Arc<ErasedAggregateFunction>,
525        arity: FunctionArity,
526    },
527    Window {
528        function: Arc<ErasedWindowFunction>,
529        aggregate: Arc<ErasedAggregateFunction>,
530        arity: FunctionArity,
531    },
532}
533
534impl ApplicationFunction {
535    const fn kind(&self) -> ApplicationFunctionKind {
536        match self {
537            Self::Scalar { .. } => ApplicationFunctionKind::Scalar,
538            Self::Aggregate { .. } => ApplicationFunctionKind::Aggregate,
539            Self::Window { .. } => ApplicationFunctionKind::Window,
540        }
541    }
542
543    const fn arity(&self) -> FunctionArity {
544        match self {
545            Self::Scalar { arity, .. }
546            | Self::Aggregate { arity, .. }
547            | Self::Window { arity, .. } => *arity,
548        }
549    }
550
551    const fn resolution(&self) -> ApplicationFunctionResolution {
552        ApplicationFunctionResolution {
553            kind: self.kind(),
554            arity: self.arity(),
555        }
556    }
557}
558
559impl Clone for ApplicationFunction {
560    fn clone(&self) -> Self {
561        match self {
562            Self::Scalar {
563                function,
564                arity,
565                schema_safety,
566                query_constancy,
567                consumes_argument_collation,
568            } => Self::Scalar {
569                function: Arc::clone(function),
570                arity: *arity,
571                schema_safety: *schema_safety,
572                query_constancy: *query_constancy,
573                consumes_argument_collation: *consumes_argument_collation,
574            },
575            Self::Aggregate { function, arity } => Self::Aggregate {
576                function: Arc::clone(function),
577                arity: *arity,
578            },
579            Self::Window {
580                function,
581                aggregate,
582                arity,
583            } => Self::Window {
584                function: Arc::clone(function),
585                aggregate: Arc::clone(aggregate),
586                arity: *arity,
587            },
588        }
589    }
590}
591
592/// Ownership token for an application registration displaced from a registry.
593///
594/// Callers may retain this value until after publishing the replacement
595/// registry and invalidating prepared statements. Dropping it then cannot run
596/// user destructors while registry state is mutably borrowed.
597pub struct DisplacedApplicationFunction {
598    _function: ApplicationFunction,
599}
600
601/// Registry for scalar, aggregate, and window functions, keyed by
602/// `(name, num_args)`.
603///
604/// Lookup strategy (§9.5):
605/// 1. A compatible exact application registration, across all function kinds.
606/// 2. A compatible variadic application registration.
607/// 3. An exact entry in the built-in/base layer requested by the caller.
608/// 4. An arity-compatible variadic entry in that base layer.
609/// 5. A known same-kind name with incompatible arity returns a function that
610///    raises SQLite's "wrong number of arguments" error when invoked.
611/// 6. `None` if neither layer contains a usable same-kind entry.
612#[derive(Default)]
613pub struct FunctionRegistry {
614    /// Connection-local application registrations. These are deliberately
615    /// layered over the built-in maps below: a bounded application variadic
616    /// that does not accept a call must leave a compatible built-in visible.
617    application_functions: HashMap<String, HashMap<i32, ApplicationFunction>>,
618    scalars: HashMap<FunctionKey, Arc<dyn ScalarFunction>>,
619    scalar_arities: HashMap<FunctionKey, FunctionArity>,
620    scalar_schema_safety: HashMap<FunctionKey, ScalarSchemaSafety>,
621    scalar_query_constancy: HashMap<FunctionKey, ScalarQueryConstancy>,
622    scalar_argument_collation: HashMap<FunctionKey, bool>,
623    aggregates: HashMap<FunctionKey, Arc<ErasedAggregateFunction>>,
624    aggregate_arities: HashMap<FunctionKey, FunctionArity>,
625    windows: HashMap<FunctionKey, Arc<ErasedWindowFunction>>,
626    window_arities: HashMap<FunctionKey, FunctionArity>,
627}
628
629struct WrongArgCountScalarFunction {
630    display_name: String,
631}
632
633fn wrong_arg_count_message(display_name: &str) -> String {
634    format!("wrong number of arguments to function {display_name}()")
635}
636
637fn wrong_arg_display_name(canonical: &str) -> String {
638    canonical.to_ascii_lowercase()
639}
640
641impl WrongArgCountScalarFunction {
642    fn new(canonical: &str) -> Self {
643        Self {
644            display_name: wrong_arg_display_name(canonical),
645        }
646    }
647
648    fn message(&self) -> String {
649        wrong_arg_count_message(&self.display_name)
650    }
651}
652
653impl ScalarFunction for WrongArgCountScalarFunction {
654    fn invoke(&self, _args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
655        Err(FrankenError::function_error(self.message()))
656    }
657
658    fn num_args(&self) -> i32 {
659        -1
660    }
661
662    fn name(&self) -> &str {
663        &self.display_name
664    }
665}
666
667struct WrongArgCountAggregateFunction {
668    display_name: String,
669}
670
671impl WrongArgCountAggregateFunction {
672    fn new(canonical: &str) -> Self {
673        Self {
674            display_name: wrong_arg_display_name(canonical),
675        }
676    }
677
678    fn message(&self) -> String {
679        wrong_arg_count_message(&self.display_name)
680    }
681}
682
683impl AggregateFunction for WrongArgCountAggregateFunction {
684    type State = ();
685
686    fn initial_state(&self) -> Self::State {}
687
688    fn step(&self, _state: &mut Self::State, _args: &[SqliteValue]) -> fsqlite_error::Result<()> {
689        Err(FrankenError::function_error(self.message()))
690    }
691
692    fn finalize(&self, _state: Self::State) -> fsqlite_error::Result<SqliteValue> {
693        Err(FrankenError::function_error(self.message()))
694    }
695
696    fn num_args(&self) -> i32 {
697        -1
698    }
699
700    fn name(&self) -> &str {
701        &self.display_name
702    }
703}
704
705struct WrongArgCountWindowFunction {
706    display_name: String,
707}
708
709impl WrongArgCountWindowFunction {
710    fn new(canonical: &str) -> Self {
711        Self {
712            display_name: wrong_arg_display_name(canonical),
713        }
714    }
715
716    fn message(&self) -> String {
717        wrong_arg_count_message(&self.display_name)
718    }
719}
720
721impl WindowFunction for WrongArgCountWindowFunction {
722    type State = ();
723
724    fn initial_state(&self) -> Self::State {}
725
726    fn step(&self, _state: &mut Self::State, _args: &[SqliteValue]) -> fsqlite_error::Result<()> {
727        Err(FrankenError::function_error(self.message()))
728    }
729
730    fn inverse(
731        &self,
732        _state: &mut Self::State,
733        _args: &[SqliteValue],
734    ) -> fsqlite_error::Result<()> {
735        Err(FrankenError::function_error(self.message()))
736    }
737
738    fn value(&self, _state: &Self::State) -> fsqlite_error::Result<SqliteValue> {
739        Err(FrankenError::function_error(self.message()))
740    }
741
742    fn finalize(&self, _state: Self::State) -> fsqlite_error::Result<SqliteValue> {
743        Err(FrankenError::function_error(self.message()))
744    }
745
746    fn num_args(&self) -> i32 {
747        -1
748    }
749
750    fn name(&self) -> &str {
751        &self.display_name
752    }
753}
754
755/// Aggregate call form of one erased application-defined window function.
756///
757/// The adapter delegates directly to the already-erased window object, so its
758/// accumulator is not boxed a second time. Name and arity are frozen at
759/// registration and no user metadata callback is re-entered after publication.
760struct WindowAggregateBridge {
761    function: Arc<ErasedWindowFunction>,
762    name: String,
763    arity: FunctionArity,
764}
765
766impl AggregateFunction for WindowAggregateBridge {
767    type State = Box<dyn Any + Send>;
768
769    fn initial_state(&self) -> Self::State {
770        self.function.initial_state()
771    }
772
773    fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> fsqlite_error::Result<()> {
774        self.function.step(state, args)
775    }
776
777    fn finalize(&self, state: Self::State) -> fsqlite_error::Result<SqliteValue> {
778        self.function.finalize(state)
779    }
780
781    fn num_args(&self) -> i32 {
782        self.arity.declared_args()
783    }
784
785    fn min_args(&self) -> i32 {
786        self.arity.min_args()
787    }
788
789    fn max_args(&self) -> Option<i32> {
790        self.arity.max_args()
791    }
792
793    fn arity(&self) -> FunctionArity {
794        self.arity
795    }
796
797    fn name(&self) -> &str {
798        &self.name
799    }
800}
801
802impl FunctionRegistry {
803    /// Create an empty registry.
804    #[must_use]
805    pub fn new() -> Self {
806        Self::default()
807    }
808
809    /// Create a mutable clone of a registry from an `Arc` reference.
810    ///
811    /// This is used by the UDF registration API to produce a new registry
812    /// containing the existing functions plus the newly registered UDF.
813    #[must_use]
814    pub fn clone_from_arc(arc: &Arc<Self>) -> Self {
815        Self {
816            application_functions: arc.application_functions.clone(),
817            scalars: arc.scalars.clone(),
818            scalar_arities: arc.scalar_arities.clone(),
819            scalar_schema_safety: arc.scalar_schema_safety.clone(),
820            scalar_query_constancy: arc.scalar_query_constancy.clone(),
821            scalar_argument_collation: arc.scalar_argument_collation.clone(),
822            aggregates: arc.aggregates.clone(),
823            aggregate_arities: arc.aggregate_arities.clone(),
824            windows: arc.windows.clone(),
825            window_arities: arc.window_arities.clone(),
826        }
827    }
828
829    fn application_function_precanonical(
830        &self,
831        canonical: &str,
832        num_args: i32,
833    ) -> Option<&ApplicationFunction> {
834        let overloads = self.application_functions.get(canonical)?;
835        if let Some(function) = overloads.get(&num_args)
836            && function.arity().accepts(num_args)
837        {
838            return Some(function);
839        }
840
841        overloads
842            .get(&-1)
843            .filter(|function| function.arity().accepts(num_args))
844    }
845
846    fn application_name_has_kind_precanonical(
847        &self,
848        canonical: &str,
849        matches_kind: impl Fn(ApplicationFunctionKind) -> bool,
850    ) -> bool {
851        self.application_functions
852            .get(canonical)
853            .is_some_and(|overloads| {
854                overloads
855                    .values()
856                    .any(|function| matches_kind(function.kind()))
857            })
858    }
859
860    /// Resolve a compatible application registration across all function kinds.
861    ///
862    /// An exact application key wins over a compatible application variadic,
863    /// independent of kind or registration order. `None` means no application
864    /// overload accepts this call; callers may then resolve built-ins.
865    #[must_use]
866    pub fn resolve_application_function(
867        &self,
868        name: &str,
869        num_args: i32,
870    ) -> Option<ApplicationFunctionResolution> {
871        let canonical = canonical_name(name);
872        self.resolve_application_function_precanonical(&canonical, num_args)
873    }
874
875    /// Precanonicalized counterpart to [`Self::resolve_application_function`].
876    #[must_use]
877    pub fn resolve_application_function_precanonical(
878        &self,
879        canonical: &str,
880        num_args: i32,
881    ) -> Option<ApplicationFunctionResolution> {
882        self.application_function_precanonical(canonical, num_args)
883            .map(ApplicationFunction::resolution)
884    }
885
886    /// Whether any application overload is registered under this name.
887    #[must_use]
888    pub fn contains_application_function(&self, name: &str) -> bool {
889        let canonical = canonical_name(name);
890        self.application_functions.contains_key(&canonical)
891    }
892
893    fn replace_application_function(
894        &mut self,
895        key: FunctionKey,
896        function: ApplicationFunction,
897    ) -> Option<DisplacedApplicationFunction> {
898        assert_key_matches_arity(&key, function.arity());
899        self.application_functions
900            .entry(key.name)
901            .or_default()
902            .insert(key.num_args, function)
903            .map(|function| DisplacedApplicationFunction {
904                _function: function,
905            })
906    }
907
908    /// Register an application-defined scalar using caller-frozen metadata.
909    ///
910    /// This shares a cross-kind namespace with application aggregate and window
911    /// registrations. Replacing an identical `(name, declared_arity)` key
912    /// therefore displaces the previous application entry regardless of kind.
913    pub fn register_application_scalar_captured<F>(
914        &mut self,
915        name: &str,
916        arity: FunctionArity,
917        deterministic: bool,
918        consumes_argument_collation: bool,
919        function: F,
920    ) -> Option<DisplacedApplicationFunction>
921    where
922        F: ScalarFunction + 'static,
923    {
924        let key = FunctionKey::new(name, arity.declared_args());
925        self.replace_application_function(
926            key,
927            ApplicationFunction::Scalar {
928                function: Arc::new(function),
929                arity,
930                schema_safety: ScalarSchemaSafety::from_deterministic(deterministic),
931                query_constancy: ScalarQueryConstancy::from_deterministic(deterministic),
932                consumes_argument_collation,
933            },
934        )
935    }
936
937    /// Register an application-defined aggregate using caller-frozen metadata.
938    ///
939    /// The returned token owns any same-key application entry displaced across
940    /// scalar, aggregate, or window kinds.
941    pub fn register_application_aggregate_captured<F>(
942        &mut self,
943        name: &str,
944        arity: FunctionArity,
945        function: F,
946    ) -> Option<DisplacedApplicationFunction>
947    where
948        F: AggregateFunction + 'static,
949        F::State: 'static,
950    {
951        let key = FunctionKey::new(name, arity.declared_args());
952        self.replace_application_function(
953            key,
954            ApplicationFunction::Aggregate {
955                function: Arc::new(AggregateAdapter::new(function)),
956                arity,
957            },
958        )
959    }
960
961    /// Register an application-defined window function using frozen metadata.
962    ///
963    /// SQLite window registrations retain an ordinary aggregate call form. The
964    /// returned window Arc and aggregate bridge share the same erased function
965    /// object and frozen arity contract.
966    pub fn register_application_window_captured<F>(
967        &mut self,
968        name: &str,
969        arity: FunctionArity,
970        function: F,
971    ) -> (
972        Arc<ErasedWindowFunction>,
973        Option<DisplacedApplicationFunction>,
974    )
975    where
976        F: WindowFunction + 'static,
977        F::State: 'static,
978    {
979        let key = FunctionKey::new(name, arity.declared_args());
980        let registered: Arc<ErasedWindowFunction> = Arc::new(WindowAdapter::new(function));
981        let aggregate: Arc<ErasedAggregateFunction> = Arc::new(WindowAggregateBridge {
982            function: Arc::clone(&registered),
983            name: key.name.clone(),
984            arity,
985        });
986        let displaced = self.replace_application_function(
987            key,
988            ApplicationFunction::Window {
989                function: Arc::clone(&registered),
990                aggregate,
991                arity,
992            },
993        );
994        (registered, displaced)
995    }
996
997    /// Register a scalar function, keyed by `(name, num_args)`.
998    ///
999    /// Overwrites any existing function with the same key. Returns the
1000    /// previous function if one existed.
1001    pub fn register_scalar<F>(&mut self, function: F) -> Option<Arc<dyn ScalarFunction>>
1002    where
1003        F: ScalarFunction + 'static,
1004    {
1005        let name = function.name().to_owned();
1006        let arity = function.arity();
1007        let deterministic = function.is_deterministic();
1008        let consumes_argument_collation = function.consumes_argument_collation();
1009        self.register_scalar_captured(
1010            &name,
1011            arity,
1012            deterministic,
1013            consumes_argument_collation,
1014            function,
1015        )
1016    }
1017
1018    /// Register a scalar function under caller-precomputed identity and arity.
1019    ///
1020    /// This variant never calls user metadata. It is intended for publication
1021    /// paths that capture metadata before taking a registry snapshot, so a
1022    /// reentrant metadata callback cannot make a stale clone overwrite a nested
1023    /// registration. The key and immutable arity contract must agree.
1024    pub fn register_scalar_keyed<F>(
1025        &mut self,
1026        key: FunctionKey,
1027        arity: FunctionArity,
1028        deterministic: bool,
1029        consumes_argument_collation: bool,
1030        function: F,
1031    ) -> Option<Arc<dyn ScalarFunction>>
1032    where
1033        F: ScalarFunction + 'static,
1034    {
1035        assert_key_matches_arity(&key, arity);
1036        self.scalar_arities.insert(key.clone(), arity);
1037        self.scalar_schema_safety.insert(
1038            key.clone(),
1039            ScalarSchemaSafety::from_deterministic(deterministic),
1040        );
1041        self.scalar_query_constancy.insert(
1042            key.clone(),
1043            ScalarQueryConstancy::from_deterministic(deterministic),
1044        );
1045        self.scalar_argument_collation
1046            .insert(key.clone(), consumes_argument_collation);
1047        self.scalars.insert(key, Arc::new(function))
1048    }
1049
1050    /// Register a scalar function with caller-captured metadata.
1051    ///
1052    /// No user metadata callback runs in this method. The registry key and
1053    /// runtime acceptance contract are both derived from the same immutable
1054    /// arity value.
1055    pub fn register_scalar_captured<F>(
1056        &mut self,
1057        name: &str,
1058        arity: FunctionArity,
1059        deterministic: bool,
1060        consumes_argument_collation: bool,
1061        function: F,
1062    ) -> Option<Arc<dyn ScalarFunction>>
1063    where
1064        F: ScalarFunction + 'static,
1065    {
1066        let key = FunctionKey::new(name, arity.declared_args());
1067        self.scalar_arities.insert(key.clone(), arity);
1068        self.scalar_schema_safety.insert(
1069            key.clone(),
1070            ScalarSchemaSafety::from_deterministic(deterministic),
1071        );
1072        self.scalar_query_constancy.insert(
1073            key.clone(),
1074            ScalarQueryConstancy::from_deterministic(deterministic),
1075        );
1076        self.scalar_argument_collation
1077            .insert(key.clone(), consumes_argument_collation);
1078        self.scalars.insert(key, Arc::new(function))
1079    }
1080
1081    /// Register one sealed built-in whose schema safety depends on evaluated
1082    /// argument values. This is crate-private so application-defined trait
1083    /// objects cannot opt into metadata callbacks on the execution hot path.
1084    pub(crate) fn register_conditionally_deterministic_scalar<F>(
1085        &mut self,
1086        function: F,
1087    ) -> Option<Arc<dyn ScalarFunction>>
1088    where
1089        F: ScalarFunction + 'static,
1090    {
1091        let name = function.name().to_owned();
1092        let arity = function.arity();
1093        let consumes_argument_collation = function.consumes_argument_collation();
1094        let key = FunctionKey::new(&name, arity.declared_args());
1095        self.scalar_arities.insert(key.clone(), arity);
1096        self.scalar_schema_safety
1097            .insert(key.clone(), ScalarSchemaSafety::DateTimeConditional);
1098        self.scalar_query_constancy
1099            .insert(key.clone(), ScalarQueryConstancy::SlowChanging);
1100        self.scalar_argument_collation
1101            .insert(key.clone(), consumes_argument_collation);
1102        self.scalars.insert(key, Arc::new(function))
1103    }
1104
1105    /// Register one sealed built-in that is constant for a single query but
1106    /// unsafe in schema-maintained expressions.
1107    ///
1108    /// This is crate-private so application-defined functions cannot opt into
1109    /// SQLite's privileged slow-changing classification.
1110    pub(crate) fn register_slow_changing_scalar<F>(
1111        &mut self,
1112        function: F,
1113    ) -> Option<Arc<dyn ScalarFunction>>
1114    where
1115        F: ScalarFunction + 'static,
1116    {
1117        let name = function.name().to_owned();
1118        let arity = function.arity();
1119        let consumes_argument_collation = function.consumes_argument_collation();
1120        let key = FunctionKey::new(&name, arity.declared_args());
1121        self.scalar_arities.insert(key.clone(), arity);
1122        self.scalar_schema_safety
1123            .insert(key.clone(), ScalarSchemaSafety::Never);
1124        self.scalar_query_constancy
1125            .insert(key.clone(), ScalarQueryConstancy::SlowChanging);
1126        self.scalar_argument_collation
1127            .insert(key.clone(), consumes_argument_collation);
1128        self.scalars.insert(key, Arc::new(function))
1129    }
1130
1131    /// Register an aggregate function using the type-erased adapter.
1132    ///
1133    /// Overwrites any existing function with the same `(name, num_args)` key.
1134    pub fn register_aggregate<F>(&mut self, function: F) -> Option<Arc<ErasedAggregateFunction>>
1135    where
1136        F: AggregateFunction + 'static,
1137        F::State: 'static,
1138    {
1139        let name = function.name().to_owned();
1140        let arity = function.arity();
1141        self.register_aggregate_captured(&name, arity, function)
1142    }
1143
1144    /// Register an aggregate function under caller-precomputed identity and arity.
1145    ///
1146    /// Returns the displaced adapter, if any. No user metadata callback runs
1147    /// here. The key and immutable arity contract must agree.
1148    pub fn register_aggregate_keyed<F>(
1149        &mut self,
1150        key: FunctionKey,
1151        arity: FunctionArity,
1152        function: F,
1153    ) -> Option<Arc<ErasedAggregateFunction>>
1154    where
1155        F: AggregateFunction + 'static,
1156        F::State: 'static,
1157    {
1158        assert_key_matches_arity(&key, arity);
1159        self.aggregate_arities.insert(key.clone(), arity);
1160        self.aggregates
1161            .insert(key, Arc::new(AggregateAdapter::new(function)))
1162    }
1163
1164    /// Register an aggregate function with caller-captured metadata.
1165    ///
1166    /// No user metadata callback runs in this method. The registry key and
1167    /// runtime acceptance contract share one immutable arity value.
1168    pub fn register_aggregate_captured<F>(
1169        &mut self,
1170        name: &str,
1171        arity: FunctionArity,
1172        function: F,
1173    ) -> Option<Arc<ErasedAggregateFunction>>
1174    where
1175        F: AggregateFunction + 'static,
1176        F::State: 'static,
1177    {
1178        let key = FunctionKey::new(name, arity.declared_args());
1179        self.aggregate_arities.insert(key.clone(), arity);
1180        self.aggregates
1181            .insert(key, Arc::new(AggregateAdapter::new(function)))
1182    }
1183
1184    /// Register a window function using the type-erased adapter.
1185    ///
1186    /// Overwrites any existing function with the same `(name, num_args)` key.
1187    pub fn register_window<F>(&mut self, function: F) -> Option<Arc<ErasedWindowFunction>>
1188    where
1189        F: WindowFunction + 'static,
1190        F::State: 'static,
1191    {
1192        let name = function.name().to_owned();
1193        let arity = function.arity();
1194        let (_, displaced) = self.register_window_captured(&name, arity, function);
1195        displaced
1196    }
1197
1198    /// Register a window function under caller-precomputed identity and arity.
1199    ///
1200    /// The returned first Arc is the newly inserted erased adapter; the second
1201    /// is the displaced adapter, if any. No user metadata callback runs here.
1202    /// The key and immutable arity contract must agree.
1203    pub fn register_window_keyed<F>(
1204        &mut self,
1205        key: FunctionKey,
1206        arity: FunctionArity,
1207        function: F,
1208    ) -> (Arc<ErasedWindowFunction>, Option<Arc<ErasedWindowFunction>>)
1209    where
1210        F: WindowFunction + 'static,
1211        F::State: 'static,
1212    {
1213        let registered: Arc<ErasedWindowFunction> = Arc::new(WindowAdapter::new(function));
1214        assert_key_matches_arity(&key, arity);
1215        self.window_arities.insert(key.clone(), arity);
1216        let displaced = self.windows.insert(key, Arc::clone(&registered));
1217        (registered, displaced)
1218    }
1219
1220    /// Register a window function with caller-captured metadata.
1221    ///
1222    /// No user metadata callback runs in this method. The registry key and
1223    /// runtime acceptance contract share one immutable arity value.
1224    pub fn register_window_captured<F>(
1225        &mut self,
1226        name: &str,
1227        arity: FunctionArity,
1228        function: F,
1229    ) -> (Arc<ErasedWindowFunction>, Option<Arc<ErasedWindowFunction>>)
1230    where
1231        F: WindowFunction + 'static,
1232        F::State: 'static,
1233    {
1234        let registered: Arc<ErasedWindowFunction> = Arc::new(WindowAdapter::new(function));
1235        let key = FunctionKey::new(name, arity.declared_args());
1236        self.window_arities.insert(key.clone(), arity);
1237        let displaced = self.windows.insert(key, Arc::clone(&registered));
1238        (registered, displaced)
1239    }
1240
1241    /// Look up a scalar function by `(name, num_args)`.
1242    ///
1243    /// Tries exact match first, then falls back to an arity-compatible
1244    /// variadic version `(name, -1)` if no exact match exists.
1245    #[must_use]
1246    pub fn find_scalar(&self, name: &str, num_args: i32) -> Option<Arc<dyn ScalarFunction>> {
1247        self.resolve_scalar(name, num_args)
1248            .map(|resolved| resolved.function)
1249    }
1250
1251    /// Look up a scalar function by already-uppercased name (avoids allocation).
1252    ///
1253    /// Used by the VDBE engine where `P4::FuncName` values are already
1254    /// canonicalized by codegen.
1255    #[must_use]
1256    pub fn find_scalar_precanonical(
1257        &self,
1258        canonical: &str,
1259        num_args: i32,
1260    ) -> Option<Arc<dyn ScalarFunction>> {
1261        self.resolve_scalar_precanonical(canonical, num_args)
1262            .map(|resolved| resolved.function)
1263    }
1264
1265    /// Resolve a scalar implementation and all execution metadata in one
1266    /// registry traversal.
1267    #[must_use]
1268    pub fn resolve_scalar(&self, name: &str, num_args: i32) -> Option<ResolvedScalarFunction> {
1269        let canonical = canonical_name(name);
1270        self.resolve_scalar_precanonical(&canonical, num_args)
1271    }
1272
1273    /// Precanonicalized counterpart to [`Self::resolve_scalar`].
1274    #[must_use]
1275    pub fn resolve_scalar_precanonical(
1276        &self,
1277        canonical: &str,
1278        num_args: i32,
1279    ) -> Option<ResolvedScalarFunction> {
1280        if let Some(application) = self.application_function_precanonical(canonical, num_args) {
1281            return match application {
1282                ApplicationFunction::Scalar {
1283                    function,
1284                    schema_safety,
1285                    query_constancy,
1286                    consumes_argument_collation,
1287                    ..
1288                } => {
1289                    debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "application", "registry lookup");
1290                    Some(ResolvedScalarFunction {
1291                        function: Arc::clone(function),
1292                        schema_safety: *schema_safety,
1293                        query_constancy: *query_constancy,
1294                        consumes_argument_collation: *consumes_argument_collation,
1295                    })
1296                }
1297                ApplicationFunction::Aggregate { .. } | ApplicationFunction::Window { .. } => {
1298                    debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "shadowed_by_application", "registry lookup");
1299                    None
1300                }
1301            };
1302        }
1303        let exact = FunctionKey {
1304            name: canonical.to_owned(),
1305            num_args,
1306        };
1307        if let Some(function) = self.scalars.get(&exact) {
1308            let Some(arity) = self.scalar_arities.get(&exact).copied() else {
1309                debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "missing_arity", "registry lookup");
1310                return Some(ResolvedScalarFunction {
1311                    function: Arc::new(WrongArgCountScalarFunction::new(canonical)),
1312                    schema_safety: ScalarSchemaSafety::Never,
1313                    query_constancy: ScalarQueryConstancy::Volatile,
1314                    consumes_argument_collation: false,
1315                });
1316            };
1317            if arity.accepts(num_args) {
1318                debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "exact", "registry lookup");
1319                return Some(ResolvedScalarFunction {
1320                    function: Arc::clone(function),
1321                    schema_safety: self
1322                        .scalar_schema_safety
1323                        .get(&exact)
1324                        .copied()
1325                        .unwrap_or(ScalarSchemaSafety::Never),
1326                    query_constancy: self
1327                        .scalar_query_constancy
1328                        .get(&exact)
1329                        .copied()
1330                        .unwrap_or(ScalarQueryConstancy::Volatile),
1331                    consumes_argument_collation: self
1332                        .scalar_argument_collation
1333                        .get(&exact)
1334                        .copied()
1335                        .unwrap_or(false),
1336                });
1337            }
1338            debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "wrong_arity", "registry lookup");
1339            return Some(ResolvedScalarFunction {
1340                function: Arc::new(WrongArgCountScalarFunction::new(canonical)),
1341                schema_safety: ScalarSchemaSafety::Never,
1342                query_constancy: ScalarQueryConstancy::Volatile,
1343                consumes_argument_collation: false,
1344            });
1345        }
1346        let variadic = FunctionKey {
1347            name: canonical.to_owned(),
1348            num_args: -1,
1349        };
1350        if let Some(function) = self.scalars.get(&variadic) {
1351            let Some(arity) = self.scalar_arities.get(&variadic).copied() else {
1352                debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "missing_arity", "registry lookup");
1353                return Some(ResolvedScalarFunction {
1354                    function: Arc::new(WrongArgCountScalarFunction::new(canonical)),
1355                    schema_safety: ScalarSchemaSafety::Never,
1356                    query_constancy: ScalarQueryConstancy::Volatile,
1357                    consumes_argument_collation: false,
1358                });
1359            };
1360            if arity.accepts(num_args) {
1361                debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "variadic", "registry lookup");
1362                return Some(ResolvedScalarFunction {
1363                    function: Arc::clone(function),
1364                    schema_safety: self
1365                        .scalar_schema_safety
1366                        .get(&variadic)
1367                        .copied()
1368                        .unwrap_or(ScalarSchemaSafety::Never),
1369                    query_constancy: self
1370                        .scalar_query_constancy
1371                        .get(&variadic)
1372                        .copied()
1373                        .unwrap_or(ScalarQueryConstancy::Volatile),
1374                    consumes_argument_collation: self
1375                        .scalar_argument_collation
1376                        .get(&variadic)
1377                        .copied()
1378                        .unwrap_or(false),
1379                });
1380            }
1381            debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "wrong_arity", "registry lookup");
1382            return Some(ResolvedScalarFunction {
1383                function: Arc::new(WrongArgCountScalarFunction::new(canonical)),
1384                schema_safety: ScalarSchemaSafety::Never,
1385                query_constancy: ScalarQueryConstancy::Volatile,
1386                consumes_argument_collation: false,
1387            });
1388        }
1389        if self.scalars.keys().any(|key| key.name == canonical)
1390            || self.application_name_has_kind_precanonical(canonical, |kind| {
1391                kind == ApplicationFunctionKind::Scalar
1392            })
1393        {
1394            debug!(name = %canonical, arity = num_args, kind = "scalar", hit = "wrong_arity", "registry lookup");
1395            return Some(ResolvedScalarFunction {
1396                function: Arc::new(WrongArgCountScalarFunction::new(canonical)),
1397                schema_safety: ScalarSchemaSafety::Never,
1398                query_constancy: ScalarQueryConstancy::Volatile,
1399                consumes_argument_collation: false,
1400            });
1401        }
1402        debug!(
1403            name = %canonical,
1404            arity = num_args,
1405            kind = "scalar",
1406            hit = "miss",
1407            "registry lookup"
1408        );
1409        None
1410    }
1411
1412    /// Look up an aggregate function by `(name, num_args)`.
1413    ///
1414    /// Tries exact match first, then falls back to variadic `(name, -1)`.
1415    #[must_use]
1416    pub fn find_aggregate(
1417        &self,
1418        name: &str,
1419        num_args: i32,
1420    ) -> Option<Arc<ErasedAggregateFunction>> {
1421        let canon = canonical_name(name);
1422        self.find_aggregate_precanonical(&canon, num_args)
1423    }
1424
1425    /// Look up an aggregate function by already-uppercased name (avoids allocation).
1426    #[must_use]
1427    pub fn find_aggregate_precanonical(
1428        &self,
1429        canonical: &str,
1430        num_args: i32,
1431    ) -> Option<Arc<ErasedAggregateFunction>> {
1432        if let Some(application) = self.application_function_precanonical(canonical, num_args) {
1433            return match application {
1434                ApplicationFunction::Aggregate { function, .. } => {
1435                    debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "application", "registry lookup");
1436                    Some(Arc::clone(function))
1437                }
1438                ApplicationFunction::Window { aggregate, .. } => {
1439                    debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "application_window", "registry lookup");
1440                    Some(Arc::clone(aggregate))
1441                }
1442                ApplicationFunction::Scalar { .. } => {
1443                    debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "shadowed_by_application", "registry lookup");
1444                    None
1445                }
1446            };
1447        }
1448        let exact = FunctionKey {
1449            name: canonical.to_owned(),
1450            num_args,
1451        };
1452        if let Some(function) = self.aggregates.get(&exact) {
1453            let Some(arity) = self.aggregate_arities.get(&exact).copied() else {
1454                debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "missing_arity", "registry lookup");
1455                return Some(Arc::new(AggregateAdapter::new(
1456                    WrongArgCountAggregateFunction::new(canonical),
1457                )));
1458            };
1459            if arity.accepts(num_args) {
1460                debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "exact", "registry lookup");
1461                return Some(Arc::clone(function));
1462            }
1463            debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "wrong_arity", "registry lookup");
1464            return Some(Arc::new(AggregateAdapter::new(
1465                WrongArgCountAggregateFunction::new(canonical),
1466            )));
1467        }
1468        let variadic = FunctionKey {
1469            name: canonical.to_owned(),
1470            num_args: -1,
1471        };
1472        if let Some(function) = self.aggregates.get(&variadic) {
1473            let Some(arity) = self.aggregate_arities.get(&variadic).copied() else {
1474                debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "missing_arity", "registry lookup");
1475                return Some(Arc::new(AggregateAdapter::new(
1476                    WrongArgCountAggregateFunction::new(canonical),
1477                )));
1478            };
1479            if arity.accepts(num_args) {
1480                debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "variadic", "registry lookup");
1481                return Some(Arc::clone(function));
1482            }
1483            debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "wrong_arity", "registry lookup");
1484            return Some(Arc::new(AggregateAdapter::new(
1485                WrongArgCountAggregateFunction::new(canonical),
1486            )));
1487        }
1488        if self.aggregates.keys().any(|key| key.name == canonical)
1489            || self.application_name_has_kind_precanonical(canonical, |kind| {
1490                kind.is_aggregate_callable()
1491            })
1492        {
1493            debug!(name = %canonical, arity = num_args, kind = "aggregate", hit = "wrong_arity", "registry lookup");
1494            return Some(Arc::new(AggregateAdapter::new(
1495                WrongArgCountAggregateFunction::new(canonical),
1496            )));
1497        }
1498        debug!(
1499            name = %canonical,
1500            arity = num_args,
1501            kind = "aggregate",
1502            hit = "miss",
1503            "registry lookup"
1504        );
1505        None
1506    }
1507
1508    /// Look up a window function by `(name, num_args)`.
1509    ///
1510    /// Tries exact match first, then falls back to variadic `(name, -1)`.
1511    #[must_use]
1512    pub fn find_window(&self, name: &str, num_args: i32) -> Option<Arc<ErasedWindowFunction>> {
1513        let canon = canonical_name(name);
1514        if let Some(application) = self.application_function_precanonical(&canon, num_args) {
1515            return match application {
1516                ApplicationFunction::Window { function, .. } => {
1517                    debug!(name = %canon, arity = num_args, kind = "window", hit = "application", "registry lookup");
1518                    Some(Arc::clone(function))
1519                }
1520                ApplicationFunction::Scalar { .. } | ApplicationFunction::Aggregate { .. } => {
1521                    debug!(name = %canon, arity = num_args, kind = "window", hit = "shadowed_by_application", "registry lookup");
1522                    None
1523                }
1524            };
1525        }
1526        let exact = FunctionKey {
1527            name: canon.clone(),
1528            num_args,
1529        };
1530        if let Some(function) = self.windows.get(&exact) {
1531            let Some(arity) = self.window_arities.get(&exact).copied() else {
1532                debug!(name = %canon, arity = num_args, kind = "window", hit = "missing_arity", "registry lookup");
1533                return Some(Arc::new(WindowAdapter::new(
1534                    WrongArgCountWindowFunction::new(&canon),
1535                )));
1536            };
1537            if arity.accepts(num_args) {
1538                debug!(name = %canon, arity = num_args, kind = "window", hit = "exact", "registry lookup");
1539                return Some(Arc::clone(function));
1540            }
1541            debug!(name = %canon, arity = num_args, kind = "window", hit = "wrong_arity", "registry lookup");
1542            return Some(Arc::new(WindowAdapter::new(
1543                WrongArgCountWindowFunction::new(&canon),
1544            )));
1545        }
1546        let variadic = FunctionKey {
1547            name: canon.clone(),
1548            num_args: -1,
1549        };
1550        if let Some(function) = self.windows.get(&variadic) {
1551            let Some(arity) = self.window_arities.get(&variadic).copied() else {
1552                debug!(name = %canon, arity = num_args, kind = "window", hit = "missing_arity", "registry lookup");
1553                return Some(Arc::new(WindowAdapter::new(
1554                    WrongArgCountWindowFunction::new(&canon),
1555                )));
1556            };
1557            if arity.accepts(num_args) {
1558                debug!(name = %canon, arity = num_args, kind = "window", hit = "variadic", "registry lookup");
1559                return Some(Arc::clone(function));
1560            }
1561            debug!(name = %canon, arity = num_args, kind = "window", hit = "wrong_arity", "registry lookup");
1562            return Some(Arc::new(WindowAdapter::new(
1563                WrongArgCountWindowFunction::new(&canon),
1564            )));
1565        }
1566        if self.windows.keys().any(|key| key.name == canon)
1567            || self.application_name_has_kind_precanonical(&canon, |kind| {
1568                kind == ApplicationFunctionKind::Window
1569            })
1570        {
1571            debug!(name = %canon, arity = num_args, kind = "window", hit = "wrong_arity", "registry lookup");
1572            return Some(Arc::new(WindowAdapter::new(
1573                WrongArgCountWindowFunction::new(&canon),
1574            )));
1575        }
1576        debug!(
1577            name = %canon,
1578            arity = num_args,
1579            kind = "window",
1580            hit = "miss",
1581            "registry lookup"
1582        );
1583        None
1584    }
1585
1586    /// Whether the registry contains any scalar function with this name
1587    /// (any arg count).
1588    #[must_use]
1589    pub fn contains_scalar(&self, name: &str) -> bool {
1590        let canon = canonical_name(name);
1591        self.scalars.keys().any(|k| k.name == canon)
1592            || self.application_name_has_kind_precanonical(&canon, |kind| {
1593                kind == ApplicationFunctionKind::Scalar
1594            })
1595    }
1596
1597    /// Whether the registry contains any aggregate function with this name
1598    /// (any arg count).
1599    #[must_use]
1600    pub fn contains_aggregate(&self, name: &str) -> bool {
1601        let canon = canonical_name(name);
1602        self.aggregates.keys().any(|k| k.name == canon)
1603            || self
1604                .application_name_has_kind_precanonical(&canon, |kind| kind.is_aggregate_callable())
1605    }
1606
1607    /// Whether the registry contains any window function with this name
1608    /// (any arg count).
1609    #[must_use]
1610    pub fn contains_window(&self, name: &str) -> bool {
1611        let canon = canonical_name(name);
1612        self.windows.keys().any(|k| k.name == canon)
1613            || self.application_name_has_kind_precanonical(&canon, |kind| {
1614                kind == ApplicationFunctionKind::Window
1615            })
1616    }
1617
1618    /// Return whether a known scalar function accepts the SQL-visible arity.
1619    ///
1620    /// `None` means the name is not registered as a scalar function at all.
1621    /// Unlike [`Self::find_scalar`], this never constructs or invokes a
1622    /// wrong-arity sentinel, so preparation-only validation remains free of
1623    /// user-function side effects.
1624    #[must_use]
1625    pub fn scalar_accepts_arg_count(&self, name: &str, num_args: i32) -> Option<bool> {
1626        let canon = canonical_name(name);
1627        if let Some(application) = self.application_function_precanonical(&canon, num_args) {
1628            return matches!(application, ApplicationFunction::Scalar { .. }).then_some(true);
1629        }
1630        let exact = FunctionKey {
1631            name: canon.clone(),
1632            num_args,
1633        };
1634        if self.scalars.contains_key(&exact) {
1635            return Some(
1636                self.scalar_arities
1637                    .get(&exact)
1638                    .is_some_and(|arity| arity.accepts(num_args)),
1639            );
1640        }
1641
1642        let variadic = FunctionKey {
1643            name: canon.clone(),
1644            num_args: -1,
1645        };
1646        if self.scalars.contains_key(&variadic) {
1647            return Some(
1648                self.scalar_arities
1649                    .get(&variadic)
1650                    .is_some_and(|arity| arity.accepts(num_args)),
1651            );
1652        }
1653
1654        (self.scalars.keys().any(|key| key.name == canon)
1655            || self.application_name_has_kind_precanonical(&canon, |kind| {
1656                kind == ApplicationFunctionKind::Scalar
1657            }))
1658        .then_some(false)
1659    }
1660
1661    /// Return the frozen schema-safety policy for the resolved scalar entry.
1662    ///
1663    /// Exact arity wins over an arity-compatible variadic entry. Missing or
1664    /// inconsistent internal metadata fails closed as [`ScalarSchemaSafety::Never`].
1665    #[must_use]
1666    pub fn scalar_schema_safety(&self, name: &str, num_args: i32) -> Option<ScalarSchemaSafety> {
1667        let canonical = canonical_name(name);
1668        self.scalar_schema_safety_precanonical(&canonical, num_args)
1669    }
1670
1671    /// Precanonicalized counterpart to [`Self::scalar_schema_safety`].
1672    #[must_use]
1673    pub fn scalar_schema_safety_precanonical(
1674        &self,
1675        canonical: &str,
1676        num_args: i32,
1677    ) -> Option<ScalarSchemaSafety> {
1678        if let Some(application) = self.application_function_precanonical(canonical, num_args) {
1679            return match application {
1680                ApplicationFunction::Scalar { schema_safety, .. } => Some(*schema_safety),
1681                ApplicationFunction::Aggregate { .. } | ApplicationFunction::Window { .. } => None,
1682            };
1683        }
1684        let exact = FunctionKey {
1685            name: canonical.to_owned(),
1686            num_args,
1687        };
1688        if self.scalars.contains_key(&exact) {
1689            let accepts = self
1690                .scalar_arities
1691                .get(&exact)
1692                .is_some_and(|arity| arity.accepts(num_args));
1693            return Some(if accepts {
1694                self.scalar_schema_safety
1695                    .get(&exact)
1696                    .copied()
1697                    .unwrap_or(ScalarSchemaSafety::Never)
1698            } else {
1699                ScalarSchemaSafety::Never
1700            });
1701        }
1702
1703        let variadic = FunctionKey {
1704            name: canonical.to_owned(),
1705            num_args: -1,
1706        };
1707        if self.scalars.contains_key(&variadic) {
1708            let Some(arity) = self.scalar_arities.get(&variadic).copied() else {
1709                return Some(ScalarSchemaSafety::Never);
1710            };
1711            if !arity.accepts(num_args) {
1712                return None;
1713            }
1714            return Some(
1715                self.scalar_schema_safety
1716                    .get(&variadic)
1717                    .copied()
1718                    .unwrap_or(ScalarSchemaSafety::Never),
1719            );
1720        }
1721        None
1722    }
1723
1724    /// Return whether the resolved scalar is statically eligible for schema
1725    /// expressions. Conditional built-in date/time entries return `true` here
1726    /// and are checked again against evaluated arguments at execution time.
1727    #[must_use]
1728    pub fn scalar_is_deterministic(&self, name: &str, num_args: i32) -> Option<bool> {
1729        self.scalar_schema_safety(name, num_args)
1730            .map(|safety| safety != ScalarSchemaSafety::Never)
1731    }
1732
1733    /// Return the frozen argument-collation contract for the resolved scalar.
1734    ///
1735    /// The trait callback is captured once at registration. Execution and
1736    /// schema dependency analysis never re-enter arbitrary user metadata.
1737    #[must_use]
1738    pub fn scalar_consumes_argument_collation(&self, name: &str, num_args: i32) -> Option<bool> {
1739        let canonical = canonical_name(name);
1740        self.scalar_consumes_argument_collation_precanonical(&canonical, num_args)
1741    }
1742
1743    /// Precanonicalized counterpart to
1744    /// [`Self::scalar_consumes_argument_collation`].
1745    #[must_use]
1746    pub fn scalar_consumes_argument_collation_precanonical(
1747        &self,
1748        canonical: &str,
1749        num_args: i32,
1750    ) -> Option<bool> {
1751        if let Some(application) = self.application_function_precanonical(canonical, num_args) {
1752            return match application {
1753                ApplicationFunction::Scalar {
1754                    consumes_argument_collation,
1755                    ..
1756                } => Some(*consumes_argument_collation),
1757                ApplicationFunction::Aggregate { .. } | ApplicationFunction::Window { .. } => None,
1758            };
1759        }
1760        let exact = FunctionKey {
1761            name: canonical.to_owned(),
1762            num_args,
1763        };
1764        if self.scalars.contains_key(&exact) {
1765            if !self
1766                .scalar_arities
1767                .get(&exact)
1768                .is_some_and(|arity| arity.accepts(num_args))
1769            {
1770                return None;
1771            }
1772            return Some(
1773                self.scalar_argument_collation
1774                    .get(&exact)
1775                    .copied()
1776                    .unwrap_or(false),
1777            );
1778        }
1779
1780        let variadic = FunctionKey {
1781            name: canonical.to_owned(),
1782            num_args: -1,
1783        };
1784        if self.scalars.contains_key(&variadic) {
1785            if !self
1786                .scalar_arities
1787                .get(&variadic)
1788                .is_some_and(|arity| arity.accepts(num_args))
1789            {
1790                return None;
1791            }
1792            return Some(
1793                self.scalar_argument_collation
1794                    .get(&variadic)
1795                    .copied()
1796                    .unwrap_or(false),
1797            );
1798        }
1799        None
1800    }
1801
1802    /// Return whether a known aggregate function accepts the SQL-visible arity.
1803    ///
1804    /// `None` means the name is not registered as an aggregate function at
1805    /// all. This is the side-effect-free counterpart to
1806    /// [`Self::find_aggregate`] for preparation-only validation.
1807    #[must_use]
1808    pub fn aggregate_accepts_arg_count(&self, name: &str, num_args: i32) -> Option<bool> {
1809        let canon = canonical_name(name);
1810        if let Some(application) = self.application_function_precanonical(&canon, num_args) {
1811            return application.kind().is_aggregate_callable().then_some(true);
1812        }
1813        let exact = FunctionKey {
1814            name: canon.clone(),
1815            num_args,
1816        };
1817        if self.aggregates.contains_key(&exact) {
1818            return Some(
1819                self.aggregate_arities
1820                    .get(&exact)
1821                    .is_some_and(|arity| arity.accepts(num_args)),
1822            );
1823        }
1824
1825        let variadic = FunctionKey {
1826            name: canon.clone(),
1827            num_args: -1,
1828        };
1829        if self.aggregates.contains_key(&variadic) {
1830            return Some(
1831                self.aggregate_arities
1832                    .get(&variadic)
1833                    .is_some_and(|arity| arity.accepts(num_args)),
1834            );
1835        }
1836
1837        (self.aggregates.keys().any(|key| key.name == canon)
1838            || self.application_name_has_kind_precanonical(&canon, |kind| {
1839                kind.is_aggregate_callable()
1840            }))
1841        .then_some(false)
1842    }
1843
1844    /// Return whether a known window function accepts the SQL-visible arity.
1845    ///
1846    /// `None` means the name is not registered as a window function at all.
1847    /// This is useful for callers that may execute optimized window paths
1848    /// without invoking the returned function's `step()` method, where the
1849    /// wrong-arity sentinel from `find_window` would otherwise be bypassed.
1850    #[must_use]
1851    pub fn window_accepts_arg_count(&self, name: &str, num_args: i32) -> Option<bool> {
1852        let canon = canonical_name(name);
1853        if let Some(application) = self.application_function_precanonical(&canon, num_args) {
1854            return (application.kind() == ApplicationFunctionKind::Window).then_some(true);
1855        }
1856        let exact = FunctionKey {
1857            name: canon.clone(),
1858            num_args,
1859        };
1860        if self.windows.contains_key(&exact) {
1861            return Some(
1862                self.window_arities
1863                    .get(&exact)
1864                    .is_some_and(|arity| arity.accepts(num_args)),
1865            );
1866        }
1867
1868        let variadic = FunctionKey {
1869            name: canon.clone(),
1870            num_args: -1,
1871        };
1872        if self.windows.contains_key(&variadic) {
1873            return Some(
1874                self.window_arities
1875                    .get(&variadic)
1876                    .is_some_and(|arity| arity.accepts(num_args)),
1877            );
1878        }
1879
1880        (self.windows.keys().any(|key| key.name == canon)
1881            || self.application_name_has_kind_precanonical(&canon, |kind| {
1882                kind == ApplicationFunctionKind::Window
1883            }))
1884        .then_some(false)
1885    }
1886
1887    /// Return deduplicated lowercase names of all registered aggregate functions.
1888    ///
1889    /// Used by the codegen thread-local to recognize custom aggregate UDFs.
1890    #[must_use]
1891    pub fn aggregate_names_lowercase(&self) -> Vec<String> {
1892        let mut names: Vec<String> = self
1893            .aggregates
1894            .keys()
1895            .map(|k| k.name.to_ascii_lowercase())
1896            .chain(
1897                self.application_functions
1898                    .iter()
1899                    .filter(|(_, overloads)| {
1900                        overloads
1901                            .values()
1902                            .any(|function| function.kind().is_aggregate_callable())
1903                    })
1904                    .map(|(name, _)| name.to_ascii_lowercase()),
1905            )
1906            .collect();
1907        names.sort();
1908        names.dedup();
1909        names
1910    }
1911}
1912
1913fn extend_builtin_surface_entries<'a>(
1914    entries: &mut Vec<BuiltinFunctionSurfaceEntry>,
1915    family: BuiltinFunctionFamily,
1916    keys: impl Iterator<Item = &'a FunctionKey>,
1917) {
1918    for key in keys {
1919        let name = key.name.to_ascii_lowercase();
1920        let class = builtin_function_class(&name, family);
1921        entries.push(BuiltinFunctionSurfaceEntry {
1922            is_alias: builtin_function_alias_flag(&name, family),
1923            surface_id: builtin_function_surface_id(family),
1924            name,
1925            num_args: key.num_args,
1926            family,
1927            class,
1928        });
1929    }
1930}
1931
1932fn builtin_function_class(name: &str, family: BuiltinFunctionFamily) -> BuiltinFunctionClass {
1933    match family {
1934        BuiltinFunctionFamily::Aggregate => BuiltinFunctionClass::Aggregate,
1935        BuiltinFunctionFamily::Window => BuiltinFunctionClass::Window,
1936        BuiltinFunctionFamily::Scalar => {
1937            if matches!(
1938                name,
1939                "acos"
1940                    | "acosh"
1941                    | "asin"
1942                    | "asinh"
1943                    | "atan"
1944                    | "atan2"
1945                    | "atanh"
1946                    | "ceil"
1947                    | "ceiling"
1948                    | "cos"
1949                    | "cosh"
1950                    | "degrees"
1951                    | "exp"
1952                    | "floor"
1953                    | "ln"
1954                    | "log"
1955                    | "log10"
1956                    | "log2"
1957                    | "mod"
1958                    | "pi"
1959                    | "pow"
1960                    | "power"
1961                    | "radians"
1962                    | "sin"
1963                    | "sinh"
1964                    | "sqrt"
1965                    | "tan"
1966                    | "tanh"
1967                    | "trunc"
1968            ) {
1969                BuiltinFunctionClass::MathScalar
1970            } else if matches!(
1971                name,
1972                "date" | "datetime" | "julianday" | "strftime" | "time" | "timediff" | "unixepoch"
1973            ) {
1974                BuiltinFunctionClass::DateTimeScalar
1975            } else {
1976                BuiltinFunctionClass::CoreScalar
1977            }
1978        }
1979    }
1980}
1981
1982fn builtin_function_alias_flag(name: &str, family: BuiltinFunctionFamily) -> bool {
1983    match family {
1984        BuiltinFunctionFamily::Scalar => {
1985            matches!(name, "ceiling" | "if" | "power" | "printf" | "substring")
1986        }
1987        BuiltinFunctionFamily::Aggregate | BuiltinFunctionFamily::Window => name == "string_agg",
1988    }
1989}
1990
1991const fn builtin_function_surface_id(family: BuiltinFunctionFamily) -> &'static str {
1992    match family {
1993        BuiltinFunctionFamily::Window => WINDOW_FUNCTION_SURFACE_ID,
1994        BuiltinFunctionFamily::Scalar | BuiltinFunctionFamily::Aggregate => {
1995            CORE_FUNCTION_SURFACE_ID
1996        }
1997    }
1998}
1999
2000fn canonical_name(name: &str) -> String {
2001    name.trim().to_ascii_uppercase()
2002}
2003
2004#[cfg(test)]
2005mod tests {
2006    use std::collections::BTreeSet;
2007
2008    use fsqlite_types::SqliteValue;
2009
2010    use super::*;
2011
2012    #[test]
2013    fn keyed_registration_preserves_bounds_without_reinvoking_user_metadata() {
2014        struct MetadataPanicsScalar;
2015
2016        impl ScalarFunction for MetadataPanicsScalar {
2017            fn invoke(&self, _args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
2018                Ok(SqliteValue::Integer(1))
2019            }
2020
2021            fn num_args(&self) -> i32 {
2022                panic!("keyed scalar registration must not ask for arity")
2023            }
2024
2025            fn is_deterministic(&self) -> bool {
2026                panic!("keyed scalar registration must not ask for determinism")
2027            }
2028
2029            fn consumes_argument_collation(&self) -> bool {
2030                panic!("keyed scalar registration must not ask for collation metadata")
2031            }
2032
2033            fn name(&self) -> &str {
2034                panic!("keyed scalar registration must not ask for name")
2035            }
2036        }
2037
2038        struct MetadataPanicsAggregate;
2039
2040        impl AggregateFunction for MetadataPanicsAggregate {
2041            type State = ();
2042
2043            fn initial_state(&self) -> Self::State {}
2044
2045            fn step(
2046                &self,
2047                _state: &mut Self::State,
2048                _args: &[SqliteValue],
2049            ) -> fsqlite_error::Result<()> {
2050                Ok(())
2051            }
2052
2053            fn finalize(&self, _state: Self::State) -> fsqlite_error::Result<SqliteValue> {
2054                Ok(SqliteValue::Integer(1))
2055            }
2056
2057            fn num_args(&self) -> i32 {
2058                panic!("keyed aggregate registration must not ask for arity")
2059            }
2060
2061            fn name(&self) -> &str {
2062                panic!("keyed aggregate registration must not ask for name")
2063            }
2064        }
2065
2066        struct MetadataPanicsWindow;
2067
2068        impl WindowFunction for MetadataPanicsWindow {
2069            type State = ();
2070
2071            fn initial_state(&self) -> Self::State {}
2072
2073            fn step(
2074                &self,
2075                _state: &mut Self::State,
2076                _args: &[SqliteValue],
2077            ) -> fsqlite_error::Result<()> {
2078                Ok(())
2079            }
2080
2081            fn inverse(
2082                &self,
2083                _state: &mut Self::State,
2084                _args: &[SqliteValue],
2085            ) -> fsqlite_error::Result<()> {
2086                Ok(())
2087            }
2088
2089            fn value(&self, _state: &Self::State) -> fsqlite_error::Result<SqliteValue> {
2090                Ok(SqliteValue::Integer(1))
2091            }
2092
2093            fn finalize(&self, _state: Self::State) -> fsqlite_error::Result<SqliteValue> {
2094                Ok(SqliteValue::Integer(1))
2095            }
2096
2097            fn num_args(&self) -> i32 {
2098                panic!("keyed window registration must not ask for arity")
2099            }
2100
2101            fn name(&self) -> &str {
2102                panic!("keyed window registration must not ask for name")
2103            }
2104        }
2105
2106        let arity = FunctionArity::variadic(1, Some(2));
2107        let scalar_key = FunctionKey::new("keyed_scalar", -1);
2108        let aggregate_key = FunctionKey::new("keyed_aggregate", -1);
2109        let window_key = FunctionKey::new("keyed_window", -1);
2110        let mut registry = FunctionRegistry::new();
2111        let scalar_displaced = registry.register_scalar_keyed(
2112            scalar_key.clone(),
2113            arity,
2114            false,
2115            false,
2116            MetadataPanicsScalar,
2117        );
2118        assert!(scalar_displaced.is_none());
2119        assert!(registry.find_scalar("keyed_scalar", 1).is_some());
2120
2121        let aggregate_displaced = registry.register_aggregate_keyed(
2122            aggregate_key.clone(),
2123            arity,
2124            MetadataPanicsAggregate,
2125        );
2126        assert!(aggregate_displaced.is_none());
2127        assert!(registry.find_aggregate("keyed_aggregate", 1).is_some());
2128
2129        let (window, window_displaced) =
2130            registry.register_window_keyed(window_key.clone(), arity, MetadataPanicsWindow);
2131        assert!(window_displaced.is_none());
2132        assert!(Arc::ptr_eq(
2133            &window,
2134            &registry.find_window("keyed_window", 1).unwrap()
2135        ));
2136
2137        for num_args in [1, 2] {
2138            assert_eq!(
2139                registry.scalar_accepts_arg_count("keyed_scalar", num_args),
2140                Some(true)
2141            );
2142            assert_eq!(
2143                registry.aggregate_accepts_arg_count("keyed_aggregate", num_args),
2144                Some(true)
2145            );
2146            assert_eq!(
2147                registry.window_accepts_arg_count("keyed_window", num_args),
2148                Some(true)
2149            );
2150            assert_eq!(
2151                registry.scalar_is_deterministic("keyed_scalar", num_args),
2152                Some(false)
2153            );
2154            assert_eq!(
2155                registry
2156                    .resolve_scalar("keyed_scalar", num_args)
2157                    .unwrap()
2158                    .query_constancy(),
2159                ScalarQueryConstancy::Volatile
2160            );
2161        }
2162        for num_args in [0, 3] {
2163            assert_eq!(
2164                registry.scalar_accepts_arg_count("keyed_scalar", num_args),
2165                Some(false)
2166            );
2167            assert_eq!(
2168                registry.aggregate_accepts_arg_count("keyed_aggregate", num_args),
2169                Some(false)
2170            );
2171            assert_eq!(
2172                registry.window_accepts_arg_count("keyed_window", num_args),
2173                Some(false)
2174            );
2175        }
2176
2177        registry.scalar_arities.remove(&scalar_key);
2178        registry.aggregate_arities.remove(&aggregate_key);
2179        registry.window_arities.remove(&window_key);
2180        assert_eq!(
2181            registry.scalar_accepts_arg_count("keyed_scalar", 1),
2182            Some(false)
2183        );
2184        assert_eq!(
2185            registry.scalar_is_deterministic("keyed_scalar", 1),
2186            Some(false)
2187        );
2188        assert_eq!(
2189            registry.aggregate_accepts_arg_count("keyed_aggregate", 1),
2190            Some(false)
2191        );
2192        assert_eq!(
2193            registry.window_accepts_arg_count("keyed_window", 1),
2194            Some(false)
2195        );
2196        assert_wrong_arg_count(
2197            registry.find_scalar("keyed_scalar", 1).unwrap().as_ref(),
2198            &[SqliteValue::Null],
2199            "keyed_scalar",
2200        );
2201        assert_wrong_arg_count_aggregate(
2202            registry
2203                .find_aggregate("keyed_aggregate", 1)
2204                .unwrap()
2205                .as_ref(),
2206            &[SqliteValue::Null],
2207            "keyed_aggregate",
2208        );
2209        assert_wrong_arg_count_window(
2210            registry.find_window("keyed_window", 1).unwrap().as_ref(),
2211            &[SqliteValue::Null],
2212            "keyed_window",
2213        );
2214    }
2215
2216    #[test]
2217    fn exact_registration_fails_closed_when_parallel_arity_metadata_is_missing() {
2218        let scalar_key = FunctionKey::new("double", 1);
2219        let aggregate_key = FunctionKey::new("product", 1);
2220        let window_key = FunctionKey::new("moving_sum", 1);
2221        let mut registry = FunctionRegistry::new();
2222        registry.register_scalar(Double);
2223        registry.register_aggregate(Product);
2224        registry.register_window(MovingSum);
2225
2226        assert_eq!(
2227            registry.scalar_arities.remove(&scalar_key),
2228            Some(FunctionArity::exact(1))
2229        );
2230        assert_eq!(
2231            registry.aggregate_arities.remove(&aggregate_key),
2232            Some(FunctionArity::exact(1))
2233        );
2234        assert_eq!(
2235            registry.window_arities.remove(&window_key),
2236            Some(FunctionArity::exact(1))
2237        );
2238
2239        assert_eq!(registry.scalar_accepts_arg_count("double", 1), Some(false));
2240        assert_eq!(
2241            registry.aggregate_accepts_arg_count("product", 1),
2242            Some(false)
2243        );
2244        assert_eq!(
2245            registry.window_accepts_arg_count("moving_sum", 1),
2246            Some(false)
2247        );
2248        assert_wrong_arg_count(
2249            registry.find_scalar("double", 1).unwrap().as_ref(),
2250            &[SqliteValue::Null],
2251            "double",
2252        );
2253        assert_wrong_arg_count_aggregate(
2254            registry.find_aggregate("product", 1).unwrap().as_ref(),
2255            &[SqliteValue::Null],
2256            "product",
2257        );
2258        assert_wrong_arg_count_window(
2259            registry.find_window("moving_sum", 1).unwrap().as_ref(),
2260            &[SqliteValue::Null],
2261            "moving_sum",
2262        );
2263    }
2264
2265    #[test]
2266    #[should_panic(
2267        expected = "function key and frozen arity contract must have the same declared argument count"
2268    )]
2269    fn keyed_registration_rejects_mismatched_arity_contract() {
2270        assert_key_matches_arity(&FunctionKey::new("mismatched", -1), FunctionArity::exact(1));
2271    }
2272
2273    #[test]
2274    #[should_panic(expected = "function argument count must be -1 or non-negative")]
2275    fn function_key_rejects_argument_counts_below_variadic_sentinel() {
2276        let _ = FunctionKey::new("invalid", -2);
2277    }
2278
2279    #[test]
2280    #[should_panic(expected = "function argument count must be -1 or non-negative")]
2281    fn default_arity_contract_rejects_argument_counts_below_variadic_sentinel() {
2282        let _ = FunctionArity::from_declared_args(-2, || {
2283            panic!("invalid declared arity must be rejected before reading variadic bounds")
2284        });
2285    }
2286
2287    fn runtime_registry_surface_keys() -> BTreeSet<(BuiltinFunctionFamily, String, i32)> {
2288        let mut registry = FunctionRegistry::new();
2289        register_builtins(&mut registry);
2290        register_window_builtins(&mut registry);
2291
2292        let scalar_keys = registry
2293            .scalars
2294            .keys()
2295            .map(|key| {
2296                (
2297                    BuiltinFunctionFamily::Scalar,
2298                    key.name.to_ascii_lowercase(),
2299                    key.num_args,
2300                )
2301            })
2302            .collect::<BTreeSet<_>>();
2303        let aggregate_keys = registry
2304            .aggregates
2305            .keys()
2306            .map(|key| {
2307                (
2308                    BuiltinFunctionFamily::Aggregate,
2309                    key.name.to_ascii_lowercase(),
2310                    key.num_args,
2311                )
2312            })
2313            .collect::<BTreeSet<_>>();
2314        let window_keys = registry
2315            .windows
2316            .keys()
2317            .map(|key| {
2318                (
2319                    BuiltinFunctionFamily::Window,
2320                    key.name.to_ascii_lowercase(),
2321                    key.num_args,
2322                )
2323            })
2324            .collect::<BTreeSet<_>>();
2325
2326        scalar_keys
2327            .into_iter()
2328            .chain(aggregate_keys)
2329            .chain(window_keys)
2330            .collect()
2331    }
2332
2333    fn inventory_surface_keys() -> BTreeSet<(BuiltinFunctionFamily, String, i32)> {
2334        builtin_function_surface_inventory()
2335            .iter()
2336            .map(|entry| (entry.family, entry.name.clone(), entry.num_args))
2337            .collect()
2338    }
2339
2340    fn find_surface_entry(
2341        family: BuiltinFunctionFamily,
2342        name: &str,
2343        num_args: i32,
2344    ) -> &'static BuiltinFunctionSurfaceEntry {
2345        builtin_function_surface_inventory()
2346            .iter()
2347            .find(|entry| {
2348                entry.family == family && entry.name == name && entry.num_args == num_args
2349            })
2350            .unwrap_or_else(|| {
2351                unreachable!(
2352                    "missing builtin surface entry: family={} name={} arity={}",
2353                    family.label(),
2354                    name,
2355                    num_args
2356                )
2357            })
2358    }
2359
2360    #[test]
2361    fn test_builtin_function_surface_inventory_matches_live_registry() {
2362        let inventory = builtin_function_surface_inventory();
2363        let inventory_keys = inventory_surface_keys();
2364        let runtime_keys = runtime_registry_surface_keys();
2365
2366        assert_eq!(
2367            inventory.len(),
2368            inventory_keys.len(),
2369            "inventory must not contain duplicate family/name/arity tuples"
2370        );
2371        assert_eq!(
2372            inventory_keys, runtime_keys,
2373            "inventory must exactly match the live registration path"
2374        );
2375        assert!(
2376            inventory.windows(2).all(|entries| {
2377                (
2378                    entries[0].family,
2379                    entries[0].class,
2380                    &entries[0].name,
2381                    entries[0].num_args,
2382                ) <= (
2383                    entries[1].family,
2384                    entries[1].class,
2385                    &entries[1].name,
2386                    entries[1].num_args,
2387                )
2388            }),
2389            "inventory must stay deterministically sorted"
2390        );
2391    }
2392
2393    #[test]
2394    fn test_builtin_function_surface_inventory_classifies_representative_entries() {
2395        let abs = find_surface_entry(BuiltinFunctionFamily::Scalar, "abs", 1);
2396        assert_eq!(abs.class, BuiltinFunctionClass::CoreScalar);
2397        assert!(!abs.is_alias);
2398        assert_eq!(abs.surface_id, CORE_FUNCTION_SURFACE_ID);
2399
2400        let date = find_surface_entry(BuiltinFunctionFamily::Scalar, "date", -1);
2401        assert_eq!(date.class, BuiltinFunctionClass::DateTimeScalar);
2402        assert!(!date.is_alias);
2403        assert_eq!(date.surface_id, CORE_FUNCTION_SURFACE_ID);
2404
2405        let power = find_surface_entry(BuiltinFunctionFamily::Scalar, "power", 2);
2406        assert_eq!(power.class, BuiltinFunctionClass::MathScalar);
2407        assert!(power.is_alias);
2408        assert_eq!(power.surface_id, CORE_FUNCTION_SURFACE_ID);
2409
2410        let count = find_surface_entry(BuiltinFunctionFamily::Aggregate, "count", 0);
2411        assert_eq!(count.class, BuiltinFunctionClass::Aggregate);
2412        assert!(!count.is_alias);
2413        assert_eq!(count.surface_id, CORE_FUNCTION_SURFACE_ID);
2414
2415        let row_number = find_surface_entry(BuiltinFunctionFamily::Window, "row_number", 0);
2416        assert_eq!(row_number.class, BuiltinFunctionClass::Window);
2417        assert!(!row_number.is_alias);
2418        assert_eq!(row_number.surface_id, WINDOW_FUNCTION_SURFACE_ID);
2419
2420        let string_agg_window = find_surface_entry(BuiltinFunctionFamily::Window, "string_agg", 2);
2421        assert_eq!(string_agg_window.class, BuiltinFunctionClass::Window);
2422        assert!(string_agg_window.is_alias);
2423        assert_eq!(string_agg_window.surface_id, WINDOW_FUNCTION_SURFACE_ID);
2424    }
2425
2426    // -- Mock: double(x) -> x * 2, fixed 1-arg --
2427
2428    struct Double;
2429
2430    impl ScalarFunction for Double {
2431        fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
2432            Ok(SqliteValue::Integer(args[0].to_integer() * 2))
2433        }
2434
2435        fn num_args(&self) -> i32 {
2436            1
2437        }
2438
2439        fn name(&self) -> &str {
2440            "double"
2441        }
2442    }
2443
2444    // -- Mock: variadic concat --
2445
2446    struct VariadicConcat;
2447
2448    impl ScalarFunction for VariadicConcat {
2449        fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
2450            let mut out = String::new();
2451            for a in args {
2452                out.push_str(&a.to_text());
2453            }
2454            Ok(SqliteValue::Text(out.into()))
2455        }
2456
2457        fn num_args(&self) -> i32 {
2458            -1
2459        }
2460
2461        fn min_args(&self) -> i32 {
2462            1
2463        }
2464
2465        fn max_args(&self) -> Option<i32> {
2466            Some(3)
2467        }
2468
2469        fn name(&self) -> &str {
2470            "my_func"
2471        }
2472    }
2473
2474    // -- Mock: fixed 2-arg version of same name --
2475
2476    struct TwoArgFunc;
2477
2478    impl ScalarFunction for TwoArgFunc {
2479        fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
2480            Ok(SqliteValue::Integer(
2481                args[0].to_integer() + args[1].to_integer(),
2482            ))
2483        }
2484
2485        fn num_args(&self) -> i32 {
2486            2
2487        }
2488
2489        fn name(&self) -> &str {
2490            "my_func"
2491        }
2492    }
2493
2494    fn assert_wrong_arg_count(
2495        function: &dyn ScalarFunction,
2496        args: &[SqliteValue],
2497        expected_name: &str,
2498    ) {
2499        let err = function.invoke(args).expect_err("wrong arity should fail");
2500        let expected = format!("wrong number of arguments to function {expected_name}()");
2501        assert!(
2502            matches!(&err, FrankenError::FunctionError(message) if message == &expected),
2503            "expected {expected:?}, got {err:?}"
2504        );
2505    }
2506
2507    fn assert_wrong_arg_count_aggregate(
2508        function: &ErasedAggregateFunction,
2509        args: &[SqliteValue],
2510        expected_name: &str,
2511    ) {
2512        let mut state = function.initial_state();
2513        let err = function
2514            .step(&mut state, args)
2515            .expect_err("wrong aggregate arity should fail");
2516        let expected = format!("wrong number of arguments to function {expected_name}()");
2517        assert!(
2518            matches!(&err, FrankenError::FunctionError(message) if message == &expected),
2519            "expected {expected:?}, got {err:?}"
2520        );
2521    }
2522
2523    fn assert_wrong_arg_count_window(
2524        function: &ErasedWindowFunction,
2525        args: &[SqliteValue],
2526        expected_name: &str,
2527    ) {
2528        let mut state = function.initial_state();
2529        let err = function
2530            .step(&mut state, args)
2531            .expect_err("wrong window arity should fail");
2532        let expected = format!("wrong number of arguments to function {expected_name}()");
2533        assert!(
2534            matches!(&err, FrankenError::FunctionError(message) if message == &expected),
2535            "expected {expected:?}, got {err:?}"
2536        );
2537    }
2538
2539    struct Product;
2540
2541    impl AggregateFunction for Product {
2542        type State = i64;
2543
2544        fn initial_state(&self) -> Self::State {
2545            1
2546        }
2547
2548        fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> fsqlite_error::Result<()> {
2549            *state *= args[0].to_integer();
2550            Ok(())
2551        }
2552
2553        fn finalize(&self, state: Self::State) -> fsqlite_error::Result<SqliteValue> {
2554            Ok(SqliteValue::Integer(state))
2555        }
2556
2557        fn num_args(&self) -> i32 {
2558            1
2559        }
2560
2561        fn name(&self) -> &str {
2562            "product"
2563        }
2564    }
2565
2566    struct MovingSum;
2567
2568    impl WindowFunction for MovingSum {
2569        type State = i64;
2570
2571        fn initial_state(&self) -> Self::State {
2572            0
2573        }
2574
2575        fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> fsqlite_error::Result<()> {
2576            *state += args[0].to_integer();
2577            Ok(())
2578        }
2579
2580        fn inverse(
2581            &self,
2582            state: &mut Self::State,
2583            args: &[SqliteValue],
2584        ) -> fsqlite_error::Result<()> {
2585            *state -= args[0].to_integer();
2586            Ok(())
2587        }
2588
2589        fn value(&self, state: &Self::State) -> fsqlite_error::Result<SqliteValue> {
2590            Ok(SqliteValue::Integer(*state))
2591        }
2592
2593        fn finalize(&self, state: Self::State) -> fsqlite_error::Result<SqliteValue> {
2594            Ok(SqliteValue::Integer(state))
2595        }
2596
2597        fn num_args(&self) -> i32 {
2598            1
2599        }
2600
2601        fn name(&self) -> &str {
2602            "moving_sum"
2603        }
2604    }
2605
2606    struct TaggedScalar {
2607        name: &'static str,
2608        num_args: i32,
2609        tag: i64,
2610    }
2611
2612    impl ScalarFunction for TaggedScalar {
2613        fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
2614            Ok(SqliteValue::Integer(
2615                self.tag + args.iter().map(SqliteValue::to_integer).sum::<i64>(),
2616            ))
2617        }
2618
2619        fn num_args(&self) -> i32 {
2620            self.num_args
2621        }
2622
2623        fn name(&self) -> &str {
2624            self.name
2625        }
2626    }
2627
2628    struct TaggedAggregate {
2629        name: &'static str,
2630        num_args: i32,
2631        tag: i64,
2632    }
2633
2634    impl AggregateFunction for TaggedAggregate {
2635        type State = i64;
2636
2637        fn initial_state(&self) -> Self::State {
2638            0
2639        }
2640
2641        fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> fsqlite_error::Result<()> {
2642            *state += args.iter().map(SqliteValue::to_integer).sum::<i64>();
2643            Ok(())
2644        }
2645
2646        fn finalize(&self, state: Self::State) -> fsqlite_error::Result<SqliteValue> {
2647            Ok(SqliteValue::Integer(self.tag + state))
2648        }
2649
2650        fn num_args(&self) -> i32 {
2651            self.num_args
2652        }
2653
2654        fn name(&self) -> &str {
2655            self.name
2656        }
2657    }
2658
2659    struct TaggedWindow {
2660        name: &'static str,
2661        num_args: i32,
2662        tag: i64,
2663    }
2664
2665    impl WindowFunction for TaggedWindow {
2666        type State = i64;
2667
2668        fn initial_state(&self) -> Self::State {
2669            0
2670        }
2671
2672        fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> fsqlite_error::Result<()> {
2673            *state += args.iter().map(SqliteValue::to_integer).sum::<i64>();
2674            Ok(())
2675        }
2676
2677        fn inverse(
2678            &self,
2679            state: &mut Self::State,
2680            args: &[SqliteValue],
2681        ) -> fsqlite_error::Result<()> {
2682            *state -= args.iter().map(SqliteValue::to_integer).sum::<i64>();
2683            Ok(())
2684        }
2685
2686        fn value(&self, state: &Self::State) -> fsqlite_error::Result<SqliteValue> {
2687            Ok(SqliteValue::Integer(self.tag + state))
2688        }
2689
2690        fn finalize(&self, state: Self::State) -> fsqlite_error::Result<SqliteValue> {
2691            Ok(SqliteValue::Integer(self.tag + state))
2692        }
2693
2694        fn num_args(&self) -> i32 {
2695            self.num_args
2696        }
2697
2698        fn name(&self) -> &str {
2699            self.name
2700        }
2701    }
2702
2703    fn finalize_aggregate(
2704        function: &ErasedAggregateFunction,
2705        rows: &[&[SqliteValue]],
2706    ) -> SqliteValue {
2707        let mut state = function.initial_state();
2708        for args in rows {
2709            function.step(&mut state, args).unwrap();
2710        }
2711        function.finalize(state).unwrap()
2712    }
2713
2714    #[test]
2715    fn application_resolution_prefers_exact_across_function_kinds() {
2716        let mut registry = FunctionRegistry::new();
2717        registry.register_application_aggregate_captured(
2718            "app_precedence",
2719            FunctionArity::variadic(0, None),
2720            TaggedAggregate {
2721                name: "app_precedence",
2722                num_args: -1,
2723                tag: 20_000,
2724            },
2725        );
2726        registry.register_application_scalar_captured(
2727            "app_precedence",
2728            FunctionArity::exact(1),
2729            true,
2730            false,
2731            TaggedScalar {
2732                name: "app_precedence",
2733                num_args: 1,
2734                tag: 10_000,
2735            },
2736        );
2737
2738        assert_eq!(
2739            registry
2740                .resolve_application_function("APP_PRECEDENCE", 1)
2741                .map(ApplicationFunctionResolution::kind),
2742            Some(ApplicationFunctionKind::Scalar)
2743        );
2744        assert_eq!(
2745            registry
2746                .find_scalar("app_precedence", 1)
2747                .unwrap()
2748                .invoke(&[SqliteValue::Integer(7)])
2749                .unwrap(),
2750            SqliteValue::Integer(10_007)
2751        );
2752        assert!(registry.find_aggregate("app_precedence", 1).is_none());
2753
2754        assert_eq!(
2755            registry
2756                .resolve_application_function("app_precedence", 2)
2757                .map(ApplicationFunctionResolution::kind),
2758            Some(ApplicationFunctionKind::Aggregate)
2759        );
2760        assert!(registry.find_scalar("app_precedence", 2).is_none());
2761        let args = [SqliteValue::Integer(2), SqliteValue::Integer(3)];
2762        assert_eq!(
2763            finalize_aggregate(
2764                registry
2765                    .find_aggregate("app_precedence", 2)
2766                    .unwrap()
2767                    .as_ref(),
2768                &[&args],
2769            ),
2770            SqliteValue::Integer(20_005)
2771        );
2772
2773        let mut reverse = FunctionRegistry::new();
2774        reverse.register_application_scalar_captured(
2775            "app_precedence",
2776            FunctionArity::variadic(0, None),
2777            true,
2778            false,
2779            TaggedScalar {
2780                name: "app_precedence",
2781                num_args: -1,
2782                tag: 30_000,
2783            },
2784        );
2785        reverse.register_application_aggregate_captured(
2786            "app_precedence",
2787            FunctionArity::exact(1),
2788            TaggedAggregate {
2789                name: "app_precedence",
2790                num_args: 1,
2791                tag: 40_000,
2792            },
2793        );
2794        assert_eq!(
2795            reverse
2796                .resolve_application_function("app_precedence", 1)
2797                .map(ApplicationFunctionResolution::kind),
2798            Some(ApplicationFunctionKind::Aggregate)
2799        );
2800        assert!(reverse.find_scalar("app_precedence", 1).is_none());
2801        assert_eq!(
2802            reverse
2803                .resolve_application_function("app_precedence", 2)
2804                .map(ApplicationFunctionResolution::kind),
2805            Some(ApplicationFunctionKind::Scalar)
2806        );
2807    }
2808
2809    #[test]
2810    fn application_variadic_shadows_builtin_exact_only_when_compatible() {
2811        let mut registry = FunctionRegistry::new();
2812        registry.register_scalar(TaggedScalar {
2813            name: "layered",
2814            num_args: 1,
2815            tag: 1_000,
2816        });
2817        registry.register_application_scalar_captured(
2818            "layered",
2819            FunctionArity::variadic(2, Some(3)),
2820            true,
2821            false,
2822            TaggedScalar {
2823                name: "layered",
2824                num_args: -1,
2825                tag: 2_000,
2826            },
2827        );
2828
2829        assert_eq!(registry.resolve_application_function("layered", 1), None);
2830        assert_eq!(
2831            registry
2832                .find_scalar("layered", 1)
2833                .unwrap()
2834                .invoke(&[SqliteValue::Integer(7)])
2835                .unwrap(),
2836            SqliteValue::Integer(1_007),
2837            "an incompatible application variadic must leave the builtin layer visible"
2838        );
2839        let two_args = [SqliteValue::Integer(7), SqliteValue::Integer(8)];
2840        assert_eq!(
2841            registry
2842                .find_scalar("layered", 2)
2843                .unwrap()
2844                .invoke(&two_args)
2845                .unwrap(),
2846            SqliteValue::Integer(2_015)
2847        );
2848
2849        let displaced = registry.register_application_scalar_captured(
2850            "layered",
2851            FunctionArity::variadic(0, None),
2852            true,
2853            false,
2854            TaggedScalar {
2855                name: "layered",
2856                num_args: -1,
2857                tag: 3_000,
2858            },
2859        );
2860        assert!(displaced.is_some());
2861        assert_eq!(
2862            registry
2863                .find_scalar("layered", 1)
2864                .unwrap()
2865                .invoke(&[SqliteValue::Integer(7)])
2866                .unwrap(),
2867            SqliteValue::Integer(3_007),
2868            "a compatible application variadic must shadow an exact builtin"
2869        );
2870
2871        registry.register_aggregate(TaggedAggregate {
2872            name: "sum_like",
2873            num_args: 1,
2874            tag: 4_000,
2875        });
2876        registry.register_application_scalar_captured(
2877            "sum_like",
2878            FunctionArity::variadic(0, None),
2879            true,
2880            false,
2881            TaggedScalar {
2882                name: "sum_like",
2883                num_args: -1,
2884                tag: 5_000,
2885            },
2886        );
2887        assert!(registry.find_aggregate("sum_like", 1).is_none());
2888        assert!(registry.find_scalar("sum_like", 1).is_some());
2889
2890        let bounded_window_arity = FunctionArity::variadic(1, Some(2));
2891        registry.register_application_window_captured(
2892            "bounded_window",
2893            bounded_window_arity,
2894            TaggedWindow {
2895                name: "bounded_window",
2896                num_args: -1,
2897                tag: 6_000,
2898            },
2899        );
2900        assert_eq!(
2901            registry
2902                .find_aggregate("bounded_window", 2)
2903                .unwrap()
2904                .arity(),
2905            bounded_window_arity,
2906            "the aggregate bridge must preserve frozen variadic bounds"
2907        );
2908        assert_eq!(
2909            registry.aggregate_accepts_arg_count("bounded_window", 0),
2910            Some(false)
2911        );
2912        assert_eq!(
2913            registry.window_accepts_arg_count("bounded_window", 3),
2914            Some(false)
2915        );
2916    }
2917
2918    #[test]
2919    fn same_application_key_replaces_kind_and_window_retains_aggregate_form() {
2920        let mut registry = FunctionRegistry::new();
2921        let mut displaced = Vec::new();
2922        assert!(
2923            registry
2924                .register_application_scalar_captured(
2925                    "cross_kind",
2926                    FunctionArity::exact(1),
2927                    true,
2928                    false,
2929                    TaggedScalar {
2930                        name: "cross_kind",
2931                        num_args: 1,
2932                        tag: 70_000,
2933                    },
2934                )
2935                .is_none()
2936        );
2937
2938        displaced.push(
2939            registry
2940                .register_application_aggregate_captured(
2941                    "cross_kind",
2942                    FunctionArity::exact(1),
2943                    TaggedAggregate {
2944                        name: "cross_kind",
2945                        num_args: 1,
2946                        tag: 80_000,
2947                    },
2948                )
2949                .expect("aggregate must displace same-key scalar"),
2950        );
2951        assert_eq!(
2952            registry
2953                .resolve_application_function("cross_kind", 1)
2954                .map(ApplicationFunctionResolution::kind),
2955            Some(ApplicationFunctionKind::Aggregate)
2956        );
2957        assert!(registry.find_scalar("cross_kind", 1).is_none());
2958        assert!(registry.find_window("cross_kind", 1).is_none());
2959
2960        let (registered_window, old_aggregate) = registry.register_application_window_captured(
2961            "cross_kind",
2962            FunctionArity::exact(1),
2963            TaggedWindow {
2964                name: "cross_kind",
2965                num_args: 1,
2966                tag: 90_000,
2967            },
2968        );
2969        displaced.push(old_aggregate.expect("window must displace same-key aggregate"));
2970        assert_eq!(
2971            registry
2972                .resolve_application_function("cross_kind", 1)
2973                .map(ApplicationFunctionResolution::kind),
2974            Some(ApplicationFunctionKind::Window)
2975        );
2976        assert!(Arc::ptr_eq(
2977            &registered_window,
2978            &registry.find_window("cross_kind", 1).unwrap()
2979        ));
2980        let row_one = [SqliteValue::Integer(1)];
2981        let row_two = [SqliteValue::Integer(2)];
2982        let row_three = [SqliteValue::Integer(3)];
2983        assert_eq!(
2984            finalize_aggregate(
2985                registry.find_aggregate("cross_kind", 1).unwrap().as_ref(),
2986                &[&row_one, &row_two, &row_three],
2987            ),
2988            SqliteValue::Integer(90_006),
2989            "window registration must keep its ordinary aggregate form"
2990        );
2991
2992        displaced.push(
2993            registry
2994                .register_application_scalar_captured(
2995                    "cross_kind",
2996                    FunctionArity::exact(1),
2997                    true,
2998                    false,
2999                    TaggedScalar {
3000                        name: "cross_kind",
3001                        num_args: 1,
3002                        tag: 100_000,
3003                    },
3004                )
3005                .expect("scalar must displace same-key window"),
3006        );
3007        assert_eq!(
3008            registry
3009                .resolve_application_function("cross_kind", 1)
3010                .map(ApplicationFunctionResolution::kind),
3011            Some(ApplicationFunctionKind::Scalar)
3012        );
3013        assert_eq!(registry.aggregate_accepts_arg_count("cross_kind", 1), None);
3014        assert_eq!(registry.window_accepts_arg_count("cross_kind", 1), None);
3015        assert!(registry.find_aggregate("cross_kind", 1).is_none());
3016        assert!(registry.find_window("cross_kind", 1).is_none());
3017        assert_eq!(displaced.len(), 3);
3018    }
3019
3020    #[test]
3021    fn test_registry_register_scalar() {
3022        let mut registry = FunctionRegistry::new();
3023        let previous = registry.register_scalar(Double);
3024        assert!(previous.is_none());
3025        assert!(registry.contains_scalar("double"));
3026        assert!(registry.contains_scalar("DOUBLE"));
3027        let f = registry
3028            .find_scalar(" Double ", 1)
3029            .expect("double registered");
3030        assert_eq!(
3031            f.invoke(&[SqliteValue::Integer(21)])
3032                .expect("invoke succeeds"),
3033            SqliteValue::Integer(42)
3034        );
3035    }
3036
3037    #[test]
3038    fn test_registry_case_insensitive_lookup() {
3039        let mut registry = FunctionRegistry::new();
3040        registry.register_scalar(Double);
3041
3042        // Register as "double", look up as "DOUBLE", "Double", " double "
3043        assert!(registry.find_scalar("DOUBLE", 1).is_some());
3044        assert!(registry.find_scalar("Double", 1).is_some());
3045        assert!(registry.find_scalar(" double ", 1).is_some());
3046    }
3047
3048    #[test]
3049    fn test_registry_overwrite() {
3050        let mut registry = FunctionRegistry::new();
3051
3052        // Register first version
3053        let prev = registry.register_scalar(Double);
3054        assert!(prev.is_none());
3055
3056        // Register second version with same (name, num_args) — overwrites
3057        let prev = registry.register_scalar(Double);
3058        assert!(prev.is_some());
3059
3060        // Still works
3061        let f = registry.find_scalar("double", 1).unwrap();
3062        assert_eq!(
3063            f.invoke(&[SqliteValue::Integer(5)]).unwrap(),
3064            SqliteValue::Integer(10)
3065        );
3066    }
3067
3068    #[test]
3069    fn test_registry_variadic_fallback() {
3070        let mut registry = FunctionRegistry::new();
3071
3072        // Register only the variadic version (num_args = -1)
3073        registry.register_scalar(VariadicConcat);
3074
3075        let too_few = registry
3076            .find_scalar("my_func", 0)
3077            .expect("known function with bad arity returns erroring scalar");
3078        assert_wrong_arg_count(too_few.as_ref(), &[], "my_func");
3079
3080        // Look up with specific arg count — no exact match, falls back to variadic
3081        let f = registry
3082            .find_scalar("my_func", 3)
3083            .expect("variadic fallback");
3084        assert_eq!(
3085            f.invoke(&[
3086                SqliteValue::Text("a".into()),
3087                SqliteValue::Text("b".into()),
3088                SqliteValue::Text("c".into()),
3089            ])
3090            .unwrap(),
3091            SqliteValue::Text("abc".into())
3092        );
3093        let too_many = registry
3094            .find_scalar("my_func", 4)
3095            .expect("known function with bad arity returns erroring scalar");
3096        assert_wrong_arg_count(
3097            too_many.as_ref(),
3098            &[
3099                SqliteValue::Null,
3100                SqliteValue::Null,
3101                SqliteValue::Null,
3102                SqliteValue::Null,
3103            ],
3104            "my_func",
3105        );
3106    }
3107
3108    #[test]
3109    fn test_registry_exact_wrong_arity_returns_function_error() {
3110        let mut registry = FunctionRegistry::new();
3111        registry.register_scalar(Double);
3112
3113        let f = registry
3114            .find_scalar("double", 2)
3115            .expect("known function with wrong arity returns erroring scalar");
3116        assert_wrong_arg_count(
3117            f.as_ref(),
3118            &[SqliteValue::Integer(1), SqliteValue::Integer(2)],
3119            "double",
3120        );
3121    }
3122
3123    #[test]
3124    fn test_registry_exact_match_over_variadic() {
3125        let mut registry = FunctionRegistry::new();
3126
3127        // Register both variadic (num_args=-1) and exact 2-arg version
3128        registry.register_scalar(VariadicConcat);
3129        registry.register_scalar(TwoArgFunc);
3130
3131        // Look up with num_args=2 — exact match wins over variadic
3132        let f = registry
3133            .find_scalar("my_func", 2)
3134            .expect("exact match found");
3135        assert_eq!(
3136            f.invoke(&[SqliteValue::Integer(10), SqliteValue::Integer(32)])
3137                .unwrap(),
3138            SqliteValue::Integer(42)
3139        );
3140
3141        // Look up with num_args=3 — no exact match, falls back to variadic
3142        let f = registry
3143            .find_scalar("my_func", 3)
3144            .expect("variadic fallback");
3145        assert_eq!(f.num_args(), -1);
3146    }
3147
3148    #[test]
3149    fn test_registry_not_found_returns_none() {
3150        let registry = FunctionRegistry::new();
3151        assert!(registry.find_scalar("nonexistent", 1).is_none());
3152        assert!(registry.find_aggregate("nonexistent", 1).is_none());
3153        assert!(registry.find_window("nonexistent", 1).is_none());
3154    }
3155
3156    #[test]
3157    fn test_registry_scalar_aggregate_arity_introspection_is_side_effect_free() {
3158        let mut registry = FunctionRegistry::new();
3159        registry.register_scalar(Double);
3160        registry.register_scalar(VariadicConcat);
3161        registry.register_aggregate(Product);
3162
3163        assert_eq!(registry.scalar_accepts_arg_count("double", 1), Some(true));
3164        assert_eq!(registry.scalar_accepts_arg_count("double", 2), Some(false));
3165        assert_eq!(registry.scalar_accepts_arg_count("my_func", 1), Some(true));
3166        assert_eq!(registry.scalar_accepts_arg_count("my_func", 3), Some(true));
3167        assert_eq!(registry.scalar_accepts_arg_count("my_func", 0), Some(false));
3168        assert_eq!(registry.scalar_accepts_arg_count("my_func", 4), Some(false));
3169        assert_eq!(registry.scalar_accepts_arg_count("missing_scalar", 1), None);
3170        assert_eq!(registry.scalar_is_deterministic("double", 1), Some(true));
3171        assert_eq!(registry.scalar_is_deterministic("my_func", 1), Some(true));
3172        assert_eq!(registry.scalar_is_deterministic("my_func", 0), None);
3173        assert_eq!(registry.scalar_is_deterministic("missing_scalar", 1), None);
3174
3175        assert_eq!(
3176            registry.aggregate_accepts_arg_count("product", 1),
3177            Some(true)
3178        );
3179        assert_eq!(
3180            registry.aggregate_accepts_arg_count("product", 0),
3181            Some(false)
3182        );
3183        assert_eq!(
3184            registry.aggregate_accepts_arg_count("missing_aggregate", 1),
3185            None
3186        );
3187
3188        registry
3189            .scalar_schema_safety
3190            .remove(&FunctionKey::new("double", 1));
3191        assert_eq!(
3192            registry.scalar_is_deterministic("double", 1),
3193            Some(false),
3194            "missing immutable metadata must fail closed"
3195        );
3196    }
3197
3198    #[test]
3199    fn scalar_query_constancy_is_frozen_cloned_and_fails_closed() {
3200        let mut registry = FunctionRegistry::new();
3201        registry.register_scalar(Double);
3202        registry.register_scalar_captured(
3203            "volatile_scalar",
3204            FunctionArity::exact(1),
3205            false,
3206            false,
3207            TaggedScalar {
3208                name: "ignored_by_captured_registration",
3209                num_args: 1,
3210                tag: 1,
3211            },
3212        );
3213        registry.register_conditionally_deterministic_scalar(TaggedScalar {
3214            name: "conditional_scalar",
3215            num_args: 1,
3216            tag: 2,
3217        });
3218        registry.register_slow_changing_scalar(TaggedScalar {
3219            name: "slow_scalar",
3220            num_args: 1,
3221            tag: 3,
3222        });
3223
3224        let resolved = registry.resolve_scalar("double", 1).unwrap();
3225        assert_eq!(resolved.query_constancy(), ScalarQueryConstancy::Constant);
3226        assert!(resolved.query_constancy().is_query_constant());
3227
3228        let resolved = registry.resolve_scalar("volatile_scalar", 1).unwrap();
3229        assert_eq!(resolved.query_constancy(), ScalarQueryConstancy::Volatile);
3230        assert!(!resolved.query_constancy().is_query_constant());
3231
3232        let conditional = registry.resolve_scalar("conditional_scalar", 1).unwrap();
3233        assert_eq!(
3234            conditional.schema_safety(),
3235            ScalarSchemaSafety::DateTimeConditional
3236        );
3237        assert_eq!(
3238            conditional.query_constancy(),
3239            ScalarQueryConstancy::SlowChanging
3240        );
3241
3242        let slow = registry.resolve_scalar("slow_scalar", 1).unwrap();
3243        assert_eq!(slow.schema_safety(), ScalarSchemaSafety::Never);
3244        assert_eq!(slow.query_constancy(), ScalarQueryConstancy::SlowChanging);
3245
3246        let wrong_arity = registry.resolve_scalar("double", 2).unwrap();
3247        assert_eq!(
3248            wrong_arity.query_constancy(),
3249            ScalarQueryConstancy::Volatile,
3250            "wrong-arity sentinels must never be treated as query constants"
3251        );
3252
3253        let mut cloned = FunctionRegistry::clone_from_arc(&Arc::new(registry));
3254        assert_eq!(
3255            cloned
3256                .resolve_scalar("slow_scalar", 1)
3257                .unwrap()
3258                .query_constancy(),
3259            ScalarQueryConstancy::SlowChanging,
3260            "registry snapshots must retain frozen query metadata"
3261        );
3262        cloned
3263            .scalar_query_constancy
3264            .remove(&FunctionKey::new("double", 1));
3265        assert_eq!(
3266            cloned
3267                .resolve_scalar("double", 1)
3268                .unwrap()
3269                .query_constancy(),
3270            ScalarQueryConstancy::Volatile,
3271            "missing immutable query metadata must fail closed"
3272        );
3273    }
3274
3275    #[test]
3276    fn application_shadowing_selects_matching_scalar_query_constancy() {
3277        let mut registry = FunctionRegistry::new();
3278        registry.register_slow_changing_scalar(TaggedScalar {
3279            name: "layered_constancy",
3280            num_args: 1,
3281            tag: 10,
3282        });
3283        registry.register_application_scalar_captured(
3284            "layered_constancy",
3285            FunctionArity::variadic(2, Some(3)),
3286            false,
3287            false,
3288            TaggedScalar {
3289                name: "layered_constancy",
3290                num_args: -1,
3291                tag: 20,
3292            },
3293        );
3294
3295        assert_eq!(
3296            registry
3297                .resolve_scalar("layered_constancy", 1)
3298                .unwrap()
3299                .query_constancy(),
3300            ScalarQueryConstancy::SlowChanging,
3301            "an incompatible application variadic must leave base metadata visible"
3302        );
3303        assert_eq!(
3304            registry
3305                .resolve_scalar("layered_constancy", 2)
3306                .unwrap()
3307                .query_constancy(),
3308            ScalarQueryConstancy::Volatile,
3309            "a matching non-deterministic application scalar must publish volatile metadata"
3310        );
3311
3312        registry.register_application_scalar_captured(
3313            "layered_constancy",
3314            FunctionArity::variadic(0, None),
3315            true,
3316            false,
3317            TaggedScalar {
3318                name: "layered_constancy",
3319                num_args: -1,
3320                tag: 30,
3321            },
3322        );
3323        assert_eq!(
3324            registry
3325                .resolve_scalar("layered_constancy", 1)
3326                .unwrap()
3327                .query_constancy(),
3328            ScalarQueryConstancy::Constant,
3329            "a compatible deterministic application scalar must shadow base metadata"
3330        );
3331
3332        let cloned = FunctionRegistry::clone_from_arc(&Arc::new(registry));
3333        assert_eq!(
3334            cloned
3335                .resolve_scalar("layered_constancy", 1)
3336                .unwrap()
3337                .query_constancy(),
3338            ScalarQueryConstancy::Constant,
3339            "registry snapshots must retain application query metadata"
3340        );
3341
3342        let mut shadowed = cloned;
3343        shadowed.register_application_aggregate_captured(
3344            "layered_constancy",
3345            FunctionArity::exact(1),
3346            TaggedAggregate {
3347                name: "layered_constancy",
3348                num_args: 1,
3349                tag: 40,
3350            },
3351        );
3352        assert!(
3353            shadowed.resolve_scalar("layered_constancy", 1).is_none(),
3354            "a matching application aggregate must shadow scalar metadata across kinds"
3355        );
3356        assert_eq!(
3357            shadowed
3358                .resolve_scalar("layered_constancy", 2)
3359                .unwrap()
3360                .query_constancy(),
3361            ScalarQueryConstancy::Constant,
3362            "an incompatible exact aggregate must leave the application variadic visible"
3363        );
3364    }
3365
3366    #[test]
3367    fn test_registry_register_and_resolve_aggregate() {
3368        let mut registry = FunctionRegistry::new();
3369        let previous = registry.register_aggregate(Product);
3370        assert!(previous.is_none());
3371        assert!(registry.contains_aggregate("product"));
3372        let f = registry
3373            .find_aggregate("PRODUCT", 1)
3374            .expect("product aggregate registered");
3375
3376        let mut state = f.initial_state();
3377        f.step(&mut state, &[SqliteValue::Integer(2)])
3378            .expect("step 1");
3379        f.step(&mut state, &[SqliteValue::Integer(3)])
3380            .expect("step 2");
3381        f.step(&mut state, &[SqliteValue::Integer(7)])
3382            .expect("step 3");
3383
3384        assert_eq!(
3385            f.finalize(state).expect("finalize succeeds"),
3386            SqliteValue::Integer(42)
3387        );
3388    }
3389
3390    #[test]
3391    fn test_registry_aggregate_type_erased() {
3392        let mut registry = FunctionRegistry::new();
3393        registry.register_aggregate(Product);
3394
3395        // Round-trip through type-erased registry
3396        let f = registry
3397            .find_aggregate("product", 1)
3398            .expect("product found");
3399        let mut state = f.initial_state();
3400        f.step(&mut state, &[SqliteValue::Integer(6)]).unwrap();
3401        f.step(&mut state, &[SqliteValue::Integer(7)]).unwrap();
3402        assert_eq!(f.finalize(state).unwrap(), SqliteValue::Integer(42));
3403        assert_eq!(f.name(), "product");
3404    }
3405
3406    #[test]
3407    fn test_registry_aggregate_wrong_arity_returns_function_error() {
3408        let mut registry = FunctionRegistry::new();
3409        registry.register_aggregate(Product);
3410
3411        let f = registry
3412            .find_aggregate("product", 0)
3413            .expect("known aggregate with wrong arity returns erroring aggregate");
3414        assert_wrong_arg_count_aggregate(f.as_ref(), &[], "product");
3415    }
3416
3417    #[test]
3418    fn test_registry_register_and_resolve_window() {
3419        let mut registry = FunctionRegistry::new();
3420        let previous = registry.register_window(MovingSum);
3421        assert!(previous.is_none());
3422        assert!(registry.contains_window("moving_sum"));
3423        let f = registry
3424            .find_window("MOVING_SUM", 1)
3425            .expect("moving_sum window registered");
3426
3427        let mut state = f.initial_state();
3428        f.step(&mut state, &[SqliteValue::Integer(10)])
3429            .expect("step 1");
3430        f.step(&mut state, &[SqliteValue::Integer(20)])
3431            .expect("step 2");
3432        f.step(&mut state, &[SqliteValue::Integer(30)])
3433            .expect("step 3");
3434        assert_eq!(f.value(&state).expect("value"), SqliteValue::Integer(60));
3435
3436        f.inverse(&mut state, &[SqliteValue::Integer(10)])
3437            .expect("inverse 1");
3438        f.step(&mut state, &[SqliteValue::Integer(40)])
3439            .expect("step 4");
3440        assert_eq!(f.value(&state).expect("value"), SqliteValue::Integer(90));
3441    }
3442
3443    #[test]
3444    fn test_registry_window_wrong_arity_returns_function_error() {
3445        let mut registry = FunctionRegistry::new();
3446        registry.register_window(MovingSum);
3447
3448        let f = registry
3449            .find_window("moving_sum", 0)
3450            .expect("known window with wrong arity returns erroring window");
3451        assert_wrong_arg_count_window(f.as_ref(), &[], "moving_sum");
3452    }
3453
3454    #[test]
3455    fn test_registry_window_accepts_arg_count_reports_known_bad_arity() {
3456        let mut registry = FunctionRegistry::new();
3457        registry.register_window(MovingSum);
3458
3459        assert_eq!(
3460            registry.window_accepts_arg_count("moving_sum", 1),
3461            Some(true)
3462        );
3463        assert_eq!(
3464            registry.window_accepts_arg_count("moving_sum", 0),
3465            Some(false)
3466        );
3467        assert_eq!(registry.window_accepts_arg_count("missing_window", 1), None);
3468    }
3469
3470    #[test]
3471    fn test_registry_window_type_erased() {
3472        let mut registry = FunctionRegistry::new();
3473        registry.register_window(MovingSum);
3474
3475        let f = registry
3476            .find_window("moving_sum", 1)
3477            .expect("moving_sum found");
3478
3479        // Full lifecycle: initial_state -> step -> inverse -> value -> finalize
3480        let mut state = f.initial_state();
3481        f.step(&mut state, &[SqliteValue::Integer(100)]).unwrap();
3482        assert_eq!(f.value(&state).unwrap(), SqliteValue::Integer(100));
3483
3484        f.inverse(&mut state, &[SqliteValue::Integer(100)]).unwrap();
3485        assert_eq!(f.value(&state).unwrap(), SqliteValue::Integer(0));
3486
3487        f.step(&mut state, &[SqliteValue::Integer(42)]).unwrap();
3488        assert_eq!(f.finalize(state).unwrap(), SqliteValue::Integer(42));
3489    }
3490
3491    #[test]
3492    fn test_function_key_equality() {
3493        let k1 = FunctionKey::new("ABS", 1);
3494        let k2 = FunctionKey::new("abs", 1);
3495        let k3 = FunctionKey::new("ABS", 2);
3496
3497        assert_eq!(k1, k2, "case-insensitive equality");
3498        assert_ne!(k1, k3, "different num_args");
3499    }
3500
3501    // ── E2E: bd-1dc9 ────────────────────────────────────────────────────
3502
3503    #[test]
3504    fn test_e2e_custom_collation_in_order_by() {
3505        use collation::{BinaryCollation, CollationFunction, NoCaseCollation, RtrimCollation};
3506
3507        // Simulate ORDER BY with a custom reverse-alphabetical collation.
3508        struct ReverseAlpha;
3509
3510        impl CollationFunction for ReverseAlpha {
3511            fn name(&self) -> &str {
3512                "REVERSE_ALPHA"
3513            }
3514
3515            fn compare(&self, left: &[u8], right: &[u8]) -> std::cmp::Ordering {
3516                // Reverse of BINARY
3517                right.cmp(left)
3518            }
3519        }
3520
3521        let coll = ReverseAlpha;
3522        let mut data: Vec<&[u8]> = vec![b"banana", b"apple", b"cherry", b"date"];
3523        data.sort_by(|a, b| coll.compare(a, b));
3524
3525        // Reverse alphabetical: date > cherry > banana > apple
3526        let expected: Vec<&[u8]> = vec![b"date", b"cherry", b"banana", b"apple"];
3527        assert_eq!(data, expected);
3528        assert_eq!(coll.name(), "REVERSE_ALPHA");
3529
3530        // Verify built-in collations are usable as trait objects.
3531        let collations: Vec<Box<dyn CollationFunction>> = vec![
3532            Box::new(BinaryCollation),
3533            Box::new(NoCaseCollation),
3534            Box::new(RtrimCollation),
3535            Box::new(ReverseAlpha),
3536        ];
3537        assert_eq!(collations.len(), 4);
3538
3539        // Sort with BINARY: normal alphabetical
3540        let mut binary_sorted = data.clone();
3541        binary_sorted.sort_by(|a, b| collations[0].compare(a, b));
3542        assert_eq!(binary_sorted[0], b"apple");
3543    }
3544
3545    #[test]
3546    fn test_e2e_authorizer_sandboxing() {
3547        use authorizer::{AuthAction, AuthResult, Authorizer};
3548
3549        // Authorizer that denies INSERT/UPDATE/DELETE but allows SELECT.
3550        struct SelectOnlyAuthorizer;
3551
3552        impl Authorizer for SelectOnlyAuthorizer {
3553            fn authorize(
3554                &self,
3555                action: AuthAction,
3556                _arg1: Option<&str>,
3557                arg2: Option<&str>,
3558                _db_name: Option<&str>,
3559                _trigger: Option<&str>,
3560            ) -> AuthResult {
3561                match action {
3562                    AuthAction::Select | AuthAction::Read => {
3563                        // Ignore the "secret" column (replaced with NULL)
3564                        if action == AuthAction::Read && arg2 == Some("secret") {
3565                            return AuthResult::Ignore;
3566                        }
3567                        AuthResult::Ok
3568                    }
3569                    AuthAction::Insert | AuthAction::Update | AuthAction::Delete => {
3570                        AuthResult::Deny
3571                    }
3572                    _ => AuthResult::Ok,
3573                }
3574            }
3575        }
3576
3577        let auth = SelectOnlyAuthorizer;
3578
3579        // SELECT is allowed at compile time.
3580        assert_eq!(
3581            auth.authorize(AuthAction::Select, None, None, Some("main"), None),
3582            AuthResult::Ok,
3583            "SELECT must be allowed"
3584        );
3585
3586        // INSERT is denied at compile time.
3587        assert_eq!(
3588            auth.authorize(AuthAction::Insert, Some("users"), None, Some("main"), None),
3589            AuthResult::Deny,
3590            "INSERT must be denied (compile-time auth error)"
3591        );
3592
3593        // UPDATE is denied.
3594        assert_eq!(
3595            auth.authorize(
3596                AuthAction::Update,
3597                Some("users"),
3598                Some("email"),
3599                Some("main"),
3600                None
3601            ),
3602            AuthResult::Deny,
3603        );
3604
3605        // DELETE is denied.
3606        assert_eq!(
3607            auth.authorize(AuthAction::Delete, Some("users"), None, Some("main"), None),
3608            AuthResult::Deny,
3609        );
3610
3611        // Read on "secret" column returns Ignore (nullify).
3612        assert_eq!(
3613            auth.authorize(
3614                AuthAction::Read,
3615                Some("users"),
3616                Some("secret"),
3617                Some("main"),
3618                None
3619            ),
3620            AuthResult::Ignore,
3621            "Ignore must nullify column"
3622        );
3623
3624        // Read on normal column is allowed.
3625        assert_eq!(
3626            auth.authorize(
3627                AuthAction::Read,
3628                Some("users"),
3629                Some("name"),
3630                Some("main"),
3631                None
3632            ),
3633            AuthResult::Ok,
3634        );
3635    }
3636
3637    #[test]
3638    fn test_e2e_function_registry_resolution() {
3639        // Register abs(1 arg) and a variadic version, then test resolution.
3640        struct Abs1;
3641
3642        impl ScalarFunction for Abs1 {
3643            fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
3644                Ok(SqliteValue::Integer(args[0].to_integer().abs()))
3645            }
3646
3647            fn num_args(&self) -> i32 {
3648                1
3649            }
3650
3651            fn name(&self) -> &str {
3652                "abs"
3653            }
3654        }
3655
3656        struct AbsVariadic;
3657
3658        impl ScalarFunction for AbsVariadic {
3659            fn invoke(&self, args: &[SqliteValue]) -> fsqlite_error::Result<SqliteValue> {
3660                // Variadic: return sum of absolute values
3661                let sum: i64 = args.iter().map(|a| a.to_integer().abs()).sum();
3662                Ok(SqliteValue::Integer(sum))
3663            }
3664
3665            fn num_args(&self) -> i32 {
3666                -1
3667            }
3668
3669            fn name(&self) -> &str {
3670                "abs"
3671            }
3672        }
3673
3674        let mut registry = FunctionRegistry::new();
3675        registry.register_scalar(Abs1);
3676        registry.register_scalar(AbsVariadic);
3677
3678        // SELECT abs(-5) should use 1-arg version.
3679        let f = registry.find_scalar("abs", 1).expect("abs(1) found");
3680        assert_eq!(f.num_args(), 1, "exact 1-arg match");
3681        assert_eq!(
3682            f.invoke(&[SqliteValue::Integer(-5)]).unwrap(),
3683            SqliteValue::Integer(5)
3684        );
3685
3686        // SELECT abs(-5, -3) should fall through to variadic.
3687        let f = registry.find_scalar("abs", 2).expect("abs variadic found");
3688        assert_eq!(f.num_args(), -1, "variadic fallback for 2 args");
3689        assert_eq!(
3690            f.invoke(&[SqliteValue::Integer(-5), SqliteValue::Integer(-3)])
3691                .unwrap(),
3692            SqliteValue::Integer(8)
3693        );
3694
3695        // Nonexistent function returns None.
3696        assert!(registry.find_scalar("nonexistent", 1).is_none());
3697    }
3698
3699    #[test]
3700    fn test_authorizer_called_at_compile_time() {
3701        use authorizer::{AuthAction, AuthResult, Authorizer};
3702        use std::sync::Mutex;
3703
3704        // Track every authorize call to verify compile-time invocation pattern.
3705        struct TrackingAuthorizer {
3706            calls: Mutex<Vec<AuthAction>>,
3707        }
3708
3709        impl TrackingAuthorizer {
3710            fn new() -> Self {
3711                Self {
3712                    calls: Mutex::new(Vec::new()),
3713                }
3714            }
3715        }
3716
3717        impl Authorizer for TrackingAuthorizer {
3718            fn authorize(
3719                &self,
3720                action: AuthAction,
3721                _arg1: Option<&str>,
3722                _arg2: Option<&str>,
3723                _db_name: Option<&str>,
3724                _trigger: Option<&str>,
3725            ) -> AuthResult {
3726                self.calls.lock().unwrap().push(action);
3727                AuthResult::Ok
3728            }
3729        }
3730
3731        let auth = TrackingAuthorizer::new();
3732
3733        // Simulate compile-time authorization for:
3734        // `SELECT name, email FROM users WHERE id = ?`
3735        //
3736        // The authorizer is called during prepare(), NOT during step().
3737        // Expected calls:
3738        //   1. Select (the statement type)
3739        //   2. Read(users, name)
3740        //   3. Read(users, email)
3741        //   4. Read(users, id)    -- WHERE clause column
3742
3743        // Phase 1: prepare (compile time) — authorizer is called
3744        auth.authorize(AuthAction::Select, None, None, Some("main"), None);
3745        auth.authorize(
3746            AuthAction::Read,
3747            Some("users"),
3748            Some("name"),
3749            Some("main"),
3750            None,
3751        );
3752        auth.authorize(
3753            AuthAction::Read,
3754            Some("users"),
3755            Some("email"),
3756            Some("main"),
3757            None,
3758        );
3759        auth.authorize(
3760            AuthAction::Read,
3761            Some("users"),
3762            Some("id"),
3763            Some("main"),
3764            None,
3765        );
3766
3767        let calls = auth.calls.lock().unwrap();
3768        assert_eq!(calls.len(), 4, "authorizer called 4 times during prepare");
3769        assert_eq!(calls[0], AuthAction::Select);
3770        assert_eq!(calls[1], AuthAction::Read);
3771        assert_eq!(calls[2], AuthAction::Read);
3772        assert_eq!(calls[3], AuthAction::Read);
3773        drop(calls);
3774
3775        // Phase 2: step (execution) — authorizer is NOT called again
3776        // (In a real implementation, step() would not invoke authorize.)
3777        // We simply verify no additional calls were recorded.
3778        let calls_after = auth.calls.lock().unwrap();
3779        assert_eq!(
3780            calls_after.len(),
3781            4,
3782            "authorizer must NOT be called during step/execution"
3783        );
3784        drop(calls_after);
3785    }
3786}