mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-26 11:31:37 +08:00
Merge pull request #474 from gwenn/move-inner
Move InnerConnection in its own module
This commit is contained in:
commit
2f965fec5a
404
src/inner_connection.rs
Normal file
404
src/inner_connection.rs
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
use std::ffi::CString;
|
||||||
|
use std::mem;
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
#[cfg(feature = "load_extension")]
|
||||||
|
use std::path::Path;
|
||||||
|
use std::ptr;
|
||||||
|
use std::str;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
|
||||||
|
use std::sync::{Arc, Mutex, Once, ONCE_INIT};
|
||||||
|
|
||||||
|
use super::ffi;
|
||||||
|
use super::str_to_cstring;
|
||||||
|
use super::{Connection, InterruptHandle, OpenFlags, Result};
|
||||||
|
use crate::error::{error_from_handle, error_from_sqlite_code, Error};
|
||||||
|
use crate::raw_statement::RawStatement;
|
||||||
|
use crate::statement::Statement;
|
||||||
|
use crate::unlock_notify;
|
||||||
|
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<fn(*mut ::std::os::raw::c_void)>,
|
||||||
|
#[cfg(feature = "hooks")]
|
||||||
|
pub free_rollback_hook: Option<fn(*mut ::std::os::raw::c_void)>,
|
||||||
|
#[cfg(feature = "hooks")]
|
||||||
|
pub free_update_hook: Option<fn(*mut ::std::os::raw::c_void)>,
|
||||||
|
owned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerConnection {
|
||||||
|
#[cfg(not(feature = "hooks"))]
|
||||||
|
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
||||||
|
InnerConnection {
|
||||||
|
db,
|
||||||
|
interrupt_lock: Arc::new(Mutex::new(db)),
|
||||||
|
owned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hooks")]
|
||||||
|
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
||||||
|
InnerConnection {
|
||||||
|
db,
|
||||||
|
interrupt_lock: Arc::new(Mutex::new(db)),
|
||||||
|
free_commit_hook: None,
|
||||||
|
free_rollback_hook: None,
|
||||||
|
free_update_hook: None,
|
||||||
|
owned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result<InnerConnection> {
|
||||||
|
#[cfg(not(feature = "bundled"))]
|
||||||
|
ensure_valid_sqlite_version();
|
||||||
|
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,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
|
||||||
|
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
|
||||||
|
if r != ffi::SQLITE_OK {
|
||||||
|
let e = if db.is_null() {
|
||||||
|
error_from_sqlite_code(r, None)
|
||||||
|
} else {
|
||||||
|
let e = error_from_handle(db, r);
|
||||||
|
ffi::sqlite3_close(db);
|
||||||
|
e
|
||||||
|
};
|
||||||
|
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to turn on extended results code; don't fail if we can't.
|
||||||
|
ffi::sqlite3_extended_result_codes(db, 1);
|
||||||
|
|
||||||
|
Ok(InnerConnection::new(db, true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn db(&self) -> *mut ffi::sqlite3 {
|
||||||
|
self.db
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_result(&mut self, code: c_int) -> Result<()> {
|
||||||
|
InnerConnection::decode_result_raw(self.db(), code)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_interrupt_handle(&self) -> InterruptHandle {
|
||||||
|
InterruptHandle {
|
||||||
|
db_lock: Arc::clone(&self.interrupt_lock),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute_batch(&mut self, sql: &str) -> Result<()> {
|
||||||
|
let c_sql = str_to_cstring(sql)?;
|
||||||
|
unsafe {
|
||||||
|
let r = ffi::sqlite3_exec(
|
||||||
|
self.db(),
|
||||||
|
c_sql.as_ptr(),
|
||||||
|
None,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
);
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "load_extension")]
|
||||||
|
pub fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
|
||||||
|
let r = unsafe { ffi::sqlite3_enable_load_extension(self.db, onoff) };
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "load_extension")]
|
||||||
|
pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> {
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
let dylib_str = super::path_to_cstring(dylib_path)?;
|
||||||
|
unsafe {
|
||||||
|
let mut errmsg: *mut c_char = mem::uninitialized();
|
||||||
|
let r = if let Some(entry_point) = entry_point {
|
||||||
|
let c_entry = 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 as *mut ::std::os::raw::c_void);
|
||||||
|
Err(error_from_sqlite_code(r, Some(message)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>> {
|
||||||
|
if sql.len() >= ::std::i32::MAX as usize {
|
||||||
|
return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
|
||||||
|
}
|
||||||
|
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
|
||||||
|
let c_sql = str_to_cstring(sql)?;
|
||||||
|
let len_with_nul = (sql.len() + 1) as c_int;
|
||||||
|
let r = unsafe {
|
||||||
|
if cfg!(feature = "unlock_notify") {
|
||||||
|
let mut rc;
|
||||||
|
loop {
|
||||||
|
rc = ffi::sqlite3_prepare_v2(
|
||||||
|
self.db(),
|
||||||
|
c_sql.as_ptr(),
|
||||||
|
len_with_nul,
|
||||||
|
&mut c_stmt,
|
||||||
|
ptr::null_mut(),
|
||||||
|
);
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
ffi::sqlite3_prepare_v2(
|
||||||
|
self.db(),
|
||||||
|
c_sql.as_ptr(),
|
||||||
|
len_with_nul,
|
||||||
|
&mut c_stmt,
|
||||||
|
ptr::null_mut(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
.map(|_| Statement::new(conn, RawStatement::new(c_stmt)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn changes(&mut self) -> usize {
|
||||||
|
unsafe { ffi::sqlite3_changes(self.db()) as usize }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_autocommit(&self) -> bool {
|
||||||
|
unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bundled")] // 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(not(feature = "hooks"))]
|
||||||
|
fn remove_hooks(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for InnerConnection {
|
||||||
|
#[allow(unused_must_use)]
|
||||||
|
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(feature = "bundled"))]
|
||||||
|
static SQLITE_VERSION_CHECK: Once = ONCE_INIT;
|
||||||
|
#[cfg(not(feature = "bundled"))]
|
||||||
|
pub static BYPASS_VERSION_CHECK: AtomicBool = ATOMIC_BOOL_INIT;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "bundled"))]
|
||||||
|
fn ensure_valid_sqlite_version() {
|
||||||
|
use crate::version::version;
|
||||||
|
|
||||||
|
SQLITE_VERSION_CHECK.call_once(|| {
|
||||||
|
let version_number = version_number();
|
||||||
|
|
||||||
|
// Check our hard floor.
|
||||||
|
if version_number < 3_006_008 {
|
||||||
|
panic!("rusqlite requires SQLite 3.6.8 or newer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the major version number for runtime and buildtime match.
|
||||||
|
let buildtime_major = ffi::SQLITE_VERSION_NUMBER / 1_000_000;
|
||||||
|
let runtime_major = version_number / 1_000_000;
|
||||||
|
if buildtime_major != runtime_major {
|
||||||
|
panic!(
|
||||||
|
"rusqlite was built against SQLite {} but is running with SQLite {}",
|
||||||
|
str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
|
||||||
|
version()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if BYPASS_VERSION_CHECK.load(Ordering::Relaxed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the runtime version number is compatible with the version number
|
||||||
|
// we found at build-time.
|
||||||
|
if version_number < ffi::SQLITE_VERSION_NUMBER {
|
||||||
|
panic!(
|
||||||
|
"\
|
||||||
|
rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fix this, either:
|
||||||
|
* Recompile rusqlite and link against the SQLite version you are using at runtime, or
|
||||||
|
* Call rusqlite::bypass_sqlite_version_check() prior to your first connection attempt. Doing this
|
||||||
|
means you're sure everything will work correctly even though the runtime version is older than
|
||||||
|
the version we found at build time.",
|
||||||
|
str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
|
||||||
|
version()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static SQLITE_INIT: Once = ONCE_INIT;
|
||||||
|
pub static BYPASS_SQLITE_INIT: AtomicBool = ATOMIC_BOOL_INIT;
|
||||||
|
|
||||||
|
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
||||||
|
// Ensure SQLite was compiled in thredsafe 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 {
|
||||||
|
let msg = "\
|
||||||
|
Could not ensure safe initialization of SQLite.
|
||||||
|
To fix this, either:
|
||||||
|
* Upgrade SQLite to at least version 3.7.0
|
||||||
|
* Ensure that SQLite has been initialized in Multi-thread or Serialized mode and call
|
||||||
|
rusqlite::bypass_sqlite_initialization() prior to your first connection attempt.";
|
||||||
|
|
||||||
|
if ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) != ffi::SQLITE_OK {
|
||||||
|
panic!(msg);
|
||||||
|
}
|
||||||
|
if ffi::sqlite3_initialize() != ffi::SQLITE_OK {
|
||||||
|
panic!(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
410
src/lib.rs
410
src/lib.rs
@ -72,39 +72,31 @@ use std::convert;
|
|||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem;
|
|
||||||
use std::os::raw::{c_char, c_int};
|
use std::os::raw::{c_char, c_int};
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::ptr;
|
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::{Arc, Mutex, Once, ONCE_INIT};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::cache::StatementCache;
|
use crate::cache::StatementCache;
|
||||||
use crate::error::{error_from_handle, error_from_sqlite_code};
|
use crate::inner_connection::{InnerConnection, BYPASS_SQLITE_INIT};
|
||||||
use crate::raw_statement::RawStatement;
|
use crate::raw_statement::RawStatement;
|
||||||
use crate::types::ValueRef;
|
use crate::types::ValueRef;
|
||||||
|
|
||||||
pub use crate::types::ToSql;
|
pub use crate::cache::CachedStatement;
|
||||||
|
|
||||||
pub use crate::statement::{Statement, StatementStatus};
|
|
||||||
|
|
||||||
pub use crate::row::{AndThenRows, MappedRows, Row, RowIndex, Rows};
|
|
||||||
|
|
||||||
pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
|
|
||||||
|
|
||||||
pub use crate::error::Error;
|
pub use crate::error::Error;
|
||||||
pub use crate::ffi::ErrorCode;
|
pub use crate::ffi::ErrorCode;
|
||||||
|
|
||||||
pub use crate::cache::CachedStatement;
|
|
||||||
pub use crate::version::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "hooks")]
|
#[cfg(feature = "hooks")]
|
||||||
pub use crate::hooks::*;
|
pub use crate::hooks::*;
|
||||||
#[cfg(feature = "load_extension")]
|
#[cfg(feature = "load_extension")]
|
||||||
pub use crate::load_extension_guard::LoadExtensionGuard;
|
pub use crate::load_extension_guard::LoadExtensionGuard;
|
||||||
|
pub use crate::row::{AndThenRows, MappedRows, Row, RowIndex, Rows};
|
||||||
|
pub use crate::statement::{Statement, StatementStatus};
|
||||||
|
pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
|
||||||
|
pub use crate::types::ToSql;
|
||||||
|
pub use crate::version::*;
|
||||||
|
|
||||||
#[cfg(feature = "backup")]
|
#[cfg(feature = "backup")]
|
||||||
pub mod backup;
|
pub mod backup;
|
||||||
@ -120,6 +112,7 @@ mod error;
|
|||||||
pub mod functions;
|
pub mod functions;
|
||||||
#[cfg(feature = "hooks")]
|
#[cfg(feature = "hooks")]
|
||||||
mod hooks;
|
mod hooks;
|
||||||
|
mod inner_connection;
|
||||||
#[cfg(feature = "limits")]
|
#[cfg(feature = "limits")]
|
||||||
pub mod limits;
|
pub mod limits;
|
||||||
#[cfg(feature = "load_extension")]
|
#[cfg(feature = "load_extension")]
|
||||||
@ -720,24 +713,6 @@ impl fmt::Debug for Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InnerConnection {
|
|
||||||
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")]
|
|
||||||
free_commit_hook: Option<fn(*mut ::std::os::raw::c_void)>,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
free_rollback_hook: Option<fn(*mut ::std::os::raw::c_void)>,
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
free_update_hook: Option<fn(*mut ::std::os::raw::c_void)>,
|
|
||||||
owned: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[doc = "Flags for opening SQLite database connections."]
|
#[doc = "Flags for opening SQLite database connections."]
|
||||||
#[doc = "See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details."]
|
#[doc = "See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details."]
|
||||||
@ -764,13 +739,6 @@ impl Default for OpenFlags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static SQLITE_INIT: Once = ONCE_INIT;
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
|
||||||
static SQLITE_VERSION_CHECK: Once = ONCE_INIT;
|
|
||||||
static BYPASS_SQLITE_INIT: AtomicBool = ATOMIC_BOOL_INIT;
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
|
||||||
static BYPASS_VERSION_CHECK: AtomicBool = ATOMIC_BOOL_INIT;
|
|
||||||
|
|
||||||
/// rusqlite's check for a safe SQLite threading mode requires SQLite 3.7.0 or
|
/// rusqlite's check for a safe SQLite threading mode requires SQLite 3.7.0 or
|
||||||
/// later. If you are running against a SQLite older than that, rusqlite
|
/// later. If you are running against a SQLite older than that, rusqlite
|
||||||
/// attempts to ensure safety by performing configuration and initialization of
|
/// attempts to ensure safety by performing configuration and initialization of
|
||||||
@ -801,348 +769,7 @@ pub unsafe fn bypass_sqlite_initialization() {
|
|||||||
/// your first connection attempt.
|
/// your first connection attempt.
|
||||||
pub unsafe fn bypass_sqlite_version_check() {
|
pub unsafe fn bypass_sqlite_version_check() {
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "bundled"))]
|
||||||
BYPASS_VERSION_CHECK.store(true, Ordering::Relaxed);
|
inner_connection::BYPASS_VERSION_CHECK.store(true, Ordering::Relaxed);
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
|
||||||
fn ensure_valid_sqlite_version() {
|
|
||||||
SQLITE_VERSION_CHECK.call_once(|| {
|
|
||||||
let version_number = version_number();
|
|
||||||
|
|
||||||
// Check our hard floor.
|
|
||||||
if version_number < 3_006_008 {
|
|
||||||
panic!("rusqlite requires SQLite 3.6.8 or newer");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the major version number for runtime and buildtime match.
|
|
||||||
let buildtime_major = ffi::SQLITE_VERSION_NUMBER / 1_000_000;
|
|
||||||
let runtime_major = version_number / 1_000_000;
|
|
||||||
if buildtime_major != runtime_major {
|
|
||||||
panic!(
|
|
||||||
"rusqlite was built against SQLite {} but is running with SQLite {}",
|
|
||||||
str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
|
|
||||||
version()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if BYPASS_VERSION_CHECK.load(Ordering::Relaxed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the runtime version number is compatible with the version number
|
|
||||||
// we found at build-time.
|
|
||||||
if version_number < ffi::SQLITE_VERSION_NUMBER {
|
|
||||||
panic!(
|
|
||||||
"\
|
|
||||||
rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fix this, either:
|
|
||||||
* Recompile rusqlite and link against the SQLite version you are using at runtime, or
|
|
||||||
* Call rusqlite::bypass_sqlite_version_check() prior to your first connection attempt. Doing this
|
|
||||||
means you're sure everything will work correctly even though the runtime version is older than
|
|
||||||
the version we found at build time.",
|
|
||||||
str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
|
|
||||||
version()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
|
||||||
// Ensure SQLite was compiled in thredsafe 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 {
|
|
||||||
let msg = "\
|
|
||||||
Could not ensure safe initialization of SQLite.
|
|
||||||
To fix this, either:
|
|
||||||
* Upgrade SQLite to at least version 3.7.0
|
|
||||||
* Ensure that SQLite has been initialized in Multi-thread or Serialized mode and call
|
|
||||||
rusqlite::bypass_sqlite_initialization() prior to your first connection attempt.";
|
|
||||||
|
|
||||||
if ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) != ffi::SQLITE_OK {
|
|
||||||
panic!(msg);
|
|
||||||
}
|
|
||||||
if ffi::sqlite3_initialize() != ffi::SQLITE_OK {
|
|
||||||
panic!(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InnerConnection {
|
|
||||||
#[cfg(not(feature = "hooks"))]
|
|
||||||
fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
|
||||||
InnerConnection {
|
|
||||||
db,
|
|
||||||
interrupt_lock: Arc::new(Mutex::new(db)),
|
|
||||||
owned,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "hooks")]
|
|
||||||
fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
|
||||||
InnerConnection {
|
|
||||||
db,
|
|
||||||
interrupt_lock: Arc::new(Mutex::new(db)),
|
|
||||||
free_commit_hook: None,
|
|
||||||
free_rollback_hook: None,
|
|
||||||
free_update_hook: None,
|
|
||||||
owned,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result<InnerConnection> {
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
|
||||||
ensure_valid_sqlite_version();
|
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
|
|
||||||
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
|
|
||||||
if r != ffi::SQLITE_OK {
|
|
||||||
let e = if db.is_null() {
|
|
||||||
error_from_sqlite_code(r, None)
|
|
||||||
} else {
|
|
||||||
let e = error_from_handle(db, r);
|
|
||||||
ffi::sqlite3_close(db);
|
|
||||||
e
|
|
||||||
};
|
|
||||||
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// attempt to turn on extended results code; don't fail if we can't.
|
|
||||||
ffi::sqlite3_extended_result_codes(db, 1);
|
|
||||||
|
|
||||||
Ok(InnerConnection::new(db, true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn db(&self) -> *mut ffi::sqlite3 {
|
|
||||||
self.db
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_result(&mut self, code: c_int) -> Result<()> {
|
|
||||||
InnerConnection::decode_result_raw(self.db(), code)
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_interrupt_handle(&self) -> InterruptHandle {
|
|
||||||
InterruptHandle {
|
|
||||||
db_lock: Arc::clone(&self.interrupt_lock),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_batch(&mut self, sql: &str) -> Result<()> {
|
|
||||||
let c_sql = str_to_cstring(sql)?;
|
|
||||||
unsafe {
|
|
||||||
let r = ffi::sqlite3_exec(
|
|
||||||
self.db(),
|
|
||||||
c_sql.as_ptr(),
|
|
||||||
None,
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
self.decode_result(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "load_extension")]
|
|
||||||
fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
|
|
||||||
let r = unsafe { ffi::sqlite3_enable_load_extension(self.db, onoff) };
|
|
||||||
self.decode_result(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "load_extension")]
|
|
||||||
fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> {
|
|
||||||
let dylib_str = path_to_cstring(dylib_path)?;
|
|
||||||
unsafe {
|
|
||||||
let mut errmsg: *mut c_char = mem::uninitialized();
|
|
||||||
let r = if let Some(entry_point) = entry_point {
|
|
||||||
let c_entry = 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 = errmsg_to_string(&*errmsg);
|
|
||||||
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
|
||||||
Err(error_from_sqlite_code(r, Some(message)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn last_insert_rowid(&self) -> i64 {
|
|
||||||
unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
|
|
||||||
if sql.len() >= ::std::i32::MAX as usize {
|
|
||||||
return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
|
|
||||||
}
|
|
||||||
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
|
|
||||||
let c_sql = str_to_cstring(sql)?;
|
|
||||||
let len_with_nul = (sql.len() + 1) as c_int;
|
|
||||||
let r = unsafe {
|
|
||||||
if cfg!(feature = "unlock_notify") {
|
|
||||||
let mut rc;
|
|
||||||
loop {
|
|
||||||
rc = ffi::sqlite3_prepare_v2(
|
|
||||||
self.db(),
|
|
||||||
c_sql.as_ptr(),
|
|
||||||
len_with_nul,
|
|
||||||
&mut c_stmt,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
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
|
|
||||||
} else {
|
|
||||||
ffi::sqlite3_prepare_v2(
|
|
||||||
self.db(),
|
|
||||||
c_sql.as_ptr(),
|
|
||||||
len_with_nul,
|
|
||||||
&mut c_stmt,
|
|
||||||
ptr::null_mut(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.decode_result(r)
|
|
||||||
.map(|_| Statement::new(conn, RawStatement::new(c_stmt)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn changes(&mut self) -> usize {
|
|
||||||
unsafe { ffi::sqlite3_changes(self.db()) as usize }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_autocommit(&self) -> bool {
|
|
||||||
unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bundled")] // 3.8.6
|
|
||||||
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(not(feature = "hooks"))]
|
|
||||||
fn remove_hooks(&mut self) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows interrupting a long-running computation.
|
/// Allows interrupting a long-running computation.
|
||||||
@ -1164,21 +791,6 @@ impl InterruptHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for InnerConnection {
|
|
||||||
#[allow(unused_must_use)]
|
|
||||||
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(feature = "bundled")] // 3.7.10
|
#[cfg(feature = "bundled")] // 3.7.10
|
||||||
unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
|
unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
|
||||||
let db_name = DatabaseName::Main.to_cstring().unwrap();
|
let db_name = DatabaseName::Main.to_cstring().unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user