Skip to main content

sqlite3_ext/
connection.rs

1#[cfg(modern_sqlite)]
2use crate::mutex::SQLiteMutexGuard;
3use crate::{ffi, sqlite3_match_version, sqlite3_require_version, types::*};
4use bitflags::bitflags;
5#[cfg(modern_sqlite)]
6use std::ptr::{null, NonNull};
7use std::{
8    ffi::{CStr, CString},
9    mem::MaybeUninit,
10    ops::{Deref, DerefMut},
11    os::raw::c_int,
12    path::Path,
13    ptr::null_mut,
14    thread::panicking,
15};
16
17bitflags! {
18    /// These are the flags that can be passed to [Database::open_with_flags] and variants.
19    #[repr(transparent)]
20    pub struct OpenFlags: c_int {
21        /// The database is opened in read-only mode. If the database does not already exist, an error is returned.
22        const READONLY = ffi::SQLITE_OPEN_READONLY;
23        /// The database is opened for reading and writing if possible, or reading only
24        /// if the file is write protected by the operating system. In either case the
25        /// database must already exist, otherwise an error is returned (see
26        /// [Self::CREATE]).
27        const READWRITE = ffi::SQLITE_OPEN_READWRITE;
28        /// Create a new, empty database file when opening if it does not already
29        /// exist. This only applies to [Self::READWRITE].
30        const CREATE = ffi::SQLITE_OPEN_CREATE;
31        /// The database will be opened as an in-memory database. The database is named
32        /// by the "filename" argument for the purposes of cache-sharing, if shared
33        /// cache mode is enabled, but the "filename" is otherwise ignored.
34        const MEMORY = ffi::SQLITE_OPEN_MEMORY;
35        /// The new database connection will use the "multi-thread" threading mode.
36        /// This means that separate threads are allowed to use SQLite at the same
37        /// time, as long as each thread is using a different database connection.
38        ///
39        /// # Safety
40        ///
41        /// When using this flag, you assume responsibility for verifying that the
42        /// database connection will only be accessed from a single thread.
43        const UNSAFE_NOMUTEX = ffi::SQLITE_OPEN_NOMUTEX;
44        /// The new database connection will use the "serialized" threading mode. This
45        /// means the multiple threads can safely attempt to use the same database
46        /// connection at the same time. (Mutexes will block any actual concurrency,
47        /// but in this mode there is no harm in trying.)
48        const FULLMUTEX = ffi::SQLITE_OPEN_FULLMUTEX;
49        /// The database is opened shared cache enabled, overriding the default shared
50        /// cache setting provided by [ffi::sqlite3_enable_shared_cache].
51        const SHAREDCACHE = ffi::SQLITE_OPEN_SHAREDCACHE;
52        /// The database is opened shared cache disabled, overriding the default shared
53        /// cache setting provided by [ffi::sqlite3_enable_shared_cache].
54        const PRIVATECACHE = ffi::SQLITE_OPEN_PRIVATECACHE;
55        /// The database connection comes up in "extended result code mode". In other
56        /// words, the database behaves has if
57        /// [ffi::sqlite3_extended_result_codes](db,1) where called on the database
58        /// connection as soon as the connection is created. In addition to setting the
59        /// extended result code mode, this flag also causes the corresponding
60        /// [Database] open method to return an extended result code.
61        const EXRESCODE = ffi::SQLITE_OPEN_EXRESCODE;
62        /// The database filename is not allowed to be a symbolic link.
63        const NOFOLLOW = ffi::SQLITE_OPEN_NOFOLLOW;
64
65        /// This is the set of flags used when calling open methods that do not accept
66        /// flags.
67        const DEFAULT = ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE;
68    }
69}
70
71/// Represents a borrowed connection to an SQLite database.
72#[repr(transparent)]
73pub struct Connection {
74    db: ffi::sqlite3,
75}
76
77impl Connection {
78    /// Convert an SQLite handle into a reference to Connection.
79    ///
80    /// # Safety
81    ///
82    /// The behavior of this method is undefined if the passed pointer is not valid.
83    pub unsafe fn from_ptr<'a>(db: *mut ffi::sqlite3) -> &'a mut Connection {
84        &mut *(db as *mut Connection)
85    }
86
87    /// Get the underlying SQLite handle.
88    ///
89    /// # Safety
90    ///
91    /// Using the returned pointer may cause undefined behavior in other, safe code.
92    pub unsafe fn as_mut_ptr(&self) -> *mut ffi::sqlite3 {
93        &self.db as *const _ as _
94    }
95
96    /// Load the extension at the given path, optionally providing a specific entry point.
97    ///
98    /// # Safety
99    ///
100    /// Loading libraries can cause undefined behavior in safe code. It is the caller's
101    /// responsibility to ensure that the extension is compatible with sqlite3_ext. In
102    /// particular, the caller must verify that the extension being loaded (and the entry
103    /// point being invoked) is actually an SQLite extension (created with sqlite3_ext or
104    /// otherwise). Never invoke this method with untrusted user-specified data.
105    ///
106    /// If extension loading is not enabled when this method is called and SQLite is at
107    /// least version 3.13.0, then extension loading will temporarily be enabled before
108    /// loading the extension, and disabled afterwards. On older versions of SQLite,
109    /// extension loading must be manually enabled using unsafe ffi functions before this
110    /// method can be used, see
111    /// [sqlite3_enable_load_extension](https://www.sqlite.org/c3ref/enable_load_extension.html)
112    /// for details.
113    ///
114    /// Requires SQLite 3.8.7.
115    pub fn load_extension(&self, path: &str, entry: Option<&str>) -> Result<()> {
116        let _ = (path, entry);
117        sqlite3_require_version!(3_008_007, {
118            let guard = self.lock();
119            LoadExtensionGuard::new(&guard)?;
120            unsafe {
121                let mut err: MaybeUninit<*mut i8> = MaybeUninit::uninit();
122                let path = CString::new(path)?;
123                let entry = match entry {
124                    Some(s) => Some(CString::new(s)?),
125                    None => None,
126                };
127                let rc = ffi::sqlite3_load_extension(
128                    guard.as_mut_ptr(),
129                    path.as_ptr(),
130                    entry.map_or_else(|| null(), |s| s.as_ptr()),
131                    err.as_mut_ptr(),
132                );
133                if rc != ffi::SQLITE_OK {
134                    let err = NonNull::new(err.assume_init()).and_then(|err| {
135                        let ret = CStr::from_ptr(err.as_ptr()).to_str().ok().map(String::from);
136                        ffi::sqlite3_free(err.as_ptr() as _);
137                        ret
138                    });
139                    Err(Error::Sqlite(rc, err))
140                } else {
141                    Ok(())
142                }
143            }
144        })
145    }
146
147    /// Enable or disable the "defensive" flag for the database.
148    ///
149    /// See
150    /// [SQLITE_DBCONFIG_DEFENSIVE](https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigdefensive)
151    /// for details.
152    ///
153    /// Requires SQLite 3.26.0. On earlier versions, this method is a no-op.
154    pub fn db_config_defensive(&self, enable: bool) -> Result<()> {
155        let _ = enable;
156        sqlite3_match_version! {
157            3_026_000 => unsafe {
158                Error::from_sqlite_desc_unchecked(
159                    ffi::sqlite3_db_config()(
160                        self.as_mut_ptr(),
161                        ffi::SQLITE_DBCONFIG_DEFENSIVE,
162                        enable as i32,
163                        0 as i32,
164                    ),
165                    self.as_mut_ptr(),
166                )
167            },
168            _ => Ok(()),
169        }
170    }
171
172    /// Prints the text of all currently prepared statements to stderr. Intended for
173    /// debugging.
174    pub fn dump_prepared_statements(&self) {
175        unsafe {
176            let mut stmt = ffi::sqlite3_next_stmt(self.as_mut_ptr(), std::ptr::null_mut());
177            while !stmt.is_null() {
178                let cstr = CStr::from_ptr(ffi::sqlite3_sql(stmt)).to_str();
179                match cstr {
180                    Ok(cstr) => eprintln!("=> {cstr}"),
181                    Err(e) => eprintln!("{stmt:?}: invalid SQL: {e}"),
182                }
183                stmt = ffi::sqlite3_next_stmt(self.as_mut_ptr(), stmt);
184            }
185        }
186    }
187}
188
189impl std::fmt::Debug for Connection {
190    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
191        f.debug_struct("Connection").finish_non_exhaustive()
192    }
193}
194
195#[cfg(unix)]
196fn path_to_cstring(path: &Path) -> CString {
197    use std::os::unix::ffi::OsStrExt;
198    CString::new(path.as_os_str().as_bytes()).unwrap()
199}
200
201/// Represents an owned connection to an SQLite database.
202///
203/// This struct is an owned version of [Connection]. When this struct is dropped, it will close
204/// the underlying connection to SQLite.
205pub struct Database {
206    db: *mut ffi::sqlite3,
207}
208
209impl Database {
210    pub fn open<P: AsRef<Path>>(path: P) -> Result<Database> {
211        let filename = path_to_cstring(path.as_ref());
212        Database::_open(filename.as_c_str(), OpenFlags::DEFAULT)
213    }
214
215    pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: OpenFlags) -> Result<Database> {
216        let filename = path_to_cstring(path.as_ref());
217        Database::_open(filename.as_c_str(), flags)
218    }
219
220    fn _open(filename: &CStr, flags: OpenFlags) -> Result<Database> {
221        let mut db = MaybeUninit::uninit();
222        let rc = Error::from_sqlite(unsafe {
223            ffi::sqlite3_open_v2(
224                filename.as_ptr() as _,
225                db.as_mut_ptr(),
226                flags.bits,
227                null_mut(),
228            )
229        });
230        match rc {
231            Ok(()) => Ok(Database {
232                db: unsafe { *db.as_ptr() },
233            }),
234            Err(e) => {
235                if !db.as_ptr().is_null() {
236                    // Panic if we can't close the database we failed to open
237                    Error::from_sqlite(unsafe { ffi::sqlite3_close(*db.as_ptr()) }).unwrap();
238                }
239                Err(e)
240            }
241        }
242    }
243
244    /// Gracefully close the database. This automatically happens when the Database is
245    /// dropped, but a failure in drop will result in a panic, while this method provides a
246    /// path for graceful error handling.
247    pub fn close(mut self) -> std::result::Result<(), (Error, Database)> {
248        match self._close() {
249            Ok(()) => Ok(()),
250            Err(e) => Err((e, self)),
251        }
252    }
253
254    fn _close(&mut self) -> Result<()> {
255        Error::from_sqlite(unsafe { ffi::sqlite3_close(self.db) })?;
256        self.db = null_mut();
257        Ok(())
258    }
259}
260
261impl std::fmt::Debug for Database {
262    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
263        f.debug_struct("Database").finish_non_exhaustive()
264    }
265}
266
267impl Drop for Database {
268    fn drop(&mut self) {
269        if let Err(e) = self._close() {
270            if panicking() {
271                eprintln!("Error while closing SQLite connection: {e:?}");
272            } else {
273                panic!("Error while closing SQLite connection: {e:?}");
274            }
275        }
276    }
277}
278
279impl Deref for Database {
280    type Target = Connection;
281
282    fn deref(&self) -> &Connection {
283        unsafe { Connection::from_ptr(self.db) }
284    }
285}
286
287impl DerefMut for Database {
288    fn deref_mut(&mut self) -> &mut Connection {
289        unsafe { Connection::from_ptr(self.db) }
290    }
291}
292
293#[cfg(modern_sqlite)]
294struct LoadExtensionGuard<'a> {
295    db: &'a SQLiteMutexGuard<'a, Connection>,
296    was_enabled: bool,
297}
298
299#[cfg(modern_sqlite)]
300impl<'a> LoadExtensionGuard<'a> {
301    pub fn new(db: &'a SQLiteMutexGuard<'a, Connection>) -> Result<Self> {
302        unsafe {
303            let was_enabled = sqlite3_match_version! {
304                3_013_000 => {
305                    let mut was_enabled: MaybeUninit<c_int> = MaybeUninit::uninit();
306                    Error::from_sqlite(ffi::sqlite3_db_config()(
307                        db.as_mut_ptr(),
308                        ffi::SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
309                        -1,
310                        was_enabled.as_mut_ptr(),
311                    ))?;
312                    Error::from_sqlite(ffi::sqlite3_db_config()(
313                        db.as_mut_ptr(),
314                        ffi::SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
315                        1,
316                        0,
317                    ))?;
318                    was_enabled.assume_init() != 0
319                }
320                _ => true,
321            };
322            Ok(Self { db, was_enabled })
323        }
324    }
325}
326
327#[cfg(modern_sqlite)]
328impl Drop for LoadExtensionGuard<'_> {
329    fn drop(&mut self) {
330        if !self.was_enabled {
331            sqlite3_match_version! {
332                3_013_000 => unsafe {
333                    Error::from_sqlite(ffi::sqlite3_db_config()(
334                        self.db.as_mut_ptr(),
335                        ffi::SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
336                        0,
337                        0,
338                    ))
339                    .unwrap();
340                },
341                _ => (),
342            }
343        }
344    }
345}