Merge branch 'master' of https://github.com/jgallagher/rusqlite into stmt-cache

This commit is contained in:
Gwenael Treguier 2015-12-13 11:23:54 +01:00
commit d5faf2fab3
17 changed files with 2134 additions and 528 deletions

View File

@ -8,7 +8,12 @@ env:
script: script:
- cargo build - cargo build
- cargo test - cargo test
- cargo doc --no-deps - cargo test --features backup
- cargo test --features load_extension
- cargo test --features trace
- cargo test --features functions
- cargo test --features "backup functions load_extension trace"
- cargo doc --no-deps --features "backup functions load_extension trace"
after_success: | after_success: |
[ $TRAVIS_BRANCH = master ] && [ $TRAVIS_BRANCH = master ] &&

View File

@ -10,3 +10,5 @@ rusqlite contributors (sorted alphabetically)
* [Steve Klabnik](https://github.com/steveklabnik) * [Steve Klabnik](https://github.com/steveklabnik)
* [krdln](https://github.com/krdln) * [krdln](https://github.com/krdln)
* [Ben Striegel](https://github.com/bstrie) * [Ben Striegel](https://github.com/bstrie)
* [Andrew Straw](https://github.com/astraw)
* [Ronald Kinard](https://github.com/Furyhunter)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rusqlite" name = "rusqlite"
version = "0.4.0" version = "0.5.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"
@ -14,7 +14,9 @@ name = "rusqlite"
[features] [features]
load_extension = ["libsqlite3-sys/load_extension"] load_extension = ["libsqlite3-sys/load_extension"]
backup = []
cache = ["lru-cache"] cache = ["lru-cache"]
functions = []
trace = [] trace = []
[dependencies] [dependencies]
@ -25,10 +27,11 @@ libc = "~0.2"
[dev-dependencies] [dev-dependencies]
tempdir = "~0.3.4" tempdir = "~0.3.4"
lazy_static = "~0.1" lazy_static = "~0.1"
regex = "~0.1.41"
[dependencies.libsqlite3-sys] [dependencies.libsqlite3-sys]
path = "libsqlite3-sys" path = "libsqlite3-sys"
version = "0.2.0" version = "0.3.0"
[dependencies.lru-cache] [dependencies.lru-cache]
version = "~0.0.4" version = "~0.0.4"

View File

@ -1,10 +1,36 @@
# Version UPCOMING (TBD) # Version UPCOMING (TBD)
* BREAKING CHANGE: `SqliteTransactionDeferred`, `SqliteTransactionImmediate`, and
`SqliteTransactionExclusive` are no longer exported. Instead, use
`TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and
`TransactionBehavior::Exclusive`.
* Removed `Sqlite` prefix on many types:
* `SqliteConnection` is now `Connection`
* `SqliteError` is now `Error`
* `SqliteResult` is now `Result`
* `SqliteStatement` is now `Statement`
* `SqliteRows` is now `Rows`
* `SqliteRow` is now `Row`
* `SqliteOpenFlags` is now `OpenFlags`
* `SqliteTransaction` is now `Transaction`.
* `SqliteTransactionBehavior` is now `TransactionBehavior`.
* `SqliteLoadExtensionGuard` is now `LoadExtensionGuard`.
The old, prefixed names are still exported but are deprecated.
* Adds a variety of `..._named` methods for executing queries using named placeholder parameters.
* Adds `backup` feature that exposes SQLite's online backup API.
* Adds `functions` feature that allows user-defined scalar functions to be added to
open `SqliteConnection`s.
# Version 0.5.0 (2015-12-08)
* Adds `trace` feature that allows the use of SQLite's logging, tracing, and profiling hooks. * Adds `trace` feature that allows the use of SQLite's logging, tracing, and profiling hooks.
* Slight change to the closure types passed to `query_map` and `query_and_then`: * Slight change to the closure types passed to `query_map` and `query_and_then`:
* Remove the `'static` requirement on the closure's output type. * Remove the `'static` requirement on the closure's output type.
* Give the closure a `&SqliteRow` instead of a `SqliteRow`. * Give the closure a `&SqliteRow` instead of a `SqliteRow`.
* When building, the environment variable `SQLITE3_LIB_DIR` now takes precedence over pkg-config.
* If `pkg-config` is not available, we will try to find `libsqlite3` in `/usr/lib`.
* Add more documentation for failure modes of functions that return `SqliteResult`s. * Add more documentation for failure modes of functions that return `SqliteResult`s.
* Updates `libc` dependency to 0.2, fixing builds on ARM for Rust 1.6 or newer.
# Version 0.4.0 (2015-11-03) # Version 0.4.0 (2015-11-03)

View File

@ -11,7 +11,7 @@ extern crate rusqlite;
extern crate time; extern crate time;
use time::Timespec; use time::Timespec;
use rusqlite::SqliteConnection; use rusqlite::Connection;
#[derive(Debug)] #[derive(Debug)]
struct Person { struct Person {
@ -22,7 +22,7 @@ struct Person {
} }
fn main() { fn main() {
let conn = SqliteConnection::open_in_memory().unwrap(); let conn = Connection::open_in_memory().unwrap();
conn.execute("CREATE TABLE person ( conn.execute("CREATE TABLE person (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
@ -56,7 +56,21 @@ fn main() {
} }
``` ```
### Design of SqliteRows and SqliteRow ### Optional Features
Rusqlite provides several features that are behind [Cargo
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.
* [`functions`](http://jgallagher.github.io/rusqlite/rusqlite/functions/index.html)
allows you to load Rust closures into SQLite connections for use in queries.
* [`trace`](http://jgallagher.github.io/rusqlite/rusqlite/trace/index.html)
allows hooks into SQLite's tracing and profiling APIs.
### Design of Rows and Row
To retrieve the result rows from a query, SQLite requires you to call To retrieve the result rows from a query, SQLite requires you to call
[sqlite3_step()](https://www.sqlite.org/c3ref/step.html) on a prepared statement. You can only [sqlite3_step()](https://www.sqlite.org/c3ref/step.html) on a prepared statement. You can only
@ -67,12 +81,12 @@ satisfy the [Iterator](http://doc.rust-lang.org/std/iter/trait.Iterator.html) tr
you cannot (as easily) loop over the rows, or use many of the helpful Iterator methods like `map` you cannot (as easily) loop over the rows, or use many of the helpful Iterator methods like `map`
and `filter`. and `filter`.
Instead, Rusqlite's `SqliteRows` handle does conform to `Iterator`. It ensures safety by Instead, Rusqlite's `Rows` handle does conform to `Iterator`. It ensures safety by
performing checks at runtime to ensure you do not try to retrieve the values of a "stale" row, and performing checks at runtime to ensure you do not try to retrieve the values of a "stale" row, and
will panic if you do so. A specific example that will panic: will panic if you do so. A specific example that will panic:
```rust ```rust
fn bad_function_will_panic(conn: &SqliteConnection) -> SqliteResult<i64> { fn bad_function_will_panic(conn: &Connection) -> Result<i64> {
let mut stmt = try!(conn.prepare("SELECT id FROM my_table")); let mut stmt = try!(conn.prepare("SELECT id FROM my_table"));
let mut rows = try!(stmt.query(&[])); let mut rows = try!(stmt.query(&[]));
@ -88,7 +102,7 @@ fn bad_function_will_panic(conn: &SqliteConnection) -> SqliteResult<i64> {
``` ```
There are other, less obvious things that may result in a panic as well, such as calling There are other, less obvious things that may result in a panic as well, such as calling
`collect()` on a `SqliteRows` and then trying to use the collected rows. `collect()` on a `Rows` and then trying to use the collected rows.
Strongly consider using the method `query_map()` instead, if you can. Strongly consider using the method `query_map()` instead, if you can.
`query_map()` returns an iterator over rows-mapped-to-some-type. This `query_map()` returns an iterator over rows-mapped-to-some-type. This

View File

@ -1,6 +1,6 @@
[package] [package]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.2.0" version = "0.3.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"] authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
repository = "https://github.com/jgallagher/rusqlite" repository = "https://github.com/jgallagher/rusqlite"
description = "Native bindings to the libsqlite3 library" description = "Native bindings to the libsqlite3 library"

View File

@ -1,5 +1,26 @@
extern crate pkg_config; extern crate pkg_config;
use std::env;
use std::fs;
fn main() { fn main() {
pkg_config::find_library("sqlite3").unwrap(); // Allow users to specify where to find SQLite.
let lib_dir = match env::var("SQLITE3_LIB_DIR") {
Ok(dir) => dir,
Err(_) => {
// See if pkg-config can do everything for us.
if pkg_config::find_library("sqlite3").is_ok() {
return
}
// Try to fall back to /usr/lib if pkg-config failed.
match fs::metadata("/usr/lib") {
Ok(ref attr) if attr.is_dir() => "/usr/lib".to_owned(),
_ => panic!("Could not find sqlite3. Try setting SQLITE3_LIB_DIR."),
}
},
};
println!("cargo:rustc-link-lib=sqlite3");
println!("cargo:rustc-link-search={}", lib_dir);
} }

View File

@ -94,3 +94,5 @@ pub fn code_to_str(code: c_int) -> &'static str {
} }
pub const SQLITE_CONFIG_LOG : c_int = 16; pub const SQLITE_CONFIG_LOG : c_int = 16;
pub const SQLITE_UTF8 : c_int = 1;
pub const SQLITE_DETERMINISTIC : c_int = 0x800;

414
src/backup.rs Normal file
View File

@ -0,0 +1,414 @@
//! Online SQLite backup API.
//!
//! To create a `Backup`, you must have two distinct `Connection`s - one
//! for the source (which can be used while the backup is running) and one for
//! the destination (which cannot). A `Backup` handle exposes three methods:
//! `step` will attempt to back up a specified number of pages, `progress` gets
//! the current progress of the backup as of the last call to `step`, and
//! `run_to_completion` will attempt to back up the entire source database,
//! allowing you to specify how many pages are backed up at a time and how long
//! the thread should sleep between chunks of pages.
//!
//! The following example is equivalent to "Example 2: Online Backup of a
//! Running Database" from [SQLite's Online Backup API
//! documentation](https://www.sqlite.org/backup.html).
//!
//! ```rust,no_run
//! # use rusqlite::{backup, Connection, Result};
//! # use std::path::Path;
//! # use std::time;
//!
//! fn backupDb<P: AsRef<Path>>(src: &Connection, dst: P, progress: fn(backup::Progress))
//! -> Result<()> {
//! let mut dst = try!(Connection::open(dst));
//! let backup = try!(backup::Backup::new(src, &mut dst));
//! backup.run_to_completion(5, time::Duration::from_millis(250), Some(progress))
//! }
//! ```
use std::marker::PhantomData;
use std::path::Path;
use std::ptr;
use libc::c_int;
use std::thread;
use std::time::Duration;
use ffi;
use {DatabaseName, Connection, Error, Result};
impl Connection {
/// Back up the `name` database to the given destination path.
/// If `progress` is not `None`, it will be called periodically
/// until the backup completes.
///
/// For more fine-grained control over the backup process (e.g.,
/// to sleep periodically during the backup or to back up to an
/// already-open database connection), see the `backup` module.
///
/// # Failure
///
/// Will return `Err` if the destination path cannot be opened
/// or if the backup fails.
pub fn backup<P: AsRef<Path>>(&self,
name: DatabaseName,
dst_path: P,
progress: Option<fn(Progress)>)
-> Result<()> {
use self::StepResult::{More, Done, Busy, Locked};
let mut dst = try!(Connection::open(dst_path));
let backup = try!(Backup::new_with_names(self, name, &mut dst, DatabaseName::Main));
let mut r = More;
while r == More {
r = try!(backup.step(100));
if let Some(f) = progress {
f(backup.progress());
}
}
match r {
Done => Ok(()),
Busy => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
Locked => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
More => unreachable!(),
}
}
/// Restore the given source path into the `name` database.
/// If `progress` is not `None`, it will be called periodically
/// until the restore completes.
///
/// For more fine-grained control over the restore process (e.g.,
/// to sleep periodically during the restore or to restore from an
/// already-open database connection), see the `backup` module.
///
/// # Failure
///
/// Will return `Err` if the destination path cannot be opened
/// or if the restore fails.
pub fn restore<P: AsRef<Path>>(&mut self,
name: DatabaseName,
src_path: P,
progress: Option<fn(Progress)>)
-> Result<()> {
use self::StepResult::{More, Done, Busy, Locked};
let src = try!(Connection::open(src_path));
let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name));
let mut r = More;
let mut busy_count = 0i32;
'restore_loop: while r == More || r == Busy {
r = try!(restore.step(100));
if let Some(f) = progress {
f(restore.progress());
}
if r == Busy {
busy_count += 1;
if busy_count >= 3 {
break 'restore_loop;
}
thread::sleep(Duration::from_millis(100));
}
}
match r {
Done => Ok(()),
Busy => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
Locked => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
More => unreachable!(),
}
}
}
/// Possible successful results of calling `Backup::step`.
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
pub enum StepResult {
/// The backup is complete.
Done,
/// The step was successful but there are still more pages that need to be backed up.
More,
/// The step failed because appropriate locks could not be aquired. This is
/// not a fatal error - the step can be retried.
Busy,
/// The step failed because the source connection was writing to the
/// database. This is not a fatal error - the step can be retried.
Locked,
}
/// Struct specifying the progress of a backup. The percentage completion can
/// be calculated as `(pagecount - remaining) / pagecount`. The progress of a
/// backup is as of the last call to `step` - if the source database is
/// modified after a call to `step`, the progress value will become outdated
/// and potentially incorrect.
#[derive(Copy,Clone,Debug)]
pub struct Progress {
/// Number of pages in the source database that still need to be backed up.
pub remaining: c_int,
/// Total number of pages in the source database.
pub pagecount: c_int,
}
/// A handle to an online backup.
pub struct Backup<'a, 'b> {
phantom_from: PhantomData<&'a ()>,
phantom_to: PhantomData<&'b ()>,
b: *mut ffi::sqlite3_backup,
}
impl<'a, 'b> Backup<'a, 'b> {
/// Attempt to create a new handle that will allow backups from `from` to
/// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any
/// API calls on the destination of a backup while the backup is taking
/// place.
///
/// # Failure
///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `NULL`.
pub fn new(from: &'a Connection,
to: &'b mut Connection)
-> Result<Backup<'a, 'b>> {
Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
}
/// Attempt to create a new handle that will allow backups from the
/// `from_name` database of `from` to the `to_name` database of `to`. Note
/// that `to` is a `&mut` - this is because SQLite forbids any API calls on
/// the destination of a backup while the backup is taking place.
///
/// # Failure
///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `NULL`.
pub fn new_with_names(from: &'a Connection,
from_name: DatabaseName,
to: &'b mut Connection,
to_name: DatabaseName)
-> Result<Backup<'a, 'b>> {
let to_name = try!(to_name.to_cstring());
let from_name = try!(from_name.to_cstring());
let to_db = to.db.borrow_mut().db;
let b = unsafe {
let b = ffi::sqlite3_backup_init(to_db,
to_name.as_ptr(),
from.db.borrow_mut().db,
from_name.as_ptr());
if b.is_null() {
return Err(Error::from_handle(to_db, ffi::sqlite3_errcode(to_db)));
}
b
};
Ok(Backup {
phantom_from: PhantomData,
phantom_to: PhantomData,
b: b,
})
}
/// Gets the progress of the backup as of the last call to `step`.
pub fn progress(&self) -> Progress {
unsafe {
Progress {
remaining: ffi::sqlite3_backup_remaining(self.b),
pagecount: ffi::sqlite3_backup_pagecount(self.b),
}
}
}
/// Attempts to back up the given number of pages. If `num_pages` is
/// negative, will attempt to back up all remaining pages. This will hold a
/// lock on the source database for the duration, so it is probably not
/// what you want for databases that are currently active (see
/// `run_to_completion` for a better alternative).
///
/// # Failure
///
/// Will return `Err` if the underlying `sqlite3_backup_step` call returns
/// an error code other than `DONE`, `OK`, `BUSY`, or `LOCKED`. `BUSY` and
/// `LOCKED` are transient errors and are therefore returned as possible
/// `Ok` values.
pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
use self::StepResult::{Done, More, Busy, Locked};
let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
match rc {
ffi::SQLITE_DONE => Ok(Done),
ffi::SQLITE_OK => Ok(More),
ffi::SQLITE_BUSY => Ok(Busy),
ffi::SQLITE_LOCKED => Ok(Locked),
rc => {
Err(Error {
code: rc,
message: ffi::code_to_str(rc).into(),
})
}
}
}
/// Attempts to run the entire backup. Will call `step(pages_per_step)` as
/// many times as necessary, sleeping for `pause_between_pages` between
/// each call to give the source database time to process any pending
/// queries. This is a direct implementation of "Example 2: Online Backup
/// of a Running Database" from [SQLite's Online Backup API
/// documentation](https://www.sqlite.org/backup.html).
///
/// If `progress` is not `None`, it will be called after each step with the
/// current progress of the backup. Note that is possible the progress may
/// not change if the step returns `Busy` or `Locked` even though the
/// backup is still running.
///
/// # Failure
///
/// Will return `Err` if any of the calls to `step` return `Err`.
pub fn run_to_completion(&self,
pages_per_step: c_int,
pause_between_pages: Duration,
progress: Option<fn(Progress)>)
-> Result<()> {
use self::StepResult::{Done, More, Busy, Locked};
assert!(pages_per_step > 0, "pages_per_step must be positive");
loop {
let r = try!(self.step(pages_per_step));
if let Some(progress) = progress {
progress(self.progress())
}
match r {
More | Busy | Locked => thread::sleep(pause_between_pages),
Done => return Ok(()),
}
}
}
}
impl<'a, 'b> Drop for Backup<'a, 'b> {
fn drop(&mut self) {
unsafe { ffi::sqlite3_backup_finish(self.b) };
}
}
#[cfg(test)]
mod test {
use {Connection, DatabaseName};
use std::time::Duration;
use super::Backup;
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup() {
let src = Connection::open_in_memory().unwrap();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42);
END;";
src.execute_batch(sql).unwrap();
let mut dst = Connection::open_in_memory().unwrap();
{
let backup = Backup::new(&src, &mut dst).unwrap();
backup.step(-1).unwrap();
}
let the_answer = dst.query_row("SELECT x FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{
let backup = Backup::new(&src, &mut dst).unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
}
let the_answer = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
assert_eq!(42 + 43, the_answer);
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup_temp() {
let src = Connection::open_in_memory().unwrap();
let sql = "BEGIN;
CREATE TEMPORARY TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42);
END;";
src.execute_batch(sql).unwrap();
let mut dst = Connection::open_in_memory().unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Temp,
&mut dst,
DatabaseName::Main)
.unwrap();
backup.step(-1).unwrap();
}
let the_answer = dst.query_row("SELECT x FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Temp,
&mut dst,
DatabaseName::Main)
.unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
}
let the_answer = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
assert_eq!(42 + 43, the_answer);
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup_attached() {
let src = Connection::open_in_memory().unwrap();
let sql = "ATTACH DATABASE ':memory:' AS my_attached;
BEGIN;
CREATE TABLE my_attached.foo(x INTEGER);
INSERT INTO my_attached.foo VALUES(42);
END;";
src.execute_batch(sql).unwrap();
let mut dst = Connection::open_in_memory().unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main)
.unwrap();
backup.step(-1).unwrap();
}
let the_answer = dst.query_row("SELECT x FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main)
.unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
}
let the_answer = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
assert_eq!(42 + 43, the_answer);
}
}

View File

@ -1,7 +1,7 @@
//! Prepared statements cache for faster execution. //! Prepared statements cache for faster execution.
extern crate lru_cache; extern crate lru_cache;
use {SqliteResult, SqliteConnection, SqliteStatement}; use {Result, Connection, Statement};
use self::lru_cache::LruCache; use self::lru_cache::LruCache;
/// Prepared statements cache. /// Prepared statements cache.
@ -9,14 +9,17 @@ use self::lru_cache::LruCache;
/// FIXME limitation: the same SQL can be cached only once... /// FIXME limitation: the same SQL can be cached only once...
#[derive(Debug)] #[derive(Debug)]
pub struct StatementCache<'conn> { pub struct StatementCache<'conn> {
pub conn: &'conn SqliteConnection, pub conn: &'conn Connection,
cache: LruCache<String, SqliteStatement<'conn>>, cache: LruCache<String, Statement<'conn>>,
} }
impl<'conn> StatementCache<'conn> { impl<'conn> StatementCache<'conn> {
/// Create a statement cache. /// Create a statement cache.
pub fn new(conn: &'conn SqliteConnection, capacity: usize) -> StatementCache<'conn> { pub fn new(conn: &'conn Connection, capacity: usize) -> StatementCache<'conn> {
StatementCache{ conn: conn, cache: LruCache::new(capacity) } StatementCache {
conn: conn,
cache: LruCache::new(capacity),
}
} }
/// Search the cache for a prepared-statement object that implements `sql`. /// Search the cache for a prepared-statement object that implements `sql`.
@ -25,11 +28,11 @@ impl<'conn> StatementCache<'conn> {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if no cached statement can be found and the underlying SQLite prepare call fails. /// Will return `Err` if no cached statement can be found and the underlying SQLite prepare call fails.
pub fn get(&mut self, sql: &str) -> SqliteResult<SqliteStatement<'conn>> { pub fn get(&mut self, sql: &str) -> Result<Statement<'conn>> {
let stmt = self.cache.remove(sql); let stmt = self.cache.remove(sql);
match stmt { match stmt {
Some(stmt) => Ok(stmt), Some(stmt) => Ok(stmt),
_ => self.conn.prepare(sql) _ => self.conn.prepare(sql),
} }
} }
@ -41,7 +44,7 @@ impl<'conn> StatementCache<'conn> {
/// ///
/// Will return `Err` if `stmt` (or the already cached statement implementing the same SQL) statement is `discard`ed /// Will return `Err` if `stmt` (or the already cached statement implementing the same SQL) statement is `discard`ed
/// and the underlying SQLite finalize call fails. /// and the underlying SQLite finalize call fails.
pub fn release(&mut self, stmt: SqliteStatement<'conn>, discard: bool) -> SqliteResult<()> { pub fn release(&mut self, mut stmt: Statement<'conn>, discard: bool) -> Result<()> {
if discard { if discard {
return stmt.finalize(); return stmt.finalize();
} }
@ -68,19 +71,19 @@ impl<'conn> StatementCache<'conn> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use SqliteConnection; use Connection;
use super::StatementCache; use super::StatementCache;
#[test] #[test]
fn test_cache() { fn test_cache() {
let db = SqliteConnection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
let mut cache = StatementCache::new(&db, 10); let mut cache = StatementCache::new(&db, 10);
let sql = "PRAGMA schema_version"; let sql = "PRAGMA schema_version";
let mut stmt = cache.get(sql).unwrap(); let mut stmt = cache.get(sql).unwrap();
//println!("NEW {:?}", stmt); // println!("NEW {:?}", stmt);
cache.release(stmt, false).unwrap(); cache.release(stmt, false).unwrap();
stmt = cache.get(sql).unwrap(); stmt = cache.get(sql).unwrap();
//println!("CACHED {:?}", stmt); // println!("CACHED {:?}", stmt);
cache.release(stmt, true).unwrap(); cache.release(stmt, true).unwrap();
cache.flush(); cache.flush();
} }

644
src/functions.rs Normal file
View File

@ -0,0 +1,644 @@
//! Create or redefine SQL functions.
//!
//! # Example
//!
//! Adding a `regexp` function to a connection in which compiled regular expressions
//! are cached in a `HashMap`. For an alternative implementation that uses SQLite's
//! [Function Auxilliary Data](https://www.sqlite.org/c3ref/get_auxdata.html) interface
//! to avoid recompiling regular expressions, see the unit tests for this module.
//!
//! ```rust
//! extern crate libsqlite3_sys;
//! extern crate rusqlite;
//! extern crate regex;
//!
//! use rusqlite::{Connection, Error, Result};
//! use std::collections::HashMap;
//! use regex::Regex;
//!
//! fn add_regexp_function(db: &Connection) -> Result<()> {
//! let mut cached_regexes = HashMap::new();
//! db.create_scalar_function("regexp", 2, true, move |ctx| {
//! let regex_s = try!(ctx.get::<String>(0));
//! let entry = cached_regexes.entry(regex_s.clone());
//! let regex = {
//! use std::collections::hash_map::Entry::{Occupied, Vacant};
//! match entry {
//! Occupied(occ) => occ.into_mut(),
//! Vacant(vac) => {
//! let r = try!(Regex::new(&regex_s).map_err(|e| Error {
//! code: libsqlite3_sys::SQLITE_ERROR,
//! message: format!("Invalid regular expression: {}", e),
//! }));
//! vac.insert(r)
//! }
//! }
//! };
//!
//! let text = try!(ctx.get::<String>(1));
//! Ok(regex.is_match(&text))
//! })
//! }
//!
//! fn main() {
//! let db = Connection::open_in_memory().unwrap();
//! add_regexp_function(&db).unwrap();
//!
//! let is_match = db.query_row("SELECT regexp('[aeiou]*', 'aaaaeeeiii')", &[],
//! |row| row.get::<bool>(0)).unwrap();
//!
//! assert!(is_match);
//! }
//! ```
use std::ffi::CStr;
use std::mem;
use std::ptr;
use std::slice;
use std::str;
use libc::{c_int, c_double, c_char, c_void};
use ffi;
pub use ffi::sqlite3_context;
pub use ffi::sqlite3_value;
pub use ffi::sqlite3_value_type;
pub use ffi::sqlite3_value_numeric_type;
use types::Null;
use {Result, Error, Connection, str_to_cstring, InnerConnection};
/// A trait for types that can be converted into the result of an SQL function.
pub trait ToResult {
unsafe fn set_result(&self, ctx: *mut sqlite3_context);
}
macro_rules! raw_to_impl(
($t:ty, $f:ident) => (
impl ToResult for $t {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
ffi::$f(ctx, *self)
}
}
)
);
raw_to_impl!(c_int, sqlite3_result_int);
raw_to_impl!(i64, sqlite3_result_int64);
raw_to_impl!(c_double, sqlite3_result_double);
impl<'a> ToResult for bool {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
match *self {
true => ffi::sqlite3_result_int(ctx, 1),
_ => ffi::sqlite3_result_int(ctx, 0),
}
}
}
impl<'a> ToResult for &'a str {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
let length = self.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
return;
}
match str_to_cstring(self) {
Ok(c_str) => {
ffi::sqlite3_result_text(ctx,
c_str.as_ptr(),
length as c_int,
ffi::SQLITE_TRANSIENT())
}
Err(_) => ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE), // TODO sqlite3_result_error
}
}
}
impl ToResult for String {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
(&self[..]).set_result(ctx)
}
}
impl<'a> ToResult for &'a [u8] {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
if self.len() > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
return;
}
ffi::sqlite3_result_blob(ctx,
mem::transmute(self.as_ptr()),
self.len() as c_int,
ffi::SQLITE_TRANSIENT())
}
}
impl ToResult for Vec<u8> {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
(&self[..]).set_result(ctx)
}
}
impl<T: ToResult> ToResult for Option<T> {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
match *self {
None => ffi::sqlite3_result_null(ctx),
Some(ref t) => t.set_result(ctx),
}
}
}
impl ToResult for Null {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
ffi::sqlite3_result_null(ctx)
}
}
// sqlite3_result_error_code, c_int
// sqlite3_result_error_nomem
// sqlite3_result_error_toobig
// sqlite3_result_error, *const c_char, c_int
// sqlite3_result_zeroblob
// sqlite3_result_value
/// A trait for types that can be created from a SQLite function parameter value.
pub trait FromValue: Sized {
unsafe fn parameter_value(v: *mut sqlite3_value) -> Result<Self>;
/// FromValue types can implement this method and use sqlite3_value_type to check that
/// the type reported by SQLite matches a type suitable for Self. This method is used
/// by `Context::get` to confirm that the parameter contains a valid type before
/// attempting to retrieve the value.
unsafe fn parameter_has_valid_sqlite_type(_: *mut sqlite3_value) -> bool {
true
}
}
macro_rules! raw_from_impl(
($t:ty, $f:ident, $c:expr) => (
impl FromValue for $t {
unsafe fn parameter_value(v: *mut sqlite3_value) -> Result<$t> {
Ok(ffi::$f(v))
}
unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool {
sqlite3_value_numeric_type(v) == $c
}
}
)
);
raw_from_impl!(c_int, sqlite3_value_int, ffi::SQLITE_INTEGER);
raw_from_impl!(i64, sqlite3_value_int64, ffi::SQLITE_INTEGER);
impl FromValue for bool {
unsafe fn parameter_value(v: *mut sqlite3_value) -> Result<bool> {
match ffi::sqlite3_value_int(v) {
0 => Ok(false),
_ => Ok(true),
}
}
unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool {
sqlite3_value_numeric_type(v) == ffi::SQLITE_INTEGER
}
}
impl FromValue for c_double {
unsafe fn parameter_value(v: *mut sqlite3_value) -> Result<c_double> {
Ok(ffi::sqlite3_value_double(v))
}
unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool {
sqlite3_value_numeric_type(v) == ffi::SQLITE_FLOAT ||
sqlite3_value_numeric_type(v) == ffi::SQLITE_INTEGER
}
}
impl FromValue for String {
unsafe fn parameter_value(v: *mut sqlite3_value) -> Result<String> {
let c_text = ffi::sqlite3_value_text(v);
if c_text.is_null() {
Ok("".to_string())
} else {
let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes();
let utf8_str = str::from_utf8(c_slice);
utf8_str.map(|s| s.to_string())
.map_err(|e| {
Error {
code: 0,
message: e.to_string(),
}
})
}
}
unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool {
sqlite3_value_type(v) == ffi::SQLITE_TEXT
}
}
impl FromValue for Vec<u8> {
unsafe fn parameter_value(v: *mut sqlite3_value) -> Result<Vec<u8>> {
use std::slice::from_raw_parts;
let c_blob = ffi::sqlite3_value_blob(v);
let len = ffi::sqlite3_value_bytes(v);
assert!(len >= 0,
"unexpected negative return from sqlite3_value_bytes");
let len = len as usize;
Ok(from_raw_parts(mem::transmute(c_blob), len).to_vec())
}
unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool {
sqlite3_value_type(v) == ffi::SQLITE_BLOB
}
}
impl<T: FromValue> FromValue for Option<T> {
unsafe fn parameter_value(v: *mut sqlite3_value) -> Result<Option<T>> {
if sqlite3_value_type(v) == ffi::SQLITE_NULL {
Ok(None)
} else {
FromValue::parameter_value(v).map(|t| Some(t))
}
}
unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool {
sqlite3_value_type(v) == ffi::SQLITE_NULL || T::parameter_has_valid_sqlite_type(v)
}
}
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
let _: Box<T> = Box::from_raw(mem::transmute(p));
}
/// Context is a wrapper for the SQLite function evaluation context.
pub struct Context<'a> {
ctx: *mut sqlite3_context,
args: &'a [*mut sqlite3_value],
}
impl<'a> Context<'a> {
/// Returns the number of arguments to the function.
pub fn len(&self) -> usize {
self.args.len()
}
/// Returns the `idx`th argument as a `T`.
///
/// # Failure
///
/// Will panic if `idx` is greater than or equal to `self.len()`.
///
/// Will return Err if the underlying SQLite type cannot be converted to a `T`.
pub fn get<T: FromValue>(&self, idx: usize) -> Result<T> {
let arg = self.args[idx];
unsafe {
if T::parameter_has_valid_sqlite_type(arg) {
T::parameter_value(arg)
} else {
Err(Error {
code: ffi::SQLITE_MISMATCH,
message: "Invalid value type".to_string(),
})
}
}
}
/// Sets the auxilliary data associated with a particular parameter. See
/// https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of
/// this feature, or the unit tests of this module for an example.
pub fn set_aux<T>(&self, arg: c_int, value: T) {
let boxed = Box::into_raw(Box::new(value));
unsafe {
ffi::sqlite3_set_auxdata(self.ctx,
arg,
mem::transmute(boxed),
Some(mem::transmute(free_boxed_value::<T>)))
};
}
/// Gets the auxilliary data that was associated with a given parameter
/// via `set_aux`. Returns `None` if no data has been associated.
///
/// # Unsafety
///
/// This function is unsafe as there is no guarantee that the type `T`
/// requested matches the type `T` that was provided to `set_aux`. The
/// types must be identical.
pub unsafe fn get_aux<T>(&self, arg: c_int) -> Option<&T> {
let p = ffi::sqlite3_get_auxdata(self.ctx, arg) as *mut T;
if p.is_null() {
None
} else {
Some(&*p)
}
}
}
impl Connection {
/// Attach a user-defined scalar function to this database connection.
///
/// `fn_name` is the name the function will be accessible from SQL.
/// `n_arg` is the number of arguments to the function. Use `-1` for a variable
/// number. If the function always returns the same value given the same
/// input, `deterministic` should be `true`.
///
/// The function will remain available until the connection is closed or
/// until it is explicitly removed via `remove_function`.
///
/// # Example
///
/// ```rust
/// # use rusqlite::{Connection, Result};
/// # type c_double = f64;
/// fn scalar_function_example(db: Connection) -> Result<()> {
/// try!(db.create_scalar_function("halve", 1, true, |ctx| {
/// let value = try!(ctx.get::<c_double>(0));
/// Ok(value / 2f64)
/// }));
///
/// let six_halved = try!(db.query_row("SELECT halve(6)", &[], |r| r.get::<f64>(0)));
/// assert_eq!(six_halved, 3f64);
/// Ok(())
/// }
/// ```
///
/// # Failure
///
/// Will return Err if the function could not be attached to the connection.
pub fn create_scalar_function<F, T>(&self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
x_func: F)
-> Result<()>
where F: FnMut(&Context) -> Result<T>,
T: ToResult
{
self.db.borrow_mut().create_scalar_function(fn_name, n_arg, deterministic, x_func)
}
/// Removes a user-defined function from this database connection.
///
/// `fn_name` and `n_arg` should match the name and number of arguments
/// given to `create_scalar_function`.
///
/// # Failure
///
/// Will return Err if the function could not be removed.
pub fn remove_function(&self, fn_name: &str, n_arg: c_int) -> Result<()> {
self.db.borrow_mut().remove_function(fn_name, n_arg)
}
}
impl InnerConnection {
fn create_scalar_function<F, T>(&mut self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
x_func: F)
-> Result<()>
where F: FnMut(&Context) -> Result<T>,
T: ToResult
{
extern "C" fn call_boxed_closure<F, T>(ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value)
where F: FnMut(&Context) -> Result<T>,
T: ToResult
{
unsafe {
let ctx = Context {
ctx: ctx,
args: slice::from_raw_parts(argv, argc as usize),
};
let boxed_f: *mut F = mem::transmute(ffi::sqlite3_user_data(ctx.ctx));
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
match (*boxed_f)(&ctx) {
Ok(r) => r.set_result(ctx.ctx),
Err(e) => {
ffi::sqlite3_result_error_code(ctx.ctx, e.code);
if let Ok(cstr) = str_to_cstring(&e.message) {
ffi::sqlite3_result_error(ctx.ctx, cstr.as_ptr(), -1);
}
}
}
}
}
let boxed_f: *mut F = Box::into_raw(Box::new(x_func));
let c_name = try!(str_to_cstring(fn_name));
let mut flags = ffi::SQLITE_UTF8;
if deterministic {
flags |= ffi::SQLITE_DETERMINISTIC;
}
let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(),
c_name.as_ptr(),
n_arg,
flags,
mem::transmute(boxed_f),
Some(call_boxed_closure::<F, T>),
None,
None,
Some(mem::transmute(free_boxed_value::<F>)))
};
self.decode_result(r)
}
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
let c_name = try!(str_to_cstring(fn_name));
let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(),
c_name.as_ptr(),
n_arg,
ffi::SQLITE_UTF8,
ptr::null_mut(),
None,
None,
None,
None)
};
self.decode_result(r)
}
}
#[cfg(test)]
mod test {
extern crate regex;
use std::collections::HashMap;
use libc::c_double;
use self::regex::Regex;
use {Connection, Error, Result};
use ffi;
use functions::Context;
fn half(ctx: &Context) -> Result<c_double> {
assert!(ctx.len() == 1, "called with unexpected number of arguments");
let value = try!(ctx.get::<c_double>(0));
Ok(value / 2f64)
}
#[test]
fn test_function_half() {
let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("half", 1, true, half).unwrap();
let result = db.query_row("SELECT half(6)", &[], |r| r.get::<f64>(0));
assert_eq!(3f64, result.unwrap());
}
#[test]
fn test_remove_function() {
let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("half", 1, true, half).unwrap();
let result = db.query_row("SELECT half(6)", &[], |r| r.get::<f64>(0));
assert_eq!(3f64, result.unwrap());
db.remove_function("half", 1).unwrap();
let result = db.query_row("SELECT half(6)", &[], |r| r.get::<f64>(0));
assert!(result.is_err());
}
// This implementation of a regexp scalar function uses SQLite's auxilliary data
// (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular
// expression multiple times within one query.
fn regexp_with_auxilliary(ctx: &Context) -> Result<bool> {
assert!(ctx.len() == 2, "called with unexpected number of arguments");
let saved_re: Option<&Regex> = unsafe { ctx.get_aux(0) };
let new_re = match saved_re {
None => {
let s = try!(ctx.get::<String>(0));
let r = try!(Regex::new(&s).map_err(|e| {
Error {
code: ffi::SQLITE_ERROR,
message: format!("Invalid regular expression: {}", e),
}
}));
Some(r)
}
Some(_) => None,
};
let is_match = {
let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap());
let text = try!(ctx.get::<String>(1));
re.is_match(&text)
};
if let Some(re) = new_re {
ctx.set_aux(0, re);
}
Ok(is_match)
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_function_regexp_with_auxilliary() {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("BEGIN;
CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX');
END;").unwrap();
db.create_scalar_function("regexp", 2, true, regexp_with_auxilliary).unwrap();
let result = db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')",
&[],
|r| r.get::<bool>(0));
assert_eq!(true, result.unwrap());
let result = db.query_row("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
&[],
|r| r.get::<i64>(0));
assert_eq!(2, result.unwrap());
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_function_regexp_with_hashmap_cache() {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("BEGIN;
CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX');
END;").unwrap();
// This implementation of a regexp scalar function uses a captured HashMap
// to keep cached regular expressions around (even across multiple queries)
// until the function is removed.
let mut cached_regexes = HashMap::new();
db.create_scalar_function("regexp", 2, true, move |ctx| {
assert!(ctx.len() == 2, "called with unexpected number of arguments");
let regex_s = try!(ctx.get::<String>(0));
let entry = cached_regexes.entry(regex_s.clone());
let regex = {
use std::collections::hash_map::Entry::{Occupied, Vacant};
match entry {
Occupied(occ) => occ.into_mut(),
Vacant(vac) => {
let r = try!(Regex::new(&regex_s).map_err(|e| Error {
code: ffi::SQLITE_ERROR,
message: format!("Invalid regular expression: {}", e),
}));
vac.insert(r)
}
}
};
let text = try!(ctx.get::<String>(1));
Ok(regex.is_match(&text))
}).unwrap();
let result = db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')",
&[],
|r| r.get::<bool>(0));
assert_eq!(true, result.unwrap());
let result = db.query_row("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
&[],
|r| r.get::<i64>(0));
assert_eq!(2, result.unwrap());
}
#[test]
fn test_varargs_function() {
let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("my_concat", -1, true, |ctx| {
let mut ret = String::new();
for idx in 0..ctx.len() {
let s = try!(ctx.get::<String>(idx));
ret.push_str(&s);
}
Ok(ret)
})
.unwrap();
for &(expected, query) in &[("", "SELECT my_concat()"),
("onetwo", "SELECT my_concat('one', 'two')"),
("abc", "SELECT my_concat('a', 'b', 'c')")] {
let result: String = db.query_row(query, &[], |r| r.get(0)).unwrap();
assert_eq!(expected, result);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,35 @@
use {SqliteResult, SqliteConnection}; use {Result, Connection};
/// Old name for `LoadExtensionGuard`. `SqliteLoadExtensionGuard` is deprecated.
pub type SqliteLoadExtensionGuard<'conn> = LoadExtensionGuard<'conn>;
/// RAII guard temporarily enabling SQLite extensions to be loaded. /// RAII guard temporarily enabling SQLite extensions to be loaded.
/// ///
/// ## Example /// ## Example
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{SqliteConnection, SqliteResult, SqliteLoadExtensionGuard}; /// # use rusqlite::{Connection, Result, LoadExtensionGuard};
/// # use std::path::{Path}; /// # use std::path::{Path};
/// fn load_my_extension(conn: &SqliteConnection) -> SqliteResult<()> { /// fn load_my_extension(conn: &Connection) -> Result<()> {
/// let _guard = try!(SqliteLoadExtensionGuard::new(conn)); /// let _guard = try!(LoadExtensionGuard::new(conn));
/// ///
/// conn.load_extension(Path::new("my_sqlite_extension"), None) /// conn.load_extension(Path::new("my_sqlite_extension"), None)
/// } /// }
/// ``` /// ```
pub struct SqliteLoadExtensionGuard<'conn> { pub struct LoadExtensionGuard<'conn> {
conn: &'conn SqliteConnection, conn: &'conn Connection,
} }
impl<'conn> SqliteLoadExtensionGuard<'conn> { impl<'conn> LoadExtensionGuard<'conn> {
/// Attempt to enable loading extensions. Loading extensions will be disabled when this /// Attempt to enable loading extensions. Loading extensions will be disabled when this
/// guard goes out of scope. Cannot be meaningfully nested. /// guard goes out of scope. Cannot be meaningfully nested.
pub fn new(conn: &SqliteConnection) -> SqliteResult<SqliteLoadExtensionGuard> { pub fn new(conn: &Connection) -> Result<LoadExtensionGuard> {
conn.load_extension_enable().map(|_| SqliteLoadExtensionGuard{ conn: conn }) conn.load_extension_enable().map(|_| LoadExtensionGuard { conn: conn })
} }
} }
#[allow(unused_must_use)] #[allow(unused_must_use)]
impl<'conn> Drop for SqliteLoadExtensionGuard<'conn> { impl<'conn> Drop for LoadExtensionGuard<'conn> {
fn drop(&mut self) { fn drop(&mut self) {
self.conn.load_extension_disable(); self.conn.load_extension_disable();
} }

223
src/named_params.rs Normal file
View File

@ -0,0 +1,223 @@
use libc::c_int;
use super::ffi;
use {Result, Error, Connection, Statement, Rows, Row, str_to_cstring};
use types::ToSql;
impl Connection {
/// Convenience method to prepare and execute a single SQL statement with named parameter(s).
///
/// On success, returns the number of rows that were changed or inserted or deleted (via
/// `sqlite3_changes`).
///
/// ## Example
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn insert(conn: &Connection) -> Result<i32> {
/// conn.execute_named("INSERT INTO test (name) VALUES (:name)", &[(":name", &"one")])
/// }
/// ```
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn execute_named(&self, sql: &str, params: &[(&str, &ToSql)]) -> Result<c_int> {
self.prepare(sql).and_then(|mut stmt| stmt.execute_named(params))
}
/// Convenience method to execute a query with named parameter(s) that is expected to return
/// a single row.
///
/// If the query returns more than one row, all rows except the first are ignored.
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn query_row_named<T, F>(&self,
sql: &str,
params: &[(&str, &ToSql)],
f: F)
-> Result<T>
where F: FnOnce(Row) -> T
{
let mut stmt = try!(self.prepare(sql));
let mut rows = try!(stmt.query_named(params));
rows.get_expected_row().map(f)
}
}
impl<'conn> Statement<'conn> {
/// Return the index of an SQL parameter given its name.
///
/// # Failure
///
/// Will return Err if `name` is invalid. Will return Ok(None) if the name
/// is valid but not a bound parameter of this statement.
pub fn parameter_index(&self, name: &str) -> Result<Option<i32>> {
let c_name = try!(str_to_cstring(name));
let c_index = unsafe { ffi::sqlite3_bind_parameter_index(self.stmt, c_name.as_ptr()) };
Ok(match c_index {
0 => None, // A zero is returned if no matching parameter is found.
n => Some(n),
})
}
/// Execute the prepared statement with named parameter(s). If any parameters
/// that were in the prepared statement are not included in `params`, they
/// will continue to use the most-recently bound value from a previous call
/// to `execute_named`, or `NULL` if they have never been bound.
///
/// On success, returns the number of rows that were changed or inserted or deleted (via
/// `sqlite3_changes`).
///
/// ## Example
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn insert(conn: &Connection) -> Result<i32> {
/// let mut stmt = try!(conn.prepare("INSERT INTO test (name) VALUES (:name)"));
/// stmt.execute_named(&[(":name", &"one")])
/// }
/// ```
///
/// # Failure
///
/// Will return `Err` if binding parameters fails, the executed statement returns rows (in
/// which case `query` should be used instead), or the underling SQLite call fails.
pub fn execute_named(&mut self, params: &[(&str, &ToSql)]) -> Result<c_int> {
try!(self.bind_parameters_named(params));
unsafe {
self.execute_()
}
}
/// Execute the prepared statement with named parameter(s), returning an iterator over the
/// resulting rows. If any parameters that were in the prepared statement are not included in
/// `params`, they will continue to use the most-recently bound value from a previous call to
/// `query_named`, or `NULL` if they have never been bound.
///
/// ## Example
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result, Rows};
/// fn query(conn: &Connection) -> Result<()> {
/// let mut stmt = try!(conn.prepare("SELECT * FROM test where name = :name"));
/// let mut rows = try!(stmt.query_named(&[(":name", &"one")]));
/// for row in rows {
/// // ...
/// }
/// Ok(())
/// }
/// ```
///
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_named<'a>(&'a mut self,
params: &[(&str, &ToSql)])
-> Result<Rows<'a>> {
self.reset_if_needed();
try!(self.bind_parameters_named(params));
self.needs_reset = true;
Ok(Rows::new(self))
}
fn bind_parameters_named(&mut self, params: &[(&str, &ToSql)]) -> Result<()> {
for &(name, value) in params {
if let Some(i) = try!(self.parameter_index(name)) {
try!(self.conn.decode_result(unsafe { value.bind_parameter(self.stmt, i) }));
} else {
return Err(Error {
code: ffi::SQLITE_MISUSE,
message: format!("Invalid parameter name: {}", name),
});
}
}
Ok(())
}
}
#[cfg(test)]
mod test {
use Connection;
#[test]
fn test_execute_named() {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)]).unwrap(),
1);
assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)]).unwrap(),
1);
assert_eq!(3i32,
db.query_row_named("SELECT SUM(x) FROM foo WHERE x > :x",
&[(":x", &0i32)],
|r| r.get(0))
.unwrap());
}
#[test]
fn test_stmt_execute_named() {
let db = Connection::open_in_memory().unwrap();
let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
INTEGER)";
db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)").unwrap();
stmt.execute_named(&[(":name", &"one")]).unwrap();
assert_eq!(1i32,
db.query_row_named("SELECT COUNT(*) FROM test WHERE name = :name",
&[(":name", &"one")],
|r| r.get(0))
.unwrap());
}
#[test]
fn test_query_named() {
let db = Connection::open_in_memory().unwrap();
let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
INTEGER)";
db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("SELECT * FROM test where name = :name").unwrap();
stmt.query_named(&[(":name", &"one")]).unwrap();
}
#[test]
fn test_unbound_parameters_are_null() {
let db = Connection::open_in_memory().unwrap();
let sql = "CREATE TABLE test (x TEXT, y TEXT)";
db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)").unwrap();
stmt.execute_named(&[(":x", &"one")]).unwrap();
let result = db.query_row("SELECT y FROM test WHERE x = 'one'", &[],
|row| row.get::<Option<String>>(0)).unwrap();
assert!(result.is_none());
}
#[test]
fn test_unbound_parameters_are_reused() {
let db = Connection::open_in_memory().unwrap();
let sql = "CREATE TABLE test (x TEXT, y TEXT)";
db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)").unwrap();
stmt.execute_named(&[(":x", &"one")]).unwrap();
stmt.execute_named(&[(":y", &"two")]).unwrap();
let result = db.query_row("SELECT x FROM test WHERE y = 'two'", &[],
|row| row.get::<String>(0)).unwrap();
assert_eq!(result, "one");
}
}

