diff --git a/Cargo.toml b/Cargo.toml index 672215d..4d55031 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rusqlite" -version = "0.9.5" +version = "0.10.0" authors = ["John Gallagher "] description = "Ergonomic wrapper for SQLite" repository = "https://github.com/jgallagher/rusqlite" diff --git a/Changelog.md b/Changelog.md index 9bdba9c..5d09688 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,8 +1,13 @@ -# Version UPCOMING (TBD) +# Version 0.10.0-UPCOMING (TBD) * Re-export the `ErrorCode` enum from `libsqlite3-sys`. -* Adds `version()`, `version_number()`, and `source_id()` functions for querying the version of - SQLite in use. +* Adds `version()` and `version_number()` functions for querying the version of SQLite in use. +* Adds the `limits` feature, exposing `limit()` and `set_limit()` methods on `Connection`. +* Updates to `libsqlite3-sys` 0.7.0, which runs rust-bindgen at build-time instead of assuming the + precense of all expected SQLite constants and functions. +* Clarifies supported SQLite versions. Running with SQLite older than 3.6.8 now panics, and + some features will not compile unless a sufficiently-recent SQLite version is used. See + the README for requirements of particular features. # Version 0.9.5 (2017-01-26) diff --git a/README.md b/README.md index d480736..0efda79 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,12 @@ fn main() { } ``` +### Supported SQLite Versions + +The base `rusqlite` package supports SQLite version 3.6.8 or newer. If you need +support for older versions, please file an issue. Some cargo features require a +newer SQLite version; see details below. + ### Optional Features Rusqlite provides several features that are behind [Cargo @@ -65,12 +71,13 @@ features](http://doc.crates.io/manifest.html#the-features-section). They are: * [`load_extension`](http://jgallagher.github.io/rusqlite/rusqlite/struct.LoadExtensionGuard.html) allows loading dynamic library-based SQLite extensions. * [`backup`](http://jgallagher.github.io/rusqlite/rusqlite/backup/index.html) - allows use of SQLite's online backup API. + allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later. * [`functions`](http://jgallagher.github.io/rusqlite/rusqlite/functions/index.html) allows you to load Rust closures into SQLite connections for use in queries. Note: This feature requires SQLite 3.7.3 or later. * [`trace`](http://jgallagher.github.io/rusqlite/rusqlite/trace/index.html) - allows hooks into SQLite's tracing and profiling APIs. + allows hooks into SQLite's tracing and profiling APIs. Note: This feature + requires SQLite 3.6.23 or later. * [`blob`](http://jgallagher.github.io/rusqlite/rusqlite/blob/index.html) gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature requires SQLite 3.7.4 or later. diff --git a/src/lib.rs b/src/lib.rs index cb15ee6..0671cf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,8 @@ use std::cell::RefCell; use std::ffi::{CStr, CString}; use std::result; use std::str; +use std::sync::{Once, ONCE_INIT}; +use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; use libc::{c_int, c_char, c_void}; use types::{ToSql, ToSqlOutput, FromSql, FromSqlError, ValueRef}; @@ -551,29 +553,109 @@ impl Default for OpenFlags { } } -impl InnerConnection { - fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result { - unsafe { - // Before opening the database, we need to check that SQLite hasn't been - // compiled or configured to be in single-threaded mode. If it has, we're - // exposing a very unsafe API to Rust, so refuse to open connections at all. - // Unfortunately, the check for this is quite gross. sqlite3_threadsafe() only - // returns how SQLite was _compiled_; there is no public API to check whether - // someone called sqlite3_config() to set single-threaded mode. We can cheat - // by trying to allocate a mutex, though; in single-threaded mode due to - // compilation settings, the magic value 8 is returned (see the definition of - // sqlite3_mutex_alloc at https://github.com/mackyle/sqlite/blob/master/src/mutex.h); - // in single-threaded mode due to sqlite3_config(), the magic value 8 is also - // returned (see the definition of noopMutexAlloc at - // https://github.com/mackyle/sqlite/blob/master/src/mutex_noop.c). - const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8; +static SQLITE_INIT: Once = ONCE_INIT; +static SQLITE_VERSION_CHECK: Once = ONCE_INIT; +static BYPASS_SQLITE_INIT: AtomicBool = ATOMIC_BOOL_INIT; + +/// 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 attempts to ensure safety by performing +/// configuration and initialization of SQLite itself the first time you attempt to open a +/// connection. By default, rusqlite panics if that initialization fails, since that could mean +/// SQLite has been initialized in single-thread mode. +/// +/// If you are encountering that panic _and_ can ensure that SQLite has been initialized in either +/// multi-thread or serialized mode, call this function prior to attempting to open a connection +/// and rusqlite's initialization process will by skipped. This function is unsafe because if you +/// call it and SQLite has actually been configured to run in single-thread mode, you may enounter +/// memory errors or data corruption or any number of terrible things that should not be possible +/// when you're using Rust. +pub unsafe fn bypass_sqlite_initialization() { + BYPASS_SQLITE_INIT.store(true, Ordering::Relaxed); +} + +fn ensure_valid_sqlite_version() { + SQLITE_VERSION_CHECK.call_once(|| { + if version_number() < 3006008 { + panic!("rusqlite requires SQLite 3.6.8 or newer"); + } + }); +} + +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() >= 3007000 { + 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); - if is_singlethreaded { - return Err(Error::SqliteSingleThreadedMode); + 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 { + fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result { + 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!(1 << SQLITE_OPEN_READ_ONLY.bits == 0x02); + debug_assert!(1 << SQLITE_OPEN_READ_WRITE.bits == 0x04); + debug_assert!(1 << (SQLITE_OPEN_READ_WRITE|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 { @@ -1533,7 +1615,7 @@ mod test { let minor = (n % 1_000_000) / 1_000; let patch = n % 1_000; - assert_eq!(version(), format!("{}.{}.{}", major, minor, patch)); + assert!(version().contains(&format!("{}.{}.{}", major, minor, patch))); } mod query_and_then_tests { diff --git a/src/limits.rs b/src/limits.rs index 1ceac88..5a9866e 100644 --- a/src/limits.rs +++ b/src/limits.rs @@ -56,8 +56,11 @@ mod test { db.set_limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER, 99); assert_eq!(99, db.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)); - db.set_limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH, 32); - assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH)); + // SQLITE_LIMIT_TRIGGER_DEPTH was added in SQLite 3.6.18. + if ::version_number() >= 3006018 { + db.set_limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH, 32); + assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH)); + } // SQLITE_LIMIT_WORKER_THREADS was added in SQLite 3.8.7. if ::version_number() >= 3008007 { diff --git a/src/version.rs b/src/version.rs index 4051770..e7d8522 100644 --- a/src/version.rs +++ b/src/version.rs @@ -15,11 +15,3 @@ pub fn version() -> &'static str { let cstr = unsafe { CStr::from_ptr(ffi::sqlite3_libversion()) }; cstr.to_str().expect("SQLite version string is not valid UTF8 ?!") } - -/// Returns the source ID of SQLite, identifying the commit of SQLite for the current version. -/// -/// See [sqlite3_sourceid()](https://www.sqlite.org/c3ref/libversion.html). -pub fn source_id() -> &'static str { - let cstr = unsafe { CStr::from_ptr(ffi::sqlite3_sourceid()) }; - cstr.to_str().expect("SQLite source ID is not valid UTF8 ?!") -} diff --git a/tests/deny_single_threaded_sqlite_config.rs b/tests/deny_single_threaded_sqlite_config.rs index 8178739..5580520 100644 --- a/tests/deny_single_threaded_sqlite_config.rs +++ b/tests/deny_single_threaded_sqlite_config.rs @@ -7,14 +7,17 @@ extern crate libsqlite3_sys as ffi; use rusqlite::Connection; #[test] +#[should_panic] fn test_error_when_singlethread_mode() { // put SQLite into single-threaded mode unsafe { - // 1 == SQLITE_CONFIG_SINGLETHREAD - assert_eq!(ffi::sqlite3_config(1), ffi::SQLITE_OK); - println!("{}", ffi::sqlite3_mutex_alloc(0) as u64); + if ffi::sqlite3_config(ffi::SQLITE_CONFIG_SINGLETHREAD) != ffi::SQLITE_OK { + return; + } + if ffi::sqlite3_initialize() != ffi::SQLITE_OK { + return; + } } - let result = Connection::open_in_memory(); - assert!(result.is_err()); + let _ = Connection::open_in_memory().unwrap(); }