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}