Merge remote-tracking branch 'jgallagher/master' into 0.14

This commit is contained in:
gwenn 2018-08-16 17:47:55 +02:00
commit 33271764b1
45 changed files with 15519 additions and 5699 deletions

View File

@ -1,5 +1,4 @@
sudo: false sudo: false
dist: trusty
language: rust language: rust
@ -39,7 +38,8 @@ script:
- cargo test --features bundled - cargo test --features bundled
- cargo test --features sqlcipher - cargo test --features sqlcipher
- cargo test --features "unlock_notify bundled" - cargo test --features "unlock_notify bundled"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace" - cargo test --features "array csvtab vtab"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen" - cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled" - cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab buildtime_bindgen"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled buildtime_bindgen" - cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"

View File

@ -29,6 +29,10 @@ limits = []
hooks = [] hooks = []
sqlcipher = ["libsqlite3-sys/sqlcipher"] sqlcipher = ["libsqlite3-sys/sqlcipher"]
unlock_notify = ["libsqlite3-sys/unlock_notify"] unlock_notify = ["libsqlite3-sys/unlock_notify"]
vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"]
csvtab = ["csv", "vtab"]
# pointer passing interfaces: 3.20.0
array = ["vtab", "bundled"]
[dependencies] [dependencies]
time = "0.1.0" time = "0.1.0"
@ -36,6 +40,8 @@ bitflags = "1.0"
lru-cache = "0.1" lru-cache = "0.1"
chrono = { version = "0.4", optional = true } chrono = { version = "0.4", optional = true }
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true }
csv = { version = "1.0", optional = true }
lazy_static = { version = "1.0", optional = true }
[dev-dependencies] [dev-dependencies]
tempdir = "0.3" tempdir = "0.3"
@ -53,8 +59,11 @@ harness = false
[[test]] [[test]]
name = "deny_single_threaded_sqlite_config" name = "deny_single_threaded_sqlite_config"
[[test]]
name = "vtab"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace" ] features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace", "vtab" ]
all-features = false all-features = false
no-default-features = true no-default-features = true
default-target = "x86_64-unknown-linux-gnu" default-target = "x86_64-unknown-linux-gnu"

View File

