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