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
dist: trusty
language: rust
@ -39,7 +38,8 @@ script:
- cargo test --features bundled
- cargo test --features sqlcipher
- cargo test --features "unlock_notify bundled"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled buildtime_bindgen"
- cargo test --features "array csvtab vtab"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab 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 = []
sqlcipher = ["libsqlite3-sys/sqlcipher"]
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]
time = "0.1.0"
@ -36,6 +40,8 @@ bitflags = "1.0"
lru-cache = "0.1"
chrono = { version = "0.4", optional = true }
serde_json = { version = "1.0", optional = true }
csv = { version = "1.0", optional = true }
lazy_static = { version = "1.0", optional = true }
[dev-dependencies]
tempdir = "0.3"
@ -53,8 +59,11 @@ harness = false
[[test]]
name = "deny_single_threaded_sqlite_config"
[[test]]
name = "vtab"
[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
no-default-features = true
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)
[![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)
[![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
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). View the full
[API documentation](http://docs.rs/rusqlite/).
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
```rust
extern crate rusqlite;
@ -68,31 +68,36 @@ newer SQLite version; see details below.
### Optional Features
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.
* [`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.
* [`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.
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
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
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.
* `chrono` implements [`FromSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.FromSql.html)
and [`ToSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.ToSql.html) for various
* `chrono` implements [`FromSql`](https://docs.rs/rusqlite/0.13.0/rusqlite/types/trait.FromSql.html)
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).
* `serde_json` implements [`FromSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.FromSql.html)
and [`ToSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.ToSql.html) for the
* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/0.13.0/rusqlite/types/trait.FromSql.html)
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).
* `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`.
* `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
@ -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
[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
is currently SQLite 3.17.0 (as of `rusqlite` 0.10.1 / `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:
is currently SQLite 3.24.0 (as of `rusqlite` 0.14.0 / `libsqlite3-sys`
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]
version = "0.11.0"
version = "0.14.0"
features = ["bundled"]
```
* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite

View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "libsqlite3-sys"
version = "0.9.2"
version = "0.9.3"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
repository = "https://github.com/jgallagher/rusqlite"
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_7_3 = ["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"]
# sqlite3_unlock_notify >= 3.6.12
unlock_notify = []
[build-dependencies]
bindgen = { version = "0.36", optional = true }
bindgen = { version = "0.38", optional = true }
pkg-config = { version = "0.3", 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")]
mod build {
extern crate cc;
use std::{env, fs};
use std::path::Path;
use std::{env, fs};
pub fn main() {
if cfg!(feature = "sqlcipher") {
@ -43,6 +43,8 @@ mod build {
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
}
cfg.compile("libsqlite3.a");
println!("cargo:lib_dir={}", out_dir);
}
}
@ -66,8 +68,10 @@ mod build {
match header {
HeaderLocation::FromEnvironment => {
let prefix = env_prefix();
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix))
.expect(&format!("{}_INCLUDE_DIR must be set if {}_LIB_DIR is set", prefix, prefix));
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!(
"{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
prefix, prefix
));
header.push_str("/sqlite3.h");
header
}
@ -86,6 +90,11 @@ mod build {
fn find_sqlite() -> HeaderLocation {
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.
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
println!("cargo:rustc-link-lib={}", link_lib);
@ -98,7 +107,10 @@ mod build {
}
// 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) => {
if let Some(mut header) = lib.include_paths.pop() {
header.push("sqlite3.h");
@ -155,25 +167,21 @@ mod build {
mod bindings {
use super::HeaderLocation;
use std::{env, fs};
use std::path::Path;
use std::{env, fs};
#[cfg_attr(rustfmt, rustfmt_skip)]
static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[
"bindgen-bindings/bindgen_3.6.8.rs",
#[cfg(feature = "min_sqlite_version_3_6_11")]
"bindgen-bindings/bindgen_3.6.11.rs",
#[cfg(feature = "min_sqlite_version_3_6_23")]
"bindgen-bindings/bindgen_3.6.23.rs",
#[cfg(feature = "min_sqlite_version_3_7_3")]
"bindgen-bindings/bindgen_3.7.3.rs",
#[cfg(feature = "min_sqlite_version_3_7_4")]
"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")]
"bindgen-bindings/bindgen_3.7.16.rs",
];
@ -190,12 +198,12 @@ mod build {
mod bindings {
extern crate bindgen;
use self::bindgen::callbacks::{ParseCallbacks, IntKind};
use self::bindgen::callbacks::{IntKind, ParseCallbacks};
use super::HeaderLocation;
use std::env;
use std::io::Write;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
#[derive(Debug)]
@ -242,7 +250,8 @@ mod build {
.open(path.clone())
.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 */
pub const __GNUC_VA_LIST: i32 = 1;
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.22.0\0";
pub const SQLITE_VERSION_NUMBER: i32 = 3022000;
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.24.0\0";
pub const SQLITE_VERSION_NUMBER: i32 = 3024000;
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_ERROR: i32 = 1;
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_ROLLBACK_ATOMIC: i32 = 7946;
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_SNAPSHOT: i32 = 517;
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_CONVPATH: i32 = 1038;
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_CANTLOCK: i32 = 520;
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_COMMIT_ATOMIC_WRITE: i32 = 32;
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_SET_LOCKPROXYFILE: i32 = 3;
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_STMTJRNL_SPILL: i32 = 26;
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_LOOKASIDE: i32 = 1001;
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_ENABLE_QPSG: i32 = 1007;
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_IGNORE: i32 = 2;
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_UTF16_ALIGNED: i32 = 8;
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_CONSTRAINT_EQ: i32 = 2;
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_DEFERRED_FKS: i32 = 10;
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_SORT: i32 = 2;
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_EXPLAIN: i32 = 4;
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 PARTLY_WITHIN: i32 = 1;
pub const FULLY_WITHIN: i32 = 2;
@ -2255,6 +2267,24 @@ extern "C" {
#[link_name = "\u{1}sqlite3_data_directory"]
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" {
pub fn sqlite3_get_autocommit(arg1: *mut sqlite3) -> ::std::os::raw::c_int;
}
@ -3412,6 +3442,72 @@ extern "C" {
extern "C" {
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" {
pub fn sqlite3_status(
op: ::std::os::raw::c_int,
@ -4070,6 +4166,24 @@ extern "C" {
zDb: *const ::std::os::raw::c_char,
) -> ::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;
extern "C" {
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 (*value_nochange)(sqlite3_value*);
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
/* Version 3.22.0 and later */
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
#define sqlite3_value_nochange sqltie3_api->value_nochange
#define sqlite3_vtab_collation sqltie3_api->vtab_collation
#define sqlite3_value_nochange sqlite3_api->value_nochange
#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) */
#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::fmt;
use std::os::raw::c_int;
/// Error Codes
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -64,30 +64,30 @@ pub struct Error {
impl Error {
pub fn new(result_code: c_int) -> Error {
let code = match result_code & 0xff {
super::SQLITE_INTERNAL => ErrorCode::InternalMalfunction,
super::SQLITE_PERM => ErrorCode::PermissionDenied,
super::SQLITE_ABORT => ErrorCode::OperationAborted,
super::SQLITE_BUSY => ErrorCode::DatabaseBusy,
super::SQLITE_LOCKED => ErrorCode::DatabaseLocked,
super::SQLITE_NOMEM => ErrorCode::OutOfMemory,
super::SQLITE_READONLY => ErrorCode::ReadOnly,
super::SQLITE_INTERNAL => ErrorCode::InternalMalfunction,
super::SQLITE_PERM => ErrorCode::PermissionDenied,
super::SQLITE_ABORT => ErrorCode::OperationAborted,
super::SQLITE_BUSY => ErrorCode::DatabaseBusy,
super::SQLITE_LOCKED => ErrorCode::DatabaseLocked,
super::SQLITE_NOMEM => ErrorCode::OutOfMemory,
super::SQLITE_READONLY => ErrorCode::ReadOnly,
super::SQLITE_INTERRUPT => ErrorCode::OperationInterrupted,
super::SQLITE_IOERR => ErrorCode::SystemIOFailure,
super::SQLITE_CORRUPT => ErrorCode::DatabaseCorrupt,
super::SQLITE_NOTFOUND => ErrorCode::NotFound,
super::SQLITE_FULL => ErrorCode::DiskFull,
super::SQLITE_CANTOPEN => ErrorCode::CannotOpen,
super::SQLITE_PROTOCOL => ErrorCode::FileLockingProtocolFailed,
super::SQLITE_SCHEMA => ErrorCode::SchemaChanged,
super::SQLITE_TOOBIG => ErrorCode::TooBig,
super::SQLITE_CONSTRAINT=> ErrorCode::ConstraintViolation,
super::SQLITE_MISMATCH => ErrorCode::TypeMismatch,
super::SQLITE_MISUSE => ErrorCode::APIMisuse,
super::SQLITE_NOLFS => ErrorCode::NoLargeFileSupport,
super::SQLITE_AUTH => ErrorCode::AuthorizationForStatementDenied,
super::SQLITE_RANGE => ErrorCode::ParameterOutOfRange,
super::SQLITE_NOTADB => ErrorCode::NotADatabase,
_ => ErrorCode::Unknown,
super::SQLITE_IOERR => ErrorCode::SystemIOFailure,
super::SQLITE_CORRUPT => ErrorCode::DatabaseCorrupt,
super::SQLITE_NOTFOUND => ErrorCode::NotFound,
super::SQLITE_FULL => ErrorCode::DiskFull,
super::SQLITE_CANTOPEN => ErrorCode::CannotOpen,
super::SQLITE_PROTOCOL => ErrorCode::FileLockingProtocolFailed,
super::SQLITE_SCHEMA => ErrorCode::SchemaChanged,
super::SQLITE_TOOBIG => ErrorCode::TooBig,
super::SQLITE_CONSTRAINT => ErrorCode::ConstraintViolation,
super::SQLITE_MISMATCH => ErrorCode::TypeMismatch,
super::SQLITE_MISUSE => ErrorCode::APIMisuse,
super::SQLITE_NOLFS => ErrorCode::NoLargeFileSupport,
super::SQLITE_AUTH => ErrorCode::AuthorizationForStatementDenied,
super::SQLITE_RANGE => ErrorCode::ParameterOutOfRange,
super::SQLITE_NOTADB => ErrorCode::NotADatabase,
_ => ErrorCode::Unknown,
};
Error {
@ -99,7 +99,12 @@ impl Error {
impl fmt::Display for Error {
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)
)
}
}
@ -114,48 +119,48 @@ impl error::Error for Error {
// in the current version of SQLite. We repeat them here so we don't have to worry about which
// version of SQLite added which constants, and we only use them to implement code_to_str below.
const SQLITE_NOTICE : c_int = 27;
const SQLITE_WARNING : c_int = 28;
const SQLITE_NOTICE: c_int = 27;
const SQLITE_WARNING: c_int = 28;
// Extended result codes.
const SQLITE_IOERR_SHMOPEN : c_int = (super::SQLITE_IOERR | (18<<8));
const SQLITE_IOERR_SHMSIZE : c_int = (super::SQLITE_IOERR | (19<<8));
const SQLITE_IOERR_SHMLOCK : c_int = (super::SQLITE_IOERR | (20<<8));
const SQLITE_IOERR_SHMMAP : c_int = (super::SQLITE_IOERR | (21<<8));
const SQLITE_IOERR_SEEK : c_int = (super::SQLITE_IOERR | (22<<8));
const SQLITE_IOERR_DELETE_NOENT : c_int = (super::SQLITE_IOERR | (23<<8));
const SQLITE_IOERR_MMAP : c_int = (super::SQLITE_IOERR | (24<<8));
const SQLITE_IOERR_GETTEMPPATH : c_int = (super::SQLITE_IOERR | (25<<8));
const SQLITE_IOERR_CONVPATH : c_int = (super::SQLITE_IOERR | (26<<8));
const SQLITE_IOERR_VNODE : c_int = (super::SQLITE_IOERR | (27<<8));
const SQLITE_LOCKED_SHAREDCACHE : c_int = (super::SQLITE_LOCKED | (1<<8));
const SQLITE_BUSY_RECOVERY : c_int = (super::SQLITE_BUSY | (1<<8));
const SQLITE_BUSY_SNAPSHOT : c_int = (super::SQLITE_BUSY | (2<<8));
const SQLITE_CANTOPEN_NOTEMPDIR : c_int = (super::SQLITE_CANTOPEN | (1<<8));
const SQLITE_CANTOPEN_ISDIR : c_int = (super::SQLITE_CANTOPEN | (2<<8));
const SQLITE_CANTOPEN_FULLPATH : c_int = (super::SQLITE_CANTOPEN | (3<<8));
const SQLITE_CANTOPEN_CONVPATH : c_int = (super::SQLITE_CANTOPEN | (4<<8));
const SQLITE_CORRUPT_VTAB : c_int = (super::SQLITE_CORRUPT | (1<<8));
const SQLITE_READONLY_RECOVERY : c_int = (super::SQLITE_READONLY | (1<<8));
const SQLITE_READONLY_CANTLOCK : c_int = (super::SQLITE_READONLY | (2<<8));
const SQLITE_READONLY_ROLLBACK : c_int = (super::SQLITE_READONLY | (3<<8));
const SQLITE_READONLY_DBMOVED : c_int = (super::SQLITE_READONLY | (4<<8));
const SQLITE_ABORT_ROLLBACK : c_int = (super::SQLITE_ABORT | (2<<8));
const SQLITE_CONSTRAINT_CHECK : c_int = (super::SQLITE_CONSTRAINT | (1<<8));
const SQLITE_CONSTRAINT_COMMITHOOK : c_int = (super::SQLITE_CONSTRAINT | (2<<8));
const SQLITE_CONSTRAINT_FOREIGNKEY : c_int = (super::SQLITE_CONSTRAINT | (3<<8));
const SQLITE_CONSTRAINT_FUNCTION : c_int = (super::SQLITE_CONSTRAINT | (4<<8));
const SQLITE_CONSTRAINT_NOTNULL : c_int = (super::SQLITE_CONSTRAINT | (5<<8));
const SQLITE_CONSTRAINT_PRIMARYKEY : c_int = (super::SQLITE_CONSTRAINT | (6<<8));
const SQLITE_CONSTRAINT_TRIGGER : c_int = (super::SQLITE_CONSTRAINT | (7<<8));
const SQLITE_CONSTRAINT_UNIQUE : c_int = (super::SQLITE_CONSTRAINT | (8<<8));
const SQLITE_CONSTRAINT_VTAB : c_int = (super::SQLITE_CONSTRAINT | (9<<8));
const SQLITE_CONSTRAINT_ROWID : c_int = (super::SQLITE_CONSTRAINT |(10<<8));
const SQLITE_NOTICE_RECOVER_WAL : c_int = (SQLITE_NOTICE | (1<<8));
const SQLITE_NOTICE_RECOVER_ROLLBACK : c_int = (SQLITE_NOTICE | (2<<8));
const SQLITE_WARNING_AUTOINDEX : c_int = (SQLITE_WARNING | (1<<8));
const SQLITE_AUTH_USER : c_int = (super::SQLITE_AUTH | (1<<8));
const SQLITE_IOERR_SHMOPEN: c_int = (super::SQLITE_IOERR | (18 << 8));
const SQLITE_IOERR_SHMSIZE: c_int = (super::SQLITE_IOERR | (19 << 8));
const SQLITE_IOERR_SHMLOCK: c_int = (super::SQLITE_IOERR | (20 << 8));
const SQLITE_IOERR_SHMMAP: c_int = (super::SQLITE_IOERR | (21 << 8));
const SQLITE_IOERR_SEEK: c_int = (super::SQLITE_IOERR | (22 << 8));
const SQLITE_IOERR_DELETE_NOENT: c_int = (super::SQLITE_IOERR | (23 << 8));
const SQLITE_IOERR_MMAP: c_int = (super::SQLITE_IOERR | (24 << 8));
const SQLITE_IOERR_GETTEMPPATH: c_int = (super::SQLITE_IOERR | (25 << 8));
const SQLITE_IOERR_CONVPATH: c_int = (super::SQLITE_IOERR | (26 << 8));
const SQLITE_IOERR_VNODE: c_int = (super::SQLITE_IOERR | (27 << 8));
const SQLITE_LOCKED_SHAREDCACHE: c_int = (super::SQLITE_LOCKED | (1 << 8));
const SQLITE_BUSY_RECOVERY: c_int = (super::SQLITE_BUSY | (1 << 8));
const SQLITE_BUSY_SNAPSHOT: c_int = (super::SQLITE_BUSY | (2 << 8));
const SQLITE_CANTOPEN_NOTEMPDIR: c_int = (super::SQLITE_CANTOPEN | (1 << 8));
const SQLITE_CANTOPEN_ISDIR: c_int = (super::SQLITE_CANTOPEN | (2 << 8));
const SQLITE_CANTOPEN_FULLPATH: c_int = (super::SQLITE_CANTOPEN | (3 << 8));
const SQLITE_CANTOPEN_CONVPATH: c_int = (super::SQLITE_CANTOPEN | (4 << 8));
const SQLITE_CORRUPT_VTAB: c_int = (super::SQLITE_CORRUPT | (1 << 8));
const SQLITE_READONLY_RECOVERY: c_int = (super::SQLITE_READONLY | (1 << 8));
const SQLITE_READONLY_CANTLOCK: c_int = (super::SQLITE_READONLY | (2 << 8));
const SQLITE_READONLY_ROLLBACK: c_int = (super::SQLITE_READONLY | (3 << 8));
const SQLITE_READONLY_DBMOVED: c_int = (super::SQLITE_READONLY | (4 << 8));
const SQLITE_ABORT_ROLLBACK: c_int = (super::SQLITE_ABORT | (2 << 8));
const SQLITE_CONSTRAINT_CHECK: c_int = (super::SQLITE_CONSTRAINT | (1 << 8));
const SQLITE_CONSTRAINT_COMMITHOOK: c_int = (super::SQLITE_CONSTRAINT | (2 << 8));
const SQLITE_CONSTRAINT_FOREIGNKEY: c_int = (super::SQLITE_CONSTRAINT | (3 << 8));
const SQLITE_CONSTRAINT_FUNCTION: c_int = (super::SQLITE_CONSTRAINT | (4 << 8));
const SQLITE_CONSTRAINT_NOTNULL: c_int = (super::SQLITE_CONSTRAINT | (5 << 8));
const SQLITE_CONSTRAINT_PRIMARYKEY: c_int = (super::SQLITE_CONSTRAINT | (6 << 8));
const SQLITE_CONSTRAINT_TRIGGER: c_int = (super::SQLITE_CONSTRAINT | (7 << 8));
const SQLITE_CONSTRAINT_UNIQUE: c_int = (super::SQLITE_CONSTRAINT | (8 << 8));
const SQLITE_CONSTRAINT_VTAB: c_int = (super::SQLITE_CONSTRAINT | (9 << 8));
const SQLITE_CONSTRAINT_ROWID: c_int = (super::SQLITE_CONSTRAINT | (10 << 8));
const SQLITE_NOTICE_RECOVER_WAL: c_int = (SQLITE_NOTICE | (1 << 8));
const SQLITE_NOTICE_RECOVER_ROLLBACK: c_int = (SQLITE_NOTICE | (2 << 8));
const SQLITE_WARNING_AUTOINDEX: c_int = (SQLITE_WARNING | (1 << 8));
const SQLITE_AUTH_USER: c_int = (super::SQLITE_AUTH | (1 << 8));
pub fn code_to_str(code: c_int) -> &'static str {
match code {

View File

@ -2,6 +2,7 @@
pub use self::error::*;
use std::default::Default;
use std::mem;
mod error;
@ -45,3 +46,18 @@ pub enum Limit {
}
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
# Download and extract amalgamation
SQLITE=sqlite-amalgamation-3220000
SQLITE=sqlite-amalgamation-3240000
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.h > $SQLITE3_LIB_DIR/sqlite3.h

View File

@ -36,8 +36,8 @@ use std::time::Duration;
use ffi;
use {DatabaseName, Connection, Result};
use error::{error_from_sqlite_code, error_from_handle};
use error::{error_from_handle, error_from_sqlite_code};
use {Connection, DatabaseName, Result};
impl Connection {
/// 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
/// or if the backup fails.
pub fn backup<P: AsRef<Path>>(&self,
name: DatabaseName,
dst_path: P,
progress: Option<fn(Progress)>)
-> Result<()> {
use self::StepResult::{More, Done, Busy, Locked};
pub fn backup<P: AsRef<Path>>(
&self,
name: DatabaseName,
dst_path: P,
progress: Option<fn(Progress)>,
) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More};
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;
while r == More {
@ -89,12 +95,13 @@ impl Connection {
///
/// Will return `Err` if the destination path cannot be opened
/// or if the restore fails.
pub fn restore<P: AsRef<Path>>(&mut self,
name: DatabaseName,
src_path: P,
progress: Option<fn(Progress)>)
-> Result<()> {
use self::StepResult::{More, Done, Busy, Locked};
pub fn restore<P: AsRef<Path>, F: Fn(Progress)>(
&mut self,
name: DatabaseName,
src_path: P,
progress: Option<F>,
) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More};
let src = try!(Connection::open(src_path));
let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name));
@ -102,7 +109,7 @@ impl Connection {
let mut busy_count = 0i32;
'restore_loop: while r == More || r == Busy {
r = try!(restore.step(100));
if let Some(f) = progress {
if let Some(ref f) = progress {
f(restore.progress());
}
if r == Busy {
@ -124,7 +131,7 @@ impl Connection {
}
/// Possible successful results of calling `Backup::step`.
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum StepResult {
/// The backup is complete.
Done,
@ -146,7 +153,7 @@ pub enum StepResult {
/// backup is as of the last call to `step` - if the source database is
/// modified after a call to `step`, the progress value will become outdated
/// and potentially incorrect.
#[derive(Copy,Clone,Debug)]
#[derive(Copy, Clone, Debug)]
pub struct Progress {
/// Number of pages in the source database that still need to be backed up.
pub remaining: c_int,
@ -184,21 +191,24 @@ impl<'a, 'b> Backup<'a, 'b> {
///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `NULL`.
pub fn new_with_names(from: &'a Connection,
from_name: DatabaseName,
to: &'b mut Connection,
to_name: DatabaseName)
-> Result<Backup<'a, 'b>> {
pub fn new_with_names(
from: &'a Connection,
from_name: DatabaseName,
to: &'b mut Connection,
to_name: DatabaseName,
) -> Result<Backup<'a, 'b>> {
let to_name = try!(to_name.to_cstring());
let from_name = try!(from_name.to_cstring());
let to_db = to.db.borrow_mut().db;
let b = unsafe {
let b = ffi::sqlite3_backup_init(to_db,
to_name.as_ptr(),
from.db.borrow_mut().db,
from_name.as_ptr());
let b = ffi::sqlite3_backup_init(
to_db,
to_name.as_ptr(),
from.db.borrow_mut().db,
from_name.as_ptr(),
);
if b.is_null() {
return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
}
@ -206,10 +216,10 @@ impl<'a, 'b> Backup<'a, 'b> {
};
Ok(Backup {
phantom_from: PhantomData,
phantom_to: PhantomData,
b,
})
phantom_from: PhantomData,
phantom_to: PhantomData,
b,
})
}
/// Gets the progress of the backup as of the last call to `step`.
@ -235,7 +245,7 @@ impl<'a, 'b> Backup<'a, 'b> {
/// `LOCKED` are transient errors and are therefore returned as possible
/// `Ok` values.
pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
use self::StepResult::{Done, More, Busy, Locked};
use self::StepResult::{Busy, Done, Locked, More};
let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
match rc {
@ -262,12 +272,13 @@ impl<'a, 'b> Backup<'a, 'b> {
/// # Failure
///
/// Will return `Err` if any of the calls to `step` return `Err`.
pub fn run_to_completion(&self,
pages_per_step: c_int,
pause_between_pages: Duration,
progress: Option<fn(Progress)>)
-> Result<()> {
use self::StepResult::{Done, More, Busy, Locked};
pub fn run_to_completion(
&self,
pages_per_step: c_int,
pause_between_pages: Duration,
progress: Option<fn(Progress)>,
) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More};
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)]
mod test {
use {Connection, DatabaseName};
use std::time::Duration;
use super::Backup;
use std::time::Duration;
use {Connection, DatabaseName};
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup() {
let src = Connection::open_in_memory().unwrap();
let sql = "BEGIN;
@ -313,22 +323,27 @@ mod test {
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);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{
let backup = Backup::new(&src, &mut dst).unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
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);
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup_temp() {
let src = Connection::open_in_memory().unwrap();
let sql = "BEGIN;
@ -340,34 +355,35 @@ mod test {
let mut dst = Connection::open_in_memory().unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Temp,
&mut dst,
DatabaseName::Main)
.unwrap();
let backup =
Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
.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);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Temp,
&mut dst,
DatabaseName::Main)
.unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
let backup =
Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
.unwrap();
backup
.run_to_completion(5, Duration::from_millis(250), None)
.unwrap();
}
let the_answer: 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);
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup_attached() {
let src = Connection::open_in_memory().unwrap();
let sql = "ATTACH DATABASE ':memory:' AS my_attached;
@ -380,29 +396,37 @@ mod test {
let mut dst = Connection::open_in_memory().unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main)
.unwrap();
let backup = Backup::new_with_names(
&src,
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main,
).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);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main)
.unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
let backup = Backup::new_with_names(
&src,
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main,
).unwrap();
backup
.run_to_completion(5, Duration::from_millis(250), None)
.unwrap();
}
let the_answer: 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);
}
}

View File

@ -48,13 +48,13 @@
//! assert_eq!(blob.size(), 64);
//! }
//! ```
use std::io;
use std::cmp::min;
use std::io;
use std::ptr;
use super::ffi;
use super::types::{ToSql, ToSqlOutput};
use {Result, Connection, DatabaseName};
use {Connection, DatabaseName, Result};
/// Handle to an open BLOB.
pub struct Blob<'conn> {
@ -70,35 +70,35 @@ impl Connection {
///
/// Will return `Err` if `db`/`table`/`column` cannot be converted to a C-compatible string
/// or if the underlying SQLite BLOB open call fails.
pub fn blob_open<'a>(&'a self,
db: DatabaseName,
table: &str,
column: &str,
row_id: i64,
read_only: bool)
-> Result<Blob<'a>> {
pub fn blob_open<'a>(
&'a self,
db: DatabaseName,
table: &str,
column: &str,
row_id: i64,
read_only: bool,
) -> Result<Blob<'a>> {
let mut c = self.db.borrow_mut();
let mut blob = ptr::null_mut();
let db = try!(db.to_cstring());
let table = try!(super::str_to_cstring(table));
let column = try!(super::str_to_cstring(column));
let rc = unsafe {
ffi::sqlite3_blob_open(c.db(),
db.as_ptr(),
table.as_ptr(),
column.as_ptr(),
row_id,
if read_only { 0 } else { 1 },
&mut blob)
ffi::sqlite3_blob_open(
c.db(),
db.as_ptr(),
table.as_ptr(),
column.as_ptr(),
row_id,
if read_only { 0 } else { 1 },
&mut blob,
)
};
c.decode_result(rc)
.map(|_| {
Blob {
conn: self,
blob,
pos: 0,
}
})
c.decode_result(rc).map(|_| Blob {
conn: self,
blob,
pos: 0,
})
}
}
@ -154,15 +154,13 @@ impl<'conn> io::Read for Blob<'conn> {
if n <= 0 {
return Ok(0);
}
let rc =
unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
let rc = unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
self.conn
.decode_result(rc)
.map(|_| {
self.pos += n;
n as usize
})
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
self.pos += n;
n as usize
}).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
}
}
@ -183,16 +181,13 @@ impl<'conn> io::Write for Blob<'conn> {
if n <= 0 {
return Ok(0);
}
let rc = unsafe {
ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos)
};
let rc = unsafe { ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
self.conn
.decode_result(rc)
.map(|_| {
self.pos += n;
n as usize
})
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
self.pos += n;
n as usize
}).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
}
fn flush(&mut self) -> io::Result<()> {
@ -210,11 +205,15 @@ impl<'conn> io::Seek for Blob<'conn> {
};
if pos < 0 {
Err(io::Error::new(io::ErrorKind::InvalidInput,
"invalid seek to negative position"))
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid seek to negative position",
))
} else if pos > i64::from(self.size()) {
Err(io::Error::new(io::ErrorKind::InvalidInput,
"invalid seek to position past end of blob"))
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid seek to position past end of blob",
))
} else {
self.pos = pos as i32;
Ok(pos as u64)
@ -235,7 +234,7 @@ impl<'conn> Drop for Blob<'conn> {
/// incremental BLOB I/O routines.
///
/// A negative value for the zeroblob results in a zero-length BLOB.
#[derive(Copy,Clone)]
#[derive(Copy, Clone)]
pub struct ZeroBlob(pub i32);
impl ToSql for ZeroBlob {
@ -247,10 +246,9 @@ impl ToSql for ZeroBlob {
#[cfg(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};
#[cfg_attr(rustfmt, rustfmt_skip)]
fn db_with_test_blob() -> Result<(Connection, i64)> {
let db = try!(Connection::open_in_memory());
let sql = "BEGIN;
@ -266,7 +264,8 @@ mod test {
fn test_blob() {
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();
assert_eq!(4, blob.write(b"Clob").unwrap());
assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10
@ -275,7 +274,8 @@ mod test {
blob.reopen(rowid).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();
let mut bytes = [0u8; 5];
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
@ -316,7 +316,8 @@ mod test {
fn test_blob_in_bufreader() {
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();
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 blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)
let blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap();
let mut writer = BufWriter::new(blob);
@ -353,7 +355,8 @@ mod test {
{
// ... 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();
let mut bytes = [0u8; 10];
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();
let mut writer = BufWriter::new(blob);
@ -372,7 +376,8 @@ mod test {
{
// ... 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();
let mut bytes = [0u8; 10];
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.
use lru_cache::LruCache;
use raw_statement::RawStatement;
use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
use lru_cache::LruCache;
use {Result, Connection, Statement};
use raw_statement::RawStatement;
use statement::StatementCrateImpl;
use {Connection, Result, Statement};
impl Connection {
/// 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
// call fails.
fn get<'conn>(&'conn self,
conn: &'conn Connection,
sql: &str)
-> Result<CachedStatement<'conn>> {
fn get<'conn>(
&'conn self,
conn: &'conn Connection,
sql: &str,
) -> Result<CachedStatement<'conn>> {
let mut cache = self.0.borrow_mut();
let stmt = match cache.remove(sql.trim()) {
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
@ -136,7 +136,9 @@ impl StatementCache {
fn cache_stmt(&self, stmt: RawStatement) {
let mut cache = self.0.borrow_mut();
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);
}
@ -148,8 +150,8 @@ impl StatementCache {
#[cfg(test)]
mod test {
use Connection;
use super::StatementCache;
use Connection;
impl StatementCache {
fn clear(&self) {
@ -177,14 +179,14 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
@ -202,7 +204,7 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
@ -212,7 +214,7 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
@ -220,7 +222,7 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
}
@ -234,7 +236,7 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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();
}
assert_eq!(0, cache.len());
@ -243,46 +245,51 @@ mod test {
#[test]
fn test_ddl() {
let db = Connection::open_in_memory().unwrap();
db.execute_batch(r#"
db.execute_batch(
r#"
CREATE TABLE foo (x INT);
INSERT INTO foo VALUES (1);
"#)
.unwrap();
"#,
).unwrap();
let sql = "SELECT * FROM foo";
{
let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(1i32,
stmt.query_map::<i32, _>(&[], |r| r.get(0))
.unwrap()
.next()
.unwrap()
.unwrap());
assert_eq!(
1i32,
stmt.query_map::<i32, _>(&[], |r| r.get(0))
.unwrap()
.next()
.unwrap()
.unwrap()
);
}
db.execute_batch(r#"
db.execute_batch(
r#"
ALTER TABLE foo ADD COLUMN y INT;
UPDATE foo SET y = 2;
"#)
.unwrap();
"#,
).unwrap();
{
let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!((1i32, 2i32),
stmt.query_map(&[], |r| (r.get(0), r.get(1)))
.unwrap()
.next()
.unwrap()
.unwrap());
assert_eq!(
(1i32, 2i32),
stmt.query_map(&[], |r| (r.get(0), r.get(1)))
.unwrap()
.next()
.unwrap()
.unwrap()
);
}
}
#[test]
fn test_connection_close() {
let conn = Connection::open_in_memory().unwrap();
conn.prepare_cached("SELECT * FROM sqlite_master;")
.unwrap();
conn.prepare_cached("SELECT * FROM sqlite_master;").unwrap();
conn.close().expect("connection not closed");
}
@ -298,14 +305,14 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
}

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::fmt;
use std::os::raw::c_int;
use std::path::PathBuf;
use std::str;
use std::os::raw::c_int;
use {ffi, errmsg_to_string};
use types::Type;
use {errmsg_to_string, ffi};
/// Old name for `Error`. `SqliteError` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Error instead")]
@ -26,9 +26,9 @@ pub enum Error {
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.,
/// 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.
IntegralValueOutOfRange(c_int, i64),
IntegralValueOutOfRange(usize, i64),
/// Error converting a string to UTF-8.
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
/// for the statement.
InvalidColumnIndex(c_int),
InvalidColumnIndex(usize),
/// Error when the value of a named column is requested, but no column matches the name
/// 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
/// 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.
StatementChangedRows(c_int),
StatementChangedRows(usize),
/// Error returned by `functions::Context::get` when the function argument cannot be converted
/// to the requested type.
#[cfg(feature = "functions")]
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.,
/// `create_scalar_function`).
@ -77,6 +81,15 @@ pub enum Error {
/// Error available for the implementors of the `ToSql` trait.
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 {
@ -96,17 +109,15 @@ impl fmt::Display for Error {
match *self {
Error::SqliteFailure(ref err, None) => err.fmt(f),
Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
Error::SqliteSingleThreadedMode => {
write!(f,
"SQLite was compiled or configured for single-threaded use only")
}
Error::FromSqlConversionFailure(i, ref t, ref err) => {
write!(f,
"Conversion error from type {} at index: {}, {}",
t,
i,
err)
}
Error::SqliteSingleThreadedMode => write!(
f,
"SQLite was compiled or configured for single-threaded use only"
),
Error::FromSqlConversionFailure(i, ref t, ref err) => write!(
f,
"Conversion error from type {} at index: {}, {}",
t, i, err
),
Error::IntegralValueOutOfRange(col, val) => {
write!(f, "Integer {} out of range at index {}", val, col)
}
@ -129,9 +140,16 @@ impl fmt::Display for Error {
Error::InvalidFunctionParameterType(i, ref t) => {
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")]
Error::UserFunctionError(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 {
Error::SqliteFailure(ref err, None) => err.description(),
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::IntegralValueOutOfRange(_, _) => "integral value out of range of requested type",
Error::Utf8Error(ref err) => err.description(),
Error::InvalidParameterName(_) => "invalid parameter name",
Error::NulError(ref err) => err.description(),
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::InvalidColumnIndex(_) => "invalid column index",
Error::InvalidColumnName(_) => "invalid column name",
@ -157,9 +179,14 @@ impl error::Error for Error {
#[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
#[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(_, _) => "invalid filter parameter type",
#[cfg(feature = "functions")]
Error::UserFunctionError(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::NulError(ref err) => Some(err),
Error::IntegralValueOutOfRange(_, _) |
Error::SqliteSingleThreadedMode |
Error::InvalidParameterName(_) |
Error::ExecuteReturnedResults |
Error::QueryReturnedNoRows |
Error::InvalidColumnIndex(_) |
Error::InvalidColumnName(_) |
Error::InvalidColumnType(_, _) |
Error::InvalidPath(_) |
Error::StatementChangedRows(_) => None,
Error::IntegralValueOutOfRange(_, _)
| Error::SqliteSingleThreadedMode
| Error::InvalidParameterName(_)
| Error::ExecuteReturnedResults
| Error::QueryReturnedNoRows
| Error::InvalidColumnIndex(_)
| Error::InvalidColumnName(_)
| Error::InvalidColumnType(_, _)
| Error::InvalidPath(_)
| Error::StatementChangedRows(_)
| Error::InvalidQuery => None,
#[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => None,
#[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(_, _) => None,
#[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => Some(&**err),
Error::FromSqlConversionFailure(_, _, ref err) |
Error::ToSqlConversionFailure(ref err) => Some(&**err),
Error::FromSqlConversionFailure(_, _, ref 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::ffi::CStr;
use std::os::raw::{c_int, c_void};
use std::ptr;
use std::slice;
use std::os::raw::{c_int, c_char, c_void};
use ffi;
use ffi::sqlite3_context;
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};
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());
}
},
}
}
use {str_to_cstring, Connection, Error, InnerConnection, Result};
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
@ -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) {
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.
@ -212,17 +124,14 @@ impl<'a> Context<'a> {
let arg = self.args[idx];
let value = unsafe { ValueRef::from_value(arg) };
FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => {
FromSqlError::InvalidType => {
Error::InvalidFunctionParameterType(idx, value.data_type())
}
FromSqlError::OutOfRange(i) => {
Error::IntegralValueOutOfRange(idx as c_int,
i)
}
FromSqlError::Other(err) => {
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err)
}
})
})
}
/// Sets the auxilliary data associated with a particular parameter. See
@ -231,10 +140,12 @@ impl<'a> Context<'a> {
pub fn set_aux<T>(&self, arg: c_int, value: T) {
let boxed = Box::into_raw(Box::new(value));
unsafe {
ffi::sqlite3_set_auxdata(self.ctx,
arg,
boxed as *mut c_void,
Some(free_boxed_value::<T>))
ffi::sqlite3_set_auxdata(
self.ctx,
arg,
boxed as *mut c_void,
Some(free_boxed_value::<T>),
)
};
}
@ -248,7 +159,11 @@ impl<'a> Context<'a> {
/// types must be identical.
pub unsafe fn get_aux<T>(&self, arg: c_int) -> Option<&T> {
let p = ffi::sqlite3_get_auxdata(self.ctx, arg) as *mut T;
if p.is_null() { None } else { Some(&*p) }
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.
/// Implementations should be stateless.
pub trait Aggregate<A, T>
where T: ToSql
where
T: ToSql,
{
/// 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:
/// `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;
/// "step" function called once for each row in an aggregate group. May be called
@ -306,14 +222,16 @@ impl Connection {
/// # Failure
///
/// Will return Err if the function could not be attached to the connection.
pub fn create_scalar_function<F, T>(&self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
x_func: F)
-> Result<()>
where F: FnMut(&Context) -> Result<T>,
T: ToSql
pub fn create_scalar_function<F, T>(
&self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
x_func: F,
) -> Result<()>
where
F: FnMut(&Context) -> Result<T> + Send + 'static,
T: ToSql,
{
self.db
.borrow_mut()
@ -325,14 +243,16 @@ impl Connection {
/// # Failure
///
/// Will return Err if the function could not be attached to the connection.
pub fn create_aggregate_function<A, D, T>(&self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
aggr: D)
-> Result<()>
where D: Aggregate<A, T>,
T: ToSql
pub fn create_aggregate_function<A, D, T>(
&self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
aggr: D,
) -> Result<()>
where
D: Aggregate<A, T>,
T: ToSql,
{
self.db
.borrow_mut()
@ -353,20 +273,24 @@ impl Connection {
}
impl InnerConnection {
fn create_scalar_function<F, T>(&mut self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
x_func: F)
-> Result<()>
where F: FnMut(&Context) -> Result<T>,
T: ToSql
fn create_scalar_function<F, T>(
&mut self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
x_func: F,
) -> Result<()>
where
F: FnMut(&Context) -> Result<T> + Send + 'static,
T: ToSql,
{
unsafe extern "C" fn call_boxed_closure<F, T>(ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value)
where F: FnMut(&Context) -> Result<T>,
T: ToSql
unsafe extern "C" fn call_boxed_closure<F, T>(
ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value,
) where
F: FnMut(&Context) -> Result<T>,
T: ToSql,
{
let ctx = Context {
ctx,
@ -392,31 +316,36 @@ impl InnerConnection {
flags |= ffi::SQLITE_DETERMINISTIC;
}
let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(),
c_name.as_ptr(),
n_arg,
flags,
boxed_f as *mut c_void,
Some(call_boxed_closure::<F, T>),
None,
None,
Some(free_boxed_value::<F>))
ffi::sqlite3_create_function_v2(
self.db(),
c_name.as_ptr(),
n_arg,
flags,
boxed_f as *mut c_void,
Some(call_boxed_closure::<F, T>),
None,
None,
Some(free_boxed_value::<F>),
)
};
self.decode_result(r)
}
fn create_aggregate_function<A, D, T>(&mut self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
aggr: D)
-> Result<()>
where D: Aggregate<A, T>,
T: ToSql
fn create_aggregate_function<A, D, T>(
&mut self,
fn_name: &str,
n_arg: c_int,
deterministic: bool,
aggr: D,
) -> Result<()>
where
D: Aggregate<A, T>,
T: ToSql,
{
unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context,
bytes: usize)
-> Option<*mut *mut A> {
unsafe fn aggregate_context<A>(
ctx: *mut sqlite3_context,
bytes: usize,
) -> Option<*mut *mut A> {
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
if pac.is_null() {
return None;
@ -424,15 +353,19 @@ impl InnerConnection {
Some(pac)
}
unsafe extern "C" fn call_boxed_step<A, D, T>(ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value)
where D: Aggregate<A, T>,
T: ToSql
unsafe extern "C" fn call_boxed_step<A, D, T>(
ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value,
) where
D: Aggregate<A, T>,
T: ToSql,
{
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(!boxed_aggr.is_null(),
"Internal error - null aggregate pointer");
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
Some(pac) => pac,
@ -458,12 +391,15 @@ impl InnerConnection {
}
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
where D: Aggregate<A, T>,
T: ToSql
where
D: Aggregate<A, T>,
T: ToSql,
{
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(!boxed_aggr.is_null(),
"Internal error - null aggregate pointer");
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
// 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.
@ -495,15 +431,17 @@ impl InnerConnection {
flags |= ffi::SQLITE_DETERMINISTIC;
}
let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(),
c_name.as_ptr(),
n_arg,
flags,
boxed_aggr as *mut c_void,
None,
Some(call_boxed_step::<A, D, T>),
Some(call_boxed_final::<A, D, T>),
Some(free_boxed_value::<D>))
ffi::sqlite3_create_function_v2(
self.db(),
c_name.as_ptr(),
n_arg,
flags,
boxed_aggr as *mut c_void,
None,
Some(call_boxed_step::<A, D, T>),
Some(call_boxed_final::<A, D, T>),
Some(free_boxed_value::<D>),
)
};
self.decode_result(r)
}
@ -511,15 +449,17 @@ impl InnerConnection {
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
let c_name = try!(str_to_cstring(fn_name));
let r = unsafe {
ffi::sqlite3_create_function_v2(self.db(),
c_name.as_ptr(),
n_arg,
ffi::SQLITE_UTF8,
ptr::null_mut(),
None,
None,
None,
None)
ffi::sqlite3_create_function_v2(
self.db(),
c_name.as_ptr(),
n_arg,
ffi::SQLITE_UTF8,
ptr::null_mut(),
None,
None,
None,
None,
)
};
self.decode_result(r)
}
@ -529,13 +469,13 @@ impl InnerConnection {
mod test {
extern crate regex;
use std::collections::HashMap;
use std::os::raw::c_double;
use self::regex::Regex;
use std::collections::HashMap;
use std::f64::EPSILON;
use std::os::raw::c_double;
use {Connection, Error, Result};
use functions::{Aggregate, Context};
use {Connection, Error, Result};
fn half(ctx: &Context) -> Result<c_double> {
assert!(ctx.len() == 1, "called with unexpected number of arguments");
@ -597,41 +537,44 @@ mod test {
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_function_regexp_with_auxilliary() {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("BEGIN;
CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX');
END;").unwrap();
db.create_scalar_function("regexp", 2, true, regexp_with_auxilliary).unwrap();
db.execute_batch(
"BEGIN;
CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX');
END;",
).unwrap();
db.create_scalar_function("regexp", 2, true, regexp_with_auxilliary)
.unwrap();
let result: Result<bool> = db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')",
&[],
|r| r.get(0));
let result: Result<bool> =
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", &[], |r| r.get(0));
assert_eq!(true, result.unwrap());
let result: Result<i64> =
db.query_row("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
&[],
|r| r.get(0));
let result: Result<i64> = db.query_row(
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
&[],
|r| r.get(0),
);
assert_eq!(2, result.unwrap());
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_function_regexp_with_hashmap_cache() {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("BEGIN;
CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX');
END;").unwrap();
db.execute_batch(
"BEGIN;
CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX');
END;",
).unwrap();
// This implementation of a regexp scalar function uses a captured HashMap
// to keep cached regular expressions around (even across multiple queries)
@ -646,12 +589,10 @@ mod test {
use std::collections::hash_map::Entry::{Occupied, Vacant};
match entry {
Occupied(occ) => occ.into_mut(),
Vacant(vac) => {
match Regex::new(&regex_s) {
Ok(r) => vac.insert(r),
Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
}
}
Vacant(vac) => match Regex::new(&regex_s) {
Ok(r) => vac.insert(r),
Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
},
}
};
@ -659,16 +600,16 @@ mod test {
Ok(regex.is_match(&text))
}).unwrap();
let result: Result<bool> = db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')",
&[],
|r| r.get(0));
let result: Result<bool> =
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", &[], |r| r.get(0));
assert_eq!(true, result.unwrap());
let result: Result<i64> =
db.query_row("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
&[],
|r| r.get(0));
let result: Result<i64> = db.query_row(
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
&[],
|r| r.get(0),
);
assert_eq!(2, result.unwrap());
}
@ -677,21 +618,21 @@ mod test {
fn test_varargs_function() {
let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("my_concat", -1, true, |ctx| {
let mut ret = String::new();
let mut ret = String::new();
for idx in 0..ctx.len() {
let s = try!(ctx.get::<String>(idx));
ret.push_str(&s);
}
for idx in 0..ctx.len() {
let s = try!(ctx.get::<String>(idx));
ret.push_str(&s);
}
Ok(ret)
})
.unwrap();
Ok(ret)
}).unwrap();
for &(expected, query) in
&[("", "SELECT my_concat()"),
("onetwo", "SELECT my_concat('one', 'two')"),
("abc", "SELECT my_concat('a', 'b', 'c')")] {
for &(expected, query) in &[
("", "SELECT my_concat()"),
("onetwo", "SELECT my_concat('one', 'two')"),
("abc", "SELECT my_concat('a', 'b', 'c')"),
] {
let result: String = db.query_row(query, &[], |r| r.get(0)).unwrap();
assert_eq!(expected, result);
}
@ -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 \
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();
assert_eq!((4, 2), result);
}

View File

@ -1,8 +1,8 @@
//! Commit, Data Change and Rollback Notification Callbacks
#![allow(non_camel_case_types)]
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::os::raw::{c_int, c_char, c_void};
use ffi;
@ -94,8 +94,9 @@ impl Connection {
/// Register a callback function to be invoked whenever a transaction is committed.
///
/// The callback returns `true` to rollback.
pub fn commit_hook<F>(&self, hook: F)
where F: FnMut() -> bool
pub fn commit_hook<F>(&self, hook: Option<F>)
where
F: FnMut() -> bool + Send + 'static,
{
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.
///
/// The callback returns `true` to rollback.
pub fn rollback_hook<F>(&self, hook: F)
where F: FnMut()
pub fn rollback_hook<F>(&self, hook: Option<F>)
where
F: FnMut() + Send + 'static,
{
self.db.borrow_mut().rollback_hook(hook);
}
@ -118,99 +120,122 @@ impl Connection {
/// - the name of the database ("main", "temp", ...),
/// - the name of the table that is updated,
/// - the ROWID of the row that is updated.
pub fn update_hook<F>(&self, hook: F)
where F: FnMut(Action, &str, &str, i64)
pub fn update_hook<F>(&self, hook: Option<F>)
where
F: FnMut(Action, &str, &str, i64) + Send + 'static,
{
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 {
pub fn remove_hooks(&mut self) {
self.remove_update_hook();
self.remove_commit_hook();
self.remove_rollback_hook();
self.update_hook(None::<fn(Action, &str, &str, i64)>);
self.commit_hook(None::<fn() -> bool>);
self.rollback_hook(None::<fn()>);
}
fn commit_hook<F>(&self, hook: F)
where F: FnMut() -> bool
fn commit_hook<F>(&mut self, hook: Option<F>)
where
F: FnMut() -> bool + Send + 'static,
{
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;
assert!(!boxed_hook.is_null(),
"Internal error - null function pointer");
if (*boxed_hook)() { 1 } else { 0 }
if (*boxed_hook)() {
1
} else {
0
}
}
let previous_hook = {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe {
ffi::sqlite3_commit_hook(self.db(),
Some(call_boxed_closure::<F>),
boxed_hook as *mut _)
}
// 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
};
free_boxed_hook(previous_hook);
let previous_hook = match hook {
Some(hook) => {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe {
ffi::sqlite3_commit_hook(
self.db(),
Some(call_boxed_closure::<F>),
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);
}
}
self.free_commit_hook = free_commit_hook;
}
fn rollback_hook<F>(&self, hook: F)
where F: FnMut()
fn rollback_hook<F>(&mut self, hook: Option<F>)
where
F: FnMut() + Send + 'static,
{
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;
assert!(!boxed_hook.is_null(),
"Internal error - null function pointer");
(*boxed_hook)();
}
let previous_hook = {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe {
ffi::sqlite3_rollback_hook(self.db(),
Some(call_boxed_closure::<F>),
boxed_hook as *mut _)
}
let free_rollback_hook = if hook.is_some() {
Some(free_boxed_hook::<F> as fn(*mut c_void))
} else {
None
};
free_boxed_hook(previous_hook);
let previous_hook = match hook {
Some(hook) => {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe {
ffi::sqlite3_rollback_hook(
self.db(),
Some(call_boxed_closure::<F>),
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);
}
}
self.free_rollback_hook = free_rollback_hook;
}
fn update_hook<F>(&mut self, hook: F)
where F: FnMut(Action, &str, &str, i64)
fn update_hook<F>(&mut self, hook: Option<F>)
where
F: FnMut(Action, &str, &str, i64) + Send + 'static,
{
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void,
action_code: c_int,
db_str: *const c_char,
tbl_str: *const c_char,
row_id: i64)
where F: FnMut(Action, &str, &str, i64)
unsafe extern "C" fn call_boxed_closure<F>(
p_arg: *mut c_void,
action_code: c_int,
db_str: *const c_char,
tbl_str: *const c_char,
row_id: i64,
) where
F: FnMut(Action, &str, &str, i64),
{
use std::ffi::CStr;
use std::str;
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 db_name = {
@ -225,84 +250,104 @@ impl InnerConnection {
(*boxed_hook)(action, db_name, tbl_name, row_id);
}
let previous_hook = {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe {
ffi::sqlite3_update_hook(self.db(),
Some(call_boxed_closure::<F>),
boxed_hook as *mut _)
}
let free_update_hook = if hook.is_some() {
Some(free_boxed_hook::<F> as fn(*mut c_void))
} else {
None
};
free_boxed_hook(previous_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);
let previous_hook = match hook {
Some(hook) => {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe {
ffi::sqlite3_update_hook(
self.db(),
Some(call_boxed_closure::<F>),
boxed_hook as *mut _,
)
}
}
_ => unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) },
};
if !previous_hook.is_null() {
if let Some(free_boxed_hook) = self.free_update_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)]
mod test {
use super::Action;
use std::sync::atomic::{AtomicBool, Ordering};
use Connection;
#[test]
fn test_commit_hook() {
let db = Connection::open_in_memory().unwrap();
let mut called = false;
db.commit_hook(|| {
called = true;
false
});
lazy_static! {
static ref called: AtomicBool = AtomicBool::new(false);
}
db.commit_hook(Some(|| {
called.store(true, Ordering::Relaxed);
false
}));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
.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]
fn test_rollback_hook() {
let db = Connection::open_in_memory().unwrap();
let mut called = false;
db.rollback_hook(|| { called = true; });
lazy_static! {
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;")
.unwrap();
assert!(called);
assert!(called.load(Ordering::Relaxed));
}
#[test]
fn test_update_hook() {
let db = Connection::open_in_memory().unwrap();
let mut called = false;
db.update_hook(|action, db, tbl, row_id| {
assert_eq!(Action::SQLITE_INSERT, action);
assert_eq!("main", db);
assert_eq!("foo", tbl);
assert_eq!(1, row_id);
called = true;
});
lazy_static! {
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!("main", db);
assert_eq!("foo", tbl);
assert_eq!(1, row_id);
called.store(true, Ordering::Relaxed);
}));
db.execute_batch("CREATE TABLE foo (t TEXT)").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;
#[macro_use]
extern crate bitflags;
#[cfg(all(test, feature = "trace"))]
#[cfg(any(test, feature = "vtab"))]
#[macro_use]
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::convert;
use std::default::Default;
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::str;
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_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 error::{error_from_handle, error_from_sqlite_code};
use raw_statement::RawStatement;
use types::{ToSql, ValueRef};
pub use statement::Statement;
use statement::StatementCrateImpl;
pub use row::{Row, Rows, MappedRows, AndThenRows, RowIndex};
use row::RowsCrateImpl;
pub use row::{AndThenRows, MappedRows, Row, RowIndex, Rows};
pub use transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
#[allow(deprecated)]
pub use transaction::{SqliteTransaction, SqliteTransactionBehavior};
pub use transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
pub use error::Error;
#[allow(deprecated)]
pub use error::SqliteError;
pub use error::Error;
pub use ffi::ErrorCode;
pub use cache::CachedStatement;
pub use version::*;
#[cfg(feature = "hooks")]
pub use hooks::*;
#[cfg(feature = "load_extension")]
#[allow(deprecated)]
pub use load_extension_guard::{SqliteLoadExtensionGuard, LoadExtensionGuard};
pub use load_extension_guard::{LoadExtensionGuard, SqliteLoadExtensionGuard};
pub mod types;
mod version;
mod transaction;
#[cfg(feature = "backup")]
pub mod backup;
#[cfg(feature = "blob")]
pub mod blob;
mod busy;
mod cache;
#[cfg(any(feature = "functions", feature = "vtab"))]
mod context;
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 row;
mod statement;
#[cfg(feature = "load_extension")]
mod load_extension_guard;
#[cfg(feature = "trace")]
pub mod trace;
#[cfg(feature = "backup")]
pub mod backup;
#[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 transaction;
pub mod types;
mod unlock_notify;
mod version;
#[cfg(feature = "vtab")]
pub mod vtab;
// Number of cached prepared statements we'll hold on to.
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.
#[derive(Copy, Clone)]
pub enum DatabaseName<'a> {
/// The main database.
Main,
@ -168,7 +173,7 @@ pub enum DatabaseName<'a> {
#[cfg(any(feature = "backup", feature = "blob"))]
impl<'a> DatabaseName<'a> {
fn to_cstring(&self) -> Result<CString> {
use self::DatabaseName::{Main, Temp, Attached};
use self::DatabaseName::{Attached, Main, Temp};
match *self {
Main => str_to_cstring("main"),
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
/// underlying SQLite open call fails.
pub fn open<P: AsRef<Path>>(path: P) -> Result<Connection> {
let flags = Default::default();
let flags = OpenFlags::default();
Connection::open_with_flags(path, flags)
}
@ -217,7 +222,7 @@ impl Connection {
///
/// Will return `Err` if the underlying SQLite open call fails.
pub fn open_in_memory() -> Result<Connection> {
let flags = Default::default();
let flags = OpenFlags::default();
Connection::open_in_memory_with_flags(flags)
}
@ -232,13 +237,11 @@ impl Connection {
/// underlying SQLite open call fails.
pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: OpenFlags) -> Result<Connection> {
let c_path = try!(path_to_cstring(path.as_ref()));
InnerConnection::open_with_flags(&c_path, flags).map(|db| {
Connection {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
path: Some(path.as_ref().to_path_buf()),
}
})
InnerConnection::open_with_flags(&c_path, flags).map(|db| Connection {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
path: Some(path.as_ref().to_path_buf()),
})
}
/// Open a new connection to an in-memory SQLite database.
@ -251,13 +254,11 @@ impl Connection {
/// Will return `Err` if the underlying SQLite open call fails.
pub fn open_in_memory_with_flags(flags: OpenFlags) -> Result<Connection> {
let c_memory = try!(str_to_cstring(":memory:"));
InnerConnection::open_with_flags(&c_memory, flags).map(|db| {
Connection {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
path: None,
}
})
InnerConnection::open_with_flags(&c_memory, flags).map(|db| Connection {
db: RefCell::new(db),
cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
path: None,
})
}
/// Convenience method to run multiple SQL statements (that cannot take any parameters).
@ -305,9 +306,8 @@ impl Connection {
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn execute(&self, sql: &str, params: &[&ToSql]) -> Result<c_int> {
self.prepare(sql)
.and_then(|mut stmt| stmt.execute(params))
pub fn execute(&self, sql: &str, params: &[&ToSql]) -> Result<usize> {
self.prepare(sql).and_then(|mut stmt| stmt.execute(params))
}
/// Convenience method to prepare and execute a single SQL statement with named parameter(s).
@ -319,7 +319,7 @@ impl Connection {
///
/// ```rust,no_run
/// # 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")])
/// }
/// ```
@ -328,7 +328,7 @@ impl Connection {
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn execute_named(&self, sql: &str, params: &[(&str, &ToSql)]) -> Result<c_int> {
pub fn execute_named(&self, sql: &str, params: &[(&str, &ToSql)]) -> Result<usize> {
self.prepare(sql)
.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
/// underlying SQLite call fails.
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));
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
/// underlying SQLite call fails.
pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &ToSql)], f: F) -> Result<T>
where F: FnOnce(&Row) -> T
where
F: FnOnce(&Row) -> T,
{
let mut stmt = try!(self.prepare(sql));
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
/// underlying SQLite call fails.
pub fn query_row_and_then<T, E, F>(&self,
sql: &str,
params: &[&ToSql],
f: F)
-> result::Result<T, E>
where F: FnOnce(&Row) -> result::Result<T, E>,
E: convert::From<Error>
pub fn query_row_and_then<T, E, F>(
&self,
sql: &str,
params: &[&ToSql],
f: F,
) -> result::Result<T, E>
where
F: FnOnce(&Row) -> result::Result<T, E>,
E: convert::From<Error>,
{
let mut stmt = try!(self.prepare(sql));
let mut rows = try!(stmt.query(params));
rows.get_expected_row()
.map_err(E::from)
.and_then(|r| f(&r))
rows.get_expected_row().map_err(E::from).and_then(|r| f(&r))
}
/// 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.
#[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>
where F: FnOnce(&Row) -> T
where
F: FnOnce(&Row) -> T,
{
self.query_row(sql, params, f)
}
@ -545,10 +548,11 @@ impl Connection {
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")]
pub fn load_extension<P: AsRef<Path>>(&self,
dylib_path: P,
entry_point: Option<&str>)
-> Result<()> {
pub fn load_extension<P: AsRef<Path>>(
&self,
dylib_path: P,
entry_point: Option<&str>,
) -> Result<()> {
self.db
.borrow_mut()
.load_extension(dylib_path.as_ref(), entry_point)
@ -570,9 +574,21 @@ impl Connection {
self.db.borrow_mut().decode_result(code)
}
fn changes(&self) -> c_int {
fn changes(&self) -> usize {
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 {
@ -585,6 +601,12 @@ impl fmt::Debug for Connection {
struct InnerConnection {
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.
@ -610,8 +632,10 @@ bitflags! {
impl Default for OpenFlags {
fn default() -> OpenFlags {
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE |
OpenFlags::SQLITE_OPEN_NO_MUTEX | OpenFlags::SQLITE_OPEN_URI
OpenFlags::SQLITE_OPEN_READ_WRITE
| 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 runtime_major = version_number / 1_000_000;
if buildtime_major != runtime_major {
panic!("rusqlite was built against SQLite {} but is running with SQLite {}",
str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
version());
panic!(
"rusqlite was built against SQLite {} but is running with SQLite {}",
str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
version()
);
}
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
// build-time.
if version_number < ffi::SQLITE_VERSION_NUMBER {
panic!("\
panic!(
"\
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
* 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
the version we found at build time.",
str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
version());
str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
version()
);
}
});
}
@ -743,6 +771,20 @@ To fix this, either:
}
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> {
ensure_valid_sqlite_version();
ensure_safe_sqlite_threading_mode()?;
@ -751,9 +793,15 @@ impl InnerConnection {
// 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_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 {
return Err(Error::SqliteFailure(ffi::Error::new(ffi::SQLITE_MISUSE), None));
return Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_MISUSE),
None,
));
}
unsafe {
@ -780,7 +828,7 @@ impl InnerConnection {
// attempt to turn on extended results code; don't fail if we can't.
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<()> {
let c_sql = try!(str_to_cstring(sql));
unsafe {
let r = ffi::sqlite3_exec(self.db(),
c_sql.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut());
let r = ffi::sqlite3_exec(
self.db(),
c_sql.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
self.decode_result(r)
}
}
@ -836,10 +886,12 @@ impl InnerConnection {
let mut errmsg: *mut c_char = mem::uninitialized();
let r = if let Some(entry_point) = entry_point {
let c_entry = try!(str_to_cstring(entry_point));
ffi::sqlite3_load_extension(self.db,
dylib_str.as_ptr(),
c_entry.as_ptr(),
&mut errmsg)
ffi::sqlite3_load_extension(
self.db,
dylib_str.as_ptr(),
c_entry.as_ptr(),
&mut errmsg,
)
} else {
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(|_| {
Statement::new(conn, RawStatement::new(c_stmt))
})
self.decode_result(r)
.map(|_| Statement::new(conn, RawStatement::new(c_stmt)))
}
fn changes(&mut self) -> c_int {
unsafe { ffi::sqlite3_changes(self.db()) }
fn changes(&mut self) -> usize {
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"))]
fn remove_hooks(&mut self) {
}
fn remove_hooks(&mut self) {}
}
impl Drop for InnerConnection {
@ -931,18 +1000,16 @@ pub type SqliteStatement<'conn> = Statement<'conn>;
#[deprecated(since = "0.6.0", note = "Use Rows instead")]
pub type SqliteRows<'stmt> = Rows<'stmt>;
/// Old name for `Row`. `SqliteRow` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Row instead")]
pub type SqliteRow<'a, 'stmt> = Row<'a, 'stmt>;
#[cfg(test)]
mod test {
extern crate tempdir;
use self::tempdir::TempDir;
pub use super::*;
use ffi;
use self::tempdir::TempDir;
pub use std::error::Error as StdError;
pub use std::fmt;
@ -959,7 +1026,53 @@ mod 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() {
let temp_dir = TempDir::new("test_open_file").unwrap();
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
// level to allow us to call .close() without dropping the prepared statement first.
let raw_stmt = {
use std::mem;
use std::ptr;
use std::os::raw::c_int;
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 sql = "SELECT 1";
let mut raw_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let rc = unsafe {
ffi::sqlite3_prepare_v2(raw_db,
str_to_cstring(sql).unwrap().as_ptr(),
(sql.len() + 1) as c_int,
&mut raw_stmt,
ptr::null_mut())
ffi::sqlite3_prepare_v2(
raw_db,
str_to_cstring(sql).unwrap().as_ptr(),
(sql.len() + 1) as c_int,
&mut raw_stmt,
ptr::null_mut(),
)
};
assert_eq!(rc, ffi::SQLITE_OK);
raw_stmt
@ -1027,15 +1142,16 @@ mod test {
#[test]
fn test_open_with_flags() {
for bad_flags in &[OpenFlags::empty(),
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_READ_WRITE,
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_CREATE] {
for bad_flags in &[
OpenFlags::empty(),
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_READ_WRITE,
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_CREATE,
] {
assert!(Connection::open_in_memory_with_flags(*bad_flags).is_err());
}
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_execute_batch() {
let db = checked_memory_handle();
let sql = "BEGIN;
@ -1047,7 +1163,8 @@ mod test {
END;";
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());
}
@ -1057,16 +1174,22 @@ mod test {
let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
assert_eq!(1,
db.execute("INSERT INTO foo(x) VALUES (?)", &[&1i32])
.unwrap());
assert_eq!(1,
db.execute("INSERT INTO foo(x) VALUES (?)", &[&2i32])
.unwrap());
assert_eq!(
1,
db.execute("INSERT INTO foo(x) VALUES (?)", &[&1i32])
.unwrap()
);
assert_eq!(
1,
db.execute("INSERT INTO foo(x) VALUES (?)", &[&2i32])
.unwrap()
);
assert_eq!(3i32,
db.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap());
assert_eq!(
3i32,
db.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()
);
}
#[test]
@ -1123,7 +1246,8 @@ mod test {
assert_eq!(insert_stmt.execute(&[&2i32]).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();
{
let mut rows = query.query(&[&4i32]).unwrap();
@ -1149,7 +1273,6 @@ mod test {
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_map() {
let db = checked_memory_handle();
let sql = "BEGIN;
@ -1162,15 +1285,13 @@ mod test {
db.execute_batch(sql).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))
.unwrap()
.collect();
let results: Result<Vec<String>> =
query.query_map(&[], |row| row.get(1)).unwrap().collect();
assert_eq!(results.unwrap().concat(), "hello, world!");
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_row() {
let db = checked_memory_handle();
let sql = "BEGIN;
@ -1182,9 +1303,11 @@ mod test {
END;";
db.execute_batch(sql).unwrap();
assert_eq!(10i64,
db.query_row::<i64, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap());
assert_eq!(
10i64,
db.query_row::<i64, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()
);
let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", &[], |r| r.get(0));
match result.unwrap_err() {
@ -1211,8 +1334,7 @@ mod test {
let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")
.unwrap();
db.execute_batch("INSERT INTO foo DEFAULT VALUES")
.unwrap();
db.execute_batch("INSERT INTO foo DEFAULT VALUES").unwrap();
assert_eq!(db.last_insert_rowid(), 1);
@ -1223,6 +1345,32 @@ mod test {
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]
fn test_statement_debugging() {
let db = checked_memory_handle();
@ -1308,7 +1456,6 @@ mod test {
type CustomResult<T> = ::std::result::Result<T, CustomError>;
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then() {
let db = checked_memory_handle();
let sql = "BEGIN;
@ -1321,8 +1468,8 @@ mod test {
db.execute_batch(sql).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(&[],
|row| row.get_checked(1))
let results: Result<Vec<String>> = query
.query_and_then(&[], |row| row.get_checked(1))
.unwrap()
.collect();
@ -1330,7 +1477,6 @@ mod test {
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then_fails() {
let db = checked_memory_handle();
let sql = "BEGIN;
@ -1343,7 +1489,8 @@ mod test {
db.execute_batch(sql).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()
.collect();
@ -1352,7 +1499,8 @@ mod test {
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()
.collect();
@ -1363,7 +1511,6 @@ mod test {
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then_custom_error() {
let db = checked_memory_handle();
let sql = "BEGIN;
@ -1376,18 +1523,15 @@ mod test {
db.execute_batch(sql).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| {
row.get_checked(1)
.map_err(CustomError::Sqlite)
})
.unwrap()
let results: CustomResult<Vec<String>> = query
.query_and_then(&[], |row| row.get_checked(1).map_err(CustomError::Sqlite))
.unwrap()
.collect();
assert_eq!(results.unwrap().concat(), "hello, world!");
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_and_then_custom_error_fails() {
let db = checked_memory_handle();
let sql = "BEGIN;
@ -1400,11 +1544,9 @@ mod test {
db.execute_batch(sql).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| {
row.get_checked(1)
.map_err(CustomError::Sqlite)
})
.unwrap()
let bad_type: CustomResult<Vec<f64>> = query
.query_and_then(&[], |row| row.get_checked(1).map_err(CustomError::Sqlite))
.unwrap()
.collect();
match bad_type.unwrap_err() {
@ -1412,11 +1554,9 @@ mod test {
err => panic!("Unexpected error {}", err),
}
let bad_idx: CustomResult<Vec<String>> = query.query_and_then(&[], |row| {
row.get_checked(3)
.map_err(CustomError::Sqlite)
})
.unwrap()
let bad_idx: CustomResult<Vec<String>> = query
.query_and_then(&[], |row| row.get_checked(3).map_err(CustomError::Sqlite))
.unwrap()
.collect();
match bad_idx.unwrap_err() {
@ -1424,10 +1564,9 @@ mod test {
err => panic!("Unexpected error {}", err),
}
let non_sqlite_err: CustomResult<Vec<String>> = query.query_and_then(&[], |_| {
Err(CustomError::SomeError)
})
.unwrap()
let non_sqlite_err: CustomResult<Vec<String>> = query
.query_and_then(&[], |_| Err(CustomError::SomeError))
.unwrap()
.collect();
match non_sqlite_err.unwrap_err() {
@ -1437,7 +1576,6 @@ mod test {
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_row_and_then_custom_error() {
let db = checked_memory_handle();
let sql = "BEGIN;
@ -1455,7 +1593,6 @@ mod test {
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_query_row_and_then_custom_error_fails() {
let db = checked_memory_handle();
let sql = "BEGIN;
@ -1483,9 +1620,8 @@ mod test {
err => panic!("Unexpected error {}", err),
}
let non_sqlite_err: CustomResult<String> = db.query_row_and_then(query, &[], |_| {
Err(CustomError::SomeError)
});
let non_sqlite_err: CustomResult<String> =
db.query_row_and_then(query, &[], |_| Err(CustomError::SomeError));
match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (),
@ -1494,7 +1630,6 @@ mod test {
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_dynamic() {
let db = checked_memory_handle();
let sql = "BEGIN;
@ -1503,7 +1638,9 @@ mod test {
END;";
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.
#[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::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.
#[derive(Debug)]
@ -17,16 +17,16 @@ impl RawStatement {
self.0
}
pub fn column_count(&self) -> c_int {
unsafe { ffi::sqlite3_column_count(self.0) }
pub fn column_count(&self) -> usize {
unsafe { ffi::sqlite3_column_count(self.0) as usize }
}
pub fn column_type(&self, idx: c_int) -> c_int {
unsafe { ffi::sqlite3_column_type(self.0, idx) }
pub fn column_type(&self, idx: usize) -> c_int {
unsafe { ffi::sqlite3_column_type(self.0, idx as c_int) }
}
pub fn column_name(&self, idx: c_int) -> &CStr {
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx)) }
pub fn column_name(&self, idx: usize) -> &CStr {
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx as c_int)) }
}
pub fn step(&self) -> c_int {
@ -54,15 +54,15 @@ impl RawStatement {
unsafe { ffi::sqlite3_reset(self.0) }
}
pub fn bind_parameter_count(&self) -> c_int {
unsafe { ffi::sqlite3_bind_parameter_count(self.0) }
pub fn bind_parameter_count(&self) -> usize {
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()) };
match r {
0 => None,
i => Some(i),
i => Some(i as usize),
}
}
@ -83,6 +83,23 @@ impl RawStatement {
self.0 = ptr::null_mut();
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 {

View File

@ -1,9 +1,8 @@
use std::{convert, result};
use std::marker::PhantomData;
use std::{convert, result};
use super::{Statement, Error, Result};
use super::{Error, Result, Statement};
use types::{FromSql, FromSqlError};
use statement::StatementCrateImpl;
/// An handle for the resulting rows of a query.
pub struct Rows<'stmt> {
@ -27,40 +26,31 @@ impl<'stmt> Rows<'stmt> {
/// 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`
/// 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>>> {
self.stmt
.and_then(|stmt| match stmt.step() {
Ok(true) => {
Some(Ok(Row {
stmt,
phantom: PhantomData,
}))
}
Ok(false) => {
self.reset();
None
}
Err(err) => {
self.reset();
Some(Err(err))
}
})
self.stmt.and_then(|stmt| match stmt.step() {
Ok(true) => Some(Ok(Row {
stmt,
phantom: PhantomData,
})),
Ok(false) => {
self.reset();
None
}
Err(err) => {
self.reset();
Some(Err(err))
}
})
}
}
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this
// once pub(crate) is stable.
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> {
impl<'stmt> Rows<'stmt> {
pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'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() {
Some(row) => row,
None => Err(Error::QueryReturnedNoRows),
@ -80,22 +70,18 @@ pub struct MappedRows<'stmt, F> {
map: F,
}
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this
// once pub(crate) is stable.
pub trait MappedRowsCrateImpl<'stmt, T, F> {
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
impl<'stmt, T, F> 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 }
}
}
impl<'conn, T, F> Iterator for MappedRows<'conn, F>
where F: FnMut(&Row) -> T
where
F: FnMut(&Row) -> T,
{
type Item = Result<T>;
@ -114,23 +100,19 @@ pub struct AndThenRows<'stmt, F> {
map: F,
}
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this
// once pub(crate) is stable.
pub trait AndThenRowsCrateImpl<'stmt, T, E, F> {
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>
impl<'stmt, T, E, F> 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 }
}
}
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
where E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>
where
E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>,
{
type Item = result::Result<T, E>;
@ -178,21 +160,16 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
let idx = try!(idx.idx(self.stmt));
let value = self.stmt.value_ref(idx);
FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => {
Error::InvalidColumnType(idx,
value.data_type())
}
FromSqlError::OutOfRange(i) => {
Error::IntegralValueOutOfRange(idx, i)
}
FromSqlError::Other(err) => {
FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
}
})
})
}
/// 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()
}
}
@ -201,13 +178,13 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
pub trait RowIndex {
/// Returns the index of the appropriate column, or `None` if no such
/// 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]
fn idx(&self, stmt: &Statement) -> Result<i32> {
if *self < 0 || *self >= stmt.column_count() {
fn idx(&self, stmt: &Statement) -> Result<usize> {
if *self >= stmt.column_count() {
Err(Error::InvalidColumnIndex(*self))
} else {
Ok(*self)
@ -217,7 +194,7 @@ impl RowIndex for i32 {
impl<'a> RowIndex for &'a str {
#[inline]
fn idx(&self, stmt: &Statement) -> Result<i32> {
fn idx(&self, stmt: &Statement) -> Result<usize> {
stmt.column_index(*self)
}
}

View File

@ -1,13 +1,18 @@
use std::{convert, fmt, mem, ptr, result, str};
use std::ffi::CStr;
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::{convert, fmt, mem, ptr, result, str};
use super::ffi;
use super::{Connection, RawStatement, Result, Error, ValueRef, Row, Rows, AndThenRows, MappedRows};
use super::str_to_cstring;
use super::{
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
};
use types::{ToSql, ToSqlOutput};
use row::{RowsCrateImpl, MappedRowsCrateImpl, AndThenRowsCrateImpl};
#[cfg(feature = "array")]
use vtab::array::{free_array, ARRAY_TYPE};
/// A prepared statement.
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.
pub fn column_count(&self) -> i32 {
pub fn column_count(&self) -> usize {
self.stmt.column_count()
}
@ -41,7 +46,7 @@ impl<'conn> Statement<'conn> {
/// # Failure
///
/// 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 n = self.column_count();
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
/// 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));
self.execute_with_bound_parameters()
}
@ -92,7 +97,7 @@ impl<'conn> Statement<'conn> {
///
/// ```rust,no_run
/// # 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)"));
/// 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
/// 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));
self.execute_with_bound_parameters()
}
@ -155,6 +160,7 @@ impl<'conn> Statement<'conn> {
///
/// Will return `Err` if binding parameters fails.
pub fn query<'a>(&'a mut self, params: &[&ToSql]) -> Result<Rows<'a>> {
try!(self.check_readonly());
try!(self.bind_parameters(params));
Ok(Rows::new(self))
}
@ -182,6 +188,7 @@ impl<'conn> Statement<'conn> {
///
/// Will return `Err` if binding parameters fails.
pub fn query_named<'a>(&'a mut self, params: &[(&str, &ToSql)]) -> Result<Rows<'a>> {
try!(self.check_readonly());
try!(self.bind_parameters_named(params));
Ok(Rows::new(self))
}
@ -210,7 +217,8 @@ impl<'conn> Statement<'conn> {
///
/// 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>>
where F: FnMut(&Row) -> T
where
F: FnMut(&Row) -> T,
{
let rows = self.query(params)?;
Ok(MappedRows::new(rows, f))
@ -242,11 +250,13 @@ impl<'conn> Statement<'conn> {
/// ## Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_map_named<'a, T, F>(&'a mut self,
params: &[(&str, &ToSql)],
f: F)
-> Result<MappedRows<'a, F>>
where F: FnMut(&Row) -> T
pub fn query_map_named<'a, T, F>(
&'a mut self,
params: &[(&str, &ToSql)],
f: F,
) -> Result<MappedRows<'a, F>>
where
F: FnMut(&Row) -> T,
{
let rows = self.query_named(params)?;
Ok(MappedRows::new(rows, f))
@ -259,12 +269,14 @@ impl<'conn> Statement<'conn> {
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_and_then<'a, T, E, F>(&'a mut self,
params: &[&ToSql],
f: F)
-> Result<AndThenRows<'a, F>>
where E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>
pub fn query_and_then<'a, T, E, F>(
&'a mut self,
params: &[&ToSql],
f: F,
) -> Result<AndThenRows<'a, F>>
where
E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>,
{
let rows = self.query(params)?;
Ok(AndThenRows::new(rows, f))
@ -305,12 +317,14 @@ impl<'conn> Statement<'conn> {
/// ## Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_and_then_named<'a, T, E, F>(&'a mut self,
params: &[(&str, &ToSql)],
f: F)
-> Result<AndThenRows<'a, F>>
where E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>
pub fn query_and_then_named<'a, T, E, F>(
&'a mut self,
params: &[(&str, &ToSql)],
f: F,
) -> Result<AndThenRows<'a, F>>
where
E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>,
{
let rows = self.query_named(params)?;
Ok(AndThenRows::new(rows, f))
@ -337,7 +351,8 @@ impl<'conn> Statement<'conn> {
///
/// Will return `Err` if the underlying SQLite call fails.
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));
@ -362,19 +377,22 @@ impl<'conn> Statement<'conn> {
///
/// Will return Err if `name` is invalid. Will return Ok(None) if the name
/// is valid but not a bound parameter of this statement.
pub fn parameter_index(&self, name: &str) -> Result<Option<i32>> {
pub fn parameter_index(&self, name: &str) -> Result<Option<usize>> {
let c_name = try!(str_to_cstring(name));
Ok(self.stmt.bind_parameter_index(&c_name))
}
fn bind_parameters(&mut self, params: &[&ToSql]) -> Result<()> {
assert_eq!(params.len() as c_int, self.stmt.bind_parameter_count(),
"incorrect number of parameters to query(): expected {}, got {}",
self.stmt.bind_parameter_count(),
params.len());
assert_eq!(
params.len(),
self.stmt.bind_parameter_count(),
"incorrect number of parameters to query(): expected {}, got {}",
self.stmt.bind_parameter_count(),
params.len()
);
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(())
@ -391,7 +409,7 @@ impl<'conn> Statement<'conn> {
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 ptr = unsafe { self.stmt.ptr() };
@ -401,20 +419,28 @@ impl<'conn> Statement<'conn> {
#[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => {
return self.conn
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col, len) });
return self
.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
.decode_result(match value {
ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, col) },
ValueRef::Integer(i) => unsafe {
ffi::sqlite3_bind_int64(ptr, col, i)
},
ValueRef::Real(r) => unsafe {
ffi::sqlite3_bind_double(ptr, col, r)
},
ValueRef::Text(s) => unsafe {
self.conn.decode_result(match value {
ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, col as c_int) },
ValueRef::Integer(i) => unsafe { ffi::sqlite3_bind_int64(ptr, col as c_int, i) },
ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
ValueRef::Text(s) => unsafe {
let length = s.len();
if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG
@ -425,27 +451,35 @@ impl<'conn> Statement<'conn> {
} else {
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 {
let length = b.len();
if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG
} else if length == 0 {
ffi::sqlite3_bind_zeroblob(ptr, col, 0)
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
} else {
ffi::sqlite3_bind_blob(ptr,
col,
b.as_ptr() as *const c_void,
length as c_int,
ffi::SQLITE_TRANSIENT())
ffi::sqlite3_bind_blob(
ptr,
col as c_int,
b.as_ptr() as *const c_void,
length as c_int,
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();
self.stmt.reset();
match r {
@ -466,6 +500,29 @@ impl<'conn> Statement<'conn> {
mem::swap(&mut stmt, &mut self.stmt);
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> {
@ -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
// once pub(crate) is stable.
pub trait StatementCrateImpl<'conn> {
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> {
fn new(conn: &Connection, stmt: RawStatement) -> Statement {
Statement {
conn,
stmt,
}
impl<'conn> Statement<'conn> {
pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement {
Statement { conn, stmt }
}
fn value_ref(&self, col: c_int) -> ValueRef {
pub(crate) fn value_ref(&self, col: usize) -> ValueRef {
let raw = unsafe { self.stmt.ptr() };
match self.stmt.column_type(col) {
ffi::SQLITE_NULL => ValueRef::Null,
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 => {
let s = unsafe {
let text = ffi::sqlite3_column_text(raw, col);
assert!(!text.is_null(),
"unexpected SQLITE_TEXT column type with NULL data");
let text = ffi::sqlite3_column_text(raw, col as c_int);
assert!(
!text.is_null(),
"unexpected SQLITE_TEXT column type with NULL data"
);
CStr::from_ptr(text as *const c_char)
};
// 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");
ValueRef::Text(s)
}
ffi::SQLITE_BLOB => {
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,
"unexpected negative return from sqlite3_column_bytes");
assert!(
len >= 0,
"unexpected negative return from sqlite3_column_bytes"
);
if len > 0 {
assert!(!blob.is_null(),
"unexpected SQLITE_BLOB column type with NULL data");
assert!(
!blob.is_null(),
"unexpected SQLITE_BLOB column type with NULL data"
);
ValueRef::Blob(unsafe { from_raw_parts(blob as *const u8, len as usize) })
} else {
// 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() {
ffi::SQLITE_ROW => Ok(true),
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()
}
}
@ -576,18 +633,25 @@ mod test {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])
.unwrap(),
1);
assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)])
.unwrap(),
1);
assert_eq!(
db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])
.unwrap(),
1
);
assert_eq!(
db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)])
.unwrap(),
1
);
assert_eq!(3i32,
db.query_row_named::<i32, _>("SELECT SUM(x) FROM foo WHERE x > :x",
&[(":x", &0i32)],
|r| r.get(0))
.unwrap());
assert_eq!(
3i32,
db.query_row_named::<i32, _>(
"SELECT SUM(x) FROM foo WHERE x > :x",
&[(":x", &0i32)],
|r| r.get(0)
).unwrap()
);
}
#[test]
@ -597,15 +661,19 @@ mod test {
INTEGER)";
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();
stmt.execute_named(&[(":name", &"one")]).unwrap();
assert_eq!(1i32,
db.query_row_named::<i32, _>("SELECT COUNT(*) FROM test WHERE name = :name",
&[(":name", &"one")],
|r| r.get(0))
.unwrap());
assert_eq!(
1i32,
db.query_row_named::<i32, _>(
"SELECT COUNT(*) FROM test WHERE name = :name",
&[(":name", &"one")],
|r| r.get(0)
).unwrap()
);
}
#[test]
@ -617,7 +685,8 @@ mod test {
"#;
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();
let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap();
@ -634,13 +703,14 @@ mod test {
"#;
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();
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);
2 * id
})
.unwrap();
}).unwrap();
let doubled_id: i32 = rows.next().unwrap().unwrap();
assert_eq!(2, doubled_id);
@ -648,7 +718,6 @@ mod test {
#[test]
fn test_query_and_then_named() {
let db = Connection::open_in_memory().unwrap();
let sql = r#"
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();
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();
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);
if id == 1 {
Ok(id)
} else {
Err(Error::SqliteSingleThreadedMode)
}
})
.unwrap();
}).unwrap();
// first row should be Ok
let doubled_id: i32 = rows.next().unwrap().unwrap();
@ -687,13 +757,14 @@ mod test {
let sql = "CREATE TABLE test (x TEXT, y TEXT)";
db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
let mut stmt = db
.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
.unwrap();
stmt.execute_named(&[(":x", &"one")]).unwrap();
let result: Option<String> =
db.query_row("SELECT y FROM test WHERE x = 'one'", &[], |row| row.get(0))
.unwrap();
let result: Option<String> = db
.query_row("SELECT y FROM test WHERE x = 'one'", &[], |row| row.get(0))
.unwrap();
assert!(result.is_none());
}
@ -703,14 +774,15 @@ mod test {
let sql = "CREATE TABLE test (x TEXT, y TEXT)";
db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
let mut stmt = db
.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
.unwrap();
stmt.execute_named(&[(":x", &"one")]).unwrap();
stmt.execute_named(&[(":y", &"two")]).unwrap();
let result: String =
db.query_row("SELECT x FROM test WHERE y = 'two'", &[], |row| row.get(0))
.unwrap();
let result: String = db
.query_row("SELECT x FROM test WHERE y = 'two'", &[], |row| row.get(0))
.unwrap();
assert_eq!(result, "one");
}
@ -719,7 +791,8 @@ mod test {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)")
.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();
assert_eq!(stmt.insert(&[&1i32]).unwrap(), 1);
assert_eq!(stmt.insert(&[&2i32]).unwrap(), 2);
@ -727,7 +800,8 @@ mod test {
Error::StatementChangedRows(0) => (),
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();
match multi.insert(&[]).unwrap_err() {
Error::StatementChangedRows(2) => (),
@ -739,22 +813,27 @@ mod test {
fn test_insert_different_tables() {
// Test for https://github.com/jgallagher/rusqlite/issues/171
let db = Connection::open_in_memory().unwrap();
db.execute_batch(r"
db.execute_batch(
r"
CREATE TABLE foo(x INTEGER);
CREATE TABLE bar(x INTEGER);
")
.unwrap();
",
).unwrap();
assert_eq!(db.prepare("INSERT INTO foo VALUES (10)")
.unwrap()
.insert(&[])
.unwrap(),
1);
assert_eq!(db.prepare("INSERT INTO bar VALUES (10)")
.unwrap()
.insert(&[])
.unwrap(),
1);
assert_eq!(
db.prepare("INSERT INTO foo VALUES (10)")
.unwrap()
.insert(&[])
.unwrap(),
1
);
assert_eq!(
db.prepare("INSERT INTO bar VALUES (10)")
.unwrap()
.insert(&[])
.unwrap(),
1
);
}
#[test]
@ -811,4 +890,13 @@ mod test {
let y: Result<i64> = stmt.query_row(&[], |r| r.get("y"));
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.
use std::os::raw::{c_char, c_int, c_void};
use std::ffi::{CStr, CString};
use std::mem;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::time::Duration;
use super::ffi;
use {Result, Connection};
use error::error_from_sqlite_code;
use {Connection, Result};
/// Set up the process-wide SQLite error logging callback.
/// 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 {
Some(f) => {
let p_arg: *mut c_void = mem::transmute(f);
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG,
log_callback as extern "C" fn(_, _, _),
p_arg)
ffi::sqlite3_config(
ffi::SQLITE_CONFIG_LOG,
log_callback as extern "C" fn(_, _, _),
p_arg,
)
}
None => {
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.
/// Setting a new profiler clears the old one.
pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
unsafe extern "C" fn profile_callback(p_arg: *mut c_void,
z_sql: *const c_char,
nanoseconds: u64) {
unsafe extern "C" fn profile_callback(
p_arg: *mut c_void,
z_sql: *const c_char,
nanoseconds: u64,
) {
let profile_fn: fn(&str, Duration) = mem::transmute(p_arg);
let c_slice = CStr::from_ptr(z_sql).to_bytes();
let s = String::from_utf8_lossy(c_slice);
const NANOS_PER_SEC: u64 = 1_000_000_000;
let duration = Duration::new(nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32);
let duration = Duration::new(
nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32,
);
profile_fn(&s, duration);
}

View File

@ -1,5 +1,5 @@
use std::ops::Deref;
use {Result, Connection};
use {Connection, Result};
/// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is deprecated.
#[deprecated(since = "0.6.0", note = "Use TransactionBehavior instead")]
@ -7,7 +7,7 @@ pub type SqliteTransactionBehavior = TransactionBehavior;
/// Options for transaction behavior. See [BEGIN
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
#[derive(Copy,Clone)]
#[derive(Copy, Clone)]
pub enum TransactionBehavior {
Deferred,
Immediate,
@ -15,7 +15,7 @@ pub enum TransactionBehavior {
}
/// Options for how a Transaction or Savepoint should behave when it is dropped.
#[derive(Copy,Clone,PartialEq,Eq)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum DropBehavior {
/// Roll back the changes. This is the default.
Rollback,
@ -61,7 +61,6 @@ pub type SqliteTransaction<'conn> = Transaction<'conn>;
pub struct Transaction<'conn> {
conn: &'conn Connection,
drop_behavior: DropBehavior,
committed: bool,
}
/// Represents a savepoint on a database connection.
@ -106,14 +105,10 @@ impl<'conn> Transaction<'conn> {
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
};
conn.execute_batch(query)
.map(move |_| {
Transaction {
conn,
drop_behavior: DropBehavior::Rollback,
committed: false,
}
})
conn.execute_batch(query).map(move |_| Transaction {
conn,
drop_behavior: DropBehavior::Rollback,
})
}
/// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested
@ -167,8 +162,8 @@ impl<'conn> Transaction<'conn> {
}
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.
@ -177,8 +172,8 @@ impl<'conn> Transaction<'conn> {
}
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
@ -191,11 +186,11 @@ impl<'conn> Transaction<'conn> {
}
fn finish_(&mut self) -> Result<()> {
if self.committed {
if self.conn.is_autocommit() {
return Ok(());
}
match self.drop_behavior() {
DropBehavior::Commit => self.commit_(),
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()),
DropBehavior::Rollback => self.rollback_(),
DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Transaction dropped unexpectedly."),
@ -219,20 +214,19 @@ impl<'conn> Drop for Transaction<'conn> {
}
impl<'conn> Savepoint<'conn> {
fn with_depth_and_name<T: Into<String>>(conn: &Connection,
depth: u32,
name: T)
-> Result<Savepoint> {
fn with_depth_and_name<T: Into<String>>(
conn: &Connection,
depth: u32,
name: T,
) -> Result<Savepoint> {
let name = name.into();
conn.execute_batch(&format!("SAVEPOINT {}", name))
.map(|_| {
Savepoint {
conn,
name,
depth,
drop_behavior: DropBehavior::Rollback,
committed: false,
}
.map(|_| Savepoint {
conn,
name,
depth,
drop_behavior: DropBehavior::Rollback,
committed: false,
})
}
@ -277,9 +271,9 @@ impl<'conn> Savepoint<'conn> {
}
fn commit_(&mut self) -> Result<()> {
self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
self.committed = true;
self.conn
.execute_batch(&format!("RELEASE {}", self.name))
Ok(())
}
/// A convenience method which rolls back a savepoint.
@ -307,7 +301,7 @@ impl<'conn> Savepoint<'conn> {
return Ok(());
}
match self.drop_behavior() {
DropBehavior::Commit => self.commit_(),
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()),
DropBehavior::Rollback => self.rollback(),
DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
@ -366,9 +360,10 @@ impl Connection {
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn transaction_with_behavior(&mut self,
behavior: TransactionBehavior)
-> Result<Transaction> {
pub fn transaction_with_behavior(
&mut self,
behavior: TransactionBehavior,
) -> Result<Transaction> {
Transaction::new(self, behavior)
}
@ -414,8 +409,8 @@ impl Connection {
#[cfg(test)]
mod test {
use Connection;
use super::DropBehavior;
use Connection;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -438,9 +433,11 @@ mod test {
}
{
let tx = db.transaction().unwrap();
assert_eq!(2i32,
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap());
assert_eq!(
2i32,
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()
);
}
}
@ -465,9 +462,11 @@ mod test {
}
{
let tx = db.transaction().unwrap();
assert_eq!(6i32,
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap());
assert_eq!(
6i32,
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap()
);
}
}
@ -561,7 +560,8 @@ mod test {
}
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();
assert_eq!(x, i);
}

View File

@ -3,10 +3,10 @@ extern crate chrono;
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 Result;
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
impl ToSql for NaiveDate {
@ -22,9 +22,9 @@ impl FromSql for NaiveDate {
value
.as_str()
.and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
})
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
})
}
}
@ -39,19 +39,17 @@ impl ToSql for NaiveTime {
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
impl FromSql for NaiveTime {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value
.as_str()
.and_then(|s| {
let fmt = match s.len() {
5 => "%H:%M",
8 => "%H:%M:%S",
_ => "%H:%M:%S%.f",
};
match NaiveTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
}
})
value.as_str().and_then(|s| {
let fmt = match s.len() {
5 => "%H:%M",
8 => "%H:%M:%S",
_ => "%H:%M:%S%.f",
};
match NaiveTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
}
})
}
}
@ -67,20 +65,18 @@ impl ToSql for NaiveDateTime {
/// without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported)
impl FromSql for NaiveDateTime {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value
.as_str()
.and_then(|s| {
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%Y-%m-%dT%H:%M:%S%.f"
} else {
"%Y-%m-%d %H:%M:%S%.f"
};
value.as_str().and_then(|s| {
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%Y-%m-%dT%H:%M:%S%.f"
} else {
"%Y-%m-%d %H:%M:%S%.f"
};
match NaiveDateTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
}
})
match NaiveDateTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
}
})
}
}
@ -130,9 +126,10 @@ impl FromSql for DateTime<Local> {
#[cfg(test)]
mod test {
use super::chrono::{
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
};
use Connection;
use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
Duration};
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -148,10 +145,12 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&date])
.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();
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();
assert_eq!(date, t);
}
@ -163,10 +162,12 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&time])
.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();
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();
assert_eq!(time, v);
}
@ -181,16 +182,18 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt])
.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();
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();
assert_eq!(dt, v);
db.execute("UPDATE foo set b = datetime(t)", &[])
.unwrap(); // "YYYY-MM-DD HH:MM:SS"
let hms: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0))
db.execute("UPDATE foo set b = datetime(t)", &[]).unwrap(); // "YYYY-MM-DD HH:MM:SS"
let hms: NaiveDateTime = db
.query_row("SELECT b FROM foo", &[], |r| r.get(0))
.unwrap();
assert_eq!(dt, hms);
}
@ -206,25 +209,29 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc])
.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();
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();
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();
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();
assert_eq!(utc - Duration::milliseconds(789), v3);
let v4: DateTime<Utc> =
db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0))
.unwrap();
let v4: DateTime<Utc> = db
.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0))
.unwrap();
assert_eq!(utc, v4);
}
@ -240,11 +247,13 @@ mod test {
.unwrap();
// 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();
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();
assert_eq!(local, v);
}

View File

@ -1,4 +1,4 @@
use super::{ValueRef, Value};
use super::{Value, ValueRef};
use std::error::Error;
use std::fmt;
@ -35,12 +35,11 @@ impl Error for FromSqlError {
}
}
#[cfg_attr(feature="clippy", allow(match_same_arms))]
#[cfg_attr(feature = "clippy", allow(match_same_arms))]
fn cause(&self) -> Option<&Error> {
match *self {
FromSqlError::Other(ref err) => err.cause(),
FromSqlError::InvalidType |
FromSqlError::OutOfRange(_) => None,
FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None,
}
}
}
@ -115,9 +114,9 @@ impl FromSql for f64 {
impl FromSql for bool {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
i64::column_result(value).map(|i| match i {
0 => false,
_ => true,
})
0 => false,
_ => true,
})
}
}
@ -150,8 +149,8 @@ impl FromSql for Value {
#[cfg(test)]
mod test {
use {Connection, Error};
use super::FromSql;
use {Connection, Error};
fn checked_memory_handle() -> Connection {
Connection::open_in_memory().unwrap()
@ -162,10 +161,12 @@ mod test {
let db = checked_memory_handle();
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 {
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_err();
match err {
@ -174,18 +175,22 @@ mod test {
}
}
for n in in_range {
assert_eq!(*n,
db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
.unwrap()
.into());
assert_eq!(
*n,
db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
.unwrap()
.into()
);
}
}
check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
check_ranges::<i32>(&db,
&[-2147483649, 2147483648],
&[-2147483648, -1, 0, 1, 2147483647]);
check_ranges::<i32>(
&db,
&[-2147483649, 2147483648],
&[-2147483648, -1, 0, 1, 2147483647],
);
check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
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;
mod value;
mod value_ref;
mod from_sql;
mod to_sql;
mod time;
#[cfg(feature = "chrono")]
mod chrono;
mod from_sql;
#[cfg(feature = "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`.
///
@ -77,17 +77,16 @@ mod serde_json;
/// # extern crate rusqlite;
/// # use rusqlite::{Connection, Result};
/// # use rusqlite::types::{Null};
/// # use std::os::raw::{c_int};
/// 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])
/// }
/// ```
#[derive(Copy,Clone)]
#[derive(Copy, Clone)]
pub struct Null;
#[derive(Clone,Debug,PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum Type {
Null,
Integer,
@ -112,11 +111,11 @@ impl fmt::Display for Type {
mod test {
extern crate time;
use super::Value;
use std::f64::EPSILON;
use std::os::raw::{c_double, c_int};
use Connection;
use Error;
use std::os::raw::{c_int, c_double};
use std::f64::EPSILON;
use super::Value;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -133,7 +132,8 @@ mod test {
db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])
.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();
assert_eq!(v, v1234);
}
@ -146,7 +146,8 @@ mod test {
db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])
.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();
assert_eq!(v, empty);
}
@ -156,10 +157,10 @@ mod test {
let db = checked_memory_handle();
let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s])
.unwrap();
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).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();
assert_eq!(from, s);
}
@ -172,7 +173,8 @@ mod test {
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_owned()])
.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();
assert_eq!(from, s);
}
@ -184,9 +186,11 @@ mod test {
db.execute("INSERT INTO foo(i) VALUES (?)", &[&Value::Integer(10)])
.unwrap();
assert_eq!(10i64,
db.query_row::<i64, _>("SELECT i FROM foo", &[], |r| r.get(0))
.unwrap());
assert_eq!(
10i64,
db.query_row::<i64, _>("SELECT i FROM foo", &[], |r| r.get(0))
.unwrap()
);
}
#[test]
@ -196,12 +200,11 @@ mod test {
let s = Some("hello, world!");
let b = Some(vec![1u8, 2, 3, 4]);
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s])
.unwrap();
db.execute("INSERT INTO foo(b) VALUES (?)", &[&b])
.unwrap();
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).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();
let mut rows = stmt.query(&[]).unwrap();
@ -233,9 +236,10 @@ mod test {
let db = checked_memory_handle();
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
&[])
.unwrap();
db.execute(
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
&[],
).unwrap();
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
let mut rows = stmt.query(&[]).unwrap();
@ -243,57 +247,103 @@ mod test {
let row = rows.next().unwrap().unwrap();
// check the correct types come back as expected
assert_eq!(vec![1, 2], row.get_checked::<i32, Vec<u8>>(0).unwrap());
assert_eq!("text", row.get_checked::<i32, String>(1).unwrap());
assert_eq!(1, row.get_checked::<i32, c_int>(2).unwrap());
assert!((1.5 - row.get_checked::<i32, c_double>(3).unwrap()).abs() < EPSILON);
assert!(row.get_checked::<i32, Option<c_int>>(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());
assert_eq!(vec![1, 2], row.get_checked::<_, Vec<u8>>(0).unwrap());
assert_eq!("text", row.get_checked::<_, String>(1).unwrap());
assert_eq!(1, row.get_checked::<_, c_int>(2).unwrap());
assert!((1.5 - row.get_checked::<_, c_double>(3).unwrap()).abs() < EPSILON);
assert!(row.get_checked::<_, Option<c_int>>(4).unwrap().is_none());
assert!(row.get_checked::<_, Option<c_double>>(4).unwrap().is_none());
assert!(row.get_checked::<_, Option<String>>(4).unwrap().is_none());
// check some invalid types
// 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(row.get_checked::<i32, 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(row.get_checked::<i32, String>(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::<_, c_int>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, 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)
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(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(row.get_checked::<i32, Option<c_int>>(1).err().unwrap()));
assert!(is_invalid_column_type(
row.get_checked::<_, c_int>(1).err().unwrap()
));
assert!(is_invalid_column_type(
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
assert!(is_invalid_column_type(row.get_checked::<i32, String>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(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::<_, 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)
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(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(row.get_checked::<i32, Option<c_int>>(3).err().unwrap()));
assert!(is_invalid_column_type(
row.get_checked::<_, c_int>(3).err().unwrap()
));
assert!(is_invalid_column_type(
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
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(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(row.get_checked::<i32, Vec<u8>>(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_int>(4).err().unwrap()
));
assert!(is_invalid_column_type(
row.get_checked::<_, i64>(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]
@ -301,23 +351,28 @@ mod test {
use super::Value;
let db = checked_memory_handle();
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
&[])
.unwrap();
db.execute(
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
&[],
).unwrap();
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
let mut rows = stmt.query(&[]).unwrap();
let row = rows.next().unwrap().unwrap();
assert_eq!(Value::Blob(vec![1, 2]),
row.get_checked::<i32, Value>(0).unwrap());
assert_eq!(Value::Text(String::from("text")),
row.get_checked::<i32, Value>(1).unwrap());
assert_eq!(Value::Integer(1), row.get_checked::<i32, Value>(2).unwrap());
match row.get_checked::<i32, Value>(3).unwrap() {
assert_eq!(
Value::Blob(vec![1, 2]),
row.get_checked::<_, Value>(0).unwrap()
);
assert_eq!(
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),
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 Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use Result;
/// Serialize JSON `Value` to text.
impl ToSql for Value {
@ -17,18 +17,17 @@ impl ToSql for Value {
impl FromSql for Value {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
match value {
ValueRef::Text(s) => serde_json::from_str(s),
ValueRef::Blob(b) => serde_json::from_slice(b),
_ => return Err(FromSqlError::InvalidType),
}
.map_err(|err| FromSqlError::Other(Box::new(err)))
ValueRef::Text(s) => serde_json::from_str(s),
ValueRef::Blob(b) => serde_json::from_slice(b),
_ => return Err(FromSqlError::InvalidType),
}.map_err(|err| FromSqlError::Other(Box::new(err)))
}
}
#[cfg(test)]
mod test {
use Connection;
use super::serde_json;
use Connection;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -43,14 +42,17 @@ mod test {
let json = r#"{"foo": 13, "bar": "baz"}"#;
let data: serde_json::Value = serde_json::from_str(json).unwrap();
db.execute("INSERT INTO foo (t, b) VALUES (?, ?)",
&[&data, &json.as_bytes()])
.unwrap();
db.execute(
"INSERT INTO foo (t, b) VALUES (?, ?)",
&[&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();
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();
assert_eq!(data, b);
}

View File

@ -1,7 +1,7 @@
extern crate time;
use Result;
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_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
@ -21,18 +21,18 @@ impl FromSql for time::Timespec {
value
.as_str()
.and_then(|s| {
time::strptime(s, SQLITE_DATETIME_FMT)
.or_else(|err| {
time::strptime(s, SQLITE_DATETIME_FMT_LEGACY)
.or_else(|_| Err(FromSqlError::Other(Box::new(err))))})})
.map(|tm| tm.to_timespec())
time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| {
time::strptime(s, SQLITE_DATETIME_FMT_LEGACY)
.or_else(|_| Err(FromSqlError::Other(Box::new(err))))
})
}).map(|tm| tm.to_timespec())
}
}
#[cfg(test)]
mod test {
use Connection;
use super::time;
use Connection;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -47,25 +47,23 @@ mod test {
let mut ts_vec = vec![];
ts_vec.push(time::Timespec::new(10_000, 0));//January 1, 1970 2:46:40 AM
ts_vec.push(time::Timespec::new(10_000, 1000));//January 1, 1970 2:46:40 AM (and one microsecond)
ts_vec.push(time::Timespec::new(1500391124, 1_000_000));//July 18, 2017
ts_vec.push(time::Timespec::new(2000000000, 2_000_000));//May 18, 2033
ts_vec.push(time::Timespec::new(3000000000, 999_999_999));//January 24, 2065
ts_vec.push(time::Timespec::new(10000000000, 0));//November 20, 2286
ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM
ts_vec.push(time::Timespec::new(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
ts_vec.push(time::Timespec::new(1500391124, 1_000_000)); //July 18, 2017
ts_vec.push(time::Timespec::new(2000000000, 2_000_000)); //May 18, 2033
ts_vec.push(time::Timespec::new(3000000000, 999_999_999)); //January 24, 2065
ts_vec.push(time::Timespec::new(10000000000, 0)); //November 20, 2286
for ts in ts_vec {
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts])
.unwrap();
let from: time::Timespec = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
let from: time::Timespec = db
.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap();
db.execute("DELETE FROM foo", &[]).unwrap();
assert_eq!(from, ts);
}
}
}

View File

@ -1,8 +1,11 @@
use super::{Null, Value, ValueRef};
use std::borrow::Cow;
#[cfg(feature = "array")]
use vtab::array::Array;
use Result;
/// `ToSqlOutput` represents the possible output types for implementors of the `ToSql` trait.
#[derive(Clone,Debug,PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum ToSqlOutput<'a> {
/// A borrowed SQLite-representable value.
Borrowed(ValueRef<'a>),
@ -13,12 +16,16 @@ pub enum ToSqlOutput<'a> {
/// A BLOB of the given length that is filled with zeroes.
#[cfg(feature = "blob")]
ZeroBlob(i32),
#[cfg(feature = "array")]
Array(Array),
}
// Generically allow any type that can be converted into a ValueRef
// to be converted into a ToSqlOutput as well.
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 {
ToSqlOutput::Borrowed(t.into())
@ -54,12 +61,14 @@ from_value!(Vec<u8>);
impl<'a> ToSql for ToSqlOutput<'a> {
fn to_sql(&self) -> Result<ToSqlOutput> {
Ok(match *self {
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
#[cfg(feature = "blob")]
#[cfg(feature = "blob")]
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);
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> {
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)]
mod test {
use super::ToSql;
@ -165,4 +181,16 @@ mod test {
is_to_sql::<u16>();
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

@ -4,7 +4,7 @@ use super::{Null, Type};
/// dictated by SQLite (not by the caller).
///
/// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value.
#[derive(Clone,Debug,PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
/// The value is a `NULL` value.
Null,
@ -31,7 +31,9 @@ impl From<bool> 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(

View File

@ -1,11 +1,11 @@
use super::{Type, Value};
use types::{FromSqlError, FromSqlResult};
use super::{Value, Type};
/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the
/// memory backing this value is owned by SQLite.
///
/// See [`Value`](enum.Value.html) for an owning dynamic type value.
#[derive(Copy,Clone,Debug,PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ValueRef<'a> {
/// The value is a `NULL` value.
Null,

View File

@ -1,10 +1,10 @@
//! [Unlock Notification](http://sqlite.org/unlock_notify.html)
#[cfg(feature = "unlock_notify")]
use std::sync::{Condvar, Mutex};
use std::os::raw::c_int;
#[cfg(feature = "unlock_notify")]
use std::os::raw::c_void;
#[cfg(feature = "unlock_notify")]
use std::sync::{Condvar, Mutex};
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")]
pub fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool {
rc == ffi::SQLITE_LOCKED_SHAREDCACHE || (rc & 0xFF) == ffi::SQLITE_LOCKED && unsafe {
ffi::sqlite3_extended_errcode(db)
}
== ffi::SQLITE_LOCKED_SHAREDCACHE
rc == ffi::SQLITE_LOCKED_SHAREDCACHE
|| (rc & 0xFF) == ffi::SQLITE_LOCKED
&& unsafe { ffi::sqlite3_extended_errcode(db) } == ffi::SQLITE_LOCKED_SHAREDCACHE
}
/// 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
//! would violate safety if multiple Rust threads tried to use connections.
extern crate rusqlite;
extern crate libsqlite3_sys as ffi;
extern crate rusqlite;
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);
}