@ -4,10 +4,10 @@
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/jgallagher/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/jgallagher/rusqlite) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/jgallagher/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/jgallagher/rusqlite)
[![dependency status](https://deps.rs/repo/github/jgallagher/rusqlite/status.svg)](https://deps.rs/repo/github/jgallagher/rusqlite) [![dependency status](https://deps.rs/repo/github/jgallagher/rusqlite/status.svg)](https://deps.rs/repo/github/jgallagher/rusqlite)
[![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite) [![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite)
[![Docs](https://docs.rs/rusqlite/badge.svg)](https://docs.rs/rusqlite)
Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). View the full an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
[API documentation](http://docs.rs/rusqlite/).
```rust ```rust
extern crate rusqlite; extern crate rusqlite;
@ -68,31 +68,36 @@ newer SQLite version; see details below.
### Optional Features ### Optional Features
Rusqlite provides several features that are behind [Cargo Rusqlite provides several features that are behind [Cargo
features](http://doc.crates.io/manifest.html#the-features-section). They are: features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are:
* [`load_extension`](http://jgallagher.github.io/rusqlite/rusqlite/struct.LoadExtensionGuard.html) * [`load_extension`](https://docs.rs/rusqlite/0.13.0/rusqlite/struct.LoadExtensionGuard.html)
allows loading dynamic library-based SQLite extensions. allows loading dynamic library-based SQLite extensions.
* [`backup`](http://jgallagher.github.io/rusqlite/rusqlite/backup/index.html) * [`backup`](https://docs.rs/rusqlite/0.13.0/rusqlite/backup/index.html)
allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later. allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later.
* [`functions`](http://jgallagher.github.io/rusqlite/rusqlite/functions/index.html) * [`functions`](https://docs.rs/rusqlite/0.13.0/rusqlite/functions/index.html)
allows you to load Rust closures into SQLite connections for use in queries. allows you to load Rust closures into SQLite connections for use in queries.
Note: This feature requires SQLite 3.7.3 or later. Note: This feature requires SQLite 3.7.3 or later.
* [`trace`](http://jgallagher.github.io/rusqlite/rusqlite/trace/index.html) * [`trace`](https://docs.rs/rusqlite/0.13.0/rusqlite/trace/index.html)
allows hooks into SQLite's tracing and profiling APIs. Note: This feature allows hooks into SQLite's tracing and profiling APIs. Note: This feature
requires SQLite 3.6.23 or later. requires SQLite 3.6.23 or later.
* [`blob`](http://jgallagher.github.io/rusqlite/rusqlite/blob/index.html) * [`blob`](https://docs.rs/rusqlite/0.13.0/rusqlite/blob/index.html)
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature
requires SQLite 3.7.4 or later. requires SQLite 3.7.4 or later.
* [`limits`](http://jgallagher.github.io/rusqlite/rusqlite/struct.Connection.html#method.limit) * [`limits`](https://docs.rs/rusqlite/0.13.0/rusqlite/struct.Connection.html#method.limit)
allows you to set and retrieve SQLite's per connection limits. allows you to set and retrieve SQLite's per connection limits.
* `chrono` implements [`FromSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.FromSql.html) * `chrono` implements [`FromSql`](https://docs.rs/rusqlite/0.13.0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.ToSql.html) for various and [`ToSql`](https://docs.rs/rusqlite/0.13.0/rusqlite/types/trait.ToSql.html) for various
types from the [`chrono` crate](https://crates.io/crates/chrono). types from the [`chrono` crate](https://crates.io/crates/chrono).
* `serde_json` implements [`FromSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.FromSql.html) * `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/0.13.0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.ToSql.html) for the and [`ToSql`](https://docs.rs/rusqlite/0.13.0/rusqlite/types/trait.ToSql.html) for the
`Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json). `Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json).
* `bundled` uses a bundled version of sqlite3. This is a good option for cases where linking to sqlite3 is complicated, such as Windows. * `bundled` uses a bundled version of sqlite3. This is a good option for cases where linking to sqlite3 is complicated, such as Windows.
* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`. * `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`.
* `hooks` for [Commit, Rollback](http://sqlite.org/c3ref/commit_hook.html) and [Data Change](http://sqlite.org/c3ref/update_hook.html) notification callbacks.
* `unlock_notify` for [Unlock](https://sqlite.org/unlock_notify.html) notification.
* `vtab` for [virtual table](https://sqlite.org/vtab.html) support (allows you to write virtual table implemntations in Rust). Currently, only read-only virtual tables are supported.
* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function.
## Notes on building rusqlite and libsqlite3-sys ## Notes on building rusqlite and libsqlite3-sys
@ -105,11 +110,11 @@ You can adjust this behavior in a number of ways:
* If you use the `bundled` feature, `libsqlite3-sys` will use the * If you use the `bundled` feature, `libsqlite3-sys` will use the
[gcc](https://crates.io/crates/gcc) crate to compile SQLite from source and [gcc](https://crates.io/crates/gcc) crate to compile SQLite from source and
link against that. This source is embedded in the `libsqlite3-sys` crate and link against that. This source is embedded in the `libsqlite3-sys` crate and
is currently SQLite 3.17.0 (as of `rusqlite` 0.10.1 / `libsqlite3-sys` is currently SQLite 3.24.0 (as of `rusqlite` 0.14.0 / `libsqlite3-sys`
0.7.1). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file: 0.9.3). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
``` ```
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.11.0" version = "0.14.0"
features = ["bundled"] features = ["bundled"]
``` ```
* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite * You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite

View File

@ -1,8 +1,8 @@
environment: environment:
matrix: matrix:
- TARGET: 1.25.0-x86_64-pc-windows-gnu - TARGET: 1.27.2-x86_64-pc-windows-gnu
MSYS2_BITS: 64 MSYS2_BITS: 64
- TARGET: 1.25.0-x86_64-pc-windows-msvc - TARGET: 1.27.2-x86_64-pc-windows-msvc
VCPKG_DEFAULT_TRIPLET: x64-windows VCPKG_DEFAULT_TRIPLET: x64-windows
VCPKGRS_DYNAMIC: 1 VCPKGRS_DYNAMIC: 1
- TARGET: nightly-x86_64-pc-windows-msvc - TARGET: nightly-x86_64-pc-windows-msvc
@ -34,10 +34,10 @@ build: false
test_script: test_script:
- cargo test --lib --verbose - cargo test --lib --verbose
- cargo test --lib --verbose --features bundled - cargo test --lib --verbose --features bundled
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace" - cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab"
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen" - cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab buildtime_bindgen"
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled" - cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled buildtime_bindgen" - cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
cache: cache:
- C:\Users\appveyor\.cargo - C:\Users\appveyor\.cargo

View File

@ -1,6 +1,6 @@
[package] [package]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.9.2" version = "0.9.3"
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"
@ -20,12 +20,13 @@ min_sqlite_version_3_6_11 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"] min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_3 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_3 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_7 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
# sqlite3_unlock_notify >= 3.6.12 # sqlite3_unlock_notify >= 3.6.12
unlock_notify = [] unlock_notify = []
[build-dependencies] [build-dependencies]
bindgen = { version = "0.36", optional = true } bindgen = { version = "0.38", optional = true }
pkg-config = { version = "0.3", optional = true } pkg-config = { version = "0.3", optional = true }
cc = { version = "1.0", optional = true } cc = { version = "1.0", optional = true }

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,8 @@ fn main() {
#[cfg(feature = "bundled")] #[cfg(feature = "bundled")]
mod build { mod build {
extern crate cc; extern crate cc;
use std::{env, fs};
use std::path::Path; use std::path::Path;
use std::{env, fs};
pub fn main() { pub fn main() {
if cfg!(feature = "sqlcipher") { if cfg!(feature = "sqlcipher") {
@ -43,6 +43,8 @@ mod build {
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY"); cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
} }
cfg.compile("libsqlite3.a"); cfg.compile("libsqlite3.a");
println!("cargo:lib_dir={}", out_dir);
} }
} }
@ -66,8 +68,10 @@ mod build {
match header { match header {
HeaderLocation::FromEnvironment => { HeaderLocation::FromEnvironment => {
let prefix = env_prefix(); let prefix = env_prefix();
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)) let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!(
.expect(&format!("{}_INCLUDE_DIR must be set if {}_LIB_DIR is set", prefix, prefix)); "{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
prefix, prefix
));
header.push_str("/sqlite3.h"); header.push_str("/sqlite3.h");
header header
} }
@ -86,6 +90,11 @@ mod build {
fn find_sqlite() -> HeaderLocation { fn find_sqlite() -> HeaderLocation {
let link_lib = link_lib(); let link_lib = link_lib();
println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix());
println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix());
if cfg!(target_os = "windows") {
println!("cargo:rerun-if-env-changed=PATH");
}
// Allow users to specify where to find SQLite. // Allow users to specify where to find SQLite.
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) { if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
println!("cargo:rustc-link-lib={}", link_lib); println!("cargo:rustc-link-lib={}", link_lib);
@ -98,7 +107,10 @@ mod build {
} }
// See if pkg-config can do everything for us. // See if pkg-config can do everything for us.
match pkg_config::Config::new().print_system_libs(false).probe(link_lib) { match pkg_config::Config::new()
.print_system_libs(false)
.probe(link_lib)
{
Ok(mut lib) => { Ok(mut lib) => {
if let Some(mut header) = lib.include_paths.pop() { if let Some(mut header) = lib.include_paths.pop() {
header.push("sqlite3.h"); header.push("sqlite3.h");
@ -155,25 +167,21 @@ mod build {
mod bindings { mod bindings {
use super::HeaderLocation; use super::HeaderLocation;
use std::{env, fs};
use std::path::Path; use std::path::Path;
use std::{env, fs};
#[cfg_attr(rustfmt, rustfmt_skip)]
static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[ static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[
"bindgen-bindings/bindgen_3.6.8.rs", "bindgen-bindings/bindgen_3.6.8.rs",
#[cfg(feature = "min_sqlite_version_3_6_11")] #[cfg(feature = "min_sqlite_version_3_6_11")]
"bindgen-bindings/bindgen_3.6.11.rs", "bindgen-bindings/bindgen_3.6.11.rs",
#[cfg(feature = "min_sqlite_version_3_6_23")] #[cfg(feature = "min_sqlite_version_3_6_23")]
"bindgen-bindings/bindgen_3.6.23.rs", "bindgen-bindings/bindgen_3.6.23.rs",
#[cfg(feature = "min_sqlite_version_3_7_3")] #[cfg(feature = "min_sqlite_version_3_7_3")]
"bindgen-bindings/bindgen_3.7.3.rs", "bindgen-bindings/bindgen_3.7.3.rs",
#[cfg(feature = "min_sqlite_version_3_7_4")] #[cfg(feature = "min_sqlite_version_3_7_4")]
"bindgen-bindings/bindgen_3.7.4.rs", "bindgen-bindings/bindgen_3.7.4.rs",
#[cfg(feature = "min_sqlite_version_3_7_7")]
"bindgen-bindings/bindgen_3.7.7.rs",
#[cfg(feature = "min_sqlite_version_3_7_16")] #[cfg(feature = "min_sqlite_version_3_7_16")]
"bindgen-bindings/bindgen_3.7.16.rs", "bindgen-bindings/bindgen_3.7.16.rs",
]; ];
@ -190,12 +198,12 @@ mod build {
mod bindings { mod bindings {
extern crate bindgen; extern crate bindgen;
use self::bindgen::callbacks::{ParseCallbacks, IntKind}; use self::bindgen::callbacks::{IntKind, ParseCallbacks};
use super::HeaderLocation; use super::HeaderLocation;
use std::env; use std::env;
use std::io::Write;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path; use std::path::Path;
#[derive(Debug)] #[derive(Debug)]
@ -242,7 +250,8 @@ mod build {
.open(path.clone()) .open(path.clone())
.expect(&format!("Could not write to {:?}", path)); .expect(&format!("Could not write to {:?}", path));
file.write_all(output.as_bytes()).expect(&format!("Could not write to {:?}", path)); file.write_all(output.as_bytes())
.expect(&format!("Could not write to {:?}", path));
} }
} }
} }

View File

@ -1,10 +1,10 @@
/* automatically generated by rust-bindgen */ /* automatically generated by rust-bindgen */
pub const __GNUC_VA_LIST: i32 = 1; pub const __GNUC_VA_LIST: i32 = 1;
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.22.0\0"; pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.24.0\0";
pub const SQLITE_VERSION_NUMBER: i32 = 3022000; pub const SQLITE_VERSION_NUMBER: i32 = 3024000;
pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] = pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] =
b"2018-01-22 18:45:57 0c55d179733b46d8d0ba4d88e01a25e10677046ee3da1d5b1581e86726f2171d\0"; b"2018-06-04 19:24:41 c7ee0833225bfd8c5ec2f9bf62b97c4e04d03bd9566366d5221ac8fb199a87ca\0";
pub const SQLITE_OK: i32 = 0; pub const SQLITE_OK: i32 = 0;
pub const SQLITE_ERROR: i32 = 1; pub const SQLITE_ERROR: i32 = 1;
pub const SQLITE_INTERNAL: i32 = 2; pub const SQLITE_INTERNAL: i32 = 2;
@ -70,6 +70,7 @@ pub const SQLITE_IOERR_BEGIN_ATOMIC: i32 = 7434;
pub const SQLITE_IOERR_COMMIT_ATOMIC: i32 = 7690; pub const SQLITE_IOERR_COMMIT_ATOMIC: i32 = 7690;
pub const SQLITE_IOERR_ROLLBACK_ATOMIC: i32 = 7946; pub const SQLITE_IOERR_ROLLBACK_ATOMIC: i32 = 7946;
pub const SQLITE_LOCKED_SHAREDCACHE: i32 = 262; pub const SQLITE_LOCKED_SHAREDCACHE: i32 = 262;
pub const SQLITE_LOCKED_VTAB: i32 = 518;
pub const SQLITE_BUSY_RECOVERY: i32 = 261; pub const SQLITE_BUSY_RECOVERY: i32 = 261;
pub const SQLITE_BUSY_SNAPSHOT: i32 = 517; pub const SQLITE_BUSY_SNAPSHOT: i32 = 517;
pub const SQLITE_CANTOPEN_NOTEMPDIR: i32 = 270; pub const SQLITE_CANTOPEN_NOTEMPDIR: i32 = 270;
@ -77,6 +78,7 @@ pub const SQLITE_CANTOPEN_ISDIR: i32 = 526;
pub const SQLITE_CANTOPEN_FULLPATH: i32 = 782; pub const SQLITE_CANTOPEN_FULLPATH: i32 = 782;
pub const SQLITE_CANTOPEN_CONVPATH: i32 = 1038; pub const SQLITE_CANTOPEN_CONVPATH: i32 = 1038;
pub const SQLITE_CORRUPT_VTAB: i32 = 267; pub const SQLITE_CORRUPT_VTAB: i32 = 267;
pub const SQLITE_CORRUPT_SEQUENCE: i32 = 523;
pub const SQLITE_READONLY_RECOVERY: i32 = 264; pub const SQLITE_READONLY_RECOVERY: i32 = 264;
pub const SQLITE_READONLY_CANTLOCK: i32 = 520; pub const SQLITE_READONLY_CANTLOCK: i32 = 520;
pub const SQLITE_READONLY_ROLLBACK: i32 = 776; pub const SQLITE_READONLY_ROLLBACK: i32 = 776;
@ -174,6 +176,7 @@ pub const SQLITE_FCNTL_PDB: i32 = 30;
pub const SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: i32 = 31; pub const SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: i32 = 31;
pub const SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: i32 = 32; pub const SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: i32 = 32;
pub const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: i32 = 33; pub const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: i32 = 33;
pub const SQLITE_FCNTL_LOCK_TIMEOUT: i32 = 34;
pub const SQLITE_GET_LOCKPROXYFILE: i32 = 2; pub const SQLITE_GET_LOCKPROXYFILE: i32 = 2;
pub const SQLITE_SET_LOCKPROXYFILE: i32 = 3; pub const SQLITE_SET_LOCKPROXYFILE: i32 = 3;
pub const SQLITE_LAST_ERRNO: i32 = 4; pub const SQLITE_LAST_ERRNO: i32 = 4;
@ -211,6 +214,7 @@ pub const SQLITE_CONFIG_PCACHE_HDRSZ: i32 = 24;
pub const SQLITE_CONFIG_PMASZ: i32 = 25; pub const SQLITE_CONFIG_PMASZ: i32 = 25;
pub const SQLITE_CONFIG_STMTJRNL_SPILL: i32 = 26; pub const SQLITE_CONFIG_STMTJRNL_SPILL: i32 = 26;
pub const SQLITE_CONFIG_SMALL_MALLOC: i32 = 27; pub const SQLITE_CONFIG_SMALL_MALLOC: i32 = 27;
pub const SQLITE_CONFIG_SORTERREF_SIZE: i32 = 28;
pub const SQLITE_DBCONFIG_MAINDBNAME: i32 = 1000; pub const SQLITE_DBCONFIG_MAINDBNAME: i32 = 1000;
pub const SQLITE_DBCONFIG_LOOKASIDE: i32 = 1001; pub const SQLITE_DBCONFIG_LOOKASIDE: i32 = 1001;
pub const SQLITE_DBCONFIG_ENABLE_FKEY: i32 = 1002; pub const SQLITE_DBCONFIG_ENABLE_FKEY: i32 = 1002;
@ -220,7 +224,8 @@ pub const SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: i32 = 1005;
pub const SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: i32 = 1006; pub const SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: i32 = 1006;
pub const SQLITE_DBCONFIG_ENABLE_QPSG: i32 = 1007; pub const SQLITE_DBCONFIG_ENABLE_QPSG: i32 = 1007;
pub const SQLITE_DBCONFIG_TRIGGER_EQP: i32 = 1008; pub const SQLITE_DBCONFIG_TRIGGER_EQP: i32 = 1008;
pub const SQLITE_DBCONFIG_MAX: i32 = 1008; pub const SQLITE_DBCONFIG_RESET_DATABASE: i32 = 1009;
pub const SQLITE_DBCONFIG_MAX: i32 = 1009;
pub const SQLITE_DENY: i32 = 1; pub const SQLITE_DENY: i32 = 1;
pub const SQLITE_IGNORE: i32 = 2; pub const SQLITE_IGNORE: i32 = 2;
pub const SQLITE_CREATE_INDEX: i32 = 1; pub const SQLITE_CREATE_INDEX: i32 = 1;
@ -287,6 +292,8 @@ pub const SQLITE_UTF16: i32 = 4;
pub const SQLITE_ANY: i32 = 5; pub const SQLITE_ANY: i32 = 5;
pub const SQLITE_UTF16_ALIGNED: i32 = 8; pub const SQLITE_UTF16_ALIGNED: i32 = 8;
pub const SQLITE_DETERMINISTIC: i32 = 2048; pub const SQLITE_DETERMINISTIC: i32 = 2048;
pub const SQLITE_WIN32_DATA_DIRECTORY_TYPE: i32 = 1;
pub const SQLITE_WIN32_TEMP_DIRECTORY_TYPE: i32 = 2;
pub const SQLITE_INDEX_SCAN_UNIQUE: i32 = 1; pub const SQLITE_INDEX_SCAN_UNIQUE: i32 = 1;
pub const SQLITE_INDEX_CONSTRAINT_EQ: i32 = 2; pub const SQLITE_INDEX_CONSTRAINT_EQ: i32 = 2;
pub const SQLITE_INDEX_CONSTRAINT_GT: i32 = 4; pub const SQLITE_INDEX_CONSTRAINT_GT: i32 = 4;
@ -365,7 +372,8 @@ pub const SQLITE_DBSTATUS_CACHE_MISS: i32 = 8;
pub const SQLITE_DBSTATUS_CACHE_WRITE: i32 = 9; pub const SQLITE_DBSTATUS_CACHE_WRITE: i32 = 9;
pub const SQLITE_DBSTATUS_DEFERRED_FKS: i32 = 10; pub const SQLITE_DBSTATUS_DEFERRED_FKS: i32 = 10;
pub const SQLITE_DBSTATUS_CACHE_USED_SHARED: i32 = 11; pub const SQLITE_DBSTATUS_CACHE_USED_SHARED: i32 = 11;
pub const SQLITE_DBSTATUS_MAX: i32 = 11; pub const SQLITE_DBSTATUS_CACHE_SPILL: i32 = 12;
pub const SQLITE_DBSTATUS_MAX: i32 = 12;
pub const SQLITE_STMTSTATUS_FULLSCAN_STEP: i32 = 1; pub const SQLITE_STMTSTATUS_FULLSCAN_STEP: i32 = 1;
pub const SQLITE_STMTSTATUS_SORT: i32 = 2; pub const SQLITE_STMTSTATUS_SORT: i32 = 2;
pub const SQLITE_STMTSTATUS_AUTOINDEX: i32 = 3; pub const SQLITE_STMTSTATUS_AUTOINDEX: i32 = 3;
@ -387,6 +395,10 @@ pub const SQLITE_SCANSTAT_EST: i32 = 2;
pub const SQLITE_SCANSTAT_NAME: i32 = 3; pub const SQLITE_SCANSTAT_NAME: i32 = 3;
pub const SQLITE_SCANSTAT_EXPLAIN: i32 = 4; pub const SQLITE_SCANSTAT_EXPLAIN: i32 = 4;
pub const SQLITE_SCANSTAT_SELECTID: i32 = 5; pub const SQLITE_SCANSTAT_SELECTID: i32 = 5;
pub const SQLITE_SERIALIZE_NOCOPY: i32 = 1;
pub const SQLITE_DESERIALIZE_FREEONCLOSE: i32 = 1;
pub const SQLITE_DESERIALIZE_RESIZEABLE: i32 = 2;
pub const SQLITE_DESERIALIZE_READONLY: i32 = 4;
pub const NOT_WITHIN: i32 = 0; pub const NOT_WITHIN: i32 = 0;
pub const PARTLY_WITHIN: i32 = 1; pub const PARTLY_WITHIN: i32 = 1;
pub const FULLY_WITHIN: i32 = 2; pub const FULLY_WITHIN: i32 = 2;
@ -2255,6 +2267,24 @@ extern "C" {
#[link_name = "\u{1}sqlite3_data_directory"] #[link_name = "\u{1}sqlite3_data_directory"]
pub static mut sqlite3_data_directory: *mut ::std::os::raw::c_char; pub static mut sqlite3_data_directory: *mut ::std::os::raw::c_char;
} }
extern "C" {
pub fn sqlite3_win32_set_directory(
type_: ::std::os::raw::c_ulong,
zValue: *mut ::std::os::raw::c_void,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_win32_set_directory8(
type_: ::std::os::raw::c_ulong,
zValue: *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_win32_set_directory16(
type_: ::std::os::raw::c_ulong,
zValue: *const ::std::os::raw::c_void,
) -> ::std::os::raw::c_int;
}
extern "C" { extern "C" {
pub fn sqlite3_get_autocommit(arg1: *mut sqlite3) -> ::std::os::raw::c_int; pub fn sqlite3_get_autocommit(arg1: *mut sqlite3) -> ::std::os::raw::c_int;
} }
@ -3412,6 +3442,72 @@ extern "C" {
extern "C" { extern "C" {
pub fn sqlite3_test_control(op: ::std::os::raw::c_int, ...) -> ::std::os::raw::c_int; pub fn sqlite3_test_control(op: ::std::os::raw::c_int, ...) -> ::std::os::raw::c_int;
} }
extern "C" {
pub fn sqlite3_keyword_count() -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_keyword_name(
arg1: ::std::os::raw::c_int,
arg2: *mut *const ::std::os::raw::c_char,
arg3: *mut ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_keyword_check(
arg1: *const ::std::os::raw::c_char,
arg2: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct sqlite3_str {
_unused: [u8; 0],
}
extern "C" {
pub fn sqlite3_str_new(arg1: *mut sqlite3) -> *mut sqlite3_str;
}
extern "C" {
pub fn sqlite3_str_finish(arg1: *mut sqlite3_str) -> *mut ::std::os::raw::c_char;
}
extern "C" {
pub fn sqlite3_str_appendf(arg1: *mut sqlite3_str, zFormat: *const ::std::os::raw::c_char, ...);
}
extern "C" {
pub fn sqlite3_str_vappendf(
arg1: *mut sqlite3_str,
zFormat: *const ::std::os::raw::c_char,
arg2: *mut __va_list_tag,
);
}
extern "C" {
pub fn sqlite3_str_append(
arg1: *mut sqlite3_str,
zIn: *const ::std::os::raw::c_char,
N: ::std::os::raw::c_int,
);
}
extern "C" {
pub fn sqlite3_str_appendall(arg1: *mut sqlite3_str, zIn: *const ::std::os::raw::c_char);
}
extern "C" {
pub fn sqlite3_str_appendchar(
arg1: *mut sqlite3_str,
N: ::std::os::raw::c_int,
C: ::std::os::raw::c_char,
);
}
extern "C" {
pub fn sqlite3_str_reset(arg1: *mut sqlite3_str);
}
extern "C" {
pub fn sqlite3_str_errcode(arg1: *mut sqlite3_str) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_str_length(arg1: *mut sqlite3_str) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn sqlite3_str_value(arg1: *mut sqlite3_str) -> *mut ::std::os::raw::c_char;
}
extern "C" { extern "C" {
pub fn sqlite3_status( pub fn sqlite3_status(
op: ::std::os::raw::c_int, op: ::std::os::raw::c_int,
@ -4070,6 +4166,24 @@ extern "C" {
zDb: *const ::std::os::raw::c_char, zDb: *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int; ) -> ::std::os::raw::c_int;
} }
extern "C" {
pub fn sqlite3_serialize(
db: *mut sqlite3,
zSchema: *const ::std::os::raw::c_char,
piSize: *mut sqlite3_int64,
mFlags: ::std::os::raw::c_uint,
) -> *mut ::std::os::raw::c_uchar;
}
extern "C" {
pub fn sqlite3_deserialize(
db: *mut sqlite3,
zSchema: *const ::std::os::raw::c_char,
pData: *mut ::std::os::raw::c_uchar,
szDb: sqlite3_int64,
szBuf: sqlite3_int64,
mFlags: ::std::os::raw::c_uint,
) -> ::std::os::raw::c_int;
}
pub type sqlite3_rtree_dbl = f64; pub type sqlite3_rtree_dbl = f64;
extern "C" { extern "C" {
pub fn sqlite3_rtree_geometry_callback( pub fn sqlite3_rtree_geometry_callback(

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -295,6 +295,21 @@ struct sqlite3_api_routines {
int (*vtab_nochange)(sqlite3_context*); int (*vtab_nochange)(sqlite3_context*);
int (*value_nochange)(sqlite3_value*); int (*value_nochange)(sqlite3_value*);
const char *(*vtab_collation)(sqlite3_index_info*,int); const char *(*vtab_collation)(sqlite3_index_info*,int);
/* Version 3.24.0 and later */
int (*keyword_count)(void);
int (*keyword_name)(int,const char**,int*);
int (*keyword_check)(const char*,int);
sqlite3_str *(*str_new)(sqlite3*);
char *(*str_finish)(sqlite3_str*);
void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
void (*str_append)(sqlite3_str*, const char *zIn, int N);
void (*str_appendall)(sqlite3_str*, const char *zIn);
void (*str_appendchar)(sqlite3_str*, int N, char C);
void (*str_reset)(sqlite3_str*);
int (*str_errcode)(sqlite3_str*);
int (*str_length)(sqlite3_str*);
char *(*str_value)(sqlite3_str*);
}; };
/* /*
@ -563,8 +578,23 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_value_pointer sqlite3_api->value_pointer #define sqlite3_value_pointer sqlite3_api->value_pointer
/* Version 3.22.0 and later */ /* Version 3.22.0 and later */
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange #define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
#define sqlite3_value_nochange sqltie3_api->value_nochange #define sqlite3_value_nochange sqlite3_api->value_nochange
#define sqlite3_vtab_collation sqltie3_api->vtab_collation #define sqlite3_vtab_collation sqlite3_api->vtab_collation
/* Version 3.24.0 and later */
#define sqlite3_keyword_count sqlite3_api->keyword_count
#define sqlite3_keyword_name sqlite3_api->keyword_name
#define sqlite3_keyword_check sqlite3_api->keyword_check
#define sqlite3_str_new sqlite3_api->str_new
#define sqlite3_str_finish sqlite3_api->str_finish
#define sqlite3_str_appendf sqlite3_api->str_appendf
#define sqlite3_str_vappendf sqlite3_api->str_vappendf
#define sqlite3_str_append sqlite3_api->str_append
#define sqlite3_str_appendall sqlite3_api->str_appendall
#define sqlite3_str_appendchar sqlite3_api->str_appendchar
#define sqlite3_str_reset sqlite3_api->str_reset
#define sqlite3_str_errcode sqlite3_api->str_errcode
#define sqlite3_str_length sqlite3_api->str_length
#define sqlite3_str_value sqlite3_api->str_value
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

View File

@ -1,6 +1,6 @@
use std::os::raw::c_int;
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::os::raw::c_int;
/// Error Codes /// Error Codes
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -99,7 +99,12 @@ impl Error {
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Error code {}: {}", self.extended_code, code_to_str(self.extended_code)) write!(
f,
"Error code {}: {}",
self.extended_code,
code_to_str(self.extended_code)
)
} }
} }

View File

@ -2,6 +2,7 @@
pub use self::error::*; pub use self::error::*;
use std::default::Default;
use std::mem; use std::mem;
mod error; mod error;
@ -45,3 +46,18 @@ pub enum Limit {
} }
include!(concat!(env!("OUT_DIR"), "/bindgen.rs")); include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
pub type sqlite3_index_constraint = sqlite3_index_info_sqlite3_index_constraint;
pub type sqlite3_index_constraint_usage = sqlite3_index_info_sqlite3_index_constraint_usage;
impl Default for sqlite3_vtab {
fn default() -> Self {
unsafe { mem::zeroed() }
}
}
impl Default for sqlite3_vtab_cursor {
fn default() -> Self {
unsafe { mem::zeroed() }
}
}

View File

@ -4,7 +4,7 @@ cd $SCRIPT_DIR
SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3 SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
# Download and extract amalgamation # Download and extract amalgamation
SQLITE=sqlite-amalgamation-3220000 SQLITE=sqlite-amalgamation-3240000
curl -O http://sqlite.org/2018/$SQLITE.zip curl -O http://sqlite.org/2018/$SQLITE.zip
unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c
unzip -p $SQLITE.zip $SQLITE/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h unzip -p $SQLITE.zip $SQLITE/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h

View File

@ -36,8 +36,8 @@ use std::time::Duration;
use ffi; use ffi;
use {DatabaseName, Connection, Result}; use error::{error_from_handle, error_from_sqlite_code};
use error::{error_from_sqlite_code, error_from_handle}; use {Connection, DatabaseName, Result};
impl Connection { impl Connection {
/// Back up the `name` database to the given destination path. /// Back up the `name` database to the given destination path.
@ -52,14 +52,20 @@ impl Connection {
/// ///
/// Will return `Err` if the destination path cannot be opened /// Will return `Err` if the destination path cannot be opened
/// or if the backup fails. /// or if the backup fails.
pub fn backup<P: AsRef<Path>>(&self, pub fn backup<P: AsRef<Path>>(
&self,
name: DatabaseName, name: DatabaseName,
dst_path: P, dst_path: P,
progress: Option<fn(Progress)>) progress: Option<fn(Progress)>,
-> Result<()> { ) -> Result<()> {
use self::StepResult::{More, Done, Busy, Locked}; use self::StepResult::{Busy, Done, Locked, More};
let mut dst = try!(Connection::open(dst_path)); let mut dst = try!(Connection::open(dst_path));
let backup = try!(Backup::new_with_names(self, name, &mut dst, DatabaseName::Main)); let backup = try!(Backup::new_with_names(
self,
name,
&mut dst,
DatabaseName::Main
));
let mut r = More; let mut r = More;
while r == More { while r == More {
@ -89,12 +95,13 @@ impl Connection {
/// ///
/// Will return `Err` if the destination path cannot be opened /// Will return `Err` if the destination path cannot be opened
/// or if the restore fails. /// or if the restore fails.
pub fn restore<P: AsRef<Path>>(&mut self, pub fn restore<P: AsRef<Path>, F: Fn(Progress)>(
&mut self,
name: DatabaseName, name: DatabaseName,
src_path: P, src_path: P,
progress: Option<fn(Progress)>) progress: Option<F>,
-> Result<()> { ) -> Result<()> {
use self::StepResult::{More, Done, Busy, Locked}; use self::StepResult::{Busy, Done, Locked, More};
let src = try!(Connection::open(src_path)); let src = try!(Connection::open(src_path));
let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name)); let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name));
@ -102,7 +109,7 @@ impl Connection {
let mut busy_count = 0i32; let mut busy_count = 0i32;
'restore_loop: while r == More || r == Busy { 'restore_loop: while r == More || r == Busy {
r = try!(restore.step(100)); r = try!(restore.step(100));
if let Some(f) = progress { if let Some(ref f) = progress {
f(restore.progress()); f(restore.progress());
} }
if r == Busy { if r == Busy {
@ -184,21 +191,24 @@ impl<'a, 'b> Backup<'a, 'b> {
/// ///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns /// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `NULL`. /// `NULL`.
pub fn new_with_names(from: &'a Connection, pub fn new_with_names(
from: &'a Connection,
from_name: DatabaseName, from_name: DatabaseName,
to: &'b mut Connection, to: &'b mut Connection,
to_name: DatabaseName) to_name: DatabaseName,
-> Result<Backup<'a, 'b>> { ) -> Result<Backup<'a, 'b>> {
let to_name = try!(to_name.to_cstring()); let to_name = try!(to_name.to_cstring());
let from_name = try!(from_name.to_cstring()); let from_name = try!(from_name.to_cstring());
let to_db = to.db.borrow_mut().db; let to_db = to.db.borrow_mut().db;
let b = unsafe { let b = unsafe {
let b = ffi::sqlite3_backup_init(to_db, let b = ffi::sqlite3_backup_init(
to_db,
to_name.as_ptr(), to_name.as_ptr(),
from.db.borrow_mut().db, from.db.borrow_mut().db,
from_name.as_ptr()); from_name.as_ptr(),
);
if b.is_null() { if b.is_null() {
return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db))); return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
} }
@ -235,7 +245,7 @@ impl<'a, 'b> Backup<'a, 'b> {
/// `LOCKED` are transient errors and are therefore returned as possible /// `LOCKED` are transient errors and are therefore returned as possible
/// `Ok` values. /// `Ok` values.
pub fn step(&self, num_pages: c_int) -> Result<StepResult> { pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
use self::StepResult::{Done, More, Busy, Locked}; use self::StepResult::{Busy, Done, Locked, More};
let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) }; let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
match rc { match rc {
@ -262,12 +272,13 @@ impl<'a, 'b> Backup<'a, 'b> {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if any of the calls to `step` return `Err`. /// Will return `Err` if any of the calls to `step` return `Err`.
pub fn run_to_completion(&self, pub fn run_to_completion(
&self,
pages_per_step: c_int, pages_per_step: c_int,
pause_between_pages: Duration, pause_between_pages: Duration,
progress: Option<fn(Progress)>) progress: Option<fn(Progress)>,
-> Result<()> { ) -> Result<()> {
use self::StepResult::{Done, More, Busy, Locked}; use self::StepResult::{Busy, Done, Locked, More};
assert!(pages_per_step > 0, "pages_per_step must be positive"); assert!(pages_per_step > 0, "pages_per_step must be positive");
@ -292,12 +303,11 @@ impl<'a, 'b> Drop for Backup<'a, 'b> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use {Connection, DatabaseName};
use std::time::Duration;
use super::Backup; use super::Backup;
use std::time::Duration;
use {Connection, DatabaseName};
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup() { fn test_backup() {
let src = Connection::open_in_memory().unwrap(); let src = Connection::open_in_memory().unwrap();
let sql = "BEGIN; let sql = "BEGIN;
@ -313,22 +323,27 @@ mod test {
backup.step(-1).unwrap(); backup.step(-1).unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT x FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42, the_answer); assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap(); src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{ {
let backup = Backup::new(&src, &mut dst).unwrap(); let backup = Backup::new(&src, &mut dst).unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap(); backup
.run_to_completion(5, Duration::from_millis(250), None)
.unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42 + 43, the_answer); assert_eq!(42 + 43, the_answer);
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup_temp() { fn test_backup_temp() {
let src = Connection::open_in_memory().unwrap(); let src = Connection::open_in_memory().unwrap();
let sql = "BEGIN; let sql = "BEGIN;
@ -340,34 +355,35 @@ mod test {
let mut dst = Connection::open_in_memory().unwrap(); let mut dst = Connection::open_in_memory().unwrap();
{ {
let backup = Backup::new_with_names(&src, let backup =
DatabaseName::Temp, Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
&mut dst,
DatabaseName::Main)
.unwrap(); .unwrap();
backup.step(-1).unwrap(); backup.step(-1).unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT x FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42, the_answer); assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap(); src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{ {
let backup = Backup::new_with_names(&src, let backup =
DatabaseName::Temp, Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
&mut dst, .unwrap();
DatabaseName::Main) backup
.run_to_completion(5, Duration::from_millis(250), None)
.unwrap(); .unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42 + 43, the_answer); assert_eq!(42 + 43, the_answer);
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup_attached() { fn test_backup_attached() {
let src = Connection::open_in_memory().unwrap(); let src = Connection::open_in_memory().unwrap();
let sql = "ATTACH DATABASE ':memory:' AS my_attached; let sql = "ATTACH DATABASE ':memory:' AS my_attached;
@ -380,29 +396,37 @@ mod test {
let mut dst = Connection::open_in_memory().unwrap(); let mut dst = Connection::open_in_memory().unwrap();
{ {
let backup = Backup::new_with_names(&src, let backup = Backup::new_with_names(
&src,
DatabaseName::Attached("my_attached"), DatabaseName::Attached("my_attached"),
&mut dst, &mut dst,
DatabaseName::Main) DatabaseName::Main,
.unwrap(); ).unwrap();
backup.step(-1).unwrap(); backup.step(-1).unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT x FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42, the_answer); assert_eq!(42, the_answer);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap(); src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{ {
let backup = Backup::new_with_names(&src, let backup = Backup::new_with_names(
&src,
DatabaseName::Attached("my_attached"), DatabaseName::Attached("my_attached"),
&mut dst, &mut dst,
DatabaseName::Main) DatabaseName::Main,
).unwrap();
backup
.run_to_completion(5, Duration::from_millis(250), None)
.unwrap(); .unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
} }
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap(); let the_answer: i64 = dst
.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(42 + 43, the_answer); assert_eq!(42 + 43, the_answer);
} }
} }

View File

@ -48,13 +48,13 @@
//! assert_eq!(blob.size(), 64); //! assert_eq!(blob.size(), 64);
//! } //! }
//! ``` //! ```
use std::io;
use std::cmp::min; use std::cmp::min;
use std::io;
use std::ptr; use std::ptr;
use super::ffi; use super::ffi;
use super::types::{ToSql, ToSqlOutput}; use super::types::{ToSql, ToSqlOutput};
use {Result, Connection, DatabaseName}; use {Connection, DatabaseName, Result};
/// Handle to an open BLOB. /// Handle to an open BLOB.
pub struct Blob<'conn> { pub struct Blob<'conn> {
@ -70,34 +70,34 @@ impl Connection {
/// ///
/// Will return `Err` if `db`/`table`/`column` cannot be converted to a C-compatible string /// Will return `Err` if `db`/`table`/`column` cannot be converted to a C-compatible string
/// or if the underlying SQLite BLOB open call fails. /// or if the underlying SQLite BLOB open call fails.
pub fn blob_open<'a>(&'a self, pub fn blob_open<'a>(
&'a self,
db: DatabaseName, db: DatabaseName,
table: &str, table: &str,
column: &str, column: &str,
row_id: i64, row_id: i64,
read_only: bool) read_only: bool,
-> Result<Blob<'a>> { ) -> Result<Blob<'a>> {
let mut c = self.db.borrow_mut(); let mut c = self.db.borrow_mut();
let mut blob = ptr::null_mut(); let mut blob = ptr::null_mut();
let db = try!(db.to_cstring()); let db = try!(db.to_cstring());
let table = try!(super::str_to_cstring(table)); let table = try!(super::str_to_cstring(table));
let column = try!(super::str_to_cstring(column)); let column = try!(super::str_to_cstring(column));
let rc = unsafe { let rc = unsafe {
ffi::sqlite3_blob_open(c.db(), ffi::sqlite3_blob_open(
c.db(),
db.as_ptr(), db.as_ptr(),
table.as_ptr(), table.as_ptr(),
column.as_ptr(), column.as_ptr(),
row_id, row_id,
if read_only { 0 } else { 1 }, if read_only { 0 } else { 1 },
&mut blob) &mut blob,
)
}; };
c.decode_result(rc) c.decode_result(rc).map(|_| Blob {
.map(|_| {
Blob {
conn: self, conn: self,
blob, blob,
pos: 0, pos: 0,
}
}) })
} }
} }
@ -154,15 +154,13 @@ impl<'conn> io::Read for Blob<'conn> {
if n <= 0 { if n <= 0 {
return Ok(0); return Ok(0);
} }
let rc = let rc = unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
self.conn self.conn
.decode_result(rc) .decode_result(rc)
.map(|_| { .map(|_| {
self.pos += n; self.pos += n;
n as usize n as usize
}) }).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
} }
} }
@ -183,16 +181,13 @@ impl<'conn> io::Write for Blob<'conn> {
if n <= 0 { if n <= 0 {
return Ok(0); return Ok(0);
} }
let rc = unsafe { let rc = unsafe { ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos)
};
self.conn self.conn
.decode_result(rc) .decode_result(rc)
.map(|_| { .map(|_| {
self.pos += n; self.pos += n;
n as usize n as usize
}) }).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
} }
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
@ -210,11 +205,15 @@ impl<'conn> io::Seek for Blob<'conn> {
}; };
if pos < 0 { if pos < 0 {
Err(io::Error::new(io::ErrorKind::InvalidInput, Err(io::Error::new(
"invalid seek to negative position")) io::ErrorKind::InvalidInput,
"invalid seek to negative position",
))
} else if pos > i64::from(self.size()) { } else if pos > i64::from(self.size()) {
Err(io::Error::new(io::ErrorKind::InvalidInput, Err(io::Error::new(
"invalid seek to position past end of blob")) io::ErrorKind::InvalidInput,
"invalid seek to position past end of blob",
))
} else { } else {
self.pos = pos as i32; self.pos = pos as i32;
Ok(pos as u64) Ok(pos as u64)
@ -247,10 +246,9 @@ impl ToSql for ZeroBlob {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::io::{BufReader, BufRead, BufWriter, Read, Write, Seek, SeekFrom}; use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
use {Connection, DatabaseName, Result}; use {Connection, DatabaseName, Result};
#[cfg_attr(rustfmt, rustfmt_skip)]
fn db_with_test_blob() -> Result<(Connection, i64)> { fn db_with_test_blob() -> Result<(Connection, i64)> {
let db = try!(Connection::open_in_memory()); let db = try!(Connection::open_in_memory());
let sql = "BEGIN; let sql = "BEGIN;
@ -266,7 +264,8 @@ mod test {
fn test_blob() { fn test_blob() {
let (db, rowid) = db_with_test_blob().unwrap(); let (db, rowid) = db_with_test_blob().unwrap();
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let mut blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
assert_eq!(4, blob.write(b"Clob").unwrap()); assert_eq!(4, blob.write(b"Clob").unwrap());
assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10 assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10
@ -275,7 +274,8 @@ mod test {
blob.reopen(rowid).unwrap(); blob.reopen(rowid).unwrap();
blob.close().unwrap(); blob.close().unwrap();
blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, true) blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, true)
.unwrap(); .unwrap();
let mut bytes = [0u8; 5]; let mut bytes = [0u8; 5];
assert_eq!(5, blob.read(&mut bytes[..]).unwrap()); assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
@ -316,7 +316,8 @@ mod test {
fn test_blob_in_bufreader() { fn test_blob_in_bufreader() {
let (db, rowid) = db_with_test_blob().unwrap(); let (db, rowid) = db_with_test_blob().unwrap();
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let mut blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
assert_eq!(8, blob.write(b"one\ntwo\n").unwrap()); assert_eq!(8, blob.write(b"one\ntwo\n").unwrap());
@ -341,7 +342,8 @@ mod test {
let (db, rowid) = db_with_test_blob().unwrap(); let (db, rowid) = db_with_test_blob().unwrap();
{ {
let blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
let mut writer = BufWriter::new(blob); let mut writer = BufWriter::new(blob);
@ -353,7 +355,8 @@ mod test {
{ {
// ... but it should've written the first 10 bytes // ... but it should've written the first 10 bytes
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let mut blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
let mut bytes = [0u8; 10]; let mut bytes = [0u8; 10];
assert_eq!(10, blob.read(&mut bytes[..]).unwrap()); assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
@ -361,7 +364,8 @@ mod test {
} }
{ {
let blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
let mut writer = BufWriter::new(blob); let mut writer = BufWriter::new(blob);
@ -372,7 +376,8 @@ mod test {
{ {
// ... but it should've written the first 10 bytes // ... but it should've written the first 10 bytes
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false) let mut blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap(); .unwrap();
let mut bytes = [0u8; 10]; let mut bytes = [0u8; 10];
assert_eq!(10, blob.read(&mut bytes[..]).unwrap()); assert_eq!(10, blob.read(&mut bytes[..]).unwrap());

162
src/busy.rs Normal file
View File

@ -0,0 +1,162 @@
///! Busy handler (when the database is locked)
use std::mem;
use std::os::raw::{c_int, c_void};
use std::ptr;
use std::time::Duration;
use ffi;
use {Connection, InnerConnection, Result};
impl Connection {
/// Set a busy handler that sleeps for a specified amount of time when a table is locked.
/// The handler will sleep multiple times until at least "ms" milliseconds of sleeping have accumulated.
///
/// Calling this routine with an argument equal to zero turns off all busy handlers.
//
/// There can only be a single busy handler for a particular database connection at any given moment.
/// If another busy handler was defined (using `busy_handler`) prior to calling this routine, that other busy handler is cleared.
pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
let ms = timeout
.as_secs()
.checked_mul(1000)
.and_then(|t| t.checked_add(timeout.subsec_millis().into()))
.expect("too big");
self.db.borrow_mut().busy_timeout(ms as i32)
}
/// Register a callback to handle `SQLITE_BUSY` errors.
///
/// If the busy callback is `None`, then `SQLITE_BUSY is returned immediately upon encountering the lock.`
/// The argument to the busy handler callback is the number of times that the busy handler has been invoked previously for the same locking event.
/// If the busy callback returns `false`, then no additional attempts are made to access the database and `SQLITE_BUSY` is returned to the application.
/// If the callback returns `true`, then another attempt is made to access the database and the cycle repeats.
///
/// There can only be a single busy handler defined for each database connection.
/// Setting a new busy handler clears any previously set handler.
/// Note that calling `busy_timeout()` or evaluating `PRAGMA busy_timeout=N` will change the busy handler and thus clear any previously set busy handler.
pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> {
unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int {
let handler_fn: fn(i32) -> bool = mem::transmute(p_arg);
if handler_fn(count) {
1
} else {
0
}
}
let mut c = self.db.borrow_mut();
let r = match callback {
Some(f) => unsafe {
ffi::sqlite3_busy_handler(c.db(), Some(busy_handler_callback), mem::transmute(f))
},
None => unsafe { ffi::sqlite3_busy_handler(c.db(), None, ptr::null_mut()) },
};
c.decode_result(r)
}
}
impl InnerConnection {
fn busy_timeout(&mut self, timeout: c_int) -> Result<()> {
let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) };
self.decode_result(r)
}
}
#[cfg(test)]
mod test {
extern crate tempdir;
use self::tempdir::TempDir;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::sync_channel;
use std::thread;
use std::time::Duration;
use {Connection, Error, ErrorCode, TransactionBehavior};
#[test]
fn test_default_busy() {
let temp_dir = TempDir::new("test_default_busy").unwrap();
let path = temp_dir.path().join("test.db3");
let mut db1 = Connection::open(&path).unwrap();
let tx1 = db1
.transaction_with_behavior(TransactionBehavior::Exclusive)
.unwrap();
let db2 = Connection::open(&path).unwrap();
let r = db2.query_row("PRAGMA schema_version", &[], |_| unreachable!());
match r.unwrap_err() {
Error::SqliteFailure(err, _) => {
assert_eq!(err.code, ErrorCode::DatabaseBusy);
}
err => panic!("Unexpected error {}", err),
}
tx1.rollback().unwrap();
}
#[test]
#[ignore] // FIXME: unstable
fn test_busy_timeout() {
let temp_dir = TempDir::new("test_busy_timeout").unwrap();
let path = temp_dir.path().join("test.db3");
let db2 = Connection::open(&path).unwrap();
db2.busy_timeout(Duration::from_secs(1)).unwrap();
let (rx, tx) = sync_channel(0);
let child = thread::spawn(move || {
let mut db1 = Connection::open(&path).unwrap();
let tx1 = db1
.transaction_with_behavior(TransactionBehavior::Exclusive)
.unwrap();
rx.send(1).unwrap();
thread::sleep(Duration::from_millis(100));
tx1.rollback().unwrap();
});
assert_eq!(tx.recv().unwrap(), 1);
let _ = db2
.query_row("PRAGMA schema_version", &[], |row| {
row.get_checked::<_, i32>(0)
}).expect("unexpected error");
child.join().unwrap();
}
#[test]
#[ignore] // FIXME: unstable
fn test_busy_handler() {
lazy_static! {
static ref CALLED: AtomicBool = AtomicBool::new(false);
}
fn busy_handler(_: i32) -> bool {
CALLED.store(true, Ordering::Relaxed);
thread::sleep(Duration::from_millis(100));
true
}
let temp_dir = TempDir::new("test_busy_handler").unwrap();
let path = temp_dir.path().join("test.db3");
let db2 = Connection::open(&path).unwrap();
db2.busy_handler(Some(busy_handler)).unwrap();
let (rx, tx) = sync_channel(0);
let child = thread::spawn(move || {
let mut db1 = Connection::open(&path).unwrap();
let tx1 = db1
.transaction_with_behavior(TransactionBehavior::Exclusive)
.unwrap();
rx.send(1).unwrap();
thread::sleep(Duration::from_millis(100));
tx1.rollback().unwrap();
});
assert_eq!(tx.recv().unwrap(), 1);
let _ = db2
.query_row("PRAGMA schema_version", &[], |row| {
row.get_checked::<_, i32>(0)
}).expect("unexpected error");
assert_eq!(CALLED.load(Ordering::Relaxed), true);
child.join().unwrap();
}
}

View File

@ -1,11 +1,10 @@
//! Prepared statements cache for faster execution. //! Prepared statements cache for faster execution.
use lru_cache::LruCache;
use raw_statement::RawStatement;
use std::cell::RefCell; use std::cell::RefCell;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use lru_cache::LruCache; use {Connection, Result, Statement};
use {Result, Connection, Statement};
use raw_statement::RawStatement;
use statement::StatementCrateImpl;
impl Connection { impl Connection {
/// Prepare a SQL statement for execution, returning a previously prepared (but /// Prepare a SQL statement for execution, returning a previously prepared (but
@ -120,10 +119,11 @@ impl StatementCache {
// //
// Will return `Err` if no cached statement can be found and the underlying SQLite prepare // Will return `Err` if no cached statement can be found and the underlying SQLite prepare
// call fails. // call fails.
fn get<'conn>(&'conn self, fn get<'conn>(
&'conn self,
conn: &'conn Connection, conn: &'conn Connection,
sql: &str) sql: &str,
-> Result<CachedStatement<'conn>> { ) -> Result<CachedStatement<'conn>> {
let mut cache = self.0.borrow_mut(); let mut cache = self.0.borrow_mut();
let stmt = match cache.remove(sql.trim()) { let stmt = match cache.remove(sql.trim()) {
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)), Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
@ -136,7 +136,9 @@ impl StatementCache {
fn cache_stmt(&self, stmt: RawStatement) { fn cache_stmt(&self, stmt: RawStatement) {
let mut cache = self.0.borrow_mut(); let mut cache = self.0.borrow_mut();
stmt.clear_bindings(); stmt.clear_bindings();
let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).trim().to_string(); let sql = String::from_utf8_lossy(stmt.sql().to_bytes())
.trim()
.to_string();
cache.insert(sql, stmt); cache.insert(sql, stmt);
} }
@ -148,8 +150,8 @@ impl StatementCache {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection;
use super::StatementCache; use super::StatementCache;
use Connection;
impl StatementCache { impl StatementCache {
fn clear(&self) { fn clear(&self) {
@ -177,14 +179,14 @@ mod test {
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(0, cache.len()); assert_eq!(0, cache.len());
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap()); assert_eq!(0, stmt.query_row(&[], |r| r.get::<_, i64>(0)).unwrap());
} }
assert_eq!(1, cache.len()); assert_eq!(1, cache.len());
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(0, cache.len()); assert_eq!(0, cache.len());
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap()); assert_eq!(0, stmt.query_row(&[], |r| r.get::<_, i64>(0)).unwrap());
} }
assert_eq!(1, cache.len()); assert_eq!(1, cache.len());
@ -202,7 +204,7 @@ mod test {
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(0, cache.len()); assert_eq!(0, cache.len());
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap()); assert_eq!(0, stmt.query_row(&[], |r| r.get::<_, i64>(0)).unwrap());
} }
assert_eq!(1, cache.len()); assert_eq!(1, cache.len());
@ -212,7 +214,7 @@ mod test {
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(0, cache.len()); assert_eq!(0, cache.len());
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap()); assert_eq!(0, stmt.query_row(&[], |r| r.get::<_, i64>(0)).unwrap());
} }
assert_eq!(0, cache.len()); assert_eq!(0, cache.len());
@ -220,7 +222,7 @@ mod test {
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(0, cache.len()); assert_eq!(0, cache.len());
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap()); assert_eq!(0, stmt.query_row(&[], |r| r.get::<_, i64>(0)).unwrap());
} }
assert_eq!(1, cache.len()); assert_eq!(1, cache.len());
} }
@ -234,7 +236,7 @@ mod test {
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(0, cache.len()); assert_eq!(0, cache.len());
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap()); assert_eq!(0, stmt.query_row(&[], |r| r.get::<_, i64>(0)).unwrap());
stmt.discard(); stmt.discard();
} }
assert_eq!(0, cache.len()); assert_eq!(0, cache.len());
@ -243,46 +245,51 @@ mod test {
#[test] #[test]
fn test_ddl() { fn test_ddl() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch(r#" db.execute_batch(
r#"
CREATE TABLE foo (x INT); CREATE TABLE foo (x INT);
INSERT INTO foo VALUES (1); INSERT INTO foo VALUES (1);
"#) "#,
.unwrap(); ).unwrap();
let sql = "SELECT * FROM foo"; let sql = "SELECT * FROM foo";
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(1i32, assert_eq!(
1i32,
stmt.query_map::<i32, _>(&[], |r| r.get(0)) stmt.query_map::<i32, _>(&[], |r| r.get(0))
.unwrap() .unwrap()
.next() .next()
.unwrap() .unwrap()
.unwrap()); .unwrap()
);
} }
db.execute_batch(r#" db.execute_batch(
r#"
ALTER TABLE foo ADD COLUMN y INT; ALTER TABLE foo ADD COLUMN y INT;
UPDATE foo SET y = 2; UPDATE foo SET y = 2;
"#) "#,
.unwrap(); ).unwrap();
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!((1i32, 2i32), assert_eq!(
(1i32, 2i32),
stmt.query_map(&[], |r| (r.get(0), r.get(1))) stmt.query_map(&[], |r| (r.get(0), r.get(1)))
.unwrap() .unwrap()
.next() .next()
.unwrap() .unwrap()
.unwrap()); .unwrap()
);
} }
} }
#[test] #[test]
fn test_connection_close() { fn test_connection_close() {
let conn = Connection::open_in_memory().unwrap(); let conn = Connection::open_in_memory().unwrap();
conn.prepare_cached("SELECT * FROM sqlite_master;") conn.prepare_cached("SELECT * FROM sqlite_master;").unwrap();
.unwrap();
conn.close().expect("connection not closed"); conn.close().expect("connection not closed");
} }
@ -298,14 +305,14 @@ mod test {
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(0, cache.len()); assert_eq!(0, cache.len());
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap()); assert_eq!(0, stmt.query_row(&[], |r| r.get::<_, i64>(0)).unwrap());
} }
assert_eq!(1, cache.len()); assert_eq!(1, cache.len());
{ {
let mut stmt = db.prepare_cached(sql).unwrap(); let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(0, cache.len()); assert_eq!(0, cache.len());
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap()); assert_eq!(0, stmt.query_row(&[], |r| r.get::<_, i64>(0)).unwrap());
} }
assert_eq!(1, cache.len()); assert_eq!(1, cache.len());
} }

124
src/context.rs Normal file
View File

@ -0,0 +1,124 @@
//! Code related to `sqlite3_context` common to `functions` and `vtab` modules.
use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_void};
#[cfg(feature = "array")]
use std::rc::Rc;
use ffi;
use ffi::sqlite3_context;
use ffi::sqlite3_value;
use str_to_cstring;
use types::{ToSqlOutput, ValueRef};
#[cfg(feature = "array")]
use vtab::array::{free_array, ARRAY_TYPE};
impl<'a> ValueRef<'a> {
pub(crate) unsafe fn from_value(value: *mut sqlite3_value) -> ValueRef<'a> {
use std::slice::from_raw_parts;
match ffi::sqlite3_value_type(value) {
ffi::SQLITE_NULL => ValueRef::Null,
ffi::SQLITE_INTEGER => ValueRef::Integer(ffi::sqlite3_value_int64(value)),
ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
ffi::SQLITE_TEXT => {
let text = ffi::sqlite3_value_text(value);
assert!(
!text.is_null(),
"unexpected SQLITE_TEXT value type with NULL data"
);
let s = CStr::from_ptr(text as *const c_char);
// sqlite3_value_text returns UTF8 data, so our unwrap here should be fine.
let s = s
.to_str()
.expect("sqlite3_value_text returned invalid UTF-8");
ValueRef::Text(s)
}
ffi::SQLITE_BLOB => {
let (blob, len) = (
ffi::sqlite3_value_blob(value),
ffi::sqlite3_value_bytes(value),
);
assert!(
len >= 0,
"unexpected negative return from sqlite3_value_bytes"
);
if len > 0 {
assert!(
!blob.is_null(),
"unexpected SQLITE_BLOB value type with NULL data"
);
ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
} else {
// The return value from sqlite3_value_blob() for a zero-length BLOB
// is a NULL pointer.
ValueRef::Blob(&[])
}
}
_ => unreachable!("sqlite3_value_type returned invalid value"),
}
}
}
pub(crate) unsafe fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a>) {
let value = match *result {
ToSqlOutput::Borrowed(v) => v,
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
#[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => {
return ffi::sqlite3_result_zeroblob(ctx, len);
}
#[cfg(feature = "array")]
ToSqlOutput::Array(ref a) => {
return ffi::sqlite3_result_pointer(
ctx,
Rc::into_raw(a.clone()) as *mut c_void,
ARRAY_TYPE,
Some(free_array),
);
}
};
match value {
ValueRef::Null => ffi::sqlite3_result_null(ctx),
ValueRef::Integer(i) => ffi::sqlite3_result_int64(ctx, i),
ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r),
ValueRef::Text(s) => {
let length = s.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else {
let c_str = match str_to_cstring(s) {
Ok(c_str) => c_str,
// TODO sqlite3_result_error
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
};
let destructor = if length > 0 {
ffi::SQLITE_TRANSIENT()
} else {
ffi::SQLITE_STATIC()
};
ffi::sqlite3_result_text(ctx, c_str.as_ptr(), length as c_int, destructor);
}
}
ValueRef::Blob(b) => {
let length = b.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else if length == 0 {
ffi::sqlite3_result_zeroblob(ctx, 0)
} else {
ffi::sqlite3_result_blob(
ctx,
b.as_ptr() as *const c_void,
length as c_int,
ffi::SQLITE_TRANSIENT(),
);
}
}
}
}

View File

@ -1,10 +1,10 @@
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::os::raw::c_int;
use std::path::PathBuf; use std::path::PathBuf;
use std::str; use std::str;
use std::os::raw::c_int;
use {ffi, errmsg_to_string};
use types::Type; use types::Type;
use {errmsg_to_string, ffi};
/// Old name for `Error`. `SqliteError` is deprecated. /// Old name for `Error`. `SqliteError` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Error instead")] #[deprecated(since = "0.6.0", note = "Use Error instead")]
@ -26,9 +26,9 @@ pub enum Error {
FromSqlConversionFailure(usize, Type, Box<error::Error + Send + Sync>), FromSqlConversionFailure(usize, Type, Box<error::Error + Send + Sync>),
/// Error when SQLite gives us an integral value outside the range of the requested type (e.g., /// Error when SQLite gives us an integral value outside the range of the requested type (e.g.,
/// trying to get the value 1000 into a `u8`). The associated `c_int` is the column index, and /// trying to get the value 1000 into a `u8`). The associated `usize` is the column index, and
/// the associated `i64` is the value returned by SQLite. /// the associated `i64` is the value returned by SQLite.
IntegralValueOutOfRange(c_int, i64), IntegralValueOutOfRange(usize, i64),
/// Error converting a string to UTF-8. /// Error converting a string to UTF-8.
Utf8Error(str::Utf8Error), Utf8Error(str::Utf8Error),
@ -51,7 +51,7 @@ pub enum Error {
/// Error when the value of a particular column is requested, but the index is out of range /// Error when the value of a particular column is requested, but the index is out of range
/// for the statement. /// for the statement.
InvalidColumnIndex(c_int), InvalidColumnIndex(usize),
/// Error when the value of a named column is requested, but no column matches the name /// Error when the value of a named column is requested, but no column matches the name
/// for the statement. /// for the statement.
@ -59,15 +59,19 @@ pub enum Error {
/// Error when the value of a particular column is requested, but the type of the result in /// Error when the value of a particular column is requested, but the type of the result in
/// that column cannot be converted to the requested Rust type. /// that column cannot be converted to the requested Rust type.
InvalidColumnType(c_int, Type), InvalidColumnType(usize, Type),
/// Error when a query that was expected to insert one row did not insert any or insert many. /// Error when a query that was expected to insert one row did not insert any or insert many.
StatementChangedRows(c_int), StatementChangedRows(usize),
/// Error returned by `functions::Context::get` when the function argument cannot be converted /// Error returned by `functions::Context::get` when the function argument cannot be converted
/// to the requested type. /// to the requested type.
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
InvalidFunctionParameterType(usize, Type), InvalidFunctionParameterType(usize, Type),
/// Error returned by `vtab::Values::get` when the filter argument cannot be converted
/// to the requested type.
#[cfg(feature = "vtab")]
InvalidFilterParameterType(usize, Type),
/// An error case available for implementors of custom user functions (e.g., /// An error case available for implementors of custom user functions (e.g.,
/// `create_scalar_function`). /// `create_scalar_function`).
@ -77,6 +81,15 @@ pub enum Error {
/// Error available for the implementors of the `ToSql` trait. /// Error available for the implementors of the `ToSql` trait.
ToSqlConversionFailure(Box<error::Error + Send + Sync>), ToSqlConversionFailure(Box<error::Error + Send + Sync>),
/// Error when the SQL is not a `SELECT`, is not read-only.
InvalidQuery,
/// An error case available for implementors of custom modules (e.g.,
/// `create_module`).
#[cfg(feature = "vtab")]
#[allow(dead_code)]
ModuleError(String),
} }
impl From<str::Utf8Error> for Error { impl From<str::Utf8Error> for Error {
@ -96,17 +109,15 @@ impl fmt::Display for Error {
match *self { match *self {
Error::SqliteFailure(ref err, None) => err.fmt(f), Error::SqliteFailure(ref err, None) => err.fmt(f),
Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s), Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
Error::SqliteSingleThreadedMode => { Error::SqliteSingleThreadedMode => write!(
write!(f, f,
"SQLite was compiled or configured for single-threaded use only") "SQLite was compiled or configured for single-threaded use only"
} ),
Error::FromSqlConversionFailure(i, ref t, ref err) => { Error::FromSqlConversionFailure(i, ref t, ref err) => write!(
write!(f, f,
"Conversion error from type {} at index: {}, {}", "Conversion error from type {} at index: {}, {}",
t, t, i, err
i, ),
err)
}
Error::IntegralValueOutOfRange(col, val) => { Error::IntegralValueOutOfRange(col, val) => {
write!(f, "Integer {} out of range at index {}", val, col) write!(f, "Integer {} out of range at index {}", val, col)
} }
@ -129,9 +140,16 @@ impl fmt::Display for Error {
Error::InvalidFunctionParameterType(i, ref t) => { Error::InvalidFunctionParameterType(i, ref t) => {
write!(f, "Invalid function parameter type {} at index {}", t, i) write!(f, "Invalid function parameter type {} at index {}", t, i)
} }
#[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(i, ref t) => {
write!(f, "Invalid filter parameter type {} at index {}", t, i)
}
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => err.fmt(f), Error::UserFunctionError(ref err) => err.fmt(f),
Error::ToSqlConversionFailure(ref err) => err.fmt(f), Error::ToSqlConversionFailure(ref err) => err.fmt(f),
Error::InvalidQuery => write!(f, "Query is not read-only"),
#[cfg(feature = "vtab")]
Error::ModuleError(ref desc) => write!(f, "{}", desc),
} }
} }
} }
@ -141,14 +159,18 @@ impl error::Error for Error {
match *self { match *self {
Error::SqliteFailure(ref err, None) => err.description(), Error::SqliteFailure(ref err, None) => err.description(),
Error::SqliteFailure(_, Some(ref s)) => s, Error::SqliteFailure(_, Some(ref s)) => s,
Error::SqliteSingleThreadedMode => "SQLite was compiled or configured for single-threaded use only", Error::SqliteSingleThreadedMode => {
"SQLite was compiled or configured for single-threaded use only"
}
Error::FromSqlConversionFailure(_, _, ref err) => err.description(), Error::FromSqlConversionFailure(_, _, ref err) => err.description(),
Error::IntegralValueOutOfRange(_, _) => "integral value out of range of requested type", Error::IntegralValueOutOfRange(_, _) => "integral value out of range of requested type",
Error::Utf8Error(ref err) => err.description(), Error::Utf8Error(ref err) => err.description(),
Error::InvalidParameterName(_) => "invalid parameter name", Error::InvalidParameterName(_) => "invalid parameter name",
Error::NulError(ref err) => err.description(), Error::NulError(ref err) => err.description(),
Error::InvalidPath(_) => "invalid path", Error::InvalidPath(_) => "invalid path",
Error::ExecuteReturnedResults => "execute returned results - did you mean to call query?", Error::ExecuteReturnedResults => {
"execute returned results - did you mean to call query?"
}
Error::QueryReturnedNoRows => "query returned no rows", Error::QueryReturnedNoRows => "query returned no rows",
Error::InvalidColumnIndex(_) => "invalid column index", Error::InvalidColumnIndex(_) => "invalid column index",
Error::InvalidColumnName(_) => "invalid column name", Error::InvalidColumnName(_) => "invalid column name",
@ -157,9 +179,14 @@ impl error::Error for Error {
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type", Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
#[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(_, _) => "invalid filter parameter type",
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => err.description(), Error::UserFunctionError(ref err) => err.description(),
Error::ToSqlConversionFailure(ref err) => err.description(), Error::ToSqlConversionFailure(ref err) => err.description(),
Error::InvalidQuery => "query is not read-only",
#[cfg(feature = "vtab")]
Error::ModuleError(ref desc) => desc,
} }
} }
@ -169,25 +196,31 @@ impl error::Error for Error {
Error::Utf8Error(ref err) => Some(err), Error::Utf8Error(ref err) => Some(err),
Error::NulError(ref err) => Some(err), Error::NulError(ref err) => Some(err),
Error::IntegralValueOutOfRange(_, _) | Error::IntegralValueOutOfRange(_, _)
Error::SqliteSingleThreadedMode | | Error::SqliteSingleThreadedMode
Error::InvalidParameterName(_) | | Error::InvalidParameterName(_)
Error::ExecuteReturnedResults | | Error::ExecuteReturnedResults
Error::QueryReturnedNoRows | | Error::QueryReturnedNoRows
Error::InvalidColumnIndex(_) | | Error::InvalidColumnIndex(_)
Error::InvalidColumnName(_) | | Error::InvalidColumnName(_)
Error::InvalidColumnType(_, _) | | Error::InvalidColumnType(_, _)
Error::InvalidPath(_) | | Error::InvalidPath(_)
Error::StatementChangedRows(_) => None, | Error::StatementChangedRows(_)
| Error::InvalidQuery => None,
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => None, Error::InvalidFunctionParameterType(_, _) => None,
#[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(_, _) => None,
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => Some(&**err), Error::UserFunctionError(ref err) => Some(&**err),
Error::FromSqlConversionFailure(_, _, ref err) | Error::FromSqlConversionFailure(_, _, ref err)
Error::ToSqlConversionFailure(ref err) => Some(&**err), | Error::ToSqlConversionFailure(ref err) => Some(&**err),
#[cfg(feature = "vtab")]
Error::ModuleError(_) => None,
} }
} }
} }

View File

@ -50,67 +50,18 @@
//! } //! }
//! ``` //! ```
use std::error::Error as StdError; use std::error::Error as StdError;
use std::ffi::CStr; use std::os::raw::{c_int, c_void};
use std::ptr; use std::ptr;
use std::slice; use std::slice;
use std::os::raw::{c_int, c_char, c_void};
use ffi; use ffi;
use ffi::sqlite3_context; use ffi::sqlite3_context;
use ffi::sqlite3_value; use ffi::sqlite3_value;
use types::{ToSql, ToSqlOutput, FromSql, FromSqlError, ValueRef}; use context::set_result;
use types::{FromSql, FromSqlError, ToSql, ValueRef};
use {Result, Error, Connection, str_to_cstring, InnerConnection}; use {str_to_cstring, Connection, Error, InnerConnection, Result};
fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a>) {
let value = match *result {
ToSqlOutput::Borrowed(v) => v,
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
#[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => {
return unsafe { ffi::sqlite3_result_zeroblob(ctx, len) };
}
};
match value {
ValueRef::Null => unsafe { ffi::sqlite3_result_null(ctx) },
ValueRef::Integer(i) => unsafe { ffi::sqlite3_result_int64(ctx, i) },
ValueRef::Real(r) => unsafe { ffi::sqlite3_result_double(ctx, r) },
ValueRef::Text(s) => unsafe {
let length = s.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else {
let c_str = match str_to_cstring(s) {
Ok(c_str) => c_str,
// TODO sqlite3_result_error
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
};
let destructor = if length > 0 {
ffi::SQLITE_TRANSIENT()
} else {
ffi::SQLITE_STATIC()
};
ffi::sqlite3_result_text(ctx, c_str.as_ptr(), length as c_int, destructor);
}
},
ValueRef::Blob(b) => unsafe {
let length = b.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else if length == 0 {
ffi::sqlite3_result_zeroblob(ctx, 0)
} else {
ffi::sqlite3_result_blob(ctx,
b.as_ptr() as *const c_void,
length as c_int,
ffi::SQLITE_TRANSIENT());
}
},
}
}
unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) { unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
// Extended constraint error codes were added in SQLite 3.7.16. We don't have an explicit // Extended constraint error codes were added in SQLite 3.7.16. We don't have an explicit
@ -142,47 +93,8 @@ unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
} }
} }
impl<'a> ValueRef<'a> {
unsafe fn from_value(value: *mut sqlite3_value) -> ValueRef<'a> {
use std::slice::from_raw_parts;
match ffi::sqlite3_value_type(value) {
ffi::SQLITE_NULL => ValueRef::Null,
ffi::SQLITE_INTEGER => ValueRef::Integer(ffi::sqlite3_value_int64(value)),
ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
ffi::SQLITE_TEXT => {
let text = ffi::sqlite3_value_text(value);
assert!(!text.is_null(),
"unexpected SQLITE_TEXT value type with NULL data");
let s = CStr::from_ptr(text as *const c_char);
// sqlite3_value_text returns UTF8 data, so our unwrap here should be fine.
let s = s.to_str()
.expect("sqlite3_value_text returned invalid UTF-8");
ValueRef::Text(s)
}
ffi::SQLITE_BLOB => {
let (blob, len) = (ffi::sqlite3_value_blob(value), ffi::sqlite3_value_bytes(value));
assert!(len >= 0,
"unexpected negative return from sqlite3_value_bytes");
if len > 0 {
assert!(!blob.is_null(),
"unexpected SQLITE_BLOB value type with NULL data");
ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
} else {
// The return value from sqlite3_value_blob() for a zero-length BLOB
// is a NULL pointer.
ValueRef::Blob(&[])
}
}
_ => unreachable!("sqlite3_value_type returned invalid value"),
}
}
}
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) { unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
let _: Box<T> = Box::from_raw(p as *mut T); drop(Box::from_raw(p as *mut T));
} }
/// Context is a wrapper for the SQLite function evaluation context. /// Context is a wrapper for the SQLite function evaluation context.
@ -215,10 +127,7 @@ impl<'a> Context<'a> {
FromSqlError::InvalidType => { FromSqlError::InvalidType => {
Error::InvalidFunctionParameterType(idx, value.data_type()) Error::InvalidFunctionParameterType(idx, value.data_type())
} }
FromSqlError::OutOfRange(i) => { FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
Error::IntegralValueOutOfRange(idx as c_int,
i)
}
FromSqlError::Other(err) => { FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err) Error::FromSqlConversionFailure(idx, value.data_type(), err)
} }
@ -231,10 +140,12 @@ impl<'a> Context<'a> {
pub fn set_aux<T>(&self, arg: c_int, value: T) { pub fn set_aux<T>(&self, arg: c_int, value: T) {
let boxed = Box::into_raw(Box::new(value)); let boxed = Box::into_raw(Box::new(value));
unsafe { unsafe {
ffi::sqlite3_set_auxdata(self.ctx, ffi::sqlite3_set_auxdata(
self.ctx,
arg, arg,
boxed as *mut c_void, boxed as *mut c_void,
Some(free_boxed_value::<T>)) Some(free_boxed_value::<T>),
)
}; };
} }
@ -248,7 +159,11 @@ impl<'a> Context<'a> {
/// types must be identical. /// types must be identical.
pub unsafe fn get_aux<T>(&self, arg: c_int) -> Option<&T> { pub unsafe fn get_aux<T>(&self, arg: c_int) -> Option<&T> {
let p = ffi::sqlite3_get_auxdata(self.ctx, arg) as *mut T; let p = ffi::sqlite3_get_auxdata(self.ctx, arg) as *mut T;
if p.is_null() { None } else { Some(&*p) } if p.is_null() {
None
} else {
Some(&*p)
}
} }
} }
@ -257,11 +172,12 @@ impl<'a> Context<'a> {
/// `A` is the type of the aggregation context and `T` is the type of the final result. /// `A` is the type of the aggregation context and `T` is the type of the final result.
/// Implementations should be stateless. /// Implementations should be stateless.
pub trait Aggregate<A, T> pub trait Aggregate<A, T>
where T: ToSql where
T: ToSql,
{ {
/// Initializes the aggregation context. Will be called prior to the first call /// Initializes the aggregation context. Will be called prior to the first call
/// to `step()` to set up the context for an invocation of the function. (Note: /// to `step()` to set up the context for an invocation of the function. (Note:
/// `init()` will not be called if the there are no rows.) /// `init()` will not be called if there are no rows.)
fn init(&self) -> A; fn init(&self) -> A;
/// "step" function called once for each row in an aggregate group. May be called /// "step" function called once for each row in an aggregate group. May be called
@ -306,14 +222,16 @@ impl Connection {
/// # Failure /// # Failure
/// ///
/// Will return Err if the function could not be attached to the connection. /// Will return Err if the function could not be attached to the connection.
pub fn create_scalar_function<F, T>(&self, pub fn create_scalar_function<F, T>(
&self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
deterministic: bool, deterministic: bool,
x_func: F) x_func: F,
-> Result<()> ) -> Result<()>
where F: FnMut(&Context) -> Result<T>, where
T: ToSql F: FnMut(&Context) -> Result<T> + Send + 'static,
T: ToSql,
{ {
self.db self.db
.borrow_mut() .borrow_mut()
@ -325,14 +243,16 @@ impl Connection {
/// # Failure /// # Failure
/// ///
/// Will return Err if the function could not be attached to the connection. /// Will return Err if the function could not be attached to the connection.
pub fn create_aggregate_function<A, D, T>(&self, pub fn create_aggregate_function<A, D, T>(
&self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
deterministic: bool, deterministic: bool,
aggr: D) aggr: D,
-> Result<()> ) -> Result<()>
where D: Aggregate<A, T>, where
T: ToSql D: Aggregate<A, T>,
T: ToSql,
{ {
self.db self.db
.borrow_mut() .borrow_mut()
@ -353,20 +273,24 @@ impl Connection {
} }
impl InnerConnection { impl InnerConnection {
fn create_scalar_function<F, T>(&mut self, fn create_scalar_function<F, T>(
&mut self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
deterministic: bool, deterministic: bool,
x_func: F) x_func: F,
-> Result<()> ) -> Result<()>
where F: FnMut(&Context) -> Result<T>, where
T: ToSql F: FnMut(&Context) -> Result<T> + Send + 'static,
T: ToSql,
{ {
unsafe extern "C" fn call_boxed_closure<F, T>(ctx: *mut sqlite3_context, unsafe extern "C" fn call_boxed_closure<F, T>(
ctx: *mut sqlite3_context,
argc: c_int, argc: c_int,
argv: *mut *mut sqlite3_value) argv: *mut *mut sqlite3_value,
where F: FnMut(&Context) -> Result<T>, ) where
T: ToSql F: FnMut(&Context) -> Result<T>,
T: ToSql,
{ {
let ctx = Context { let ctx = Context {
ctx, ctx,
@ -392,7 +316,8 @@ impl InnerConnection {
flags |= ffi::SQLITE_DETERMINISTIC; flags |= ffi::SQLITE_DETERMINISTIC;
} }
let r = unsafe { let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(), ffi::sqlite3_create_function_v2(
self.db(),
c_name.as_ptr(), c_name.as_ptr(),
n_arg, n_arg,
flags, flags,
@ -400,23 +325,27 @@ impl InnerConnection {
Some(call_boxed_closure::<F, T>), Some(call_boxed_closure::<F, T>),
None, None,
None, None,
Some(free_boxed_value::<F>)) Some(free_boxed_value::<F>),
)
}; };
self.decode_result(r) self.decode_result(r)
} }
fn create_aggregate_function<A, D, T>(&mut self, fn create_aggregate_function<A, D, T>(
&mut self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
deterministic: bool, deterministic: bool,
aggr: D) aggr: D,
-> Result<()> ) -> Result<()>
where D: Aggregate<A, T>, where
T: ToSql D: Aggregate<A, T>,
T: ToSql,
{ {
unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context, unsafe fn aggregate_context<A>(
bytes: usize) ctx: *mut sqlite3_context,
-> Option<*mut *mut A> { bytes: usize,
) -> Option<*mut *mut A> {
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A; let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
if pac.is_null() { if pac.is_null() {
return None; return None;
@ -424,15 +353,19 @@ impl InnerConnection {
Some(pac) Some(pac)
} }
unsafe extern "C" fn call_boxed_step<A, D, T>(ctx: *mut sqlite3_context, unsafe extern "C" fn call_boxed_step<A, D, T>(
ctx: *mut sqlite3_context,
argc: c_int, argc: c_int,
argv: *mut *mut sqlite3_value) argv: *mut *mut sqlite3_value,
where D: Aggregate<A, T>, ) where
T: ToSql D: Aggregate<A, T>,
T: ToSql,
{ {
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D; let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(!boxed_aggr.is_null(), assert!(
"Internal error - null aggregate pointer"); !boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) { let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
Some(pac) => pac, Some(pac) => pac,
@ -458,12 +391,15 @@ impl InnerConnection {
} }
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context) unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
where D: Aggregate<A, T>, where
T: ToSql D: Aggregate<A, T>,
T: ToSql,
{ {
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D; let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(!boxed_aggr.is_null(), assert!(
"Internal error - null aggregate pointer"); !boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
// Within the xFinal callback, it is customary to set N=0 in calls to // Within the xFinal callback, it is customary to set N=0 in calls to
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur. // sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
@ -495,7 +431,8 @@ impl InnerConnection {
flags |= ffi::SQLITE_DETERMINISTIC; flags |= ffi::SQLITE_DETERMINISTIC;
} }
let r = unsafe { let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(), ffi::sqlite3_create_function_v2(
self.db(),
c_name.as_ptr(), c_name.as_ptr(),
n_arg, n_arg,
flags, flags,
@ -503,7 +440,8 @@ impl InnerConnection {
None, None,
Some(call_boxed_step::<A, D, T>), Some(call_boxed_step::<A, D, T>),
Some(call_boxed_final::<A, D, T>), Some(call_boxed_final::<A, D, T>),
Some(free_boxed_value::<D>)) Some(free_boxed_value::<D>),
)
}; };
self.decode_result(r) self.decode_result(r)
} }
@ -511,7 +449,8 @@ impl InnerConnection {
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> { fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
let c_name = try!(str_to_cstring(fn_name)); let c_name = try!(str_to_cstring(fn_name));
let r = unsafe { let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(), ffi::sqlite3_create_function_v2(
self.db(),
c_name.as_ptr(), c_name.as_ptr(),
n_arg, n_arg,
ffi::SQLITE_UTF8, ffi::SQLITE_UTF8,
@ -519,7 +458,8 @@ impl InnerConnection {
None, None,
None, None,
None, None,
None) None,
)
}; };
self.decode_result(r) self.decode_result(r)
} }
@ -529,13 +469,13 @@ impl InnerConnection {
mod test { mod test {
extern crate regex; extern crate regex;
use std::collections::HashMap;
use std::os::raw::c_double;
use self::regex::Regex; use self::regex::Regex;
use std::collections::HashMap;
use std::f64::EPSILON; use std::f64::EPSILON;
use std::os::raw::c_double;
use {Connection, Error, Result};
use functions::{Aggregate, Context}; use functions::{Aggregate, Context};
use {Connection, Error, Result};
fn half(ctx: &Context) -> Result<c_double> { fn half(ctx: &Context) -> Result<c_double> {
assert!(ctx.len() == 1, "called with unexpected number of arguments"); assert!(ctx.len() == 1, "called with unexpected number of arguments");
@ -597,41 +537,44 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_function_regexp_with_auxilliary() { fn test_function_regexp_with_auxilliary() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch("BEGIN; db.execute_batch(
"BEGIN;
CREATE TABLE foo (x string); CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lisa'); INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lXsi'); INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX'); INSERT INTO foo VALUES ('lisX');
END;").unwrap(); END;",
db.create_scalar_function("regexp", 2, true, regexp_with_auxilliary).unwrap(); ).unwrap();
db.create_scalar_function("regexp", 2, true, regexp_with_auxilliary)
.unwrap();
let result: Result<bool> = db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", let result: Result<bool> =
&[], db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", &[], |r| r.get(0));
|r| r.get(0));
assert_eq!(true, result.unwrap()); assert_eq!(true, result.unwrap());
let result: Result<i64> = let result: Result<i64> = db.query_row(
db.query_row("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1", "SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
&[], &[],
|r| r.get(0)); |r| r.get(0),
);
assert_eq!(2, result.unwrap()); assert_eq!(2, result.unwrap());
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_function_regexp_with_hashmap_cache() { fn test_function_regexp_with_hashmap_cache() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch("BEGIN; db.execute_batch(
"BEGIN;
CREATE TABLE foo (x string); CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lisa'); INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lXsi'); INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX'); INSERT INTO foo VALUES ('lisX');
END;").unwrap(); END;",
).unwrap();
// This implementation of a regexp scalar function uses a captured HashMap // This implementation of a regexp scalar function uses a captured HashMap
// to keep cached regular expressions around (even across multiple queries) // to keep cached regular expressions around (even across multiple queries)
@ -646,12 +589,10 @@ mod test {
use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::Entry::{Occupied, Vacant};
match entry { match entry {
Occupied(occ) => occ.into_mut(), Occupied(occ) => occ.into_mut(),
Vacant(vac) => { Vacant(vac) => match Regex::new(&regex_s) {
match Regex::new(&regex_s) {
Ok(r) => vac.insert(r), Ok(r) => vac.insert(r),
Err(err) => return Err(Error::UserFunctionError(Box::new(err))), Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
} },
}
} }
}; };
@ -659,16 +600,16 @@ mod test {
Ok(regex.is_match(&text)) Ok(regex.is_match(&text))
}).unwrap(); }).unwrap();
let result: Result<bool> = db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", let result: Result<bool> =
&[], db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", &[], |r| r.get(0));
|r| r.get(0));
assert_eq!(true, result.unwrap()); assert_eq!(true, result.unwrap());
let result: Result<i64> = let result: Result<i64> = db.query_row(
db.query_row("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1", "SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
&[], &[],
|r| r.get(0)); |r| r.get(0),
);
assert_eq!(2, result.unwrap()); assert_eq!(2, result.unwrap());
} }
@ -685,13 +626,13 @@ mod test {
} }
Ok(ret) Ok(ret)
}) }).unwrap();
.unwrap();
for &(expected, query) in for &(expected, query) in &[
&[("", "SELECT my_concat()"), ("", "SELECT my_concat()"),
("onetwo", "SELECT my_concat('one', 'two')"), ("onetwo", "SELECT my_concat('one', 'two')"),
("abc", "SELECT my_concat('a', 'b', 'c')")] { ("abc", "SELECT my_concat('a', 'b', 'c')"),
] {
let result: String = db.query_row(query, &[], |r| r.get(0)).unwrap(); let result: String = db.query_row(query, &[], |r| r.get(0)).unwrap();
assert_eq!(expected, result); assert_eq!(expected, result);
} }
@ -747,7 +688,8 @@ mod test {
let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \ let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \
2, 1)"; 2, 1)";
let result: (i64, i64) = db.query_row(dual_sum, &[], |r| (r.get(0), r.get(1))) let result: (i64, i64) = db
.query_row(dual_sum, &[], |r| (r.get(0), r.get(1)))
.unwrap(); .unwrap();
assert_eq!((4, 2), result); assert_eq!((4, 2), result);
} }

View File

@ -1,8 +1,8 @@
//! Commit, Data Change and Rollback Notification Callbacks //! Commit, Data Change and Rollback Notification Callbacks
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::ptr;
use std::os::raw::{c_int, c_char, c_void};
use ffi; use ffi;
@ -94,8 +94,9 @@ impl Connection {
/// Register a callback function to be invoked whenever a transaction is committed. /// Register a callback function to be invoked whenever a transaction is committed.
/// ///
/// The callback returns `true` to rollback. /// The callback returns `true` to rollback.
pub fn commit_hook<F>(&self, hook: F) pub fn commit_hook<F>(&self, hook: Option<F>)
where F: FnMut() -> bool where
F: FnMut() -> bool + Send + 'static,
{ {
self.db.borrow_mut().commit_hook(hook); self.db.borrow_mut().commit_hook(hook);
} }
@ -103,8 +104,9 @@ impl Connection {
/// Register a callback function to be invoked whenever a transaction is committed. /// Register a callback function to be invoked whenever a transaction is committed.
/// ///
/// The callback returns `true` to rollback. /// The callback returns `true` to rollback.
pub fn rollback_hook<F>(&self, hook: F) pub fn rollback_hook<F>(&self, hook: Option<F>)
where F: FnMut() where
F: FnMut() + Send + 'static,
{ {
self.db.borrow_mut().rollback_hook(hook); self.db.borrow_mut().rollback_hook(hook);
} }
@ -118,99 +120,122 @@ impl Connection {
/// - the name of the database ("main", "temp", ...), /// - the name of the database ("main", "temp", ...),
/// - the name of the table that is updated, /// - the name of the table that is updated,
/// - the ROWID of the row that is updated. /// - the ROWID of the row that is updated.
pub fn update_hook<F>(&self, hook: F) pub fn update_hook<F>(&self, hook: Option<F>)
where F: FnMut(Action, &str, &str, i64) where
F: FnMut(Action, &str, &str, i64) + Send + 'static,
{ {
self.db.borrow_mut().update_hook(hook); self.db.borrow_mut().update_hook(hook);
} }
/// Remove hook installed by `update_hook`.
pub fn remove_update_hook(&self) {
self.db.borrow_mut().remove_update_hook();
}
/// Remove hook installed by `commit_hook`.
pub fn remove_commit_hook(&self) {
self.db.borrow_mut().remove_commit_hook();
}
/// Remove hook installed by `rollback_hook`.
pub fn remove_rollback_hook(&self) {
self.db.borrow_mut().remove_rollback_hook();
}
} }
impl InnerConnection { impl InnerConnection {
pub fn remove_hooks(&mut self) { pub fn remove_hooks(&mut self) {
self.remove_update_hook(); self.update_hook(None::<fn(Action, &str, &str, i64)>);
self.remove_commit_hook(); self.commit_hook(None::<fn() -> bool>);
self.remove_rollback_hook(); self.rollback_hook(None::<fn()>);
} }
fn commit_hook<F>(&self, hook: F) fn commit_hook<F>(&mut self, hook: Option<F>)
where F: FnMut() -> bool where
F: FnMut() -> bool + Send + 'static,
{ {
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
where F: FnMut() -> bool where
F: FnMut() -> bool,
{ {
let boxed_hook: *mut F = p_arg as *mut F; let boxed_hook: *mut F = p_arg as *mut F;
assert!(!boxed_hook.is_null(), if (*boxed_hook)() {
"Internal error - null function pointer"); 1
} else {
if (*boxed_hook)() { 1 } else { 0 } 0
}
} }
let previous_hook = { // unlike `sqlite3_create_function_v2`, we cannot specify a `xDestroy` with `sqlite3_commit_hook`.
// so we keep the `xDestroy` function in `InnerConnection.free_boxed_hook`.
let free_commit_hook = if hook.is_some() {
Some(free_boxed_hook::<F> as fn(*mut c_void))
} else {
None
};
let previous_hook = match hook {
Some(hook) => {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook)); let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe { unsafe {
ffi::sqlite3_commit_hook(self.db(), ffi::sqlite3_commit_hook(
self.db(),
Some(call_boxed_closure::<F>), Some(call_boxed_closure::<F>),
boxed_hook as *mut _) boxed_hook as *mut _,
)
} }
}
_ => unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) },
}; };
if !previous_hook.is_null() {
if let Some(free_boxed_hook) = self.free_commit_hook {
free_boxed_hook(previous_hook); free_boxed_hook(previous_hook);
} }
}
self.free_commit_hook = free_commit_hook;
}
fn rollback_hook<F>(&self, hook: F) fn rollback_hook<F>(&mut self, hook: Option<F>)
where F: FnMut() where
F: FnMut() + Send + 'static,
{ {
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
where F: FnMut() where
F: FnMut(),
{ {
let boxed_hook: *mut F = p_arg as *mut F; let boxed_hook: *mut F = p_arg as *mut F;
assert!(!boxed_hook.is_null(),
"Internal error - null function pointer");
(*boxed_hook)(); (*boxed_hook)();
} }
let previous_hook = { let free_rollback_hook = if hook.is_some() {
Some(free_boxed_hook::<F> as fn(*mut c_void))
} else {
None
};
let previous_hook = match hook {
Some(hook) => {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook)); let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe { unsafe {
ffi::sqlite3_rollback_hook(self.db(), ffi::sqlite3_rollback_hook(
self.db(),
Some(call_boxed_closure::<F>), Some(call_boxed_closure::<F>),
boxed_hook as *mut _) boxed_hook as *mut _,
)
} }
}
_ => unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) },
}; };
if !previous_hook.is_null() {
if let Some(free_boxed_hook) = self.free_rollback_hook {
free_boxed_hook(previous_hook); free_boxed_hook(previous_hook);
} }
}
self.free_rollback_hook = free_rollback_hook;
}
fn update_hook<F>(&mut self, hook: F) fn update_hook<F>(&mut self, hook: Option<F>)
where F: FnMut(Action, &str, &str, i64) where
F: FnMut(Action, &str, &str, i64) + Send + 'static,
{ {
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void, unsafe extern "C" fn call_boxed_closure<F>(
p_arg: *mut c_void,
action_code: c_int, action_code: c_int,
db_str: *const c_char, db_str: *const c_char,
tbl_str: *const c_char, tbl_str: *const c_char,
row_id: i64) row_id: i64,
where F: FnMut(Action, &str, &str, i64) ) where
F: FnMut(Action, &str, &str, i64),
{ {
use std::ffi::CStr; use std::ffi::CStr;
use std::str; use std::str;
let boxed_hook: *mut F = p_arg as *mut F; let boxed_hook: *mut F = p_arg as *mut F;
assert!(!boxed_hook.is_null(),
"Internal error - null function pointer");
let action = Action::from(action_code); let action = Action::from(action_code);
let db_name = { let db_name = {
@ -225,84 +250,104 @@ impl InnerConnection {
(*boxed_hook)(action, db_name, tbl_name, row_id); (*boxed_hook)(action, db_name, tbl_name, row_id);
} }
let previous_hook = { let free_update_hook = if hook.is_some() {
Some(free_boxed_hook::<F> as fn(*mut c_void))
} else {
None
};
let previous_hook = match hook {
Some(hook) => {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook)); let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe { unsafe {
ffi::sqlite3_update_hook(self.db(), ffi::sqlite3_update_hook(
self.db(),
Some(call_boxed_closure::<F>), Some(call_boxed_closure::<F>),
boxed_hook as *mut _) boxed_hook as *mut _,
)
} }
}
_ => unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) },
}; };
free_boxed_hook(previous_hook); if !previous_hook.is_null() {
} if let Some(free_boxed_hook) = self.free_update_hook {
fn remove_update_hook(&mut self) {
let previous_hook = unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) };
free_boxed_hook(previous_hook);
}
fn remove_commit_hook(&mut self) {
let previous_hook = unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) };
free_boxed_hook(previous_hook);
}
fn remove_rollback_hook(&mut self) {
let previous_hook = unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) };
free_boxed_hook(previous_hook); free_boxed_hook(previous_hook);
} }
} }
self.free_update_hook = free_update_hook;
fn free_boxed_hook(hook: *mut c_void) {
if !hook.is_null() {
// TODO make sure that size_of::<*mut F>() is always equal to size_of::<*mut c_void>()
let _: Box<*mut c_void> = unsafe { Box::from_raw(hook as *mut _) };
} }
} }
fn free_boxed_hook<F>(p: *mut c_void) {
drop(unsafe { Box::from_raw(p as *mut F) });
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::Action; use super::Action;
use std::sync::atomic::{AtomicBool, Ordering};
use Connection; use Connection;
#[test] #[test]
fn test_commit_hook() { fn test_commit_hook() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
let mut called = false; lazy_static! {
db.commit_hook(|| { static ref called: AtomicBool = AtomicBool::new(false);
called = true; }
db.commit_hook(Some(|| {
called.store(true, Ordering::Relaxed);
false false
}); }));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;") db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
.unwrap(); .unwrap();
assert!(called); assert!(called.load(Ordering::Relaxed));
}
#[test]
fn test_fn_commit_hook() {
let db = Connection::open_in_memory().unwrap();
fn hook() -> bool {
true
}
db.commit_hook(Some(hook));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
.unwrap_err();
} }
#[test] #[test]
fn test_rollback_hook() { fn test_rollback_hook() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
let mut called = false; lazy_static! {
db.rollback_hook(|| { called = true; }); static ref called: AtomicBool = AtomicBool::new(false);
}
db.rollback_hook(Some(|| {
called.store(true, Ordering::Relaxed);
}));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;") db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
.unwrap(); .unwrap();
assert!(called); assert!(called.load(Ordering::Relaxed));
} }
#[test] #[test]
fn test_update_hook() { fn test_update_hook() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
let mut called = false; lazy_static! {
db.update_hook(|action, db, tbl, row_id| { static ref called: AtomicBool = AtomicBool::new(false);
}
db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
assert_eq!(Action::SQLITE_INSERT, action); assert_eq!(Action::SQLITE_INSERT, action);
assert_eq!("main", db); assert_eq!("main", db);
assert_eq!("foo", tbl); assert_eq!("foo", tbl);
assert_eq!(1, row_id); assert_eq!(1, row_id);
called = true; called.store(true, Ordering::Relaxed);
}); }));
db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap(); db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap(); db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
assert!(called); assert!(called.load(Ordering::Relaxed));
} }
} }

View File

@ -56,76 +56,80 @@ extern crate libsqlite3_sys as ffi;
extern crate lru_cache; extern crate lru_cache;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
#[cfg(all(test, feature = "trace"))] #[cfg(any(test, feature = "vtab"))]
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
use std::default::Default;
use std::convert;
use std::mem;
use std::ptr;
use std::fmt;
use std::path::{Path, PathBuf};
use std::cell::RefCell; use std::cell::RefCell;
use std::convert;
use std::default::Default;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::fmt;
use std::mem;
use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf};
use std::ptr;
use std::result; use std::result;
use std::str; use std::str;
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
use std::sync::{Once, ONCE_INIT}; use std::sync::{Once, ONCE_INIT};
use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
use std::os::raw::{c_int, c_char};
use types::{ToSql, ValueRef};
use error::{error_from_sqlite_code, error_from_handle};
use raw_statement::RawStatement;
use cache::StatementCache; use cache::StatementCache;
use error::{error_from_handle, error_from_sqlite_code};
use raw_statement::RawStatement;
use types::{ToSql, ValueRef};
pub use statement::Statement; pub use statement::Statement;
use statement::StatementCrateImpl;
pub use row::{Row, Rows, MappedRows, AndThenRows, RowIndex}; pub use row::{AndThenRows, MappedRows, Row, RowIndex, Rows};
use row::RowsCrateImpl;
pub use transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
#[allow(deprecated)] #[allow(deprecated)]
pub use transaction::{SqliteTransaction, SqliteTransactionBehavior}; pub use transaction::{SqliteTransaction, SqliteTransactionBehavior};
pub use transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
pub use error::Error;
#[allow(deprecated)] #[allow(deprecated)]
pub use error::SqliteError; pub use error::SqliteError;
pub use error::Error;
pub use ffi::ErrorCode; pub use ffi::ErrorCode;
pub use cache::CachedStatement; pub use cache::CachedStatement;
pub use version::*; pub use version::*;
#[cfg(feature = "hooks")]
pub use hooks::*;
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
#[allow(deprecated)] #[allow(deprecated)]
pub use load_extension_guard::{SqliteLoadExtensionGuard, LoadExtensionGuard}; pub use load_extension_guard::{LoadExtensionGuard, SqliteLoadExtensionGuard};
pub mod types; #[cfg(feature = "backup")]
mod version; pub mod backup;
mod transaction; #[cfg(feature = "blob")]
pub mod blob;
mod busy;
mod cache; mod cache;
#[cfg(any(feature = "functions", feature = "vtab"))]
mod context;
mod error; mod error;
#[cfg(feature = "functions")]
pub mod functions;
#[cfg(feature = "hooks")]
mod hooks;
#[cfg(feature = "limits")]
pub mod limits;
#[cfg(feature = "load_extension")]
mod load_extension_guard;
mod raw_statement; mod raw_statement;
mod row; mod row;
mod statement; mod statement;
#[cfg(feature = "load_extension")]
mod load_extension_guard;
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
pub mod trace; pub mod trace;
#[cfg(feature = "backup")] mod transaction;
pub mod backup; pub mod types;
#[cfg(feature = "functions")]
pub mod functions;
#[cfg(feature = "blob")]
pub mod blob;
#[cfg(feature = "limits")]
pub mod limits;
#[cfg(feature = "hooks")]
mod hooks;
#[cfg(feature = "hooks")]
pub use hooks::*;
mod unlock_notify; mod unlock_notify;
mod version;
#[cfg(feature = "vtab")]
pub mod vtab;
// Number of cached prepared statements we'll hold on to. // Number of cached prepared statements we'll hold on to.
const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
@ -152,6 +156,7 @@ fn path_to_cstring(p: &Path) -> Result<CString> {
} }
/// Name for a database within a SQLite connection. /// Name for a database within a SQLite connection.
#[derive(Copy, Clone)]
pub enum DatabaseName<'a> { pub enum DatabaseName<'a> {
/// The main database. /// The main database.
Main, Main,
@ -168,7 +173,7 @@ pub enum DatabaseName<'a> {
#[cfg(any(feature = "backup", feature = "blob"))] #[cfg(any(feature = "backup", feature = "blob"))]
impl<'a> DatabaseName<'a> { impl<'a> DatabaseName<'a> {
fn to_cstring(&self) -> Result<CString> { fn to_cstring(&self) -> Result<CString> {
use self::DatabaseName::{Main, Temp, Attached}; use self::DatabaseName::{Attached, Main, Temp};
match *self { match *self {
Main => str_to_cstring("main"), Main => str_to_cstring("main"),
Temp => str_to_cstring("temp"), Temp => str_to_cstring("temp"),
@ -207,7 +212,7 @@ impl Connection {
/// Will return `Err` if `path` cannot be converted to a C-compatible string or if the /// Will return `Err` if `path` cannot be converted to a C-compatible string or if the
/// underlying SQLite open call fails. /// underlying SQLite open call fails.
pub fn open<P: AsRef<Path>>(path: P) -> Result<Connection> { pub fn open<P: AsRef<Path>>(path: P) -> Result<Connection> {
let flags = Default::default(); let flags = OpenFlags::default();
Connection::open_with_flags(path, flags) Connection::open_with_flags(path, flags)
} }
@ -217,7 +222,7 @@ impl Connection {
/// ///
/// Will return `Err` if the underlying SQLite open call fails. /// Will return `Err` if the underlying SQLite open call fails.
pub fn open_in_memory() -> Result<Connection> { pub fn open_in_memory() -> Result<Connection> {
let flags = Default::default(); let flags = OpenFlags::default();
Connection::open_in_memory_with_flags(flags) Connection::open_in_memory_with_flags(flags)
} }
@ -232,12 +237,10 @@ impl Connection {
/// underlying SQLite open call fails. /// underlying SQLite open call fails.
pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: OpenFlags) -> Result<Connection> { pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: OpenFlags) -> Result<Connection> {
let c_path = try!(path_to_cstring(path.as_ref())); let c_path = try!(path_to_cstring(path.as_ref()));
InnerConnection::open_with_flags(&c_path, flags).map(|db| { InnerConnection::open_with_flags(&c_path, flags).map(|db| Connection {
Connection {
db: RefCell::new(db), db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY), cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
path: Some(path.as_ref().to_path_buf()), path: Some(path.as_ref().to_path_buf()),
}
}) })
} }
@ -251,12 +254,10 @@ impl Connection {
/// Will return `Err` if the underlying SQLite open call fails. /// Will return `Err` if the underlying SQLite open call fails.
pub fn open_in_memory_with_flags(flags: OpenFlags) -> Result<Connection> { pub fn open_in_memory_with_flags(flags: OpenFlags) -> Result<Connection> {
let c_memory = try!(str_to_cstring(":memory:")); let c_memory = try!(str_to_cstring(":memory:"));
InnerConnection::open_with_flags(&c_memory, flags).map(|db| { InnerConnection::open_with_flags(&c_memory, flags).map(|db| Connection {
Connection {
db: RefCell::new(db), db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY), cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
path: None, path: None,
}
}) })
} }
@ -305,9 +306,8 @@ impl Connection {
/// ///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails. /// underlying SQLite call fails.
pub fn execute(&self, sql: &str, params: &[&ToSql]) -> Result<c_int> { pub fn execute(&self, sql: &str, params: &[&ToSql]) -> Result<usize> {
self.prepare(sql) self.prepare(sql).and_then(|mut stmt| stmt.execute(params))
.and_then(|mut stmt| stmt.execute(params))
} }
/// Convenience method to prepare and execute a single SQL statement with named parameter(s). /// Convenience method to prepare and execute a single SQL statement with named parameter(s).
@ -319,7 +319,7 @@ impl Connection {
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// fn insert(conn: &Connection) -> Result<i32> { /// fn insert(conn: &Connection) -> Result<usize> {
/// conn.execute_named("INSERT INTO test (name) VALUES (:name)", &[(":name", &"one")]) /// conn.execute_named("INSERT INTO test (name) VALUES (:name)", &[(":name", &"one")])
/// } /// }
/// ``` /// ```
@ -328,7 +328,7 @@ impl Connection {
/// ///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails. /// underlying SQLite call fails.
pub fn execute_named(&self, sql: &str, params: &[(&str, &ToSql)]) -> Result<c_int> { pub fn execute_named(&self, sql: &str, params: &[(&str, &ToSql)]) -> Result<usize> {
self.prepare(sql) self.prepare(sql)
.and_then(|mut stmt| stmt.execute_named(params)) .and_then(|mut stmt| stmt.execute_named(params))
} }
@ -361,7 +361,8 @@ impl Connection {
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails. /// underlying SQLite call fails.
pub fn query_row<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T> pub fn query_row<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T>
where F: FnOnce(&Row) -> T where
F: FnOnce(&Row) -> T,
{ {
let mut stmt = try!(self.prepare(sql)); let mut stmt = try!(self.prepare(sql));
stmt.query_row(params, f) stmt.query_row(params, f)
@ -377,7 +378,8 @@ impl Connection {
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails. /// underlying SQLite call fails.
pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &ToSql)], f: F) -> Result<T> pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &ToSql)], f: F) -> Result<T>
where F: FnOnce(&Row) -> T where
F: FnOnce(&Row) -> T,
{ {
let mut stmt = try!(self.prepare(sql)); let mut stmt = try!(self.prepare(sql));
let mut rows = try!(stmt.query_named(params)); let mut rows = try!(stmt.query_named(params));
@ -408,20 +410,20 @@ impl Connection {
/// ///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails. /// underlying SQLite call fails.
pub fn query_row_and_then<T, E, F>(&self, pub fn query_row_and_then<T, E, F>(
&self,
sql: &str, sql: &str,
params: &[&ToSql], params: &[&ToSql],
f: F) f: F,
-> result::Result<T, E> ) -> result::Result<T, E>
where F: FnOnce(&Row) -> result::Result<T, E>, where
E: convert::From<Error> F: FnOnce(&Row) -> result::Result<T, E>,
E: convert::From<Error>,
{ {
let mut stmt = try!(self.prepare(sql)); let mut stmt = try!(self.prepare(sql));
let mut rows = try!(stmt.query(params)); let mut rows = try!(stmt.query(params));
rows.get_expected_row() rows.get_expected_row().map_err(E::from).and_then(|r| f(&r))
.map_err(E::from)
.and_then(|r| f(&r))
} }
/// Convenience method to execute a query that is expected to return a single row. /// Convenience method to execute a query that is expected to return a single row.
@ -445,7 +447,8 @@ impl Connection {
/// does exactly the same thing. /// does exactly the same thing.
#[deprecated(since = "0.1.0", note = "Use query_row instead")] #[deprecated(since = "0.1.0", note = "Use query_row instead")]
pub fn query_row_safe<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T> pub fn query_row_safe<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T>
where F: FnOnce(&Row) -> T where
F: FnOnce(&Row) -> T,
{ {
self.query_row(sql, params, f) self.query_row(sql, params, f)
} }
@ -545,10 +548,11 @@ impl Connection {
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
pub fn load_extension<P: AsRef<Path>>(&self, pub fn load_extension<P: AsRef<Path>>(
&self,
dylib_path: P, dylib_path: P,
entry_point: Option<&str>) entry_point: Option<&str>,
-> Result<()> { ) -> Result<()> {
self.db self.db
.borrow_mut() .borrow_mut()
.load_extension(dylib_path.as_ref(), entry_point) .load_extension(dylib_path.as_ref(), entry_point)
@ -570,9 +574,21 @@ impl Connection {
self.db.borrow_mut().decode_result(code) self.db.borrow_mut().decode_result(code)
} }
fn changes(&self) -> c_int { fn changes(&self) -> usize {
self.db.borrow_mut().changes() self.db.borrow_mut().changes()
} }
/// Test for auto-commit mode.
/// Autocommit mode is on by default.
pub fn is_autocommit(&self) -> bool {
self.db.borrow().is_autocommit()
}
/// Determine if all associated prepared statements have been reset.
#[cfg(feature = "bundled")]
pub fn is_busy(&self) -> bool {
self.db.borrow().is_busy()
}
} }
impl fmt::Debug for Connection { impl fmt::Debug for Connection {
@ -585,6 +601,12 @@ impl fmt::Debug for Connection {
struct InnerConnection { struct InnerConnection {
db: *mut ffi::sqlite3, db: *mut ffi::sqlite3,
#[cfg(feature = "hooks")]
free_commit_hook: Option<fn(*mut ::std::os::raw::c_void)>,
#[cfg(feature = "hooks")]
free_rollback_hook: Option<fn(*mut ::std::os::raw::c_void)>,
#[cfg(feature = "hooks")]
free_update_hook: Option<fn(*mut ::std::os::raw::c_void)>,
} }
/// Old name for `OpenFlags`. `SqliteOpenFlags` is deprecated. /// Old name for `OpenFlags`. `SqliteOpenFlags` is deprecated.
@ -610,8 +632,10 @@ bitflags! {
impl Default for OpenFlags { impl Default for OpenFlags {
fn default() -> OpenFlags { fn default() -> OpenFlags {
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE
OpenFlags::SQLITE_OPEN_NO_MUTEX | OpenFlags::SQLITE_OPEN_URI | OpenFlags::SQLITE_OPEN_CREATE
| OpenFlags::SQLITE_OPEN_NO_MUTEX
| OpenFlags::SQLITE_OPEN_URI
} }
} }
@ -658,9 +682,11 @@ fn ensure_valid_sqlite_version() {
let buildtime_major = ffi::SQLITE_VERSION_NUMBER / 1_000_000; let buildtime_major = ffi::SQLITE_VERSION_NUMBER / 1_000_000;
let runtime_major = version_number / 1_000_000; let runtime_major = version_number / 1_000_000;
if buildtime_major != runtime_major { if buildtime_major != runtime_major {
panic!("rusqlite was built against SQLite {} but is running with SQLite {}", panic!(
"rusqlite was built against SQLite {} but is running with SQLite {}",
str::from_utf8(ffi::SQLITE_VERSION).unwrap(), str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
version()); version()
);
} }
if BYPASS_VERSION_CHECK.load(Ordering::Relaxed) { if BYPASS_VERSION_CHECK.load(Ordering::Relaxed) {
@ -670,14 +696,16 @@ fn ensure_valid_sqlite_version() {
// Check that the runtime version number is compatible with the version number we found at // Check that the runtime version number is compatible with the version number we found at
// build-time. // build-time.
if version_number < ffi::SQLITE_VERSION_NUMBER { if version_number < ffi::SQLITE_VERSION_NUMBER {
panic!("\ panic!(
"\
rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fix this, either: rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fix this, either:
* Recompile rusqlite and link against the SQLite version you are using at runtime, or * Recompile rusqlite and link against the SQLite version you are using at runtime, or
* Call rusqlite::bypass_sqlite_version_check() prior to your first connection attempt. Doing this * Call rusqlite::bypass_sqlite_version_check() prior to your first connection attempt. Doing this
means you're sure everything will work correctly even though the runtime version is older than means you're sure everything will work correctly even though the runtime version is older than
the version we found at build time.", the version we found at build time.",
str::from_utf8(ffi::SQLITE_VERSION).unwrap(), str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
version()); version()
);
} }
}); });
} }
@ -743,6 +771,20 @@ To fix this, either:
} }
impl InnerConnection { impl InnerConnection {
#[cfg(not(feature = "hooks"))]
fn new(db: *mut ffi::sqlite3) -> InnerConnection {
InnerConnection { db }
}
#[cfg(feature = "hooks")]
fn new(db: *mut ffi::sqlite3) -> InnerConnection {
InnerConnection {
db,
free_commit_hook: None,
free_rollback_hook: None,
free_update_hook: None,
}
}
fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result<InnerConnection> { fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result<InnerConnection> {
ensure_valid_sqlite_version(); ensure_valid_sqlite_version();
ensure_safe_sqlite_threading_mode()?; ensure_safe_sqlite_threading_mode()?;
@ -751,9 +793,15 @@ impl InnerConnection {
// wasn't added until version 3.7.3. // wasn't added until version 3.7.3.
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits, 0x02); debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits, 0x02);
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits, 0x04); debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits, 0x04);
debug_assert_eq!(1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits, 0x40); debug_assert_eq!(
1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits,
0x40
);
if (1 << (flags.bits & 0x7)) & 0x46 == 0 { if (1 << (flags.bits & 0x7)) & 0x46 == 0 {
return Err(Error::SqliteFailure(ffi::Error::new(ffi::SQLITE_MISUSE), None)); return Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_MISUSE),
None,
));
} }
unsafe { unsafe {
@ -780,7 +828,7 @@ impl InnerConnection {
// attempt to turn on extended results code; don't fail if we can't. // attempt to turn on extended results code; don't fail if we can't.
ffi::sqlite3_extended_result_codes(db, 1); ffi::sqlite3_extended_result_codes(db, 1);
Ok(InnerConnection { db }) Ok(InnerConnection::new(db))
} }
} }
@ -814,11 +862,13 @@ impl InnerConnection {
fn execute_batch(&mut self, sql: &str) -> Result<()> { fn execute_batch(&mut self, sql: &str) -> Result<()> {
let c_sql = try!(str_to_cstring(sql)); let c_sql = try!(str_to_cstring(sql));
unsafe { unsafe {
let r = ffi::sqlite3_exec(self.db(), let r = ffi::sqlite3_exec(
self.db(),
c_sql.as_ptr(), c_sql.as_ptr(),
None, None,
ptr::null_mut(), ptr::null_mut(),
ptr::null_mut()); ptr::null_mut(),
);
self.decode_result(r) self.decode_result(r)
} }
} }
@ -836,10 +886,12 @@ impl InnerConnection {
let mut errmsg: *mut c_char = mem::uninitialized(); let mut errmsg: *mut c_char = mem::uninitialized();
let r = if let Some(entry_point) = entry_point { let r = if let Some(entry_point) = entry_point {
let c_entry = try!(str_to_cstring(entry_point)); let c_entry = try!(str_to_cstring(entry_point));
ffi::sqlite3_load_extension(self.db, ffi::sqlite3_load_extension(
self.db,
dylib_str.as_ptr(), dylib_str.as_ptr(),
c_entry.as_ptr(), c_entry.as_ptr(),
&mut errmsg) &mut errmsg,
)
} else { } else {
ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg) ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg)
}; };
@ -894,18 +946,35 @@ impl InnerConnection {
) )
} }
}; };
self.decode_result(r).map(|_| { self.decode_result(r)
Statement::new(conn, RawStatement::new(c_stmt)) .map(|_| Statement::new(conn, RawStatement::new(c_stmt)))
})
} }
fn changes(&mut self) -> c_int { fn changes(&mut self) -> usize {
unsafe { ffi::sqlite3_changes(self.db()) } unsafe { ffi::sqlite3_changes(self.db()) as usize }
}
fn is_autocommit(&self) -> bool {
unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
}
#[cfg(feature = "bundled")] // 3.8.6
fn is_busy(&self) -> bool {
let db = self.db();
unsafe {
let mut stmt = ffi::sqlite3_next_stmt(db, ptr::null_mut());
while !stmt.is_null() {
if ffi::sqlite3_stmt_busy(stmt) != 0 {
return true;
}
stmt = ffi::sqlite3_next_stmt(db, stmt);
}
}
false
} }
#[cfg(not(feature = "hooks"))] #[cfg(not(feature = "hooks"))]
fn remove_hooks(&mut self) { fn remove_hooks(&mut self) {}
}
} }
impl Drop for InnerConnection { impl Drop for InnerConnection {
@ -931,18 +1000,16 @@ pub type SqliteStatement<'conn> = Statement<'conn>;
#[deprecated(since = "0.6.0", note = "Use Rows instead")] #[deprecated(since = "0.6.0", note = "Use Rows instead")]
pub type SqliteRows<'stmt> = Rows<'stmt>; pub type SqliteRows<'stmt> = Rows<'stmt>;
/// Old name for `Row`. `SqliteRow` is deprecated. /// Old name for `Row`. `SqliteRow` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Row instead")] #[deprecated(since = "0.6.0", note = "Use Row instead")]
pub type SqliteRow<'a, 'stmt> = Row<'a, 'stmt>; pub type SqliteRow<'a, 'stmt> = Row<'a, 'stmt>;
#[cfg(test)] #[cfg(test)]
mod test { mod test {
extern crate tempdir; extern crate tempdir;
use self::tempdir::TempDir;
pub use super::*; pub use super::*;
use ffi; use ffi;
use self::tempdir::TempDir;
pub use std::error::Error as StdError; pub use std::error::Error as StdError;
pub use std::fmt; pub use std::fmt;
@ -959,7 +1026,53 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)] fn test_concurrent_transactions_busy_commit() {
use std::time::Duration;
let tmp = TempDir::new("locked").unwrap();
let path = tmp.path().join("transactions.db3");
Connection::open(&path)
.expect("create temp db")
.execute_batch(
"
BEGIN; CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42); END;",
).expect("create temp db");
let mut db1 = Connection::open(&path).unwrap();
let mut db2 = Connection::open(&path).unwrap();
db1.busy_timeout(Duration::from_millis(0)).unwrap();
db2.busy_timeout(Duration::from_millis(0)).unwrap();
{
let tx1 = db1.transaction().unwrap();
let tx2 = db2.transaction().unwrap();
// SELECT first makes sqlite lock with a shared lock
let _ = tx1
.query_row("SELECT x FROM foo LIMIT 1", &[], |_| ())
.unwrap();
let _ = tx2
.query_row("SELECT x FROM foo LIMIT 1", &[], |_| ())
.unwrap();
tx1.execute("INSERT INTO foo VALUES(?1)", &[&1]).unwrap();
let _ = tx2.execute("INSERT INTO foo VALUES(?1)", &[&2]);
let _ = tx1.commit();
let _ = tx2.commit();
}
let _ = db1
.transaction()
.expect("commit should have closed transaction");
let _ = db2
.transaction()
.expect("commit should have closed transaction");
}
#[test]
fn test_persistence() { fn test_persistence() {
let temp_dir = TempDir::new("test_open_file").unwrap(); let temp_dir = TempDir::new("test_open_file").unwrap();
let path = temp_dir.path().join("test.db3"); let path = temp_dir.path().join("test.db3");
@ -995,20 +1108,22 @@ mod test {
// force the DB to be busy by preparing a statement; this must be done at the FFI // force the DB to be busy by preparing a statement; this must be done at the FFI
// level to allow us to call .close() without dropping the prepared statement first. // level to allow us to call .close() without dropping the prepared statement first.
let raw_stmt = { let raw_stmt = {
use std::mem;
use std::ptr;
use std::os::raw::c_int;
use super::str_to_cstring; use super::str_to_cstring;
use std::mem;
use std::os::raw::c_int;
use std::ptr;
let raw_db = db.db.borrow_mut().db; let raw_db = db.db.borrow_mut().db;
let sql = "SELECT 1"; let sql = "SELECT 1";
let mut raw_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let mut raw_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let rc = unsafe { let rc = unsafe {
ffi::sqlite3_prepare_v2(raw_db, ffi::sqlite3_prepare_v2(
raw_db,
str_to_cstring(sql).unwrap().as_ptr(), str_to_cstring(sql).unwrap().as_ptr(),
(sql.len() + 1) as c_int, (sql.len() + 1) as c_int,
&mut raw_stmt, &mut raw_stmt,
ptr::null_mut()) ptr::null_mut(),
)
}; };
assert_eq!(rc, ffi::SQLITE_OK); assert_eq!(rc, ffi::SQLITE_OK);
raw_stmt raw_stmt
@ -1027,15 +1142,16 @@ mod test {
#[test] #[test]
fn test_open_with_flags() { fn test_open_with_flags() {
for bad_flags in &[OpenFlags::empty(), for bad_flags in &[
OpenFlags::empty(),
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_READ_WRITE, OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_READ_WRITE,
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_CREATE] { OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_CREATE,
] {
assert!(Connection::open_in_memory_with_flags(*bad_flags).is_err()); assert!(Connection::open_in_memory_with_flags(*bad_flags).is_err());
} }
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_execute_batch() { fn test_execute_batch() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1047,7 +1163,8 @@ mod test {
END;"; END;";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3").unwrap(); db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3")
.unwrap();
assert!(db.execute_batch("INVALID SQL").is_err()); assert!(db.execute_batch("INVALID SQL").is_err());
} }
@ -1057,16 +1174,22 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap(); db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
assert_eq!(1, assert_eq!(
1,
db.execute("INSERT INTO foo(x) VALUES (?)", &[&1i32]) db.execute("INSERT INTO foo(x) VALUES (?)", &[&1i32])
.unwrap()); .unwrap()
assert_eq!(1, );
assert_eq!(
1,
db.execute("INSERT INTO foo(x) VALUES (?)", &[&2i32]) db.execute("INSERT INTO foo(x) VALUES (?)", &[&2i32])
.unwrap()); .unwrap()
);
assert_eq!(3i32, assert_eq!(
3i32,
db.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0)) db.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()); .unwrap()
);
} }
#[test] #[test]
@ -1123,7 +1246,8 @@ mod test {
assert_eq!(insert_stmt.execute(&[&2i32]).unwrap(), 1); assert_eq!(insert_stmt.execute(&[&2i32]).unwrap(), 1);
assert_eq!(insert_stmt.execute(&[&3i32]).unwrap(), 1); assert_eq!(insert_stmt.execute(&[&3i32]).unwrap(), 1);
let mut query = db.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC") let mut query = db
.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC")
.unwrap(); .unwrap();
{ {
let mut rows = query.query(&[&4i32]).unwrap(); let mut rows = query.query(&[&4i32]).unwrap();
@ -1149,7 +1273,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_map() { fn test_query_map() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1162,15 +1285,13 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: Result<Vec<String>> = query.query_map(&[], |row| row.get(1)) let results: Result<Vec<String>> =
.unwrap() query.query_map(&[], |row| row.get(1)).unwrap().collect();
.collect();
assert_eq!(results.unwrap().concat(), "hello, world!"); assert_eq!(results.unwrap().concat(), "hello, world!");
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_row() { fn test_query_row() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1182,9 +1303,11 @@ mod test {
END;"; END;";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
assert_eq!(10i64, assert_eq!(
10i64,
db.query_row::<i64, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0)) db.query_row::<i64, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()); .unwrap()
);
let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", &[], |r| r.get(0)); let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", &[], |r| r.get(0));
match result.unwrap_err() { match result.unwrap_err() {
@ -1211,8 +1334,7 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)") db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")
.unwrap(); .unwrap();
db.execute_batch("INSERT INTO foo DEFAULT VALUES") db.execute_batch("INSERT INTO foo DEFAULT VALUES").unwrap();
.unwrap();
assert_eq!(db.last_insert_rowid(), 1); assert_eq!(db.last_insert_rowid(), 1);
@ -1223,6 +1345,32 @@ mod test {
assert_eq!(db.last_insert_rowid(), 10); assert_eq!(db.last_insert_rowid(), 10);
} }
#[test]
fn test_is_autocommit() {
let db = checked_memory_handle();
assert!(
db.is_autocommit(),
"autocommit expected to be active by default"
);
}
#[test]
#[cfg(feature = "bundled")]
fn test_is_busy() {
let db = checked_memory_handle();
assert!(!db.is_busy());
let mut stmt = db.prepare("PRAGMA schema_version").unwrap();
assert!(!db.is_busy());
{
let mut rows = stmt.query(&[]).unwrap();
assert!(!db.is_busy());
let row = rows.next();
assert!(db.is_busy());
assert!(row.is_some());
}
assert!(!db.is_busy());
}
#[test] #[test]
fn test_statement_debugging() { fn test_statement_debugging() {
let db = checked_memory_handle(); let db = checked_memory_handle();
@ -1308,7 +1456,6 @@ mod test {
type CustomResult<T> = ::std::result::Result<T, CustomError>; type CustomResult<T> = ::std::result::Result<T, CustomError>;
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then() { fn test_query_and_then() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1321,8 +1468,8 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: Result<Vec<String>> = query.query_and_then(&[], let results: Result<Vec<String>> = query
|row| row.get_checked(1)) .query_and_then(&[], |row| row.get_checked(1))
.unwrap() .unwrap()
.collect(); .collect();
@ -1330,7 +1477,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then_fails() { fn test_query_and_then_fails() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1343,7 +1489,8 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let bad_type: Result<Vec<f64>> = query.query_and_then(&[], |row| row.get_checked(1)) let bad_type: Result<Vec<f64>> = query
.query_and_then(&[], |row| row.get_checked(1))
.unwrap() .unwrap()
.collect(); .collect();
@ -1352,7 +1499,8 @@ mod test {
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
let bad_idx: Result<Vec<String>> = query.query_and_then(&[], |row| row.get_checked(3)) let bad_idx: Result<Vec<String>> = query
.query_and_then(&[], |row| row.get_checked(3))
.unwrap() .unwrap()
.collect(); .collect();
@ -1363,7 +1511,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then_custom_error() { fn test_query_and_then_custom_error() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1376,10 +1523,8 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: CustomResult<Vec<String>> = query.query_and_then(&[], |row| { let results: CustomResult<Vec<String>> = query
row.get_checked(1) .query_and_then(&[], |row| row.get_checked(1).map_err(CustomError::Sqlite))
.map_err(CustomError::Sqlite)
})
.unwrap() .unwrap()
.collect(); .collect();
@ -1387,7 +1532,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then_custom_error_fails() { fn test_query_and_then_custom_error_fails() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1400,10 +1544,8 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let bad_type: CustomResult<Vec<f64>> = query.query_and_then(&[], |row| { let bad_type: CustomResult<Vec<f64>> = query
row.get_checked(1) .query_and_then(&[], |row| row.get_checked(1).map_err(CustomError::Sqlite))
.map_err(CustomError::Sqlite)
})
.unwrap() .unwrap()
.collect(); .collect();
@ -1412,10 +1554,8 @@ mod test {
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
let bad_idx: CustomResult<Vec<String>> = query.query_and_then(&[], |row| { let bad_idx: CustomResult<Vec<String>> = query
row.get_checked(3) .query_and_then(&[], |row| row.get_checked(3).map_err(CustomError::Sqlite))
.map_err(CustomError::Sqlite)
})
.unwrap() .unwrap()
.collect(); .collect();
@ -1424,9 +1564,8 @@ mod test {
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
let non_sqlite_err: CustomResult<Vec<String>> = query.query_and_then(&[], |_| { let non_sqlite_err: CustomResult<Vec<String>> = query
Err(CustomError::SomeError) .query_and_then(&[], |_| Err(CustomError::SomeError))
})
.unwrap() .unwrap()
.collect(); .collect();
@ -1437,7 +1576,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_row_and_then_custom_error() { fn test_query_row_and_then_custom_error() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1455,7 +1593,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_row_and_then_custom_error_fails() { fn test_query_row_and_then_custom_error_fails() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1483,9 +1620,8 @@ mod test {
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
let non_sqlite_err: CustomResult<String> = db.query_row_and_then(query, &[], |_| { let non_sqlite_err: CustomResult<String> =
Err(CustomError::SomeError) db.query_row_and_then(query, &[], |_| Err(CustomError::SomeError));
});
match non_sqlite_err.unwrap_err() { match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (), CustomError::SomeError => (),
@ -1494,7 +1630,6 @@ mod test {
} }
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_dynamic() { fn test_dynamic() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let sql = "BEGIN; let sql = "BEGIN;
@ -1503,7 +1638,9 @@ mod test {
END;"; END;";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
db.query_row("SELECT * FROM foo", &[], |r| assert_eq!(2, r.column_count())).unwrap(); db.query_row("SELECT * FROM foo", &[], |r| {
assert_eq!(2, r.column_count())
}).unwrap();
} }
} }
} }

View File

@ -1,4 +1,4 @@
use {Result, Connection}; use {Connection, Result};
/// Old name for `LoadExtensionGuard`. `SqliteLoadExtensionGuard` is deprecated. /// Old name for `LoadExtensionGuard`. `SqliteLoadExtensionGuard` is deprecated.
#[deprecated(since = "0.6.0", note = "Use LoadExtensionGuard instead")] #[deprecated(since = "0.6.0", note = "Use LoadExtensionGuard instead")]

View File

@ -1,8 +1,8 @@
use std::ffi::CStr;
use std::ptr;
use std::os::raw::c_int;
use super::ffi; use super::ffi;
use super::unlock_notify; use super::unlock_notify;
use std::ffi::CStr;
use std::os::raw::c_int;
use std::ptr;
// Private newtype for raw sqlite3_stmts that finalize themselves when dropped. // Private newtype for raw sqlite3_stmts that finalize themselves when dropped.
#[derive(Debug)] #[derive(Debug)]
@ -17,16 +17,16 @@ impl RawStatement {
self.0 self.0
} }
pub fn column_count(&self) -> c_int { pub fn column_count(&self) -> usize {
unsafe { ffi::sqlite3_column_count(self.0) } unsafe { ffi::sqlite3_column_count(self.0) as usize }
} }
pub fn column_type(&self, idx: c_int) -> c_int { pub fn column_type(&self, idx: usize) -> c_int {
unsafe { ffi::sqlite3_column_type(self.0, idx) } unsafe { ffi::sqlite3_column_type(self.0, idx as c_int) }
} }
pub fn column_name(&self, idx: c_int) -> &CStr { pub fn column_name(&self, idx: usize) -> &CStr {
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx)) } unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx as c_int)) }
} }
pub fn step(&self) -> c_int { pub fn step(&self) -> c_int {
@ -54,15 +54,15 @@ impl RawStatement {
unsafe { ffi::sqlite3_reset(self.0) } unsafe { ffi::sqlite3_reset(self.0) }
} }
pub fn bind_parameter_count(&self) -> c_int { pub fn bind_parameter_count(&self) -> usize {
unsafe { ffi::sqlite3_bind_parameter_count(self.0) } unsafe { ffi::sqlite3_bind_parameter_count(self.0) as usize }
} }
pub fn bind_parameter_index(&self, name: &CStr) -> Option<c_int> { pub fn bind_parameter_index(&self, name: &CStr) -> Option<usize> {
let r = unsafe { ffi::sqlite3_bind_parameter_index(self.0, name.as_ptr()) }; let r = unsafe { ffi::sqlite3_bind_parameter_index(self.0, name.as_ptr()) };
match r { match r {
0 => None, 0 => None,
i => Some(i), i => Some(i as usize),
} }
} }
@ -83,6 +83,23 @@ impl RawStatement {
self.0 = ptr::null_mut(); self.0 = ptr::null_mut();
r r
} }
#[cfg(feature = "bundled")]
pub fn readonly(&self) -> bool {
unsafe { ffi::sqlite3_stmt_readonly(self.0) != 0 }
}
#[cfg(feature = "bundled")]
pub fn expanded_sql(&self) -> Option<&CStr> {
unsafe {
let ptr = ffi::sqlite3_expanded_sql(self.0);
if ptr.is_null() {
None
} else {
Some(CStr::from_ptr(ptr))
}
}
}
} }
impl Drop for RawStatement { impl Drop for RawStatement {

View File

@ -1,9 +1,8 @@
use std::{convert, result};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::{convert, result};
use super::{Statement, Error, Result}; use super::{Error, Result, Statement};
use types::{FromSql, FromSqlError}; use types::{FromSql, FromSqlError};
use statement::StatementCrateImpl;
/// An handle for the resulting rows of a query. /// An handle for the resulting rows of a query.
pub struct Rows<'stmt> { pub struct Rows<'stmt> {
@ -27,15 +26,13 @@ impl<'stmt> Rows<'stmt> {
/// lifetime of the returned row is tied to the lifetime of `self`. This is a /// lifetime of the returned row is tied to the lifetime of `self`. This is a
/// "streaming iterator". For a more natural interface, consider using `query_map` /// "streaming iterator". For a more natural interface, consider using `query_map`
/// or `query_and_then` instead, which return types that implement `Iterator`. /// or `query_and_then` instead, which return types that implement `Iterator`.
#[cfg_attr(feature = "cargo-clippy", allow(should_implement_trait))] // cannot implement Iterator
pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> { pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> {
self.stmt self.stmt.and_then(|stmt| match stmt.step() {
.and_then(|stmt| match stmt.step() { Ok(true) => Some(Ok(Row {
Ok(true) => {
Some(Ok(Row {
stmt, stmt,
phantom: PhantomData, phantom: PhantomData,
})) })),
}
Ok(false) => { Ok(false) => {
self.reset(); self.reset();
None None
@ -48,19 +45,12 @@ impl<'stmt> Rows<'stmt> {
} }
} }
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this impl<'stmt> Rows<'stmt> {
// once pub(crate) is stable. pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
pub trait RowsCrateImpl<'stmt> {
fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt>;
fn get_expected_row<'a>(&'a mut self) -> Result<Row<'a, 'stmt>>;
}
impl<'stmt> RowsCrateImpl<'stmt> for Rows<'stmt> {
fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
Rows { stmt: Some(stmt) } Rows { stmt: Some(stmt) }
} }
fn get_expected_row<'a>(&'a mut self) -> Result<Row<'a, 'stmt>> { pub(crate) fn get_expected_row<'a>(&'a mut self) -> Result<Row<'a, 'stmt>> {
match self.next() { match self.next() {
Some(row) => row, Some(row) => row,
None => Err(Error::QueryReturnedNoRows), None => Err(Error::QueryReturnedNoRows),
@ -80,22 +70,18 @@ pub struct MappedRows<'stmt, F> {
map: F, map: F,
} }
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this impl<'stmt, T, F> MappedRows<'stmt, F>
// once pub(crate) is stable. where
pub trait MappedRowsCrateImpl<'stmt, T, F> { F: FnMut(&Row) -> T,
fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F>;
}
impl<'stmt, T, F> MappedRowsCrateImpl<'stmt, T, F> for MappedRows<'stmt, F>
where F: FnMut(&Row) -> T
{ {
fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> { pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
MappedRows { rows, map: f } MappedRows { rows, map: f }
} }
} }
impl<'conn, T, F> Iterator for MappedRows<'conn, F> impl<'conn, T, F> Iterator for MappedRows<'conn, F>
where F: FnMut(&Row) -> T where
F: FnMut(&Row) -> T,
{ {
type Item = Result<T>; type Item = Result<T>;
@ -114,23 +100,19 @@ pub struct AndThenRows<'stmt, F> {
map: F, map: F,
} }
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this impl<'stmt, T, E, F> AndThenRows<'stmt, F>
// once pub(crate) is stable. where
pub trait AndThenRowsCrateImpl<'stmt, T, E, F> { F: FnMut(&Row) -> result::Result<T, E>,
fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F>;
}
impl<'stmt, T, E, F> AndThenRowsCrateImpl<'stmt, T, E, F> for AndThenRows<'stmt, F>
where F: FnMut(&Row) -> result::Result<T, E>
{ {
fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> { pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
AndThenRows { rows, map: f } AndThenRows { rows, map: f }
} }
} }
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F> impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
where E: convert::From<Error>, where
F: FnMut(&Row) -> result::Result<T, E> E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>,
{ {
type Item = result::Result<T, E>; type Item = result::Result<T, E>;
@ -178,13 +160,8 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
let idx = try!(idx.idx(self.stmt)); let idx = try!(idx.idx(self.stmt));
let value = self.stmt.value_ref(idx); let value = self.stmt.value_ref(idx);
FromSql::column_result(value).map_err(|err| match err { FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => { FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
Error::InvalidColumnType(idx, FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
value.data_type())
}
FromSqlError::OutOfRange(i) => {
Error::IntegralValueOutOfRange(idx, i)
}
FromSqlError::Other(err) => { FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err) Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
} }
@ -192,7 +169,7 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
} }
/// Return the number of columns in the current row. /// Return the number of columns in the current row.
pub fn column_count(&self) -> i32 { pub fn column_count(&self) -> usize {
self.stmt.column_count() self.stmt.column_count()
} }
} }
@ -201,13 +178,13 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
pub trait RowIndex { pub trait RowIndex {
/// Returns the index of the appropriate column, or `None` if no such /// Returns the index of the appropriate column, or `None` if no such
/// column exists. /// column exists.
fn idx(&self, stmt: &Statement) -> Result<i32>; fn idx(&self, stmt: &Statement) -> Result<usize>;
} }
impl RowIndex for i32 { impl RowIndex for usize {
#[inline] #[inline]
fn idx(&self, stmt: &Statement) -> Result<i32> { fn idx(&self, stmt: &Statement) -> Result<usize> {
if *self < 0 || *self >= stmt.column_count() { if *self >= stmt.column_count() {
Err(Error::InvalidColumnIndex(*self)) Err(Error::InvalidColumnIndex(*self))
} else { } else {
Ok(*self) Ok(*self)
@ -217,7 +194,7 @@ impl RowIndex for i32 {
impl<'a> RowIndex for &'a str { impl<'a> RowIndex for &'a str {
#[inline] #[inline]
fn idx(&self, stmt: &Statement) -> Result<i32> { fn idx(&self, stmt: &Statement) -> Result<usize> {
stmt.column_index(*self) stmt.column_index(*self)
} }
} }

View File

@ -1,13 +1,18 @@
use std::{convert, fmt, mem, ptr, result, str};
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
#[cfg(feature = "array")]
use std::rc::Rc;
use std::slice::from_raw_parts; use std::slice::from_raw_parts;
use std::{convert, fmt, mem, ptr, result, str};
use super::ffi; use super::ffi;
use super::{Connection, RawStatement, Result, Error, ValueRef, Row, Rows, AndThenRows, MappedRows};
use super::str_to_cstring; use super::str_to_cstring;
use super::{
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
};
use types::{ToSql, ToSqlOutput}; use types::{ToSql, ToSqlOutput};
use row::{RowsCrateImpl, MappedRowsCrateImpl, AndThenRowsCrateImpl}; #[cfg(feature = "array")]
use vtab::array::{free_array, ARRAY_TYPE};
/// A prepared statement. /// A prepared statement.
pub struct Statement<'conn> { pub struct Statement<'conn> {
@ -29,7 +34,7 @@ impl<'conn> Statement<'conn> {
} }
/// Return the number of columns in the result set returned by the prepared statement. /// Return the number of columns in the result set returned by the prepared statement.
pub fn column_count(&self) -> i32 { pub fn column_count(&self) -> usize {
self.stmt.column_count() self.stmt.column_count()
} }
@ -41,7 +46,7 @@ impl<'conn> Statement<'conn> {
/// # Failure /// # Failure
/// ///
/// Will return an `Error::InvalidColumnName` when there is no column with the specified `name`. /// Will return an `Error::InvalidColumnName` when there is no column with the specified `name`.
pub fn column_index(&self, name: &str) -> Result<i32> { pub fn column_index(&self, name: &str) -> Result<usize> {
let bytes = name.as_bytes(); let bytes = name.as_bytes();
let n = self.column_count(); let n = self.column_count();
for i in 0..n { for i in 0..n {
@ -75,7 +80,7 @@ impl<'conn> Statement<'conn> {
/// ///
/// Will return `Err` if binding parameters fails, the executed statement returns rows (in /// 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. /// which case `query` should be used instead), or the underling SQLite call fails.
pub fn execute(&mut self, params: &[&ToSql]) -> Result<c_int> { pub fn execute(&mut self, params: &[&ToSql]) -> Result<usize> {
try!(self.bind_parameters(params)); try!(self.bind_parameters(params));
self.execute_with_bound_parameters() self.execute_with_bound_parameters()
} }
@ -92,7 +97,7 @@ impl<'conn> Statement<'conn> {
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// fn insert(conn: &Connection) -> Result<i32> { /// fn insert(conn: &Connection) -> Result<usize> {
/// let mut stmt = try!(conn.prepare("INSERT INTO test (name) VALUES (:name)")); /// let mut stmt = try!(conn.prepare("INSERT INTO test (name) VALUES (:name)"));
/// stmt.execute_named(&[(":name", &"one")]) /// stmt.execute_named(&[(":name", &"one")])
/// } /// }
@ -102,7 +107,7 @@ impl<'conn> Statement<'conn> {
/// ///
/// Will return `Err` if binding parameters fails, the executed statement returns rows (in /// 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. /// 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> { pub fn execute_named(&mut self, params: &[(&str, &ToSql)]) -> Result<usize> {
try!(self.bind_parameters_named(params)); try!(self.bind_parameters_named(params));
self.execute_with_bound_parameters() self.execute_with_bound_parameters()
} }
@ -155,6 +160,7 @@ impl<'conn> Statement<'conn> {
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query<'a>(&'a mut self, params: &[&ToSql]) -> Result<Rows<'a>> { pub fn query<'a>(&'a mut self, params: &[&ToSql]) -> Result<Rows<'a>> {
try!(self.check_readonly());
try!(self.bind_parameters(params)); try!(self.bind_parameters(params));
Ok(Rows::new(self)) Ok(Rows::new(self))
} }
@ -182,6 +188,7 @@ impl<'conn> Statement<'conn> {
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_named<'a>(&'a mut self, params: &[(&str, &ToSql)]) -> Result<Rows<'a>> { pub fn query_named<'a>(&'a mut self, params: &[(&str, &ToSql)]) -> Result<Rows<'a>> {
try!(self.check_readonly());
try!(self.bind_parameters_named(params)); try!(self.bind_parameters_named(params));
Ok(Rows::new(self)) Ok(Rows::new(self))
} }
@ -210,7 +217,8 @@ impl<'conn> Statement<'conn> {
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F) -> Result<MappedRows<'a, F>> pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F) -> Result<MappedRows<'a, F>>
where F: FnMut(&Row) -> T where
F: FnMut(&Row) -> T,
{ {
let rows = self.query(params)?; let rows = self.query(params)?;
Ok(MappedRows::new(rows, f)) Ok(MappedRows::new(rows, f))
@ -242,11 +250,13 @@ impl<'conn> Statement<'conn> {
/// ## Failure /// ## Failure
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_map_named<'a, T, F>(&'a mut self, pub fn query_map_named<'a, T, F>(
&'a mut self,
params: &[(&str, &ToSql)], params: &[(&str, &ToSql)],
f: F) f: F,
-> Result<MappedRows<'a, F>> ) -> Result<MappedRows<'a, F>>
where F: FnMut(&Row) -> T where
F: FnMut(&Row) -> T,
{ {
let rows = self.query_named(params)?; let rows = self.query_named(params)?;
Ok(MappedRows::new(rows, f)) Ok(MappedRows::new(rows, f))
@ -259,12 +269,14 @@ impl<'conn> Statement<'conn> {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_and_then<'a, T, E, F>(&'a mut self, pub fn query_and_then<'a, T, E, F>(
&'a mut self,
params: &[&ToSql], params: &[&ToSql],
f: F) f: F,
-> Result<AndThenRows<'a, F>> ) -> Result<AndThenRows<'a, F>>
where E: convert::From<Error>, where
F: FnMut(&Row) -> result::Result<T, E> E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>,
{ {
let rows = self.query(params)?; let rows = self.query(params)?;
Ok(AndThenRows::new(rows, f)) Ok(AndThenRows::new(rows, f))
@ -305,12 +317,14 @@ impl<'conn> Statement<'conn> {
/// ## Failure /// ## Failure
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_and_then_named<'a, T, E, F>(&'a mut self, pub fn query_and_then_named<'a, T, E, F>(
&'a mut self,
params: &[(&str, &ToSql)], params: &[(&str, &ToSql)],
f: F) f: F,
-> Result<AndThenRows<'a, F>> ) -> Result<AndThenRows<'a, F>>
where E: convert::From<Error>, where
F: FnMut(&Row) -> result::Result<T, E> E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>,
{ {
let rows = self.query_named(params)?; let rows = self.query_named(params)?;
Ok(AndThenRows::new(rows, f)) Ok(AndThenRows::new(rows, f))
@ -337,7 +351,8 @@ impl<'conn> Statement<'conn> {
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
pub fn query_row<T, F>(&mut self, params: &[&ToSql], f: F) -> Result<T> pub fn query_row<T, F>(&mut self, params: &[&ToSql], f: F) -> Result<T>
where F: FnOnce(&Row) -> T where
F: FnOnce(&Row) -> T,
{ {
let mut rows = try!(self.query(params)); let mut rows = try!(self.query(params));
@ -362,19 +377,22 @@ impl<'conn> Statement<'conn> {
/// ///
/// Will return Err if `name` is invalid. Will return Ok(None) if the name /// Will return Err if `name` is invalid. Will return Ok(None) if the name
/// is valid but not a bound parameter of this statement. /// is valid but not a bound parameter of this statement.
pub fn parameter_index(&self, name: &str) -> Result<Option<i32>> { pub fn parameter_index(&self, name: &str) -> Result<Option<usize>> {
let c_name = try!(str_to_cstring(name)); let c_name = try!(str_to_cstring(name));
Ok(self.stmt.bind_parameter_index(&c_name)) Ok(self.stmt.bind_parameter_index(&c_name))
} }
fn bind_parameters(&mut self, params: &[&ToSql]) -> Result<()> { fn bind_parameters(&mut self, params: &[&ToSql]) -> Result<()> {
assert_eq!(params.len() as c_int, self.stmt.bind_parameter_count(), assert_eq!(
params.len(),
self.stmt.bind_parameter_count(),
"incorrect number of parameters to query(): expected {}, got {}", "incorrect number of parameters to query(): expected {}, got {}",
self.stmt.bind_parameter_count(), self.stmt.bind_parameter_count(),
params.len()); params.len()
);
for (i, p) in params.iter().enumerate() { for (i, p) in params.iter().enumerate() {
try!(self.bind_parameter(*p, (i + 1) as c_int)); try!(self.bind_parameter(*p, i + 1));
} }
Ok(()) Ok(())
@ -391,7 +409,7 @@ impl<'conn> Statement<'conn> {
Ok(()) Ok(())
} }
fn bind_parameter(&self, param: &ToSql, col: c_int) -> Result<()> { fn bind_parameter(&self, param: &ToSql, col: usize) -> Result<()> {
let value = try!(param.to_sql()); let value = try!(param.to_sql());
let ptr = unsafe { self.stmt.ptr() }; let ptr = unsafe { self.stmt.ptr() };
@ -401,19 +419,27 @@ impl<'conn> Statement<'conn> {
#[cfg(feature = "blob")] #[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => { ToSqlOutput::ZeroBlob(len) => {
return self.conn return self
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col, len) }); .conn
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, len) });
}
#[cfg(feature = "array")]
ToSqlOutput::Array(a) => {
return self.conn.decode_result(unsafe {
ffi::sqlite3_bind_pointer(
ptr,
col as c_int,
Rc::into_raw(a) as *mut c_void,
ARRAY_TYPE,
Some(free_array),
)
});
} }
}; };
self.conn self.conn.decode_result(match value {
.decode_result(match value { ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, col as c_int) },
ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, col) }, ValueRef::Integer(i) => unsafe { ffi::sqlite3_bind_int64(ptr, col as c_int, i) },
ValueRef::Integer(i) => unsafe { ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
ffi::sqlite3_bind_int64(ptr, col, i)
},
ValueRef::Real(r) => unsafe {
ffi::sqlite3_bind_double(ptr, col, r)
},
ValueRef::Text(s) => unsafe { ValueRef::Text(s) => unsafe {
let length = s.len(); let length = s.len();
if length > ::std::i32::MAX as usize { if length > ::std::i32::MAX as usize {
@ -425,7 +451,13 @@ impl<'conn> Statement<'conn> {
} else { } else {
ffi::SQLITE_STATIC() ffi::SQLITE_STATIC()
}; };
ffi::sqlite3_bind_text(ptr, col, c_str.as_ptr(), length as c_int, destructor) ffi::sqlite3_bind_text(
ptr,
col as c_int,
c_str.as_ptr(),
length as c_int,
destructor,
)
} }
}, },
ValueRef::Blob(b) => unsafe { ValueRef::Blob(b) => unsafe {
@ -433,19 +465,21 @@ impl<'conn> Statement<'conn> {
if length > ::std::i32::MAX as usize { if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG ffi::SQLITE_TOOBIG
} else if length == 0 { } else if length == 0 {
ffi::sqlite3_bind_zeroblob(ptr, col, 0) ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
} else { } else {
ffi::sqlite3_bind_blob(ptr, ffi::sqlite3_bind_blob(
col, ptr,
col as c_int,
b.as_ptr() as *const c_void, b.as_ptr() as *const c_void,
length as c_int, length as c_int,
ffi::SQLITE_TRANSIENT()) ffi::SQLITE_TRANSIENT(),
)
} }
}, },
}) })
} }
fn execute_with_bound_parameters(&mut self) -> Result<c_int> { fn execute_with_bound_parameters(&mut self) -> Result<usize> {
let r = self.stmt.step(); let r = self.stmt.step();
self.stmt.reset(); self.stmt.reset();
match r { match r {
@ -466,6 +500,29 @@ impl<'conn> Statement<'conn> {
mem::swap(&mut stmt, &mut self.stmt); mem::swap(&mut stmt, &mut self.stmt);
self.conn.decode_result(stmt.finalize()) self.conn.decode_result(stmt.finalize())
} }
#[cfg(not(feature = "bundled"))]
fn check_readonly(&self) -> Result<()> {
Ok(())
}
#[cfg(feature = "bundled")]
fn check_readonly(&self) -> Result<()> {
if !self.stmt.readonly() {
return Err(Error::InvalidQuery);
}
Ok(())
}
/// Returns a string containing the SQL text of prepared statement with bound parameters expanded.
#[cfg(feature = "bundled")]
pub fn expanded_sql(&self) -> Option<&str> {
unsafe {
self.stmt
.expanded_sql()
.map(|s| str::from_utf8_unchecked(s.to_bytes()))
}
}
} }
impl<'conn> Into<RawStatement> for Statement<'conn> { impl<'conn> Into<RawStatement> for Statement<'conn> {
@ -494,55 +551,55 @@ impl<'conn> Drop for Statement<'conn> {
} }
} }
// TODO: This trait lets us have "pub(crate)" visibility on some Statement methods. Remove this impl<'conn> Statement<'conn> {
// once pub(crate) is stable. pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement {
pub trait StatementCrateImpl<'conn> { Statement { conn, stmt }
fn new(conn: &'conn Connection, stmt: RawStatement) -> Self;
fn value_ref(&self, col: c_int) -> ValueRef;
fn step(&self) -> Result<bool>;
fn reset(&self) -> c_int;
} }
impl<'conn> StatementCrateImpl<'conn> for Statement<'conn> { pub(crate) fn value_ref(&self, col: usize) -> ValueRef {
fn new(conn: &Connection, stmt: RawStatement) -> Statement {
Statement {
conn,
stmt,
}
}
fn value_ref(&self, col: c_int) -> ValueRef {
let raw = unsafe { self.stmt.ptr() }; let raw = unsafe { self.stmt.ptr() };
match self.stmt.column_type(col) { match self.stmt.column_type(col) {
ffi::SQLITE_NULL => ValueRef::Null, ffi::SQLITE_NULL => ValueRef::Null,
ffi::SQLITE_INTEGER => { ffi::SQLITE_INTEGER => {
ValueRef::Integer(unsafe { ffi::sqlite3_column_int64(raw, col) }) ValueRef::Integer(unsafe { ffi::sqlite3_column_int64(raw, col as c_int) })
}
ffi::SQLITE_FLOAT => {
ValueRef::Real(unsafe { ffi::sqlite3_column_double(raw, col as c_int) })
} }
ffi::SQLITE_FLOAT => ValueRef::Real(unsafe { ffi::sqlite3_column_double(raw, col) }),
ffi::SQLITE_TEXT => { ffi::SQLITE_TEXT => {
let s = unsafe { let s = unsafe {
let text = ffi::sqlite3_column_text(raw, col); let text = ffi::sqlite3_column_text(raw, col as c_int);
assert!(!text.is_null(), assert!(
"unexpected SQLITE_TEXT column type with NULL data"); !text.is_null(),
"unexpected SQLITE_TEXT column type with NULL data"
);
CStr::from_ptr(text as *const c_char) CStr::from_ptr(text as *const c_char)
}; };
// sqlite3_column_text returns UTF8 data, so our unwrap here should be fine. // sqlite3_column_text returns UTF8 data, so our unwrap here should be fine.
let s = s.to_str() let s = s
.to_str()
.expect("sqlite3_column_text returned invalid UTF-8"); .expect("sqlite3_column_text returned invalid UTF-8");
ValueRef::Text(s) ValueRef::Text(s)
} }
ffi::SQLITE_BLOB => { ffi::SQLITE_BLOB => {
let (blob, len) = unsafe { let (blob, len) = unsafe {
(ffi::sqlite3_column_blob(raw, col), ffi::sqlite3_column_bytes(raw, col)) (
ffi::sqlite3_column_blob(raw, col as c_int),
ffi::sqlite3_column_bytes(raw, col as c_int),
)
}; };
assert!(len >= 0, assert!(
"unexpected negative return from sqlite3_column_bytes"); len >= 0,
"unexpected negative return from sqlite3_column_bytes"
);
if len > 0 { if len > 0 {
assert!(!blob.is_null(), assert!(
"unexpected SQLITE_BLOB column type with NULL data"); !blob.is_null(),
"unexpected SQLITE_BLOB column type with NULL data"
);
ValueRef::Blob(unsafe { from_raw_parts(blob as *const u8, len as usize) }) ValueRef::Blob(unsafe { from_raw_parts(blob as *const u8, len as usize) })
} else { } else {
// The return value from sqlite3_column_blob() for a zero-length BLOB // The return value from sqlite3_column_blob() for a zero-length BLOB
@ -554,7 +611,7 @@ impl<'conn> StatementCrateImpl<'conn> for Statement<'conn> {
} }
} }
fn step(&self) -> Result<bool> { pub(crate) fn step(&self) -> Result<bool> {
match self.stmt.step() { match self.stmt.step() {
ffi::SQLITE_ROW => Ok(true), ffi::SQLITE_ROW => Ok(true),
ffi::SQLITE_DONE => Ok(false), ffi::SQLITE_DONE => Ok(false),
@ -562,7 +619,7 @@ impl<'conn> StatementCrateImpl<'conn> for Statement<'conn> {
} }
} }
fn reset(&self) -> c_int { pub(crate) fn reset(&self) -> c_int {
self.stmt.reset() self.stmt.reset()
} }
} }
@ -576,18 +633,25 @@ mod test {
let db = Connection::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();
assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)]) assert_eq!(
db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])
.unwrap(), .unwrap(),
1); 1
assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)]) );
assert_eq!(
db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)])
.unwrap(), .unwrap(),
1); 1
);
assert_eq!(3i32, assert_eq!(
db.query_row_named::<i32, _>("SELECT SUM(x) FROM foo WHERE x > :x", 3i32,
db.query_row_named::<i32, _>(
"SELECT SUM(x) FROM foo WHERE x > :x",
&[(":x", &0i32)], &[(":x", &0i32)],
|r| r.get(0)) |r| r.get(0)
.unwrap()); ).unwrap()
);
} }
#[test] #[test]
@ -597,15 +661,19 @@ mod test {
INTEGER)"; INTEGER)";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)") let mut stmt = db
.prepare("INSERT INTO test (name) VALUES (:name)")
.unwrap(); .unwrap();
stmt.execute_named(&[(":name", &"one")]).unwrap(); stmt.execute_named(&[(":name", &"one")]).unwrap();
assert_eq!(1i32, assert_eq!(
db.query_row_named::<i32, _>("SELECT COUNT(*) FROM test WHERE name = :name", 1i32,
db.query_row_named::<i32, _>(
"SELECT COUNT(*) FROM test WHERE name = :name",
&[(":name", &"one")], &[(":name", &"one")],
|r| r.get(0)) |r| r.get(0)
.unwrap()); ).unwrap()
);
} }
#[test] #[test]
@ -617,7 +685,8 @@ mod test {
"#; "#;
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("SELECT id FROM test where name = :name") let mut stmt = db
.prepare("SELECT id FROM test where name = :name")
.unwrap(); .unwrap();
let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap(); let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap();
@ -634,13 +703,14 @@ mod test {
"#; "#;
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("SELECT id FROM test where name = :name") let mut stmt = db
.prepare("SELECT id FROM test where name = :name")
.unwrap(); .unwrap();
let mut rows = stmt.query_map_named(&[(":name", &"one")], |row| { let mut rows = stmt
.query_map_named(&[(":name", &"one")], |row| {
let id: i32 = row.get(0); let id: i32 = row.get(0);
2 * id 2 * id
}) }).unwrap();
.unwrap();
let doubled_id: i32 = rows.next().unwrap().unwrap(); let doubled_id: i32 = rows.next().unwrap().unwrap();
assert_eq!(2, doubled_id); assert_eq!(2, doubled_id);
@ -648,7 +718,6 @@ mod test {
#[test] #[test]
fn test_query_and_then_named() { fn test_query_and_then_named() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
let sql = r#" let sql = r#"
CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER); CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
@ -657,17 +726,18 @@ mod test {
"#; "#;
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("SELECT id FROM test where name = :name ORDER BY id ASC") let mut stmt = db
.prepare("SELECT id FROM test where name = :name ORDER BY id ASC")
.unwrap(); .unwrap();
let mut rows = stmt.query_and_then_named(&[(":name", &"one")], |row| { let mut rows = stmt
.query_and_then_named(&[(":name", &"one")], |row| {
let id: i32 = row.get(0); let id: i32 = row.get(0);
if id == 1 { if id == 1 {
Ok(id) Ok(id)
} else { } else {
Err(Error::SqliteSingleThreadedMode) Err(Error::SqliteSingleThreadedMode)
} }
}) }).unwrap();
.unwrap();
// first row should be Ok // first row should be Ok
let doubled_id: i32 = rows.next().unwrap().unwrap(); let doubled_id: i32 = rows.next().unwrap().unwrap();
@ -687,12 +757,13 @@ mod test {
let sql = "CREATE TABLE test (x TEXT, y TEXT)"; let sql = "CREATE TABLE test (x TEXT, y TEXT)";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)") let mut stmt = db
.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
.unwrap(); .unwrap();
stmt.execute_named(&[(":x", &"one")]).unwrap(); stmt.execute_named(&[(":x", &"one")]).unwrap();
let result: Option<String> = let result: Option<String> = db
db.query_row("SELECT y FROM test WHERE x = 'one'", &[], |row| row.get(0)) .query_row("SELECT y FROM test WHERE x = 'one'", &[], |row| row.get(0))
.unwrap(); .unwrap();
assert!(result.is_none()); assert!(result.is_none());
} }
@ -703,13 +774,14 @@ mod test {
let sql = "CREATE TABLE test (x TEXT, y TEXT)"; let sql = "CREATE TABLE test (x TEXT, y TEXT)";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)") let mut stmt = db
.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
.unwrap(); .unwrap();
stmt.execute_named(&[(":x", &"one")]).unwrap(); stmt.execute_named(&[(":x", &"one")]).unwrap();
stmt.execute_named(&[(":y", &"two")]).unwrap(); stmt.execute_named(&[(":y", &"two")]).unwrap();
let result: String = let result: String = db
db.query_row("SELECT x FROM test WHERE y = 'two'", &[], |row| row.get(0)) .query_row("SELECT x FROM test WHERE y = 'two'", &[], |row| row.get(0))
.unwrap(); .unwrap();
assert_eq!(result, "one"); assert_eq!(result, "one");
} }
@ -719,7 +791,8 @@ mod test {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)") db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)")
.unwrap(); .unwrap();
let mut stmt = db.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)") let mut stmt = db
.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)")
.unwrap(); .unwrap();
assert_eq!(stmt.insert(&[&1i32]).unwrap(), 1); assert_eq!(stmt.insert(&[&1i32]).unwrap(), 1);
assert_eq!(stmt.insert(&[&2i32]).unwrap(), 2); assert_eq!(stmt.insert(&[&2i32]).unwrap(), 2);
@ -727,7 +800,8 @@ mod test {
Error::StatementChangedRows(0) => (), Error::StatementChangedRows(0) => (),
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
let mut multi = db.prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4") let mut multi = db
.prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4")
.unwrap(); .unwrap();
match multi.insert(&[]).unwrap_err() { match multi.insert(&[]).unwrap_err() {
Error::StatementChangedRows(2) => (), Error::StatementChangedRows(2) => (),
@ -739,22 +813,27 @@ mod test {
fn test_insert_different_tables() { fn test_insert_different_tables() {
// Test for https://github.com/jgallagher/rusqlite/issues/171 // Test for https://github.com/jgallagher/rusqlite/issues/171
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch(r" db.execute_batch(
r"
CREATE TABLE foo(x INTEGER); CREATE TABLE foo(x INTEGER);
CREATE TABLE bar(x INTEGER); CREATE TABLE bar(x INTEGER);
") ",
.unwrap(); ).unwrap();
assert_eq!(db.prepare("INSERT INTO foo VALUES (10)") assert_eq!(
db.prepare("INSERT INTO foo VALUES (10)")
.unwrap() .unwrap()
.insert(&[]) .insert(&[])
.unwrap(), .unwrap(),
1); 1
assert_eq!(db.prepare("INSERT INTO bar VALUES (10)") );
assert_eq!(
db.prepare("INSERT INTO bar VALUES (10)")
.unwrap() .unwrap()
.insert(&[]) .insert(&[])
.unwrap(), .unwrap(),
1); 1
);
} }
#[test] #[test]
@ -811,4 +890,13 @@ mod test {
let y: Result<i64> = stmt.query_row(&[], |r| r.get("y")); let y: Result<i64> = stmt.query_row(&[], |r| r.get("y"));
assert_eq!(3i64, y.unwrap()); assert_eq!(3i64, y.unwrap());
} }
#[test]
#[cfg(feature = "bundled")]
fn test_expanded_sql() {
let db = Connection::open_in_memory().unwrap();
let stmt = db.prepare("SELECT ?").unwrap();
stmt.bind_parameter(&1, 1).unwrap();
assert_eq!(Some("SELECT 1"), stmt.expanded_sql());
}
} }

View File

@ -1,14 +1,14 @@
//! Tracing and profiling functions. Error and warning log. //! Tracing and profiling functions. Error and warning log.
use std::os::raw::{c_char, c_int, c_void};
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::mem; use std::mem;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::ptr;
use std::time::Duration; use std::time::Duration;
use super::ffi; use super::ffi;
use {Result, Connection};
use error::error_from_sqlite_code; use error::error_from_sqlite_code;
use {Connection, Result};
/// 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:
@ -33,9 +33,11 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
let rc = match callback { let rc = match callback {
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, ffi::sqlite3_config(
ffi::SQLITE_CONFIG_LOG,
log_callback as extern "C" fn(_, _, _), log_callback as extern "C" fn(_, _, _),
p_arg) p_arg,
)
} }
None => { None => {
let nullptr: *mut c_void = ptr::null_mut(); let nullptr: *mut c_void = ptr::null_mut();
@ -90,16 +92,20 @@ impl Connection {
/// 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)>) {
unsafe extern "C" fn profile_callback(p_arg: *mut c_void, unsafe extern "C" fn profile_callback(
p_arg: *mut c_void,
z_sql: *const c_char, z_sql: *const c_char,
nanoseconds: u64) { nanoseconds: u64,
) {
let profile_fn: fn(&str, Duration) = mem::transmute(p_arg); let profile_fn: fn(&str, Duration) = mem::transmute(p_arg);
let c_slice = CStr::from_ptr(z_sql).to_bytes(); let c_slice = CStr::from_ptr(z_sql).to_bytes();
let s = String::from_utf8_lossy(c_slice); let s = String::from_utf8_lossy(c_slice);
const NANOS_PER_SEC: u64 = 1_000_000_000; const NANOS_PER_SEC: u64 = 1_000_000_000;
let duration = Duration::new(nanoseconds / NANOS_PER_SEC, let duration = Duration::new(
(nanoseconds % NANOS_PER_SEC) as u32); nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32,
);
profile_fn(&s, duration); profile_fn(&s, duration);
} }

View File

@ -1,5 +1,5 @@
use std::ops::Deref; use std::ops::Deref;
use {Result, Connection}; use {Connection, Result};
/// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is deprecated. /// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is deprecated.
#[deprecated(since = "0.6.0", note = "Use TransactionBehavior instead")] #[deprecated(since = "0.6.0", note = "Use TransactionBehavior instead")]
@ -61,7 +61,6 @@ pub type SqliteTransaction<'conn> = Transaction<'conn>;
pub struct Transaction<'conn> { pub struct Transaction<'conn> {
conn: &'conn Connection, conn: &'conn Connection,
drop_behavior: DropBehavior, drop_behavior: DropBehavior,
committed: bool,
} }
/// Represents a savepoint on a database connection. /// Represents a savepoint on a database connection.
@ -106,13 +105,9 @@ impl<'conn> Transaction<'conn> {
TransactionBehavior::Immediate => "BEGIN IMMEDIATE", TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE", TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
}; };
conn.execute_batch(query) conn.execute_batch(query).map(move |_| Transaction {
.map(move |_| {
Transaction {
conn, conn,
drop_behavior: DropBehavior::Rollback, drop_behavior: DropBehavior::Rollback,
committed: false,
}
}) })
} }
@ -167,8 +162,8 @@ impl<'conn> Transaction<'conn> {
} }
fn commit_(&mut self) -> Result<()> { fn commit_(&mut self) -> Result<()> {
self.committed = true; self.conn.execute_batch("COMMIT")?;
self.conn.execute_batch("COMMIT") Ok(())
} }
/// A convenience method which consumes and rolls back a transaction. /// A convenience method which consumes and rolls back a transaction.
@ -177,8 +172,8 @@ impl<'conn> Transaction<'conn> {
} }
fn rollback_(&mut self) -> Result<()> { fn rollback_(&mut self) -> Result<()> {
self.committed = true; self.conn.execute_batch("ROLLBACK")?;
self.conn.execute_batch("ROLLBACK") Ok(())
} }
/// Consumes the transaction, committing or rolling back according to the current setting /// Consumes the transaction, committing or rolling back according to the current setting
@ -191,11 +186,11 @@ impl<'conn> Transaction<'conn> {
} }
fn finish_(&mut self) -> Result<()> { fn finish_(&mut self) -> Result<()> {
if self.committed { if self.conn.is_autocommit() {
return Ok(()); return Ok(());
} }
match self.drop_behavior() { match self.drop_behavior() {
DropBehavior::Commit => self.commit_(), DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()),
DropBehavior::Rollback => self.rollback_(), DropBehavior::Rollback => self.rollback_(),
DropBehavior::Ignore => Ok(()), DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Transaction dropped unexpectedly."), DropBehavior::Panic => panic!("Transaction dropped unexpectedly."),
@ -219,20 +214,19 @@ impl<'conn> Drop for Transaction<'conn> {
} }
impl<'conn> Savepoint<'conn> { impl<'conn> Savepoint<'conn> {
fn with_depth_and_name<T: Into<String>>(conn: &Connection, fn with_depth_and_name<T: Into<String>>(
conn: &Connection,
depth: u32, depth: u32,
name: T) name: T,
-> Result<Savepoint> { ) -> Result<Savepoint> {
let name = name.into(); let name = name.into();
conn.execute_batch(&format!("SAVEPOINT {}", name)) conn.execute_batch(&format!("SAVEPOINT {}", name))
.map(|_| { .map(|_| Savepoint {
Savepoint {
conn, conn,
name, name,
depth, depth,
drop_behavior: DropBehavior::Rollback, drop_behavior: DropBehavior::Rollback,
committed: false, committed: false,
}
}) })
} }
@ -277,9 +271,9 @@ impl<'conn> Savepoint<'conn> {
} }
fn commit_(&mut self) -> Result<()> { fn commit_(&mut self) -> Result<()> {
self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
self.committed = true; self.committed = true;
self.conn Ok(())
.execute_batch(&format!("RELEASE {}", self.name))
} }
/// A convenience method which rolls back a savepoint. /// A convenience method which rolls back a savepoint.
@ -307,7 +301,7 @@ impl<'conn> Savepoint<'conn> {
return Ok(()); return Ok(());
} }
match self.drop_behavior() { match self.drop_behavior() {
DropBehavior::Commit => self.commit_(), DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()),
DropBehavior::Rollback => self.rollback(), DropBehavior::Rollback => self.rollback(),
DropBehavior::Ignore => Ok(()), DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."), DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
@ -366,9 +360,10 @@ impl Connection {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
pub fn transaction_with_behavior(&mut self, pub fn transaction_with_behavior(
behavior: TransactionBehavior) &mut self,
-> Result<Transaction> { behavior: TransactionBehavior,
) -> Result<Transaction> {
Transaction::new(self, behavior) Transaction::new(self, behavior)
} }
@ -414,8 +409,8 @@ impl Connection {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection;
use super::DropBehavior; use super::DropBehavior;
use Connection;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -438,9 +433,11 @@ mod test {
} }
{ {
let tx = db.transaction().unwrap(); let tx = db.transaction().unwrap();
assert_eq!(2i32, assert_eq!(
2i32,
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0)) tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()); .unwrap()
);
} }
} }
@ -465,9 +462,11 @@ mod test {
} }
{ {
let tx = db.transaction().unwrap(); let tx = db.transaction().unwrap();
assert_eq!(6i32, assert_eq!(
6i32,
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0)) tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()); .unwrap()
);
} }
} }
@ -561,7 +560,8 @@ mod test {
} }
fn assert_current_sum(x: i32, conn: &Connection) { fn assert_current_sum(x: i32, conn: &Connection) {
let i = conn.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0)) let i = conn
.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(x, i); assert_eq!(x, i);
} }

