Skip to main content

fsqlite_func/
lib.rs

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