Skip to main content

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