View File

@ -3,10 +3,10 @@ extern crate chrono;
use std::borrow::Cow; use std::borrow::Cow;
use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, Utc, Local}; use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use Result;
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD" /// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
impl ToSql for NaiveDate { impl ToSql for NaiveDate {
@ -39,9 +39,7 @@ impl ToSql for NaiveTime {
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone. /// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
impl FromSql for NaiveTime { impl FromSql for NaiveTime {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value value.as_str().and_then(|s| {
.as_str()
.and_then(|s| {
let fmt = match s.len() { let fmt = match s.len() {
5 => "%H:%M", 5 => "%H:%M",
8 => "%H:%M:%S", 8 => "%H:%M:%S",
@ -67,9 +65,7 @@ impl ToSql for NaiveDateTime {
/// without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported) /// without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported)
impl FromSql for NaiveDateTime { impl FromSql for NaiveDateTime {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value value.as_str().and_then(|s| {
.as_str()
.and_then(|s| {
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' { let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%Y-%m-%dT%H:%M:%S%.f" "%Y-%m-%dT%H:%M:%S%.f"
} else { } else {
@ -130,9 +126,10 @@ impl FromSql for DateTime<Local> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::chrono::{
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
};
use Connection; use Connection;
use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
Duration};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -148,10 +145,12 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&date]) db.execute("INSERT INTO foo (t) VALUES (?)", &[&date])
.unwrap(); .unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let s: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!("2016-02-23", s); assert_eq!("2016-02-23", s);
let t: NaiveDate = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let t: NaiveDate = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(date, t); assert_eq!(date, t);
} }
@ -163,10 +162,12 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&time]) db.execute("INSERT INTO foo (t) VALUES (?)", &[&time])
.unwrap(); .unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let s: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!("23:56:04", s); assert_eq!("23:56:04", s);
let v: NaiveTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let v: NaiveTime = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(time, v); assert_eq!(time, v);
} }
@ -181,16 +182,18 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt]) db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt])
.unwrap(); .unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let s: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!("2016-02-23T23:56:04", s); assert_eq!("2016-02-23T23:56:04", s);
let v: NaiveDateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let v: NaiveDateTime = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(dt, v); assert_eq!(dt, v);
db.execute("UPDATE foo set b = datetime(t)", &[]) db.execute("UPDATE foo set b = datetime(t)", &[]).unwrap(); // "YYYY-MM-DD HH:MM:SS"
.unwrap(); // "YYYY-MM-DD HH:MM:SS" let hms: NaiveDateTime = db
let hms: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)) .query_row("SELECT b FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(dt, hms); assert_eq!(dt, hms);
} }
@ -206,24 +209,28 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc]) db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc])
.unwrap(); .unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let s: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!("2016-02-23T23:56:04.789+00:00", s); assert_eq!("2016-02-23T23:56:04.789+00:00", s);
let v1: DateTime<Utc> = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let v1: DateTime<Utc> = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(utc, v1); assert_eq!(utc, v1);
let v2: DateTime<Utc> = db.query_row("SELECT '2016-02-23 23:56:04.789'", &[], |r| r.get(0)) let v2: DateTime<Utc> = db
.query_row("SELECT '2016-02-23 23:56:04.789'", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(utc, v2); assert_eq!(utc, v2);
let v3: DateTime<Utc> = db.query_row("SELECT '2016-02-23 23:56:04'", &[], |r| r.get(0)) let v3: DateTime<Utc> = db
.query_row("SELECT '2016-02-23 23:56:04'", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(utc - Duration::milliseconds(789), v3); assert_eq!(utc - Duration::milliseconds(789), v3);
let v4: DateTime<Utc> = let v4: DateTime<Utc> = db
db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0)) .query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(utc, v4); assert_eq!(utc, v4);
} }
@ -240,11 +247,13 @@ mod test {
.unwrap(); .unwrap();
// Stored string should be in UTC // Stored string should be in UTC
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let s: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert!(s.ends_with("+00:00")); assert!(s.ends_with("+00:00"));
let v: DateTime<Local> = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let v: DateTime<Local> = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(local, v); assert_eq!(local, v);
} }

