| use std::ffi::CStr; |
| use std::os::raw::{c_char, c_int}; |
| #[cfg(feature = "load_extension")] |
| use std::path::Path; |
| use std::ptr; |
| use std::str; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| use std::sync::{Arc, Mutex}; |
| |
| use super::ffi; |
| use super::str_for_sqlite; |
| use super::{Connection, InterruptHandle, OpenFlags, Result}; |
| use crate::error::{error_from_handle, error_from_sqlite_code, error_with_offset, Error}; |
| use crate::raw_statement::RawStatement; |
| use crate::statement::Statement; |
| use crate::version::version_number; |
| |
| pub struct InnerConnection { |
| pub db: *mut ffi::sqlite3, |
| // It's unsafe to call `sqlite3_close` while another thread is performing |
| // a `sqlite3_interrupt`, and vice versa, so we take this mutex during |
| // those functions. This protects a copy of the `db` pointer (which is |
| // cleared on closing), however the main copy, `db`, is unprotected. |
| // Otherwise, a long running query would prevent calling interrupt, as |
| // interrupt would only acquire the lock after the query's completion. |
| interrupt_lock: Arc<Mutex<*mut ffi::sqlite3>>, |
| #[cfg(feature = "hooks")] |
| pub free_commit_hook: Option<unsafe fn(*mut std::os::raw::c_void)>, |
| #[cfg(feature = "hooks")] |
| pub free_rollback_hook: Option<unsafe fn(*mut std::os::raw::c_void)>, |
| #[cfg(feature = "hooks")] |
| pub free_update_hook: Option<unsafe fn(*mut std::os::raw::c_void)>, |
| #[cfg(feature = "hooks")] |
| pub progress_handler: Option<Box<dyn FnMut() -> bool + Send>>, |
| #[cfg(feature = "hooks")] |
| pub authorizer: Option<crate::hooks::BoxedAuthorizer>, |
| owned: bool, |
| } |
| |
| unsafe impl Send for InnerConnection {} |
| |
| impl InnerConnection { |
| #[allow(clippy::mutex_atomic)] |
| #[inline] |
| pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection { |
| InnerConnection { |
| db, |
| interrupt_lock: Arc::new(Mutex::new(db)), |
| #[cfg(feature = "hooks")] |
| free_commit_hook: None, |
| #[cfg(feature = "hooks")] |
| free_rollback_hook: None, |
| #[cfg(feature = "hooks")] |
| free_update_hook: None, |
| #[cfg(feature = "hooks")] |
| progress_handler: None, |
| #[cfg(feature = "hooks")] |
| authorizer: None, |
| owned, |
| } |
| } |
| |
| pub fn open_with_flags( |
| c_path: &CStr, |
| flags: OpenFlags, |
| vfs: Option<&CStr>, |
| ) -> Result<InnerConnection> { |
| ensure_safe_sqlite_threading_mode()?; |
| |
| // Replicate the check for sane open flags from SQLite, because the check in |
| // SQLite itself wasn't added until version 3.7.3. |
| debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits(), 0x02); |
| debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits(), 0x04); |
| debug_assert_eq!( |
| 1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits(), |
| 0x40 |
| ); |
| if (1 << (flags.bits() & 0x7)) & 0x46 == 0 { |
| return Err(Error::SqliteFailure( |
| ffi::Error::new(ffi::SQLITE_MISUSE), |
| None, |
| )); |
| } |
| |
| let z_vfs = match vfs { |
| Some(c_vfs) => c_vfs.as_ptr(), |
| None => ptr::null(), |
| }; |
| |
| unsafe { |
| let mut db: *mut ffi::sqlite3 = ptr::null_mut(); |
| let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), z_vfs); |
| if r != ffi::SQLITE_OK { |
| let e = if db.is_null() { |
| error_from_sqlite_code(r, Some(c_path.to_string_lossy().to_string())) |
| } else { |
| let mut e = error_from_handle(db, r); |
| if let Error::SqliteFailure( |
| ffi::Error { |
| code: ffi::ErrorCode::CannotOpen, |
| .. |
| }, |
| Some(msg), |
| ) = e |
| { |
| e = Error::SqliteFailure( |
| ffi::Error::new(r), |
| Some(format!("{msg}: {}", c_path.to_string_lossy())), |
| ); |
| } |
| ffi::sqlite3_close(db); |
| e |
| }; |
| |
| return Err(e); |
| } |
| |
| // attempt to turn on extended results code; don't fail if we can't. |
| ffi::sqlite3_extended_result_codes(db, 1); |
| |
| let r = ffi::sqlite3_busy_timeout(db, 5000); |
| if r != ffi::SQLITE_OK { |
| let e = error_from_handle(db, r); |
| ffi::sqlite3_close(db); |
| return Err(e); |
| } |
| |
| Ok(InnerConnection::new(db, true)) |
| } |
| } |
| |
| #[inline] |
| pub fn db(&self) -> *mut ffi::sqlite3 { |
| self.db |
| } |
| |
| #[inline] |
| pub fn decode_result(&self, code: c_int) -> Result<()> { |
| unsafe { InnerConnection::decode_result_raw(self.db(), code) } |
| } |
| |
| #[inline] |
| unsafe fn decode_result_raw(db: *mut ffi::sqlite3, code: c_int) -> Result<()> { |
| if code == ffi::SQLITE_OK { |
| Ok(()) |
| } else { |
| Err(error_from_handle(db, code)) |
| } |
| } |
| |
| #[allow(clippy::mutex_atomic)] |
| pub fn close(&mut self) -> Result<()> { |
| if self.db.is_null() { |
| return Ok(()); |
| } |
| self.remove_hooks(); |
| let mut shared_handle = self.interrupt_lock.lock().unwrap(); |
| assert!( |
| !shared_handle.is_null(), |
| "Bug: Somehow interrupt_lock was cleared before the DB was closed" |
| ); |
| if !self.owned { |
| self.db = ptr::null_mut(); |
| return Ok(()); |
| } |
| unsafe { |
| let r = ffi::sqlite3_close(self.db); |
| // Need to use _raw because _guard has a reference out, and |
| // decode_result takes &mut self. |
| let r = InnerConnection::decode_result_raw(self.db, r); |
| if r.is_ok() { |
| *shared_handle = ptr::null_mut(); |
| self.db = ptr::null_mut(); |
| } |
| r |
| } |
| } |
| |
| #[inline] |
| pub fn get_interrupt_handle(&self) -> InterruptHandle { |
| InterruptHandle { |
| db_lock: Arc::clone(&self.interrupt_lock), |
| } |
| } |
| |
| #[inline] |
| #[cfg(feature = "load_extension")] |
| pub unsafe fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> { |
| let r = ffi::sqlite3_enable_load_extension(self.db, onoff); |
| self.decode_result(r) |
| } |
| |
| #[cfg(feature = "load_extension")] |
| pub unsafe fn load_extension( |
| &self, |
| dylib_path: &Path, |
| entry_point: Option<&str>, |
| ) -> Result<()> { |
| let dylib_str = super::path_to_cstring(dylib_path)?; |
| let mut errmsg: *mut c_char = ptr::null_mut(); |
| let r = if let Some(entry_point) = entry_point { |
| let c_entry = crate::str_to_cstring(entry_point)?; |
| ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), c_entry.as_ptr(), &mut errmsg) |
| } else { |
| ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg) |
| }; |
| if r == ffi::SQLITE_OK { |
| Ok(()) |
| } else { |
| let message = super::errmsg_to_string(errmsg); |
| ffi::sqlite3_free(errmsg.cast::<std::os::raw::c_void>()); |
| Err(error_from_sqlite_code(r, Some(message))) |
| } |
| } |
| |
| #[inline] |
| pub fn last_insert_rowid(&self) -> i64 { |
| unsafe { ffi::sqlite3_last_insert_rowid(self.db()) } |
| } |
| |
| pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> { |
| let mut c_stmt = ptr::null_mut(); |
| let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?; |
| let mut c_tail = ptr::null(); |
| // TODO sqlite3_prepare_v3 (https://sqlite.org/c3ref/c_prepare_normalize.html) // 3.20.0, #728 |
| #[cfg(not(feature = "unlock_notify"))] |
| let r = unsafe { |
| ffi::sqlite3_prepare_v2( |
| self.db(), |
| c_sql, |
| len, |
| &mut c_stmt as *mut *mut ffi::sqlite3_stmt, |
| &mut c_tail as *mut *const c_char, |
| ) |
| }; |
| #[cfg(feature = "unlock_notify")] |
| let r = unsafe { |
| use crate::unlock_notify; |
| let mut rc; |
| loop { |
| rc = ffi::sqlite3_prepare_v2( |
| self.db(), |
| c_sql, |
| len, |
| &mut c_stmt as *mut *mut ffi::sqlite3_stmt, |
| &mut c_tail as *mut *const c_char, |
| ); |
| if !unlock_notify::is_locked(self.db, rc) { |
| break; |
| } |
| rc = unlock_notify::wait_for_unlock_notify(self.db); |
| if rc != ffi::SQLITE_OK { |
| break; |
| } |
| } |
| rc |
| }; |
| // If there is an error, *ppStmt is set to NULL. |
| if r != ffi::SQLITE_OK { |
| return Err(unsafe { error_with_offset(self.db, r, sql) }); |
| } |
| // If the input text contains no SQL (if the input is an empty string or a |
| // comment) then *ppStmt is set to NULL. |
| let c_stmt: *mut ffi::sqlite3_stmt = c_stmt; |
| let c_tail: *const c_char = c_tail; |
| let tail = if c_tail.is_null() { |
| 0 |
| } else { |
| let n = (c_tail as isize) - (c_sql as isize); |
| if n <= 0 || n >= len as isize { |
| 0 |
| } else { |
| n as usize |
| } |
| }; |
| Ok(Statement::new(conn, unsafe { |
| RawStatement::new(c_stmt, tail) |
| })) |
| } |
| |
| #[inline] |
| pub fn changes(&self) -> u64 { |
| #[cfg(not(feature = "modern_sqlite"))] |
| unsafe { |
| ffi::sqlite3_changes(self.db()) as u64 |
| } |
| #[cfg(feature = "modern_sqlite")] // 3.37.0 |
| unsafe { |
| ffi::sqlite3_changes64(self.db()) as u64 |
| } |
| } |
| |
| #[inline] |
| pub fn is_autocommit(&self) -> bool { |
| unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 } |
| } |
| |
| pub fn is_busy(&self) -> bool { |
| let db = self.db(); |
| unsafe { |
| let mut stmt = ffi::sqlite3_next_stmt(db, ptr::null_mut()); |
| while !stmt.is_null() { |
| if ffi::sqlite3_stmt_busy(stmt) != 0 { |
| return true; |
| } |
| stmt = ffi::sqlite3_next_stmt(db, stmt); |
| } |
| } |
| false |
| } |
| |
| pub fn cache_flush(&mut self) -> Result<()> { |
| crate::error::check(unsafe { ffi::sqlite3_db_cacheflush(self.db()) }) |
| } |
| |
| #[cfg(not(feature = "hooks"))] |
| #[inline] |
| fn remove_hooks(&mut self) {} |
| |
| pub fn db_readonly(&self, db_name: super::DatabaseName<'_>) -> Result<bool> { |
| let name = db_name.as_cstring()?; |
| let r = unsafe { ffi::sqlite3_db_readonly(self.db, name.as_ptr()) }; |
| match r { |
| 0 => Ok(false), |
| 1 => Ok(true), |
| -1 => Err(Error::SqliteFailure( |
| ffi::Error::new(ffi::SQLITE_MISUSE), |
| Some(format!("{db_name:?} is not the name of a database")), |
| )), |
| _ => Err(error_from_sqlite_code( |
| r, |
| Some("Unexpected result".to_owned()), |
| )), |
| } |
| } |
| |
| #[cfg(feature = "modern_sqlite")] // 3.37.0 |
| pub fn txn_state( |
| &self, |
| db_name: Option<super::DatabaseName<'_>>, |
| ) -> Result<super::transaction::TransactionState> { |
| let r = if let Some(ref name) = db_name { |
| let name = name.as_cstring()?; |
| unsafe { ffi::sqlite3_txn_state(self.db, name.as_ptr()) } |
| } else { |
| unsafe { ffi::sqlite3_txn_state(self.db, ptr::null()) } |
| }; |
| match r { |
| 0 => Ok(super::transaction::TransactionState::None), |
| 1 => Ok(super::transaction::TransactionState::Read), |
| 2 => Ok(super::transaction::TransactionState::Write), |
| -1 => Err(Error::SqliteFailure( |
| ffi::Error::new(ffi::SQLITE_MISUSE), |
| Some(format!("{db_name:?} is not the name of a valid schema")), |
| )), |
| _ => Err(error_from_sqlite_code( |
| r, |
| Some("Unexpected result".to_owned()), |
| )), |
| } |
| } |
| |
| #[inline] |
| #[cfg(feature = "release_memory")] |
| pub fn release_memory(&self) -> Result<()> { |
| self.decode_result(unsafe { ffi::sqlite3_db_release_memory(self.db) }) |
| } |
| } |
| |
| impl Drop for InnerConnection { |
| #[allow(unused_must_use)] |
| #[inline] |
| fn drop(&mut self) { |
| self.close(); |
| } |
| } |
| |
| #[cfg(not(any(target_arch = "wasm32")))] |
| static SQLITE_INIT: std::sync::Once = std::sync::Once::new(); |
| |
| pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false); |
| |
| // threading mode checks are not necessary (and do not work) on target |
| // platforms that do not have threading (such as webassembly) |
| #[cfg(any(target_arch = "wasm32"))] |
| fn ensure_safe_sqlite_threading_mode() -> Result<()> { |
| Ok(()) |
| } |
| |
| #[cfg(not(any(target_arch = "wasm32")))] |
| fn ensure_safe_sqlite_threading_mode() -> Result<()> { |
| // Ensure SQLite was compiled in threadsafe mode. |
| if unsafe { ffi::sqlite3_threadsafe() == 0 } { |
| return Err(Error::SqliteSingleThreadedMode); |
| } |
| |
| // Now we know SQLite is _capable_ of being in Multi-thread of Serialized mode, |
| // but it's possible someone configured it to be in Single-thread mode |
| // before calling into us. That would mean we're exposing an unsafe API via |
| // a safe one (in Rust terminology), which is no good. We have two options |
| // to protect against this, depending on the version of SQLite we're linked |
| // with: |
| // |
| // 1. If we're on 3.7.0 or later, we can ask SQLite for a mutex and check for |
| // the magic value 8. This isn't documented, but it's what SQLite |
| // returns for its mutex allocation function in Single-thread mode. |
| // 2. If we're prior to SQLite 3.7.0, AFAIK there's no way to check the |
| // threading mode. The check we perform for >= 3.7.0 will segfault. |
| // Instead, we insist on being able to call sqlite3_config and |
| // sqlite3_initialize ourself, ensuring we know the threading |
| // mode. This will fail if someone else has already initialized SQLite |
| // even if they initialized it safely. That's not ideal either, which is |
| // why we expose bypass_sqlite_initialization above. |
| if version_number() >= 3_007_000 { |
| const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8; |
| let is_singlethreaded = unsafe { |
| let mutex_ptr = ffi::sqlite3_mutex_alloc(0); |
| let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC; |
| ffi::sqlite3_mutex_free(mutex_ptr); |
| is_singlethreaded |
| }; |
| if is_singlethreaded { |
| Err(Error::SqliteSingleThreadedMode) |
| } else { |
| Ok(()) |
| } |
| } else { |
| SQLITE_INIT.call_once(|| { |
| if BYPASS_SQLITE_INIT.load(Ordering::Relaxed) { |
| return; |
| } |
| |
| unsafe { |
| assert!(ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) == ffi::SQLITE_OK && ffi::sqlite3_initialize() == ffi::SQLITE_OK, |
| "Could not ensure safe initialization of SQLite.\n\ |
| To fix this, either:\n\ |
| * Upgrade SQLite to at least version 3.7.0\n\ |
| * Ensure that SQLite has been initialized in Multi-thread or Serialized mode and call\n\ |
| rusqlite::bypass_sqlite_initialization() prior to your first connection attempt." |
| ); |
| } |
| }); |
| Ok(()) |
| } |
| } |