Skip to main content

fsqlite_func/
lib.rs

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