View File

@ -1,4 +1,4 @@
use super::{ValueRef, Value}; use super::{Value, ValueRef};
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
@ -39,8 +39,7 @@ impl Error for FromSqlError {
fn cause(&self) -> Option<&Error> { fn cause(&self) -> Option<&Error> {
match *self { match *self {
FromSqlError::Other(ref err) => err.cause(), FromSqlError::Other(ref err) => err.cause(),
FromSqlError::InvalidType | FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None,
FromSqlError::OutOfRange(_) => None,
} }
} }
} }
@ -150,8 +149,8 @@ impl FromSql for Value {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use {Connection, Error};
use super::FromSql; use super::FromSql;
use {Connection, Error};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
Connection::open_in_memory().unwrap() Connection::open_in_memory().unwrap()
@ -162,10 +161,12 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64]) fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64])
where T: Into<i64> + FromSql + ::std::fmt::Debug where
T: Into<i64> + FromSql + ::std::fmt::Debug,
{ {
for n in out_of_range { for n in out_of_range {
let err = db.query_row("SELECT ?", &[n], |r| r.get_checked::<_, T>(0)) let err = db
.query_row("SELECT ?", &[n], |r| r.get_checked::<_, T>(0))
.unwrap() .unwrap()
.unwrap_err(); .unwrap_err();
match err { match err {
@ -174,18 +175,22 @@ mod test {
} }
} }
for n in in_range { for n in in_range {
assert_eq!(*n, assert_eq!(
*n,
db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0)) db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
.unwrap() .unwrap()
.into()); .into()
);
} }
} }
check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]); check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]); check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
check_ranges::<i32>(&db, check_ranges::<i32>(
&db,
&[-2147483649, 2147483648], &[-2147483649, 2147483648],
&[-2147483648, -1, 0, 1, 2147483647]); &[-2147483648, -1, 0, 1, 2147483647],
);
check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]); check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]); check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
check_ranges::<u32>(&db, &[-2, -1, 4294967296], &[0, 1, 4294967295]); check_ranges::<u32>(&db, &[-2, -1, 4294967296], &[0, 1, 4294967295]);

