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