mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 09:09:19 +08:00
Merge branch 'master' of https://github.com/jgallagher/rusqlite into stmt-cache
This commit is contained in:
commit
d5faf2fab3
@ -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 ] &&
|
||||||
|
@ -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)
|
||||||
|
@ -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"
|
||||||
|
26
Changelog.md
26
Changelog.md
@ -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)
|
||||||
|
|
||||||
|
26
README.md
26
README.md
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
414
src/backup.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
27
src/cache.rs
27
src/cache.rs
@ -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
644
src/functions.rs
Normal 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(®ex_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(®ex_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
699
src/lib.rs
699
src/lib.rs
File diff suppressed because it is too large
Load Diff
@ -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
223
src/named_params.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
37
src/trace.rs
37
src/trace.rs
@ -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,7 +107,9 @@ 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 {
|
||||||
|
ffi::sqlite3_profile(c.db(), Some(profile_callback), mem::transmute(f))
|
||||||
|
},
|
||||||
None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) },
|
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);
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
198
src/types.rs
198
src/types.rs
@ -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,8 +116,13 @@ 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,
|
||||||
|
col,
|
||||||
|
c_str.as_ptr(),
|
||||||
|
length as c_int,
|
||||||
|
ffi::SQLITE_TRANSIENT())
|
||||||
|
}
|
||||||
Err(_) => ffi::SQLITE_MISUSE,
|
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,14 +269,16 @@ 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 {
|
||||||
@ -268,13 +305,13 @@ impl<T: FromSql> FromSql for Option<T> {
|
|||||||
|
|
||||||
#[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,7 +392,7 @@ 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());
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user