View File

@ -59,15 +59,15 @@ pub use self::value_ref::ValueRef;
use std::fmt; use std::fmt;
mod value;
mod value_ref;
mod from_sql;
mod to_sql;
mod time;
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
mod chrono; mod chrono;
mod from_sql;
#[cfg(feature = "serde_json")] #[cfg(feature = "serde_json")]
mod serde_json; mod serde_json;
mod time;
mod to_sql;
mod value;
mod value_ref;
/// Empty struct that can be used to fill in a query parameter as `NULL`. /// Empty struct that can be used to fill in a query parameter as `NULL`.
/// ///
@ -77,10 +77,9 @@ mod serde_json;
/// # extern crate rusqlite; /// # extern crate rusqlite;
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// # use rusqlite::types::{Null}; /// # use rusqlite::types::{Null};
/// # use std::os::raw::{c_int};
/// fn main() { /// fn main() {
/// } /// }
/// fn insert_null(conn: &Connection) -> Result<c_int> { /// fn insert_null(conn: &Connection) -> Result<usize> {
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[&Null]) /// conn.execute("INSERT INTO people (name) VALUES (?)", &[&Null])
/// } /// }
/// ``` /// ```
@ -112,11 +111,11 @@ impl fmt::Display for Type {
mod test { mod test {
extern crate time; extern crate time;
use super::Value;
use std::f64::EPSILON;
use std::os::raw::{c_double, c_int};
use Connection; use Connection;
use Error; use Error;
use std::os::raw::{c_int, c_double};
use std::f64::EPSILON;
use super::Value;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -133,7 +132,8 @@ mod test {
db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234]) db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])
.unwrap(); .unwrap();
let v: Vec<u8> = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)) let v: Vec<u8> = db
.query_row("SELECT b FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(v, v1234); assert_eq!(v, v1234);
} }
@ -146,7 +146,8 @@ mod test {
db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty]) db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])
.unwrap(); .unwrap();
let v: Vec<u8> = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)) let v: Vec<u8> = db
.query_row("SELECT b FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(v, empty); assert_eq!(v, empty);
} }
@ -156,10 +157,10 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
let s = "hello, world!"; let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]) db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
.unwrap();
let from: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let from: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(from, s); assert_eq!(from, s);
} }
@ -172,7 +173,8 @@ mod test {
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_owned()]) db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_owned()])
.unwrap(); .unwrap();
let from: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let from: String = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(from, s); assert_eq!(from, s);
} }
@ -184,9 +186,11 @@ mod test {
db.execute("INSERT INTO foo(i) VALUES (?)", &[&Value::Integer(10)]) db.execute("INSERT INTO foo(i) VALUES (?)", &[&Value::Integer(10)])
.unwrap(); .unwrap();
assert_eq!(10i64, assert_eq!(
10i64,
db.query_row::<i64, _>("SELECT i FROM foo", &[], |r| r.get(0)) db.query_row::<i64, _>("SELECT i FROM foo", &[], |r| r.get(0))
.unwrap()); .unwrap()
);
} }
#[test] #[test]
@ -196,12 +200,11 @@ mod test {
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]) db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
.unwrap(); db.execute("INSERT INTO foo(b) VALUES (?)", &[&b]).unwrap();
db.execute("INSERT INTO foo(b) VALUES (?)", &[&b])
.unwrap();
let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC") let mut stmt = db
.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")
.unwrap(); .unwrap();
let mut rows = stmt.query(&[]).unwrap(); let mut rows = stmt.query(&[]).unwrap();
@ -233,9 +236,10 @@ mod test {
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)", db.execute(
&[]) "INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
.unwrap(); &[],
).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();
@ -243,57 +247,103 @@ 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::<i32, Vec<u8>>(0).unwrap()); assert_eq!(vec![1, 2], row.get_checked::<_, Vec<u8>>(0).unwrap());
assert_eq!("text", row.get_checked::<i32, String>(1).unwrap()); assert_eq!("text", row.get_checked::<_, String>(1).unwrap());
assert_eq!(1, row.get_checked::<i32, c_int>(2).unwrap()); assert_eq!(1, row.get_checked::<_, c_int>(2).unwrap());
assert!((1.5 - row.get_checked::<i32, c_double>(3).unwrap()).abs() < EPSILON); assert!((1.5 - row.get_checked::<_, c_double>(3).unwrap()).abs() < EPSILON);
assert!(row.get_checked::<i32, Option<c_int>>(4) assert!(row.get_checked::<_, Option<c_int>>(4).unwrap().is_none());
.unwrap() assert!(row.get_checked::<_, Option<c_double>>(4).unwrap().is_none());
.is_none()); assert!(row.get_checked::<_, Option<String>>(4).unwrap().is_none());
assert!(row.get_checked::<i32, Option<c_double>>(4)
.unwrap()
.is_none());
assert!(row.get_checked::<i32, Option<String>>(4)
.unwrap()
.is_none());
// check some invalid types // check some invalid types
// 0 is actually a blob (Vec<u8>) // 0 is actually a blob (Vec<u8>)
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(0).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(0).err().unwrap())); row.get_checked::<_, c_int>(0).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(0).err().unwrap())); ));
assert!(is_invalid_column_type(row.get_checked::<i32, c_double>(0).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<i32, String>(0).err().unwrap())); row.get_checked::<_, c_int>(0).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<i32, time::Timespec>(0).err().unwrap())); ));
assert!(is_invalid_column_type(row.get_checked::<i32, Option<c_int>>(0).err().unwrap())); assert!(is_invalid_column_type(
row.get_checked::<_, i64>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, c_double>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, String>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, time::Timespec>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Option<c_int>>(0).err().unwrap()
));
// 1 is actually a text (String) // 1 is actually a text (String)
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(1).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(1).err().unwrap())); row.get_checked::<_, c_int>(1).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<i32, c_double>(1).err().unwrap())); ));
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(1).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<i32, Option<c_int>>(1).err().unwrap())); row.get_checked::<_, i64>(1).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, c_double>(1).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Vec<u8>>(1).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Option<c_int>>(1).err().unwrap()
));
// 2 is actually an integer // 2 is actually an integer
assert!(is_invalid_column_type(row.get_checked::<i32, String>(2).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(2).err().unwrap())); row.get_checked::<_, String>(2).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<i32, Option<String>>(2).err().unwrap())); ));
assert!(is_invalid_column_type(
row.get_checked::<_, Vec<u8>>(2).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Option<String>>(2).err().unwrap()
));
// 3 is actually a float (c_double) // 3 is actually a float (c_double)
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(3).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(3).err().unwrap())); row.get_checked::<_, c_int>(3).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<i32, String>(3).err().unwrap())); ));
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(3).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<i32, Option<c_int>>(3).err().unwrap())); row.get_checked::<_, i64>(3).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, String>(3).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Vec<u8>>(3).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Option<c_int>>(3).err().unwrap()
));
// 4 is actually NULL // 4 is actually NULL
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(4).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(4).err().unwrap())); row.get_checked::<_, c_int>(4).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<i32, c_double>(4).err().unwrap())); ));
assert!(is_invalid_column_type(row.get_checked::<i32, String>(4).err().unwrap())); assert!(is_invalid_column_type(
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(4).err().unwrap())); row.get_checked::<_, i64>(4).err().unwrap()
assert!(is_invalid_column_type(row.get_checked::<i32, time::Timespec>(4).err().unwrap())); ));
assert!(is_invalid_column_type(
row.get_checked::<_, c_double>(4).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, String>(4).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, Vec<u8>>(4).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, time::Timespec>(4).err().unwrap()
));
} }
#[test] #[test]
@ -301,23 +351,28 @@ mod test {
use super::Value; use super::Value;
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)", db.execute(
&[]) "INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
.unwrap(); &[],
).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();
let row = rows.next().unwrap().unwrap(); let row = rows.next().unwrap().unwrap();
assert_eq!(Value::Blob(vec![1, 2]), assert_eq!(
row.get_checked::<i32, Value>(0).unwrap()); Value::Blob(vec![1, 2]),
assert_eq!(Value::Text(String::from("text")), row.get_checked::<_, Value>(0).unwrap()
row.get_checked::<i32, Value>(1).unwrap()); );
assert_eq!(Value::Integer(1), row.get_checked::<i32, Value>(2).unwrap()); assert_eq!(
match row.get_checked::<i32, Value>(3).unwrap() { Value::Text(String::from("text")),
row.get_checked::<_, Value>(1).unwrap()
);
assert_eq!(Value::Integer(1), row.get_checked::<_, Value>(2).unwrap());
match row.get_checked::<_, Value>(3).unwrap() {
Value::Real(val) => assert!((1.5 - val).abs() < EPSILON), Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
x => panic!("Invalid Value {:?}", x), x => panic!("Invalid Value {:?}", x),
} }
assert_eq!(Value::Null, row.get_checked::<i32, Value>(4).unwrap()); assert_eq!(Value::Null, row.get_checked::<_, Value>(4).unwrap());
} }
} }

