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