Skip to main content

sqlite_provider/connection/
hooks.rs

1use core::ffi::{c_char, c_void};
2use core::marker::PhantomData;
3use core::ptr::NonNull;
4use std::collections::HashMap;
5use std::sync::{Mutex, MutexGuard, OnceLock};
6
7use crate::error::Result;
8use crate::provider::{Sqlite3Api, Sqlite3Hooks};
9
10use super::core::Connection;
11
12/// Bitmask for trace_v2 callbacks.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct TraceMask {
15    bits: u32,
16}
17
18impl TraceMask {
19    /// Trace SQL text before execution.
20    pub const STMT: TraceMask = TraceMask { bits: 0x01 };
21    /// Trace statement execution timing samples.
22    pub const PROFILE: TraceMask = TraceMask { bits: 0x02 };
23    /// Trace each produced row.
24    pub const ROW: TraceMask = TraceMask { bits: 0x04 };
25    /// Trace connection close events.
26    pub const CLOSE: TraceMask = TraceMask { bits: 0x08 };
27
28    /// Empty trace mask.
29    pub const fn empty() -> Self {
30        Self { bits: 0 }
31    }
32
33    /// Raw SQLite mask bits.
34    pub const fn bits(self) -> u32 {
35        self.bits
36    }
37
38    /// Whether `other` is a subset of this mask.
39    pub const fn contains(self, other: TraceMask) -> bool {
40        (self.bits & other.bits) == other.bits
41    }
42}
43
44impl core::ops::BitOr for TraceMask {
45    type Output = TraceMask;
46
47    fn bitor(self, rhs: TraceMask) -> TraceMask {
48        TraceMask {
49            bits: self.bits | rhs.bits,
50        }
51    }
52}
53
54impl core::ops::BitOrAssign for TraceMask {
55    fn bitor_assign(&mut self, rhs: TraceMask) {
56        self.bits |= rhs.bits;
57    }
58}
59
60/// Decoded trace callback event.
61pub enum TraceEvent<'a, P: Sqlite3Api> {
62    /// Statement trace event.
63    Stmt {
64        /// Statement handle associated with the trace event.
65        stmt: NonNull<P::Stmt>,
66        /// SQL text pointer decoded as UTF-8 when available.
67        sql: Option<&'a str>,
68    },
69    /// Profile trace event.
70    Profile {
71        /// Statement handle associated with the profile sample.
72        stmt: NonNull<P::Stmt>,
73        /// Execution time in nanoseconds.
74        nsec: i64,
75    },
76    /// Row trace event.
77    Row {
78        /// Statement handle currently producing a row.
79        stmt: NonNull<P::Stmt>,
80    },
81    /// Connection-close trace event.
82    Close {
83        /// Database handle being closed.
84        db: NonNull<P::Db>,
85    },
86    /// Unrecognized/raw trace event payload.
87    Raw {
88        /// Original SQLite trace mask bits.
89        mask: u32,
90        /// Raw first pointer payload supplied by SQLite.
91        p1: *mut c_void,
92        /// Raw second pointer payload supplied by SQLite.
93        p2: *mut c_void,
94    },
95}
96
97type TraceCallback<P> = dyn for<'a> FnMut(TraceEvent<'a, P>) + Send;
98
99struct TraceState<P: Sqlite3Api> {
100    cb: Box<TraceCallback<P>>,
101}
102
103type ProgressCallback = dyn FnMut() -> i32 + Send;
104
105struct ProgressState {
106    cb: Box<ProgressCallback>,
107}
108
109extern "C" fn trace_trampoline<P: Sqlite3Api>(
110    mask: u32,
111    ctx: *mut c_void,
112    p1: *mut c_void,
113    p2: *mut c_void,
114) {
115    let state = unsafe { &mut *(ctx as *mut TraceState<P>) };
116    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
117        let event = decode_trace::<P>(mask, p1, p2);
118        (state.cb)(event);
119    }));
120}
121
122fn decode_trace<'a, P: Sqlite3Api>(
123    mask: u32,
124    p1: *mut c_void,
125    p2: *mut c_void,
126) -> TraceEvent<'a, P> {
127    if (mask & TraceMask::STMT.bits()) != 0 {
128        let stmt = match NonNull::new(p1 as *mut P::Stmt) {
129            Some(stmt) => stmt,
130            None => return TraceEvent::Raw { mask, p1, p2 },
131        };
132        let sql = unsafe { cstr_to_opt(p2 as *const c_char) };
133        return TraceEvent::Stmt { stmt, sql };
134    }
135    if (mask & TraceMask::PROFILE.bits()) != 0 {
136        let stmt = match NonNull::new(p1 as *mut P::Stmt) {
137            Some(stmt) => stmt,
138            None => return TraceEvent::Raw { mask, p1, p2 },
139        };
140        if p2.is_null() {
141            return TraceEvent::Raw { mask, p1, p2 };
142        }
143        let nsec = unsafe { *(p2 as *const i64) };
144        return TraceEvent::Profile { stmt, nsec };
145    }
146    if (mask & TraceMask::ROW.bits()) != 0 {
147        let stmt = match NonNull::new(p1 as *mut P::Stmt) {
148            Some(stmt) => stmt,
149            None => return TraceEvent::Raw { mask, p1, p2 },
150        };
151        return TraceEvent::Row { stmt };
152    }
153    if (mask & TraceMask::CLOSE.bits()) != 0 {
154        let db = match NonNull::new(p1 as *mut P::Db) {
155            Some(db) => db,
156            None => return TraceEvent::Raw { mask, p1, p2 },
157        };
158        return TraceEvent::Close { db };
159    }
160    TraceEvent::Raw { mask, p1, p2 }
161}
162
163/// Authorizer action codes (SQLite values).
164pub mod authorizer {
165    /// `SQLITE_CREATE_INDEX`.
166    pub const CREATE_INDEX: i32 = 1;
167    /// `SQLITE_CREATE_TABLE`.
168    pub const CREATE_TABLE: i32 = 2;
169    /// `SQLITE_CREATE_TEMP_INDEX`.
170    pub const CREATE_TEMP_INDEX: i32 = 3;
171    /// `SQLITE_CREATE_TEMP_TABLE`.
172    pub const CREATE_TEMP_TABLE: i32 = 4;
173    /// `SQLITE_CREATE_TEMP_TRIGGER`.
174    pub const CREATE_TEMP_TRIGGER: i32 = 5;
175    /// `SQLITE_CREATE_TEMP_VIEW`.
176    pub const CREATE_TEMP_VIEW: i32 = 6;
177    /// `SQLITE_CREATE_TRIGGER`.
178    pub const CREATE_TRIGGER: i32 = 7;
179    /// `SQLITE_CREATE_VIEW`.
180    pub const CREATE_VIEW: i32 = 8;
181    /// `SQLITE_DELETE`.
182    pub const DELETE: i32 = 9;
183    /// `SQLITE_DROP_INDEX`.
184    pub const DROP_INDEX: i32 = 10;
185    /// `SQLITE_DROP_TABLE`.
186    pub const DROP_TABLE: i32 = 11;
187    /// `SQLITE_DROP_TEMP_INDEX`.
188    pub const DROP_TEMP_INDEX: i32 = 12;
189    /// `SQLITE_DROP_TEMP_TABLE`.
190    pub const DROP_TEMP_TABLE: i32 = 13;
191    /// `SQLITE_DROP_TEMP_TRIGGER`.
192    pub const DROP_TEMP_TRIGGER: i32 = 14;
193    /// `SQLITE_DROP_TEMP_VIEW`.
194    pub const DROP_TEMP_VIEW: i32 = 15;
195    /// `SQLITE_DROP_TRIGGER`.
196    pub const DROP_TRIGGER: i32 = 16;
197    /// `SQLITE_DROP_VIEW`.
198    pub const DROP_VIEW: i32 = 17;
199    /// `SQLITE_INSERT`.
200    pub const INSERT: i32 = 18;
201    /// `SQLITE_PRAGMA`.
202    pub const PRAGMA: i32 = 19;
203    /// `SQLITE_READ`.
204    pub const READ: i32 = 20;
205    /// `SQLITE_SELECT`.
206    pub const SELECT: i32 = 21;
207    /// `SQLITE_TRANSACTION`.
208    pub const TRANSACTION: i32 = 22;
209    /// `SQLITE_UPDATE`.
210    pub const UPDATE: i32 = 23;
211    /// `SQLITE_ATTACH`.
212    pub const ATTACH: i32 = 24;
213    /// `SQLITE_DETACH`.
214    pub const DETACH: i32 = 25;
215    /// `SQLITE_ALTER_TABLE`.
216    pub const ALTER_TABLE: i32 = 26;
217    /// `SQLITE_REINDEX`.
218    pub const REINDEX: i32 = 27;
219    /// `SQLITE_ANALYZE`.
220    pub const ANALYZE: i32 = 28;
221    /// `SQLITE_CREATE_VTABLE`.
222    pub const CREATE_VTABLE: i32 = 29;
223    /// `SQLITE_DROP_VTABLE`.
224    pub const DROP_VTABLE: i32 = 30;
225    /// `SQLITE_FUNCTION`.
226    pub const FUNCTION: i32 = 31;
227    /// `SQLITE_SAVEPOINT`.
228    pub const SAVEPOINT: i32 = 32;
229    /// `SQLITE_RECURSIVE`.
230    pub const RECURSIVE: i32 = 33;
231}
232
233/// Authorizer action decoded from the SQLite action code.
234#[derive(Clone, Copy, Debug, PartialEq, Eq)]
235pub enum AuthorizerAction {
236    /// Create index operation.
237    CreateIndex,
238    /// Create table operation.
239    CreateTable,
240    /// Create temporary index operation.
241    CreateTempIndex,
242    /// Create temporary table operation.
243    CreateTempTable,
244    /// Create temporary trigger operation.
245    CreateTempTrigger,
246    /// Create temporary view operation.
247    CreateTempView,
248    /// Create trigger operation.
249    CreateTrigger,
250    /// Create view operation.
251    CreateView,
252    /// Delete operation.
253    Delete,
254    /// Drop index operation.
255    DropIndex,
256    /// Drop table operation.
257    DropTable,
258    /// Drop temporary index operation.
259    DropTempIndex,
260    /// Drop temporary table operation.
261    DropTempTable,
262    /// Drop temporary trigger operation.
263    DropTempTrigger,
264    /// Drop temporary view operation.
265    DropTempView,
266    /// Drop trigger operation.
267    DropTrigger,
268    /// Drop view operation.
269    DropView,
270    /// Insert operation.
271    Insert,
272    /// PRAGMA operation.
273    Pragma,
274    /// Read operation.
275    Read,
276    /// Select operation.
277    Select,
278    /// Transaction control operation.
279    Transaction,
280    /// Update operation.
281    Update,
282    /// Attach database operation.
283    Attach,
284    /// Detach database operation.
285    Detach,
286    /// Alter-table operation.
287    AlterTable,
288    /// Reindex operation.
289    Reindex,
290    /// Analyze operation.
291    Analyze,
292    /// Create virtual-table operation.
293    CreateVTable,
294    /// Drop virtual-table operation.
295    DropVTable,
296    /// Function invocation operation.
297    Function,
298    /// Savepoint operation.
299    Savepoint,
300    /// Recursive query operation.
301    Recursive,
302    /// Unrecognized authorizer action code.
303    Unknown(i32),
304}
305
306impl AuthorizerAction {
307    /// Decode a raw SQLite authorizer action code into a typed action.
308    pub fn from_code(code: i32) -> Self {
309        match code {
310            authorizer::CREATE_INDEX => Self::CreateIndex,
311            authorizer::CREATE_TABLE => Self::CreateTable,
312            authorizer::CREATE_TEMP_INDEX => Self::CreateTempIndex,
313            authorizer::CREATE_TEMP_TABLE => Self::CreateTempTable,
314            authorizer::CREATE_TEMP_TRIGGER => Self::CreateTempTrigger,
315            authorizer::CREATE_TEMP_VIEW => Self::CreateTempView,
316            authorizer::CREATE_TRIGGER => Self::CreateTrigger,
317            authorizer::CREATE_VIEW => Self::CreateView,
318            authorizer::DELETE => Self::Delete,
319            authorizer::DROP_INDEX => Self::DropIndex,
320            authorizer::DROP_TABLE => Self::DropTable,
321            authorizer::DROP_TEMP_INDEX => Self::DropTempIndex,
322            authorizer::DROP_TEMP_TABLE => Self::DropTempTable,
323            authorizer::DROP_TEMP_TRIGGER => Self::DropTempTrigger,
324            authorizer::DROP_TEMP_VIEW => Self::DropTempView,
325            authorizer::DROP_TRIGGER => Self::DropTrigger,
326            authorizer::DROP_VIEW => Self::DropView,
327            authorizer::INSERT => Self::Insert,
328            authorizer::PRAGMA => Self::Pragma,
329            authorizer::READ => Self::Read,
330            authorizer::SELECT => Self::Select,
331            authorizer::TRANSACTION => Self::Transaction,
332            authorizer::UPDATE => Self::Update,
333            authorizer::ATTACH => Self::Attach,
334            authorizer::DETACH => Self::Detach,
335            authorizer::ALTER_TABLE => Self::AlterTable,
336            authorizer::REINDEX => Self::Reindex,
337            authorizer::ANALYZE => Self::Analyze,
338            authorizer::CREATE_VTABLE => Self::CreateVTable,
339            authorizer::DROP_VTABLE => Self::DropVTable,
340            authorizer::FUNCTION => Self::Function,
341            authorizer::SAVEPOINT => Self::Savepoint,
342            authorizer::RECURSIVE => Self::Recursive,
343            other => Self::Unknown(other),
344        }
345    }
346}
347
348/// Return value for authorizer callbacks.
349#[derive(Clone, Copy, Debug, PartialEq, Eq)]
350pub enum AuthorizerResult {
351    /// Allow the operation.
352    Ok,
353    /// Allow but suppress specific reads where SQLite supports it.
354    Ignore,
355    /// Deny the operation.
356    Deny,
357}
358
359impl AuthorizerResult {
360    /// Encode the typed authorizer decision into the SQLite return code.
361    pub fn into_code(self) -> i32 {
362        match self {
363            Self::Ok => 0,
364            Self::Ignore => 2,
365            Self::Deny => 1,
366        }
367    }
368}
369
370/// Arguments passed to the authorizer callback.
371pub struct AuthorizerEvent<'a> {
372    /// Action decoded into a typed enum when recognized.
373    pub action: AuthorizerAction,
374    /// Original numeric SQLite authorizer action code.
375    pub code: i32,
376    /// First optional action argument.
377    pub arg1: Option<&'a str>,
378    /// Second optional action argument.
379    pub arg2: Option<&'a str>,
380    /// Optional database name for the action.
381    pub db_name: Option<&'a str>,
382    /// Optional trigger or view name for the action.
383    pub trigger_or_view: Option<&'a str>,
384}
385
386struct AuthorizerState {
387    cb: Box<dyn for<'a> FnMut(AuthorizerEvent<'a>) -> AuthorizerResult + Send>,
388}
389
390extern "C" fn authorizer_trampoline(
391    ctx: *mut c_void,
392    action: i32,
393    arg1: *const c_char,
394    arg2: *const c_char,
395    db_name: *const c_char,
396    trigger_or_view: *const c_char,
397) -> i32 {
398    if ctx.is_null() {
399        return AuthorizerResult::Deny.into_code();
400    }
401    let state = unsafe { &mut *(ctx as *mut AuthorizerState) };
402    // Fail closed if callback panics.
403    let mut out = AuthorizerResult::Deny.into_code();
404    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
405        let event = AuthorizerEvent {
406            action: AuthorizerAction::from_code(action),
407            code: action,
408            arg1: unsafe { cstr_to_opt(arg1) },
409            arg2: unsafe { cstr_to_opt(arg2) },
410            db_name: unsafe { cstr_to_opt(db_name) },
411            trigger_or_view: unsafe { cstr_to_opt(trigger_or_view) },
412        };
413        out = (state.cb)(event).into_code();
414    }));
415    out
416}
417
418unsafe fn cstr_to_opt<'a>(ptr: *const c_char) -> Option<&'a str> {
419    if ptr.is_null() {
420        return None;
421    }
422    unsafe { core::ffi::CStr::from_ptr(ptr) }.to_str().ok()
423}
424
425extern "C" fn progress_trampoline(ctx: *mut c_void) -> i32 {
426    if ctx.is_null() {
427        return 0;
428    }
429    let state = unsafe { &mut *(ctx as *mut ProgressState) };
430    // Fail closed if callback panics.
431    let mut out = 1;
432    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
433        out = (state.cb)();
434    }));
435    out
436}
437
438type CallbackRegistry = HashMap<CallbackRegistryKey, usize>;
439
440#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
441struct CallbackRegistryKey {
442    db_addr: usize,
443    kind: CallbackKind,
444}
445
446fn callback_registry() -> &'static Mutex<CallbackRegistry> {
447    static REGISTRY: OnceLock<Mutex<CallbackRegistry>> = OnceLock::new();
448    REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
449}
450
451fn lock_callback_registry() -> MutexGuard<'static, CallbackRegistry> {
452    callback_registry()
453        .lock()
454        .unwrap_or_else(|poisoned| poisoned.into_inner())
455}
456
457fn callback_registry_key<T>(db: NonNull<T>, kind: CallbackKind) -> CallbackRegistryKey {
458    CallbackRegistryKey {
459        db_addr: db.as_ptr() as usize,
460        kind,
461    }
462}
463
464#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
465enum CallbackKind {
466    Trace,
467    Authorizer,
468    Progress,
469}
470
471/// RAII handle for registered callbacks (trace/authorizer/progress).
472///
473/// The handle borrows the originating connection so callback state cannot
474/// outlive the database handle.
475pub struct CallbackHandle<'c, 'p, P: Sqlite3Hooks> {
476    api: &'p P,
477    db: NonNull<P::Db>,
478    kind: CallbackKind,
479    ctx: *mut c_void,
480    _conn: PhantomData<&'c Connection<'p, P>>,
481}
482
483unsafe fn drop_callback_context<P: Sqlite3Hooks>(kind: CallbackKind, ctx: *mut c_void) {
484    if ctx.is_null() {
485        return;
486    }
487    match kind {
488        CallbackKind::Trace => unsafe { drop(Box::from_raw(ctx as *mut TraceState<P>)) },
489        CallbackKind::Authorizer => unsafe { drop(Box::from_raw(ctx as *mut AuthorizerState)) },
490        CallbackKind::Progress => unsafe { drop(Box::from_raw(ctx as *mut ProgressState)) },
491    }
492}
493
494unsafe fn unregister_callback<P: Sqlite3Hooks>(
495    api: &P,
496    db: NonNull<P::Db>,
497    kind: CallbackKind,
498) -> Result<()> {
499    match kind {
500        CallbackKind::Trace => unsafe { api.trace_v2(db, 0, None, core::ptr::null_mut()) },
501        CallbackKind::Authorizer => unsafe { api.set_authorizer(db, None, core::ptr::null_mut()) },
502        CallbackKind::Progress => unsafe {
503            api.progress_handler(db, 0, None, core::ptr::null_mut())
504        },
505    }
506}
507
508impl<'c, 'p, P: Sqlite3Hooks> CallbackHandle<'c, 'p, P> {
509    fn new_trace(conn: &'c Connection<'p, P>, ctx: *mut c_void) -> Self {
510        Self {
511            api: conn.api,
512            db: conn.db,
513            kind: CallbackKind::Trace,
514            ctx,
515            _conn: PhantomData,
516        }
517    }
518
519    fn new_authorizer(conn: &'c Connection<'p, P>, ctx: *mut c_void) -> Self {
520        Self {
521            api: conn.api,
522            db: conn.db,
523            kind: CallbackKind::Authorizer,
524            ctx,
525            _conn: PhantomData,
526        }
527    }
528
529    fn new_progress(conn: &'c Connection<'p, P>, ctx: *mut c_void) -> Self {
530        Self {
531            api: conn.api,
532            db: conn.db,
533            kind: CallbackKind::Progress,
534            ctx,
535            _conn: PhantomData,
536        }
537    }
538}
539
540impl<'c, 'p, P: Sqlite3Hooks> Drop for CallbackHandle<'c, 'p, P> {
541    fn drop(&mut self) {
542        let key = callback_registry_key(self.db, self.kind);
543        let mut registry = lock_callback_registry();
544        let is_active = registry.get(&key).copied() == Some(self.ctx as usize);
545        if !is_active {
546            drop(registry);
547            unsafe { drop_callback_context::<P>(self.kind, self.ctx) };
548            return;
549        }
550
551        // If unregister fails, leak state to avoid callback UAF.
552        if unsafe { unregister_callback(self.api, self.db, self.kind) }.is_ok() {
553            if registry.get(&key).copied() == Some(self.ctx as usize) {
554                registry.remove(&key);
555            }
556            drop(registry);
557            unsafe { drop_callback_context::<P>(self.kind, self.ctx) };
558        }
559    }
560}
561
562impl<'p, P: Sqlite3Hooks> Connection<'p, P> {
563    /// Set busy timeout.
564    pub fn busy_timeout(&self, ms: i32) -> Result<()> {
565        unsafe { self.api.busy_timeout(self.db, ms) }
566    }
567
568    /// Register or clear a raw progress callback.
569    ///
570    /// # Safety
571    /// Callers must ensure the callback/context pair remains valid until it is
572    /// explicitly cleared and that the callback never unwinds.
573    pub unsafe fn progress_handler_raw(
574        &self,
575        n: i32,
576        cb: Option<extern "C" fn(*mut c_void) -> i32>,
577        context: *mut c_void,
578    ) -> Result<()> {
579        let key = callback_registry_key(self.db, CallbackKind::Progress);
580        let mut registry = lock_callback_registry();
581        let out = unsafe { self.api.progress_handler(self.db, n, cb, context) };
582        if out.is_ok() {
583            registry.remove(&key);
584        }
585        out
586    }
587
588    /// Register a panic-contained progress callback.
589    ///
590    /// The callback is invoked through a trampoline that catches panics and
591    /// returns `1` on unwind so SQLite interrupts execution (fail-closed).
592    pub fn register_progress_handler<'c, F>(
593        &'c self,
594        n: i32,
595        f: F,
596    ) -> Result<CallbackHandle<'c, 'p, P>>
597    where
598        F: FnMut() -> i32 + Send + 'static,
599    {
600        let state = Box::new(ProgressState { cb: Box::new(f) });
601        let ctx = Box::into_raw(state) as *mut c_void;
602        let key = callback_registry_key(self.db, CallbackKind::Progress);
603        let mut registry = lock_callback_registry();
604        if let Err(err) = unsafe {
605            self.api
606                .progress_handler(self.db, n, Some(progress_trampoline), ctx)
607        } {
608            unsafe { drop(Box::from_raw(ctx as *mut ProgressState)) };
609            return Err(err);
610        }
611        registry.insert(key, ctx as usize);
612        Ok(CallbackHandle::new_progress(self, ctx))
613    }
614
615    /// Remove any previously registered progress callback.
616    pub fn clear_progress_handler(&self) -> Result<()> {
617        let key = callback_registry_key(self.db, CallbackKind::Progress);
618        let mut registry = lock_callback_registry();
619        let out = unsafe {
620            self.api
621                .progress_handler(self.db, 0, None, core::ptr::null_mut())
622        };
623        if out.is_ok() {
624            registry.remove(&key);
625        }
626        out
627    }
628
629    /// Register a typed trace callback.
630    pub fn register_trace<'c, F>(
631        &'c self,
632        mask: TraceMask,
633        f: F,
634    ) -> Result<CallbackHandle<'c, 'p, P>>
635    where
636        F: for<'a> FnMut(TraceEvent<'a, P>) + Send + 'static,
637    {
638        let state = Box::new(TraceState::<P> { cb: Box::new(f) });
639        let ctx = Box::into_raw(state) as *mut c_void;
640        let key = callback_registry_key(self.db, CallbackKind::Trace);
641        let mut registry = lock_callback_registry();
642        if let Err(err) = unsafe {
643            self.api
644                .trace_v2(self.db, mask.bits(), Some(trace_trampoline::<P>), ctx)
645        } {
646            unsafe { drop(Box::from_raw(ctx as *mut TraceState<P>)) };
647            return Err(err);
648        }
649        registry.insert(key, ctx as usize);
650        Ok(CallbackHandle::new_trace(self, ctx))
651    }
652
653    /// Register a typed authorizer callback.
654    pub fn register_authorizer<'c, F>(&'c self, f: F) -> Result<CallbackHandle<'c, 'p, P>>
655    where
656        F: FnMut(AuthorizerEvent<'_>) -> AuthorizerResult + Send + 'static,
657    {
658        let state = Box::new(AuthorizerState { cb: Box::new(f) });
659        let ctx = Box::into_raw(state) as *mut c_void;
660        let key = callback_registry_key(self.db, CallbackKind::Authorizer);
661        let mut registry = lock_callback_registry();
662        if let Err(err) = unsafe {
663            self.api
664                .set_authorizer(self.db, Some(authorizer_trampoline), ctx)
665        } {
666            unsafe { drop(Box::from_raw(ctx as *mut AuthorizerState)) };
667            return Err(err);
668        }
669        registry.insert(key, ctx as usize);
670        Ok(CallbackHandle::new_authorizer(self, ctx))
671    }
672}