View File

@ -3,8 +3,8 @@ extern crate serde_json;
use self::serde_json::Value; use self::serde_json::Value;
use Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use Result;
/// Serialize JSON `Value` to text. /// Serialize JSON `Value` to text.
impl ToSql for Value { impl ToSql for Value {
@ -20,15 +20,14 @@ impl FromSql for Value {
ValueRef::Text(s) => serde_json::from_str(s), ValueRef::Text(s) => serde_json::from_str(s),
ValueRef::Blob(b) => serde_json::from_slice(b), ValueRef::Blob(b) => serde_json::from_slice(b),
_ => return Err(FromSqlError::InvalidType), _ => return Err(FromSqlError::InvalidType),
} }.map_err(|err| FromSqlError::Other(Box::new(err)))
.map_err(|err| FromSqlError::Other(Box::new(err)))
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection;
use super::serde_json; use super::serde_json;
use Connection;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -43,14 +42,17 @@ mod test {
let json = r#"{"foo": 13, "bar": "baz"}"#; let json = r#"{"foo": 13, "bar": "baz"}"#;
let data: serde_json::Value = serde_json::from_str(json).unwrap(); let data: serde_json::Value = serde_json::from_str(json).unwrap();
db.execute("INSERT INTO foo (t, b) VALUES (?, ?)", db.execute(
&[&data, &json.as_bytes()]) "INSERT INTO foo (t, b) VALUES (?, ?)",
.unwrap(); &[&data, &json.as_bytes()],
).unwrap();
let t: serde_json::Value = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)) let t: serde_json::Value = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(data, t); assert_eq!(data, t);
let b: serde_json::Value = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)) let b: serde_json::Value = db
.query_row("SELECT b FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
assert_eq!(data, b); assert_eq!(data, b);
} }