View File

@ -8,7 +8,7 @@ use std::str;
use std::time::Duration; use std::time::Duration;
use super::ffi; use super::ffi;
use {SqliteError, SqliteResult, SqliteConnection}; use {Error, Result, Connection};
/// Set up the process-wide SQLite error logging callback. /// Set up the process-wide SQLite error logging callback.
/// This function is marked unsafe for two reasons: /// This function is marked unsafe for two reasons:
@ -21,7 +21,7 @@ use {SqliteError, SqliteResult, SqliteConnection};
/// * It must be threadsafe if SQLite is used in a multithreaded way. /// * It must be threadsafe if SQLite is used in a multithreaded way.
/// ///
/// cf [The Error And Warning Log](http://sqlite.org/errlog.html). /// cf [The Error And Warning Log](http://sqlite.org/errlog.html).
pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> SqliteResult<()> { pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
extern "C" fn log_callback(p_arg: *mut c_void, err: c_int, msg: *const c_char) { extern "C" fn log_callback(p_arg: *mut c_void, err: c_int, msg: *const c_char) {
let c_slice = unsafe { CStr::from_ptr(msg).to_bytes() }; let c_slice = unsafe { CStr::from_ptr(msg).to_bytes() };
let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) }; let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
@ -35,7 +35,7 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> SqliteResult<()>
Some(f) => { Some(f) => {
let p_arg: *mut c_void = mem::transmute(f); let p_arg: *mut c_void = mem::transmute(f);
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, Some(log_callback), p_arg) ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, Some(log_callback), p_arg)
}, }
None => { None => {
let nullptr: *mut c_void = ptr::null_mut(); let nullptr: *mut c_void = ptr::null_mut();
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr) ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
@ -43,7 +43,10 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> SqliteResult<()>
}; };
if rc != ffi::SQLITE_OK { if rc != ffi::SQLITE_OK {
return Err(SqliteError{ code: rc, message: "sqlite3_config(SQLITE_CONFIG_LOG, ...)".to_string() }); return Err(Error {
code: rc,
message: "sqlite3_config(SQLITE_CONFIG_LOG, ...)".to_string(),
});
} }
Ok(()) Ok(())
@ -57,14 +60,14 @@ pub fn log(err_code: c_int, msg: &str) {
} }
} }
impl SqliteConnection { impl Connection {
/// Register or clear a callback function that can be used for tracing the execution of SQL statements. /// Register or clear a callback function that can be used for tracing the execution of SQL statements.
/// ///
/// Prepared statement placeholders are replaced/logged with their assigned values. /// Prepared statement placeholders are replaced/logged with their assigned values.
/// There can only be a single tracer defined for each database connection. /// There can only be a single tracer defined for each database connection.
/// Setting a new tracer clears the old one. /// Setting a new tracer clears the old one.
pub fn trace(&mut self, trace_fn: Option<fn(&str)>) { pub fn trace(&mut self, trace_fn: Option<fn(&str)>) {
extern "C" fn trace_callback (p_arg: *mut c_void, z_sql: *const c_char) { extern "C" fn trace_callback(p_arg: *mut c_void, z_sql: *const c_char) {
let trace_fn: fn(&str) = unsafe { mem::transmute(p_arg) }; let trace_fn: fn(&str) = unsafe { mem::transmute(p_arg) };
let c_slice = unsafe { CStr::from_ptr(z_sql).to_bytes() }; let c_slice = unsafe { CStr::from_ptr(z_sql).to_bytes() };
if let Ok(s) = str::from_utf8(c_slice) { if let Ok(s) = str::from_utf8(c_slice) {
@ -74,8 +77,12 @@ impl SqliteConnection {
let c = self.db.borrow_mut(); let c = self.db.borrow_mut();
match trace_fn { match trace_fn {
Some(f) => unsafe { ffi::sqlite3_trace(c.db(), Some(trace_callback), mem::transmute(f)); }, Some(f) => unsafe {
None => unsafe { ffi::sqlite3_trace(c.db(), None, ptr::null_mut()); }, ffi::sqlite3_trace(c.db(), Some(trace_callback), mem::transmute(f));
},
None => unsafe {
ffi::sqlite3_trace(c.db(), None, ptr::null_mut());
},
} }
} }
@ -84,7 +91,9 @@ impl SqliteConnection {
/// There can only be a single profiler defined for each database connection. /// There can only be a single profiler defined for each database connection.
/// Setting a new profiler clears the old one. /// Setting a new profiler clears the old one.
pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) { pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
extern "C" fn profile_callback(p_arg: *mut c_void, z_sql: *const c_char, nanoseconds: u64) { extern "C" fn profile_callback(p_arg: *mut c_void,
z_sql: *const c_char,
nanoseconds: u64) {
let profile_fn: fn(&str, Duration) = unsafe { mem::transmute(p_arg) }; let profile_fn: fn(&str, Duration) = unsafe { mem::transmute(p_arg) };
let c_slice = unsafe { CStr::from_ptr(z_sql).to_bytes() }; let c_slice = unsafe { CStr::from_ptr(z_sql).to_bytes() };
if let Ok(s) = str::from_utf8(c_slice) { if let Ok(s) = str::from_utf8(c_slice) {
@ -98,8 +107,10 @@ impl SqliteConnection {
let c = self.db.borrow_mut(); let c = self.db.borrow_mut();
match profile_fn { match profile_fn {
Some(f) => unsafe { ffi::sqlite3_profile(c.db(), Some(profile_callback), mem::transmute(f)) }, Some(f) => unsafe {
None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) }, ffi::sqlite3_profile(c.db(), Some(profile_callback), mem::transmute(f))
},
None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) },
}; };
} }
} }
@ -109,7 +120,7 @@ mod test {
use std::sync::Mutex; use std::sync::Mutex;
use std::time::Duration; use std::time::Duration;
use SqliteConnection; use Connection;
#[test] #[test]
fn test_trace() { fn test_trace() {
@ -121,7 +132,7 @@ mod test {
traced_stmts.push(s.to_owned()); traced_stmts.push(s.to_owned());
} }
let mut db = SqliteConnection::open_in_memory().unwrap(); let mut db = Connection::open_in_memory().unwrap();
db.trace(Some(tracer)); db.trace(Some(tracer));
{ {
let _ = db.query_row("SELECT ?", &[&1i32], |_| {}); let _ = db.query_row("SELECT ?", &[&1i32], |_| {});
@ -149,7 +160,7 @@ mod test {
profiled.push((s.to_owned(), d)); profiled.push((s.to_owned(), d));
} }
let mut db = SqliteConnection::open_in_memory().unwrap(); let mut db = Connection::open_in_memory().unwrap();
db.profile(Some(profiler)); db.profile(Some(profiler));
db.execute_batch("PRAGMA application_id = 1").unwrap(); db.execute_batch("PRAGMA application_id = 1").unwrap();
db.profile(None); db.profile(None);

View File

@ -1,19 +1,21 @@
use {SqliteResult, SqliteConnection}; use {Result, Connection};
pub use SqliteTransactionBehavior::{ /// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is deprecated.
SqliteTransactionDeferred, pub type SqliteTransactionBehavior = TransactionBehavior;
SqliteTransactionImmediate,
SqliteTransactionExclusive};
/// Options for transaction behavior. See [BEGIN /// Options for transaction behavior. See [BEGIN
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details. /// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
#[derive(Copy,Clone)] #[derive(Copy,Clone)]
pub enum SqliteTransactionBehavior { pub enum TransactionBehavior {
SqliteTransactionDeferred, Deferred,
SqliteTransactionImmediate, Immediate,
SqliteTransactionExclusive, Exclusive,
} }
/// Old name for `Transaction`. `SqliteTransaction` is deprecated.
pub type SqliteTransaction<'conn> = Transaction<'conn>;
///
/// Represents a transaction on a database connection. /// Represents a transaction on a database connection.
/// ///
/// ## Note /// ## Note
@ -24,10 +26,10 @@ pub enum SqliteTransactionBehavior {
/// ## Example /// ## Example
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{SqliteConnection, SqliteResult}; /// # use rusqlite::{Connection, Result};
/// # fn do_queries_part_1(conn: &SqliteConnection) -> SqliteResult<()> { Ok(()) } /// # fn do_queries_part_1(conn: &Connection) -> Result<()> { Ok(()) }
/// # fn do_queries_part_2(conn: &SqliteConnection) -> SqliteResult<()> { Ok(()) } /// # fn do_queries_part_2(conn: &Connection) -> Result<()> { Ok(()) }
/// fn perform_queries(conn: &SqliteConnection) -> SqliteResult<()> { /// fn perform_queries(conn: &Connection) -> Result<()> {
/// let tx = try!(conn.transaction()); /// let tx = try!(conn.transaction());
/// ///
/// try!(do_queries_part_1(conn)); // tx causes rollback if this fails /// try!(do_queries_part_1(conn)); // tx causes rollback if this fails
@ -36,24 +38,30 @@ pub enum SqliteTransactionBehavior {
/// tx.commit() /// tx.commit()
/// } /// }
/// ``` /// ```
pub struct SqliteTransaction<'conn> { pub struct Transaction<'conn> {
conn: &'conn SqliteConnection, conn: &'conn Connection,
depth: u32, depth: u32,
commit: bool, commit: bool,
finished: bool, finished: bool,
} }
impl<'conn> SqliteTransaction<'conn> { impl<'conn> Transaction<'conn> {
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested transactions. /// Begin a new transaction. Cannot be nested; see `savepoint` for nested transactions.
pub fn new(conn: &SqliteConnection, pub fn new(conn: &Connection,
behavior: SqliteTransactionBehavior) -> SqliteResult<SqliteTransaction> { behavior: TransactionBehavior)
-> Result<Transaction> {
let query = match behavior { let query = match behavior {
SqliteTransactionDeferred => "BEGIN DEFERRED", TransactionBehavior::Deferred => "BEGIN DEFERRED",
SqliteTransactionImmediate => "BEGIN IMMEDIATE", TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
SqliteTransactionExclusive => "BEGIN EXCLUSIVE", TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
}; };
conn.execute_batch(query).map(|_| { conn.execute_batch(query).map(|_| {
SqliteTransaction{ conn: conn, depth: 0, commit: false, finished: false } Transaction {
conn: conn,
depth: 0,
commit: false,
finished: false,
}
}) })
} }
@ -67,9 +75,9 @@ impl<'conn> SqliteTransaction<'conn> {
/// ## Example /// ## Example
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{SqliteConnection, SqliteResult}; /// # use rusqlite::{Connection, Result};
/// # fn perform_queries_part_1_succeeds(conn: &SqliteConnection) -> bool { true } /// # fn perform_queries_part_1_succeeds(conn: &Connection) -> bool { true }
/// fn perform_queries(conn: &SqliteConnection) -> SqliteResult<()> { /// fn perform_queries(conn: &Connection) -> Result<()> {
/// let tx = try!(conn.transaction()); /// let tx = try!(conn.transaction());
/// ///
/// { /// {
@ -83,10 +91,13 @@ impl<'conn> SqliteTransaction<'conn> {
/// tx.commit() /// tx.commit()
/// } /// }
/// ``` /// ```
pub fn savepoint<'a>(&'a self) -> SqliteResult<SqliteTransaction<'a>> { pub fn savepoint<'a>(&'a self) -> Result<Transaction<'a>> {
self.conn.execute_batch("SAVEPOINT sp").map(|_| { self.conn.execute_batch("SAVEPOINT sp").map(|_| {
SqliteTransaction{ Transaction {
conn: self.conn, depth: self.depth + 1, commit: false, finished: false conn: self.conn,
depth: self.depth + 1,
commit: false,
finished: false,
} }
}) })
} }
@ -112,23 +123,31 @@ impl<'conn> SqliteTransaction<'conn> {
} }
/// A convenience method which consumes and commits a transaction. /// A convenience method which consumes and commits a transaction.
pub fn commit(mut self) -> SqliteResult<()> { pub fn commit(mut self) -> Result<()> {
self.commit_() self.commit_()
} }
fn commit_(&mut self) -> SqliteResult<()> { fn commit_(&mut self) -> Result<()> {
self.finished = true; self.finished = true;
self.conn.execute_batch(if self.depth == 0 { "COMMIT" } else { "RELEASE sp" }) self.conn.execute_batch(if self.depth == 0 {
"COMMIT"
} else {
"RELEASE sp"
})
} }
/// A convenience method which consumes and rolls back a transaction. /// A convenience method which consumes and rolls back a transaction.
pub fn rollback(mut self) -> SqliteResult<()> { pub fn rollback(mut self) -> Result<()> {
self.rollback_() self.rollback_()
} }
fn rollback_(&mut self) -> SqliteResult<()> { fn rollback_(&mut self) -> Result<()> {
self.finished = true; self.finished = true;
self.conn.execute_batch(if self.depth == 0 { "ROLLBACK" } else { "ROLLBACK TO sp" }) self.conn.execute_batch(if self.depth == 0 {
"ROLLBACK"
} else {
"ROLLBACK TO sp"
})
} }
/// Consumes the transaction, committing or rolling back according to the current setting /// Consumes the transaction, committing or rolling back according to the current setting
@ -136,11 +155,11 @@ impl<'conn> SqliteTransaction<'conn> {
/// ///
/// Functionally equivalent to the `Drop` implementation, but allows callers to see any /// Functionally equivalent to the `Drop` implementation, but allows callers to see any
/// errors that occur. /// errors that occur.
pub fn finish(mut self) -> SqliteResult<()> { pub fn finish(mut self) -> Result<()> {
self.finish_() self.finish_()
} }
fn finish_(&mut self) -> SqliteResult<()> { fn finish_(&mut self) -> Result<()> {
match (self.finished, self.commit) { match (self.finished, self.commit) {
(true, _) => Ok(()), (true, _) => Ok(()),
(false, true) => self.commit_(), (false, true) => self.commit_(),
@ -150,7 +169,7 @@ impl<'conn> SqliteTransaction<'conn> {
} }
#[allow(unused_must_use)] #[allow(unused_must_use)]
impl<'conn> Drop for SqliteTransaction<'conn> { impl<'conn> Drop for Transaction<'conn> {
fn drop(&mut self) { fn drop(&mut self) {
self.finish_(); self.finish_();
} }
@ -158,10 +177,10 @@ impl<'conn> Drop for SqliteTransaction<'conn> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use SqliteConnection; use Connection;
fn checked_memory_handle() -> SqliteConnection { fn checked_memory_handle() -> Connection {
let db = SqliteConnection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo (x INTEGER)").unwrap(); db.execute_batch("CREATE TABLE foo (x INTEGER)").unwrap();
db db
} }
@ -181,7 +200,8 @@ mod test {
} }
{ {
let _tx = db.transaction().unwrap(); let _tx = db.transaction().unwrap();
assert_eq!(2i32, db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap()); assert_eq!(2i32,
db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
} }
} }
@ -200,7 +220,8 @@ mod test {
} }
{ {
let _tx = db.transaction().unwrap(); let _tx = db.transaction().unwrap();
assert_eq!(2i32, db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap()); assert_eq!(2i32,
db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
} }
} }
@ -228,6 +249,7 @@ mod test {
} }
} }
} }
assert_eq!(3i32, db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap()); assert_eq!(3i32,
db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
} }
} }

View File

@ -26,7 +26,7 @@
//! extern crate libc; //! extern crate libc;
//! //!
//! use rusqlite::types::{FromSql, ToSql, sqlite3_stmt}; //! use rusqlite::types::{FromSql, ToSql, sqlite3_stmt};
//! use rusqlite::{SqliteResult}; //! use rusqlite::{Result};
//! use libc::c_int; //! use libc::c_int;
//! use time; //! use time;
//! //!
@ -34,7 +34,7 @@
//! //!
//! impl FromSql for TimespecSql { //! impl FromSql for TimespecSql {
//! unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) //! unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int)
//! -> SqliteResult<TimespecSql> { //! -> Result<TimespecSql> {
//! let as_f64_result = FromSql::column_result(stmt, col); //! let as_f64_result = FromSql::column_result(stmt, col);
//! as_f64_result.map(|as_f64: f64| { //! as_f64_result.map(|as_f64: f64| {
//! TimespecSql(time::Timespec{ sec: as_f64.trunc() as i64, //! TimespecSql(time::Timespec{ sec: as_f64.trunc() as i64,
@ -55,14 +55,14 @@
extern crate time; extern crate time;
use libc::{c_int, c_double, c_char}; use libc::{c_int, c_double, c_char};
use std::ffi::{CStr}; use std::ffi::CStr;
use std::mem; use std::mem;
use std::str; use std::str;
use super::ffi; use super::ffi;
use super::{SqliteResult, SqliteError, str_to_cstring}; use super::{Result, Error, str_to_cstring};
pub use ffi::sqlite3_stmt as sqlite3_stmt; pub use ffi::sqlite3_stmt;
pub use ffi::sqlite3_column_type as sqlite3_column_type; pub use ffi::sqlite3_column_type;
pub use ffi::{SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NULL}; pub use ffi::{SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NULL};
@ -75,11 +75,11 @@ pub trait ToSql {
/// A trait for types that can be created from a SQLite value. /// A trait for types that can be created from a SQLite value.
pub trait FromSql: Sized { pub trait FromSql: Sized {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> SqliteResult<Self>; unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<Self>;
/// FromSql types can implement this method and use sqlite3_column_type to check that /// FromSql types can implement this method and use sqlite3_column_type to check that
/// the type reported by SQLite matches a type suitable for Self. This method is used /// the type reported by SQLite matches a type suitable for Self. This method is used
/// by `SqliteRow::get_checked` to confirm that the column contains a valid type before /// by `Row::get_checked` to confirm that the column contains a valid type before
/// attempting to retrieve the value. /// attempting to retrieve the value.
unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool {
true true
@ -100,6 +100,15 @@ raw_to_impl!(c_int, sqlite3_bind_int);
raw_to_impl!(i64, sqlite3_bind_int64); raw_to_impl!(i64, sqlite3_bind_int64);
raw_to_impl!(c_double, sqlite3_bind_double); raw_to_impl!(c_double, sqlite3_bind_double);
impl ToSql for bool {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
match *self {
true => ffi::sqlite3_bind_int(stmt, col, 1),
_ => ffi::sqlite3_bind_int(stmt, col, 0),
}
}
}
impl<'a> ToSql for &'a str { impl<'a> ToSql for &'a str {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
let length = self.len(); let length = self.len();
@ -107,9 +116,14 @@ impl<'a> ToSql for &'a str {
return ffi::SQLITE_TOOBIG; return ffi::SQLITE_TOOBIG;
} }
match str_to_cstring(self) { match str_to_cstring(self) {
Ok(c_str) => ffi::sqlite3_bind_text(stmt, col, c_str.as_ptr(), length as c_int, Ok(c_str) => {
ffi::SQLITE_TRANSIENT()), ffi::sqlite3_bind_text(stmt,
Err(_) => ffi::SQLITE_MISUSE, col,
c_str.as_ptr(),
length as c_int,
ffi::SQLITE_TRANSIENT())
}
Err(_) => ffi::SQLITE_MISUSE,
} }
} }
} }
@ -125,8 +139,11 @@ impl<'a> ToSql for &'a [u8] {
if self.len() > ::std::i32::MAX as usize { if self.len() > ::std::i32::MAX as usize {
return ffi::SQLITE_TOOBIG; return ffi::SQLITE_TOOBIG;
} }
ffi::sqlite3_bind_blob( ffi::sqlite3_bind_blob(stmt,
stmt, col, mem::transmute(self.as_ptr()), self.len() as c_int, ffi::SQLITE_TRANSIENT()) col,
mem::transmute(self.as_ptr()),
self.len() as c_int,
ffi::SQLITE_TRANSIENT())
} }
} }
@ -159,12 +176,12 @@ impl<T: ToSql> ToSql for Option<T> {
/// ```rust,no_run /// ```rust,no_run
/// # extern crate libc; /// # extern crate libc;
/// # extern crate rusqlite; /// # extern crate rusqlite;
/// # use rusqlite::{SqliteConnection, SqliteResult}; /// # use rusqlite::{Connection, Result};
/// # use rusqlite::types::{Null}; /// # use rusqlite::types::{Null};
/// # use libc::{c_int}; /// # use libc::{c_int};
/// fn main() { /// fn main() {
/// } /// }
/// fn insert_null(conn: &SqliteConnection) -> SqliteResult<c_int> { /// fn insert_null(conn: &Connection) -> Result<c_int> {
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[&Null]) /// conn.execute("INSERT INTO people (name) VALUES (?)", &[&Null])
/// } /// }
/// ``` /// ```
@ -180,7 +197,7 @@ impl ToSql for Null {
macro_rules! raw_from_impl( macro_rules! raw_from_impl(
($t:ty, $f:ident, $c:expr) => ( ($t:ty, $f:ident, $c:expr) => (
impl FromSql for $t { impl FromSql for $t {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> SqliteResult<$t> { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<$t> {
Ok(ffi::$f(stmt, col)) Ok(ffi::$f(stmt, col))
} }
@ -195,17 +212,34 @@ raw_from_impl!(c_int, sqlite3_column_int, ffi::SQLITE_INTEGER);
raw_from_impl!(i64, sqlite3_column_int64, ffi::SQLITE_INTEGER); raw_from_impl!(i64, sqlite3_column_int64, ffi::SQLITE_INTEGER);
raw_from_impl!(c_double, sqlite3_column_double, ffi::SQLITE_FLOAT); raw_from_impl!(c_double, sqlite3_column_double, ffi::SQLITE_FLOAT);
impl FromSql for bool {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<bool> {
match ffi::sqlite3_column_int(stmt, col) {
0 => Ok(false),
_ => Ok(true),
}
}
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
sqlite3_column_type(stmt, col) == ffi::SQLITE_INTEGER
}
}
impl FromSql for String { impl FromSql for String {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> SqliteResult<String> { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<String> {
let c_text = ffi::sqlite3_column_text(stmt, col); let c_text = ffi::sqlite3_column_text(stmt, col);
if c_text.is_null() { if c_text.is_null() {
Ok("".to_string()) Ok("".to_string())
} else { } else {
let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes(); let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes();
let utf8_str = str::from_utf8(c_slice); let utf8_str = str::from_utf8(c_slice);
utf8_str utf8_str.map(|s| s.to_string())
.map(|s| { s.to_string() }) .map_err(|e| {
.map_err(|e| { SqliteError{code: 0, message: e.to_string()} }) Error {
code: 0,
message: e.to_string(),
}
})
} }
} }
@ -215,14 +249,15 @@ impl FromSql for String {
} }
impl FromSql for Vec<u8> { impl FromSql for Vec<u8> {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> SqliteResult<Vec<u8>> { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<Vec<u8>> {
use std::slice::from_raw_parts; use std::slice::from_raw_parts;
let c_blob = ffi::sqlite3_column_blob(stmt, col); let c_blob = ffi::sqlite3_column_blob(stmt, col);
let len = ffi::sqlite3_column_bytes(stmt, col); let len = ffi::sqlite3_column_bytes(stmt, col);
// The documentation for sqlite3_column_bytes indicates it is always non-negative, // The documentation for sqlite3_column_bytes indicates it is always non-negative,
// but we should assert here just to be sure. // but we should assert here just to be sure.
assert!(len >= 0, "unexpected negative return from sqlite3_column_bytes"); assert!(len >= 0,
"unexpected negative return from sqlite3_column_bytes");
let len = len as usize; let len = len as usize;
Ok(from_raw_parts(mem::transmute(c_blob), len).to_vec()) Ok(from_raw_parts(mem::transmute(c_blob), len).to_vec())
@ -234,15 +269,17 @@ impl FromSql for Vec<u8> {
} }
impl FromSql for time::Timespec { impl FromSql for time::Timespec {
unsafe fn column_result(stmt: *mut sqlite3_stmt, unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<time::Timespec> {
col: c_int) -> SqliteResult<time::Timespec> {
let col_str = FromSql::column_result(stmt, col); let col_str = FromSql::column_result(stmt, col);
col_str.and_then(|txt: String| { col_str.and_then(|txt: String| {
time::strptime(&txt, SQLITE_DATETIME_FMT).map(|tm| { time::strptime(&txt, SQLITE_DATETIME_FMT)
tm.to_timespec() .map(|tm| tm.to_timespec())
}).map_err(|parse_error| { .map_err(|parse_error| {
SqliteError{ code: ffi::SQLITE_MISMATCH, message: format!("{}", parse_error) } Error {
}) code: ffi::SQLITE_MISMATCH,
message: format!("{}", parse_error),
}
})
}) })
} }
@ -252,7 +289,7 @@ impl FromSql for time::Timespec {
} }
impl<T: FromSql> FromSql for Option<T> { impl<T: FromSql> FromSql for Option<T> {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> SqliteResult<Option<T>> { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<Option<T>> {
if sqlite3_column_type(stmt, col) == ffi::SQLITE_NULL { if sqlite3_column_type(stmt, col) == ffi::SQLITE_NULL {
Ok(None) Ok(None)
} else { } else {
@ -262,19 +299,19 @@ impl<T: FromSql> FromSql for Option<T> {
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
sqlite3_column_type(stmt, col) == ffi::SQLITE_NULL || sqlite3_column_type(stmt, col) == ffi::SQLITE_NULL ||
T::column_has_valid_sqlite_type(stmt, col) T::column_has_valid_sqlite_type(stmt, col)
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use SqliteConnection; use Connection;
use ffi; use ffi;
use super::time; use super::time;
use libc::{c_int, c_double}; use libc::{c_int, c_double};
fn checked_memory_handle() -> SqliteConnection { fn checked_memory_handle() -> Connection {
let db = SqliteConnection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT, i INTEGER, f FLOAT, n)").unwrap(); db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT, i INTEGER, f FLOAT, n)").unwrap();
db db
} }
@ -283,7 +320,7 @@ mod test {
fn test_blob() { fn test_blob() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let v1234 = vec![1u8,2,3,4]; let v1234 = vec![1u8, 2, 3, 4];
db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234]).unwrap(); db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234]).unwrap();
let v: Vec<u8> = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap(); let v: Vec<u8> = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap();
@ -305,7 +342,10 @@ mod test {
fn test_timespec() { fn test_timespec() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let ts = time::Timespec{sec: 10_000, nsec: 0 }; let ts = time::Timespec {
sec: 10_000,
nsec: 0,
};
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap(); db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
let from: time::Timespec = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); let from: time::Timespec = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
@ -317,7 +357,7 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
let s = Some("hello, world!"); let s = Some("hello, world!");
let b = Some(vec![1u8,2,3,4]); let b = Some(vec![1u8, 2, 3, 4]);
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap(); db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
db.execute("INSERT INTO foo(b) VALUES (?)", &[&b]).unwrap(); db.execute("INSERT INTO foo(b) VALUES (?)", &[&b]).unwrap();
@ -342,7 +382,9 @@ mod test {
fn test_mismatched_types() { fn test_mismatched_types() {
let db = checked_memory_handle(); let db = checked_memory_handle();
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)", &[]).unwrap(); db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
&[])
.unwrap();
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap(); let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
let mut rows = stmt.query(&[]).unwrap(); let mut rows = stmt.query(&[]).unwrap();
@ -350,10 +392,10 @@ mod test {
let row = rows.next().unwrap().unwrap(); let row = rows.next().unwrap().unwrap();
// check the correct types come back as expected // check the correct types come back as expected
assert_eq!(vec![1,2], row.get_checked::<Vec<u8>>(0).unwrap()); assert_eq!(vec![1, 2], row.get_checked::<Vec<u8>>(0).unwrap());
assert_eq!("text", row.get_checked::<String>(1).unwrap()); assert_eq!("text", row.get_checked::<String>(1).unwrap());
assert_eq!(1, row.get_checked::<c_int>(2).unwrap()); assert_eq!(1, row.get_checked::<c_int>(2).unwrap());
assert_eq!(1.5, row.get_checked::<c_double>(3).unwrap()); assert_eq!(1.5, row.get_checked::<c_double>(3).unwrap());
assert!(row.get_checked::<Option<c_int>>(4).unwrap().is_none()); assert!(row.get_checked::<Option<c_int>>(4).unwrap().is_none());
assert!(row.get_checked::<Option<c_double>>(4).unwrap().is_none()); assert!(row.get_checked::<Option<c_double>>(4).unwrap().is_none());
assert!(row.get_checked::<Option<String>>(4).unwrap().is_none()); assert!(row.get_checked::<Option<String>>(4).unwrap().is_none());
@ -361,41 +403,69 @@ mod test {
// check some invalid types // check some invalid types
// 0 is actually a blob (Vec<u8>) // 0 is actually a blob (Vec<u8>)
assert_eq!(row.get_checked::<c_int>(0).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<c_int>(0).err().unwrap().code,
assert_eq!(row.get_checked::<i64>(0).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<c_double>(0).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<i64>(0).err().unwrap().code,
assert_eq!(row.get_checked::<String>(0).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<time::Timespec>(0).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<c_double>(0).err().unwrap().code,
assert_eq!(row.get_checked::<Option<c_int>>(0).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<String>(0).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<time::Timespec>(0).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Option<c_int>>(0).err().unwrap().code,
ffi::SQLITE_MISMATCH);
// 1 is actually a text (String) // 1 is actually a text (String)
assert_eq!(row.get_checked::<c_int>(1).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<c_int>(1).err().unwrap().code,
assert_eq!(row.get_checked::<i64>(1).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<c_double>(1).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<i64>(1).err().unwrap().code,
assert_eq!(row.get_checked::<Vec<u8>>(1).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Option<c_int>>(1).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<c_double>(1).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Vec<u8>>(1).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Option<c_int>>(1).err().unwrap().code,
ffi::SQLITE_MISMATCH);
// 2 is actually an integer // 2 is actually an integer
assert_eq!(row.get_checked::<c_double>(2).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<c_double>(2).err().unwrap().code,
assert_eq!(row.get_checked::<String>(2).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Vec<u8>>(2).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<String>(2).err().unwrap().code,
assert_eq!(row.get_checked::<time::Timespec>(2).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Option<c_double>>(2).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<Vec<u8>>(2).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<time::Timespec>(2).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Option<c_double>>(2).err().unwrap().code,
ffi::SQLITE_MISMATCH);
// 3 is actually a float (c_double) // 3 is actually a float (c_double)
assert_eq!(row.get_checked::<c_int>(3).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<c_int>(3).err().unwrap().code,
assert_eq!(row.get_checked::<i64>(3).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<String>(3).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<i64>(3).err().unwrap().code,
assert_eq!(row.get_checked::<Vec<u8>>(3).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<time::Timespec>(3).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<String>(3).err().unwrap().code,
assert_eq!(row.get_checked::<Option<c_int>>(3).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Vec<u8>>(3).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<time::Timespec>(3).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Option<c_int>>(3).err().unwrap().code,
ffi::SQLITE_MISMATCH);
// 4 is actually NULL // 4 is actually NULL
assert_eq!(row.get_checked::<c_int>(4).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<c_int>(4).err().unwrap().code,
assert_eq!(row.get_checked::<i64>(4).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<c_double>(4).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<i64>(4).err().unwrap().code,
assert_eq!(row.get_checked::<String>(4).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Vec<u8>>(4).err().unwrap().code, ffi::SQLITE_MISMATCH); assert_eq!(row.get_checked::<c_double>(4).err().unwrap().code,
assert_eq!(row.get_checked::<time::Timespec>(4).err().unwrap().code, ffi::SQLITE_MISMATCH); ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<String>(4).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Vec<u8>>(4).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<time::Timespec>(4).err().unwrap().code,
ffi::SQLITE_MISMATCH);
} }
} }