1#![allow(unsafe_code)] // ffi calls
2#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3extern crate libsqlite3_sys as ffi;
45#[cfg(all(target_family = "wasm", target_os = "unknown"))]
6use sqlite_wasm_rs as ffi;
78use 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::ffias libc;
30use core::ffi::CStr;
31use core::num::NonZeroU32;
32use core::ptr::NonNull;
33use core::{mem, ptr, slice, str};
3435// `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;
4344// 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.
4950/// 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")]
55eprint!(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!());
60crate::util::std_compat::abort()
61 };
62}
6364#[allow(missing_debug_implementations, missing_copy_implementations)]
65pub(super) struct RawConnection {
66pub(super) internal_connection: NonNull<ffi::sqlite3>,
67/// Boxed closure kept alive while the update hook is registered.
68update_hook: Option<Box<dyn FnMut(SqliteChangeEvent<'_>) + Send>>,
69/// Boxed closure kept alive while the commit hook is registered.
70commit_hook: Option<Box<dyn FnMut() -> CommitDecision + Send>>,
71/// Boxed closure kept alive while the rollback hook is registered.
72rollback_hook: Option<Box<dyn FnMut() + Send>>,
73/// Boxed closure kept alive while the progress handler is registered.
74progress_hook: Option<Box<dyn FnMut() -> ProgressDecision + Send>>,
75/// Boxed closure kept alive while the WAL hook is registered.
76wal_hook: Option<Box<dyn Fn(&mut SqliteConnection, &str, u32) + Send>>,
77/// Boxed closure kept alive while the busy handler is registered.
78busy_handler: Option<Box<dyn FnMut(i32) -> BusyDecision + Send>>,
79/// Boxed closure kept alive while the authorizer is registered.
80authorizer_hook: Option<Box<dyn FnMut(AuthorizerContext<'_>) -> AuthorizerDecision + Send>>,
81/// Boxed closure kept alive while the trace callback is registered.
82trace_hook: Option<Box<dyn FnMut(SqliteTraceEvent<'_>) + Send>>,
83/// Boxed closure kept alive while the collation-needed callback is registered.
84collation_needed_hook:
85Option<Box<dyn Fn(&mut SqliteConnection, CollationNeededContext<'_>) + Send>>,
86}
8788impl 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).
91pub(super) fn from_ptr(conn: NonNull<ffi::sqlite3>) -> Self {
92RawConnection {
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 }
105106pub(super) fn establish(database_url: &str) -> ConnectionResult<Self> {
107let mut conn_pointer = ptr::null_mut();
108109let 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};
114let flags = ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE | ffi::SQLITE_OPEN_URI;
115let connection_status = unsafe {
116 ffi::sqlite3_open_v2(database_url.as_ptr(), &mut conn_pointer, flags, ptr::null())
117 };
118119match connection_status {
120 ffi::SQLITE_OK => {
121let conn_pointer = unsafe { NonNull::new_unchecked(conn_pointer) };
122Ok(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 => {
136let 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
142unsafe { ffi::sqlite3_close(conn_pointer) };
143Err(ConnectionError::BadConnection(message.into()))
144 }
145 }
146 }
147148pub(super) fn exec(&self, query: &str) -> QueryResult<()> {
149let query = CString::new(query)?;
150let callback_fn = None;
151let callback_arg = ptr::null_mut();
152let result = unsafe {
153 ffi::sqlite3_exec(
154self.internal_connection.as_ptr(),
155query.as_ptr(),
156callback_fn,
157callback_arg,
158 ptr::null_mut(),
159 )
160 };
161162ensure_sqlite_ok(result, self.internal_connection.as_ptr())
163 }
164165pub(super) fn rows_affected_by_last_query(
166&self,
167 ) -> Result<usize, Box<dyn core::error::Error + Send + Sync>> {
168let r = unsafe { ffi::sqlite3_changes(self.internal_connection.as_ptr()) };
169170Ok(r.try_into()?)
171 }
172173pub(super) fn last_insert_rowid(&self) -> i64 {
174unsafe { ffi::sqlite3_last_insert_rowid(self.internal_connection.as_ptr()) }
175 }
176177pub(super) fn register_sql_function<F, Ret, RetSqlType>(
178&self,
179 fn_name: &str,
180 num_args: usize,
181 behavior: SqliteFunctionBehavior,
182 f: F,
183 ) -> QueryResult<()>
184where
185F: FnMut(&Self, &mut [*mut ffi::sqlite3_value]) -> QueryResult<Ret>
186 + core::panic::UnwindSafe187 + Send188 + 'static,
189 Ret: ToSql<RetSqlType, Sqlite>,
190Sqlite: HasSqlType<RetSqlType>,
191 {
192let c_fn_name = Self::get_fn_name(fn_name)?;
193let flags = behavior.to_flags();
194let num_args = num_args
195 .try_into()
196 .map_err(|e| Error::SerializationError(Box::new(e)))?;
197// only create the pointer as last step here
198 // as we can otherwise leak memory
199let callback_fn = Box::into_raw(Box::new(CustomFunctionUserPtr {
200 callback: f,
201 function_name: fn_name.to_owned(),
202 }));
203204let result = unsafe {
205 ffi::sqlite3_create_function_v2(
206self.internal_connection.as_ptr(),
207c_fn_name.as_ptr(),
208num_args,
209flags,
210callback_fnas *mut _,
211Some(run_custom_function::<F, Ret, RetSqlType>),
212None,
213None,
214Some(destroy_boxed::<CustomFunctionUserPtr<F>>),
215 )
216 };
217218Self::process_sql_function_result(result)
219 }
220221pub(super) fn register_aggregate_function<ArgsSqlType, RetSqlType, Args, Ret, A>(
222&self,
223 fn_name: &str,
224 num_args: usize,
225 behavior: SqliteFunctionBehavior,
226 ) -> QueryResult<()>
227where
228A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send + core::panic::UnwindSafe,
229 Args: FromSqlRow<ArgsSqlType, Sqlite>,
230 Ret: ToSql<RetSqlType, Sqlite>,
231Sqlite: HasSqlType<RetSqlType>,
232 {
233let fn_name = Self::get_fn_name(fn_name)?;
234let flags = behavior.to_flags();
235let num_args = num_args
236 .try_into()
237 .map_err(|e| Error::SerializationError(Box::new(e)))?;
238239let result = unsafe {
240 ffi::sqlite3_create_function_v2(
241self.internal_connection.as_ptr(),
242fn_name.as_ptr(),
243num_args,
244flags,
245 core::ptr::null_mut(),
246None,
247Some(run_aggregator_step_function::<_, _, _, _, A>),
248Some(run_aggregator_final_function::<_, _, _, _, A>),
249None,
250 )
251 };
252253Self::process_sql_function_result(result)
254 }
255256pub(super) fn register_collation_function<F>(
257&self,
258 collation_name: &str,
259 collation: F,
260 ) -> QueryResult<()>
261where
262F: Fn(&str, &str) -> core::cmp::Ordering + core::panic::UnwindSafe + Send + 'static,
263 {
264let c_collation_name = Self::get_fn_name(collation_name)?;
265// only create the pointer as last step here as we otherwise could leak memory
266let callback_fn = Box::into_raw(Box::new(CollationUserPtr {
267 callback: collation,
268 collation_name: collation_name.to_owned(),
269 }));
270271let result = unsafe {
272 ffi::sqlite3_create_collation_v2(
273self.internal_connection.as_ptr(),
274c_collation_name.as_ptr(),
275 ffi::SQLITE_UTF8,
276callback_fnas *mut _,
277Some(run_collation_function::<F>),
278Some(destroy_boxed::<CollationUserPtr<F>>),
279 )
280 };
281282let result = Self::process_sql_function_result(result);
283if result.is_err() {
284destroy_boxed::<CollationUserPtr<F>>(callback_fnas *mut _);
285 }
286result287 }
288289pub(super) fn serialize(&mut self) -> SerializedDatabase {
290let mut size: ffi::sqlite3_int64 = 0;
291// SAFETY: The connection is live, a null schema selects `main`, and `size` is a writable out-parameter.
292let data_ptr = unsafe {
293 ffi::sqlite3_serialize(
294self.internal_connection.as_ptr(),
295 core::ptr::null(),
296&mut sizeas *mut _,
2970,
298 )
299 };
300// SQLite returns null with size zero for a valid empty deserialized
301 // database and null with a nonzero size when the output allocation
302 // fails. `SerializedDatabase` reports the failure when accessed.
303let data = match core::ptr::NonNull::new(data_ptr) {
304Some(data) => data,
305Noneif size == 0 => return SerializedDatabase::empty(),
306None => return SerializedDatabase::allocation_failed(),
307 };
308// SAFETY: SQLite transferred an exclusive allocation holding `size` initialized bytes.
309unsafe { SerializedDatabase::new(data, size) }
310 }
311312// SAFETY:
313 // Any caller must ensure that the provided data buffer is valid and not modified until the database connection is closed
314 // Sqlite's documentation states:
315 // Applications must not modify the buffer P or invalidate it before the database connection D is closed.
316pub(super) unsafe fn deserialize(&mut self, data: &[u8]) -> QueryResult<()> {
317let db_size = data
318 .len()
319 .try_into()
320 .map_err(|e| Error::DeserializationError(Box::new(e)))?;
321// the cast for `ffi::SQLITE_DESERIALIZE_READONLY` is required for old libsqlite3-sys versions
322#[allow(clippy::unnecessary_cast)]
323unsafe {
324let result = ffi::sqlite3_deserialize(
325self.internal_connection.as_ptr(),
326 core::ptr::null(),
327data.as_ptr() as *mut u8,
328db_size,
329db_size,
330 ffi::SQLITE_DESERIALIZE_READONLYas u32,
331 );
332333ensure_sqlite_ok(result, self.internal_connection.as_ptr())
334 }
335 }
336337pub(super) fn set_limit(&self, limit: SqliteLimit, value: i32) -> i32 {
338unsafe { ffi::sqlite3_limit(self.internal_connection.as_ptr(), limit.to_ffi(), value) }
339 }
340341pub(super) fn get_limit(&self, limit: SqliteLimit) -> i32 {
342unsafe {
343// Passing -1 queries the current value without changing it
344ffi::sqlite3_limit(self.internal_connection.as_ptr(), limit.to_ffi(), -1)
345 }
346 }
347348/// Set a boolean db_config option.
349pub(super) fn set_db_config_bool(&self, op: i32, value: bool) -> QueryResult<()> {
350let mut result_value: libc::c_int = 0;
351let new_value: libc::c_int = if value { 1 } else { 0 };
352353let result = unsafe {
354 ffi::sqlite3_db_config(
355self.internal_connection.as_ptr(),
356op,
357new_value,
358&mut result_valueas *mut libc::c_int,
359 )
360 };
361362ensure_sqlite_ok(result, self.internal_connection.as_ptr())
363 }
364365/// Get a boolean db_config option.
366pub(super) fn get_db_config_bool(&self, op: i32) -> QueryResult<bool> {
367let mut current_value: libc::c_int = 0;
368369let result = unsafe {
370 ffi::sqlite3_db_config(
371self.internal_connection.as_ptr(),
372op,
373 -1_i32, // -1 queries without changing
374&mut current_valueas *mut libc::c_int,
375 )
376 };
377378 ensure_sqlite_ok(result, self.internal_connection.as_ptr())?;
379Ok(current_value != 0)
380 }
381382fn get_fn_name(fn_name: &str) -> Result<CString, NulError> {
383CString::new(fn_name)
384 }
385386fn process_sql_function_result(result: i32) -> Result<(), Error> {
387if result == ffi::SQLITE_OK {
388Ok(())
389 } else {
390let error_message = super::error_message(result);
391Err(DatabaseError(
392 DatabaseErrorKind::Unknown,
393Box::new(error_message.to_string()),
394 ))
395 }
396 }
397398pub(super) fn blob_open<'conn>(
399&'conn self,
400 database_name: &str,
401 table_name: &str,
402 column_name: &str,
403 row_id: i64,
404 ) -> Result<super::sqlite_blob::SqliteReadOnlyBlob<'conn>, Error> {
405let database_name = alloc::ffi::CString::new(database_name)?;
406let column_name = alloc::ffi::CString::new(column_name)?;
407let table_name = alloc::ffi::CString::new(table_name)?;
408409let mut blob: *mut ffi::sqlite3_blob = core::ptr::null_mut();
410411// SAFETY: All variables are properly initialized
412let ret = unsafe {
413 ffi::sqlite3_blob_open(
414self.internal_connection.as_ptr(),
415database_name.as_c_str().as_ptr(),
416table_name.as_c_str().as_ptr(),
417column_name.as_c_str().as_ptr(),
418row_id,
4190,
420&mut blob,
421 )
422 };
423424Self::process_sql_function_result(ret)?;
425426// SAFETY: `sqlite3_blob_open` initializes the `blob` variable IF the return value:
427 //
428 // > On success, SQLITE_OK is returned and the new BLOB handle is stored in *ppBlob.
429 // > Otherwise an error code is returned and, unless the error code is SQLITE_MISUSE,
430 // > *ppBlob is set to NULL.
431 //
432 // And we checked the `ret` value above
433let blob = unsafe { core::ptr::NonNull::new_unchecked(blob) };
434435// SAFETY: According to the SQLite docs, this can only fail if an invalid pointer is passed
436let blob_size = unsafe { ffi::sqlite3_blob_bytes(blob.as_ptr()) };
437let blob_size = usize::try_from(blob_size).map_err(Error::IntegerConversion)?;
438439Ok(super::sqlite_blob::SqliteReadOnlyBlob {
440blob,
441 read_index: 0,
442blob_size,
443 _pd: core::marker::PhantomData,
444 })
445 }
446447/// Sets the update hook, replacing any previous one.
448 ///
449 /// # Safety
450 ///
451 /// `ptr` is derived from `&raw mut *boxed` and points to the heap
452 /// allocation of the closure. `update_hook_trampoline::<F>` matches the
453 /// signature `sqlite3_update_hook` expects. The pointer stays valid because
454 /// we store `boxed` in `self.update_hook` below, keeping it alive for the
455 /// lifetime of this `RawConnection`, and the hook is removed before
456 /// `sqlite3_close` (see `Drop`), so the pointer is never read after the box
457 /// is freed.
458pub(super) fn set_update_hook<F>(&mut self, hook: F)
459where
460F: FnMut(SqliteChangeEvent<'_>) + Send + 'static,
461 {
462let mut boxed: Box<dyn FnMut(SqliteChangeEvent<'_>) + Send> = Box::new(hook);
463let ptr = &raw mut *boxedas *mut libc::c_void;
464465unsafe {
466 ffi::sqlite3_update_hook(
467self.internal_connection.as_ptr(),
468Some(update_hook_trampoline::<F>),
469ptr,
470 );
471 }
472473// The old box (if any) is dropped here after SQLite has already
474 // switched to the new pointer, preventing use-after-free.
475self.update_hook = Some(boxed);
476 }
477478/// Removes the update hook.
479 ///
480 /// # Safety
481 ///
482 /// `self.internal_connection` is a valid open SQLite connection. Passing
483 /// `None` and `null_mut()` clears any installed update hook. The hook is
484 /// unregistered before dropping `self.update_hook` so SQLite no longer
485 /// reads the pointer during cleanup.
486pub(super) fn remove_update_hook(&mut self) {
487unsafe {
488 ffi::sqlite3_update_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
489 }
490self.update_hook = None;
491 }
492493/// Sets the commit hook, replacing any previous one.
494 ///
495 /// # Safety
496 ///
497 /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
498 /// allocation of the closure. The `commit_hook_trampoline` function
499 /// matches the signature expected by `sqlite3_commit_hook`. The pointer
500 /// remains valid because we store `boxed` in `self.commit_hook` below,
501 /// keeping it alive for the lifetime of this `RawConnection`.
502pub(super) fn set_commit_hook<F>(&mut self, hook: F)
503where
504F: FnMut() -> CommitDecision + Send + 'static,
505 {
506let mut boxed: Box<dyn FnMut() -> CommitDecision + Send> = Box::new(hook);
507let ptr = &raw mut *boxedas *mut libc::c_void;
508509unsafe {
510 ffi::sqlite3_commit_hook(
511self.internal_connection.as_ptr(),
512Some(commit_hook_trampoline::<F>),
513ptr,
514 );
515 }
516517// The old Box (if any) is dropped here after SQLite has already
518 // switched to the new callback, preventing use-after-free.
519self.commit_hook = Some(boxed);
520 }
521522/// Removes the commit hook.
523 ///
524 /// # Safety
525 ///
526 /// `self.internal_connection` is a valid pointer to an open SQLite
527 /// connection. Passing `None` as the hook and `null_mut()` as the user
528 /// data unregisters any existing commit hook. The hook is unregistered
529 /// before dropping `self.commit_hook` to prevent callbacks from firing
530 /// during cleanup.
531pub(super) fn remove_commit_hook(&mut self) {
532unsafe {
533 ffi::sqlite3_commit_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
534 }
535self.commit_hook = None;
536 }
537538/// Sets the rollback hook, replacing any previous one.
539 ///
540 /// # Safety
541 ///
542 /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
543 /// allocation of the closure. The `rollback_hook_trampoline` function
544 /// matches the signature expected by `sqlite3_rollback_hook`. The pointer
545 /// remains valid because we store `boxed` in `self.rollback_hook` below,
546 /// keeping it alive for the lifetime of this `RawConnection`.
547pub(super) fn set_rollback_hook<F>(&mut self, hook: F)
548where
549F: FnMut() + Send + 'static,
550 {
551let mut boxed: Box<dyn FnMut() + Send> = Box::new(hook);
552let ptr = &raw mut *boxedas *mut libc::c_void;
553554unsafe {
555 ffi::sqlite3_rollback_hook(
556self.internal_connection.as_ptr(),
557Some(rollback_hook_trampoline::<F>),
558ptr,
559 );
560 }
561562// The old Box (if any) is dropped here after SQLite has already
563 // switched to the new callback, preventing use-after-free.
564self.rollback_hook = Some(boxed);
565 }
566567/// Removes the rollback hook.
568 ///
569 /// # Safety
570 ///
571 /// `self.internal_connection` is a valid pointer to an open SQLite
572 /// connection. Passing `None` as the hook and `null_mut()` as the user
573 /// data unregisters any existing rollback hook. The hook is unregistered
574 /// before dropping `self.rollback_hook` to prevent callbacks from firing
575 /// during cleanup.
576pub(super) fn remove_rollback_hook(&mut self) {
577unsafe {
578 ffi::sqlite3_rollback_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
579 }
580self.rollback_hook = None;
581 }
582583/// Sets the progress handler, replacing any previous one.
584 ///
585 /// `n` is the approximate number of VM instructions between callbacks.
586 ///
587 /// # Safety
588 ///
589 /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
590 /// allocation of the closure. The `progress_handler_trampoline` function
591 /// matches the signature expected by `sqlite3_progress_handler`. The
592 /// pointer remains valid because we store `boxed` in `self.progress_hook`
593 /// below, keeping it alive for the lifetime of this `RawConnection`.
594pub(super) fn set_progress_handler<F>(&mut self, n: NonZeroU32, hook: F)
595where
596F: FnMut() -> ProgressDecision + Send + 'static,
597 {
598let mut boxed: Box<dyn FnMut() -> ProgressDecision + Send> = Box::new(hook);
599let ptr = &raw mut *boxedas *mut libc::c_void;
600601// `sqlite3_progress_handler` takes a c_int. A value above `i32::MAX`
602 // would wrap to a non-positive number and disable the handler, so clamp
603 // it to `i32::MAX` instead.
604let n = i32::try_from(n.get()).unwrap_or(i32::MAX);
605606unsafe {
607 ffi::sqlite3_progress_handler(
608self.internal_connection.as_ptr(),
609n,
610Some(progress_handler_trampoline::<F>),
611ptr,
612 );
613 }
614615// The old Box (if any) is dropped here after SQLite has already
616 // switched to the new callback, preventing use-after-free.
617self.progress_hook = Some(boxed);
618 }
619620/// Removes the progress handler.
621 ///
622 /// # Safety
623 ///
624 /// `self.internal_connection` is a valid pointer to an open SQLite
625 /// connection. Passing `None` as the handler and `null_mut()` as the user
626 /// data unregisters any existing progress handler. The handler is
627 /// unregistered before dropping `self.progress_hook` to prevent callbacks
628 /// from firing during cleanup.
629pub(super) fn remove_progress_handler(&mut self) {
630unsafe {
631 ffi::sqlite3_progress_handler(
632self.internal_connection.as_ptr(),
6330,
634None,
635 ptr::null_mut(),
636 );
637 }
638self.progress_hook = None;
639 }
640641/// Sets the WAL hook, replacing any previous one.
642 ///
643 /// The callback receives a borrowed `&mut SqliteConnection`, the database
644 /// name (e.g. `"main"`) and the number of pages currently in the WAL file.
645 ///
646 /// # Safety
647 ///
648 /// The `ptr` is derived from `&raw const *boxed` and points to the heap
649 /// allocation of the closure. The `wal_hook_trampoline` function matches the
650 /// signature expected by `sqlite3_wal_hook`. The pointer remains valid
651 /// because we store `boxed` in `self.wal_hook` below, keeping it alive for
652 /// the lifetime of this `RawConnection`.
653pub(super) fn set_wal_hook<F>(&mut self, hook: F)
654where
655F: Fn(&mut SqliteConnection, &str, u32) + Send + 'static,
656 {
657let boxed: Box<dyn Fn(&mut SqliteConnection, &str, u32) + Send> = Box::new(hook);
658let ptr = &raw const *boxedas *mut libc::c_void;
659660unsafe {
661 ffi::sqlite3_wal_hook(
662self.internal_connection.as_ptr(),
663Some(wal_hook_trampoline::<F>),
664ptr,
665 );
666 }
667668// The old Box (if any) is dropped here after SQLite has already
669 // switched to the new callback, preventing use-after-free.
670self.wal_hook = Some(boxed);
671 }
672673/// Removes the WAL hook.
674 ///
675 /// # Safety
676 ///
677 /// `self.internal_connection` is a valid pointer to an open SQLite
678 /// connection. Passing `None` as the hook and `null_mut()` as the user
679 /// data unregisters any existing WAL hook. The hook is unregistered before
680 /// dropping `self.wal_hook` to prevent callbacks from firing during
681 /// cleanup.
682pub(super) fn remove_wal_hook(&mut self) {
683unsafe {
684 ffi::sqlite3_wal_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
685 }
686self.wal_hook = None;
687 }
688689/// Sets the busy handler, replacing any previous one.
690 ///
691 /// Only one busy handler can be active at a time. Setting this clears any
692 /// busy timeout previously set with `set_busy_timeout`.
693 ///
694 /// # Safety
695 ///
696 /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
697 /// allocation of the closure. The `busy_handler_trampoline` function
698 /// matches the signature expected by `sqlite3_busy_handler`. The pointer
699 /// remains valid because we store `boxed` in `self.busy_handler` below,
700 /// keeping it alive for the lifetime of this `RawConnection`.
701pub(super) fn set_busy_handler<F>(&mut self, hook: F)
702where
703F: FnMut(i32) -> BusyDecision + Send + 'static,
704 {
705let mut boxed: Box<dyn FnMut(i32) -> BusyDecision + Send> = Box::new(hook);
706let ptr = &raw mut *boxedas *mut libc::c_void;
707708unsafe {
709 ffi::sqlite3_busy_handler(
710self.internal_connection.as_ptr(),
711Some(busy_handler_trampoline::<F>),
712ptr,
713 );
714 }
715716// The old Box (if any) is dropped here after SQLite has already
717 // switched to the new callback, preventing use-after-free.
718self.busy_handler = Some(boxed);
719 }
720721/// Removes the busy handler.
722 ///
723 /// # Safety
724 ///
725 /// `self.internal_connection` is a valid pointer to an open SQLite
726 /// connection. Passing `None` as the hook and `null_mut()` as the user
727 /// data unregisters any existing busy handler. The hook is unregistered
728 /// before dropping `self.busy_handler` to prevent callbacks from firing
729 /// during cleanup.
730pub(super) fn remove_busy_handler(&mut self) {
731unsafe {
732 ffi::sqlite3_busy_handler(self.internal_connection.as_ptr(), None, ptr::null_mut());
733 }
734self.busy_handler = None;
735 }
736737/// Sets a simple timeout-based busy handler.
738 ///
739 /// SQLite will sleep and retry until `ms` milliseconds have elapsed.
740 /// Setting this clears any custom busy handler.
741 ///
742 /// # Safety
743 ///
744 /// `self.internal_connection` is a valid pointer to an open SQLite
745 /// connection. `sqlite3_busy_timeout` installs its own internal busy
746 /// handler, so the stored `busy_handler` is dropped afterwards to release
747 /// the now-unused closure.
748pub(super) fn set_busy_timeout(&mut self, ms: i32) {
749unsafe {
750 ffi::sqlite3_busy_timeout(self.internal_connection.as_ptr(), ms);
751 }
752self.busy_handler = None;
753 }
754755/// Sets the authorizer callback, replacing any previous one. Only one
756 /// can be active at a time per connection.
757 ///
758 /// # Safety
759 ///
760 /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
761 /// allocation of the closure. The `authorizer_trampoline` function
762 /// matches the signature expected by `sqlite3_set_authorizer`. The pointer
763 /// remains valid because we store `boxed` in `self.authorizer_hook` below,
764 /// keeping it alive for the lifetime of this `RawConnection`.
765pub(super) fn set_authorizer<F>(&mut self, hook: F)
766where
767F: FnMut(AuthorizerContext<'_>) -> AuthorizerDecision + Send + 'static,
768 {
769let mut boxed: Box<dyn FnMut(AuthorizerContext<'_>) -> AuthorizerDecision + Send> =
770Box::new(hook);
771let ptr = &raw mut *boxedas *mut libc::c_void;
772773unsafe {
774 ffi::sqlite3_set_authorizer(
775self.internal_connection.as_ptr(),
776Some(authorizer_trampoline::<F>),
777ptr,
778 );
779 }
780781// The old Box (if any) is dropped here after SQLite has already
782 // switched to the new callback, preventing use-after-free.
783self.authorizer_hook = Some(boxed);
784 }
785786/// Removes the authorizer callback.
787 ///
788 /// # Safety
789 ///
790 /// `self.internal_connection` is a valid pointer to an open SQLite
791 /// connection. Passing `None` as the callback and `null_mut()` as the
792 /// user data unregisters any existing authorizer. The authorizer is
793 /// unregistered before dropping `self.authorizer_hook` to prevent
794 /// callbacks from firing during cleanup.
795pub(super) fn remove_authorizer(&mut self) {
796unsafe {
797 ffi::sqlite3_set_authorizer(self.internal_connection.as_ptr(), None, ptr::null_mut());
798 }
799self.authorizer_hook = None;
800 }
801802/// Sets a trace callback, replacing any previous one.
803 ///
804 /// The callback is invoked for SQL execution tracing based on the
805 /// provided event mask.
806 ///
807 /// # Safety
808 ///
809 /// The `ptr` is derived from `&raw mut *boxed` and points to the heap
810 /// allocation of the closure. The `trace_trampoline` function matches the
811 /// signature expected by `sqlite3_trace_v2`. The pointer remains valid
812 /// because we store `boxed` in `self.trace_hook` below, keeping it alive
813 /// for the lifetime of this `RawConnection`.
814pub(super) fn set_trace<F>(&mut self, mask: SqliteTraceFlags, hook: F)
815where
816F: FnMut(SqliteTraceEvent<'_>) + Send + 'static,
817 {
818let mut boxed: Box<dyn FnMut(SqliteTraceEvent<'_>) + Send> = Box::new(hook);
819let ptr = &raw mut *boxedas *mut libc::c_void;
820821unsafe {
822 ffi::sqlite3_trace_v2(
823self.internal_connection.as_ptr(),
824mask.bits(),
825Some(trace_trampoline::<F>),
826ptr,
827 );
828 }
829830// The old Box (if any) is dropped here after SQLite has already
831 // switched to the new callback, preventing use-after-free.
832self.trace_hook = Some(boxed);
833 }
834835/// Removes the trace callback.
836 ///
837 /// # Safety
838 ///
839 /// `self.internal_connection` is a valid pointer to an open SQLite
840 /// connection. Passing a mask of `0`, `None` as the callback, and
841 /// `null_mut()` as the user data unregisters any existing trace callback.
842 /// The callback is unregistered before dropping `self.trace_hook` to
843 /// prevent callbacks from firing during cleanup.
844pub(super) fn remove_trace(&mut self) {
845unsafe {
846 ffi::sqlite3_trace_v2(self.internal_connection.as_ptr(), 0, None, ptr::null_mut());
847 }
848self.trace_hook = None;
849 }
850851/// Sets the collation-needed callback, replacing any previous one.
852 ///
853 /// # Safety
854 ///
855 /// `ptr` points to the heap allocation of `boxed`, stored in
856 /// `self.collation_needed_hook` below so it outlives the C-side
857 /// registration.
858pub(super) fn set_collation_needed_hook<F>(&mut self, hook: F)
859where
860F: Fn(&mut SqliteConnection, CollationNeededContext<'_>) + Send + 'static,
861 {
862let boxed: Box<dyn Fn(&mut SqliteConnection, CollationNeededContext<'_>) + Send> =
863Box::new(hook);
864let ptr = &raw const *boxedas *mut libc::c_void;
865866unsafe {
867 ffi::sqlite3_collation_needed(
868self.internal_connection.as_ptr(),
869ptr,
870Some(collation_needed_trampoline::<F>),
871 );
872 }
873874// The old Box (if any) is dropped here after SQLite has already
875 // switched to the new callback, preventing use-after-free.
876self.collation_needed_hook = Some(boxed);
877 }
878879/// Removes the collation-needed callback.
880 ///
881 /// # Safety
882 ///
883 /// Unregisters via `sqlite3_collation_needed(db, null, None)` before
884 /// dropping `self.collation_needed_hook`, so no callback can fire during
885 /// cleanup.
886pub(super) fn remove_collation_needed_hook(&mut self) {
887unsafe {
888 ffi::sqlite3_collation_needed(self.internal_connection.as_ptr(), ptr::null_mut(), None);
889 }
890self.collation_needed_hook = None;
891 }
892}
893894impl Dropfor RawConnection {
895fn drop(&mut self) {
896use crate::util::std_compat::panicking;
897898// Unregister before close so the boxed closures drop before sqlite3_close.
899self.remove_update_hook();
900self.remove_commit_hook();
901self.remove_rollback_hook();
902self.remove_progress_handler();
903self.remove_wal_hook();
904self.remove_busy_handler();
905self.remove_authorizer();
906self.remove_trace();
907self.remove_collation_needed_hook();
908909let close_result = unsafe { ffi::sqlite3_close(self.internal_connection.as_ptr()) };
910if close_result != ffi::SQLITE_OK {
911let error_message = super::error_message(close_result);
912if panicking() {
913#[cfg(feature = "std")]
914{
::std::io::_eprint(format_args!("Error closing SQLite connection: {0}\n",
error_message));
};eprintln!("Error closing SQLite connection: {error_message}");
915 } else {
916{
::core::panicking::panic_fmt(format_args!("Error closing SQLite connection: {0}",
error_message));
};panic!("Error closing SQLite connection: {error_message}");
917 }
918 }
919 }
920}
921922enum SqliteCallbackError {
923 Abort(&'static str),
924 DieselError(crate::result::Error),
925 Panic(String),
926}
927928impl SqliteCallbackError {
929fn emit(&self, ctx: *mut ffi::sqlite3_context) {
930let s;
931let msg = match self {
932 SqliteCallbackError::Abort(msg) => *msg,
933 SqliteCallbackError::DieselError(e) => {
934s = e.to_string();
935&s936 }
937 SqliteCallbackError::Panic(msg) => msg,
938 };
939unsafe {
940context_error_str(ctx, msg);
941 }
942 }
943}
944945impl From<crate::result::Error> for SqliteCallbackError {
946fn from(e: crate::result::Error) -> Self {
947Self::DieselError(e)
948 }
949}
950951struct CustomFunctionUserPtr<F> {
952 callback: F,
953 function_name: String,
954}
955956#[allow(warnings)]
957extern "C" fn run_custom_function<F, Ret, RetSqlType>(
958 ctx: *mut ffi::sqlite3_context,
959 num_args: libc::c_int,
960 value_ptr: *mut *mut ffi::sqlite3_value,
961) where
962F: FnMut(&RawConnection, &mut [*mut ffi::sqlite3_value]) -> QueryResult<Ret>
963 + core::panic::UnwindSafe964 + Send965 + 'static,
966 Ret: ToSql<RetSqlType, Sqlite>,
967Sqlite: HasSqlType<RetSqlType>,
968{
969use core::ops::Deref;
970static NULL_DATA_ERR: &str = "An unknown error occurred. sqlite3_user_data returned a null pointer. This should never happen.";
971static NULL_CONN_ERR: &str = "An unknown error occurred. sqlite3_context_db_handle returned a null pointer. This should never happen.";
972973let conn = match unsafe { NonNull::new(ffi::sqlite3_context_db_handle(ctx)) } {
974// We use `ManuallyDrop` here because we do not want to run the
975 // Drop impl of `RawConnection` as this would close the connection
976Some(conn) => mem::ManuallyDrop::new(RawConnection::from_ptr(conn)),
977None => {
978unsafe { context_error_str(ctx, NULL_CONN_ERR) };
979return;
980 }
981 };
982983let data_ptr = unsafe { ffi::sqlite3_user_data(ctx) };
984985let mut data_ptr = match NonNull::new(data_ptras *mut CustomFunctionUserPtr<F>) {
986None => unsafe {
987context_error_str(ctx, NULL_DATA_ERR);
988return;
989 },
990Some(mut f) => f,
991 };
992let data_ptr = unsafe { data_ptr.as_mut() };
993994// We need this to move the reference into the catch_unwind part
995 // this is sound as `F` itself and the stored string is `UnwindSafe`
996let callback = core::panic::AssertUnwindSafe(&mut data_ptr.callback);
997// conn holds non-UnwindSafe fields: the boxed commit hook and the boxed
998 // update hook. The ManuallyDrop wrapper ensures we never run RawConnection's Drop.
999let conn = core::panic::AssertUnwindSafe(conn);
10001001let result = crate::util::std_compat::catch_unwind(move || {
1002let _ = &callback;
1003let args = unsafe { slice::from_raw_parts_mut(value_ptr, num_argsas _) };
1004let res = (callback.0)(&*conn, args)?;
1005let value = process_sql_function_result(&res)?;
1006// We've checked already that ctx is not null
1007unsafe {
1008value.result_of(&mut *ctx);
1009 }
1010Ok(())
1011 })
1012 .unwrap_or_else(|p| Err(SqliteCallbackError::Panic(data_ptr.function_name.clone())));
1013if let Err(e) = result {
1014e.emit(ctx);
1015 }
1016}
10171018#[allow(warnings)]
1019extern "C" fn run_aggregator_step_function<ArgsSqlType, RetSqlType, Args, Ret, A>(
1020 ctx: *mut ffi::sqlite3_context,
1021 num_args: libc::c_int,
1022 value_ptr: *mut *mut ffi::sqlite3_value,
1023) where
1024A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send + core::panic::UnwindSafe,
1025 Args: FromSqlRow<ArgsSqlType, Sqlite>,
1026 Ret: ToSql<RetSqlType, Sqlite>,
1027Sqlite: HasSqlType<RetSqlType>,
1028{
1029let result = crate::util::std_compat::catch_unwind(move || {
1030let args = unsafe { slice::from_raw_parts_mut(value_ptr, num_argsas _) };
1031run_aggregator_step::<A, Args, ArgsSqlType>(ctx, args)
1032 })
1033 .unwrap_or_else(|e| {
1034Err(SqliteCallbackError::Panic(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}::step() panicked",
core::any::type_name::<A>()))
})alloc::format!(
1035"{}::step() panicked",
1036 core::any::type_name::<A>()
1037 )))
1038 });
10391040match result {
1041Ok(()) => {}
1042Err(e) => e.emit(ctx),
1043 }
1044}
10451046fn run_aggregator_step<A, Args, ArgsSqlType>(
1047 ctx: *mut ffi::sqlite3_context,
1048 args: &mut [*mut ffi::sqlite3_value],
1049) -> Result<(), SqliteCallbackError>
1050where
1051A: SqliteAggregateFunction<Args>,
1052 Args: FromSqlRow<ArgsSqlType, Sqlite>,
1053{
1054let aggregator = unsafe {
1055const {
1056if core::mem::size_of::<*mut A>() == 0 {
1057{
::core::panicking::panic_fmt(format_args!("The pointer size is zero, that\'s unexpected.If you ever see this error message open a issuedescribing your environment"));
};panic!(
1058"The pointer size is zero, that's unexpected.\
1059 If you ever see this error message open a issue\
1060 describing your environment"
1061);
1062 }
1063 }
1064// sqlite3_aggregate_context will return a memory allocation of the requested
1065 // size. For the first call this will be zeroed, for any future call in the same execution
1066 // this will contain the value we wrote into it.
1067 //
1068 // We write just a pointer to rust allocated memory in there to
1069 // have the rust side deal with layout and alignment of our aggregator
1070let ctx = ffi::sqlite3_aggregate_context(
1071ctx,
1072 core::mem::size_of::<*mut A>()
1073 .try_into()
1074 .expect("Memory size of a pointer is smaller than i32::MAX"),
1075 )
1076// we cast the returned memory here to be a pointer to the aggregate instance
1077.cast::<*mut A>();
1078// sqlite3_aggregate_context returns a null pointer if the allocation fails
1079if ctx.is_null() {
1080return Err(SqliteCallbackError::Abort(
1081"sqlite3_aggregate_context failed to allocate memory for the aggregate state",
1082 ));
1083 }
1084// we are interested in the inner pointer
1085let inner = &mut *ctx;
1086// if the inner pointer is null we the aggregate_step
1087 // function is executed the first time and we need to create the actual
1088 // aggregator
1089if inner.is_null() {
1090// for that we allocate a box and turn it into a raw pointer
1091 // by leaking the memory
1092let obj = Box::into_raw(Box::new(A::default()));
1093*inner = obj;
1094 }
1095// at this point the inner value is never null
1096 // as we initialised in in the null branch above,
1097 // therefore it's sound to dereference the pointer here
1098&mut **inner1099 };
11001101// SAFETY: SQLite passes a live context for the duration of this callback.
1102let connection = unsafe { NonNull::new(ffi::sqlite3_context_db_handle(ctx)) };
1103let connection = connection.ok_or(SqliteCallbackError::Abort(
1104"sqlite3_context_db_handle returned a null pointer. This should never happen",
1105 ))?;
1106let args = build_sql_function_args::<ArgsSqlType, Args>(args, connection)?;
11071108aggregator.step(args);
1109Ok(())
1110}
11111112extern "C" fn run_aggregator_final_function<ArgsSqlType, RetSqlType, Args, Ret, A>(
1113 ctx: *mut ffi::sqlite3_context,
1114) where
1115A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send,
1116 Args: FromSqlRow<ArgsSqlType, Sqlite>,
1117 Ret: ToSql<RetSqlType, Sqlite>,
1118Sqlite: HasSqlType<RetSqlType>,
1119{
1120let result = crate::util::std_compat::catch_unwind(|| {
1121let aggregator = unsafe {
1122// Get back the aggregated context
1123 // This might be null
1124let ctx = ffi::sqlite3_aggregate_context(
1125ctx,
1126// use zero sized allocation here to not allocate if this is the first call to `sqlite3_aggregate_context`
11270,
1128 )
1129// the allocation contains a pointer to the actual aggregator
1130.cast::<*mut A>();
1131// if the context was not allocated yet
1132 // we get back a null pointer here due to
1133 // the requested zero sized allocation
1134if ctx.is_null() {
1135None1136 } else {
1137// from this point we are interested in the inner pointer
1138 // we checked above that this pointer is not null
1139 // so it's sound to dereference it
1140let inner = &mut *ctx;
1141if inner.is_null() {
1142// if the inner pointer is null the aggregator has not been initialized
1143None1144 } else {
1145// if it's not null
1146 // we need to construct back the box and move out the
1147 // value to correctly deallocate the allocation
1148let value = Box::from_raw(*inner);
1149let value = Some(*value);
1150// we also want to write a null pointer back to the
1151 // context to make sure that there is no dangling pointer left
1152*inner = core::ptr::null_mut();
1153value1154 }
1155 }
1156 };
11571158let res = A::finalize(aggregator);
1159let value = process_sql_function_result(&res)?;
1160// We've checked already that ctx is not null
1161let r = unsafe { value.result_of(&mut *ctx) };
1162 r.map_err(|e| {
1163 SqliteCallbackError::DieselError(crate::result::Error::SerializationError(Box::new(e)))
1164 })?;
1165Ok(())
1166 })
1167 .unwrap_or_else(|_e| {
1168Err(SqliteCallbackError::Panic(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}::finalize() panicked",
core::any::type_name::<A>()))
})alloc::format!(
1169"{}::finalize() panicked",
1170 core::any::type_name::<A>()
1171 )))
1172 });
1173if let Err(e) = result {
1174e.emit(ctx);
1175 }
1176}
11771178unsafe fn context_error_str(ctx: *mut ffi::sqlite3_context, error: &str) {
1179let len: i32 = error.len().try_into().unwrap_or(i32::MAX);
1180unsafe {
1181 ffi::sqlite3_result_error(ctx, error.as_ptr() as *const _, len);
1182 }
1183}
11841185struct CollationUserPtr<F> {
1186 callback: F,
1187 collation_name: String,
1188}
11891190#[allow(warnings)]
1191extern "C" fn run_collation_function<F>(
1192 user_ptr: *mut libc::c_void,
1193 lhs_len: libc::c_int,
1194 lhs_ptr: *const libc::c_void,
1195 rhs_len: libc::c_int,
1196 rhs_ptr: *const libc::c_void,
1197) -> libc::c_int1198where
1199F: Fn(&str, &str) -> core::cmp::Ordering + Send + core::panic::UnwindSafe + 'static,
1200{
1201let user_ptr = user_ptras *const CollationUserPtr<F>;
1202let user_ptr = core::panic::AssertUnwindSafe(unsafe { user_ptr.as_ref() });
12031204let result = crate::util::std_compat::catch_unwind(|| {
1205let user_ptr = user_ptr.ok_or_else(|| {
1206 SqliteCallbackError::Abort(
1207"Got a null pointer as data pointer. This should never happen",
1208 )
1209 })?;
1210for (ptr, len, side) in &[(rhs_ptr, rhs_len, "rhs"), (lhs_ptr, lhs_len, "lhs")] {
1211if *len < 0 {
1212{
::std::io::_eprint(format_args!("An unknown error occurred. {0}_len is negative. This should never happen.If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {1}:{2}\n",
side, "diesel/src/sqlite/connection/raw.rs", 1212u32));
};
crate::util::std_compat::abort();assert_fail!(
1213"An unknown error occurred. {}_len is negative. This should never happen.",
1214 side
1215 );
1216 }
1217if ptr.is_null() {
1218{
::std::io::_eprint(format_args!("An unknown error occurred. {0}_ptr is a null pointer. This should never happen.If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {1}:{2}\n",
side, "diesel/src/sqlite/connection/raw.rs", 1218u32));
};
crate::util::std_compat::abort();assert_fail!(
1219"An unknown error occurred. {}_ptr is a null pointer. This should never happen.",
1220 side
1221 );
1222 }
1223 }
12241225let (rhs, lhs) = unsafe {
1226// Depending on the eTextRep-parameter to sqlite3_create_collation_v2() the strings can
1227 // have various encodings. register_collation_function() always selects SQLITE_UTF8, so the
1228 // pointers point to valid UTF-8 strings (assuming correct behavior of libsqlite3).
1229(
1230 str::from_utf8(slice::from_raw_parts(rhs_ptras *const u8, rhs_lenas _)),
1231 str::from_utf8(slice::from_raw_parts(lhs_ptras *const u8, lhs_lenas _)),
1232 )
1233 };
12341235let rhs =
1236 rhs.map_err(|_| SqliteCallbackError::Abort("Got an invalid UTF-8 string for rhs"))?;
1237let lhs =
1238 lhs.map_err(|_| SqliteCallbackError::Abort("Got an invalid UTF-8 string for lhs"))?;
12391240Ok((user_ptr.callback)(rhs, lhs))
1241 })
1242 .unwrap_or_else(|p| {
1243Err(SqliteCallbackError::Panic(
1244user_ptr1245 .map(|u| u.collation_name.clone())
1246 .unwrap_or_default(),
1247 ))
1248 });
12491250match result {
1251Ok(core::cmp::Ordering::Less) => -1,
1252Ok(core::cmp::Ordering::Equal) => 0,
1253Ok(core::cmp::Ordering::Greater) => 1,
1254Err(SqliteCallbackError::Abort(a)) => {
1255#[cfg(feature = "std")]
1256{
::std::io::_eprint(format_args!("Collation function {0} failed with: {1}\n",
user_ptr.map(|c| &c.collation_name as &str).unwrap_or_default(),
a));
};eprintln!(
1257"Collation function {} failed with: {}",
1258 user_ptr
1259 .map(|c| &c.collation_name as &str)
1260 .unwrap_or_default(),
1261 a
1262 );
1263crate::util::std_compat::abort()
1264 }
1265Err(SqliteCallbackError::DieselError(e)) => {
1266#[cfg(feature = "std")]
1267{
::std::io::_eprint(format_args!("Collation function {0} failed with: {1}\n",
user_ptr.map(|c| &c.collation_name as &str).unwrap_or_default(),
e));
};eprintln!(
1268"Collation function {} failed with: {}",
1269 user_ptr
1270 .map(|c| &c.collation_name as &str)
1271 .unwrap_or_default(),
1272 e
1273 );
1274crate::util::std_compat::abort()
1275 }
1276Err(SqliteCallbackError::Panic(msg)) => {
1277#[cfg(feature = "std")]
1278{
::std::io::_eprint(format_args!("Collation function {0} panicked\n",
msg));
};eprintln!("Collation function {} panicked", msg);
1279crate::util::std_compat::abort()
1280 }
1281 }
1282}
12831284extern "C" fn destroy_boxed<F>(data: *mut libc::c_void) {
1285let ptr = dataas *mut F;
1286unsafe { core::mem::drop(Box::from_raw(ptr)) };
1287}
12881289/// C trampoline for `sqlite3_update_hook`.
1290///
1291/// # Safety
1292///
1293/// `user_data` must point to a live `F` stored in `RawConnection::update_hook`.
1294/// This is guaranteed because the box is kept alive there and the hook is
1295/// unregistered before the connection is dropped. SQLite forbids the callback
1296/// from modifying the connection, so it cannot re-enter this trampoline, and
1297/// the `&mut` borrow is never aliased (the same contract the commit hook
1298/// relies on).
1299unsafe extern "C" fn update_hook_trampoline<F>(
1300 user_data: *mut libc::c_void,
1301 op: libc::c_int,
1302 db_name: *const libc::c_char,
1303 table_name: *const libc::c_char,
1304 rowid: ffi::sqlite3_int64,
1305) where
1306F: FnMut(SqliteChangeEvent<'_>),
1307{
1308let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1309// SAFETY: `user_data` points to a live `F` in `RawConnection::update_hook`.
1310let hook = unsafe { &mut *(user_dataas *mut F) };
13111312// SAFETY: SQLite passes valid C strings. Decode lossily so a non-UTF-8
1313 // name cannot abort the process (matching the trace and wal hooks).
1314let db_name = unsafe { CStr::from_ptr(db_name) }.to_string_lossy();
1315let table_name = unsafe { CStr::from_ptr(table_name) }.to_string_lossy();
13161317hook(SqliteChangeEvent {
1318 op: SqliteChangeOp::from_ffi(op),
1319 db_name: &db_name,
1320 table_name: &table_name,
1321rowid,
1322 });
1323 }));
13241325if result.is_err() {
1326{
::std::io::_eprint(format_args!("Panic in sqlite3_update_hook trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
"diesel/src/sqlite/connection/raw.rs", 1326u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_update_hook trampoline. ");
1327 }
1328}
13291330/// C trampoline for `sqlite3_commit_hook`.
1331///
1332/// # Safety
1333///
1334/// `user_data` must point to a live `F` stored in `RawConnection::commit_hook`.
1335unsafe extern "C" fn commit_hook_trampoline<F>(user_data: *mut libc::c_void) -> libc::c_int1336where
1337F: FnMut() -> CommitDecision,
1338{
1339let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1340// SAFETY: `user_data` points to a live `F` in `RawConnection::commit_hook`.
1341let f = unsafe { &mut *(user_dataas *mut F) };
1342f()
1343 }));
13441345match result {
1346Ok(CommitDecision::Rollback) => 1,
1347Ok(CommitDecision::Proceed) => 0,
1348Err(_) => {
1349{
::std::io::_eprint(format_args!("Panic in sqlite3_commit_hook trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
"diesel/src/sqlite/connection/raw.rs", 1349u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_commit_hook trampoline. ");
1350 }
1351 }
1352}
13531354/// C trampoline for `sqlite3_rollback_hook`.
1355///
1356/// # Safety
1357///
1358/// `user_data` must point to a live `F` stored in `RawConnection::rollback_hook`.
1359unsafe extern "C" fn rollback_hook_trampoline<F>(user_data: *mut libc::c_void)
1360where
1361F: FnMut(),
1362{
1363let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1364// SAFETY: `user_data` points to a live `F` in `RawConnection::rollback_hook`.
1365let f = unsafe { &mut *(user_dataas *mut F) };
1366f();
1367 }));
13681369if result.is_err() {
1370{
::std::io::_eprint(format_args!("Panic in sqlite3_rollback_hook trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
"diesel/src/sqlite/connection/raw.rs", 1370u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_rollback_hook trampoline. ");
1371 }
1372}
13731374/// C trampoline for `sqlite3_progress_handler`.
1375///
1376/// # Safety
1377///
1378/// `user_data` must point to a live `F` stored in `RawConnection::progress_hook`.
1379unsafe extern "C" fn progress_handler_trampoline<F>(user_data: *mut libc::c_void) -> libc::c_int1380where
1381F: FnMut() -> ProgressDecision,
1382{
1383let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1384// SAFETY: `user_data` points to a live `F` in `RawConnection::progress_hook`.
1385let f = unsafe { &mut *(user_dataas *mut F) };
1386f()
1387 }));
13881389match result {
1390Ok(ProgressDecision::Interrupt) => 1,
1391Ok(ProgressDecision::Continue) => 0,
1392Err(_) => {
1393{
::std::io::_eprint(format_args!("Panic in sqlite3_progress_handler trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
"diesel/src/sqlite/connection/raw.rs", 1393u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_progress_handler trampoline. ");
1394 }
1395 }
1396}
13971398/// C trampoline for `sqlite3_wal_hook`.
1399///
1400/// # Safety
1401///
1402/// `user_data` must point to a live `F` in `RawConnection::wal_hook`. `db` is
1403/// the `sqlite3` handle that fired the hook, open and unlocked for the duration
1404/// of the call.
1405unsafe extern "C" fn wal_hook_trampoline<F>(
1406 user_data: *mut libc::c_void,
1407 db: *mut ffi::sqlite3,
1408 db_name: *const libc::c_char,
1409 n_pages: libc::c_int,
1410) -> libc::c_int1411where
1412F: Fn(&mut SqliteConnection, &str, u32),
1413{
1414let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1415// SAFETY: `user_data` points to a live `F` in `RawConnection::wal_hook`.
1416let f = unsafe { &*(user_dataas *const F) };
14171418// SAFETY: when non-null, `db_name` is a valid C string from SQLite. Use
1419 // a lossy conversion so a pathological name cannot abort, and map null
1420 // to "".
1421let db_name: Cow<'_, str> = if db_name.is_null() {
1422 Cow::Borrowed("")
1423 } else {
1424unsafe { CStr::from_ptr(db_name) }.to_string_lossy()
1425 };
1426// SQLite always reports a non-negative page count. Clamp defensively.
1427let n_pages = u32::try_from(n_pages).unwrap_or(0);
14281429let Some(db) = NonNull::new(db) else {
1430return;
1431 };
14321433// SAFETY: per the `sqlite3_wal_hook` docs the commit is complete and the
1434 // write-lock released, so `db` may be used. `with_borrowed_connection`
1435 // finalizes statements on return and never closes `db`. The callback is
1436 // `Fn`, so a re-entrant call (a committing write inside it) is sound.
1437unsafe {
1438SqliteConnection::with_borrowed_connection(db, |conn| f(conn, &db_name, n_pages));
1439 }
1440 }));
14411442if result.is_err() {
1443{
::std::io::_eprint(format_args!("Panic in sqlite3_wal_hook trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
"diesel/src/sqlite/connection/raw.rs", 1443u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_wal_hook trampoline. ");
1444 }
14451446 ffi::SQLITE_OK1447}
14481449/// C trampoline for `sqlite3_busy_handler`.
1450///
1451/// # Safety
1452///
1453/// `user_data` must point to a live `F` stored in `RawConnection::busy_handler`.
1454unsafe extern "C" fn busy_handler_trampoline<F>(
1455 user_data: *mut libc::c_void,
1456 retry_count: libc::c_int,
1457) -> libc::c_int1458where
1459F: FnMut(i32) -> BusyDecision,
1460{
1461let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1462// SAFETY: `user_data` points to a live `F` in `RawConnection::busy_handler`.
1463let f = unsafe { &mut *(user_dataas *mut F) };
1464f(retry_count)
1465 }));
14661467match result {
1468Ok(BusyDecision::Retry) => 1,
1469Ok(BusyDecision::GiveUp) => 0,
1470Err(_) => {
1471{
::std::io::_eprint(format_args!("Panic in sqlite3_busy_handler trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
"diesel/src/sqlite/connection/raw.rs", 1471u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_busy_handler trampoline. ");
1472 }
1473 }
1474}
14751476/// C trampoline for `sqlite3_set_authorizer`.
1477///
1478/// # Safety
1479///
1480/// `user_data` must point to a live `F` stored in `RawConnection::authorizer_hook`.
1481unsafe extern "C" fn authorizer_trampoline<F>(
1482 user_data: *mut libc::c_void,
1483 action_code: libc::c_int,
1484 arg1: *const libc::c_char,
1485 arg2: *const libc::c_char,
1486 db_name: *const libc::c_char,
1487 accessor: *const libc::c_char,
1488) -> libc::c_int1489where
1490F: FnMut(AuthorizerContext<'_>) -> AuthorizerDecision,
1491{
1492// Convert a nullable C string argument to `Option<&str>`. A null pointer or
1493 // a non-UTF-8 string both map to `None`. The borrow is tied to this call via
1494 // the generic lifetime, and the resulting `&str` only lives inside the
1495 // `AuthorizerContext` passed to the callback, which cannot escape the call.
1496fn to_str<'a>(ptr: *const libc::c_char) -> Option<&'a str> {
1497if ptr.is_null() {
1498None1499 } else {
1500// SAFETY: per the `sqlite3_set_authorizer` contract a non-null
1501 // pointer is a valid C string for the duration of the call.
1502unsafe { CStr::from_ptr(ptr) }.to_str().ok()
1503 }
1504 }
15051506let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1507// SAFETY: `user_data` points to a live `F` in `RawConnection::authorizer_hook`.
1508let f = unsafe { &mut *(user_dataas *mut F) };
15091510let ctx = AuthorizerContext::from_ffi(
1511action_code,
1512to_str(arg1),
1513to_str(arg2),
1514to_str(db_name),
1515to_str(accessor),
1516 );
15171518f(ctx)
1519 }));
15201521match result {
1522Ok(decision) => decision.to_ffi(),
1523Err(_) => {
1524{
::std::io::_eprint(format_args!("Panic in sqlite3_set_authorizer trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
"diesel/src/sqlite/connection/raw.rs", 1524u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_set_authorizer trampoline. ");
1525 }
1526 }
1527}
15281529/// C trampoline for `sqlite3_trace_v2`.
1530///
1531/// # Safety
1532///
1533/// `user_data` must point to a live `F` stored in `RawConnection::trace_hook`.
1534unsafe extern "C" fn trace_trampoline<F>(
1535 event_code: libc::c_uint,
1536 user_data: *mut libc::c_void,
1537 p: *mut libc::c_void,
1538 x: *mut libc::c_void,
1539) -> libc::c_int1540where
1541F: FnMut(SqliteTraceEvent<'_>) + Send + 'static,
1542{
1543let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1544// SAFETY: `user_data` points to a live `F` in `RawConnection::trace_hook`.
1545let f = unsafe { &mut *(user_dataas *mut F) };
15461547// The callback is invoked inside each arm so the lossy `Cow` holding the
1548 // SQL text outlives the `&str` borrow handed to it. `TRACE_STMT`,
1549 // `TRACE_PROFILE`, and `TRACE_ROW` are the `u32`-normalized event codes
1550 // from the `trace` module, so they match `event_code` directly.
1551match event_code {
1552TRACE_STMT => {
1553// p = sqlite3_stmt*, x = const char* (unexpanded SQL).
1554let stmt_ptr = pas *mut ffi::sqlite3_stmt;
1555let sql_ptr = xas *const libc::c_char;
1556if sql_ptr.is_null() {
1557return;
1558 }
1559// SAFETY: a non-null `x` is a valid C string from SQLite. Use a
1560 // lossy conversion so non-UTF-8 SQL cannot abort the process.
1561let sql = unsafe { CStr::from_ptr(sql_ptr) }.to_string_lossy();
1562let readonly =
1563 !stmt_ptr.is_null() && unsafe { ffi::sqlite3_stmt_readonly(stmt_ptr) != 0 };
1564f(SqliteTraceEvent::Statement {
1565 sql: &sql,
1566readonly,
1567 });
1568 }
1569TRACE_PROFILE => {
1570// p = sqlite3_stmt*, x = sqlite3_int64* (nanoseconds).
1571let stmt_ptr = pas *mut ffi::sqlite3_stmt;
1572let duration_ns = unsafe {
1573// x points to a sqlite3_int64. The duration is non-negative.
1574(xas *const ffi::sqlite3_int64).as_ref()
1575 }
1576 .copied()
1577 .unwrap_or_default()
1578 .cast_unsigned();
15791580let readonly =
1581 !stmt_ptr.is_null() && unsafe { ffi::sqlite3_stmt_readonly(stmt_ptr) != 0 };
1582let sql_ptr = if stmt_ptr.is_null() {
1583 core::ptr::null()
1584 } else {
1585unsafe { ffi::sqlite3_sql(stmt_ptr) }
1586 };
1587// SAFETY: when non-null, `sqlite3_sql` returns a valid C string.
1588let sql = if sql_ptr.is_null() {
1589 Cow::Borrowed("")
1590 } else {
1591unsafe { CStr::from_ptr(sql_ptr) }.to_string_lossy()
1592 };
1593f(SqliteTraceEvent::Profile {
1594 sql: &sql,
1595duration_ns,
1596readonly,
1597 });
1598 }
1599TRACE_ROW => f(SqliteTraceEvent::Row),
1600// Unknown or unhandled events are ignored. CLOSE is intentionally
1601 // not handled: diesel removes the trace before closing the
1602 // connection, so it never fires.
1603_ => {}
1604 }
1605 }));
16061607if result.is_err() {
1608{
::std::io::_eprint(format_args!("Panic in sqlite3_trace_v2 trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
"diesel/src/sqlite/connection/raw.rs", 1608u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_trace_v2 trampoline. ");
1609 }
161016110 // Return value is currently unused by SQLite
1612}
16131614/// C trampoline for `sqlite3_collation_needed`.
1615///
1616/// # Safety
1617///
1618/// `user_data` must point to a live `F` in
1619/// `RawConnection::collation_needed_hook`. `db` is the connection that fired
1620/// the callback and is open for the duration of the call. SQLite may
1621/// re-enter this trampoline if the callback registers a collation that is
1622/// itself missing, so the closure is stored as `Fn` and borrowed shared.
1623unsafe extern "C" fn collation_needed_trampoline<F>(
1624 user_data: *mut libc::c_void,
1625 db: *mut ffi::sqlite3,
1626 e_text_rep: libc::c_int,
1627 name: *const libc::c_char,
1628) where
1629F: Fn(&mut SqliteConnection, CollationNeededContext<'_>),
1630{
1631let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
1632// SAFETY: `user_data` points to a live `F` in
1633 // `RawConnection::collation_needed_hook`.
1634let f = unsafe { &*(user_dataas *const F) };
16351636// SAFETY: when non-null, `name` is a valid C string from SQLite. Use a
1637 // lossy conversion so a pathological name cannot abort the process, and
1638 // map null to "".
1639let name: Cow<'_, str> = if name.is_null() {
1640 Cow::Borrowed("")
1641 } else {
1642unsafe { CStr::from_ptr(name) }.to_string_lossy()
1643 };
16441645let Some(db) = NonNull::new(db) else {
1646return;
1647 };
16481649let ctx = CollationNeededContext {
1650 name: &name,
1651 text_rep: SqliteTextRep::from_ffi(e_text_rep),
1652 };
16531654// SAFETY: `db` is the connection that fired the callback (per
1655 // `sqlite3_collation_needed` contract). The closure is `Fn`, so
1656 // SQLite re-entering via a nested unresolved lookup is sound.
1657unsafe {
1658SqliteConnection::with_borrowed_connection(db, |conn| f(conn, ctx));
1659 }
1660 }));
16611662if result.is_err() {
1663{
::std::io::_eprint(format_args!("Panic in sqlite3_collation_needed trampoline. If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\nSource location: {0}:{1}\n",
"diesel/src/sqlite/connection/raw.rs", 1663u32));
};
crate::util::std_compat::abort();assert_fail!("Panic in sqlite3_collation_needed trampoline. ");
1664 }
1665}
16661667#[cfg(test)]
1668mod tests {
1669use super::super::update_hook::SqliteChangeOp;
1670use super::*;
1671use std::sync::{Arc, Mutex};
16721673fn test_connection() -> RawConnection {
1674 RawConnection::establish(":memory:").expect("failed to establish :memory: connection")
1675 }
16761677#[test]
1678fn insert_event_dispatched_directly() {
1679let mut conn = test_connection();
1680 conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1681 .unwrap();
16821683let fired = Arc::new(Mutex::new(Vec::new()));
1684let f2 = fired.clone();
1685 conn.set_update_hook(move |e| {
1686 f2.lock().unwrap().push((e.op, e.rowid));
1687 });
16881689 conn.exec("INSERT INTO t VALUES (1)").unwrap();
16901691let events = fired.lock().unwrap();
1692assert_eq!(events.len(), 1);
1693assert_eq!(events[0].0, SqliteChangeOp::Insert);
1694assert_eq!(events[0].1, 1);
1695 }
16961697#[test]
1698fn consecutive_inserts_dispatch_immediately() {
1699let mut conn = test_connection();
1700 conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1701 .unwrap();
17021703let fired = Arc::new(Mutex::new(Vec::new()));
1704let f2 = fired.clone();
1705 conn.set_update_hook(move |e| {
1706 f2.lock().unwrap().push(e.rowid);
1707 });
17081709 conn.exec("INSERT INTO t VALUES (2); INSERT INTO t VALUES (3)")
1710 .unwrap();
17111712let events = fired.lock().unwrap();
1713assert_eq!(events.len(), 2);
1714assert_eq!(events[0], 2);
1715assert_eq!(events[1], 3);
1716 }
17171718#[test]
1719fn update_event() {
1720let mut conn = test_connection();
1721 conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
1722 .unwrap();
1723 conn.exec("INSERT INTO t VALUES (1, 'a')").unwrap();
17241725let fired = Arc::new(Mutex::new(Vec::new()));
1726let f2 = fired.clone();
1727 conn.set_update_hook(move |e| {
1728 f2.lock().unwrap().push((e.op, e.rowid));
1729 });
17301731 conn.exec("UPDATE t SET v = 'b' WHERE id = 1").unwrap();
17321733let events = fired.lock().unwrap();
1734assert_eq!(events.len(), 1);
1735assert_eq!(events[0].0, SqliteChangeOp::Update);
1736assert_eq!(events[0].1, 1);
1737 }
17381739#[test]
1740fn delete_event() {
1741let mut conn = test_connection();
1742 conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1743 .unwrap();
1744 conn.exec("INSERT INTO t VALUES (1)").unwrap();
17451746let fired = Arc::new(Mutex::new(Vec::new()));
1747let f2 = fired.clone();
1748 conn.set_update_hook(move |e| {
1749 f2.lock().unwrap().push((e.op, e.rowid));
1750 });
17511752 conn.exec("DELETE FROM t WHERE id = 1").unwrap();
17531754let events = fired.lock().unwrap();
1755assert_eq!(events.len(), 1);
1756assert_eq!(events[0].0, SqliteChangeOp::Delete);
1757assert_eq!(events[0].1, 1);
1758 }
17591760#[test]
1761fn remove_stops_events() {
1762let mut conn = test_connection();
1763 conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1764 .unwrap();
17651766let fired = Arc::new(Mutex::new(Vec::new()));
1767let f2 = fired.clone();
1768 conn.set_update_hook(move |e| {
1769 f2.lock().unwrap().push(e.rowid);
1770 });
17711772 conn.exec("INSERT INTO t VALUES (1)").unwrap();
1773assert_eq!(fired.lock().unwrap().len(), 1);
17741775 conn.remove_update_hook();
1776 conn.exec("INSERT INTO t VALUES (2)").unwrap();
1777assert_eq!(fired.lock().unwrap().len(), 1); // still 1
1778}
17791780#[test]
1781fn replacing_hook_drops_old() {
1782let mut conn = test_connection();
1783 conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1784 .unwrap();
17851786let first = Arc::new(Mutex::new(Vec::new()));
1787let f1 = first.clone();
1788 conn.set_update_hook(move |e| {
1789 f1.lock().unwrap().push(e.rowid);
1790 });
17911792 conn.exec("INSERT INTO t VALUES (1)").unwrap();
1793assert_eq!(first.lock().unwrap().len(), 1);
17941795// Replacing the hook installs the second closure and drops the first.
1796let second = Arc::new(Mutex::new(Vec::new()));
1797let f2 = second.clone();
1798 conn.set_update_hook(move |e| {
1799 f2.lock().unwrap().push(e.rowid);
1800 });
18011802 conn.exec("INSERT INTO t VALUES (2)").unwrap();
1803assert_eq!(first.lock().unwrap().len(), 1); // first no longer fires
1804assert_eq!(*second.lock().unwrap(), vec![2]);
1805 }
18061807#[test]
1808fn drop_does_not_panic() {
1809let mut conn = test_connection();
1810 conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
1811 .unwrap();
18121813 conn.set_update_hook(|_| {});
1814 conn.exec("INSERT INTO t VALUES (1)").unwrap();
1815 drop(conn);
1816// If we get here, drop succeeded without panic.
1817}
1818}