mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-26 19:41:37 +08:00
Merge pull request #233 from jgallagher/clarify-old-sqlite-version-support
Clarify support of older SQLite versions.
This commit is contained in:
commit
7444f7b30a
@ -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"
|
||||||
|
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`.
|
* 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)
|
||||||
|
|
||||||
|
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
|
### 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.
|
||||||
|
124
src/lib.rs
124
src/lib.rs
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 ?!")
|
|
||||||
}
|
|
||||||
|
@ -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());
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user