diff options
Diffstat (limited to 'third_party/rust/rusqlite/src/inner_connection.rs')
-rw-r--r-- | third_party/rust/rusqlite/src/inner_connection.rs | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/third_party/rust/rusqlite/src/inner_connection.rs b/third_party/rust/rusqlite/src/inner_connection.rs new file mode 100644 index 0000000000..e5bc3f1acf --- /dev/null +++ b/third_party/rust/rusqlite/src/inner_connection.rs @@ -0,0 +1,456 @@ +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 } + } + + #[cfg(feature = "modern_sqlite")] // 3.8.6 + 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 + } + + #[cfg(feature = "modern_sqlite")] // 3.10.0 + 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) {} + + #[cfg(feature = "modern_sqlite")] // 3.7.11 + 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!("{:?} is not the name of a database", db_name)), + )), + _ => 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!("{:?} is not the name of a valid schema", db_name)), + )), + _ => 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) { + use std::thread::panicking; + + if let Err(e) = self.close() { + if panicking() { + eprintln!("Error while closing SQLite connection: {:?}", e); + } else { + panic!("Error while closing SQLite connection: {:?}", e); + } + } + } +} + +#[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(()) + } +} |