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