rusqlite/
inner_connection.rs

1use std::ffi::{c_char, c_int, CStr};
2#[cfg(feature = "load_extension")]
3use std::path::Path;
4use std::ptr;
5use std::str;
6use std::sync::{Arc, Mutex};
7
8use super::ffi;
9use super::str_for_sqlite;
10use super::{Connection, InterruptHandle, Name, OpenFlags, PrepFlags, Result};
11use crate::error::{decode_result_raw, error_from_handle, error_with_offset, Error};
12use crate::raw_statement::RawStatement;
13use crate::statement::Statement;
14use crate::version_number;
15
16pub struct InnerConnection {
17    pub db: *mut ffi::sqlite3,
18    // It's unsafe to call `sqlite3_close` while another thread is performing
19    // a `sqlite3_interrupt`, and vice versa, so we take this mutex during
20    // those functions. This protects a copy of the `db` pointer (which is
21    // cleared on closing), however the main copy, `db`, is unprotected.
22    // Otherwise, a long-running query would prevent calling interrupt, as
23    // interrupt would only acquire the lock after the query's completion.
24    interrupt_lock: Arc<Mutex<*mut ffi::sqlite3>>,
25    #[cfg(feature = "hooks")]
26    pub commit_hook: Option<Box<dyn FnMut() -> bool + Send>>,
27    #[cfg(feature = "hooks")]
28    pub rollback_hook: Option<Box<dyn FnMut() + Send>>,
29    #[cfg(feature = "hooks")]
30    #[expect(clippy::type_complexity)]
31    pub update_hook: Option<Box<dyn FnMut(crate::hooks::Action, &str, &str, i64) + Send>>,
32    #[cfg(feature = "hooks")]
33    pub progress_handler: Option<Box<dyn FnMut() -> bool + Send>>,
34    #[cfg(feature = "hooks")]
35    pub authorizer: Option<crate::hooks::BoxedAuthorizer>,
36    #[cfg(feature = "preupdate_hook")]
37    #[expect(clippy::type_complexity)]
38    pub preupdate_hook: Option<
39        Box<dyn FnMut(crate::hooks::Action, &str, &str, &crate::hooks::PreUpdateCase) + Send>,
40    >,
41    owned: bool,
42}
43
44unsafe impl Send for InnerConnection {}
45
46impl InnerConnection {
47    #[expect(clippy::mutex_atomic, clippy::arc_with_non_send_sync)] // See unsafe impl Send / Sync for InterruptHandle
48    #[inline]
49    pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> Self {
50        Self {
51            db,
52            interrupt_lock: Arc::new(Mutex::new(if owned { db } else { ptr::null_mut() })),
53            #[cfg(feature = "hooks")]
54            commit_hook: None,
55            #[cfg(feature = "hooks")]
56            rollback_hook: None,
57            #[cfg(feature = "hooks")]
58            update_hook: None,
59            #[cfg(feature = "hooks")]
60            progress_handler: None,
61            #[cfg(feature = "hooks")]
62            authorizer: None,
63            #[cfg(feature = "preupdate_hook")]
64            preupdate_hook: None,
65            owned,
66        }
67    }
68
69    pub fn open_with_flags(
70        c_path: &CStr,
71        mut flags: OpenFlags,
72        vfs: Option<&CStr>,
73    ) -> Result<Self> {
74        ensure_safe_sqlite_threading_mode()?;
75
76        let z_vfs = match vfs {
77            Some(c_vfs) => c_vfs.as_ptr(),
78            None => ptr::null(),
79        };
80
81        // turn on extended results code before opening database to have a better diagnostic if a failure happens
82        let exrescode = if version_number() >= 3_037_000 {
83            flags |= OpenFlags::SQLITE_OPEN_EXRESCODE;
84            true
85        } else {
86            false // flag SQLITE_OPEN_EXRESCODE is ignored by SQLite version < 3.37.0
87        };
88
89        unsafe {
90            let mut db: *mut ffi::sqlite3 = ptr::null_mut();
91            let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), z_vfs);
92            if r != ffi::SQLITE_OK {
93                let e = if db.is_null() {
94                    err!(r, "{}", c_path.to_string_lossy())
95                } else {
96                    let mut e = error_from_handle(db, r);
97                    if let Error::SqliteFailure(
98                        ffi::Error {
99                            code: ffi::ErrorCode::CannotOpen,
100                            ..
101                        },
102                        Some(msg),
103                    ) = e
104                    {
105                        e = err!(r, "{msg}: {}", c_path.to_string_lossy());
106                    }
107                    ffi::sqlite3_close(db);
108                    e
109                };
110
111                return Err(e);
112            }
113
114            // attempt to turn on extended results code; don't fail if we can't.
115            if !exrescode {
116                ffi::sqlite3_extended_result_codes(db, 1);
117            }
118
119            let r = ffi::sqlite3_busy_timeout(db, 5000);
120            if r != ffi::SQLITE_OK {
121                let e = error_from_handle(db, r);
122                ffi::sqlite3_close(db);
123                return Err(e);
124            }
125
126            Ok(Self::new(db, true))
127        }
128    }
129
130    #[inline]
131    pub fn db(&self) -> *mut ffi::sqlite3 {
132        self.db
133    }
134
135    #[inline]
136    pub fn decode_result(&self, code: c_int) -> Result<()> {
137        unsafe { decode_result_raw(self.db(), code) }
138    }
139
140    pub fn close(&mut self) -> Result<()> {
141        if self.db.is_null() {
142            return Ok(());
143        }
144        self.remove_hooks();
145        self.remove_preupdate_hook();
146        let mut shared_handle = self.interrupt_lock.lock().unwrap();
147        assert!(
148            !self.owned || !shared_handle.is_null(),
149            "Bug: Somehow interrupt_lock was cleared before the DB was closed"
150        );
151        if !self.owned {
152            self.db = ptr::null_mut();
153            return Ok(());
154        }
155        unsafe {
156            let r = ffi::sqlite3_close(self.db);
157            // Need to use _raw because _guard has a reference out, and
158            // decode_result takes &mut self.
159            let r = decode_result_raw(self.db, r);
160            if r.is_ok() {
161                *shared_handle = ptr::null_mut();
162                self.db = ptr::null_mut();
163            }
164            r
165        }
166    }
167
168    #[inline]
169    pub fn get_interrupt_handle(&self) -> InterruptHandle {
170        InterruptHandle {
171            db_lock: Arc::clone(&self.interrupt_lock),
172        }
173    }
174
175    #[inline]
176    #[cfg(feature = "load_extension")]
177    pub unsafe fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
178        let r = ffi::sqlite3_enable_load_extension(self.db, onoff);
179        self.decode_result(r)
180    }
181
182    #[cfg(feature = "load_extension")]
183    pub unsafe fn load_extension<N: Name>(
184        &self,
185        dylib_path: &Path,
186        entry_point: Option<N>,
187    ) -> Result<()> {
188        let dylib_str = super::path_to_cstring(dylib_path)?;
189        let mut errmsg: *mut c_char = ptr::null_mut();
190        let cs = entry_point.as_ref().map(N::as_cstr).transpose()?;
191        let c_entry = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
192        let r = ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), c_entry, &mut errmsg);
193        if r == ffi::SQLITE_OK {
194            Ok(())
195        } else {
196            let message = super::errmsg_to_string(errmsg);
197            ffi::sqlite3_free(errmsg.cast::<std::ffi::c_void>());
198            Err(crate::error::error_from_sqlite_code(r, Some(message)))
199        }
200    }
201
202    #[inline]
203    pub fn last_insert_rowid(&self) -> i64 {
204        unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
205    }
206
207    pub fn prepare<'a>(
208        &mut self,
209        conn: &'a Connection,
210        sql: &str,
211        flags: PrepFlags,
212    ) -> Result<(Statement<'a>, usize)> {
213        let mut c_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
214        let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
215        let mut c_tail: *const c_char = ptr::null();
216        #[cfg(not(feature = "unlock_notify"))]
217        let r = unsafe { self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail) };
218        #[cfg(feature = "unlock_notify")]
219        let r = unsafe {
220            use crate::unlock_notify;
221            let mut rc;
222            loop {
223                rc = self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail);
224                if !unlock_notify::is_locked(self.db, rc) {
225                    break;
226                }
227                rc = unlock_notify::wait_for_unlock_notify(self.db);
228                if rc != ffi::SQLITE_OK {
229                    break;
230                }
231            }
232            rc
233        };
234        // If there is an error, *ppStmt is set to NULL.
235        if r != ffi::SQLITE_OK {
236            return Err(unsafe { error_with_offset(self.db, r, sql) });
237        }
238        // If the input text contains no SQL (if the input is an empty string or a
239        // comment) then *ppStmt is set to NULL.
240        let tail = if c_tail.is_null() {
241            0
242        } else {
243            let n = (c_tail as isize) - (c_sql as isize);
244            if n <= 0 || n >= len as isize {
245                0
246            } else {
247                n as usize
248            }
249        };
250        Ok((
251            Statement::new(conn, unsafe { RawStatement::new(c_stmt) }),
252            tail,
253        ))
254    }
255
256    #[inline]
257    #[cfg(not(feature = "modern_sqlite"))]
258    unsafe fn prepare_(
259        &self,
260        z_sql: *const c_char,
261        n_byte: c_int,
262        _: PrepFlags,
263        pp_stmt: *mut *mut ffi::sqlite3_stmt,
264        pz_tail: *mut *const c_char,
265    ) -> c_int {
266        ffi::sqlite3_prepare_v2(self.db(), z_sql, n_byte, pp_stmt, pz_tail)
267    }
268
269    #[inline]
270    #[cfg(feature = "modern_sqlite")]
271    unsafe fn prepare_(
272        &self,
273        z_sql: *const c_char,
274        n_byte: c_int,
275        flags: PrepFlags,
276        pp_stmt: *mut *mut ffi::sqlite3_stmt,
277        pz_tail: *mut *const c_char,
278    ) -> c_int {
279        ffi::sqlite3_prepare_v3(self.db(), z_sql, n_byte, flags.bits(), pp_stmt, pz_tail)
280    }
281
282    #[inline]
283    pub fn changes(&self) -> u64 {
284        #[cfg(not(feature = "modern_sqlite"))]
285        unsafe {
286            ffi::sqlite3_changes(self.db()) as u64
287        }
288        #[cfg(feature = "modern_sqlite")] // 3.37.0
289        unsafe {
290            ffi::sqlite3_changes64(self.db()) as u64
291        }
292    }
293
294    #[inline]
295    pub fn total_changes(&self) -> u64 {
296        #[cfg(not(feature = "modern_sqlite"))]
297        unsafe {
298            ffi::sqlite3_total_changes(self.db()) as u64
299        }
300        #[cfg(feature = "modern_sqlite")] // 3.37.0
301        unsafe {
302            ffi::sqlite3_total_changes64(self.db()) as u64
303        }
304    }
305
306    #[inline]
307    pub fn is_autocommit(&self) -> bool {
308        unsafe { get_autocommit(self.db()) }
309    }
310
311    pub fn is_busy(&self) -> bool {
312        let db = self.db();
313        unsafe {
314            let mut stmt = ffi::sqlite3_next_stmt(db, ptr::null_mut());
315            while !stmt.is_null() {
316                if ffi::sqlite3_stmt_busy(stmt) != 0 {
317                    return true;
318                }
319                stmt = ffi::sqlite3_next_stmt(db, stmt);
320            }
321        }
322        false
323    }
324
325    pub fn cache_flush(&mut self) -> Result<()> {
326        crate::error::check(unsafe { ffi::sqlite3_db_cacheflush(self.db()) })
327    }
328
329    #[cfg(not(feature = "hooks"))]
330    #[inline]
331    fn remove_hooks(&mut self) {}
332
333    #[cfg(not(feature = "preupdate_hook"))]
334    #[inline]
335    fn remove_preupdate_hook(&mut self) {}
336
337    pub fn db_readonly<N: Name>(&self, db_name: N) -> Result<bool> {
338        let name = db_name.as_cstr()?;
339        let r = unsafe { ffi::sqlite3_db_readonly(self.db, name.as_ptr()) };
340        match r {
341            0 => Ok(false),
342            1 => Ok(true),
343            -1 => Err(err!(
344                ffi::SQLITE_MISUSE,
345                "{db_name:?} is not the name of a database"
346            )),
347            _ => Err(err!(r, "Unexpected result")),
348        }
349    }
350
351    #[cfg(feature = "modern_sqlite")] // 3.37.0
352    pub fn txn_state<N: Name>(
353        &self,
354        db_name: Option<N>,
355    ) -> Result<super::transaction::TransactionState> {
356        let cs = db_name.as_ref().map(N::as_cstr).transpose()?;
357        let name = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
358        let r = unsafe { ffi::sqlite3_txn_state(self.db, name) };
359        match r {
360            0 => Ok(super::transaction::TransactionState::None),
361            1 => Ok(super::transaction::TransactionState::Read),
362            2 => Ok(super::transaction::TransactionState::Write),
363            -1 => Err(err!(
364                ffi::SQLITE_MISUSE,
365                "{db_name:?} is not the name of a valid schema"
366            )),
367            _ => Err(err!(r, "Unexpected result")),
368        }
369    }
370
371    #[inline]
372    pub fn release_memory(&self) -> Result<()> {
373        self.decode_result(unsafe { ffi::sqlite3_db_release_memory(self.db) })
374    }
375
376    #[cfg(feature = "modern_sqlite")] // 3.41.0
377    pub fn is_interrupted(&self) -> bool {
378        unsafe { ffi::sqlite3_is_interrupted(self.db) == 1 }
379    }
380}
381
382#[inline]
383pub(crate) unsafe fn get_autocommit(ptr: *mut ffi::sqlite3) -> bool {
384    ffi::sqlite3_get_autocommit(ptr) != 0
385}
386
387#[inline]
388pub(crate) unsafe fn db_filename<N: Name>(
389    _: std::marker::PhantomData<&()>,
390    ptr: *mut ffi::sqlite3,
391    db_name: N,
392) -> Option<&str> {
393    let db_name = db_name.as_cstr().unwrap();
394    let db_filename = ffi::sqlite3_db_filename(ptr, db_name.as_ptr());
395    if db_filename.is_null() {
396        None
397    } else {
398        CStr::from_ptr(db_filename).to_str().ok()
399    }
400}
401
402impl Drop for InnerConnection {
403    #[expect(unused_must_use)]
404    #[inline]
405    fn drop(&mut self) {
406        self.close();
407    }
408}
409
410// threading mode checks are not necessary (and do not work) on target
411// platforms that do not have threading (such as webassembly)
412#[cfg(target_arch = "wasm32")]
413fn ensure_safe_sqlite_threading_mode() -> Result<()> {
414    Ok(())
415}
416
417#[cfg(not(any(target_arch = "wasm32")))]
418fn ensure_safe_sqlite_threading_mode() -> Result<()> {
419    // Ensure SQLite was compiled in threadsafe mode.
420    if unsafe { ffi::sqlite3_threadsafe() == 0 } {
421        return Err(Error::SqliteSingleThreadedMode);
422    }
423
424    // Now we know SQLite is _capable_ of being in Multi-thread of Serialized mode,
425    // but it's possible someone configured it to be in Single-thread mode
426    // before calling into us. That would mean we're exposing an unsafe API via
427    // a safe one (in Rust terminology).
428    //
429    // We can ask SQLite for a mutex and check for
430    // the magic value 8. This isn't documented, but it's what SQLite
431    // returns for its mutex allocation function in Single-thread mode.
432    const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8;
433    let is_singlethreaded = unsafe {
434        let mutex_ptr = ffi::sqlite3_mutex_alloc(0);
435        let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC;
436        ffi::sqlite3_mutex_free(mutex_ptr);
437        is_singlethreaded
438    };
439    if is_singlethreaded {
440        Err(Error::SqliteSingleThreadedMode)
441    } else {
442        Ok(())
443    }
444}