View File

@ -1,7 +1,7 @@
extern crate time; extern crate time;
use Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use Result;
const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ"; const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ";
const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z"; const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
@ -21,18 +21,18 @@ impl FromSql for time::Timespec {
value value
.as_str() .as_str()
.and_then(|s| { .and_then(|s| {
time::strptime(s, SQLITE_DATETIME_FMT) time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| {
.or_else(|err| {
time::strptime(s, SQLITE_DATETIME_FMT_LEGACY) time::strptime(s, SQLITE_DATETIME_FMT_LEGACY)
.or_else(|_| Err(FromSqlError::Other(Box::new(err))))})}) .or_else(|_| Err(FromSqlError::Other(Box::new(err))))
.map(|tm| tm.to_timespec()) })
}).map(|tm| tm.to_timespec())
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection;
use super::time; use super::time;
use Connection;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -55,17 +55,15 @@ mod test {
ts_vec.push(time::Timespec::new(10000000000, 0)); //November 20, 2286 ts_vec.push(time::Timespec::new(10000000000, 0)); //November 20, 2286
for ts in ts_vec { for ts in ts_vec {
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]) let from: time::Timespec = db
.unwrap(); .query_row("SELECT t FROM foo", &[], |r| r.get(0))
let from: time::Timespec = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap(); .unwrap();
db.execute("DELETE FROM foo", &[]).unwrap(); db.execute("DELETE FROM foo", &[]).unwrap();
assert_eq!(from, ts); assert_eq!(from, ts);
} }
} }
} }

View File

@ -1,4 +1,7 @@
use super::{Null, Value, ValueRef}; use super::{Null, Value, ValueRef};
use std::borrow::Cow;
#[cfg(feature = "array")]
use vtab::array::Array;
use Result; use Result;
/// `ToSqlOutput` represents the possible output types for implementors of the `ToSql` trait. /// `ToSqlOutput` represents the possible output types for implementors of the `ToSql` trait.
@ -13,12 +16,16 @@ pub enum ToSqlOutput<'a> {
/// A BLOB of the given length that is filled with zeroes. /// A BLOB of the given length that is filled with zeroes.
#[cfg(feature = "blob")] #[cfg(feature = "blob")]
ZeroBlob(i32), ZeroBlob(i32),
#[cfg(feature = "array")]
Array(Array),
} }
// Generically allow any type that can be converted into a ValueRef // Generically allow any type that can be converted into a ValueRef
// to be converted into a ToSqlOutput as well. // to be converted into a ToSqlOutput as well.
impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a> impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a>
where &'a T: Into<ValueRef<'a>> where
&'a T: Into<ValueRef<'a>>,
{ {
fn from(t: &'a T) -> Self { fn from(t: &'a T) -> Self {
ToSqlOutput::Borrowed(t.into()) ToSqlOutput::Borrowed(t.into())
@ -59,6 +66,8 @@ impl<'a> ToSql for ToSqlOutput<'a> {
#[cfg(feature = "blob")] #[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i), ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i),
#[cfg(feature = "array")]
ToSqlOutput::Array(ref a) => ToSqlOutput::Array(a.clone()),
}) })
} }
} }
@ -103,7 +112,8 @@ to_sql_self!(u32);
to_sql_self!(f64); to_sql_self!(f64);
impl<'a, T: ?Sized> ToSql for &'a T impl<'a, T: ?Sized> ToSql for &'a T
where &'a T: Into<ToSqlOutput<'a>> where
&'a T: Into<ToSqlOutput<'a>>,
{ {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput> {
Ok((*self).into()) Ok((*self).into())
@ -149,6 +159,12 @@ impl<T: ToSql> ToSql for Option<T> {
} }
} }
impl<'a> ToSql for Cow<'a, str> {
fn to_sql(&self) -> Result<ToSqlOutput> {
Ok(ToSqlOutput::from(self.as_ref()))
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::ToSql; use super::ToSql;
@ -165,4 +181,16 @@ mod test {
is_to_sql::<u16>(); is_to_sql::<u16>();
is_to_sql::<u32>(); is_to_sql::<u32>();
} }
#[test]
fn test_cow_str() {
use std::borrow::Cow;
let s = "str";
let cow = Cow::Borrowed(s);
let r = cow.to_sql();
assert!(r.is_ok());
let cow = Cow::Owned::<str>(String::from(s));
let r = cow.to_sql();
assert!(r.is_ok());
}
} }

View File

@ -31,7 +31,9 @@ impl From<bool> for Value {
} }
impl From<isize> for Value { impl From<isize> for Value {
fn from(i: isize) -> Value { Value::Integer(i as i64) } fn from(i: isize) -> Value {
Value::Integer(i as i64)
}
} }
macro_rules! from_i64( macro_rules! from_i64(

View File

@ -1,5 +1,5 @@
use super::{Type, Value};
use types::{FromSqlError, FromSqlResult}; use types::{FromSqlError, FromSqlResult};
use super::{Value, Type};
/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the /// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the
/// memory backing this value is owned by SQLite. /// memory backing this value is owned by SQLite.

View File

@ -1,10 +1,10 @@
//! [Unlock Notification](http://sqlite.org/unlock_notify.html) //! [Unlock Notification](http://sqlite.org/unlock_notify.html)
#[cfg(feature = "unlock_notify")]
use std::sync::{Condvar, Mutex};
use std::os::raw::c_int; use std::os::raw::c_int;
#[cfg(feature = "unlock_notify")] #[cfg(feature = "unlock_notify")]
use std::os::raw::c_void; use std::os::raw::c_void;
#[cfg(feature = "unlock_notify")]
use std::sync::{Condvar, Mutex};
use ffi; use ffi;
@ -49,10 +49,9 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) {
#[cfg(feature = "unlock_notify")] #[cfg(feature = "unlock_notify")]
pub fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool { pub fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool {
rc == ffi::SQLITE_LOCKED_SHAREDCACHE || (rc & 0xFF) == ffi::SQLITE_LOCKED && unsafe { rc == ffi::SQLITE_LOCKED_SHAREDCACHE
ffi::sqlite3_extended_errcode(db) || (rc & 0xFF) == ffi::SQLITE_LOCKED
} && unsafe { ffi::sqlite3_extended_errcode(db) } == ffi::SQLITE_LOCKED_SHAREDCACHE
== ffi::SQLITE_LOCKED_SHAREDCACHE
} }
/// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()` /// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()`

194
src/vtab/array.rs Normal file
View File

@ -0,0 +1,194 @@
//! Array Virtual Table.
//!
//! Port of [carray](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/carray.c) C extension.
use std::default::Default;
use std::os::raw::{c_char, c_int, c_void};
use std::rc::Rc;
use ffi;
use types::{ToSql, ToSqlOutput, Value};
use vtab::{
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
VTabCursor, Values,
};
use {Connection, Result};
// http://sqlite.org/bindptr.html
pub(crate) const ARRAY_TYPE: *const c_char = b"rarray\0" as *const u8 as *const c_char;
pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
let _: Array = Rc::from_raw(p as *const Vec<Value>);
}
pub type Array = Rc<Vec<Value>>;
impl ToSql for Array {
fn to_sql(&self) -> Result<ToSqlOutput> {
Ok(ToSqlOutput::Array(self.clone()))
}
}
/// Register the "rarray" module.
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
conn.create_module("rarray", &ARRAY_MODULE, aux)
}
lazy_static! {
static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1);
}
// Column numbers
// const CARRAY_COLUMN_VALUE : c_int = 0;
const CARRAY_COLUMN_POINTER: c_int = 1;
/// An instance of the Array virtual table
#[repr(C)]
struct ArrayTab {
/// Base class. Must be first
base: ffi::sqlite3_vtab,
}
impl VTab for ArrayTab {
type Aux = ();
type Cursor = ArrayTabCursor;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
_args: &[&[u8]],
) -> Result<(String, ArrayTab)> {
let vtab = ArrayTab {
base: ffi::sqlite3_vtab::default(),
};
Ok(("CREATE TABLE x(value,pointer hidden)".to_owned(), vtab))
}
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
// Index of the pointer= constraint
let mut ptr_idx = None;
for (i, constraint) in info.constraints().enumerate() {
if !constraint.is_usable() {
continue;
}
if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
continue;
}
if let CARRAY_COLUMN_POINTER = constraint.column() {
ptr_idx = Some(i);
}
}
if let Some(ptr_idx) = ptr_idx {
{
let mut constraint_usage = info.constraint_usage(ptr_idx);
constraint_usage.set_argv_index(1);
constraint_usage.set_omit(true);
}
info.set_estimated_cost(1f64);
info.set_estimated_rows(100);
info.set_idx_num(1);
} else {
info.set_estimated_cost(2_147_483_647f64);
info.set_estimated_rows(2_147_483_647);
info.set_idx_num(0);
}
Ok(())
}
fn open(&self) -> Result<ArrayTabCursor> {
Ok(ArrayTabCursor::new())
}
}
/// A cursor for the Array virtual table
#[repr(C)]
struct ArrayTabCursor {
/// Base class. Must be first
base: ffi::sqlite3_vtab_cursor,
/// The rowid
row_id: i64,
/// Pointer to the array of values ("pointer")
ptr: Option<Array>,
}
impl ArrayTabCursor {
fn new() -> ArrayTabCursor {
ArrayTabCursor {
base: ffi::sqlite3_vtab_cursor::default(),
row_id: 0,
ptr: None,
}
}
fn len(&self) -> i64 {
match self.ptr {
Some(ref a) => a.len() as i64,
_ => 0,
}
}
}
impl VTabCursor for ArrayTabCursor {
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values) -> Result<()> {
if idx_num > 0 {
self.ptr = try!(args.get_array(0));
} else {
self.ptr = None;
}
self.row_id = 1;
Ok(())
}
fn next(&mut self) -> Result<()> {
self.row_id += 1;
Ok(())
}
fn eof(&self) -> bool {
self.row_id > self.len()
}
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
match i {
CARRAY_COLUMN_POINTER => Ok(()),
_ => {
if let Some(ref array) = self.ptr {
let value = &array[(self.row_id - 1) as usize];
ctx.set_result(&value)
} else {
Ok(())
}
}
}
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_id)
}
}
#[cfg(test)]
mod test {
use std::rc::Rc;
use types::Value;
use vtab::array;
use Connection;
#[test]
fn test_array_module() {
let db = Connection::open_in_memory().unwrap();
array::load_module(&db).unwrap();
let v = vec![1i64, 2, 3, 4];
let values = v.into_iter().map(|i| Value::from(i)).collect();
let ptr = Rc::new(values);
{
let mut stmt = db.prepare("SELECT value from rarray(?);").unwrap();
let rows = stmt.query_map(&[&ptr], |row| row.get::<_, i64>(0)).unwrap();
assert_eq!(2, Rc::strong_count(&ptr));
let mut count = 0;
for (i, value) in rows.enumerate() {
assert_eq!(i as i64, value.unwrap() - 1);
count += 1;
}
assert_eq!(4, count);
}
assert_eq!(1, Rc::strong_count(&ptr));
}
}

386
src/vtab/csvtab.rs Normal file
View File

@ -0,0 +1,386 @@
//! CSV Virtual Table.
//!
//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C extension.
extern crate csv;
use std::fs::File;
use std::os::raw::c_int;
use std::path::Path;
use std::result;
use std::str;
use ffi;
use types::Null;
use vtab::{
dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
Module, VTab, VTabConnection, VTabCursor, Values,
};
use {Connection, Error, Result};
/// Register the "csv" module.
/// ```sql
/// CREATE VIRTUAL TABLE vtab USING csv(
/// filename=FILENAME -- Name of file containing CSV content
/// [, schema=SCHEMA] -- Alternative CSV schema. 'CREATE TABLE x(col1 TEXT NOT NULL, col2 INT, ...);'
/// [, header=YES|NO] -- First row of CSV defines the names of columns if "yes". Default "no".
/// [, columns=N] -- Assume the CSV file contains N columns.
/// [, delimiter=C] -- CSV delimiter. Default ','.
/// [, quote=C] -- CSV quote. Default '"'. 0 means no quote.
/// );
/// ```
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
conn.create_module("csv", &CSV_MODULE, aux)
}
lazy_static! {
static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1);
}
/// An instance of the CSV virtual table
#[repr(C)]
struct CSVTab {
/// Base class. Must be first
base: ffi::sqlite3_vtab,
/// Name of the CSV file
filename: String,
has_headers: bool,
delimiter: u8,
quote: u8,
/// Offset to start of data
offset_first_row: csv::Position,
}
impl CSVTab {
fn reader(&self) -> result::Result<csv::Reader<File>, csv::Error> {
csv::ReaderBuilder::new()
.has_headers(self.has_headers)
.delimiter(self.delimiter)
.quote(self.quote)
.from_path(&self.filename)
}
fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
let arg = try!(str::from_utf8(c_slice)).trim();
let mut split = arg.split('=');
if let Some(key) = split.next() {
if let Some(value) = split.next() {
let param = key.trim();
let value = dequote(value);
return Ok((param, value));
}
}
Err(Error::ModuleError(format!("illegal argument: '{}'", arg)))
}
fn parse_byte(arg: &str) -> Option<u8> {
if arg.len() == 1 {
arg.bytes().next()
} else {
None
}
}
}
impl VTab for CSVTab {
type Aux = ();
type Cursor = CSVTabCursor;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
args: &[&[u8]],
) -> Result<(String, CSVTab)> {
if args.len() < 4 {
return Err(Error::ModuleError("no CSV file specified".to_owned()));
}
let mut vtab = CSVTab {
base: ffi::sqlite3_vtab::default(),
filename: "".to_owned(),
has_headers: false,
delimiter: b',',
quote: b'"',
offset_first_row: csv::Position::new(),
};
let mut schema = None;
let mut n_col = None;
let args = &args[3..];
for c_slice in args {
let (param, value) = try!(CSVTab::parameter(c_slice));
match param {
"filename" => {
if !Path::new(value).exists() {
return Err(Error::ModuleError(format!(
"file '{}' does not exist",
value
)));
}
vtab.filename = value.to_owned();
}
"schema" => {
schema = Some(value.to_owned());
}
"columns" => {
if let Ok(n) = value.parse::<u16>() {
if n_col.is_some() {
return Err(Error::ModuleError(
"more than one 'columns' parameter".to_owned(),
));
} else if n == 0 {
return Err(Error::ModuleError(
"must have at least one column".to_owned(),
));
}
n_col = Some(n);
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'columns': {}",
value
)));
}
}
"header" => {
if let Some(b) = parse_boolean(value) {
vtab.has_headers = b;
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'header': {}",
value
)));
}
}
"delimiter" => {
if let Some(b) = CSVTab::parse_byte(value) {
vtab.delimiter = b;
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'delimiter': {}",
value
)));
}
}
"quote" => {
if let Some(b) = CSVTab::parse_byte(value) {
if b == b'0' {
vtab.quote = 0;
} else {
vtab.quote = b;
}
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'quote': {}",
value
)));
}
}
_ => {
return Err(Error::ModuleError(format!(
"unrecognized parameter '{}'",
param
)));
}
}
}
if vtab.filename.is_empty() {
return Err(Error::ModuleError("no CSV file specified".to_owned()));
}
let mut cols: Vec<String> = Vec::new();
if vtab.has_headers || (n_col.is_none() && schema.is_none()) {
let mut reader = try!(vtab.reader());
if vtab.has_headers {
{
let headers = try!(reader.headers());
// headers ignored if cols is not empty
if n_col.is_none() && schema.is_none() {
cols = headers
.into_iter()
.map(|header| escape_double_quote(&header).into_owned())
.collect();
}
}
vtab.offset_first_row = reader.position().clone();
} else {
let mut record = csv::ByteRecord::new();
if try!(reader.read_byte_record(&mut record)) {
for (i, _) in record.iter().enumerate() {
cols.push(format!("c{}", i));
}
}
}
} else if let Some(n_col) = n_col {
for i in 0..n_col {
cols.push(format!("c{}", i));
}
}
if cols.is_empty() && schema.is_none() {
return Err(Error::ModuleError("no column specified".to_owned()));
}
if schema.is_none() {
let mut sql = String::from("CREATE TABLE x(");
for (i, col) in cols.iter().enumerate() {
sql.push('"');
sql.push_str(col);
sql.push_str("\" TEXT");
if i == cols.len() - 1 {
sql.push_str(");");
} else {
sql.push_str(", ");
}
}
schema = Some(sql);
}
Ok((schema.unwrap().to_owned(), vtab))
}
// Only a forward full table scan is supported.
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
info.set_estimated_cost(1_000_000.);
Ok(())
}
fn open(&self) -> Result<CSVTabCursor> {
Ok(CSVTabCursor::new(try!(self.reader())))
}
}
impl CreateVTab for CSVTab {}
/// A cursor for the CSV virtual table
#[repr(C)]
struct CSVTabCursor {
/// Base class. Must be first
base: ffi::sqlite3_vtab_cursor,
/// The CSV reader object
reader: csv::Reader<File>,
/// Current cursor position used as rowid
row_number: usize,
/// Values of the current row
cols: csv::StringRecord,
eof: bool,
}
impl CSVTabCursor {
fn new(reader: csv::Reader<File>) -> CSVTabCursor {
CSVTabCursor {
base: ffi::sqlite3_vtab_cursor::default(),
reader,
row_number: 0,
cols: csv::StringRecord::new(),
eof: false,
}
}
/// Accessor to the associated virtual table.
fn vtab(&self) -> &CSVTab {
unsafe { &*(self.base.pVtab as *const CSVTab) }
}
}
impl VTabCursor for CSVTabCursor {
// Only a full table scan is supported. So `filter` simply rewinds to
// the beginning.
fn filter(&mut self, _idx_num: c_int, _idx_str: Option<&str>, _args: &Values) -> Result<()> {
{
let offset_first_row = self.vtab().offset_first_row.clone();
try!(self.reader.seek(offset_first_row));
}
self.row_number = 0;
self.next()
}
fn next(&mut self) -> Result<()> {
{
self.eof = self.reader.is_done();
if self.eof {
return Ok(());
}
self.eof = !try!(self.reader.read_record(&mut self.cols));
}
self.row_number += 1;
Ok(())
}
fn eof(&self) -> bool {
self.eof
}
fn column(&self, ctx: &mut Context, col: c_int) -> Result<()> {
if col < 0 || col as usize >= self.cols.len() {
return Err(Error::ModuleError(format!(
"column index out of bounds: {}",
col
)));
}
if self.cols.is_empty() {
return ctx.set_result(&Null);
}
// TODO Affinity
ctx.set_result(&self.cols[col as usize].to_owned())
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_number as i64)
}
}
impl From<csv::Error> for Error {
fn from(err: csv::Error) -> Error {
use std::error::Error as StdError;
Error::ModuleError(String::from(err.description()))
}
}
#[cfg(test)]
mod test {
use vtab::csvtab;
use {Connection, Result};
#[test]
fn test_csv_module() {
let db = Connection::open_in_memory().unwrap();
csvtab::load_module(&db).unwrap();
db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
.unwrap();
{
let mut s = db.prepare("SELECT rowid, * FROM vtab").unwrap();
{
let headers = s.column_names();
assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers);
}
let ids: Result<Vec<i32>> = s
.query_map(&[], |row| row.get::<_, i32>(0))
.unwrap()
.collect();
let sum = ids.unwrap().iter().fold(0, |acc, &id| acc + id);
assert_eq!(sum, 15);
}
db.execute_batch("DROP TABLE vtab").unwrap();
}
#[test]
fn test_csv_cursor() {
let db = Connection::open_in_memory().unwrap();
csvtab::load_module(&db).unwrap();
db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
.unwrap();
{
let mut s = db
.prepare(
"SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
v1.rowid < v2.rowid",
).unwrap();
let mut rows = s.query(&[]).unwrap();
let row = rows.next().unwrap().unwrap();
assert_eq!(row.get::<_, i32>(0), 2);
}
db.execute_batch("DROP TABLE vtab").unwrap();
}
}

991
src/vtab/mod.rs Normal file
View File

