Skip to main content

diesel/sqlite/connection/
raw.rs

1#![allow(unsafe_code)] // ffi calls
2#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3extern crate libsqlite3_sys as ffi;
4
5#[cfg(all(target_family = "wasm", target_os = "unknown"))]
6use sqlite_wasm_rs as ffi;
7
8use super::SqliteConnection;
9use super::authorizer::{AuthorizerContext, AuthorizerDecision};
10use super::collation_needed::{CollationNeededContext, SqliteTextRep};
11use super::functions::{build_sql_function_args, process_sql_function_result};
12use super::limits::SqliteLimit;
13use super::serialized_database::SerializedDatabase;
14use super::stmt::ensure_sqlite_ok;
15use super::trace::{SqliteTraceEvent, SqliteTraceFlags, TRACE_PROFILE, TRACE_ROW, TRACE_STMT};
16use super::update_hook::{SqliteChangeEvent, SqliteChangeOp};
17use super::{BusyDecision, CommitDecision, ProgressDecision};
18use super::{Sqlite, SqliteAggregateFunction};
19use crate::deserialize::FromSqlRow;
20use crate::result::Error::DatabaseError;
21use crate::result::*;
22use crate::serialize::ToSql;
23use crate::sql_types::HasSqlType;
24use crate::sqlite::SqliteFunctionBehavior;
25use alloc::borrow::{Cow, ToOwned};
26use alloc::boxed::Box;
27use alloc::ffi::{CString, NulError};
28use alloc::string::{String, ToString};
29use core::ffi as libc;
30use core::ffi::CStr;
31use core::num::NonZeroU32;
32use core::ptr::NonNull;
33use core::{mem, ptr, slice, str};
34
35// `sqlite3_db_config()` option codes controlling whether ATTACH may create new
36// database files (ATTACH_CREATE) or open them in write mode (ATTACH_WRITE).
37// Introduced in SQLite 3.49.0 / `libsqlite3-sys` 0.35.0, but Diesel supports
38// `libsqlite3-sys` >= 0.17.2, so we define them here to build against any
39// supported version. On an older linked SQLite the `sqlite3_db_config()` call
40// fails at runtime, which callers already handle.
41pub(super) const SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE: i32 = 1020;
42pub(super) const SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE: i32 = 1021;
43
44// Runtime extension loading (`sqlite3_load_extension`) is deliberately unsupported.
45// Platforms that build SQLite with `-DSQLITE_OMIT_LOAD_EXTENSION` (see #2180) drop
46// the symbol from the ABI, and the runtime `dlsym` workaround in #4954 proved too
47// fragile to ship. Use `declare_sql_function`, `register_auto_extension`, or
48// `SqliteConnection::with_raw_connection` instead.
49
50/// For use in FFI function, which cannot unwind.
51/// Print the message, ask to open an issue at Github and [`abort`](std::process::abort).
52macro_rules! assert_fail {
53    ($fmt:expr_2021 $(,$args:tt)*) => {
54        #[cfg(feature = "std")]
55        eprint!(concat!(
56            $fmt,
57            "If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\n",
58            "Source location: {}:{}\n",
59        ), $($args,)* file!(), line!());
60        crate::util::std_compat::abort()
61    };
62}
63
64#[allow(missing_debug_implementations, missing_copy_implementations)]
65pub(super) struct RawConnection {
66    pub(super) internal_connection: NonNull<ffi::sqlite3>,
67    /// Boxed closure kept alive while the update hook is registered.
68    update_hook: Option<Box<dyn FnMut(SqliteChangeEvent<'_>) + Send>>,
69    /// Boxed closure kept alive while the commit hook is registered.
70    commit_hook: Option<Box<dyn FnMut() -> CommitDecision + Send>>,
71    /// Boxed closure kept alive while the rollback hook is registered.
72    rollback_hook: Option<Box<dyn FnMut() + Send>>,
73    /// Boxed closure kept alive while the progress handler is registered.
74    progress_hook: Option<Box<dyn FnMut() -> ProgressDecision + Send>>,
75    /// Boxed closure kept alive while the WAL hook is registered.
76    wal_hook: Option<Box<dyn Fn(&mut SqliteConnection, &str, u32) + Send>>,
77    /// Boxed closure kept alive while the busy handler is registered.
78    busy_handler: Option<Box<dyn FnMut(i32) -> BusyDecision + Send>>,
79    /// Boxed closure kept alive while the authorizer is registered.
80    authorizer_hook: Option<Box<dyn FnMut(AuthorizerContext<'_>) -> AuthorizerDecision + Send>>,
81    /// Boxed closure kept alive while the trace callback is registered.
82    trace_hook: Option<Box<dyn FnMut(SqliteTraceEvent<'_>) + Send>>,
83    /// Boxed closure kept alive while the collation-needed callback is registered.
84    collation_needed_hook:
85        Option<Box<dyn Fn(&mut SqliteConnection, CollationNeededContext<'_>) + Send>>,
86}
87
88impl RawConnection {
89    /// Wraps a borrowed `sqlite3` pointer this `RawConnection` does not own
90    /// (kept in a `ManuallyDrop` for SQL-function callbacks, so `Drop` never runs).
91    pub(super) fn from_ptr(conn: NonNull<ffi::sqlite3>) -> Self {
92        RawConnection {
93            internal_connection: conn,
94            update_hook: None,
95            commit_hook: None,
96            rollback_hook: None,
97            progress_hook: None,
98            wal_hook: None,
99            busy_handler: None,
100            authorizer_hook: None,
101            trace_hook: None,
102            collation_needed_hook: None,
103        }
104    }
105
106    pub(super) fn establish(database_url: &str) -> ConnectionResult<Self> {
107        let mut conn_pointer = ptr::null_mut();
108
109        let database_url = if database_url.starts_with("sqlite://") {
110            CString::new(database_url.replacen("sqlite://", "file:", 1))?
111        } else {
112            CString::new(database_url)?
113        };
114        let flags = ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE | ffi::SQLITE_OPEN_URI;
115        let connection_status = unsafe {
116            ffi::sqlite3_open_v2(database_url.as_ptr(), &mut conn_pointer, flags, ptr::null())
117        };
118
119        match connection_status {
120            ffi::SQLITE_OK => {
121                let conn_pointer = unsafe { NonNull::new_unchecked(conn_pointer) };
122                Ok(RawConnection {
123                    internal_connection: conn_pointer,
124                    update_hook: None,
125                    commit_hook: None,
126                    rollback_hook: None,
127                    progress_hook: None,
128                    wal_hook: None,
129                    busy_handler: None,
130                    authorizer_hook: None,
131                    trace_hook: None,
132                    collation_needed_hook: None,
133                })
134            }
135            err_code => {
136                let message = super::error_message(err_code);
137                // sqlite3_open_v2() may allocate a database connection handle
138                // even on failure. To avoid a resource leak, it must be released
139                // with sqlite3_close(). Passing a null pointer to sqlite3_close()
140                // is a harmless no-op, so no null check is needed.
141                // See: https://www.sqlite.org/c3ref/open.html
142                unsafe { ffi::sqlite3_close(conn_pointer) };
143                Err(ConnectionError::BadConnection(message.into()))
144            }
145        }
146    }
147
148    pub(super) fn exec(&self, query: &str) -> QueryResult<()> {
149        let query = CString::new(query)?;
150        let callback_fn = None;
151        let callback_arg = ptr::null_mut();
152        let result = unsafe {
153            ffi::sqlite3_exec(
154                self.internal_connection.as_ptr(),
155                query.as_ptr(),
156                callback_fn,
157                callback_arg,
158                ptr::null_mut(),
159            )
160        };
161
162        ensure_sqlite_ok(result, self.internal_connection.as_ptr())
163    }
164
165    pub(super) fn rows_affected_by_last_query(
166        &self,
167    ) -> Result<usize, Box<dyn core::error::Error + Send + Sync>> {
168        let r = unsafe { ffi::sqlite3_changes(self.internal_connection.as_ptr()) };
169
170        Ok(r.try_into()?)
171    }
172
173    pub(super) fn last_insert_rowid(&self) -> i64 {
174        unsafe { ffi::sqlite3_last_insert_rowid(self.internal_connection.as_ptr()) }
175    }
176
177    pub(super) fn register_sql_function<F, Ret, RetSqlType>(
178        &self,
179        fn_name: &str,
180        num_args: usize,
181        behavior: SqliteFunctionBehavior,
182        f: F,
183    ) -> QueryResult<()>
184    where
185        F: FnMut(&Self, &mut [*mut ffi::sqlite3_value]) -> QueryResult<Ret>
186            + core::panic::UnwindSafe
187            + Send
188            + 'static,
189        Ret: ToSql<RetSqlType, Sqlite>,
190        Sqlite: HasSqlType<RetSqlType>,
191    {
192        let c_fn_name = Self::get_fn_name(fn_name)?;
193        let flags = behavior.to_flags();
194        let num_args = num_args
195            .try_into()
196            .map_err(|e| Error::SerializationError(Box::new(e)))?;
197        // only create the pointer as last step here
198        // as we can otherwise leak memory
199        let callback_fn = Box::into_raw(Box::new(CustomFunctionUserPtr {
200            callback: f,
201            function_name: fn_name.to_owned(),
202        }));
203
204        let result = unsafe {
205            ffi::sqlite3_create_function_v2(
206                self.internal_connection.as_ptr(),
207                c_fn_name.as_ptr(),
208                num_args,
209                flags,
210                callback_fn as *mut _,
211                Some(run_custom_function::<F, Ret, RetSqlType>),
212                None,
213                None,
214                Some(destroy_boxed::<CustomFunctionUserPtr<F>>),
215            )
216        };
217
218        Self::process_sql_function_result(result)
219    }
220
221    pub(super) fn register_aggregate_function<ArgsSqlType, RetSqlType, Args, Ret, A>(
222        &self,
223        fn_name: &str,
224        num_args: usize,
225        behavior: SqliteFunctionBehavior,
226    ) -> QueryResult<()>
227    where
228        A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send + core::panic::UnwindSafe,
229        Args: FromSqlRow<ArgsSqlType, Sqlite>,
230        Ret: ToSql<RetSqlType, Sqlite>,
231        Sqlite: HasSqlType<RetSqlType>,
232    {
233        let fn_name = Self::get_fn_name(fn_name)?;
234        let flags = behavior.to_flags();
235        let num_args = num_args
236            .try_into()
237            .map_err(|e| Error::SerializationError(Box::new(e)))?;
238
239        let result = unsafe {
240            ffi::sqlite3_create_function_v2(
241                self.internal_connection.as_ptr(),
242                fn_name.as_ptr(),
243                num_args,
244                flags,
245                core::ptr::null_mut(),
246                None,
247                Some(run_aggregator_step_function::<_, _, _, _, A>),
248                Some(run_aggregator_final_function::<_, _, _, _, A>),
249                None,
250            )
251        };
252
253        Self::process_sql_function_result(result)
254    }
255
256    pub(super) fn register_collation_function<F>(
257        &self,
258        collation_name: &str,
259        collation: F,
260    ) -> QueryResult<()>
261    where
262        F: Fn(&str, &str) -> core::cmp::Ordering + core::panic::UnwindSafe + Send + 'static,
263    {
264        let c_collation_name = Self::get_fn_name(collation_name)?;
265        // only create the pointer as last step here as we otherwise could leak memory
266        let callback_fn = Box::into_raw(Box::new(CollationUserPtr {
267            callback: collation,
268            collation_name: collation_name.to_owned(),
269        }));
270
271        let result = unsafe {
272            ffi::sqlite3_create_collation_v2(
273                self.internal_connection.as_ptr(),
274                c_collation_name.as_ptr(),
275                ffi::SQLITE_UTF8,
276                callback_fn as *mut _,
277                Some(run_collation_function::<F>),
278                Some(destroy_boxed::<CollationUserPtr<F>>),
279            )
280        };
281
282        let result = Self::process_sql_function_result(result);
283        if result.is_err() {
284            destroy_boxed::<CollationUserPtr<F>>(callback_fn as *mut _);
285        }
286        result
287    }
288
289    pub(super) fn serialize(&mut self) -> SerializedDatabase {
290        let mut size: ffi::sqlite3_int64 = 0;
291        // SAFETY: The connection is live, a null schema selects `main`, and `size` is a writable out-parameter.
292        let data_ptr = unsafe {
293            ffi::sqlite3_serialize(
294                self.internal_connection.as_ptr(),
295                core::ptr::null(),
296                &mut size as *mut _,
297                0,
298            )
299        };
300        // SQLite returns null with size zero for a valid empty deserialized
301        // database and null with a nonzero size when the output allocation
302        // fails. `SerializedDatabase` reports the failure when accessed.
303        let data = match core::ptr::NonNull::new(data_ptr) {
304            Some(data) => data,
305            None if size == 0 => return SerializedDatabase::empty(),
306            None => return SerializedDatabase::allocation_failed(),
307        };
308        // SAFETY: SQLite transferred an exclusive allocation holding `size` initialized bytes.
309        unsafe { SerializedDatabase::new(data, size) }
310    }
311
312    // SAFETY:
313    // Any caller must ensure that the provided data buffer is valid and not modified until the database connection is closed
314    // Sqlite's documentation states:
315    // Applications must not modify the buffer P or invalidate it before the database connection D is closed.
316    pub(super) unsafe fn deserialize(&mut self, data: &[u8]) -> QueryResult<()> {
317        let db_size = data
318            .len()
319            .try_into()
320            .map_err(|e| Error::DeserializationError(Box::new(e)))?;
321        // the cast for `ffi::SQLITE_DESERIALIZE_READONLY` is required for old libsqlite3-sys versions
322        #[allow(clippy::unnecessary_cast)]
323        unsafe {
324            let result = ffi::sqlite3_deserialize(
325                self.internal_connection.as_ptr(),
326                core::ptr::null(),
327                data.as_ptr() as *mut u8,
328                db_size,
329                db_size,
330                ffi::SQLITE_DESERIALIZE_READONLY as u32,
331            );
332
333            ensure_sqlite_ok(result, self.internal_connection.as_ptr())
334        }
335    }
336
337    pub(super) fn set_limit(&self, limit: SqliteLimit, value: i32) -> i32 {
338        unsafe { ffi::sqlite3_limit(self.internal_connection.as_ptr(), limit.to_ffi(), value) }
339    }
340
341    pub(super) fn get_limit(&self, limit: SqliteLimit) -> i32 {
342        unsafe {
343            // Passing -1 queries the current value without changing it
344            ffi::sqlite3_limit(self.internal_connection.as_ptr(), limit.to_ffi(), -1)
345        }
346    }
347
348    /// Set a boolean db_config option.
349    pub(super) fn set_db_config_bool(&self, op: i32, value: bool) -> QueryResult<()> {
350        let mut result_value: libc::c_int = 0;
351        let new_value: libc::c_int = if value { 1 } else { 0 };
352
353        let result = unsafe {
354            ffi::sqlite3_db_config(
355                self.internal_connection.as_ptr(),
356                op,
357                new_value,
358                &mut result_value as *mut libc::c_int,
359            )
360        };
361
362        ensure_sqlite_ok(result, self.internal_connection.as_ptr())
363    }
364
365    /// Get a boolean db_config option.
366    pub(super) fn get_db_config_bool(&self, op: i32) -> QueryResult<bool> {
367        let mut current_value: libc::c_int = 0;
368
369        let result = unsafe {
370            ffi::sqlite3_db_config(
371                self.internal_connection.as_ptr(),
372                op,
373                -1_i32, // -1 queries without changing
374                &mut current_value as *mut libc::c_int,
375            )
376        };
377
378        ensure_sqlite_ok(result, self.internal_connection.as_ptr())?;
379        Ok(current_value != 0)
380    }
381
382    fn get_fn_name(fn_name: &str) -> Result<CString, NulError> {
383        CString::new(fn_name)
384    }
385
386    fn process_sql_function_result(result: i32) -> Result<(), Error> {
387        if result == ffi::SQLITE_OK {
388            Ok(())
389        } else {
390            let error_message = super::error_message(result);
391            Err(DatabaseError(
392                DatabaseErrorKind::Unknown,
393                Box::new(error_message.to_string()),
394            ))
395        }
396    }
397
398    pub(super) fn blob_open<'conn>(
399        &'conn self,
400        database_name: &str,
401        table_name: &str,
402        column_name: &str,
403        row_id: i64,
404    ) -> Result<super::sqlite_blob::SqliteReadOnlyBlob<'conn>, Error> {
405        let database_name = alloc::ffi::CString::new(database_name)?;
406        let column_name = alloc::ffi::CString::new(column_name)?;
407        let table_name = alloc::ffi::CString::new(table_name)?;
408
409        let mut blob: *mut ffi::sqlite3_blob = core::ptr::null_mut();
410
411        // SAFETY: All variables are properly initialized
412        let ret = unsafe {
413            ffi::sqlite3_blob_open(
414                self.internal_connection.as_ptr(),
415                database_name.as_c_str().as_ptr(),
416                table_name.as_c_str().as_ptr(),
417                column_name.as_c_str().as_ptr(),
418                row_id,
419                0,
420                &mut blob,
421            )
422        };
423
424        Self::process_sql_function_result(ret)?;
425
426        // SAFETY: `sqlite3_blob_open` initializes the `blob` variable IF the return value:
427        //
428        // > On success, SQLITE_OK is returned and the new BLOB handle is stored in *ppBlob.
429        // > Otherwise an error code is returned and, unless the error code is SQLITE_MISUSE,
430        // > *ppBlob is set to NULL.
431        //
432        // And we checked the `ret` value above
433        let blob = unsafe { core::ptr::NonNull::new_unchecked(blob) };
434
435        // SAFETY: According to the SQLite docs, this can only fail if an invalid pointer is passed
436        let blob_size = unsafe { ffi::sqlite3_blob_bytes(blob.as_ptr()) };
437        let blob_size = usize::try_from(blob_size).map_err(Error::IntegerConversion)?;
438
439        Ok(super::sqlite_blob::SqliteReadOnlyBlob {
440            blob,
441            read_index: 0,
442            blob_size,
443            _pd: core::marker::PhantomData,
444        })
445    }
446
447    /// Sets the update hook, replacing any previous one.
448    ///
449    /// # Safety
450    ///
451    /// `ptr` is derived from `&raw mut *boxed` and points to the heap
452    /// allocation of the closure. `update_hook_trampoline::<F>` matches the
453    /// signature `sqlite3_update_hook` expects. The pointer stays valid because
454    /// we store `boxed` in `self.update_hook` below, keeping it alive for the
455    /// lifetime of this `RawConnection`, and the hook is removed before
456    /// `sqlite3_close` (see `Drop`), so the pointer is never read after the box
457    /// is freed.
458    pub(super) fn set_update_hook<F>(&mut self, hook: F)
459    where
460        F: FnMut(SqliteChangeEvent<'_>) + Send + 'static,
461    {
462        let mut boxed: Box<dyn FnMut(SqliteChangeEvent<'_>) + Send> = Box::new(hook);
463        let ptr = &raw mut *boxed as *mut libc::c_void;
464
465        unsafe {
466            ffi::sqlite3_update_hook(
467                self.internal_connection.as_ptr(),
468                Some(update_hook_trampoline::<F>),
469                ptr,
470            );
471        }
472
473        // The old box (if any) is dropped here after SQLite has already
474        // switched to the new pointer, preventing use-after-free.
475        self.update_hook = Some(boxed);
476    }
477
478    /// Removes the update hook.
479    ///
480    /// # Safety
481    ///
482    /// `self.internal_connection` is a valid open SQLite connection. Passing
483    /// `None` and `null_mut()` clears any installed update hook. The hook is
484    /// unregistered before dropping `self.update_hook` so SQLite no longer
485    /// reads the pointer during cleanup.
486    pub(super) fn remove_update_hook(&mut self) {
487        unsafe {
488            ffi::sqlite3_update_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
489        }
490        self.update_hook = None;
491    }
492
493    /// Sets the commit hook, replacing any previous one.
494    ///
495    /// # Safety
496    ///
497    /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
498    /// allocation of the closure. The `commit_hook_trampoline` function
499    /// matches the signature expected by `sqlite3_commit_hook`. The pointer
500    /// remains valid because we store `boxed` in `self.commit_hook` below,
501    /// keeping it alive for the lifetime of this `RawConnection`.
502    pub(super) fn set_commit_hook<F>(&mut self, hook: F)
503    where
504        F: FnMut() -> CommitDecision + Send + 'static,
505    {
506        let mut boxed: Box<dyn FnMut() -> CommitDecision + Send> = Box::new(hook);
507        let ptr = &raw mut *boxed as *mut libc::c_void;
508
509        unsafe {
510            ffi::sqlite3_commit_hook(
511                self.internal_connection.as_ptr(),
512                Some(commit_hook_trampoline::<F>),
513                ptr,
514            );
515        }
516
517        // The old Box (if any) is dropped here after SQLite has already
518        // switched to the new callback, preventing use-after-free.
519        self.commit_hook = Some(boxed);
520    }
521
522    /// Removes the commit hook.
523    ///
524    /// # Safety
525    ///
526    /// `self.internal_connection` is a valid pointer to an open SQLite
527    /// connection. Passing `None` as the hook and `null_mut()` as the user
528    /// data unregisters any existing commit hook. The hook is unregistered
529    /// before dropping `self.commit_hook` to prevent callbacks from firing
530    /// during cleanup.
531    pub(super) fn remove_commit_hook(&mut self) {
532        unsafe {
533            ffi::sqlite3_commit_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
534        }
535        self.commit_hook = None;
536    }
537
538    /// Sets the rollback hook, replacing any previous one.
539    ///
540    /// # Safety
541    ///
542    /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
543    /// allocation of the closure. The `rollback_hook_trampoline` function
544    /// matches the signature expected by `sqlite3_rollback_hook`. The pointer
545    /// remains valid because we store `boxed` in `self.rollback_hook` below,
546    /// keeping it alive for the lifetime of this `RawConnection`.
547    pub(super) fn set_rollback_hook<F>(&mut self, hook: F)
548    where
549        F: FnMut() + Send + 'static,
550    {
551        let mut boxed: Box<dyn FnMut() + Send> = Box::new(hook);
552        let ptr = &raw mut *boxed as *mut libc::c_void;
553
554        unsafe {
555            ffi::sqlite3_rollback_hook(
556                self.internal_connection.as_ptr(),
557                Some(rollback_hook_trampoline::<F>),
558                ptr,
559            );
560        }
561
562        // The old Box (if any) is dropped here after SQLite has already
563        // switched to the new callback, preventing use-after-free.
564        self.rollback_hook = Some(boxed);
565    }
566
567    /// Removes the rollback hook.
568    ///
569    /// # Safety
570    ///
571    /// `self.internal_connection` is a valid pointer to an open SQLite
572    /// connection. Passing `None` as the hook and `null_mut()` as the user
573    /// data unregisters any existing rollback hook. The hook is unregistered
574    /// before dropping `self.rollback_hook` to prevent callbacks from firing
575    /// during cleanup.
576    pub(super) fn remove_rollback_hook(&mut self) {
577        unsafe {
578            ffi::sqlite3_rollback_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
579        }
580        self.rollback_hook = None;
581    }
582
583    /// Sets the progress handler, replacing any previous one.
584    ///
585    /// `n` is the approximate number of VM instructions between callbacks.
586    ///
587    /// # Safety
588    ///
589    /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
590    /// allocation of the closure. The `progress_handler_trampoline` function
591    /// matches the signature expected by `sqlite3_progress_handler`. The
592    /// pointer remains valid because we store `boxed` in `self.progress_hook`
593    /// below, keeping it alive for the lifetime of this `RawConnection`.
594    pub(super) fn set_progress_handler<F>(&mut self, n: NonZeroU32, hook: F)
595    where
596        F: FnMut() -> ProgressDecision + Send + 'static,
597    {
598        let mut boxed: Box<dyn FnMut() -> ProgressDecision + Send> = Box::new(hook);
599        let ptr = &raw mut *boxed as *mut libc::c_void;
600
601        // `sqlite3_progress_handler` takes a c_int. A value above `i32::MAX`
602        // would wrap to a non-positive number and disable the handler, so clamp
603        // it to `i32::MAX` instead.
604        let n = i32::try_from(n.get()).unwrap_or(i32::MAX);
605
606        unsafe {
607            ffi::sqlite3_progress_handler(
608                self.internal_connection.as_ptr(),
609                n,
610                Some(progress_handler_trampoline::<F>),
611                ptr,
612            );
613        }
614
615        // The old Box (if any) is dropped here after SQLite has already
616        // switched to the new callback, preventing use-after-free.
617        self.progress_hook = Some(boxed);
618    }
619
620    /// Removes the progress handler.
621    ///
622    /// # Safety
623    ///
624    /// `self.internal_connection` is a valid pointer to an open SQLite
625    /// connection. Passing `None` as the handler and `null_mut()` as the user
626    /// data unregisters any existing progress handler. The handler is
627    /// unregistered before dropping `self.progress_hook` to prevent callbacks
628    /// from firing during cleanup.
629    pub(super) fn remove_progress_handler(&mut self) {
630        unsafe {
631            ffi::sqlite3_progress_handler(
632                self.internal_connection.as_ptr(),
633                0,
634                None,
635                ptr::null_mut(),
636            );
637        }
638        self.progress_hook = None;
639    }
640
641    /// Sets the WAL hook, replacing any previous one.
642    ///
643    /// The callback receives a borrowed `&mut SqliteConnection`, the database
644    /// name (e.g. `"main"`) and the number of pages currently in the WAL file.
645    ///
646    /// # Safety
647    ///
648    /// The `ptr` is derived from `&raw const *boxed` and points to the heap
649    /// allocation of the closure. The `wal_hook_trampoline` function matches the
650    /// signature expected by `sqlite3_wal_hook`. The pointer remains valid
651    /// because we store `boxed` in `self.wal_hook` below, keeping it alive for
652    /// the lifetime of this `RawConnection`.
653    pub(super) fn set_wal_hook<F>(&mut self, hook: F)
654    where
655        F: Fn(&mut SqliteConnection, &str, u32) + Send + 'static,
656    {
657        let boxed: Box<dyn Fn(&mut SqliteConnection, &str, u32) + Send> = Box::new(hook);
658        let ptr = &raw const *boxed as *mut libc::c_void;
659
660        unsafe {
661            ffi::sqlite3_wal_hook(
662                self.internal_connection.as_ptr(),
663                Some(wal_hook_trampoline::<F>),
664                ptr,
665            );
666        }
667
668        // The old Box (if any) is dropped here after SQLite has already
669        // switched to the new callback, preventing use-after-free.
670        self.wal_hook = Some(boxed);
671    }
672
673    /// Removes the WAL hook.
674    ///
675    /// # Safety
676    ///
677    /// `self.internal_connection` is a valid pointer to an open SQLite
678    /// connection. Passing `None` as the hook and `null_mut()` as the user
679    /// data unregisters any existing WAL hook. The hook is unregistered before
680    /// dropping `self.wal_hook` to prevent callbacks from firing during
681    /// cleanup.
682    pub(super) fn remove_wal_hook(&mut self) {
683        unsafe {
684            ffi::sqlite3_wal_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
685        }
686        self.wal_hook = None;
687    }
688
689    /// Sets the busy handler, replacing any previous one.
690    ///
691    /// Only one busy handler can be active at a time. Setting this clears any
692    /// busy timeout previously set with `set_busy_timeout`.
693    ///
694    /// # Safety
695    ///
696    /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
697    /// allocation of the closure. The `busy_handler_trampoline` function
698    /// matches the signature expected by `sqlite3_busy_handler`. The pointer
699    /// remains valid because we store `boxed` in `self.busy_handler` below,
700    /// keeping it alive for the lifetime of this `RawConnection`.
701    pub(super) fn set_busy_handler<F>(&mut self, hook: F)
702    where
703        F: FnMut(i32) -> BusyDecision + Send + 'static,
704    {
705        let mut boxed: Box<dyn FnMut(i32) -> BusyDecision + Send> = Box::new(hook);
706        let ptr = &raw mut *boxed as *mut libc::c_void;
707
708        unsafe {
709            ffi::sqlite3_busy_handler(
710                self.internal_connection.as_ptr(),
711                Some(busy_handler_trampoline::<F>),
712                ptr,
713            );
714        }
715
716        // The old Box (if any) is dropped here after SQLite has already
717        // switched to the new callback, preventing use-after-free.
718        self.busy_handler = Some(boxed);
719    }
720
721    /// Removes the busy handler.
722    ///
723    /// # Safety
724    ///
725    /// `self.internal_connection` is a valid pointer to an open SQLite
726    /// connection. Passing `None` as the hook and `null_mut()` as the user
727    /// data unregisters any existing busy handler. The hook is unregistered
728    /// before dropping `self.busy_handler` to prevent callbacks from firing
729    /// during cleanup.
730    pub(super) fn remove_busy_handler(&mut self) {
731        unsafe {
732            ffi::sqlite3_busy_handler(self.internal_connection.as_ptr(), None, ptr::null_mut());
733        }
734        self.busy_handler = None;
735    }
736
737    /// Sets a simple timeout-based busy handler.
738    ///
739    /// SQLite will sleep and retry until `ms` milliseconds have elapsed.
740    /// Setting this clears any custom busy handler.
741    ///
742    /// # Safety
743    ///
744    /// `self.internal_connection` is a valid pointer to an open SQLite
745    /// connection. `sqlite3_busy_timeout` installs its own internal busy
746    /// handler, so the stored `busy_handler` is dropped afterwards to release
747    /// the now-unused closure.
748    pub(super) fn set_busy_timeout(&mut self, ms: i32) {
749        unsafe {
750            ffi::sqlite3_busy_timeout(self.internal_connection.as_ptr(), ms);
751        }
752        self.busy_handler = None;
753    }
754
755    /// Sets the authorizer callback, replacing any previous one. Only one
756    /// can be active at a time per connection.
757    ///
758    /// # Safety
759    ///
760    /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
761    /// allocation of the closure. The `authorizer_trampoline` function
762    /// matches the signature expected by `sqlite3_set_authorizer`. The pointer
763    /// remains valid because we store `boxed` in `self.authorizer_hook` below,
764    /// keeping it alive for the lifetime of this `RawConnection`.
765    pub(super) fn set_authorizer<F>(&mut self, hook: F)
766    where
767        F: FnMut(AuthorizerContext<'_>) -> AuthorizerDecision + Send + 'static,
768    {
769        let mut boxed: Box<dyn FnMut(AuthorizerContext<'_>) -> AuthorizerDecision + Send> =
770            Box::new(hook);
771        let ptr = &raw mut *boxed as *mut libc::c_void;
772
773        unsafe {
774            ffi::sqlite3_set_authorizer(
775                self.internal_connection.as_ptr(),
776                Some(authorizer_trampoline::<F>),
777                ptr,
778            );
779        }
780
781        // The old Box (if any) is dropped here after SQLite has already
782        // switched to the new callback, preventing use-after-free.
783        self.authorizer_hook = Some(boxed);
784    }
785
786    /// Removes the authorizer callback.
787    ///
788    /// # Safety
789    ///
790    /// `self.internal_connection` is a valid pointer to an open SQLite
791    /// connection. Passing `None` as the callback and `null_mut()` as the
792    /// user data unregisters any existing authorizer. The authorizer is
793    /// unregistered before dropping `self.authorizer_hook` to prevent
794    /// callbacks from firing during cleanup.
795    pub(super) fn remove_authorizer(&mut self) {
796        unsafe {
797            ffi::sqlite3_set_authorizer(self.internal_connection.as_ptr(), None, ptr::null_mut());
798        }
799        self.authorizer_hook = None;
800    }
801
802    /// Sets a trace callback, replacing any previous one.
803    ///
804    /// The callback is invoked for SQL execution tracing based on the
805    /// provided event mask.
806    ///
807    /// # Safety
808    ///
809    /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
810    /// allocation of the closure. The `trace_trampoline` function matches the
811    /// signature expected by `sqlite3_trace_v2`. The pointer remains valid
812    /// because we store `boxed` in `self.trace_hook` below, keeping it alive
813    /// for the lifetime of this `RawConnection`.
814    pub(super) fn set_trace<F>(&mut self, mask: SqliteTraceFlags, hook: F)
815    where
816        F: FnMut(SqliteTraceEvent<'_>) + Send + 'static,
817    {
818        let mut boxed: Box<dyn FnMut(SqliteTraceEvent<'_>) + Send> = Box::new(hook);
819        let ptr = &raw mut *boxed as *mut libc::c_void;
820
821        unsafe {
822            ffi::sqlite3_trace_v2(
823                self.internal_connection.as_ptr(),
824                mask.bits(),
825                Some(trace_trampoline::<F>),
826                ptr,
827            );
828        }
829
830        // The old Box (if any) is dropped here after SQLite has already
831        // switched to the new callback, preventing use-after-free.
832        self.trace_hook = Some(boxed);
833    }
834
835    /// Removes the trace callback.
836    ///
837    /// # Safety
838    ///
839    /// `self.internal_connection` is a valid pointer to an open SQLite
840    /// connection. Passing a mask of `0`, `None` as the callback, and
841    /// `null_mut()` as the user data unregisters any existing trace callback.
842    /// The callback is unregistered before dropping `self.trace_hook` to
843    /// prevent callbacks from firing during cleanup.
844    pub(super) fn remove_trace(&mut self) {
845        unsafe {
846            ffi::sqlite3_trace_v2(self.internal_connection.as_ptr(), 0, None, ptr::null_mut());
847        }
848        self.trace_hook = None;
849    }
850
851    /// Sets the collation-needed callback, replacing any previous one.
852    ///
853    /// # Safety
854    ///
855    /// `ptr` points to the heap allocation of `boxed`, stored in
856    /// `self.collation_needed_hook` below so it outlives the C-side
857    /// registration.
858    pub(super) fn set_collation_needed_hook<F>(&mut self, hook: F)
859    where
860        F: Fn(&mut SqliteConnection, CollationNeededContext<'_>) + Send + 'static,
861    {
862        let boxed: Box<dyn Fn(&mut SqliteConnection, CollationNeededContext<'_>) + Send> =
863            Box::new(hook);
864        let ptr = &raw const *boxed as *mut libc::c_void;
865
866        unsafe {
867            ffi::sqlite3_collation_needed(
868                self.internal_connection.as_ptr(),
869                ptr,
870                Some(collation_needed_trampoline::<F>),
871            );
872        }
873
874        // The old Box (if any) is dropped here after SQLite has already
875        // switched to the new callback, preventing use-after-free.
876        self.collation_needed_hook = Some(boxed);
877    }
878
879    /// Removes the collation-needed callback.
880    ///
881    /// # Safety
882    ///
883    /// Unregisters via `sqlite3_collation_needed(db, null, None)` before
884    /// dropping `self.collation_needed_hook`, so no callback can fire during
885    /// cleanup.
886    pub(super) fn remove_collation_needed_hook(&mut self) {
887        unsafe {
888            ffi::sqlite3_collation_needed(self.internal_connection.as_ptr(), ptr::null_mut(), None);
889        }
890        self.collation_needed_hook = None;
891    }
892}
893
894impl Drop for RawConnection {
895    fn drop(&mut self) {
896        use crate::util::std_compat::panicking;
897
898        // Unregister before close so the boxed closures drop before sqlite3_close.
899        self.remove_update_hook();
900        self.remove_commit_hook();
901        self.remove_rollback_hook();
902        self.remove_progress_handler();
903        self.remove_wal_hook();
904        self.remove_busy_handler();
905        self.remove_authorizer();
906        self.remove_trace();
907        self.remove_collation_needed_hook();
908
909        let close_result = unsafe { ffi::sqlite3_close(self.internal_connection.as_ptr()) };
910        if close_result != ffi::SQLITE_OK {
911            let error_message = super::error_message(close_result);
912            if panicking() {
913                #[cfg(feature = "std")]
914                {
    ::std::io::_eprint(format_args!("Error closing SQLite connection: {0}\n",
            error_message));
};eprintln!("Error closing SQLite connection: {error_message}");
915            } else {
916                {
    ::core::panicking::panic_fmt(format_args!("Error closing SQLite connection: {0}",
            error_message));
};panic!("Error closing SQLite connection: {error_message}");
917            }
918        }
919    }
920}
921
922enum SqliteCallbackError {
923    Abort(&'static str),
924    DieselError(crate::result::Error),
925    Panic(String),
926}
927
928impl SqliteCallbackError {
929    fn emit(&self, ctx: *mut ffi::sqlite3_context) {
930        let s;
931        let msg = match self {
932            SqliteCallbackError::Abort(msg) => *msg,
933            SqliteCallbackError::DieselError(e) => {
934                s = e.to_string();
935                &s
936            }
937            SqliteCallbackError::Panic(msg) => msg,
938        };
939        unsafe {
940            context_error_str(ctx, msg);
941        }
942    }
943}
944
945impl From<crate::result::Error> for SqliteCallbackError {
946    fn from(e: crate::result::Error) -> Self {
947        Self::DieselError(e)
948    }
949}
950
951struct CustomFunctionUserPtr<F> {
952    callback: F,
953    function_name: String,
954}
955
956#[allow(warnings)]
957extern "C" fn run_custom_function<F, Ret, RetSqlType>(
958    ctx: *mut ffi::sqlite3_context,
959    num_args: libc::c_int,
960    value_ptr: *mut *mut ffi::sqlite3_value,
961) where
962    F: FnMut(&RawConnection, &mut [*mut ffi::sqlite3_value]) -> QueryResult<Ret>
963        + core::panic::UnwindSafe
964        + Send
965        + 'static,
966    Ret: ToSql<RetSqlType, Sqlite>,
967    Sqlite: HasSqlType<RetSqlType>,
968{
969    use core::ops::Deref;
970    static NULL_DATA_ERR: &str = "An unknown error occurred. sqlite3_user_data returned a null pointer. This should never happen.";
971    static NULL_CONN_ERR: &str = "An unknown error occurred. sqlite3_context_db_handle returned a null pointer. This should never happen.";
972
973    let conn = match unsafe { NonNull::new(ffi::sqlite3_context_db_handle(ctx)) } {
974        // We use `ManuallyDrop` here because we do not want to run the
975        // Drop impl of `RawConnection` as this would close the connection
976        Some(conn) => mem::ManuallyDrop::new(RawConnection::from_ptr(conn)),
977        None => {
978            unsafe { context_error_str(ctx, NULL_CONN_ERR) };
979            return;
980        }
981    };
982
983    let data_ptr = unsafe { ffi::sqlite3_user_data(ctx) };
984
985    let mut data_ptr = match NonNull::new(data_ptr as *mut CustomFunctionUserPtr<F>) {
986        None => unsafe {
987            context_error_str(ctx, NULL_DATA_ERR);
988            return;
989        },
990        Some(mut f) => f,
991    };
992    let data_ptr = unsafe { data_ptr.as_mut() };
993
994    // We need this to move the reference into the catch_unwind part
995    // this is sound as `F` itself and the stored string is `UnwindSafe`
996    let callback = core::panic::AssertUnwindSafe(&mut data_ptr.callback);
997    // conn holds non-UnwindSafe fields: the boxed commit hook and the boxed
998    // update hook. The ManuallyDrop wrapper ensures we never run RawConnection's Drop.
999    let conn = core::panic::AssertUnwindSafe(conn);
1000
1001    let result = crate::util::std_compat::catch_unwind(move || {
1002        let _ = &callback;
1003        let args = unsafe { slice::from_raw_parts_mut(value_ptr, num_args as _) };
1004        let res = (callback.0)(&*conn, args)?;
1005        let value = process_sql_function_result(&res)?;
1006        // We've checked already that ctx is not null
1007        unsafe {
1008            value.result_of(&mut *ctx);
1009        }
1010        Ok(())
1011    })
1012    .unwrap_or_else(|p| Err(SqliteCallbackError::Panic(data_ptr.function_name.clone())));
1013    if let Err(e) = result {
1014        e.emit(ctx);
1015    }
1016}
1017
1018#[allow(warnings)]
1019extern "C" fn run_aggregator_step_function<ArgsSqlType, RetSqlType, Args, Ret, A>(
1020    ctx: *mut ffi::sqlite3_context,
1021    num_args: libc::c_int,
1022    value_ptr: *mut *mut ffi::sqlite3_value,
1023) where
1024    A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send + core::panic::UnwindSafe,
1025    Args: FromSqlRow<ArgsSqlType, Sqlite>,
1026    Ret: ToSql<RetSqlType, Sqlite>,
1027    Sqlite: HasSqlType<RetSqlType>,
1028{
1029    let result = crate::util::std_compat::catch_unwind(move || {
1030        let args = unsafe { slice::from_raw_parts_mut(value_ptr, num_args as _) };
1031        run_aggregator_step::<A, Args, ArgsSqlType>(ctx, args)
1032    })
1033    .unwrap_or_else(|e| {
1034        Err(SqliteCallbackError::Panic(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}::step() panicked",
                core::any::type_name::<A>()))
    })alloc::format!(
1035            "{}::step() panicked",
1036            core::any::type_name::<A>()
1037        )))
1038    });
1039
1040    match result {
1041        Ok(()) => {}
1042        Err(e) => e.emit(ctx),
1043    }
1044}
1045
1046fn run_aggregator_step<A, Args, ArgsSqlType>(
1047    ctx: *mut ffi::sqlite3_context,
1048    args: &mut [*mut ffi::sqlite3_value],
1049) -> Result<(), SqliteCallbackError>
1050where
1051    A: SqliteAggregateFunction<Args>,
1052    Args: FromSqlRow<ArgsSqlType, Sqlite>,
1053{
1054    let aggregator = unsafe {
1055        const {
1056            if core::mem::size_of::<*mut A>() == 0 {
1057                {
    ::core::panicking::panic_fmt(format_args!("The pointer size is zero, that\'s unexpected.If you ever see this error message open a issuedescribing your environment"));
};panic!(
1058                    "The pointer size is zero, that's unexpected.\
1059                        If you ever see this error message open a issue\
1060                        describing your environment"
1061                );
1062            }
1063        }
1064        // sqlite3_aggregate_context will return a memory allocation of the requested
1065        // size. For the first call this will be zeroed, for any future call in the same execution
1066        // this will contain the value we wrote into it.
1067        //
1068        // We write just a pointer to rust allocated memory in there to
1069        // have the rust side deal with layout and alignment of our aggregator
1070        let ctx = ffi::sqlite3_aggregate_context(
1071            ctx,
1072            core::mem::size_of::<*mut A>()
1073                .try_into()
1074                .expect("Memory size of a pointer is smaller than i32::MAX"),
1075        )
1076        // we cast the returned memory here to be a pointer to the aggregate instance
1077        .cast::<*mut A>();
1078        // sqlite3_aggregate_context returns a null pointer if the allocation fails
1079        if ctx.is_null() {
1080            return Err(SqliteCallbackError::Abort(
1081                "sqlite3_aggregate_context failed to allocate memory for the aggregate state",
1082            ));
1083        }
1084        // we are interested in the inner pointer
1085        let inner = &mut *ctx;
1086        // if the inner pointer is null we the aggregate_step
1087        // function is executed the first time and we need to create the actual
1088        // aggregator
1089        if inner.is_null() {
1090            // for that we allocate a box and turn it into a raw pointer
1091            // by leaking the memory
1092            let obj = Box::into_raw(Box::new(A::default()));
1093            *inner = obj;
1094        }
1095        // at this point the inner value is never null
1096        // as we initialised in in the null branch above,
1097        // therefore it's sound to dereference the pointer here
1098        &mut **inner
1099    };
1100
1101    // SAFETY: SQLite passes a live context for the duration of this callback.
1102    let connection = unsafe { NonNull::new(ffi::sqlite3_context_db_handle(ctx)) };
1103    let connection = connection.ok_or(SqliteCallbackError::Abort(
1104        "sqlite3_context_db_handle returned a null pointer. This should never happen",
1105    ))?;
1106    let args = build_sql_function_args::<ArgsSqlType, Args>(args, connection)?;
1107
1108    aggregator.step(args);
1109    Ok(())
1110}
1111
1112extern "C" fn run_aggregator_final_function<ArgsSqlType, RetSqlType, Args, Ret, A>(
1113    ctx: *mut ffi::sqlite3_context,
1114) where
1115    A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send,
1116    Args: FromSqlRow<ArgsSqlType, Sqlite>,
1117    Ret: ToSql<RetSqlType, Sqlite>,
1118    Sqlite: HasSqlType<RetSqlType>,
1119{
1120    let result = crate::util::std_compat::catch_unwind(|| {
1121        let aggregator = unsafe {
1122            // Get back the aggregated context
1123            // This might be null
1124            let ctx = ffi::sqlite3_aggregate_context(
1125                ctx,
1126                // use zero sized allocation here to not allocate if this is the first call to `sqlite3_aggregate_context`
1127                0,
1128            )
1129            // the allocation contains a pointer to the actual aggregator
1130            .cast::<*mut A>();
1131            // if the context was not allocated yet
1132            // we get back a null pointer here due to
1133            // the requested zero sized allocation
1134            if ctx.is_null() {
1135                None
1136            } else {
1137                // from this point we are interested in the inner pointer
1138                // we checked above that this pointer is not null
1139                // so it's sound to dereference it
1140                let inner = &mut *ctx;
1141                if inner.is_null() {
1142                    // if the inner pointer is null the aggregator has not been initialized
1143                    None
1144                } else {
1145                    // if it's not null
1146                    // we need to construct back the box and move out the
1147                    // value to correctly deallocate the allocation
1148                    let value = Box::from_raw(*inner);
1149                    let value = Some(*value);
1150                    // we also want to write a null pointer back to the
1151                    // context to make sure that there is no dangling pointer left
1152                    *inner = core::ptr::null_mut();
1153                    value
1154                }
1155            }
1156        };
1157
1158        let res = A::finalize(aggregator);
1159        let value = process_sql_function_result(&res)?;
1160        // We've checked already that ctx is not null
1161        let r = unsafe { value.result_of(&mut *ctx) };
1162        r.map_err(|e| {
1163            SqliteCallbackError::DieselError(crate::result::Error::SerializationError(Box::new(e)))
1164        })?;
1165        Ok(())
1166    })
1167    .unwrap_or_else(|_e| {
1168        Err(SqliteCallbackError::Panic(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}::finalize() panicked",
                core::any::type_name::<A>()))
    })alloc::format!(
1169            "{}::finalize() panicked",
1170            core::any::type_name::<A>()
1171        )))
1172    });
1173    if let Err(e) = result {
1174        e.emit(ctx);
1175    }
1176}
1177
1178unsafe fn context_error_str(ctx: *mut ffi::sqlite3_context, error: &str) {
1179    let len: i32 = error.len().try_into().unwrap_or(i32::MAX);
1180    unsafe {
1181        ffi::sqlite3_result_error(ctx, error.as_ptr() as *const _, len);
1182    }
1183}
1184
1185struct CollationUserPtr<F> {
1186    callback: F,
1187    collation_name: String,
1188}
1189
1190#[allow(warnings)]
1191extern "C" fn run_collation_function<F>(
1192    user_ptr: *mut libc::c_void,
1193    lhs_len: libc::c_int,
1194    lhs_ptr: *const libc::c_void,
1195    rhs_len: libc::c_int,
1196    rhs_ptr: *const libc::c_void,
1197) -> libc::c_int
1198where
1199    F: Fn(&str, &str) -> core::cmp::Ordering + Send + core::panic::UnwindSafe + 'static,
1200{
1201    let user_ptr = user_ptr as *const CollationUserPtr<F>;
1202    let user_ptr = core::panic::AssertUnwindSafe(unsafe { user_ptr.as_ref() });
1203
1204    let result = crate::util::std_compat::catch_unwind(|| {
1205        let user_ptr = user_ptr.ok_or_else(|| {
1206            SqliteCallbackError::Abort(
1207                "Got a null pointer as data pointer. This should never happen",
1208            )
1209        })?;
1210        for (ptr, len, side) in &[(rhs_ptr, rhs_len, "rhs"), (lhs_ptr, lhs_len, "lhs")] {
1211            if *len < 0 {
1212                {
    ::std::io::_eprint(format_args!("An unknown error occurred. {0}_len is negative. This should never happen.If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {1}:{2}\n",
            side, "diesel/src/sqlite/connection/raw.rs", 1212u32));
};
crate::util::std_compat::abort();assert_fail!(
1213                    "An unknown error occurred. {}_len is negative. This should never happen.",
1214                    side
1215                );
1216            }
1217            if ptr.is_null() {
1218                {
    ::std::io::_eprint(format_args!("An unknown error occurred. {0}_ptr is a null pointer. This should never happen.If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {1}:{2}\n",
            side, "diesel/src/sqlite/connection/raw.rs", 1218u32));
};
crate::util::std_compat::abort();assert_fail!(
1219                "An unknown error occurred. {}_ptr is a null pointer. This should never happen.",
1220                side
1221            );
1222            }
1223        }
1224
1225        let (rhs, lhs) = unsafe {
1226            // Depending on the eTextRep-parameter to sqlite3_create_collation_v2() the strings can
1227            // have various encodings. register_collation_function() always selects SQLITE_UTF8, so the
1228            // pointers point to valid UTF-8 strings (assuming correct behavior of libsqlite3).
1229            (
1230                str::from_utf8(slice::from_raw_parts(rhs_ptr as *const u8, rhs_len as _)),
1231                str::from_utf8(slice::from_raw_parts(lhs_ptr as *const u8, lhs_len as _)),
1232            )
1233        };
1234
1235        let rhs =
1236            rhs.map_err(|_| SqliteCallbackError::Abort("Got an invalid UTF-8 string for rhs"))?;
1237        let lhs =
1238            lhs.map_err(|_| SqliteCallbackError::Abort("Got an invalid UTF-8 string for lhs"))?;
1239
1240        Ok((user_ptr.callback)(rhs, lhs))
1241    })
1242    .unwrap_or_else(|p| {
1243        Err(SqliteCallbackError::Panic(
1244            user_ptr
1245                .map(|u| u.collation_name.clone())
1246                .unwrap_or_default(),
1247        ))
1248    });
1249
1250    match result {
1251        Ok(core::cmp::Ordering::Less) => -1,
1252        Ok(core::cmp::Ordering::Equal) => 0,
1253        Ok(core::cmp::Ordering::Greater) => 1,
1254        Err(SqliteCallbackError::Abort(a)) => {
1255            #[cfg(feature = "std")]
1256            {
    ::std::io::_eprint(format_args!("Collation function {0} failed with: {1}\n",
            user_ptr.map(|c| &c.collation_name as &str).unwrap_or_default(),
            a));
};eprintln!(
1257                "Collation function {} failed with: {}",
1258                user_ptr
1259                    .map(|c| &c.collation_name as &str)
1260                    .unwrap_or_default(),
1261                a
1262            );
1263            crate::util::std_compat::abort()
1264        }
1265        Err(SqliteCallbackError::DieselError(e)) => {
1266            #[cfg(feature = "std")]
1267            {
    ::std::io::_eprint(format_args!("Collation function {0} failed with: {1}\n",
            user_ptr.map(|c| &c.collation_name as &str).unwrap_or_default(),
            e));
};eprintln!(
1268                "Collation function {} failed with: {}",
1269                user_ptr
1270                    .map(|c| &c.collation_name as &str)
1271                    .unwrap_or_default(),
1272                e
1273            );
1274            crate::util::std_compat::abort()
1275        }
1276        Err(SqliteCallbackError::Panic(msg)) => {
1277            #[cfg(feature = "std")]
1278            {
    ::std::io::_eprint(format_args!("Collation function {0} panicked\n",
            msg));
};eprintln!("Collation function {} panicked", msg);
1279            crate::util::std_compat::abort()
1280        }
1281    }
1282}
1283
1284extern "C" fn destroy_boxed<F>(data: *mut libc::c_void) {
1285    let ptr = data as *mut F;
1286    unsafe { core::mem::drop(Box::from_raw(ptr)) };
1287}
1288
1289/// C trampoline for `sqlite3_update_hook`.
1290///
1291/// # Safety
1292///
1293/// `user_data` must point to a live `F` stored in `RawConnection::update_hook`.
1294/// This is guaranteed because the box is kept alive there and the hook is
1295/// unregistered before the connection is dropped. SQLite forbids the callback
1296/// from modifying the connection, so it cannot re-enter this trampoline, and
1297/// the `&mut` borrow is never aliased (the same contract the commit hook
1298/// relies on).
1299unsafe extern "C" fn update_hook_trampoline<F>(
1300    user_data: *mut libc::c_void,
1301    op: libc::c_int,
1302    db_name: *const libc::c_char,
1303    table_name: *const libc::c_char,
1304    rowid: ffi::sqlite3_int64,
1305) where
1306    F: FnMut(SqliteChangeEvent<'_>),
1307{
1308    let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1309        // SAFETY: `user_data` points to a live `F` in `RawConnection::update_hook`.
1310        let hook = unsafe { &mut *(user_data as *mut F) };
1311
1312        // SAFETY: SQLite passes valid C strings. Decode lossily so a non-UTF-8
1313        // name cannot abort the process (matching the trace and wal hooks).
1314        let db_name = unsafe { CStr::from_ptr(db_name) }.to_string_lossy();
1315        let table_name = unsafe { CStr::from_ptr(table_name) }.to_string_lossy();
1316
1317        hook(SqliteChangeEvent {
1318            op: SqliteChangeOp::from_ffi(op),
1319            db_name: &db_name,
1320            table_name: &table_name,
1321            rowid,
1322        });
1323    }));
1324
1325    if result.is_err() {
1326        {
    ::std::io::_eprint(format_args!("Panic in sqlite3_update_hook trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
            "diesel/src/sqlite/connection/raw.rs", 1326u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_update_hook trampoline. ");
1327    }
1328}
1329
1330/// C trampoline for `sqlite3_commit_hook`.
1331///
1332/// # Safety
1333///
1334/// `user_data` must point to a live `F` stored in `RawConnection::commit_hook`.
1335unsafe extern "C" fn commit_hook_trampoline<F>(user_data: *mut libc::c_void) -> libc::c_int
1336where
1337    F: FnMut() -> CommitDecision,
1338{
1339    let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1340        // SAFETY: `user_data` points to a live `F` in `RawConnection::commit_hook`.
1341        let f = unsafe { &mut *(user_data as *mut F) };
1342        f()
1343    }));
1344
1345    match result {
1346        Ok(CommitDecision::Rollback) => 1,
1347        Ok(CommitDecision::Proceed) => 0,
1348        Err(_) => {
1349            {
    ::std::io::_eprint(format_args!("Panic in sqlite3_commit_hook trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
            "diesel/src/sqlite/connection/raw.rs", 1349u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_commit_hook trampoline. ");
1350        }
1351    }
1352}
1353
1354/// C trampoline for `sqlite3_rollback_hook`.
1355///
1356/// # Safety
1357///
1358/// `user_data` must point to a live `F` stored in `RawConnection::rollback_hook`.
1359unsafe extern "C" fn rollback_hook_trampoline<F>(user_data: *mut libc::c_void)
1360where
1361    F: FnMut(),
1362{
1363    let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1364        // SAFETY: `user_data` points to a live `F` in `RawConnection::rollback_hook`.
1365        let f = unsafe { &mut *(user_data as *mut F) };
1366        f();
1367    }));
1368
1369    if result.is_err() {
1370        {
    ::std::io::_eprint(format_args!("Panic in sqlite3_rollback_hook trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
            "diesel/src/sqlite/connection/raw.rs", 1370u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_rollback_hook trampoline. ");
1371    }
1372}
1373
1374/// C trampoline for `sqlite3_progress_handler`.
1375///
1376/// # Safety
1377///
1378/// `user_data` must point to a live `F` stored in `RawConnection::progress_hook`.
1379unsafe extern "C" fn progress_handler_trampoline<F>(user_data: *mut libc::c_void) -> libc::c_int
1380where
1381    F: FnMut() -> ProgressDecision,
1382{
1383    let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1384        // SAFETY: `user_data` points to a live `F` in `RawConnection::progress_hook`.
1385        let f = unsafe { &mut *(user_data as *mut F) };
1386        f()
1387    }));
1388
1389    match result {
1390        Ok(ProgressDecision::Interrupt) => 1,
1391        Ok(ProgressDecision::Continue) => 0,
1392        Err(_) => {
1393            {
    ::std::io::_eprint(format_args!("Panic in sqlite3_progress_handler trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
            "diesel/src/sqlite/connection/raw.rs", 1393u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_progress_handler trampoline. ");
1394        }
1395    }
1396}
1397
1398/// C trampoline for `sqlite3_wal_hook`.
1399///
1400/// # Safety
1401///
1402/// `user_data` must point to a live `F` in `RawConnection::wal_hook`. `db` is
1403/// the `sqlite3` handle that fired the hook, open and unlocked for the duration
1404/// of the call.
1405unsafe extern "C" fn wal_hook_trampoline<F>(
1406    user_data: *mut libc::c_void,
1407    db: *mut ffi::sqlite3,
1408    db_name: *const libc::c_char,
1409    n_pages: libc::c_int,
1410) -> libc::c_int
1411where
1412    F: Fn(&mut SqliteConnection, &str, u32),
1413{
1414    let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1415        // SAFETY: `user_data` points to a live `F` in `RawConnection::wal_hook`.
1416        let f = unsafe { &*(user_data as *const F) };
1417
1418        // SAFETY: when non-null, `db_name` is a valid C string from SQLite. Use
1419        // a lossy conversion so a pathological name cannot abort, and map null
1420        // to "".
1421        let db_name: Cow<'_, str> = if db_name.is_null() {
1422            Cow::Borrowed("")
1423        } else {
1424            unsafe { CStr::from_ptr(db_name) }.to_string_lossy()
1425        };
1426        // SQLite always reports a non-negative page count. Clamp defensively.
1427        let n_pages = u32::try_from(n_pages).unwrap_or(0);
1428
1429        let Some(db) = NonNull::new(db) else {
1430            return;
1431        };
1432
1433        // SAFETY: per the `sqlite3_wal_hook` docs the commit is complete and the
1434        // write-lock released, so `db` may be used. `with_borrowed_connection`
1435        // finalizes statements on return and never closes `db`. The callback is
1436        // `Fn`, so a re-entrant call (a committing write inside it) is sound.
1437        unsafe {
1438            SqliteConnection::with_borrowed_connection(db, |conn| f(conn, &db_name, n_pages));
1439        }
1440    }));
1441
1442    if result.is_err() {
1443        {
    ::std::io::_eprint(format_args!("Panic in sqlite3_wal_hook trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
            "diesel/src/sqlite/connection/raw.rs", 1443u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_wal_hook trampoline. ");
1444    }
1445
1446    ffi::SQLITE_OK
1447}
1448
1449/// C trampoline for `sqlite3_busy_handler`.
1450///
1451/// # Safety
1452///
1453/// `user_data` must point to a live `F` stored in `RawConnection::busy_handler`.
1454unsafe extern "C" fn busy_handler_trampoline<F>(
1455    user_data: *mut libc::c_void,
1456    retry_count: libc::c_int,
1457) -> libc::c_int
1458where
1459    F: FnMut(i32) -> BusyDecision,
1460{
1461    let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1462        // SAFETY: `user_data` points to a live `F` in `RawConnection::busy_handler`.
1463        let f = unsafe { &mut *(user_data as *mut F) };
1464        f(retry_count)
1465    }));
1466
1467    match result {
1468        Ok(BusyDecision::Retry) => 1,
1469        Ok(BusyDecision::GiveUp) => 0,
1470        Err(_) => {
1471            {
    ::std::io::_eprint(format_args!("Panic in sqlite3_busy_handler trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
            "diesel/src/sqlite/connection/raw.rs", 1471u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_busy_handler trampoline. ");
1472        }
1473    }
1474}
1475
1476/// C trampoline for `sqlite3_set_authorizer`.
1477///
1478/// # Safety
1479///
1480/// `user_data` must point to a live `F` stored in `RawConnection::authorizer_hook`.
1481unsafe extern "C" fn authorizer_trampoline<F>(
1482    user_data: *mut libc::c_void,
1483    action_code: libc::c_int,
1484    arg1: *const libc::c_char,
1485    arg2: *const libc::c_char,
1486    db_name: *const libc::c_char,
1487    accessor: *const libc::c_char,
1488) -> libc::c_int
1489where
1490    F: FnMut(AuthorizerContext<'_>) -> AuthorizerDecision,
1491{
1492    // Convert a nullable C string argument to `Option<&str>`. A null pointer or
1493    // a non-UTF-8 string both map to `None`. The borrow is tied to this call via
1494    // the generic lifetime, and the resulting `&str` only lives inside the
1495    // `AuthorizerContext` passed to the callback, which cannot escape the call.
1496    fn to_str<'a>(ptr: *const libc::c_char) -> Option<&'a str> {
1497        if ptr.is_null() {
1498            None
1499        } else {
1500            // SAFETY: per the `sqlite3_set_authorizer` contract a non-null
1501            // pointer is a valid C string for the duration of the call.
1502            unsafe { CStr::from_ptr(ptr) }.to_str().ok()
1503        }
1504    }
1505
1506    let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1507        // SAFETY: `user_data` points to a live `F` in `RawConnection::authorizer_hook`.
1508        let f = unsafe { &mut *(user_data as *mut F) };
1509
1510        let ctx = AuthorizerContext::from_ffi(
1511            action_code,
1512            to_str(arg1),
1513            to_str(arg2),
1514            to_str(db_name),
1515            to_str(accessor),
1516        );
1517
1518        f(ctx)
1519    }));
1520
1521    match result {
1522        Ok(decision) => decision.to_ffi(),
1523        Err(_) => {
1524            {
    ::std::io::_eprint(format_args!("Panic in sqlite3_set_authorizer trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
            "diesel/src/sqlite/connection/raw.rs", 1524u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_set_authorizer trampoline. ");
1525        }
1526    }
1527}
1528
1529/// C trampoline for `sqlite3_trace_v2`.
1530///
1531/// # Safety
1532///
1533/// `user_data` must point to a live `F` stored in `RawConnection::trace_hook`.
1534unsafe extern "C" fn trace_trampoline<F>(
1535    event_code: libc::c_uint,
1536    user_data: *mut libc::c_void,
1537    p: *mut libc::c_void,
1538    x: *mut libc::c_void,
1539) -> libc::c_int
1540where
1541    F: FnMut(SqliteTraceEvent<'_>) + Send + 'static,
1542{
1543    let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1544        // SAFETY: `user_data` points to a live `F` in `RawConnection::trace_hook`.
1545        let f = unsafe { &mut *(user_data as *mut F) };
1546
1547        // The callback is invoked inside each arm so the lossy `Cow` holding the
1548        // SQL text outlives the `&str` borrow handed to it. `TRACE_STMT`,
1549        // `TRACE_PROFILE`, and `TRACE_ROW` are the `u32`-normalized event codes
1550        // from the `trace` module, so they match `event_code` directly.
1551        match event_code {
1552            TRACE_STMT => {
1553                // p = sqlite3_stmt*, x = const char* (unexpanded SQL).
1554                let stmt_ptr = p as *mut ffi::sqlite3_stmt;
1555                let sql_ptr = x as *const libc::c_char;
1556                if sql_ptr.is_null() {
1557                    return;
1558                }
1559                // SAFETY: a non-null `x` is a valid C string from SQLite. Use a
1560                // lossy conversion so non-UTF-8 SQL cannot abort the process.
1561                let sql = unsafe { CStr::from_ptr(sql_ptr) }.to_string_lossy();
1562                let readonly =
1563                    !stmt_ptr.is_null() && unsafe { ffi::sqlite3_stmt_readonly(stmt_ptr) != 0 };
1564                f(SqliteTraceEvent::Statement {
1565                    sql: &sql,
1566                    readonly,
1567                });
1568            }
1569            TRACE_PROFILE => {
1570                // p = sqlite3_stmt*, x = sqlite3_int64* (nanoseconds).
1571                let stmt_ptr = p as *mut ffi::sqlite3_stmt;
1572                let duration_ns = unsafe {
1573                    // x points to a sqlite3_int64. The duration is non-negative.
1574                    (x as *const ffi::sqlite3_int64).as_ref()
1575                }
1576                .copied()
1577                .unwrap_or_default()
1578                .cast_unsigned();
1579
1580                let readonly =
1581                    !stmt_ptr.is_null() && unsafe { ffi::sqlite3_stmt_readonly(stmt_ptr) != 0 };
1582                let sql_ptr = if stmt_ptr.is_null() {
1583                    core::ptr::null()
1584                } else {
1585                    unsafe { ffi::sqlite3_sql(stmt_ptr) }
1586                };
1587                // SAFETY: when non-null, `sqlite3_sql` returns a valid C string.
1588                let sql = if sql_ptr.is_null() {
1589                    Cow::Borrowed("")
1590                } else {
1591                    unsafe { CStr::from_ptr(sql_ptr) }.to_string_lossy()
1592                };
1593                f(SqliteTraceEvent::Profile {
1594                    sql: &sql,
1595                    duration_ns,
1596                    readonly,
1597                });
1598            }
1599            TRACE_ROW => f(SqliteTraceEvent::Row),
1600            // Unknown or unhandled events are ignored. CLOSE is intentionally
1601            // not handled: diesel removes the trace before closing the
1602            // connection, so it never fires.
1603            _ => {}
1604        }
1605    }));
1606
1607    if result.is_err() {
1608        {
    ::std::io::_eprint(format_args!("Panic in sqlite3_trace_v2 trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
            "diesel/src/sqlite/connection/raw.rs", 1608u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_trace_v2 trampoline. ");
1609    }
1610
1611    0 // Return value is currently unused by SQLite
1612}
1613
1614/// C trampoline for `sqlite3_collation_needed`.
1615///
1616/// # Safety
1617///
1618/// `user_data` must point to a live `F` in
1619/// `RawConnection::collation_needed_hook`. `db` is the connection that fired
1620/// the callback and is open for the duration of the call. SQLite may
1621/// re-enter this trampoline if the callback registers a collation that is
1622/// itself missing, so the closure is stored as `Fn` and borrowed shared.
1623unsafe extern "C" fn collation_needed_trampoline<F>(
1624    user_data: *mut libc::c_void,
1625    db: *mut ffi::sqlite3,
1626    e_text_rep: libc::c_int,
1627    name: *const libc::c_char,
1628) where
1629    F: Fn(&mut SqliteConnection, CollationNeededContext<'_>),
1630{
1631    let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1632        // SAFETY: `user_data` points to a live `F` in
1633        // `RawConnection::collation_needed_hook`.
1634        let f = unsafe { &*(user_data as *const F) };
1635
1636        // SAFETY: when non-null, `name` is a valid C string from SQLite. Use a
1637        // lossy conversion so a pathological name cannot abort the process, and
1638        // map null to "".
1639        let name: Cow<'_, str> = if name.is_null() {
1640            Cow::Borrowed("")
1641        } else {
1642            unsafe { CStr::from_ptr(name) }.to_string_lossy()
1643        };
1644
1645        let Some(db) = NonNull::new(db) else {
1646            return;
1647        };
1648
1649        let ctx = CollationNeededContext {
1650            name: &name,
1651            text_rep: SqliteTextRep::from_ffi(e_text_rep),
1652        };
1653
1654        // SAFETY: `db` is the connection that fired the callback (per
1655        // `sqlite3_collation_needed` contract). The closure is `Fn`, so
1656        // SQLite re-entering via a nested unresolved lookup is sound.
1657        unsafe {
1658            SqliteConnection::with_borrowed_connection(db, |conn| f(conn, ctx));
1659        }
1660    }));
1661
1662    if result.is_err() {
1663        {
    ::std::io::_eprint(format_args!("Panic in sqlite3_collation_needed trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
            "diesel/src/sqlite/connection/raw.rs", 1663u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_collation_needed trampoline. ");
1664    }
1665}
1666
1667#[cfg(test)]
1668mod tests {
1669    use super::super::update_hook::SqliteChangeOp;
1670    use super::*;
1671    use std::sync::{Arc, Mutex};
1672
1673    fn test_connection() -> RawConnection {
1674        RawConnection::establish(":memory:").expect("failed to establish :memory: connection")
1675    }
1676
1677    #[test]
1678    fn insert_event_dispatched_directly() {
1679        let mut conn = test_connection();
1680        conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1681            .unwrap();
1682
1683        let fired = Arc::new(Mutex::new(Vec::new()));
1684        let f2 = fired.clone();
1685        conn.set_update_hook(move |e| {
1686            f2.lock().unwrap().push((e.op, e.rowid));
1687        });
1688
1689        conn.exec("INSERT INTO t VALUES (1)").unwrap();
1690
1691        let events = fired.lock().unwrap();
1692        assert_eq!(events.len(), 1);
1693        assert_eq!(events[0].0, SqliteChangeOp::Insert);
1694        assert_eq!(events[0].1, 1);
1695    }
1696
1697    #[test]
1698    fn consecutive_inserts_dispatch_immediately() {
1699        let mut conn = test_connection();
1700        conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1701            .unwrap();
1702
1703        let fired = Arc::new(Mutex::new(Vec::new()));
1704        let f2 = fired.clone();
1705        conn.set_update_hook(move |e| {
1706            f2.lock().unwrap().push(e.rowid);
1707        });
1708
1709        conn.exec("INSERT INTO t VALUES (2); INSERT INTO t VALUES (3)")
1710            .unwrap();
1711
1712        let events = fired.lock().unwrap();
1713        assert_eq!(events.len(), 2);
1714        assert_eq!(events[0], 2);
1715        assert_eq!(events[1], 3);
1716    }
1717
1718    #[test]
1719    fn update_event() {
1720        let mut conn = test_connection();
1721        conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
1722            .unwrap();
1723        conn.exec("INSERT INTO t VALUES (1, 'a')").unwrap();
1724
1725        let fired = Arc::new(Mutex::new(Vec::new()));
1726        let f2 = fired.clone();
1727        conn.set_update_hook(move |e| {
1728            f2.lock().unwrap().push((e.op, e.rowid));
1729        });
1730
1731        conn.exec("UPDATE t SET v = 'b' WHERE id = 1").unwrap();
1732
1733        let events = fired.lock().unwrap();
1734        assert_eq!(events.len(), 1);
1735        assert_eq!(events[0].0, SqliteChangeOp::Update);
1736        assert_eq!(events[0].1, 1);
1737    }
1738
1739    #[test]
1740    fn delete_event() {
1741        let mut conn = test_connection();
1742        conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1743            .unwrap();
1744        conn.exec("INSERT INTO t VALUES (1)").unwrap();
1745
1746        let fired = Arc::new(Mutex::new(Vec::new()));
1747        let f2 = fired.clone();
1748        conn.set_update_hook(move |e| {
1749            f2.lock().unwrap().push((e.op, e.rowid));
1750        });
1751
1752        conn.exec("DELETE FROM t WHERE id = 1").unwrap();
1753
1754        let events = fired.lock().unwrap();
1755        assert_eq!(events.len(), 1);
1756        assert_eq!(events[0].0, SqliteChangeOp::Delete);
1757        assert_eq!(events[0].1, 1);
1758    }
1759
1760    #[test]
1761    fn remove_stops_events() {
1762        let mut conn = test_connection();
1763        conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1764            .unwrap();
1765
1766        let fired = Arc::new(Mutex::new(Vec::new()));
1767        let f2 = fired.clone();
1768        conn.set_update_hook(move |e| {
1769            f2.lock().unwrap().push(e.rowid);
1770        });
1771
1772        conn.exec("INSERT INTO t VALUES (1)").unwrap();
1773        assert_eq!(fired.lock().unwrap().len(), 1);
1774
1775        conn.remove_update_hook();
1776        conn.exec("INSERT INTO t VALUES (2)").unwrap();
1777        assert_eq!(fired.lock().unwrap().len(), 1); // still 1
1778    }
1779
1780    #[test]
1781    fn replacing_hook_drops_old() {
1782        let mut conn = test_connection();
1783        conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1784            .unwrap();
1785
1786        let first = Arc::new(Mutex::new(Vec::new()));
1787        let f1 = first.clone();
1788        conn.set_update_hook(move |e| {
1789            f1.lock().unwrap().push(e.rowid);
1790        });
1791
1792        conn.exec("INSERT INTO t VALUES (1)").unwrap();
1793        assert_eq!(first.lock().unwrap().len(), 1);
1794
1795        // Replacing the hook installs the second closure and drops the first.
1796        let second = Arc::new(Mutex::new(Vec::new()));
1797        let f2 = second.clone();
1798        conn.set_update_hook(move |e| {
1799            f2.lock().unwrap().push(e.rowid);
1800        });
1801
1802        conn.exec("INSERT INTO t VALUES (2)").unwrap();
1803        assert_eq!(first.lock().unwrap().len(), 1); // first no longer fires
1804        assert_eq!(*second.lock().unwrap(), vec![2]);
1805    }
1806
1807    #[test]
1808    fn drop_does_not_panic() {
1809        let mut conn = test_connection();
1810        conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1811            .unwrap();
1812
1813        conn.set_update_hook(|_| {});
1814        conn.exec("INSERT INTO t VALUES (1)").unwrap();
1815        drop(conn);
1816        // If we get here, drop succeeded without panic.
1817    }
1818}