mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-22 16:29:20 +08:00
Merge remote-tracking branch 'jgallagher/master' into 0.14
This commit is contained in:
commit
33271764b1
10
.travis.yml
10
.travis.yml
@ -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"
|
||||
|
11
Cargo.toml
11
Cargo.toml
@ -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"
|
||||
|
37
README.md
37
README.md
@ -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
|
||||
|
12
appveyor.yml
12
appveyor.yml
@ -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
|
||||
|
@ -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 }
|
||||
|
||||
|
2145
libsqlite3-sys/bindgen-bindings/bindgen_3.7.7.rs
Normal file
2145
libsqlite3-sys/bindgen-bindings/bindgen_3.7.7.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
124
libsqlite3-sys/sqlite3/bindgen_bundled_version.rs
vendored
124
libsqlite3-sys/sqlite3/bindgen_bundled_version.rs
vendored
@ -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(
|
||||
|
12878
libsqlite3-sys/sqlite3/sqlite3.c
vendored
12878
libsqlite3-sys/sqlite3/sqlite3.c
vendored
File diff suppressed because it is too large
Load Diff
801
libsqlite3-sys/sqlite3/sqlite3.h
vendored
801
libsqlite3-sys/sqlite3/sqlite3.h
vendored
File diff suppressed because it is too large
Load Diff
34
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
34
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
168
src/backup.rs
168
src/backup.rs
@ -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);
|
||||
}
|
||||
}
|
||||
|
107
src/blob.rs
107
src/blob.rs
@ -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
162
src/busy.rs
Normal 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();
|
||||
}
|
||||
}
|
83
src/cache.rs
83
src/cache.rs
@ -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
124
src/context.rs
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
97
src/error.rs
97
src/error.rs
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
414
src/functions.rs
414
src/functions.rs
@ -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(®ex_s) {
|
||||
Ok(r) => vac.insert(r),
|
||||
Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
||||
}
|
||||
}
|
||||
Vacant(vac) => match Regex::new(®ex_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);
|
||||
}
|
||||
|
263
src/hooks.rs
263
src/hooks.rs
@ -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));
|
||||
}
|
||||
}
|
||||
|
483
src/lib.rs
483
src/lib.rs
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")]
|
||||
|
@ -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 {
|
||||
|
109
src/row.rs
109
src/row.rs
@ -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)
|
||||
}
|
||||
}
|
||||
|
358
src/statement.rs
358
src/statement.rs
@ -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());
|
||||
}
|
||||
}
|
||||
|
26
src/trace.rs
26
src/trace.rs
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]);
|
||||
|
211
src/types/mod.rs
211
src/types/mod.rs
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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
194
src/vtab/array.rs
Normal 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
386
src/vtab/csvtab.rs
Normal 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
991
src/vtab/mod.rs
Normal 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
286
src/vtab/series.rs
Normal 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
6
test.csv
Normal 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,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
97
tests/vtab.rs
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user