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