mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-22 05:41:47 +08:00
Clarify support of older SQLite versions.
This commit is contained in:
parent
e92a0ef30b
commit
358cca1638
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rusqlite"
|
||||
version = "0.9.5"
|
||||
version = "0.10.0"
|
||||
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
||||
description = "Ergonomic wrapper for SQLite"
|
||||
repository = "https://github.com/jgallagher/rusqlite"
|
||||
|
11
Changelog.md
11
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)
|
||||
|
||||
|
11
README.md
11
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.
|
||||
|
120
src/lib.rs
120
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<InnerConnection> {
|
||||
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<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 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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 ?!")
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user