1#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2extern crate libsqlite3_sys as ffi;
3#[cfg(all(target_family = "wasm", target_os = "unknown"))]
4use sqlite_wasm_rs as ffi;
56use crate::result::Error::DatabaseError;
7use crate::result::*;
8use crate::sqlite::SqliteConnection;
9use alloc::boxed::Box;
10use alloc::string::{String, ToString};
11use core::ffi::{c_char, c_int};
12#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
13type RawApiPointer = *const core::ffi::c_void;
14#[cfg(all(target_family = "wasm", target_os = "unknown"))]
15type RawApiPointer = *const ffi::sqlite3_api_routines;
1617/// SQLite's auto-extension callback type.
18type RawAutoExtension = unsafe extern "C" fn(
19 db: *mut ffi::sqlite3,
20 pz_err_msg: *mut *mut c_char,
21 p_api: RawApiPointer,
22) -> c_int;
2324// TODO: diesel 3.0 use `libsqlite3-sys` declarations after raising the minimum to 0.29.
25#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
26mod auto_extension_ffi {
27use super::{RawAutoExtension, c_int};
2829// SAFETY: These declarations match SQLite's C ABI and bypass incompatible callback types in older bindings.
30#[allow(unsafe_code)]
31unsafe extern "C" {
32pub(super) fn sqlite3_auto_extension(entry_point: Option<RawAutoExtension>) -> c_int;
33pub(super) fn sqlite3_cancel_auto_extension(entry_point: Option<RawAutoExtension>)
34 -> c_int;
35 }
36}
3738#[cfg(all(target_family = "wasm", target_os = "unknown"))]
39use ffi as auto_extension_ffi;
4041/// Registers an auto-extension that runs for every SQLite connection opened in
42/// this process, including non-Diesel ones.
43///
44/// This is a safe wrapper around [`sqlite3_auto_extension`][docs]. The callback
45/// receives the [`SqliteConnection`] being opened and returns `Ok(())` to
46/// continue or an error to fail the open. Use it to register SQL functions,
47/// collations, or aggregates through the usual connection API, or to initialize
48/// a statically linked C extension such as Spatialite or sqlite-vec via
49/// [`SqliteConnection::with_raw_connection`].
50///
51/// Call this before opening any connection. Extensions run in registration
52/// order, and the first error aborts the open. The callback must be a `fn` item
53/// or a closure that captures only zero-sized values (enforced at compile
54/// time), and registering the same `fn` twice is a no-op. It may run on several
55/// threads at once and must not open another connection (which would re-enter
56/// the auto-extensions and recurse) or call [`register_auto_extension`],
57/// [`cancel_auto_extension`], or [`reset_auto_extension`]. Panics are caught and
58/// turned into a failed open.
59///
60/// [docs]: https://www.sqlite.org/c3ref/auto_extension.html
61///
62/// # Example
63///
64/// ```rust
65/// use diesel::dsl::sql;
66/// use diesel::prelude::*;
67/// use diesel::sql_types::Integer;
68/// use diesel::sqlite::{register_auto_extension, reset_auto_extension, SqliteConnection};
69///
70/// // Registers a case-insensitive collation on every new connection.
71/// fn my_ext(conn: &mut SqliteConnection) -> QueryResult<()> {
72/// conn.register_collation("RUSTNOCASE", |a, b| a.to_lowercase().cmp(&b.to_lowercase()))
73/// }
74///
75/// register_auto_extension(my_ext).unwrap();
76///
77/// // Every future connection now has the collation.
78/// let mut conn = SqliteConnection::establish(":memory:").unwrap();
79/// let equal: i32 = sql::<Integer>("SELECT 'a' = 'A' COLLATE RUSTNOCASE")
80/// .get_result(&mut conn)
81/// .unwrap();
82/// assert_eq!(equal, 1);
83/// # reset_auto_extension();
84/// ```
85#[allow(unsafe_code)]
86pub fn register_auto_extension<F>(extension: F) -> QueryResult<()>
87where
88F: Fn(&mut SqliteConnection) -> QueryResult<()> + Sync + 'static,
89{
90// SAFETY: `entry_point` returns a stable function pointer with SQLite's callback ABI.
91let result =
92unsafe { auto_extension_ffi::sqlite3_auto_extension(Some(entry_point(extension))) };
93if result == ffi::SQLITE_OK {
94Ok(())
95 } else {
96Err(DatabaseError(
97 DatabaseErrorKind::Unknown,
98Box::new(ffi::code_to_str(result).to_string()),
99 ))
100 }
101}
102103/// Removes a previously registered auto-extension, returning `true` if it was
104/// found ([docs][cancel_docs]).
105///
106/// Pass the same `fn` item given to [`register_auto_extension`]. A closure
107/// cannot be cancelled this way, because its type cannot be named again. Use
108/// [`reset_auto_extension`] to clear everything instead.
109///
110/// [cancel_docs]: https://www.sqlite.org/c3ref/cancel_auto_extension.html
111#[allow(unsafe_code)]
112pub fn cancel_auto_extension<F>(extension: F) -> bool113where
114F: Fn(&mut SqliteConnection) -> QueryResult<()> + Sync + 'static,
115{
116// SAFETY: `entry_point` returns the same stable pointer used for registration.
117unsafe { auto_extension_ffi::sqlite3_cancel_auto_extension(Some(entry_point(extension))) != 0 }
118}
119120/// Clears **all** registered auto-extensions ([docs][reset_docs]).
121///
122/// After this call, no auto-extensions will run for newly opened connections.
123///
124/// [reset_docs]: https://www.sqlite.org/c3ref/reset_auto_extension.html
125#[allow(unsafe_code)]
126pub fn reset_auto_extension() {
127unsafe { ffi::sqlite3_reset_auto_extension() }
128}
129130/// Returns the trampoline for `F`. `extension` is taken by value to infer `F`,
131/// then `forget`-ten so the zero-sized callback stays conceptually alive for the
132/// process and its destructor never runs.
133fn entry_point<F>(extension: F) -> RawAutoExtension134where
135F: Fn(&mut SqliteConnection) -> QueryResult<()> + Sync + 'static,
136{
137 core::mem::forget(extension);
138trampoline::<F>
139}
140141/// The C entry point handed to SQLite, monomorphized per callback type `F` so
142/// each distinct callback maps to a distinct, stable address. SQLite's
143/// pointer-based deduplication and [`cancel_auto_extension`] rely on that.
144#[allow(unsafe_code)]
145unsafe extern "C" fn trampoline<F>(
146 db: *mut ffi::sqlite3,
147 pz_err_msg: *mut *mut c_char,
148 _p_api: RawApiPointer,
149) -> c_int150where
151F: Fn(&mut SqliteConnection) -> QueryResult<()> + Sync + 'static,
152{
153const {
154if !(core::mem::size_of::<F>() == 0) {
{
::core::panicking::panic_fmt(format_args!("an auto-extension callback must not capture non-zero-sized state. Use a `fn` item or a closure that captures only zero-sized values"));
}
};assert!(
155 core::mem::size_of::<F>() == 0,
156"an auto-extension callback must not capture non-zero-sized state. \
157 Use a `fn` item or a closure that captures only zero-sized values"
158);
159 }
160161// `_p_api` matters only for runtime-loaded shared libraries. Statically
162 // linked extensions link the SQLite symbols directly, so we ignore it.
163let result: Result<(), String> =
164crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
165let Some(db) = core::ptr::NonNull::new(db) else {
166return Err(String::from(
167"auto-extension received a null database handle",
168 ));
169 };
170// Reconstruct a *reference* to the zero-sized callback, never an
171 // owned value, so its destructor never runs. `&F: Fn` because `F: Fn`.
172 // SAFETY: `F` is zero-sized (asserted above), so a dangling, aligned,
173 // non-null pointer is a valid `&F`, which `NonNull::dangling` provides.
174let extension: &F = unsafe { core::ptr::NonNull::<F>::dangling().as_ref() };
175// SAFETY: `db` is a valid handle for the duration of this call, and
176 // the borrowed connection does not take ownership of it.
177unsafe { SqliteConnection::with_borrowed_connection(db, extension) }
178 .map_err(|e| e.to_string())
179 }))
180 .unwrap_or_else(|panic| {
181Err(match panic_detail(panic) {
182Some(message) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("auto-extension panicked: {0}",
message))
})alloc::format!("auto-extension panicked: {message}"),
183None => String::from("auto-extension panicked"),
184 })
185 });
186187match result {
188Ok(()) => ffi::SQLITE_OK,
189Err(message) => {
190set_error_message(pz_err_msg, &message);
191 ffi::SQLITE_ERROR192 }
193 }
194}
195196/// Best-effort message from a caught panic payload. The no_std `catch_unwind`
197/// carries no payload, so only the `std` variant can recover the text.
198#[cfg(feature = "std")]
199fn panic_detail(panic: alloc::boxed::Box<dyn core::any::Any + Send>) -> Option<String> {
200panic201 .downcast_ref::<&str>()
202 .map(|s| (*s).to_owned())
203 .or_else(|| panic.downcast_ref::<String>().cloned())
204}
205206#[cfg(not(feature = "std"))]
207fn panic_detail(_panic: ()) -> Option<String> {
208None
209}
210211/// Writes `message` into `*pz_err_msg` with `sqlite3_malloc`, which is the
212/// allocator SQLite later frees it with. The message is truncated at the first
213/// NUL byte to form a valid C string, and allocation failure is ignored.
214#[allow(unsafe_code)]
215fn set_error_message(pz_err_msg: *mut *mut c_char, message: &str) {
216if pz_err_msg.is_null() {
217return;
218 }
219220let bytes = message.as_bytes();
221let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
222223// SQLite sizes allocations with a C `int`. A message that does not fit is
224 // dropped rather than truncated to a bogus length.
225let Ok(size) = c_int::try_from(len + 1) else {
226return;
227 };
228let buffer = unsafe { ffi::sqlite3_malloc(size) } as *mut u8;
229if buffer.is_null() {
230return;
231 }
232233unsafe {
234 core::ptr::copy_nonoverlapping(bytes.as_ptr(), buffer, len);
235*buffer.add(len) = 0;
236*pz_err_msg = bufferas *mut c_char;
237 }
238}
239240#[cfg(test)]
241mod tests {
242use super::*;
243use crate::dsl::sql;
244use crate::prelude::*;
245use crate::sql_types::Integer;
246use std::sync::Mutex;
247248// `sqlite3_auto_extension` is process-global, so these tests serialize on
249 // this lock and register only benign (never-failing) extensions, leaving
250 // connections opened by other tests unaffected. The failing path is covered
251 // by `trampoline_maps_result_to_return_code`, which calls the trampoline
252 // directly without touching the global registry.
253static AUTO_EXT_TEST_LOCK: Mutex<()> = Mutex::new(());
254255// A benign auto-extension: registers a `TESTCOLL` collation through the
256 // normal connection API.
257fn test_ext_init(conn: &mut SqliteConnection) -> QueryResult<()> {
258 conn.register_collation("TESTCOLL", |a, b| a.cmp(b))
259 }
260261fn open_memory_connection() -> SqliteConnection {
262 SqliteConnection::establish(":memory:").expect("Failed to open :memory: connection")
263 }
264265// Errors out if `TESTCOLL` is not registered on a freshly opened connection.
266fn probe_collation() -> QueryResult<i32> {
267let mut conn = open_memory_connection();
268 sql::<Integer>("SELECT 'a' = 'a' COLLATE TESTCOLL").get_result(&mut conn)
269 }
270271/// RAII guard that calls `reset_auto_extension()` on drop, ensuring global
272 /// state is cleaned up even if a test panics.
273struct TestResetGuard;
274275impl Drop for TestResetGuard {
276fn drop(&mut self) {
277 reset_auto_extension();
278 }
279 }
280281#[test]
282fn auto_extension_lifecycle() {
283let _lock = AUTO_EXT_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
284let _guard = TestResetGuard;
285 reset_auto_extension();
286287// -- 1. register + new connection has the collation --
288register_auto_extension(test_ext_init).unwrap();
289assert_eq!(probe_collation().unwrap(), 1);
290291// -- 2. cancel + new connection does NOT have the collation --
292let removed = cancel_auto_extension(test_ext_init);
293assert!(
294 removed,
295"cancel should return true for registered extension"
296);
297assert!(
298 probe_collation().is_err(),
299"collation should not be available after cancel"
300);
301302// -- 3. cancel returns false for unregistered --
303let removed = cancel_auto_extension(test_ext_init);
304assert!(
305 !removed,
306"cancel should return false for unregistered extension"
307);
308309// -- 4. reset clears all --
310register_auto_extension(test_ext_init).unwrap();
311 reset_auto_extension();
312assert!(
313 probe_collation().is_err(),
314"collation should not be available after reset"
315);
316317// -- 5. duplicate registration is idempotent --
318register_auto_extension(test_ext_init).unwrap();
319 register_auto_extension(test_ext_init).unwrap();
320assert_eq!(probe_collation().unwrap(), 1);
321// _guard drops here, ensuring reset even on panic.
322}
323324// Drives the trampoline directly (via `entry_point`), without registering
325 // it in SQLite's global list, so the Ok/Err/null paths can be checked
326 // deterministically without affecting connections opened by other tests.
327#[test]
328 #[allow(unsafe_code)]
329fn trampoline_maps_result_to_return_code() {
330fn ok_ext(_conn: &mut SqliteConnection) -> QueryResult<()> {
331Ok(())
332 }
333fn err_ext(_conn: &mut SqliteConnection) -> QueryResult<()> {
334Err(Error::QueryBuilderError("boom".into()))
335 }
336337let ok_tramp = entry_point(ok_ext);
338let err_tramp = entry_point(err_ext);
339340let mut conn = open_memory_connection();
341// SAFETY: the pointer is only used for the duration of the closure,
342 // while `conn` is alive.
343unsafe {
344 conn.with_raw_connection(|db| {
345let mut err: *mut c_char = core::ptr::null_mut();
346347// Ok -> SQLITE_OK, no error message allocated.
348let rc = ok_tramp(db, &mut err, core::ptr::null());
349assert_eq!(rc, ffi::SQLITE_OK);
350assert!(err.is_null());
351352// Err -> SQLITE_ERROR, message written via sqlite3_malloc.
353let rc = err_tramp(db, &mut err, core::ptr::null());
354assert_eq!(rc, ffi::SQLITE_ERROR);
355assert!(!err.is_null());
356let message = core::ffi::CStr::from_ptr(err)
357 .to_string_lossy()
358 .into_owned();
359assert_eq!(message, "boom");
360 ffi::sqlite3_free(err as *mut core::ffi::c_void);
361362// Null db handle -> SQLITE_ERROR, never dereferenced.
363let mut err: *mut c_char = core::ptr::null_mut();
364let rc = ok_tramp(core::ptr::null_mut(), &mut err, core::ptr::null());
365assert_eq!(rc, ffi::SQLITE_ERROR);
366if !err.is_null() {
367 ffi::sqlite3_free(err as *mut core::ffi::c_void);
368 }
369 })
370 }
371 }
372373// Regression test for the closure-reconstruction soundness fix. The callback
374 // captures a zero-sized guard with a load-bearing `Drop` that must never run,
375 // because the trampoline only reproduces the callback behind a reference and
376 // `forget`s the registered value (the old `mem::zeroed()` ran it repeatedly).
377#[test]
378fn callback_zero_sized_capture_is_never_dropped() {
379use std::sync::atomic::{AtomicUsize, Ordering};
380381static DROPS: AtomicUsize = AtomicUsize::new(0);
382383// Zero-sized, `Sync`, with a load-bearing `Drop`.
384struct Guard;
385impl Drop for Guard {
386fn drop(&mut self) {
387 DROPS.fetch_add(1, Ordering::SeqCst);
388 }
389 }
390391let _lock = AUTO_EXT_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
392let _reset = TestResetGuard;
393 reset_auto_extension();
394395let guard = Guard;
396// `move` captures the zero-sized `guard` by value, so the closure is a
397 // zero-sized type with a non-trivial destructor.
398register_auto_extension(move |conn: &mut SqliteConnection| {
399let _ = &guard;
400 conn.register_collation("TESTCOLL", |a, b| a.cmp(b))
401 })
402 .unwrap();
403404for _ in 0..5 {
405assert_eq!(probe_collation().unwrap(), 1);
406 }
407 reset_auto_extension();
408409assert_eq!(
410 DROPS.load(Ordering::SeqCst),
4110,
412"the captured guard's destructor must never run"
413);
414 }
415416// A panicking callback becomes `SQLITE_ERROR` with the payload recovered
417 // into the message (`&str` and `String` payloads, generic fallback
418 // otherwise), and drives the panic-unwind path through the drop guard. Gated
419 // off WASM, where `catch_unwind` aborts because `panic = "abort"`.
420#[test]
421 #[allow(unsafe_code)]
422 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
423fn trampoline_reports_panic_message() {
424fn panic_str(_conn: &mut SqliteConnection) -> QueryResult<()> {
425panic!("boom-str");
426 }
427fn panic_string(_conn: &mut SqliteConnection) -> QueryResult<()> {
428panic!("boom-{}", 7);
429 }
430fn panic_other(_conn: &mut SqliteConnection) -> QueryResult<()> {
431 std::panic::panic_any(7_u8);
432 }
433434let cases: [(RawAutoExtension, &str); 3] = [
435 (entry_point(panic_str), "auto-extension panicked: boom-str"),
436 (entry_point(panic_string), "auto-extension panicked: boom-7"),
437 (entry_point(panic_other), "auto-extension panicked"),
438 ];
439440let mut conn = open_memory_connection();
441// SAFETY: the pointer is only used while `conn` is alive.
442unsafe {
443 conn.with_raw_connection(|db| {
444for (tramp, expected) in cases {
445let mut err: *mut c_char = core::ptr::null_mut();
446let rc = tramp(db, &mut err, core::ptr::null());
447assert_eq!(rc, ffi::SQLITE_ERROR);
448assert!(!err.is_null());
449let message = core::ffi::CStr::from_ptr(err)
450 .to_string_lossy()
451 .into_owned();
452assert_eq!(message, expected);
453 ffi::sqlite3_free(err as *mut core::ffi::c_void);
454 }
455 })
456 }
457 }
458459#[test]
460 #[allow(unsafe_code)]
461fn error_message_truncates_at_interior_nul() {
462let mut err: *mut c_char = core::ptr::null_mut();
463 set_error_message(&mut err, "before\0after");
464assert!(!err.is_null());
465// SAFETY: `err` is a sqlite-allocated C string we own until we free it.
466unsafe {
467let truncated = core::ffi::CStr::from_ptr(err).to_str().unwrap();
468assert_eq!(truncated, "before");
469 ffi::sqlite3_free(err as *mut core::ffi::c_void);
470 }
471472// A null out-pointer is a no-op (must not write through it or crash).
473set_error_message(core::ptr::null_mut(), "ignored");
474 }
475}