Merge pull request #233 from jgallagher/clarify-old-sqlite-version-support

Clarify support of older SQLite versions.
This commit is contained in:
John Gallagher 2017-02-09 20:38:37 -05:00 committed by GitHub
commit 7444f7b30a
7 changed files with 132 additions and 40 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rusqlite" name = "rusqlite"
version = "0.9.5" version = "0.10.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"] authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
description = "Ergonomic wrapper for SQLite" description = "Ergonomic wrapper for SQLite"
repository = "https://github.com/jgallagher/rusqlite" repository = "https://github.com/jgallagher/rusqlite"

View File

@ -1,8 +1,13 @@
# Version UPCOMING (TBD) # Version 0.10.0-UPCOMING (TBD)
* Re-export the `ErrorCode` enum from `libsqlite3-sys`. * Re-export the `ErrorCode` enum from `libsqlite3-sys`.
* Adds `version()`, `version_number()`, and `source_id()` functions for querying the version of * Adds `version()` and `version_number()` functions for querying the version of SQLite in use.
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) # Version 0.9.5 (2017-01-26)

View File

@ -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 ### Optional Features
Rusqlite provides several features that are behind [Cargo 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) * [`load_extension`](http://jgallagher.github.io/rusqlite/rusqlite/struct.LoadExtensionGuard.html)
allows loading dynamic library-based SQLite extensions. allows loading dynamic library-based SQLite extensions.
* [`backup`](http://jgallagher.github.io/rusqlite/rusqlite/backup/index.html) * [`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) * [`functions`](http://jgallagher.github.io/rusqlite/rusqlite/functions/index.html)
allows you to load Rust closures into SQLite connections for use in queries. allows you to load Rust closures into SQLite connections for use in queries.
Note: This feature requires SQLite 3.7.3 or later. Note: This feature requires SQLite 3.7.3 or later.
* [`trace`](http://jgallagher.github.io/rusqlite/rusqlite/trace/index.html) * [`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) * [`blob`](http://jgallagher.github.io/rusqlite/rusqlite/blob/index.html)
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature
requires SQLite 3.7.4 or later. requires SQLite 3.7.4 or later.

View File

@ -72,6 +72,8 @@ use std::cell::RefCell;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::result; use std::result;
use std::str; 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 libc::{c_int, c_char, c_void};
use types::{ToSql, ToSqlOutput, FromSql, FromSqlError, ValueRef}; use types::{ToSql, ToSqlOutput, FromSql, FromSqlError, ValueRef};
@ -551,29 +553,109 @@ impl Default for OpenFlags {
} }
} }
impl InnerConnection { static SQLITE_INIT: Once = ONCE_INIT;
fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result<InnerConnection> { static SQLITE_VERSION_CHECK: Once = ONCE_INIT;
unsafe { static BYPASS_SQLITE_INIT: AtomicBool = ATOMIC_BOOL_INIT;
// 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 /// rusqlite's check for a safe SQLite threading mode requires SQLite 3.7.0 or later. If you are
// exposing a very unsafe API to Rust, so refuse to open connections at all. /// running against a SQLite older than that, rusqlite attempts to ensure safety by performing
// Unfortunately, the check for this is quite gross. sqlite3_threadsafe() only /// configuration and initialization of SQLite itself the first time you attempt to open a
// returns how SQLite was _compiled_; there is no public API to check whether /// connection. By default, rusqlite panics if that initialization fails, since that could mean
// someone called sqlite3_config() to set single-threaded mode. We can cheat /// SQLite has been initialized in single-thread mode.
// 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 /// If you are encountering that panic _and_ can ensure that SQLite has been initialized in either
// sqlite3_mutex_alloc at https://github.com/mackyle/sqlite/blob/master/src/mutex.h); /// multi-thread or serialized mode, call this function prior to attempting to open a connection
// in single-threaded mode due to sqlite3_config(), the magic value 8 is also /// and rusqlite's initialization process will by skipped. This function is unsafe because if you
// returned (see the definition of noopMutexAlloc at /// call it and SQLite has actually been configured to run in single-thread mode, you may enounter
// https://github.com/mackyle/sqlite/blob/master/src/mutex_noop.c). /// memory errors or data corruption or any number of terrible things that should not be possible
const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8; /// when you're using Rust.
let mutex_ptr = ffi::sqlite3_mutex_alloc(0); pub unsafe fn bypass_sqlite_initialization() {
let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC; BYPASS_SQLITE_INIT.store(true, Ordering::Relaxed);
ffi::sqlite3_mutex_free(mutex_ptr); }
if is_singlethreaded {
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); 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);
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<InnerConnection> {
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 mut db: *mut ffi::sqlite3 = mem::uninitialized();
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null()); let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
if r != ffi::SQLITE_OK { if r != ffi::SQLITE_OK {
@ -1533,7 +1615,7 @@ mod test {
let minor = (n % 1_000_000) / 1_000; let minor = (n % 1_000_000) / 1_000;
let patch = n % 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 { mod query_and_then_tests {

View File

@ -56,8 +56,11 @@ mod test {
db.set_limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER, 99); db.set_limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER, 99);
assert_eq!(99, db.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)); assert_eq!(99, db.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER));
// SQLITE_LIMIT_TRIGGER_DEPTH was added in SQLite 3.6.18.
if ::version_number() >= 3006018 {
db.set_limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH, 32); db.set_limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH, 32);
assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH)); assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH));
}
// SQLITE_LIMIT_WORKER_THREADS was added in SQLite 3.8.7. // SQLITE_LIMIT_WORKER_THREADS was added in SQLite 3.8.7.
if ::version_number() >= 3008007 { if ::version_number() >= 3008007 {

View File

@ -15,11 +15,3 @@ pub fn version() -> &'static str {
let cstr = unsafe { CStr::from_ptr(ffi::sqlite3_libversion()) }; let cstr = unsafe { CStr::from_ptr(ffi::sqlite3_libversion()) };
cstr.to_str().expect("SQLite version string is not valid UTF8 ?!") 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 ?!")
}

View File

@ -7,14 +7,17 @@ extern crate libsqlite3_sys as ffi;
use rusqlite::Connection; use rusqlite::Connection;
#[test] #[test]
#[should_panic]
fn test_error_when_singlethread_mode() { fn test_error_when_singlethread_mode() {
// put SQLite into single-threaded mode // put SQLite into single-threaded mode
unsafe { unsafe {
// 1 == SQLITE_CONFIG_SINGLETHREAD if ffi::sqlite3_config(ffi::SQLITE_CONFIG_SINGLETHREAD) != ffi::SQLITE_OK {
assert_eq!(ffi::sqlite3_config(1), ffi::SQLITE_OK); return;
println!("{}", ffi::sqlite3_mutex_alloc(0) as u64); }
if ffi::sqlite3_initialize() != ffi::SQLITE_OK {
return;
}
} }
let result = Connection::open_in_memory(); let _ = Connection::open_in_memory().unwrap();
assert!(result.is_err());
} }