@ -0,0 +1,991 @@
//! Create virtual tables.
//!
//! Follow these steps to create your own virtual table:
//! 1. Write implemenation of `VTab` and `VTabCursor` traits.
//! 2. Create an instance of the `Module` structure specialized for `VTab` impl. from step 1.
//! 3. Register your `Module` structure using `Connection.create_module`.
//! 4. Run a `CREATE VIRTUAL TABLE` command that specifies the new module in the `USING` clause.
//!
//! (See [SQLite doc](http://sqlite.org/vtab.html))
use std::borrow::Cow::{self, Borrowed, Owned};
use std::ffi::CString;
use std::marker::PhantomData;
use std::marker::Sync;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::slice;
use context::set_result;
use error::error_from_sqlite_code;
use ffi;
pub use ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
use types::{FromSql, FromSqlError, ToSql, ValueRef};
use {str_to_cstring, Connection, Error, InnerConnection, Result};
// let conn: Connection = ...;
// let mod: Module = ...; // VTab builder
// conn.create_module("module", mod);
//
// conn.execute("CREATE VIRTUAL TABLE foo USING module(...)");
// \-> Module::xcreate
// |-> let vtab: VTab = ...; // on the heap
// \-> conn.declare_vtab("CREATE TABLE foo (...)");
// conn = Connection::open(...);
// \-> Module::xconnect
// |-> let vtab: VTab = ...; // on the heap
// \-> conn.declare_vtab("CREATE TABLE foo (...)");
//
// conn.close();
// \-> vtab.xdisconnect
// conn.execute("DROP TABLE foo");
// \-> vtab.xDestroy
//
// let stmt = conn.prepare("SELECT ... FROM foo WHERE ...");
// \-> vtab.xbestindex
// stmt.query().next();
// \-> vtab.xopen
// |-> let cursor: VTabCursor = ...; // on the heap
// |-> cursor.xfilter or xnext
// |-> cursor.xeof
// \-> if not eof { cursor.column or xrowid } else { cursor.xclose }
//
// db: *mut ffi::sqlite3 => VTabConnection
// module: *const ffi::sqlite3_module => Module
// aux: *mut c_void => Module::Aux
// ffi::sqlite3_vtab => VTab
// ffi::sqlite3_vtab_cursor => VTabCursor
/// Virtual table module
///
/// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
#[repr(C)]
pub struct Module<T: VTab> {
base: ffi::sqlite3_module,
phantom: PhantomData<T>,
}
unsafe impl<T: VTab> Sync for Module<T> {}
/// Create a read-only virtual table implementation.
///
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
pub fn read_only_module<T: CreateVTab>(version: c_int) -> Module<T> {
// The xConnect and xCreate methods do the same thing, but they must be
// different so that the virtual table is not an eponymous virtual table.
let ffi_module = ffi::sqlite3_module {
iVersion: version,
xCreate: Some(rust_create::<T>),
xConnect: Some(rust_connect::<T>),
xBestIndex: Some(rust_best_index::<T>),
xDisconnect: Some(rust_disconnect::<T>),
xDestroy: Some(rust_destroy::<T>),
xOpen: Some(rust_open::<T>),
xClose: Some(rust_close::<T::Cursor>),
xFilter: Some(rust_filter::<T::Cursor>),
xNext: Some(rust_next::<T::Cursor>),
xEof: Some(rust_eof::<T::Cursor>),
xColumn: Some(rust_column::<T::Cursor>),
xRowid: Some(rust_rowid::<T::Cursor>),
xUpdate: None,
xBegin: None,
xSync: None,
xCommit: None,
xRollback: None,
xFindFunction: None,
xRename: None,
xSavepoint: None,
xRelease: None,
xRollbackTo: None,
};
Module {
base: ffi_module,
phantom: PhantomData::<T>,
}
}
/// Create an eponymous only virtual table implementation.
///
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
pub fn eponymous_only_module<T: VTab>(version: c_int) -> Module<T> {
// A virtual table is eponymous if its xCreate method is the exact same function as the xConnect method
// For eponymous-only virtual tables, the xCreate method is NULL
let ffi_module = ffi::sqlite3_module {
iVersion: version,
xCreate: None,
xConnect: Some(rust_connect::<T>),
xBestIndex: Some(rust_best_index::<T>),
xDisconnect: Some(rust_disconnect::<T>),
xDestroy: None,
xOpen: Some(rust_open::<T>),
xClose: Some(rust_close::<T::Cursor>),
xFilter: Some(rust_filter::<T::Cursor>),
xNext: Some(rust_next::<T::Cursor>),
xEof: Some(rust_eof::<T::Cursor>),
xColumn: Some(rust_column::<T::Cursor>),
xRowid: Some(rust_rowid::<T::Cursor>),
xUpdate: None,
xBegin: None,
xSync: None,
xCommit: None,
xRollback: None,
xFindFunction: None,
xRename: None,
xSavepoint: None,
xRelease: None,
xRollbackTo: None,
};
Module {
base: ffi_module,
phantom: PhantomData::<T>,
}
}
pub struct VTabConnection(*mut ffi::sqlite3);
impl VTabConnection {
// TODO sqlite3_vtab_config (http://sqlite.org/c3ref/vtab_config.html)
// TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html)
/// Get access to the underlying SQLite database connection handle.
///
/// # Warning
///
/// You should not need to use this function. If you do need to, please [open an issue
/// on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe
/// your use case. This function is unsafe because it gives you raw access to the SQLite
/// connection, and what you do with it could impact the safety of this `Connection`.
pub unsafe fn handle(&mut self) -> *mut ffi::sqlite3 {
self.0
}
}
/// Virtual table instance trait.
///
/// Implementations must be like:
/// ```rust,ignore
/// #[repr(C)]
/// struct MyTab {
/// /// Base class. Must be first
/// base: ffi::sqlite3_vtab,
/// /* Virtual table implementations will typically add additional fields */
/// }
/// ```
///
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
pub trait VTab: Sized {
type Aux;
type Cursor: VTabCursor;
/// Establish a new connection to an existing virtual table.
///
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xconnect_method))
fn connect(
db: &mut VTabConnection,
aux: Option<&Self::Aux>,
args: &[&[u8]],
) -> Result<(String, Self)>;
/// Determine the best way to access the virtual table.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xbestindex_method))
fn best_index(&self, info: &mut IndexInfo) -> Result<()>;
/// Create a new cursor used for accessing a virtual table.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xopen_method))
fn open(&self) -> Result<Self::Cursor>;
}
/// Non-eponymous virtual table instance trait.
///
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
pub trait CreateVTab: VTab {
/// Create a new instance of a virtual table in response to a CREATE VIRTUAL TABLE statement.
/// The `db` parameter is a pointer to the SQLite database connection that is executing
/// the CREATE VIRTUAL TABLE statement.
///
/// Call `connect` by default.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xcreate_method))
fn create(
db: &mut VTabConnection,
aux: Option<&Self::Aux>,
args: &[&[u8]],
) -> Result<(String, Self)> {
Self::connect(db, aux, args)
}
/// Destroy the underlying table implementation. This method undoes the work of `create`.
///
/// Do nothing by default.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xdestroy_method))
fn destroy(&self) -> Result<()> {
Ok(())
}
}
bitflags! {
#[doc = "Index constraint operator."]
#[repr(C)]
pub struct IndexConstraintOp: ::std::os::raw::c_uchar {
const SQLITE_INDEX_CONSTRAINT_EQ = 2;
const SQLITE_INDEX_CONSTRAINT_GT = 4;
const SQLITE_INDEX_CONSTRAINT_LE = 8;
const SQLITE_INDEX_CONSTRAINT_LT = 16;
const SQLITE_INDEX_CONSTRAINT_GE = 32;
const SQLITE_INDEX_CONSTRAINT_MATCH = 64;
}
}
/// Pass information into and receive the reply from the `VTab.best_index` method.
///
/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
pub struct IndexInfo(*mut ffi::sqlite3_index_info);
impl IndexInfo {
/// Record WHERE clause constraints.
pub fn constraints(&self) -> IndexConstraintIter {
let constraints =
unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
IndexConstraintIter {
iter: constraints.iter(),
}
}
/// Information about the ORDER BY clause.
pub fn order_bys(&self) -> OrderByIter {
let order_bys =
unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) };
OrderByIter {
iter: order_bys.iter(),
}
}
/// Number of terms in the ORDER BY clause
pub fn num_of_order_by(&self) -> usize {
unsafe { (*self.0).nOrderBy as usize }
}
pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage {
let constraint_usages = unsafe {
slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
};
IndexConstraintUsage(&mut constraint_usages[constraint_idx])
}
/// Number used to identify the index
pub fn set_idx_num(&mut self, idx_num: c_int) {
unsafe {
(*self.0).idxNum = idx_num;
}
}
/// True if output is already ordered
pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
unsafe {
(*self.0).orderByConsumed = if order_by_consumed { 1 } else { 0 };
}
}
/// Estimated cost of using this index
pub fn set_estimated_cost(&mut self, estimated_ost: f64) {
unsafe {
(*self.0).estimatedCost = estimated_ost;
}
}
/// Estimated number of rows returned
#[cfg(feature = "bundled")] // SQLite >= 3.8.2
pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
unsafe {
(*self.0).estimatedRows = estimated_rows;
}
}
// TODO idxFlags
// TODO colUsed
// TODO sqlite3_vtab_collation (http://sqlite.org/c3ref/vtab_collation.html)
}
pub struct IndexConstraintIter<'a> {
iter: slice::Iter<'a, ffi::sqlite3_index_constraint>,
}
impl<'a> Iterator for IndexConstraintIter<'a> {
type Item = IndexConstraint<'a>;
fn next(&mut self) -> Option<IndexConstraint<'a>> {
self.iter.next().map(|raw| IndexConstraint(raw))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
/// WHERE clause constraint
pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
impl<'a> IndexConstraint<'a> {
/// Column constrained. -1 for ROWID
pub fn column(&self) -> c_int {
self.0.iColumn
}
/// Constraint operator
pub fn operator(&self) -> IndexConstraintOp {
IndexConstraintOp::from_bits_truncate(self.0.op)
}
/// True if this constraint is usable
pub fn is_usable(&self) -> bool {
self.0.usable != 0
}
}
/// Information about what parameters to pass to `VTabCursor.filter`.
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
impl<'a> IndexConstraintUsage<'a> {
/// if `argv_index` > 0, constraint is part of argv to `VTabCursor.filter`
pub fn set_argv_index(&mut self, argv_index: c_int) {
self.0.argvIndex = argv_index;
}
/// if `omit`, do not code a test for this constraint
pub fn set_omit(&mut self, omit: bool) {
self.0.omit = if omit { 1 } else { 0 };
}
}
pub struct OrderByIter<'a> {
iter: slice::Iter<'a, ffi::sqlite3_index_info_sqlite3_index_orderby>,
}
impl<'a> Iterator for OrderByIter<'a> {
type Item = OrderBy<'a>;
fn next(&mut self) -> Option<OrderBy<'a>> {
self.iter.next().map(|raw| OrderBy(raw))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
/// A column of the ORDER BY clause.
pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
impl<'a> OrderBy<'a> {
/// Column number
pub fn column(&self) -> c_int {
self.0.iColumn
}
/// True for DESC. False for ASC.
pub fn is_order_by_desc(&self) -> bool {
self.0.desc != 0
}
}
/// Virtual table cursor trait.
///
/// Implementations must be like:
/// ```rust,ignore
/// #[repr(C)]
/// struct MyTabCursor {
/// /// Base class. Must be first
/// base: ffi::sqlite3_vtab_cursor,
/// /* Virtual table implementations will typically add additional fields */
/// }
/// ```
///
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab_cursor.html))
pub trait VTabCursor: Sized {
/// Begin a search of a virtual table.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xfilter_method))
fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Values) -> Result<()>;
/// Advance cursor to the next row of a result set initiated by `filter`.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method))
fn next(&mut self) -> Result<()>;
/// Must return `false` if the cursor currently points to a valid row of data,
/// or `true` otherwise.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xeof_method))
fn eof(&self) -> bool;
/// Find the value for the `i`-th column of the current row.
/// `i` is zero-based so the first column is numbered 0.
/// May return its result back to SQLite using one of the specified `ctx`.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xcolumn_method))
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()>;
/// Return the rowid of row that the cursor is currently pointing at.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xrowid_method))
fn rowid(&self) -> Result<i64>;
}
/// Context is used by `VTabCursor.column`` to specify the cell value.
pub struct Context(*mut ffi::sqlite3_context);
impl Context {
pub fn set_result<T: ToSql>(&mut self, value: &T) -> Result<()> {
let t = value.to_sql()?;
unsafe { set_result(self.0, &t) };
Ok(())
}
// TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html)
}
/// Wrapper to `VTabCursor.filter` arguments, the values requested by `VTab.best_index`.
pub struct Values<'a> {
args: &'a [*mut ffi::sqlite3_value],
}
impl<'a> Values<'a> {
pub fn len(&self) -> usize {
self.args.len()
}
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
pub fn get<T: FromSql>(&self, idx: usize) -> Result<T> {
let arg = self.args[idx];
let value = unsafe { ValueRef::from_value(arg) };
FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => Error::InvalidFilterParameterType(idx, value.data_type()),
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err)
}
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
})
}
// `sqlite3_value_type` returns `SQLITE_NULL` for pointer.
// So it seems not possible to enhance `ValueRef::from_value`.
#[cfg(feature = "array")]
pub(crate) fn get_array(&self, idx: usize) -> Result<Option<array::Array>> {
use types::Value;
let arg = self.args[idx];
let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) };
if ptr.is_null() {
Ok(None)
} else {
Ok(Some(unsafe {
let rc = array::Array::from_raw(ptr as *const Vec<Value>);
let array = rc.clone();
array::Array::into_raw(rc); // don't consume it
array
}))
}
}
pub fn iter(&self) -> ValueIter {
ValueIter {
iter: self.args.iter(),
}
}
}
impl<'a> IntoIterator for &'a Values<'a> {
type Item = ValueRef<'a>;
type IntoIter = ValueIter<'a>;
fn into_iter(self) -> ValueIter<'a> {
self.iter()
}
}
pub struct ValueIter<'a> {
iter: slice::Iter<'a, *mut ffi::sqlite3_value>,
}
impl<'a> Iterator for ValueIter<'a> {
type Item = ValueRef<'a>;
fn next(&mut self) -> Option<ValueRef<'a>> {
self.iter
.next()
.map(|&raw| unsafe { ValueRef::from_value(raw) })
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl Connection {
/// Register a virtual table implementation.
///
/// Step 3 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
pub fn create_module<T: VTab>(
&self,
module_name: &str,
module: &Module<T>,
aux: Option<T::Aux>,
) -> Result<()> {
self.db.borrow_mut().create_module(module_name, module, aux)
}
}
impl InnerConnection {
fn create_module<T: VTab>(
&mut self,
module_name: &str,
module: &Module<T>,
aux: Option<T::Aux>,
) -> Result<()> {
let c_name = try!(str_to_cstring(module_name));
let r = match aux {
Some(aux) => {
let boxed_aux: *mut T::Aux = Box::into_raw(Box::new(aux));
unsafe {
ffi::sqlite3_create_module_v2(
self.db(),
c_name.as_ptr(),
&module.base,
boxed_aux as *mut c_void,
Some(free_boxed_value::<T::Aux>),
)
}
}
None => unsafe {
ffi::sqlite3_create_module_v2(
self.db(),
c_name.as_ptr(),
&module.base,
ptr::null_mut(),
None,
)
},
};
self.decode_result(r)
}
}
/// Escape double-quote (`"`) character occurences by doubling them (`""`).
pub fn escape_double_quote(identifier: &str) -> Cow<str> {
if identifier.contains('"') {
// escape quote by doubling them
Owned(identifier.replace("\"", "\"\""))
} else {
Borrowed(identifier)
}
}
/// Dequote string
pub fn dequote(s: &str) -> &str {
if s.len() < 2 {
return s;
}
match s.bytes().next() {
Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() {
Some(e) if e == b => &s[1..s.len() - 1],
_ => s,
},
_ => s,
}
}
/// The boolean can be one of:
/// ```text
/// 1 yes true on
/// 0 no false off
/// ```
pub fn parse_boolean(s: &str) -> Option<bool> {
if s.eq_ignore_ascii_case("yes")
|| s.eq_ignore_ascii_case("on")
|| s.eq_ignore_ascii_case("true")
|| s.eq("1")
{
Some(true)
} else if s.eq_ignore_ascii_case("no")
|| s.eq_ignore_ascii_case("off")
|| s.eq_ignore_ascii_case("false")
|| s.eq("0")
{
Some(false)
} else {
None
}
}
// FIXME copy/paste from function.rs
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
let _: Box<T> = Box::from_raw(p as *mut T);
}
unsafe extern "C" fn rust_create<T>(
db: *mut ffi::sqlite3,
aux: *mut c_void,
argc: c_int,
argv: *const *const c_char,
pp_vtab: *mut *mut ffi::sqlite3_vtab,
err_msg: *mut *mut c_char,
) -> c_int
where
T: CreateVTab,
{
use std::error::Error as StdError;
use std::ffi::CStr;
use std::slice;
let mut conn = VTabConnection(db);
let aux = aux as *mut T::Aux;
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args
.iter()
.map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error>
.collect::<Vec<_>>();
match T::create(&mut conn, aux.as_ref(), &vec[..]) {
Ok((sql, vtab)) => match ::std::ffi::CString::new(sql) {
Ok(c_sql) => {
let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
if rc == ffi::SQLITE_OK {
let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
*pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab;
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
*err_msg = mprintf(err.description());
rc
}
}
Err(err) => {
*err_msg = mprintf(err.description());
ffi::SQLITE_ERROR
}
},
Err(Error::SqliteFailure(err, s)) => {
if let Some(s) = s {
*err_msg = mprintf(&s);
}
err.extended_code
}
Err(err) => {
*err_msg = mprintf(err.description());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_connect<T>(
db: *mut ffi::sqlite3,
aux: *mut c_void,
argc: c_int,
argv: *const *const c_char,
pp_vtab: *mut *mut ffi::sqlite3_vtab,
err_msg: *mut *mut c_char,
) -> c_int
where
T: VTab,
{
use std::error::Error as StdError;
use std::ffi::CStr;
use std::slice;
let mut conn = VTabConnection(db);
let aux = aux as *mut T::Aux;
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args
.iter()
.map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error>
.collect::<Vec<_>>();
match T::connect(&mut conn, aux.as_ref(), &vec[..]) {
Ok((sql, vtab)) => match ::std::ffi::CString::new(sql) {
Ok(c_sql) => {
let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
if rc == ffi::SQLITE_OK {
let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
*pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab;
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
*err_msg = mprintf(err.description());
rc
}
}
Err(err) => {
*err_msg = mprintf(err.description());
ffi::SQLITE_ERROR
}
},
Err(Error::SqliteFailure(err, s)) => {
if let Some(s) = s {
*err_msg = mprintf(&s);
}
err.extended_code
}
Err(err) => {
*err_msg = mprintf(err.description());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_best_index<T>(
vtab: *mut ffi::sqlite3_vtab,
info: *mut ffi::sqlite3_index_info,
) -> c_int
where
T: VTab,
{
use std::error::Error as StdError;
let vt = vtab as *mut T;
let mut idx_info = IndexInfo(info);
match (*vt).best_index(&mut idx_info) {
Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, err.description());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_disconnect<T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
where
T: VTab,
{
if vtab.is_null() {
return ffi::SQLITE_OK;
}
let vtab = vtab as *mut T;
let _: Box<T> = Box::from_raw(vtab);
ffi::SQLITE_OK
}
unsafe extern "C" fn rust_destroy<T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
where
T: CreateVTab,
{
use std::error::Error as StdError;
if vtab.is_null() {
return ffi::SQLITE_OK;
}
let vt = vtab as *mut T;
match (*vt).destroy() {
Ok(_) => {
let _: Box<T> = Box::from_raw(vt);
ffi::SQLITE_OK
}
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, err.description());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_open<T>(
vtab: *mut ffi::sqlite3_vtab,
pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor,
) -> c_int
where
T: VTab,
{
use std::error::Error as StdError;
let vt = vtab as *mut T;
match (*vt).open() {
Ok(cursor) => {
let boxed_cursor: *mut T::Cursor = Box::into_raw(Box::new(cursor));
*pp_cursor = boxed_cursor as *mut ffi::sqlite3_vtab_cursor;
ffi::SQLITE_OK
}
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, err.description());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_close<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
let _: Box<C> = Box::from_raw(cr);
ffi::SQLITE_OK
}
unsafe extern "C" fn rust_filter<C>(
cursor: *mut ffi::sqlite3_vtab_cursor,
idx_num: c_int,
idx_str: *const c_char,
argc: c_int,
argv: *mut *mut ffi::sqlite3_value,
) -> c_int
where
C: VTabCursor,
{
use std::ffi::CStr;
use std::slice;
use std::str;
let idx_name = if idx_str.is_null() {
None
} else {
let c_slice = CStr::from_ptr(idx_str).to_bytes();
Some(str::from_utf8_unchecked(c_slice))
};
let args = slice::from_raw_parts_mut(argv, argc as usize);
let values = Values { args };
let cr = cursor as *mut C;
cursor_error(cursor, (*cr).filter(idx_num, idx_name, &values))
}
unsafe extern "C" fn rust_next<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
cursor_error(cursor, (*cr).next())
}
unsafe extern "C" fn rust_eof<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
(*cr).eof() as c_int
}
unsafe extern "C" fn rust_column<C>(
cursor: *mut ffi::sqlite3_vtab_cursor,
ctx: *mut ffi::sqlite3_context,
i: c_int,
) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
let mut ctxt = Context(ctx);
result_error(ctx, (*cr).column(&mut ctxt, i))
}
unsafe extern "C" fn rust_rowid<C>(
cursor: *mut ffi::sqlite3_vtab_cursor,
p_rowid: *mut ffi::sqlite3_int64,
) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
match (*cr).rowid() {
Ok(rowid) => {
*p_rowid = rowid;
ffi::SQLITE_OK
}
err => cursor_error(cursor, err),
}
}
/// Virtual table cursors can set an error message by assigning a string to `zErrMsg`.
unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
use std::error::Error as StdError;
match result {
Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg((*cursor).pVtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg((*cursor).pVtab, err.description());
ffi::SQLITE_ERROR
}
}
}
/// Virtual tables methods can set an error message by assigning a string to `zErrMsg`.
unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
if !(*vtab).zErrMsg.is_null() {
ffi::sqlite3_free((*vtab).zErrMsg as *mut c_void);
}
(*vtab).zErrMsg = mprintf(err_msg);
}
/// To raise an error, the `column` method should use this method to set the error message
/// and return the error code.
unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int {
use std::error::Error as StdError;
match result {
Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => {
match err.extended_code {
ffi::SQLITE_TOOBIG => {
ffi::sqlite3_result_error_toobig(ctx);
}
ffi::SQLITE_NOMEM => {
ffi::sqlite3_result_error_nomem(ctx);
}
code => {
ffi::sqlite3_result_error_code(ctx, code);
if let Some(Ok(cstr)) = s.map(|s| str_to_cstring(&s)) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
}
};
err.extended_code
}
Err(err) => {
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_ERROR);
if let Ok(cstr) = str_to_cstring(err.description()) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
ffi::SQLITE_ERROR
}
}
}
// Space to hold this error message string must be obtained
// from an SQLite memory allocation function.
fn mprintf(err_msg: &str) -> *mut c_char {
let c_format = CString::new("%s").unwrap();
let c_err = CString::new(err_msg).unwrap();
unsafe { ffi::sqlite3_mprintf(c_format.as_ptr(), c_err.as_ptr()) }
}
#[cfg(feature = "array")]
pub mod array;
#[cfg(feature = "csvtab")]
pub mod csvtab;
#[cfg(feature = "bundled")]
pub mod series; // SQLite >= 3.9.0
#[cfg(test)]
mod test {
#[test]
fn test_dequote() {
assert_eq!("", super::dequote(""));
assert_eq!("'", super::dequote("'"));
assert_eq!("\"", super::dequote("\""));
assert_eq!("'\"", super::dequote("'\""));
assert_eq!("", super::dequote("''"));
assert_eq!("", super::dequote("\"\""));
assert_eq!("x", super::dequote("'x'"));
assert_eq!("x", super::dequote("\"x\""));
assert_eq!("x", super::dequote("x"));
}
#[test]
fn test_parse_boolean() {
assert_eq!(None, super::parse_boolean(""));
assert_eq!(Some(true), super::parse_boolean("1"));
assert_eq!(Some(true), super::parse_boolean("yes"));
assert_eq!(Some(true), super::parse_boolean("on"));
assert_eq!(Some(true), super::parse_boolean("true"));
assert_eq!(Some(false), super::parse_boolean("0"));
assert_eq!(Some(false), super::parse_boolean("no"));
assert_eq!(Some(false), super::parse_boolean("off"));
assert_eq!(Some(false), super::parse_boolean("false"));
}
}

286
src/vtab/series.rs Normal file
View File

@ -0,0 +1,286 @@
//! generate series virtual table.
//!
//! Port of C [generate series "function"](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/series.c).
use std::default::Default;
use std::os::raw::c_int;
use ffi;
use types::Type;
use vtab::{
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
VTabCursor, Values,
};
use {Connection, Result};
/// Register the "generate_series" module.
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
conn.create_module("generate_series", &SERIES_MODULE, aux)
}
lazy_static! {
static ref SERIES_MODULE: Module<SeriesTab> = eponymous_only_module::<SeriesTab>(1);
}
// Column numbers
// const SERIES_COLUMN_VALUE : c_int = 0;
const SERIES_COLUMN_START: c_int = 1;
const SERIES_COLUMN_STOP: c_int = 2;
const SERIES_COLUMN_STEP: c_int = 3;
bitflags! {
#[repr(C)]
struct QueryPlanFlags: ::std::os::raw::c_int {
// start = $value -- constraint exists
const START = 1;
// stop = $value -- constraint exists
const STOP = 2;
// step = $value -- constraint exists
const STEP = 4;
// output in descending order
const DESC = 8;
// Both start and stop
const BOTH = QueryPlanFlags::START.bits | QueryPlanFlags::STOP.bits;
}
}
/// An instance of the Series virtual table
#[repr(C)]
struct SeriesTab {
/// Base class. Must be first
base: ffi::sqlite3_vtab,
}
impl VTab for SeriesTab {
type Aux = ();
type Cursor = SeriesTabCursor;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
_args: &[&[u8]],
) -> Result<(String, SeriesTab)> {
let vtab = SeriesTab {
base: ffi::sqlite3_vtab::default(),
};
Ok((
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(),
vtab,
))
}
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
// The query plan bitmask
let mut idx_num: QueryPlanFlags = QueryPlanFlags::empty();
// Index of the start= constraint
let mut start_idx = None;
// Index of the stop= constraint
let mut stop_idx = None;
// Index of the step= constraint
let mut step_idx = None;
for (i, constraint) in info.constraints().enumerate() {
if !constraint.is_usable() {
continue;
}
if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
continue;
}
match constraint.column() {
SERIES_COLUMN_START => {
start_idx = Some(i);
idx_num |= QueryPlanFlags::START;
}
SERIES_COLUMN_STOP => {
stop_idx = Some(i);
idx_num |= QueryPlanFlags::STOP;
}
SERIES_COLUMN_STEP => {
step_idx = Some(i);
idx_num |= QueryPlanFlags::STEP;
}
_ => {}
};
}
let mut num_of_arg = 0;
if let Some(start_idx) = start_idx {
num_of_arg += 1;
let mut constraint_usage = info.constraint_usage(start_idx);
constraint_usage.set_argv_index(num_of_arg);
constraint_usage.set_omit(true);
}
if let Some(stop_idx) = stop_idx {
num_of_arg += 1;
let mut constraint_usage = info.constraint_usage(stop_idx);
constraint_usage.set_argv_index(num_of_arg);
constraint_usage.set_omit(true);
}
if let Some(step_idx) = step_idx {
num_of_arg += 1;
let mut constraint_usage = info.constraint_usage(step_idx);
constraint_usage.set_argv_index(num_of_arg);
constraint_usage.set_omit(true);
}
if idx_num.contains(QueryPlanFlags::BOTH) {
// Both start= and stop= boundaries are available.
info.set_estimated_cost(f64::from(
2 - if idx_num.contains(QueryPlanFlags::STEP) {
1
} else {
0
},
));
info.set_estimated_rows(1000);
let order_by_consumed = {
let mut order_bys = info.order_bys();
if let Some(order_by) = order_bys.next() {
if order_by.is_order_by_desc() {
idx_num |= QueryPlanFlags::DESC;
}
true
} else {
false
}
};
if order_by_consumed {
info.set_order_by_consumed(true);
}
} else {
info.set_estimated_cost(2_147_483_647f64);
info.set_estimated_rows(2_147_483_647);
}
info.set_idx_num(idx_num.bits());
Ok(())
}
fn open(&self) -> Result<SeriesTabCursor> {
Ok(SeriesTabCursor::new())
}
}
/// A cursor for the Series virtual table
#[derive(Default)]
#[repr(C)]
struct SeriesTabCursor {
/// Base class. Must be first
base: ffi::sqlite3_vtab_cursor,
/// True to count down rather than up
is_desc: bool,
/// The rowid
row_id: i64,
/// Current value ("value")
value: i64,
/// Mimimum value ("start")
min_value: i64,
/// Maximum value ("stop")
max_value: i64,
/// Increment ("step")
step: i64,
}
impl SeriesTabCursor {
fn new() -> SeriesTabCursor {
SeriesTabCursor::default()
}
}
impl VTabCursor for SeriesTabCursor {
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values) -> Result<()> {
let idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
let mut i = 0;
if idx_num.contains(QueryPlanFlags::START) {
self.min_value = try!(args.get(i));
i += 1;
} else {
self.min_value = 0;
}
if idx_num.contains(QueryPlanFlags::STOP) {
self.max_value = try!(args.get(i));
i += 1;
} else {
self.max_value = 0xffff_ffff;
}
if idx_num.contains(QueryPlanFlags::STEP) {
self.step = try!(args.get(i));
if self.step < 1 {
self.step = 1;
}
} else {
self.step = 1;
};
for arg in args.iter() {
if arg.data_type() == Type::Null {
// If any of the constraints have a NULL value, then return no rows.
self.min_value = 1;
self.max_value = 0;
break;
}
}
self.is_desc = idx_num.contains(QueryPlanFlags::DESC);
if self.is_desc {
self.value = self.max_value;
if self.step > 0 {
self.value -= (self.max_value - self.min_value) % self.step;
}
} else {
self.value = self.min_value;
}
self.row_id = 1;
Ok(())
}
fn next(&mut self) -> Result<()> {
if self.is_desc {
self.value -= self.step;
} else {
self.value += self.step;
}
self.row_id += 1;
Ok(())
}
fn eof(&self) -> bool {
if self.is_desc {
self.value < self.min_value
} else {
self.value > self.max_value
}
}
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
let x = match i {
SERIES_COLUMN_START => self.min_value,
SERIES_COLUMN_STOP => self.max_value,
SERIES_COLUMN_STEP => self.step,
_ => self.value,
};
ctx.set_result(&x)
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_id)
}
}
#[cfg(test)]
mod test {
use ffi;
use vtab::series;
use Connection;
#[test]
fn test_series_module() {
let version = unsafe { ffi::sqlite3_libversion_number() };
if version < 3008012 {
return;
}
let db = Connection::open_in_memory().unwrap();
series::load_module(&db).unwrap();
let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)").unwrap();
let series = s.query_map(&[], |row| row.get::<_, i32>(0)).unwrap();
let mut expected = 0;
for value in series {
assert_eq!(expected, value.unwrap());
expected += 5;
}
}
}

6
test.csv Normal file
View File

@ -0,0 +1,6 @@
"colA","colB","colC"
1,2,3
a,b,c
a,"b",c
"a","b","c .. z"
"a","b","c,d"
1 colA colB colC
2 1 2 3
3 a b c
4 a b c
5 a b c .. z
6 a b c,d

View File

@ -1,8 +1,8 @@
//! Ensure we reject connections when SQLite is in single-threaded mode, as it //! Ensure we reject connections when SQLite is in single-threaded mode, as it
//! would violate safety if multiple Rust threads tried to use connections. //! would violate safety if multiple Rust threads tried to use connections.
extern crate rusqlite;
extern crate libsqlite3_sys as ffi; extern crate libsqlite3_sys as ffi;
extern crate rusqlite;
use rusqlite::Connection; use rusqlite::Connection;

97
tests/vtab.rs Normal file
View File

@ -0,0 +1,97 @@
//! Ensure Virtual tables can be declared outside `rusqlite` crate.
#[cfg(feature = "vtab")]
extern crate rusqlite;
#[cfg(feature = "vtab")]
#[test]
fn test_dummy_module() {
use rusqlite::vtab::{
eponymous_only_module, sqlite3_vtab, sqlite3_vtab_cursor, Context, IndexInfo, VTab,
VTabConnection, VTabCursor, Values,
};
use rusqlite::{version_number, Connection, Result};
use std::os::raw::c_int;
let module = eponymous_only_module::<DummyTab>(1);
#[repr(C)]
struct DummyTab {
/// Base class. Must be first
base: sqlite3_vtab,
}
impl VTab for DummyTab {
type Aux = ();
type Cursor = DummyTabCursor;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
_args: &[&[u8]],
) -> Result<(String, DummyTab)> {
let vtab = DummyTab {
base: sqlite3_vtab::default(),
};
Ok(("CREATE TABLE x(value)".to_owned(), vtab))
}
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
info.set_estimated_cost(1.);
Ok(())
}
fn open(&self) -> Result<DummyTabCursor> {
Ok(DummyTabCursor::default())
}
}
#[derive(Default)]
#[repr(C)]
struct DummyTabCursor {
/// Base class. Must be first
base: sqlite3_vtab_cursor,
/// The rowid
row_id: i64,
}
impl VTabCursor for DummyTabCursor {
fn filter(
&mut self,
_idx_num: c_int,
_idx_str: Option<&str>,
_args: &Values,
) -> Result<()> {
self.row_id = 1;
Ok(())
}
fn next(&mut self) -> Result<()> {
self.row_id += 1;
Ok(())
}
fn eof(&self) -> bool {
self.row_id > 1
}
fn column(&self, ctx: &mut Context, _: c_int) -> Result<()> {
ctx.set_result(&self.row_id)
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_id)
}
}
let db = Connection::open_in_memory().unwrap();
db.create_module::<DummyTab>("dummy", &module, None)
.unwrap();
let version = version_number();
if version < 3008012 {
return;
}
let mut s = db.prepare("SELECT * FROM dummy()").unwrap();
let dummy = s.query_row(&[], |row| row.get::<_, i32>(0)).unwrap();
assert_eq!(1, dummy);
}