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