summaryrefslogtreecommitdiffstats
path: root/third_party/rust/rusqlite/src/inner_connection.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/rusqlite/src/inner_connection.rs')
-rw-r--r--third_party/rust/rusqlite/src/inner_connection.rs456
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(())
+ }
+}