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

This commit is contained in:
gwenn 2019-02-02 13:22:40 +01:00
commit e8e76852bd
49 changed files with 7146 additions and 2010 deletions

View File

@ -8,6 +8,7 @@ rust:
- nightly - nightly
matrix: matrix:
fast_finish: true
allow_failures: allow_failures:
- rust: nightly - rust: nightly

View File

@ -1,7 +1,8 @@
[package] [package]
name = "rusqlite" name = "rusqlite"
version = "0.15.0" version = "0.16.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"] authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
edition = "2018"
description = "Ergonomic wrapper for SQLite" description = "Ergonomic wrapper for SQLite"
repository = "https://github.com/jgallagher/rusqlite" repository = "https://github.com/jgallagher/rusqlite"
documentation = "http://docs.rs/rusqlite/" documentation = "http://docs.rs/rusqlite/"
@ -18,6 +19,9 @@ maintenance = { status = "actively-developed" }
[lib] [lib]
name = "rusqlite" name = "rusqlite"
[workspace]
members = ["libsqlite3-sys"]
[features] [features]
load_extension = [] load_extension = []
# hot-backup interface: 3.6.11 (2009-02-18) # hot-backup interface: 3.6.11 (2009-02-18)
@ -37,9 +41,13 @@ sqlcipher = ["libsqlite3-sys/sqlcipher"]
unlock_notify = ["libsqlite3-sys/unlock_notify"] unlock_notify = ["libsqlite3-sys/unlock_notify"]
# xSavepoint, xRelease and xRollbackTo: 3.7.7 (2011-06-23) # xSavepoint, xRelease and xRollbackTo: 3.7.7 (2011-06-23)
vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"] vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"]
# xShadowName: 3.26.0
vtab_v3 = ["vtab"]
csvtab = ["csv", "vtab"] csvtab = ["csv", "vtab"]
# pointer passing interfaces: 3.20.0 # pointer passing interfaces: 3.20.0
array = ["vtab"] array = ["vtab"]
# session extension: 3.13.0
session = ["libsqlite3-sys/session", "hooks", "fallible-streaming-iterator"]
[dependencies] [dependencies]
time = "0.1.0" time = "0.1.0"
@ -50,6 +58,7 @@ serde_json = { version = "1.0", optional = true }
csv = { version = "1.0", optional = true } csv = { version = "1.0", optional = true }
lazy_static = { version = "1.0", optional = true } lazy_static = { version = "1.0", optional = true }
byteorder = { version = "1.2", features = ["i128"], optional = true } byteorder = { version = "1.2", features = ["i128"], optional = true }
fallible-streaming-iterator = { version = "0.1", optional = true }
[dev-dependencies] [dev-dependencies]
tempdir = "0.3" tempdir = "0.3"
@ -58,7 +67,7 @@ regex = "1.0"
[dependencies.libsqlite3-sys] [dependencies.libsqlite3-sys]
path = "libsqlite3-sys" path = "libsqlite3-sys"
version = "0.10" version = "0.12"
[[test]] [[test]]
name = "config_log" name = "config_log"

View File

@ -10,9 +10,6 @@ Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expo
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
```rust ```rust
extern crate rusqlite;
extern crate time;
use rusqlite::types::ToSql; use rusqlite::types::ToSql;
use rusqlite::{Connection, NO_PARAMS}; use rusqlite::{Connection, NO_PARAMS};
use time::Timespec; use time::Timespec;
@ -77,26 +74,26 @@ newer SQLite version; see details below.
Rusqlite provides several features that are behind [Cargo Rusqlite provides several features that are behind [Cargo
features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are: features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are:
* [`load_extension`](https://docs.rs/rusqlite/0.15.0/rusqlite/struct.LoadExtensionGuard.html) * [`load_extension`](https://docs.rs/rusqlite/0.16.0/rusqlite/struct.LoadExtensionGuard.html)
allows loading dynamic library-based SQLite extensions. allows loading dynamic library-based SQLite extensions.
* [`backup`](https://docs.rs/rusqlite/0.15.0/rusqlite/backup/index.html) * [`backup`](https://docs.rs/rusqlite/0.16.0/rusqlite/backup/index.html)
allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later. allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later.
* [`functions`](https://docs.rs/rusqlite/0.15.0/rusqlite/functions/index.html) * [`functions`](https://docs.rs/rusqlite/0.16.0/rusqlite/functions/index.html)
allows you to load Rust closures into SQLite connections for use in queries. allows you to load Rust closures into SQLite connections for use in queries.
Note: This feature requires SQLite 3.7.3 or later. Note: This feature requires SQLite 3.7.3 or later.
* [`trace`](https://docs.rs/rusqlite/0.15.0/rusqlite/trace/index.html) * [`trace`](https://docs.rs/rusqlite/0.16.0/rusqlite/trace/index.html)
allows hooks into SQLite's tracing and profiling APIs. Note: This feature allows hooks into SQLite's tracing and profiling APIs. Note: This feature
requires SQLite 3.6.23 or later. requires SQLite 3.6.23 or later.
* [`blob`](https://docs.rs/rusqlite/0.15.0/rusqlite/blob/index.html) * [`blob`](https://docs.rs/rusqlite/0.16.0/rusqlite/blob/index.html)
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature
requires SQLite 3.7.4 or later. requires SQLite 3.7.4 or later.
* [`limits`](https://docs.rs/rusqlite/0.15.0/rusqlite/struct.Connection.html#method.limit) * [`limits`](https://docs.rs/rusqlite/0.16.0/rusqlite/struct.Connection.html#method.limit)
allows you to set and retrieve SQLite's per connection limits. allows you to set and retrieve SQLite's per connection limits.
* `chrono` implements [`FromSql`](https://docs.rs/rusqlite/0.15.0/rusqlite/types/trait.FromSql.html) * `chrono` implements [`FromSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/0.15.0/rusqlite/types/trait.ToSql.html) for various and [`ToSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.ToSql.html) for various
types from the [`chrono` crate](https://crates.io/crates/chrono). types from the [`chrono` crate](https://crates.io/crates/chrono).
* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/0.15.0/rusqlite/types/trait.FromSql.html) * `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/0.15.0/rusqlite/types/trait.ToSql.html) for the and [`ToSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.ToSql.html) for the
`Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json). `Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json).
* `bundled` uses a bundled version of sqlite3. This is a good option for cases where linking to sqlite3 is complicated, such as Windows. * `bundled` uses a bundled version of sqlite3. This is a good option for cases where linking to sqlite3 is complicated, such as Windows.
* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`. * `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`.
@ -106,6 +103,7 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust. * [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function. * [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function.
* `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected. * `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected.
* [`session`](https://sqlite.org/sessionintro.html), Session module extension.
## Notes on building rusqlite and libsqlite3-sys ## Notes on building rusqlite and libsqlite3-sys
@ -118,11 +116,11 @@ You can adjust this behavior in a number of ways:
* If you use the `bundled` feature, `libsqlite3-sys` will use the * If you use the `bundled` feature, `libsqlite3-sys` will use the
[gcc](https://crates.io/crates/gcc) crate to compile SQLite from source and [gcc](https://crates.io/crates/gcc) crate to compile SQLite from source and
link against that. This source is embedded in the `libsqlite3-sys` crate and link against that. This source is embedded in the `libsqlite3-sys` crate and
is currently SQLite 3.25.2 (as of `rusqlite` 0.15.0 / `libsqlite3-sys` is currently SQLite 3.26.0 (as of `rusqlite` 0.16.0 / `libsqlite3-sys`
0.10.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file: 0.11.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
``` ```
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.15.0" version = "0.16.0"
features = ["bundled"] features = ["bundled"]
``` ```
* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite * You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite

View File

@ -5,9 +5,9 @@ environment:
- TARGET: x86_64-pc-windows-msvc - TARGET: x86_64-pc-windows-msvc
VCPKG_DEFAULT_TRIPLET: x64-windows VCPKG_DEFAULT_TRIPLET: x64-windows
VCPKGRS_DYNAMIC: 1 VCPKGRS_DYNAMIC: 1
- TARGET: x86_64-pc-windows-msvc # - TARGET: x86_64-pc-windows-msvc
VCPKG_DEFAULT_TRIPLET: x64-windows-static # VCPKG_DEFAULT_TRIPLET: x64-windows-static
RUSTFLAGS: -Ctarget-feature=+crt-static # RUSTFLAGS: -Ctarget-feature=+crt-static
install: install:
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init.exe -y --default-host %TARGET% - rustup-init.exe -y --default-host %TARGET%

View File

@ -1,8 +1,6 @@
#![feature(test)] #![feature(test)]
extern crate test; extern crate test;
extern crate rusqlite;
use rusqlite::Connection; use rusqlite::Connection;
use test::Bencher; use test::Bencher;

View File

@ -1,7 +1,8 @@
[package] [package]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.10.0" version = "0.12.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"] authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
edition = "2018"
repository = "https://github.com/jgallagher/rusqlite" repository = "https://github.com/jgallagher/rusqlite"
description = "Native bindings to the libsqlite3 library" description = "Native bindings to the libsqlite3 library"
license = "MIT" license = "MIT"
@ -18,11 +19,16 @@ sqlcipher = []
min_sqlite_version_3_6_8 = ["pkg-config", "vcpkg"] min_sqlite_version_3_6_8 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"] min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_7 = ["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 # sqlite3_unlock_notify >= 3.6.12
unlock_notify = [] unlock_notify = []
# 3.13.0
preupdate_hook = []
# 3.13.0
session = ["preupdate_hook"]
[build-dependencies] [build-dependencies]
bindgen = { version = "0.43", optional = true } bindgen = { version = "0.47", optional = true }
pkg-config = { version = "0.3", optional = true } pkg-config = { version = "0.3", optional = true }
cc = { version = "1.0", optional = true } cc = { version = "1.0", optional = true }

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,34 @@
use std::env;
use std::path::Path;
fn main() { fn main() {
build::main(); let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("bindgen.rs");
build::main(&out_dir, &out_path);
} }
#[cfg(feature = "bundled")] #[cfg(feature = "bundled")]
mod build { mod build {
extern crate cc; use cc;
use std::path::Path; use std::path::Path;
use std::{env, fs};
pub fn main() { pub fn main(out_dir: &str, out_path: &Path) {
if cfg!(feature = "sqlcipher") { if cfg!(feature = "sqlcipher") {
panic!("Builds with bundled SQLCipher are not supported"); panic!("Builds with bundled SQLCipher are not supported");
} }
let out_dir = env::var("OUT_DIR").unwrap(); #[cfg(feature = "buildtime_bindgen")]
let out_path = Path::new(&out_dir).join("bindgen.rs"); {
fs::copy("sqlite3/bindgen_bundled_version.rs", out_path) use super::{bindings, HeaderLocation};
.expect("Could not copy bindings to output directory"); let header = HeaderLocation::FromPath("sqlite3/sqlite3.h".to_owned());
bindings::write_to_out_dir(header, out_path);
}
#[cfg(not(feature = "buildtime_bindgen"))]
{
use std::fs;
fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
.expect("Could not copy bindings to output directory");
}
let mut cfg = cc::Build::new(); let mut cfg = cc::Build::new();
cfg.file("sqlite3/sqlite3.c") cfg.file("sqlite3/sqlite3.c")
@ -42,62 +54,87 @@ mod build {
if cfg!(feature = "unlock_notify") { if cfg!(feature = "unlock_notify") {
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY"); cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
} }
if cfg!(feature = "preupdate_hook") {
cfg.flag("-DSQLITE_ENABLE_PREUPDATE_HOOK");
}
if cfg!(feature = "session") {
cfg.flag("-DSQLITE_ENABLE_SESSION");
}
cfg.compile("libsqlite3.a"); cfg.compile("libsqlite3.a");
println!("cargo:lib_dir={}", out_dir); println!("cargo:lib_dir={}", out_dir);
} }
} }
fn env_prefix() -> &'static str {
if cfg!(feature = "sqlcipher") {
"SQLCIPHER"
} else {
"SQLITE3"
}
}
pub enum HeaderLocation {
FromEnvironment,
Wrapper,
FromPath(String),
}
impl From<HeaderLocation> for String {
fn from(header: HeaderLocation) -> String {
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
));
header.push_str("/sqlite3.h");
header
}
HeaderLocation::Wrapper => "wrapper.h".into(),
HeaderLocation::FromPath(path) => path,
}
}
}
#[cfg(not(feature = "bundled"))] #[cfg(not(feature = "bundled"))]
mod build { mod build {
extern crate pkg_config; use pkg_config;
#[cfg(all(feature = "vcpkg", target_env = "msvc"))] #[cfg(all(feature = "vcpkg", target_env = "msvc"))]
extern crate vcpkg; extern crate vcpkg;
use super::{bindings, env_prefix, HeaderLocation};
use std::env; use std::env;
use std::path::Path;
pub enum HeaderLocation { pub fn main(_out_dir: &str, out_path: &Path) {
FromEnvironment, let header = find_sqlite();
Wrapper, bindings::write_to_out_dir(header, out_path);
FromPath(String),
} }
impl From<HeaderLocation> for String { fn find_link_mode() -> &'static str {
fn from(header: HeaderLocation) -> String { // If the user specifies SQLITE_STATIC (or SQLCIPHER_STATIC), do static
match header { // linking, unless it's explicitly set to 0.
HeaderLocation::FromEnvironment => { match &env::var(format!("{}_STATIC", env_prefix())) {
let prefix = env_prefix(); Ok(v) if v != "0" => "static",
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!( _ => "dylib",
"{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
prefix, prefix
));
header.push_str("/sqlite3.h");
header
}
HeaderLocation::Wrapper => "wrapper.h".into(),
HeaderLocation::FromPath(path) => path,
}
} }
} }
pub fn main() {
let header = find_sqlite();
bindings::write_to_out_dir(header);
}
// Prints the necessary cargo link commands and returns the path to the header. // Prints the necessary cargo link commands and returns the path to the header.
fn find_sqlite() -> HeaderLocation { fn find_sqlite() -> HeaderLocation {
let link_lib = link_lib(); let link_lib = link_lib();
println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix()); println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix());
println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix()); println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix());
println!("cargo:rerun-if-env-changed={}_STATIC", env_prefix());
if cfg!(target_os = "windows") { if cfg!(target_os = "windows") {
println!("cargo:rerun-if-env-changed=PATH"); println!("cargo:rerun-if-env-changed=PATH");
} }
// Allow users to specify where to find SQLite. // Allow users to specify where to find SQLite.
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) { if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
println!("cargo:rustc-link-lib={}", link_lib); println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
println!("cargo:rustc-link-search={}", dir); println!("cargo:rustc-link-search={}", dir);
return HeaderLocation::FromEnvironment; return HeaderLocation::FromEnvironment;
} }
@ -122,9 +159,9 @@ mod build {
Err(_) => { Err(_) => {
// No env var set and pkg-config couldn't help; just output the link-lib // No env var set and pkg-config couldn't help; just output the link-lib
// request and hope that the library exists on the system paths. We used to // request and hope that the library exists on the system paths. We used to
// output /usr/lib explicitly, but that can introduce other linking problems; see // output /usr/lib explicitly, but that can introduce other linking problems;
// https://github.com/jgallagher/rusqlite/issues/207. // see https://github.com/jgallagher/rusqlite/issues/207.
println!("cargo:rustc-link-lib={}", link_lib); println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
HeaderLocation::Wrapper HeaderLocation::Wrapper
} }
} }
@ -147,14 +184,6 @@ mod build {
None None
} }
fn env_prefix() -> &'static str {
if cfg!(feature = "sqlcipher") {
"SQLCIPHER"
} else {
"SQLITE3"
}
}
fn link_lib() -> &'static str { fn link_lib() -> &'static str {
if cfg!(feature = "sqlcipher") { if cfg!(feature = "sqlcipher") {
"sqlcipher" "sqlcipher"
@ -162,88 +191,98 @@ mod build {
"sqlite3" "sqlite3"
} }
} }
}
#[cfg(not(feature = "buildtime_bindgen"))] #[cfg(all(not(feature = "buildtime_bindgen"), not(feature = "bundled")))]
mod bindings { mod bindings {
use super::HeaderLocation; use super::HeaderLocation;
use std::path::Path; use std::fs;
use std::{env, fs}; use std::path::Path;
static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[ static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[
"bindgen-bindings/bindgen_3.6.8.rs", "bindgen-bindings/bindgen_3.6.8.rs",
#[cfg(feature = "min_sqlite_version_3_6_23")] #[cfg(feature = "min_sqlite_version_3_6_23")]
"bindgen-bindings/bindgen_3.6.23.rs", "bindgen-bindings/bindgen_3.6.23.rs",
#[cfg(feature = "min_sqlite_version_3_7_7")] #[cfg(feature = "min_sqlite_version_3_7_7")]
"bindgen-bindings/bindgen_3.7.7.rs", "bindgen-bindings/bindgen_3.7.7.rs",
]; #[cfg(feature = "min_sqlite_version_3_7_16")]
"bindgen-bindings/bindgen_3.7.16.rs",
];
pub fn write_to_out_dir(_header: HeaderLocation) { pub fn write_to_out_dir(_header: HeaderLocation, out_path: &Path) {
let out_dir = env::var("OUT_DIR").unwrap(); let in_path = PREBUILT_BINDGEN_PATHS[PREBUILT_BINDGEN_PATHS.len() - 1];
let out_path = Path::new(&out_dir).join("bindgen.rs"); fs::copy(in_path, out_path).expect("Could not copy bindings to output directory");
let in_path = PREBUILT_BINDGEN_PATHS[PREBUILT_BINDGEN_PATHS.len() - 1]; }
fs::copy(in_path, out_path).expect("Could not copy bindings to output directory"); }
}
} #[cfg(feature = "buildtime_bindgen")]
mod bindings {
#[cfg(feature = "buildtime_bindgen")] use bindgen;
mod bindings {
extern crate bindgen; use self::bindgen::callbacks::{IntKind, ParseCallbacks};
use super::HeaderLocation;
use self::bindgen::callbacks::{IntKind, ParseCallbacks};
use super::HeaderLocation; use std::fs::OpenOptions;
use std::io::Write;
use std::env; use std::path::Path;
use std::fs::OpenOptions;
use std::io::Write; #[derive(Debug)]
use std::path::Path; struct SqliteTypeChooser;
#[derive(Debug)] impl ParseCallbacks for SqliteTypeChooser {
struct SqliteTypeChooser; fn int_macro(&self, _name: &str, value: i64) -> Option<IntKind> {
if value >= i32::min_value() as i64 && value <= i32::max_value() as i64 {
impl ParseCallbacks for SqliteTypeChooser { Some(IntKind::I32)
fn int_macro(&self, _name: &str, value: i64) -> Option<IntKind> { } else {
if value >= i32::min_value() as i64 && value <= i32::max_value() as i64 { None
Some(IntKind::I32) }
} else { }
None }
}
} pub fn write_to_out_dir(header: HeaderLocation, out_path: &Path) {
} let header: String = header.into();
let mut output = Vec::new();
pub fn write_to_out_dir(header: HeaderLocation) { let mut bindings = bindgen::builder()
let header: String = header.into(); .header(header.clone())
let out_dir = env::var("OUT_DIR").unwrap(); .parse_callbacks(Box::new(SqliteTypeChooser))
let mut output = Vec::new(); .rustfmt_bindings(true);
bindgen::builder()
.header(header.clone()) if cfg!(feature = "unlock_notify") {
.parse_callbacks(Box::new(SqliteTypeChooser)) bindings = bindings.clang_arg("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
.rustfmt_bindings(true) }
.generate() if cfg!(feature = "preupdate_hook") {
.expect(&format!("could not run bindgen on header {}", header)) bindings = bindings.clang_arg("-DSQLITE_ENABLE_PREUPDATE_HOOK");
.write(Box::new(&mut output)) }
.expect("could not write output of bindgen"); if cfg!(feature = "session") {
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!"); bindings = bindings.clang_arg("-DSQLITE_ENABLE_SESSION");
}
// rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it can. This flag
// was added in SQLite 3.8.3, but oring it in in prior versions of SQLite is harmless. We bindings
// don't want to not build just because this flag is missing (e.g., if we're linking against .generate()
// SQLite 3.7.x), so append the flag manually if it isn't present in bindgen's output. .expect(&format!("could not run bindgen on header {}", header))
if !output.contains("pub const SQLITE_DETERMINISTIC") { .write(Box::new(&mut output))
output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n"); .expect("could not write output of bindgen");
} let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
let path = Path::new(&out_dir).join("bindgen.rs"); // rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it
// can. This flag was added in SQLite 3.8.3, but oring it in in prior
let mut file = OpenOptions::new() // versions of SQLite is harmless. We don't want to not build just
.write(true) // because this flag is missing (e.g., if we're linking against
.truncate(true) // SQLite 3.7.x), so append the flag manually if it isn't present in bindgen's
.create(true) // output.
.open(path.clone()) if !output.contains("pub const SQLITE_DETERMINISTIC") {
.expect(&format!("Could not write to {:?}", path)); output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n");
}
file.write_all(output.as_bytes())
.expect(&format!("Could not write to {:?}", path)); let mut file = OpenOptions::new()
} .write(true)
.truncate(true)
.create(true)
.open(out_path.clone())
.expect(&format!("Could not write to {:?}", out_path));
file.write_all(output.as_bytes())
.expect(&format!("Could not write to {:?}", out_path));
} }
} }

View File

@ -1,10 +1,10 @@
/* automatically generated by rust-bindgen */ /* automatically generated by rust-bindgen */
pub const __GNUC_VA_LIST: i32 = 1; pub const __GNUC_VA_LIST: i32 = 1;
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.25.2\0"; pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.26.0\0";
pub const SQLITE_VERSION_NUMBER: i32 = 3025002; pub const SQLITE_VERSION_NUMBER: i32 = 3026000;
pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] = pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] =
b"2018-09-25 19:08:10 fb90e7189ae6d62e77ba3a308ca5d683f90bbe633cf681865365b8e92792d1c7\0"; b"2018-12-01 12:34:55 bf8c1b2b7a5960c282e543b9c293686dccff272512d08865f4600fb58238b4f9\0";
pub const SQLITE_OK: i32 = 0; pub const SQLITE_OK: i32 = 0;
pub const SQLITE_ERROR: i32 = 1; pub const SQLITE_ERROR: i32 = 1;
pub const SQLITE_INTERNAL: i32 = 2; pub const SQLITE_INTERNAL: i32 = 2;
@ -228,7 +228,8 @@ pub const SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: i32 = 1006;
pub const SQLITE_DBCONFIG_ENABLE_QPSG: i32 = 1007; pub const SQLITE_DBCONFIG_ENABLE_QPSG: i32 = 1007;
pub const SQLITE_DBCONFIG_TRIGGER_EQP: i32 = 1008; pub const SQLITE_DBCONFIG_TRIGGER_EQP: i32 = 1008;
pub const SQLITE_DBCONFIG_RESET_DATABASE: i32 = 1009; pub const SQLITE_DBCONFIG_RESET_DATABASE: i32 = 1009;
pub const SQLITE_DBCONFIG_MAX: i32 = 1009; pub const SQLITE_DBCONFIG_DEFENSIVE: i32 = 1010;
pub const SQLITE_DBCONFIG_MAX: i32 = 1010;
pub const SQLITE_DENY: i32 = 1; pub const SQLITE_DENY: i32 = 1;
pub const SQLITE_IGNORE: i32 = 2; pub const SQLITE_IGNORE: i32 = 2;
pub const SQLITE_CREATE_INDEX: i32 = 1; pub const SQLITE_CREATE_INDEX: i32 = 1;
@ -282,6 +283,7 @@ pub const SQLITE_LIMIT_VARIABLE_NUMBER: i32 = 9;
pub const SQLITE_LIMIT_TRIGGER_DEPTH: i32 = 10; pub const SQLITE_LIMIT_TRIGGER_DEPTH: i32 = 10;
pub const SQLITE_LIMIT_WORKER_THREADS: i32 = 11; pub const SQLITE_LIMIT_WORKER_THREADS: i32 = 11;
pub const SQLITE_PREPARE_PERSISTENT: i32 = 1; pub const SQLITE_PREPARE_PERSISTENT: i32 = 1;
pub const SQLITE_PREPARE_NORMALIZE: i32 = 2;
pub const SQLITE_INTEGER: i32 = 1; pub const SQLITE_INTEGER: i32 = 1;
pub const SQLITE_FLOAT: i32 = 2; pub const SQLITE_FLOAT: i32 = 2;
pub const SQLITE_BLOB: i32 = 4; pub const SQLITE_BLOB: i32 = 4;
@ -343,6 +345,7 @@ pub const SQLITE_TESTCTRL_RESERVE: i32 = 14;
pub const SQLITE_TESTCTRL_OPTIMIZATIONS: i32 = 15; pub const SQLITE_TESTCTRL_OPTIMIZATIONS: i32 = 15;
pub const SQLITE_TESTCTRL_ISKEYWORD: i32 = 16; pub const SQLITE_TESTCTRL_ISKEYWORD: i32 = 16;
pub const SQLITE_TESTCTRL_SCRATCHMALLOC: i32 = 17; pub const SQLITE_TESTCTRL_SCRATCHMALLOC: i32 = 17;
pub const SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: i32 = 17;
pub const SQLITE_TESTCTRL_LOCALTIME_FAULT: i32 = 18; pub const SQLITE_TESTCTRL_LOCALTIME_FAULT: i32 = 18;
pub const SQLITE_TESTCTRL_EXPLAIN_STMT: i32 = 19; pub const SQLITE_TESTCTRL_EXPLAIN_STMT: i32 = 19;
pub const SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD: i32 = 19; pub const SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD: i32 = 19;
@ -531,24 +534,34 @@ pub struct sqlite3_io_methods {
unsafe extern "C" fn(arg1: *mut sqlite3_file, size: sqlite3_int64) -> ::std::os::raw::c_int, unsafe extern "C" fn(arg1: *mut sqlite3_file, size: sqlite3_int64) -> ::std::os::raw::c_int,
>, >,
pub xSync: ::std::option::Option< pub xSync: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_file, flags: ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut sqlite3_file,
flags: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
pub xFileSize: ::std::option::Option< pub xFileSize: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_file, pSize: *mut sqlite3_int64) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut sqlite3_file,
pSize: *mut sqlite3_int64,
) -> ::std::os::raw::c_int,
>, >,
pub xLock: ::std::option::Option< pub xLock: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_file, arg2: ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut sqlite3_file,
arg2: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
pub xUnlock: ::std::option::Option< pub xUnlock: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_file, arg2: ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut sqlite3_file,
arg2: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
pub xCheckReservedLock: ::std::option::Option< pub xCheckReservedLock: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_file, pResOut: *mut ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut sqlite3_file,
pResOut: *mut ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
pub xFileControl: ::std::option::Option< pub xFileControl: ::std::option::Option<
unsafe extern "C" fn( unsafe extern "C" fn(
@ -582,8 +595,10 @@ pub struct sqlite3_io_methods {
>, >,
pub xShmBarrier: ::std::option::Option<unsafe extern "C" fn(arg1: *mut sqlite3_file)>, pub xShmBarrier: ::std::option::Option<unsafe extern "C" fn(arg1: *mut sqlite3_file)>,
pub xShmUnmap: ::std::option::Option< pub xShmUnmap: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_file, deleteFlag: ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut sqlite3_file,
deleteFlag: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
pub xFetch: ::std::option::Option< pub xFetch: ::std::option::Option<
unsafe extern "C" fn( unsafe extern "C" fn(
@ -862,8 +877,10 @@ pub struct sqlite3_vfs {
) -> ::std::os::raw::c_int, ) -> ::std::os::raw::c_int,
>, >,
pub xDlOpen: ::std::option::Option< pub xDlOpen: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, zFilename: *const ::std::os::raw::c_char) unsafe extern "C" fn(
-> *mut ::std::os::raw::c_void, arg1: *mut sqlite3_vfs,
zFilename: *const ::std::os::raw::c_char,
) -> *mut ::std::os::raw::c_void,
>, >,
pub xDlError: ::std::option::Option< pub xDlError: ::std::option::Option<
unsafe extern "C" fn( unsafe extern "C" fn(
@ -896,8 +913,10 @@ pub struct sqlite3_vfs {
) -> ::std::os::raw::c_int, ) -> ::std::os::raw::c_int,
>, >,
pub xSleep: ::std::option::Option< pub xSleep: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, microseconds: ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut sqlite3_vfs,
microseconds: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
pub xCurrentTime: ::std::option::Option< pub xCurrentTime: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, arg2: *mut f64) -> ::std::os::raw::c_int, unsafe extern "C" fn(arg1: *mut sqlite3_vfs, arg2: *mut f64) -> ::std::os::raw::c_int,
@ -910,8 +929,10 @@ pub struct sqlite3_vfs {
) -> ::std::os::raw::c_int, ) -> ::std::os::raw::c_int,
>, >,
pub xCurrentTimeInt64: ::std::option::Option< pub xCurrentTimeInt64: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, arg2: *mut sqlite3_int64) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut sqlite3_vfs,
arg2: *mut sqlite3_int64,
) -> ::std::os::raw::c_int,
>, >,
pub xSetSystemCall: ::std::option::Option< pub xSetSystemCall: ::std::option::Option<
unsafe extern "C" fn( unsafe extern "C" fn(
@ -921,12 +942,16 @@ pub struct sqlite3_vfs {
) -> ::std::os::raw::c_int, ) -> ::std::os::raw::c_int,
>, >,
pub xGetSystemCall: ::std::option::Option< pub xGetSystemCall: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, zName: *const ::std::os::raw::c_char) unsafe extern "C" fn(
-> sqlite3_syscall_ptr, arg1: *mut sqlite3_vfs,
zName: *const ::std::os::raw::c_char,
) -> sqlite3_syscall_ptr,
>, >,
pub xNextSystemCall: ::std::option::Option< pub xNextSystemCall: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, zName: *const ::std::os::raw::c_char) unsafe extern "C" fn(
-> *const ::std::os::raw::c_char, arg1: *mut sqlite3_vfs,
zName: *const ::std::os::raw::c_char,
) -> *const ::std::os::raw::c_char,
>, >,
} }
#[test] #[test]
@ -1192,8 +1217,10 @@ pub struct sqlite3_mem_methods {
>, >,
pub xFree: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>, pub xFree: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
pub xRealloc: ::std::option::Option< pub xRealloc: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void, arg2: ::std::os::raw::c_int) unsafe extern "C" fn(
-> *mut ::std::os::raw::c_void, arg1: *mut ::std::os::raw::c_void,
arg2: ::std::os::raw::c_int,
) -> *mut ::std::os::raw::c_void,
>, >,
pub xSize: ::std::option::Option< pub xSize: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int, unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int,
@ -1331,8 +1358,10 @@ extern "C" {
pub fn sqlite3_busy_handler( pub fn sqlite3_busy_handler(
arg1: *mut sqlite3, arg1: *mut sqlite3,
arg2: ::std::option::Option< arg2: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void, arg2: ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut ::std::os::raw::c_void,
arg2: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
arg3: *mut ::std::os::raw::c_void, arg3: *mut ::std::os::raw::c_void,
) -> ::std::os::raw::c_int; ) -> ::std::os::raw::c_int;
@ -1610,6 +1639,9 @@ extern "C" {
extern "C" { extern "C" {
pub fn sqlite3_expanded_sql(pStmt: *mut sqlite3_stmt) -> *mut ::std::os::raw::c_char; pub fn sqlite3_expanded_sql(pStmt: *mut sqlite3_stmt) -> *mut ::std::os::raw::c_char;
} }
extern "C" {
pub fn sqlite3_normalized_sql(pStmt: *mut sqlite3_stmt) -> *const ::std::os::raw::c_char;
}
extern "C" { extern "C" {
pub fn sqlite3_stmt_readonly(pStmt: *mut sqlite3_stmt) -> ::std::os::raw::c_int; pub fn sqlite3_stmt_readonly(pStmt: *mut sqlite3_stmt) -> ::std::os::raw::c_int;
} }
@ -2447,8 +2479,10 @@ pub struct sqlite3_module {
) -> ::std::os::raw::c_int, ) -> ::std::os::raw::c_int,
>, >,
pub xBestIndex: ::std::option::Option< pub xBestIndex: ::std::option::Option<
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab, arg1: *mut sqlite3_index_info) unsafe extern "C" fn(
-> ::std::os::raw::c_int, pVTab: *mut sqlite3_vtab,
arg1: *mut sqlite3_index_info,
) -> ::std::os::raw::c_int,
>, >,
pub xDisconnect: ::std::option::Option< pub xDisconnect: ::std::option::Option<
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab) -> ::std::os::raw::c_int, unsafe extern "C" fn(pVTab: *mut sqlite3_vtab) -> ::std::os::raw::c_int,
@ -2457,8 +2491,10 @@ pub struct sqlite3_module {
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab) -> ::std::os::raw::c_int, unsafe extern "C" fn(pVTab: *mut sqlite3_vtab) -> ::std::os::raw::c_int,
>, >,
pub xOpen: ::std::option::Option< pub xOpen: ::std::option::Option<
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab, ppCursor: *mut *mut sqlite3_vtab_cursor) unsafe extern "C" fn(
-> ::std::os::raw::c_int, pVTab: *mut sqlite3_vtab,
ppCursor: *mut *mut sqlite3_vtab_cursor,
) -> ::std::os::raw::c_int,
>, >,
pub xClose: ::std::option::Option< pub xClose: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_vtab_cursor) -> ::std::os::raw::c_int, unsafe extern "C" fn(arg1: *mut sqlite3_vtab_cursor) -> ::std::os::raw::c_int,
@ -2486,8 +2522,10 @@ pub struct sqlite3_module {
) -> ::std::os::raw::c_int, ) -> ::std::os::raw::c_int,
>, >,
pub xRowid: ::std::option::Option< pub xRowid: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_vtab_cursor, pRowid: *mut sqlite3_int64) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut sqlite3_vtab_cursor,
pRowid: *mut sqlite3_int64,
) -> ::std::os::raw::c_int,
>, >,
pub xUpdate: ::std::option::Option< pub xUpdate: ::std::option::Option<
unsafe extern "C" fn( unsafe extern "C" fn(
@ -2525,27 +2563,38 @@ pub struct sqlite3_module {
) -> ::std::os::raw::c_int, ) -> ::std::os::raw::c_int,
>, >,
pub xRename: ::std::option::Option< pub xRename: ::std::option::Option<
unsafe extern "C" fn(pVtab: *mut sqlite3_vtab, zNew: *const ::std::os::raw::c_char) unsafe extern "C" fn(
-> ::std::os::raw::c_int, pVtab: *mut sqlite3_vtab,
zNew: *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int,
>, >,
pub xSavepoint: ::std::option::Option< pub xSavepoint: ::std::option::Option<
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab, arg1: ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, pVTab: *mut sqlite3_vtab,
arg1: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
pub xRelease: ::std::option::Option< pub xRelease: ::std::option::Option<
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab, arg1: ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, pVTab: *mut sqlite3_vtab,
arg1: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
pub xRollbackTo: ::std::option::Option< pub xRollbackTo: ::std::option::Option<
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab, arg1: ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, pVTab: *mut sqlite3_vtab,
arg1: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>,
pub xShadowName: ::std::option::Option<
unsafe extern "C" fn(arg1: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int,
>, >,
} }
#[test] #[test]
fn bindgen_test_layout_sqlite3_module() { fn bindgen_test_layout_sqlite3_module() {
assert_eq!( assert_eq!(
::std::mem::size_of::<sqlite3_module>(), ::std::mem::size_of::<sqlite3_module>(),
184usize, 192usize,
concat!("Size of: ", stringify!(sqlite3_module)) concat!("Size of: ", stringify!(sqlite3_module))
); );
assert_eq!( assert_eq!(
@ -2783,6 +2832,16 @@ fn bindgen_test_layout_sqlite3_module() {
stringify!(xRollbackTo) stringify!(xRollbackTo)
) )
); );
assert_eq!(
unsafe { &(*(::std::ptr::null::<sqlite3_module>())).xShadowName as *const _ as usize },
184usize,
concat!(
"Offset of field: ",
stringify!(sqlite3_module),
"::",
stringify!(xShadowName)
)
);
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -3828,8 +3887,10 @@ pub struct sqlite3_pcache_methods {
>, >,
pub xShutdown: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>, pub xShutdown: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
pub xCreate: ::std::option::Option< pub xCreate: ::std::option::Option<
unsafe extern "C" fn(szPage: ::std::os::raw::c_int, bPurgeable: ::std::os::raw::c_int) unsafe extern "C" fn(
-> *mut sqlite3_pcache, szPage: ::std::os::raw::c_int,
bPurgeable: ::std::os::raw::c_int,
) -> *mut sqlite3_pcache,
>, >,
pub xCachesize: ::std::option::Option< pub xCachesize: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut sqlite3_pcache, nCachesize: ::std::os::raw::c_int), unsafe extern "C" fn(arg1: *mut sqlite3_pcache, nCachesize: ::std::os::raw::c_int),
@ -4586,8 +4647,10 @@ pub struct Fts5ExtensionApi {
unsafe extern "C" fn(arg1: *mut Fts5Context) -> ::std::os::raw::c_int, unsafe extern "C" fn(arg1: *mut Fts5Context) -> ::std::os::raw::c_int,
>, >,
pub xRowCount: ::std::option::Option< pub xRowCount: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut Fts5Context, pnRow: *mut sqlite3_int64) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut Fts5Context,
pnRow: *mut sqlite3_int64,
) -> ::std::os::raw::c_int,
>, >,
pub xColumnTotalSize: ::std::option::Option< pub xColumnTotalSize: ::std::option::Option<
unsafe extern "C" fn( unsafe extern "C" fn(
@ -4618,12 +4681,16 @@ pub struct Fts5ExtensionApi {
unsafe extern "C" fn(arg1: *mut Fts5Context) -> ::std::os::raw::c_int, unsafe extern "C" fn(arg1: *mut Fts5Context) -> ::std::os::raw::c_int,
>, >,
pub xPhraseSize: ::std::option::Option< pub xPhraseSize: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut Fts5Context, iPhrase: ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut Fts5Context,
iPhrase: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
pub xInstCount: ::std::option::Option< pub xInstCount: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut Fts5Context, pnInst: *mut ::std::os::raw::c_int) unsafe extern "C" fn(
-> ::std::os::raw::c_int, arg1: *mut Fts5Context,
pnInst: *mut ::std::os::raw::c_int,
) -> ::std::os::raw::c_int,
>, >,
pub xInst: ::std::option::Option< pub xInst: ::std::option::Option<
unsafe extern "C" fn( unsafe extern "C" fn(
@ -4669,14 +4736,14 @@ pub struct Fts5ExtensionApi {
unsafe extern "C" fn( unsafe extern "C" fn(
arg1: *mut Fts5Context, arg1: *mut Fts5Context,
pAux: *mut ::std::os::raw::c_void, pAux: *mut ::std::os::raw::c_void,
xDelete: ::std::option::Option< xDelete: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void),
>,
) -> ::std::os::raw::c_int, ) -> ::std::os::raw::c_int,
>, >,
pub xGetAuxdata: ::std::option::Option< pub xGetAuxdata: ::std::option::Option<
unsafe extern "C" fn(arg1: *mut Fts5Context, bClear: ::std::os::raw::c_int) unsafe extern "C" fn(
-> *mut ::std::os::raw::c_void, arg1: *mut Fts5Context,
bClear: ::std::os::raw::c_int,
) -> *mut ::std::os::raw::c_void,
>, >,
pub xPhraseFirst: ::std::option::Option< pub xPhraseFirst: ::std::option::Option<
unsafe extern "C" fn( unsafe extern "C" fn(
@ -5020,9 +5087,7 @@ pub struct fts5_api {
zName: *const ::std::os::raw::c_char, zName: *const ::std::os::raw::c_char,
pContext: *mut ::std::os::raw::c_void, pContext: *mut ::std::os::raw::c_void,
pTokenizer: *mut fts5_tokenizer, pTokenizer: *mut fts5_tokenizer,
xDestroy: ::std::option::Option< xDestroy: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void),
>,
) -> ::std::os::raw::c_int, ) -> ::std::os::raw::c_int,
>, >,
pub xFindTokenizer: ::std::option::Option< pub xFindTokenizer: ::std::option::Option<
@ -5039,9 +5104,7 @@ pub struct fts5_api {
zName: *const ::std::os::raw::c_char, zName: *const ::std::os::raw::c_char,
pContext: *mut ::std::os::raw::c_void, pContext: *mut ::std::os::raw::c_void,
xFunction: fts5_extension_function, xFunction: fts5_extension_function,
xDestroy: ::std::option::Option< xDestroy: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void),
>,
) -> ::std::os::raw::c_int, ) -> ::std::os::raw::c_int,
>, >,
} }

File diff suppressed because it is too large Load Diff

View File

@ -123,9 +123,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()]. ** [sqlite_version()] and [sqlite_source_id()].
*/ */
#define SQLITE_VERSION "3.25.2" #define SQLITE_VERSION "3.26.0"
#define SQLITE_VERSION_NUMBER 3025002 #define SQLITE_VERSION_NUMBER 3026000
#define SQLITE_SOURCE_ID "2018-09-25 19:08:10 fb90e7189ae6d62e77ba3a308ca5d683f90bbe633cf681865365b8e92792d1c7" #define SQLITE_SOURCE_ID "2018-12-01 12:34:55 bf8c1b2b7a5960c282e543b9c293686dccff272512d08865f4600fb58238b4f9"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** CAPI3REF: Run-Time Library Version Numbers
@ -2017,6 +2017,7 @@ struct sqlite3_mem_methods {
** is invoked. ** is invoked.
** **
** <dl> ** <dl>
** [[SQLITE_DBCONFIG_LOOKASIDE]]
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt> ** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
** <dd> ^This option takes three additional arguments that determine the ** <dd> ^This option takes three additional arguments that determine the
** [lookaside memory allocator] configuration for the [database connection]. ** [lookaside memory allocator] configuration for the [database connection].
@ -2039,6 +2040,7 @@ struct sqlite3_mem_methods {
** memory is in use leaves the configuration unchanged and returns ** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY].)^</dd> ** [SQLITE_BUSY].)^</dd>
** **
** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt> ** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
** <dd> ^This option is used to enable or disable the enforcement of ** <dd> ^This option is used to enable or disable the enforcement of
** [foreign key constraints]. There should be two additional arguments. ** [foreign key constraints]. There should be two additional arguments.
@ -2049,6 +2051,7 @@ struct sqlite3_mem_methods {
** following this call. The second parameter may be a NULL pointer, in ** following this call. The second parameter may be a NULL pointer, in
** which case the FK enforcement setting is not reported back. </dd> ** which case the FK enforcement setting is not reported back. </dd>
** **
** [[SQLITE_DBCONFIG_ENABLE_TRIGGER]]
** <dt>SQLITE_DBCONFIG_ENABLE_TRIGGER</dt> ** <dt>SQLITE_DBCONFIG_ENABLE_TRIGGER</dt>
** <dd> ^This option is used to enable or disable [CREATE TRIGGER | triggers]. ** <dd> ^This option is used to enable or disable [CREATE TRIGGER | triggers].
** There should be two additional arguments. ** There should be two additional arguments.
@ -2059,6 +2062,7 @@ struct sqlite3_mem_methods {
** following this call. The second parameter may be a NULL pointer, in ** following this call. The second parameter may be a NULL pointer, in
** which case the trigger setting is not reported back. </dd> ** which case the trigger setting is not reported back. </dd>
** **
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
** <dd> ^This option is used to enable or disable the two-argument ** <dd> ^This option is used to enable or disable the two-argument
** version of the [fts3_tokenizer()] function which is part of the ** version of the [fts3_tokenizer()] function which is part of the
@ -2072,6 +2076,7 @@ struct sqlite3_mem_methods {
** following this call. The second parameter may be a NULL pointer, in ** following this call. The second parameter may be a NULL pointer, in
** which case the new setting is not reported back. </dd> ** which case the new setting is not reported back. </dd>
** **
** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]]
** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> ** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt>
** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()] ** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()]
** interface independently of the [load_extension()] SQL function. ** interface independently of the [load_extension()] SQL function.
@ -2089,7 +2094,7 @@ struct sqlite3_mem_methods {
** be a NULL pointer, in which case the new setting is not reported back. ** be a NULL pointer, in which case the new setting is not reported back.
** </dd> ** </dd>
** **
** <dt>SQLITE_DBCONFIG_MAINDBNAME</dt> ** [[SQLITE_DBCONFIG_MAINDBNAME]] <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
** <dd> ^This option is used to change the name of the "main" database ** <dd> ^This option is used to change the name of the "main" database
** schema. ^The sole argument is a pointer to a constant UTF8 string ** schema. ^The sole argument is a pointer to a constant UTF8 string
** which will become the new schema name in place of "main". ^SQLite ** which will become the new schema name in place of "main". ^SQLite
@ -2098,6 +2103,7 @@ struct sqlite3_mem_methods {
** until after the database connection closes. ** until after the database connection closes.
** </dd> ** </dd>
** **
** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]]
** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt> ** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt>
** <dd> Usually, when a database in wal mode is closed or detached from a ** <dd> Usually, when a database in wal mode is closed or detached from a
** database handle, SQLite checks if this will mean that there are now no ** database handle, SQLite checks if this will mean that there are now no
@ -2111,7 +2117,7 @@ struct sqlite3_mem_methods {
** have been disabled - 0 if they are not disabled, 1 if they are. ** have been disabled - 0 if they are not disabled, 1 if they are.
** </dd> ** </dd>
** **
** <dt>SQLITE_DBCONFIG_ENABLE_QPSG</dt> ** [[SQLITE_DBCONFIG_ENABLE_QPSG]] <dt>SQLITE_DBCONFIG_ENABLE_QPSG</dt>
** <dd>^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates ** <dd>^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates
** the [query planner stability guarantee] (QPSG). When the QPSG is active, ** the [query planner stability guarantee] (QPSG). When the QPSG is active,
** a single SQL query statement will always use the same algorithm regardless ** a single SQL query statement will always use the same algorithm regardless
@ -2127,7 +2133,7 @@ struct sqlite3_mem_methods {
** following this call. ** following this call.
** </dd> ** </dd>
** **
** <dt>SQLITE_DBCONFIG_TRIGGER_EQP</dt> ** [[SQLITE_DBCONFIG_TRIGGER_EQP]] <dt>SQLITE_DBCONFIG_TRIGGER_EQP</dt>
** <dd> By default, the output of EXPLAIN QUERY PLAN commands does not ** <dd> By default, the output of EXPLAIN QUERY PLAN commands does not
** include output for any operations performed by trigger programs. This ** include output for any operations performed by trigger programs. This
** option is used to set or clear (the default) a flag that governs this ** option is used to set or clear (the default) a flag that governs this
@ -2139,7 +2145,7 @@ struct sqlite3_mem_methods {
** it is not disabled, 1 if it is. ** it is not disabled, 1 if it is.
** </dd> ** </dd>
** **
** <dt>SQLITE_DBCONFIG_RESET_DATABASE</dt> ** [[SQLITE_DBCONFIG_RESET_DATABASE]] <dt>SQLITE_DBCONFIG_RESET_DATABASE</dt>
** <dd> Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run ** <dd> Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run
** [VACUUM] in order to reset a database back to an empty database ** [VACUUM] in order to reset a database back to an empty database
** with no schema and no content. The following process works even for ** with no schema and no content. The following process works even for
@ -2158,6 +2164,18 @@ struct sqlite3_mem_methods {
** Because resetting a database is destructive and irreversible, the ** Because resetting a database is destructive and irreversible, the
** process requires the use of this obscure API and multiple steps to help ** process requires the use of this obscure API and multiple steps to help
** ensure that it does not happen by accident. ** ensure that it does not happen by accident.
**
** [[SQLITE_DBCONFIG_DEFENSIVE]] <dt>SQLITE_DBCONFIG_DEFENSIVE</dt>
** <dd>The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the
** "defensive" flag for a database connection. When the defensive
** flag is enabled, language features that allow ordinary SQL to
** deliberately corrupt the database file are disabled. The disabled
** features include but are not limited to the following:
** <ul>
** <li> The [PRAGMA writable_schema=ON] statement.
** <li> Writes to the [sqlite_dbpage] virtual table.
** <li> Direct writes to [shadow tables].
** </ul>
** </dd> ** </dd>
** </dl> ** </dl>
*/ */
@ -2171,7 +2189,8 @@ struct sqlite3_mem_methods {
#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */
#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */ #define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */
#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */ #define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */
#define SQLITE_DBCONFIG_MAX 1009 /* Largest DBCONFIG */ #define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */
#define SQLITE_DBCONFIG_MAX 1010 /* Largest DBCONFIG */
/* /*
** CAPI3REF: Enable Or Disable Extended Result Codes ** CAPI3REF: Enable Or Disable Extended Result Codes
@ -3609,9 +3628,19 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
** on this hint by avoiding the use of [lookaside memory] so as not to ** on this hint by avoiding the use of [lookaside memory] so as not to
** deplete the limited store of lookaside memory. Future versions of ** deplete the limited store of lookaside memory. Future versions of
** SQLite may act on this hint differently. ** SQLite may act on this hint differently.
**
** [[SQLITE_PREPARE_NORMALIZE]] ^(<dt>SQLITE_PREPARE_NORMALIZE</dt>
** <dd>The SQLITE_PREPARE_NORMALIZE flag indicates that a normalized
** representation of the SQL statement should be calculated and then
** associated with the prepared statement, which can be obtained via
** the [sqlite3_normalized_sql()] interface.)^ The semantics used to
** normalize a SQL statement are unspecified and subject to change.
** At a minimum, literal values will be replaced with suitable
** placeholders.
** </dl> ** </dl>
*/ */
#define SQLITE_PREPARE_PERSISTENT 0x01 #define SQLITE_PREPARE_PERSISTENT 0x01
#define SQLITE_PREPARE_NORMALIZE 0x02
/* /*
** CAPI3REF: Compiling An SQL Statement ** CAPI3REF: Compiling An SQL Statement
@ -3769,6 +3798,11 @@ SQLITE_API int sqlite3_prepare16_v3(
** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8 ** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8
** string containing the SQL text of prepared statement P with ** string containing the SQL text of prepared statement P with
** [bound parameters] expanded. ** [bound parameters] expanded.
** ^The sqlite3_normalized_sql(P) interface returns a pointer to a UTF-8
** string containing the normalized SQL text of prepared statement P. The
** semantics used to normalize a SQL statement are unspecified and subject
** to change. At a minimum, literal values will be replaced with suitable
** placeholders.
** **
** ^(For example, if a prepared statement is created using the SQL ** ^(For example, if a prepared statement is created using the SQL
** text "SELECT $abc,:xyz" and if parameter $abc is bound to integer 2345 ** text "SELECT $abc,:xyz" and if parameter $abc is bound to integer 2345
@ -3784,14 +3818,16 @@ SQLITE_API int sqlite3_prepare16_v3(
** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time ** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time
** option causes sqlite3_expanded_sql() to always return NULL. ** option causes sqlite3_expanded_sql() to always return NULL.
** **
** ^The string returned by sqlite3_sql(P) is managed by SQLite and is ** ^The strings returned by sqlite3_sql(P) and sqlite3_normalized_sql(P)
** automatically freed when the prepared statement is finalized. ** are managed by SQLite and are automatically freed when the prepared
** statement is finalized.
** ^The string returned by sqlite3_expanded_sql(P), on the other hand, ** ^The string returned by sqlite3_expanded_sql(P), on the other hand,
** is obtained from [sqlite3_malloc()] and must be free by the application ** is obtained from [sqlite3_malloc()] and must be free by the application
** by passing it to [sqlite3_free()]. ** by passing it to [sqlite3_free()].
*/ */
SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt);
SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt);
SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt);
/* /*
** CAPI3REF: Determine If An SQL Statement Writes The Database ** CAPI3REF: Determine If An SQL Statement Writes The Database
@ -6281,6 +6317,9 @@ struct sqlite3_module {
int (*xSavepoint)(sqlite3_vtab *pVTab, int); int (*xSavepoint)(sqlite3_vtab *pVTab, int);
int (*xRelease)(sqlite3_vtab *pVTab, int); int (*xRelease)(sqlite3_vtab *pVTab, int);
int (*xRollbackTo)(sqlite3_vtab *pVTab, int); int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
/* The methods above are in versions 1 and 2 of the sqlite_module object.
** Those below are for version 3 and greater. */
int (*xShadowName)(const char*);
}; };
/* /*
@ -7203,6 +7242,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_OPTIMIZATIONS 15
#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */ #define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */ #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
#define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ #define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */
#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19 #define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19
@ -8615,6 +8655,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
** can use to customize and optimize their behavior. ** can use to customize and optimize their behavior.
** **
** <dl> ** <dl>
** [[SQLITE_VTAB_CONSTRAINT_SUPPORT]]
** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT ** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT
** <dd>Calls of the form ** <dd>Calls of the form
** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported, ** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported,
@ -9384,7 +9425,7 @@ struct sqlite3_rtree_query_info {
sqlite3_int64 iRowid; /* Rowid for current entry */ sqlite3_int64 iRowid; /* Rowid for current entry */
sqlite3_rtree_dbl rParentScore; /* Score of parent node */ sqlite3_rtree_dbl rParentScore; /* Score of parent node */
int eParentWithin; /* Visibility of parent node */ int eParentWithin; /* Visibility of parent node */
int eWithin; /* OUT: Visiblity */ int eWithin; /* OUT: Visibility */
sqlite3_rtree_dbl rScore; /* OUT: Write the score here */ sqlite3_rtree_dbl rScore; /* OUT: Write the score here */
/* The following fields are only available in 3.8.11 and later */ /* The following fields are only available in 3.8.11 and later */
sqlite3_value **apSqlParam; /* Original SQL values of parameters */ sqlite3_value **apSqlParam; /* Original SQL values of parameters */
@ -9880,12 +9921,38 @@ SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession);
** consecutively. There is no chance that the iterator will visit a change ** consecutively. There is no chance that the iterator will visit a change
** the applies to table X, then one for table Y, and then later on visit ** the applies to table X, then one for table Y, and then later on visit
** another change for table X. ** another change for table X.
**
** The behavior of sqlite3changeset_start_v2() and its streaming equivalent
** may be modified by passing a combination of
** [SQLITE_CHANGESETSTART_INVERT | supported flags] as the 4th parameter.
**
** Note that the sqlite3changeset_start_v2() API is still <b>experimental</b>
** and therefore subject to change.
*/ */
SQLITE_API int sqlite3changeset_start( SQLITE_API int sqlite3changeset_start(
sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
int nChangeset, /* Size of changeset blob in bytes */ int nChangeset, /* Size of changeset blob in bytes */
void *pChangeset /* Pointer to blob containing changeset */ void *pChangeset /* Pointer to blob containing changeset */
); );
SQLITE_API int sqlite3changeset_start_v2(
sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
int nChangeset, /* Size of changeset blob in bytes */
void *pChangeset, /* Pointer to blob containing changeset */
int flags /* SESSION_CHANGESETSTART_* flags */
);
/*
** CAPI3REF: Flags for sqlite3changeset_start_v2
**
** The following flags may passed via the 4th parameter to
** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]:
**
** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
** Invert the changeset while iterating through it. This is equivalent to
** inverting a changeset using sqlite3changeset_invert() before applying it.
** It is an error to specify this flag with a patchset.
*/
#define SQLITE_CHANGESETSTART_INVERT 0x0002
/* /*
@ -10540,7 +10607,7 @@ SQLITE_API int sqlite3changeset_apply_v2(
), ),
void *pCtx, /* First argument passed to xConflict */ void *pCtx, /* First argument passed to xConflict */
void **ppRebase, int *pnRebase, /* OUT: Rebase data */ void **ppRebase, int *pnRebase, /* OUT: Rebase data */
int flags /* Combination of SESSION_APPLY_* flags */ int flags /* SESSION_CHANGESETAPPLY_* flags */
); );
/* /*
@ -10558,8 +10625,14 @@ SQLITE_API int sqlite3changeset_apply_v2(
** causes the sessions module to omit this savepoint. In this case, if the ** causes the sessions module to omit this savepoint. In this case, if the
** caller has an open transaction or savepoint when apply_v2() is called, ** caller has an open transaction or savepoint when apply_v2() is called,
** it may revert the partially applied changeset by rolling it back. ** it may revert the partially applied changeset by rolling it back.
**
** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
** Invert the changeset before applying it. This is equivalent to inverting
** a changeset using sqlite3changeset_invert() before applying it. It is
** an error to specify this flag with a patchset.
*/ */
#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
/* /*
** CAPI3REF: Constants Passed To The Conflict Handler ** CAPI3REF: Constants Passed To The Conflict Handler
@ -10953,6 +11026,12 @@ SQLITE_API int sqlite3changeset_start_strm(
int (*xInput)(void *pIn, void *pData, int *pnData), int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn void *pIn
); );
SQLITE_API int sqlite3changeset_start_v2_strm(
sqlite3_changeset_iter **pp,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int flags
);
SQLITE_API int sqlite3session_changeset_strm( SQLITE_API int sqlite3session_changeset_strm(
sqlite3_session *pSession, sqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData), int (*xOutput)(void *pOut, const void *pData, int nData),
@ -10979,6 +11058,45 @@ SQLITE_API int sqlite3rebaser_rebase_strm(
void *pOut void *pOut
); );
/*
** CAPI3REF: Configure global parameters
**
** The sqlite3session_config() interface is used to make global configuration
** changes to the sessions module in order to tune it to the specific needs
** of the application.
**
** The sqlite3session_config() interface is not threadsafe. If it is invoked
** while any other thread is inside any other sessions method then the
** results are undefined. Furthermore, if it is invoked after any sessions
** related objects have been created, the results are also undefined.
**
** The first argument to the sqlite3session_config() function must be one
** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The
** interpretation of the (void*) value passed as the second parameter and
** the effect of calling this function depends on the value of the first
** parameter.
**
** <dl>
** <dt>SQLITE_SESSION_CONFIG_STRMSIZE<dd>
** By default, the sessions module streaming interfaces attempt to input
** and output data in approximately 1 KiB chunks. This operand may be used
** to set and query the value of this configuration setting. The pointer
** passed as the second argument must point to a value of type (int).
** If this value is greater than 0, it is used as the new streaming data
** chunk size for both input and output. Before returning, the (int) value
** pointed to by pArg is set to the final value of the streaming interface
** chunk size.
** </dl>
**
** This function returns SQLITE_OK if successful, or an SQLite error code
** otherwise.
*/
SQLITE_API int sqlite3session_config(int op, void *pArg);
/*
** CAPI3REF: Values for sqlite3session_config().
*/
#define SQLITE_SESSION_CONFIG_STRMSIZE 1
/* /*
** Make sure we can call this stuff from C++. ** Make sure we can call this stuff from C++.

View File

@ -310,12 +310,15 @@ struct sqlite3_api_routines {
int (*str_errcode)(sqlite3_str*); int (*str_errcode)(sqlite3_str*);
int (*str_length)(sqlite3_str*); int (*str_length)(sqlite3_str*);
char *(*str_value)(sqlite3_str*); char *(*str_value)(sqlite3_str*);
/* Version 3.25.0 and later */
int (*create_window_function)(sqlite3*,const char*,int,int,void*, int (*create_window_function)(sqlite3*,const char*,int,int,void*,
void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*), void (*xFinal)(sqlite3_context*),
void (*xValue)(sqlite3_context*), void (*xValue)(sqlite3_context*),
void (*xInv)(sqlite3_context*,int,sqlite3_value**), void (*xInv)(sqlite3_context*,int,sqlite3_value**),
void(*xDestroy)(void*)); void(*xDestroy)(void*));
/* Version 3.26.0 and later */
const char *(*normalized_sql)(sqlite3_stmt*);
}; };
/* /*
@ -603,6 +606,8 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_str_value sqlite3_api->str_value #define sqlite3_str_value sqlite3_api->str_value
/* Version 3.25.0 and later */ /* Version 3.25.0 and later */
#define sqlite3_create_window_function sqlite3_api->create_window_function #define sqlite3_create_window_function sqlite3_api->create_window_function
/* Version 3.26.0 and later */
#define sqlite3_normalized_sql sqlite3_api->normalized_sql
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

View File

@ -98,7 +98,7 @@ impl Error {
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
"Error code {}: {}", "Error code {}: {}",
@ -115,9 +115,10 @@ impl error::Error for Error {
} }
// Result codes. // Result codes.
// Note: These are not public because our bindgen bindings export whichever constants are present // Note: These are not public because our bindgen bindings export whichever
// in the current version of SQLite. We repeat them here so we don't have to worry about which // constants are present in the current version of SQLite. We repeat them here
// version of SQLite added which constants, and we only use them to implement code_to_str below. // 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_NOTICE: c_int = 27;
const SQLITE_WARNING: c_int = 28; const SQLITE_WARNING: c_int = 28;

View File

@ -16,32 +16,36 @@ pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type {
} }
/// Run-Time Limit Categories /// Run-Time Limit Categories
#[repr(C)] #[repr(i32)]
pub enum Limit { pub enum Limit {
/// The maximum size of any string or BLOB or table row, in bytes. /// The maximum size of any string or BLOB or table row, in bytes.
SQLITE_LIMIT_LENGTH = SQLITE_LIMIT_LENGTH as isize, SQLITE_LIMIT_LENGTH = SQLITE_LIMIT_LENGTH,
/// The maximum length of an SQL statement, in bytes. /// The maximum length of an SQL statement, in bytes.
SQLITE_LIMIT_SQL_LENGTH = SQLITE_LIMIT_SQL_LENGTH as isize, SQLITE_LIMIT_SQL_LENGTH = SQLITE_LIMIT_SQL_LENGTH,
/// The maximum number of columns in a table definition or in the result set of a SELECT /// The maximum number of columns in a table definition or in the result set
/// or the maximum number of columns in an index or in an ORDER BY or GROUP BY clause. /// of a SELECT or the maximum number of columns in an index or in an
SQLITE_LIMIT_COLUMN = SQLITE_LIMIT_COLUMN as isize, /// ORDER BY or GROUP BY clause.
SQLITE_LIMIT_COLUMN = SQLITE_LIMIT_COLUMN,
/// The maximum depth of the parse tree on any expression. /// The maximum depth of the parse tree on any expression.
SQLITE_LIMIT_EXPR_DEPTH = SQLITE_LIMIT_EXPR_DEPTH as isize, SQLITE_LIMIT_EXPR_DEPTH = SQLITE_LIMIT_EXPR_DEPTH,
/// The maximum number of terms in a compound SELECT statement. /// The maximum number of terms in a compound SELECT statement.
SQLITE_LIMIT_COMPOUND_SELECT = SQLITE_LIMIT_COMPOUND_SELECT as isize, SQLITE_LIMIT_COMPOUND_SELECT = SQLITE_LIMIT_COMPOUND_SELECT,
/// The maximum number of instructions in a virtual machine program used to implement an SQL statement. /// The maximum number of instructions in a virtual machine program used to
SQLITE_LIMIT_VDBE_OP = SQLITE_LIMIT_VDBE_OP as isize, /// implement an SQL statement.
SQLITE_LIMIT_VDBE_OP = SQLITE_LIMIT_VDBE_OP,
/// The maximum number of arguments on a function. /// The maximum number of arguments on a function.
SQLITE_LIMIT_FUNCTION_ARG = SQLITE_LIMIT_FUNCTION_ARG as isize, SQLITE_LIMIT_FUNCTION_ARG = SQLITE_LIMIT_FUNCTION_ARG,
/// The maximum number of attached databases. /// The maximum number of attached databases.
SQLITE_LIMIT_ATTACHED = SQLITE_LIMIT_ATTACHED as isize, SQLITE_LIMIT_ATTACHED = SQLITE_LIMIT_ATTACHED,
/// The maximum length of the pattern argument to the LIKE or GLOB operators. /// The maximum length of the pattern argument to the LIKE or GLOB
SQLITE_LIMIT_LIKE_PATTERN_LENGTH = SQLITE_LIMIT_LIKE_PATTERN_LENGTH as isize, /// operators.
SQLITE_LIMIT_LIKE_PATTERN_LENGTH = SQLITE_LIMIT_LIKE_PATTERN_LENGTH,
/// The maximum index number of any parameter in an SQL statement. /// The maximum index number of any parameter in an SQL statement.
SQLITE_LIMIT_VARIABLE_NUMBER = SQLITE_LIMIT_VARIABLE_NUMBER as isize, SQLITE_LIMIT_VARIABLE_NUMBER = SQLITE_LIMIT_VARIABLE_NUMBER,
/// The maximum depth of recursion for triggers. /// The maximum depth of recursion for triggers.
SQLITE_LIMIT_TRIGGER_DEPTH = 10, SQLITE_LIMIT_TRIGGER_DEPTH = 10,
/// The maximum number of auxiliary worker threads that a single prepared statement may start. /// The maximum number of auxiliary worker threads that a single prepared
/// statement may start.
SQLITE_LIMIT_WORKER_THREADS = 11, SQLITE_LIMIT_WORKER_THREADS = 11,
} }

View File

@ -4,7 +4,7 @@ cd $SCRIPT_DIR
export SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3 export SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
# Download and extract amalgamation # Download and extract amalgamation
SQLITE=sqlite-amalgamation-3250200 SQLITE=sqlite-amalgamation-3260000
curl -O http://sqlite.org/2018/$SQLITE.zip curl -O http://sqlite.org/2018/$SQLITE.zip
unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c
unzip -p $SQLITE.zip $SQLITE/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h unzip -p $SQLITE.zip $SQLITE/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h
@ -22,5 +22,5 @@ find $SCRIPT_DIR/target -type f -name bindgen.rs -exec cp {} $SQLITE3_LIB_DIR/bi
# Sanity check # Sanity check
cd $SCRIPT_DIR/.. cd $SCRIPT_DIR/..
cargo update cargo update
cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled" cargo test --features "backup blob chrono functions limits load_extension serde_json trace vtab bundled"
echo 'You should increment the version in libsqlite3-sys/Cargo.toml' echo 'You should increment the version in libsqlite3-sys/Cargo.toml'

View File

@ -23,8 +23,8 @@
//! dst: P, //! dst: P,
//! progress: fn(backup::Progress), //! progress: fn(backup::Progress),
//! ) -> Result<()> { //! ) -> Result<()> {
//! let mut dst = try!(Connection::open(dst)); //! let mut dst = Connection::open(dst)?;
//! let backup = try!(backup::Backup::new(src, &mut dst)); //! let backup = backup::Backup::new(src, &mut dst)?;
//! backup.run_to_completion(5, time::Duration::from_millis(250), Some(progress)) //! backup.run_to_completion(5, time::Duration::from_millis(250), Some(progress))
//! } //! }
//! ``` //! ```
@ -37,10 +37,10 @@ use std::os::raw::c_int;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use ffi; use crate::ffi;
use error::{error_from_handle, error_from_sqlite_code}; use crate::error::{error_from_handle, error_from_sqlite_code};
use {Connection, DatabaseName, Result}; use crate::{Connection, DatabaseName, Result};
impl Connection { impl Connection {
/// Back up the `name` database to the given destination path. /// Back up the `name` database to the given destination path.
@ -57,22 +57,17 @@ impl Connection {
/// or if the backup fails. /// or if the backup fails.
pub fn backup<P: AsRef<Path>>( pub fn backup<P: AsRef<Path>>(
&self, &self,
name: DatabaseName, name: DatabaseName<'_>,
dst_path: P, dst_path: P,
progress: Option<fn(Progress)>, progress: Option<fn(Progress)>,
) -> Result<()> { ) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More}; use self::StepResult::{Busy, Done, Locked, More};
let mut dst = try!(Connection::open(dst_path)); let mut dst = Connection::open(dst_path)?;
let backup = try!(Backup::new_with_names( let backup = Backup::new_with_names(self, name, &mut dst, DatabaseName::Main)?;
self,
name,
&mut dst,
DatabaseName::Main
));
let mut r = More; let mut r = More;
while r == More { while r == More {
r = try!(backup.step(100)); r = backup.step(100)?;
if let Some(f) = progress { if let Some(f) = progress {
f(backup.progress()); f(backup.progress());
} }
@ -100,18 +95,18 @@ impl Connection {
/// or if the restore fails. /// or if the restore fails.
pub fn restore<P: AsRef<Path>, F: Fn(Progress)>( pub fn restore<P: AsRef<Path>, F: Fn(Progress)>(
&mut self, &mut self,
name: DatabaseName, name: DatabaseName<'_>,
src_path: P, src_path: P,
progress: Option<F>, progress: Option<F>,
) -> Result<()> { ) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More}; use self::StepResult::{Busy, Done, Locked, More};
let src = try!(Connection::open(src_path)); let src = Connection::open(src_path)?;
let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name)); let restore = Backup::new_with_names(&src, DatabaseName::Main, self, name)?;
let mut r = More; let mut r = More;
let mut busy_count = 0i32; let mut busy_count = 0i32;
'restore_loop: while r == More || r == Busy { 'restore_loop: while r == More || r == Busy {
r = try!(restore.step(100)); r = restore.step(100)?;
if let Some(ref f) = progress { if let Some(ref f) = progress {
f(restore.progress()); f(restore.progress());
} }
@ -197,12 +192,12 @@ impl<'a, 'b> Backup<'a, 'b> {
/// `NULL`. /// `NULL`.
pub fn new_with_names( pub fn new_with_names(
from: &'a Connection, from: &'a Connection,
from_name: DatabaseName, from_name: DatabaseName<'_>,
to: &'b mut Connection, to: &'b mut Connection,
to_name: DatabaseName, to_name: DatabaseName<'_>,
) -> Result<Backup<'a, 'b>> { ) -> Result<Backup<'a, 'b>> {
let to_name = try!(to_name.to_cstring()); let to_name = to_name.to_cstring()?;
let from_name = try!(from_name.to_cstring()); let from_name = from_name.to_cstring()?;
let to_db = to.db.borrow_mut().db; let to_db = to.db.borrow_mut().db;
@ -287,7 +282,7 @@ impl<'a, 'b> Backup<'a, 'b> {
assert!(pages_per_step > 0, "pages_per_step must be positive"); assert!(pages_per_step > 0, "pages_per_step must be positive");
loop { loop {
let r = try!(self.step(pages_per_step)); let r = self.step(pages_per_step)?;
if let Some(progress) = progress { if let Some(progress) = progress {
progress(self.progress()) progress(self.progress())
} }
@ -308,8 +303,8 @@ impl<'a, 'b> Drop for Backup<'a, 'b> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::Backup; use super::Backup;
use crate::{Connection, DatabaseName, NO_PARAMS};
use std::time::Duration; use std::time::Duration;
use {Connection, DatabaseName, NO_PARAMS};
#[test] #[test]
fn test_backup() { fn test_backup() {

View File

@ -14,9 +14,6 @@
//! ## Example //! ## Example
//! //!
//! ```rust //! ```rust
//! extern crate libsqlite3_sys;
//! extern crate rusqlite;
//!
//! use rusqlite::blob::ZeroBlob; //! use rusqlite::blob::ZeroBlob;
//! use rusqlite::{Connection, DatabaseName, NO_PARAMS}; //! use rusqlite::{Connection, DatabaseName, NO_PARAMS};
//! use std::io::{Read, Seek, SeekFrom, Write}; //! use std::io::{Read, Seek, SeekFrom, Write};
@ -64,7 +61,7 @@ use std::ptr;
use super::ffi; use super::ffi;
use super::types::{ToSql, ToSqlOutput}; use super::types::{ToSql, ToSqlOutput};
use {Connection, DatabaseName, Result}; use crate::{Connection, DatabaseName, Result};
/// Handle to an open BLOB. /// Handle to an open BLOB.
pub struct Blob<'conn> { pub struct Blob<'conn> {
@ -84,7 +81,7 @@ impl Connection {
/// fails. /// fails.
pub fn blob_open<'a>( pub fn blob_open<'a>(
&'a self, &'a self,
db: DatabaseName, db: DatabaseName<'_>,
table: &str, table: &str,
column: &str, column: &str,
row_id: i64, row_id: i64,
@ -92,9 +89,9 @@ impl Connection {
) -> Result<Blob<'a>> { ) -> Result<Blob<'a>> {
let mut c = self.db.borrow_mut(); let mut c = self.db.borrow_mut();
let mut blob = ptr::null_mut(); let mut blob = ptr::null_mut();
let db = try!(db.to_cstring()); let db = db.to_cstring()?;
let table = try!(super::str_to_cstring(table)); let table = super::str_to_cstring(table)?;
let column = try!(super::str_to_cstring(column)); let column = super::str_to_cstring(column)?;
let rc = unsafe { let rc = unsafe {
ffi::sqlite3_blob_open( ffi::sqlite3_blob_open(
c.db(), c.db(),
@ -254,7 +251,7 @@ impl<'conn> Drop for Blob<'conn> {
pub struct ZeroBlob(pub i32); pub struct ZeroBlob(pub i32);
impl ToSql for ZeroBlob { impl ToSql for ZeroBlob {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let ZeroBlob(length) = *self; let ZeroBlob(length) = *self;
Ok(ToSqlOutput::ZeroBlob(length)) Ok(ToSqlOutput::ZeroBlob(length))
} }
@ -262,16 +259,16 @@ impl ToSql for ZeroBlob {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{Connection, DatabaseName, Result};
use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write}; use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
use {Connection, DatabaseName, Result};
fn db_with_test_blob() -> Result<(Connection, i64)> { fn db_with_test_blob() -> Result<(Connection, i64)> {
let db = try!(Connection::open_in_memory()); let db = Connection::open_in_memory()?;
let sql = "BEGIN; let sql = "BEGIN;
CREATE TABLE test (content BLOB); CREATE TABLE test (content BLOB);
INSERT INTO test VALUES (ZEROBLOB(10)); INSERT INTO test VALUES (ZEROBLOB(10));
END;"; END;";
try!(db.execute_batch(sql)); db.execute_batch(sql)?;
let rowid = db.last_insert_rowid(); let rowid = db.last_insert_rowid();
Ok((db, rowid)) Ok((db, rowid))
} }

View File

@ -1,11 +1,12 @@
///! Busy handler (when the database is locked) ///! Busy handler (when the database is locked)
use std::mem; use std::mem;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::panic::catch_unwind;
use std::ptr; use std::ptr;
use std::time::Duration; use std::time::Duration;
use ffi; use crate::ffi;
use {Connection, InnerConnection, Result}; use crate::{Connection, InnerConnection, Result};
impl Connection { impl Connection {
/// Set a busy handler that sleeps for a specified amount of time when a /// Set a busy handler that sleeps for a specified amount of time when a
@ -48,7 +49,7 @@ impl Connection {
pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> { 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 { 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); let handler_fn: fn(i32) -> bool = mem::transmute(p_arg);
if handler_fn(count) { if let Ok(true) = catch_unwind(|| handler_fn(count)) {
1 1
} else { } else {
0 0
@ -74,14 +75,14 @@ impl InnerConnection {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
extern crate tempdir;
use self::tempdir::TempDir; use self::tempdir::TempDir;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::sync_channel; use std::sync::mpsc::sync_channel;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use tempdir;
use {Connection, Error, ErrorCode, TransactionBehavior, NO_PARAMS}; use crate::{Connection, Error, ErrorCode, TransactionBehavior, NO_PARAMS};
#[test] #[test]
fn test_default_busy() { fn test_default_busy() {

View File

@ -1,10 +1,10 @@
//! Prepared statements cache for faster execution. //! Prepared statements cache for faster execution.
use crate::raw_statement::RawStatement;
use crate::{Connection, Result, Statement};
use lru_cache::LruCache; use lru_cache::LruCache;
use raw_statement::RawStatement;
use std::cell::RefCell; use std::cell::RefCell;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use {Connection, Result, Statement};
impl Connection { impl Connection {
/// Prepare a SQL statement for execution, returning a previously prepared /// Prepare a SQL statement for execution, returning a previously prepared
@ -16,14 +16,14 @@ impl Connection {
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// fn insert_new_people(conn: &Connection) -> Result<()> { /// fn insert_new_people(conn: &Connection) -> Result<()> {
/// { /// {
/// let mut stmt = try!(conn.prepare_cached("INSERT INTO People (name) VALUES (?)")); /// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
/// try!(stmt.execute(&["Joe Smith"])); /// stmt.execute(&["Joe Smith"])?;
/// } /// }
/// { /// {
/// // This will return the same underlying SQLite statement handle without /// // This will return the same underlying SQLite statement handle without
/// // having to prepare it again. /// // having to prepare it again.
/// let mut stmt = try!(conn.prepare_cached("INSERT INTO People (name) VALUES (?)")); /// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
/// try!(stmt.execute(&["Bob Jones"])); /// stmt.execute(&["Bob Jones"])?;
/// } /// }
/// Ok(()) /// Ok(())
/// } /// }
@ -33,7 +33,7 @@ impl Connection {
/// ///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string /// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails. /// or if the underlying SQLite call fails.
pub fn prepare_cached<'a>(&'a self, sql: &str) -> Result<CachedStatement<'a>> { pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>> {
self.cache.get(self, sql) self.cache.get(self, sql)
} }
@ -152,7 +152,7 @@ impl StatementCache {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::StatementCache; use super::StatementCache;
use {Connection, NO_PARAMS}; use crate::{Connection, NO_PARAMS};
impl StatementCache { impl StatementCache {
fn clear(&self) { fn clear(&self) {

View File

@ -1,69 +1,18 @@
//! Code related to `sqlite3_context` common to `functions` and `vtab` modules. //! Code related to `sqlite3_context` common to `functions` and `vtab` modules.
use std::ffi::CStr; use std::os::raw::{c_int, c_void};
use std::os::raw::{c_char, c_int, c_void};
#[cfg(feature = "array")] #[cfg(feature = "array")]
use std::rc::Rc; use std::rc::Rc;
use ffi; use crate::ffi;
use ffi::sqlite3_context; use crate::ffi::sqlite3_context;
use ffi::sqlite3_value;
use str_to_cstring; use crate::str_to_cstring;
use types::{ToSqlOutput, ValueRef}; use crate::types::{ToSqlOutput, ValueRef};
#[cfg(feature = "array")] #[cfg(feature = "array")]
use vtab::array::{free_array, ARRAY_TYPE}; use crate::vtab::array::{free_array, ARRAY_TYPE};
impl<'a> ValueRef<'a> { pub(crate) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<'_>) {
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 { let value = match *result {
ToSqlOutput::Borrowed(v) => v, ToSqlOutput::Borrowed(v) => v,
ToSqlOutput::Owned(ref v) => ValueRef::from(v), ToSqlOutput::Owned(ref v) => ValueRef::from(v),

View File

@ -1,18 +1,14 @@
use crate::types::Type;
use crate::{errmsg_to_string, ffi};
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::os::raw::c_int; use std::os::raw::c_int;
use std::path::PathBuf; use std::path::PathBuf;
use std::str; use std::str;
use types::Type;
use {errmsg_to_string, ffi};
/// Old name for `Error`. `SqliteError` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Error instead")]
pub type SqliteError = Error;
/// Enum listing possible errors from rusqlite. /// Enum listing possible errors from rusqlite.
#[derive(Debug)] #[derive(Debug)]
#[allow(enum_variant_names)] #[allow(clippy::enum_variant_names)]
pub enum Error { pub enum Error {
/// An error from an underlying SQLite call. /// An error from an underlying SQLite call.
SqliteFailure(ffi::Error, Option<String>), SqliteFailure(ffi::Error, Option<String>),
@ -23,7 +19,7 @@ pub enum Error {
/// Error when the value of a particular column is requested, but it cannot /// Error when the value of a particular column is requested, but it cannot
/// be converted to the requested Rust type. /// be converted to the requested Rust type.
FromSqlConversionFailure(usize, Type, Box<error::Error + Send + Sync>), FromSqlConversionFailure(usize, Type, Box<dyn error::Error + Send + Sync>),
/// Error when SQLite gives us an integral value outside the range of the /// 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`). /// requested type (e.g., trying to get the value 1000 into a `u8`).
@ -82,10 +78,10 @@ pub enum Error {
/// `create_scalar_function`). /// `create_scalar_function`).
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
#[allow(dead_code)] #[allow(dead_code)]
UserFunctionError(Box<error::Error + Send + Sync>), UserFunctionError(Box<dyn error::Error + Send + Sync>),
/// Error available for the implementors of the `ToSql` trait. /// Error available for the implementors of the `ToSql` trait.
ToSqlConversionFailure(Box<error::Error + Send + Sync>), ToSqlConversionFailure(Box<dyn error::Error + Send + Sync>),
/// Error when the SQL is not a `SELECT`, is not read-only. /// Error when the SQL is not a `SELECT`, is not read-only.
InvalidQuery, InvalidQuery,
@ -96,8 +92,57 @@ pub enum Error {
#[allow(dead_code)] #[allow(dead_code)]
ModuleError(String), ModuleError(String),
#[cfg(feature = "functions")]
UnwindingPanic,
/// An error returned when `Context::get_aux` attempts to retrieve data
/// of a different type than what had been stored using `Context::set_aux`.
#[cfg(feature = "functions")]
GetAuxWrongType,
/// Error when the SQL contains multiple statements. /// Error when the SQL contains multiple statements.
MultipleStatement, MultipleStatement,}
impl PartialEq for Error {
fn eq(&self, other: &Error) -> bool {
match (self, other) {
(Error::SqliteFailure(e1, s1), Error::SqliteFailure(e2, s2)) => e1 == e2 && s1 == s2,
(Error::SqliteSingleThreadedMode, Error::SqliteSingleThreadedMode) => true,
(Error::IntegralValueOutOfRange(i1, n1), Error::IntegralValueOutOfRange(i2, n2)) => {
i1 == i2 && n1 == n2
}
(Error::Utf8Error(e1), Error::Utf8Error(e2)) => e1 == e2,
(Error::NulError(e1), Error::NulError(e2)) => e1 == e2,
(Error::InvalidParameterName(n1), Error::InvalidParameterName(n2)) => n1 == n2,
(Error::InvalidPath(p1), Error::InvalidPath(p2)) => p1 == p2,
(Error::ExecuteReturnedResults, Error::ExecuteReturnedResults) => true,
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
(Error::InvalidColumnType(i1, t1), Error::InvalidColumnType(i2, t2)) => {
i1 == i2 && t1 == t2
}
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
#[cfg(feature = "functions")]
(
Error::InvalidFunctionParameterType(i1, t1),
Error::InvalidFunctionParameterType(i2, t2),
) => i1 == i2 && t1 == t2,
#[cfg(feature = "vtab")]
(
Error::InvalidFilterParameterType(i1, t1),
Error::InvalidFilterParameterType(i2, t2),
) => i1 == i2 && t1 == t2,
(Error::InvalidQuery, Error::InvalidQuery) => true,
#[cfg(feature = "vtab")]
(Error::ModuleError(s1), Error::ModuleError(s2)) => s1 == s2,
#[cfg(feature = "functions")]
(Error::UnwindingPanic, Error::UnwindingPanic) => true,
#[cfg(feature = "functions")]
(Error::GetAuxWrongType, Error::GetAuxWrongType) => true,
(_, _) => false,
}
}
} }
impl From<str::Utf8Error> for Error { impl From<str::Utf8Error> for Error {
@ -113,7 +158,7 @@ impl From<::std::ffi::NulError> for Error {
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
Error::SqliteFailure(ref err, None) => err.fmt(f), Error::SqliteFailure(ref err, None) => err.fmt(f),
Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s), Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
@ -158,6 +203,10 @@ impl fmt::Display for Error {
Error::InvalidQuery => write!(f, "Query is not read-only"), Error::InvalidQuery => write!(f, "Query is not read-only"),
#[cfg(feature = "vtab")] #[cfg(feature = "vtab")]
Error::ModuleError(ref desc) => write!(f, "{}", desc), Error::ModuleError(ref desc) => write!(f, "{}", desc),
#[cfg(feature = "functions")]
Error::UnwindingPanic => write!(f, "unwinding panic"),
#[cfg(feature = "functions")]
Error::GetAuxWrongType => write!(f, "get_aux called with wrong type"),
Error::MultipleStatement => write!(f, "Multiple statements provided"), Error::MultipleStatement => write!(f, "Multiple statements provided"),
} }
} }
@ -196,11 +245,15 @@ impl error::Error for Error {
Error::InvalidQuery => "query is not read-only", Error::InvalidQuery => "query is not read-only",
#[cfg(feature = "vtab")] #[cfg(feature = "vtab")]
Error::ModuleError(ref desc) => desc, Error::ModuleError(ref desc) => desc,
#[cfg(feature = "functions")]
Error::UnwindingPanic => "unwinding panic",
#[cfg(feature = "functions")]
Error::GetAuxWrongType => "get_aux called with wrong type",
Error::MultipleStatement => "multiple statements provided", Error::MultipleStatement => "multiple statements provided",
} }
} }
fn cause(&self) -> Option<&error::Error> { fn cause(&self) -> Option<&dyn error::Error> {
match *self { match *self {
Error::SqliteFailure(ref err, _) => Some(err), Error::SqliteFailure(ref err, _) => Some(err),
Error::Utf8Error(ref err) => Some(err), Error::Utf8Error(ref err) => Some(err),
@ -232,6 +285,12 @@ impl error::Error for Error {
#[cfg(feature = "vtab")] #[cfg(feature = "vtab")]
Error::ModuleError(_) => None, Error::ModuleError(_) => None,
#[cfg(feature = "functions")]
Error::UnwindingPanic => None,
#[cfg(feature = "functions")]
Error::GetAuxWrongType => None,
} }
} }
} }
@ -250,3 +309,12 @@ pub fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error {
}; };
error_from_sqlite_code(code, message) error_from_sqlite_code(code, message)
} }
macro_rules! check {
($funcall:expr) => {{
let rc = $funcall;
if rc != crate::ffi::SQLITE_OK {
Err(crate::error::error_from_sqlite_code(rc, None))?;
}
}};
}

View File

@ -9,10 +9,6 @@
//! module. //! module.
//! //!
//! ```rust //! ```rust
//! extern crate libsqlite3_sys;
//! extern crate rusqlite;
//! extern crate regex;
//!
//! use regex::Regex; //! use regex::Regex;
//! use rusqlite::{Connection, Error, Result, NO_PARAMS}; //! use rusqlite::{Connection, Error, Result, NO_PARAMS};
//! use std::collections::HashMap; //! use std::collections::HashMap;
@ -20,7 +16,7 @@
//! fn add_regexp_function(db: &Connection) -> Result<()> { //! fn add_regexp_function(db: &Connection) -> Result<()> {
//! let mut cached_regexes = HashMap::new(); //! let mut cached_regexes = HashMap::new();
//! db.create_scalar_function("regexp", 2, true, move |ctx| { //! db.create_scalar_function("regexp", 2, true, move |ctx| {
//! let regex_s = try!(ctx.get::<String>(0)); //! let regex_s = ctx.get::<String>(0)?;
//! let entry = cached_regexes.entry(regex_s.clone()); //! let entry = cached_regexes.entry(regex_s.clone());
//! let regex = { //! let regex = {
//! use std::collections::hash_map::Entry::{Occupied, Vacant}; //! use std::collections::hash_map::Entry::{Occupied, Vacant};
@ -33,7 +29,7 @@
//! } //! }
//! }; //! };
//! //!
//! let text = try!(ctx.get::<String>(1)); //! let text = ctx.get::<String>(1)?;
//! Ok(regex.is_match(&text)) //! Ok(regex.is_match(&text))
//! }) //! })
//! } //! }
@ -55,17 +51,18 @@
//! ``` //! ```
use std::error::Error as StdError; use std::error::Error as StdError;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe};
use std::ptr; use std::ptr;
use std::slice; use std::slice;
use ffi; use crate::ffi;
use ffi::sqlite3_context; use crate::ffi::sqlite3_context;
use ffi::sqlite3_value; use crate::ffi::sqlite3_value;
use context::set_result; use crate::context::set_result;
use types::{FromSql, FromSqlError, ToSql, ValueRef}; use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
use {str_to_cstring, Connection, Error, InnerConnection, Result}; use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) { unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
// Extended constraint error codes were added in SQLite 3.7.16. We don't have // Extended constraint error codes were added in SQLite 3.7.16. We don't have
@ -137,6 +134,10 @@ impl<'a> Context<'a> {
FromSqlError::Other(err) => { FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err) Error::FromSqlConversionFailure(idx, value.data_type(), err)
} }
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => {
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
}
}) })
} }
@ -153,32 +154,32 @@ impl<'a> Context<'a> {
/// Sets the auxilliary data associated with a particular parameter. See /// Sets the auxilliary data associated with a particular parameter. See
/// https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of /// https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of
/// this feature, or the unit tests of this module for an example. /// this feature, or the unit tests of this module for an example.
pub fn set_aux<T>(&self, arg: c_int, value: T) { pub fn set_aux<T: 'static>(&self, arg: c_int, value: T) {
let boxed = Box::into_raw(Box::new(value)); let boxed = Box::into_raw(Box::new((std::any::TypeId::of::<T>(), value)));
unsafe { unsafe {
ffi::sqlite3_set_auxdata( ffi::sqlite3_set_auxdata(
self.ctx, self.ctx,
arg, arg,
boxed as *mut c_void, boxed as *mut c_void,
Some(free_boxed_value::<T>), Some(free_boxed_value::<(std::any::TypeId, T)>),
) )
}; };
} }
/// Gets the auxilliary data that was associated with a given parameter /// Gets the auxilliary data that was associated with a given parameter
/// via `set_aux`. Returns `None` if no data has been associated. /// via `set_aux`. Returns `Ok(None)` if no data has been associated,
/// /// and .
/// # Unsafety pub fn get_aux<T: 'static>(&self, arg: c_int) -> Result<Option<&T>> {
/// let p = unsafe { ffi::sqlite3_get_auxdata(self.ctx, arg) as *mut (std::any::TypeId, T) };
/// This function is unsafe as there is no guarantee that the type `T`
/// requested matches the type `T` that was provided to `set_aux`. The
/// types must be identical.
pub unsafe fn get_aux<T>(&self, arg: c_int) -> Option<&T> {
let p = ffi::sqlite3_get_auxdata(self.ctx, arg) as *mut T;
if p.is_null() { if p.is_null() {
None Ok(None)
} else { } else {
Some(&*p) let id_val = unsafe { &*p };
if std::any::TypeId::of::<T>() != id_val.0 {
Err(Error::GetAuxWrongType)
} else {
Ok(Some(&id_val.1))
}
} }
} }
} }
@ -189,6 +190,7 @@ impl<'a> Context<'a> {
/// result. Implementations should be stateless. /// result. Implementations should be stateless.
pub trait Aggregate<A, T> pub trait Aggregate<A, T>
where where
A: RefUnwindSafe + UnwindSafe,
T: ToSql, T: ToSql,
{ {
/// Initializes the aggregation context. Will be called prior to the first /// Initializes the aggregation context. Will be called prior to the first
@ -198,14 +200,14 @@ where
/// "step" function called once for each row in an aggregate group. May be /// "step" function called once for each row in an aggregate group. May be
/// called 0 times if there are no rows. /// called 0 times if there are no rows.
fn step(&self, &mut Context, &mut A) -> Result<()>; fn step(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
/// Computes and returns the final result. Will be called exactly once for /// Computes and returns the final result. Will be called exactly once for
/// each invocation of the function. If `step()` was called at least /// each invocation of the function. If `step()` was called at least
/// once, will be given `Some(A)` (the same `A` as was created by /// once, will be given `Some(A)` (the same `A` as was created by
/// `init` and given to `step`); if `step()` was not called (because /// `init` and given to `step`); if `step()` was not called (because
/// the function is running against 0 rows), will be given `None`. /// the function is running against 0 rows), will be given `None`.
fn finalize(&self, Option<A>) -> Result<T>; fn finalize(&self, _: Option<A>) -> Result<T>;
} }
impl Connection { impl Connection {
@ -224,12 +226,12 @@ impl Connection {
/// ```rust /// ```rust
/// # use rusqlite::{Connection, Result, NO_PARAMS}; /// # use rusqlite::{Connection, Result, NO_PARAMS};
/// fn scalar_function_example(db: Connection) -> Result<()> { /// fn scalar_function_example(db: Connection) -> Result<()> {
/// try!(db.create_scalar_function("halve", 1, true, |ctx| { /// db.create_scalar_function("halve", 1, true, |ctx| {
/// let value = try!(ctx.get::<f64>(0)); /// let value = ctx.get::<f64>(0)?;
/// Ok(value / 2f64) /// Ok(value / 2f64)
/// })); /// })?;
/// ///
/// let six_halved: f64 = try!(db.query_row("SELECT halve(6)", NO_PARAMS, |r| r.get(0))); /// let six_halved: f64 = db.query_row("SELECT halve(6)", NO_PARAMS, |r| r.get(0))?;
/// assert_eq!(six_halved, 3f64); /// assert_eq!(six_halved, 3f64);
/// Ok(()) /// Ok(())
/// } /// }
@ -246,7 +248,7 @@ impl Connection {
x_func: F, x_func: F,
) -> Result<()> ) -> Result<()>
where where
F: FnMut(&Context) -> Result<T> + Send + 'static, F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'static,
T: ToSql, T: ToSql,
{ {
self.db self.db
@ -267,6 +269,7 @@ impl Connection {
aggr: D, aggr: D,
) -> Result<()> ) -> Result<()>
where where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>, D: Aggregate<A, T>,
T: ToSql, T: ToSql,
{ {
@ -297,7 +300,7 @@ impl InnerConnection {
x_func: F, x_func: F,
) -> Result<()> ) -> Result<()>
where where
F: FnMut(&Context) -> Result<T> + Send + 'static, F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'static,
T: ToSql, T: ToSql,
{ {
unsafe extern "C" fn call_boxed_closure<F, T>( unsafe extern "C" fn call_boxed_closure<F, T>(
@ -305,28 +308,36 @@ impl InnerConnection {
argc: c_int, argc: c_int,
argv: *mut *mut sqlite3_value, argv: *mut *mut sqlite3_value,
) where ) where
F: FnMut(&Context) -> Result<T>, F: FnMut(&Context<'_>) -> Result<T>,
T: ToSql, T: ToSql,
{ {
let ctx = Context { let r = catch_unwind(|| {
ctx, let boxed_f: *mut F = ffi::sqlite3_user_data(ctx) as *mut F;
args: slice::from_raw_parts(argv, argc as usize), assert!(!boxed_f.is_null(), "Internal error - null function pointer");
let ctx = Context {
ctx,
args: slice::from_raw_parts(argv, argc as usize),
};
(*boxed_f)(&ctx)
});
let t = match r {
Err(_) => {
report_error(ctx, &Error::UnwindingPanic);
return;
}
Ok(r) => r,
}; };
let boxed_f: *mut F = ffi::sqlite3_user_data(ctx.ctx) as *mut F;
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
let t = (*boxed_f)(&ctx);
let t = t.as_ref().map(|t| ToSql::to_sql(t)); let t = t.as_ref().map(|t| ToSql::to_sql(t));
match t { match t {
Ok(Ok(ref value)) => set_result(ctx.ctx, value), Ok(Ok(ref value)) => set_result(ctx, value),
Ok(Err(err)) => report_error(ctx.ctx, &err), Ok(Err(err)) => report_error(ctx, &err),
Err(err) => report_error(ctx.ctx, err), Err(err) => report_error(ctx, err),
} }
} }
let boxed_f: *mut F = Box::into_raw(Box::new(x_func)); let boxed_f: *mut F = Box::into_raw(Box::new(x_func));
let c_name = try!(str_to_cstring(fn_name)); let c_name = str_to_cstring(fn_name)?;
let mut flags = ffi::SQLITE_UTF8; let mut flags = ffi::SQLITE_UTF8;
if deterministic { if deterministic {
flags |= ffi::SQLITE_DETERMINISTIC; flags |= ffi::SQLITE_DETERMINISTIC;
@ -355,6 +366,7 @@ impl InnerConnection {
aggr: D, aggr: D,
) -> Result<()> ) -> Result<()>
where where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>, D: Aggregate<A, T>,
T: ToSql, T: ToSql,
{ {
@ -374,15 +386,10 @@ impl InnerConnection {
argc: c_int, argc: c_int,
argv: *mut *mut sqlite3_value, argv: *mut *mut sqlite3_value,
) where ) where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>, D: Aggregate<A, T>,
T: ToSql, 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"
);
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) { let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
Some(pac) => pac, Some(pac) => pac,
None => { None => {
@ -391,32 +398,40 @@ impl InnerConnection {
} }
}; };
if (*pac as *mut A).is_null() { let r = catch_unwind(|| {
*pac = Box::into_raw(Box::new((*boxed_aggr).init())); let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
} assert!(
!boxed_aggr.is_null(),
let mut ctx = Context { "Internal error - null aggregate pointer"
ctx, );
args: slice::from_raw_parts(argv, argc as usize), if (*pac as *mut A).is_null() {
*pac = Box::into_raw(Box::new((*boxed_aggr).init()));
}
let mut ctx = Context {
ctx,
args: slice::from_raw_parts(argv, argc as usize),
};
(*boxed_aggr).step(&mut ctx, &mut **pac)
});
let r = match r {
Err(_) => {
report_error(ctx, &Error::UnwindingPanic);
return;
}
Ok(r) => r,
}; };
match r {
match (*boxed_aggr).step(&mut ctx, &mut **pac) {
Ok(_) => {} Ok(_) => {}
Err(err) => report_error(ctx.ctx, &err), Err(err) => report_error(ctx, &err),
}; };
} }
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context) unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
where where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>, D: Aggregate<A, T>,
T: ToSql, 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"
);
// Within the xFinal callback, it is customary to set N=0 in calls to // Within the xFinal callback, it is customary to set N=0 in calls to
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur. // sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
let a: Option<A> = match aggregate_context(ctx, 0) { let a: Option<A> = match aggregate_context(ctx, 0) {
@ -431,7 +446,21 @@ impl InnerConnection {
None => None, None => None,
}; };
let t = (*boxed_aggr).finalize(a); let r = catch_unwind(|| {
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
(*boxed_aggr).finalize(a)
});
let t = match r {
Err(_) => {
report_error(ctx, &Error::UnwindingPanic);
return;
}
Ok(r) => r,
};
let t = t.as_ref().map(|t| ToSql::to_sql(t)); let t = t.as_ref().map(|t| ToSql::to_sql(t));
match t { match t {
Ok(Ok(ref value)) => set_result(ctx, value), Ok(Ok(ref value)) => set_result(ctx, value),
@ -441,7 +470,7 @@ impl InnerConnection {
} }
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr)); let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
let c_name = try!(str_to_cstring(fn_name)); let c_name = str_to_cstring(fn_name)?;
let mut flags = ffi::SQLITE_UTF8; let mut flags = ffi::SQLITE_UTF8;
if deterministic { if deterministic {
flags |= ffi::SQLITE_DETERMINISTIC; flags |= ffi::SQLITE_DETERMINISTIC;
@ -463,7 +492,7 @@ impl InnerConnection {
} }
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> { fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
let c_name = try!(str_to_cstring(fn_name)); let c_name = str_to_cstring(fn_name)?;
let r = unsafe { let r = unsafe {
ffi::sqlite3_create_function_v2( ffi::sqlite3_create_function_v2(
self.db(), self.db(),
@ -483,19 +512,19 @@ impl InnerConnection {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
extern crate regex; use regex;
use self::regex::Regex; use self::regex::Regex;
use std::collections::HashMap; use std::collections::HashMap;
use std::f64::EPSILON; use std::f64::EPSILON;
use std::os::raw::c_double; use std::os::raw::c_double;
use functions::{Aggregate, Context}; use crate::functions::{Aggregate, Context};
use {Connection, Error, Result, NO_PARAMS}; use crate::{Connection, Error, Result, NO_PARAMS};
fn half(ctx: &Context) -> Result<c_double> { fn half(ctx: &Context<'_>) -> Result<c_double> {
assert!(ctx.len() == 1, "called with unexpected number of arguments"); assert_eq!(ctx.len(), 1, "called with unexpected number of arguments");
let value = try!(ctx.get::<c_double>(0)); let value = ctx.get::<c_double>(0)?;
Ok(value / 2f64) Ok(value / 2f64)
} }
@ -523,13 +552,13 @@ mod test {
// This implementation of a regexp scalar function uses SQLite's auxilliary data // This implementation of a regexp scalar function uses SQLite's auxilliary data
// (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular // (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular
// expression multiple times within one query. // expression multiple times within one query.
fn regexp_with_auxilliary(ctx: &Context) -> Result<bool> { fn regexp_with_auxilliary(ctx: &Context<'_>) -> Result<bool> {
assert!(ctx.len() == 2, "called with unexpected number of arguments"); assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
let saved_re: Option<&Regex> = unsafe { ctx.get_aux(0) }; let saved_re: Option<&Regex> = ctx.get_aux(0)?;
let new_re = match saved_re { let new_re = match saved_re {
None => { None => {
let s = try!(ctx.get::<String>(0)); let s = ctx.get::<String>(0)?;
match Regex::new(&s) { match Regex::new(&s) {
Ok(r) => Some(r), Ok(r) => Some(r),
Err(err) => return Err(Error::UserFunctionError(Box::new(err))), Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
@ -605,9 +634,9 @@ mod test {
// until the function is removed. // until the function is removed.
let mut cached_regexes = HashMap::new(); let mut cached_regexes = HashMap::new();
db.create_scalar_function("regexp", 2, true, move |ctx| { db.create_scalar_function("regexp", 2, true, move |ctx| {
assert!(ctx.len() == 2, "called with unexpected number of arguments"); assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
let regex_s = try!(ctx.get::<String>(0)); let regex_s = ctx.get::<String>(0)?;
let entry = cached_regexes.entry(regex_s.clone()); let entry = cached_regexes.entry(regex_s.clone());
let regex = { let regex = {
use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::Entry::{Occupied, Vacant};
@ -620,7 +649,7 @@ mod test {
} }
}; };
let text = try!(ctx.get::<String>(1)); let text = ctx.get::<String>(1)?;
Ok(regex.is_match(&text)) Ok(regex.is_match(&text))
}) })
.unwrap(); .unwrap();
@ -648,7 +677,7 @@ mod test {
let mut ret = String::new(); let mut ret = String::new();
for idx in 0..ctx.len() { for idx in 0..ctx.len() {
let s = try!(ctx.get::<String>(idx)); let s = ctx.get::<String>(idx)?;
ret.push_str(&s); ret.push_str(&s);
} }
@ -666,6 +695,31 @@ mod test {
} }
} }
#[test]
fn test_get_aux_type_checking() {
let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("example", 2, false, |ctx| {
if !ctx.get::<bool>(1)? {
ctx.set_aux::<i64>(0, 100);
} else {
assert_eq!(ctx.get_aux::<String>(0), Err(Error::GetAuxWrongType));
assert_eq!(ctx.get_aux::<i64>(0), Ok(Some(&100)));
}
Ok(true)
})
.unwrap();
let res: bool = db
.query_row(
"SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)",
NO_PARAMS,
|r| r.get(0),
)
.unwrap();
// Doesn't actually matter, we'll assert in the function if there's a problem.
assert!(res);
}
struct Sum; struct Sum;
struct Count; struct Count;
@ -674,8 +728,8 @@ mod test {
0 0
} }
fn step(&self, ctx: &mut Context, sum: &mut i64) -> Result<()> { fn step(&self, ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
*sum += try!(ctx.get::<i64>(0)); *sum += ctx.get::<i64>(0)?;
Ok(()) Ok(())
} }
@ -689,7 +743,7 @@ mod test {
0 0
} }
fn step(&self, _ctx: &mut Context, sum: &mut i64) -> Result<()> { fn step(&self, _ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
*sum += 1; *sum += 1;
Ok(()) Ok(())
} }

View File

@ -2,89 +2,29 @@
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::panic::catch_unwind;
use std::ptr; use std::ptr;
use ffi; use crate::ffi;
use {Connection, InnerConnection}; use crate::{Connection, InnerConnection};
/// Authorizer Action Codes /// Action Codes
#[derive(Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
#[repr(i32)]
pub enum Action { pub enum Action {
UNKNOWN = -1, UNKNOWN = -1,
SQLITE_CREATE_INDEX = ffi::SQLITE_CREATE_INDEX as isize, SQLITE_DELETE = ffi::SQLITE_DELETE,
SQLITE_CREATE_TABLE = ffi::SQLITE_CREATE_TABLE as isize, SQLITE_INSERT = ffi::SQLITE_INSERT,
SQLITE_CREATE_TEMP_INDEX = ffi::SQLITE_CREATE_TEMP_INDEX as isize, SQLITE_UPDATE = ffi::SQLITE_UPDATE,
SQLITE_CREATE_TEMP_TABLE = ffi::SQLITE_CREATE_TEMP_TABLE as isize,
SQLITE_CREATE_TEMP_TRIGGER = ffi::SQLITE_CREATE_TEMP_TRIGGER as isize,
SQLITE_CREATE_TEMP_VIEW = ffi::SQLITE_CREATE_TEMP_VIEW as isize,
SQLITE_CREATE_TRIGGER = ffi::SQLITE_CREATE_TRIGGER as isize,
SQLITE_CREATE_VIEW = ffi::SQLITE_CREATE_VIEW as isize,
SQLITE_DELETE = ffi::SQLITE_DELETE as isize,
SQLITE_DROP_INDEX = ffi::SQLITE_DROP_INDEX as isize,
SQLITE_DROP_TABLE = ffi::SQLITE_DROP_TABLE as isize,
SQLITE_DROP_TEMP_INDEX = ffi::SQLITE_DROP_TEMP_INDEX as isize,
SQLITE_DROP_TEMP_TABLE = ffi::SQLITE_DROP_TEMP_TABLE as isize,
SQLITE_DROP_TEMP_TRIGGER = ffi::SQLITE_DROP_TEMP_TRIGGER as isize,
SQLITE_DROP_TEMP_VIEW = ffi::SQLITE_DROP_TEMP_VIEW as isize,
SQLITE_DROP_TRIGGER = ffi::SQLITE_DROP_TRIGGER as isize,
SQLITE_DROP_VIEW = ffi::SQLITE_DROP_VIEW as isize,
SQLITE_INSERT = ffi::SQLITE_INSERT as isize,
SQLITE_PRAGMA = ffi::SQLITE_PRAGMA as isize,
SQLITE_READ = ffi::SQLITE_READ as isize,
SQLITE_SELECT = ffi::SQLITE_SELECT as isize,
SQLITE_TRANSACTION = ffi::SQLITE_TRANSACTION as isize,
SQLITE_UPDATE = ffi::SQLITE_UPDATE as isize,
SQLITE_ATTACH = ffi::SQLITE_ATTACH as isize,
SQLITE_DETACH = ffi::SQLITE_DETACH as isize,
SQLITE_ALTER_TABLE = ffi::SQLITE_ALTER_TABLE as isize,
SQLITE_REINDEX = ffi::SQLITE_REINDEX as isize,
SQLITE_ANALYZE = ffi::SQLITE_ANALYZE as isize,
SQLITE_CREATE_VTABLE = ffi::SQLITE_CREATE_VTABLE as isize,
SQLITE_DROP_VTABLE = ffi::SQLITE_DROP_VTABLE as isize,
SQLITE_FUNCTION = ffi::SQLITE_FUNCTION as isize,
SQLITE_SAVEPOINT = ffi::SQLITE_SAVEPOINT as isize,
SQLITE_COPY = ffi::SQLITE_COPY as isize,
SQLITE_RECURSIVE = 33,
} }
impl From<i32> for Action { impl From<i32> for Action {
fn from(code: i32) -> Action { fn from(code: i32) -> Action {
match code { match code {
ffi::SQLITE_CREATE_INDEX => Action::SQLITE_CREATE_INDEX,
ffi::SQLITE_CREATE_TABLE => Action::SQLITE_CREATE_TABLE,
ffi::SQLITE_CREATE_TEMP_INDEX => Action::SQLITE_CREATE_TEMP_INDEX,
ffi::SQLITE_CREATE_TEMP_TABLE => Action::SQLITE_CREATE_TEMP_TABLE,
ffi::SQLITE_CREATE_TEMP_TRIGGER => Action::SQLITE_CREATE_TEMP_TRIGGER,
ffi::SQLITE_CREATE_TEMP_VIEW => Action::SQLITE_CREATE_TEMP_VIEW,
ffi::SQLITE_CREATE_TRIGGER => Action::SQLITE_CREATE_TRIGGER,
ffi::SQLITE_CREATE_VIEW => Action::SQLITE_CREATE_VIEW,
ffi::SQLITE_DELETE => Action::SQLITE_DELETE, ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
ffi::SQLITE_DROP_INDEX => Action::SQLITE_DROP_INDEX,
ffi::SQLITE_DROP_TABLE => Action::SQLITE_DROP_TABLE,
ffi::SQLITE_DROP_TEMP_INDEX => Action::SQLITE_DROP_TEMP_INDEX,
ffi::SQLITE_DROP_TEMP_TABLE => Action::SQLITE_DROP_TEMP_TABLE,
ffi::SQLITE_DROP_TEMP_TRIGGER => Action::SQLITE_DROP_TEMP_TRIGGER,
ffi::SQLITE_DROP_TEMP_VIEW => Action::SQLITE_DROP_TEMP_VIEW,
ffi::SQLITE_DROP_TRIGGER => Action::SQLITE_DROP_TRIGGER,
ffi::SQLITE_DROP_VIEW => Action::SQLITE_DROP_VIEW,
ffi::SQLITE_INSERT => Action::SQLITE_INSERT, ffi::SQLITE_INSERT => Action::SQLITE_INSERT,
ffi::SQLITE_PRAGMA => Action::SQLITE_PRAGMA,
ffi::SQLITE_READ => Action::SQLITE_READ,
ffi::SQLITE_SELECT => Action::SQLITE_SELECT,
ffi::SQLITE_TRANSACTION => Action::SQLITE_TRANSACTION,
ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE, ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE,
ffi::SQLITE_ATTACH => Action::SQLITE_ATTACH,
ffi::SQLITE_DETACH => Action::SQLITE_DETACH,
ffi::SQLITE_ALTER_TABLE => Action::SQLITE_ALTER_TABLE,
ffi::SQLITE_REINDEX => Action::SQLITE_REINDEX,
ffi::SQLITE_ANALYZE => Action::SQLITE_ANALYZE,
ffi::SQLITE_CREATE_VTABLE => Action::SQLITE_CREATE_VTABLE,
ffi::SQLITE_DROP_VTABLE => Action::SQLITE_DROP_VTABLE,
ffi::SQLITE_FUNCTION => Action::SQLITE_FUNCTION,
ffi::SQLITE_SAVEPOINT => Action::SQLITE_SAVEPOINT,
ffi::SQLITE_COPY => Action::SQLITE_COPY,
33 => Action::SQLITE_RECURSIVE,
_ => Action::UNKNOWN, _ => Action::UNKNOWN,
} }
} }
@ -146,8 +86,11 @@ impl InnerConnection {
where where
F: FnMut() -> bool, F: FnMut() -> bool,
{ {
let boxed_hook: *mut F = p_arg as *mut F; let r = catch_unwind(|| {
if (*boxed_hook)() { let boxed_hook: *mut F = p_arg as *mut F;
(*boxed_hook)()
});
if let Ok(true) = r {
1 1
} else { } else {
0 0
@ -192,8 +135,10 @@ impl InnerConnection {
where where
F: FnMut(), F: FnMut(),
{ {
let boxed_hook: *mut F = p_arg as *mut F; let _ = catch_unwind(|| {
(*boxed_hook)(); let boxed_hook: *mut F = p_arg as *mut F;
(*boxed_hook)();
});
} }
let free_rollback_hook = if hook.is_some() { let free_rollback_hook = if hook.is_some() {
@ -239,8 +184,6 @@ impl InnerConnection {
use std::ffi::CStr; use std::ffi::CStr;
use std::str; use std::str;
let boxed_hook: *mut F = p_arg as *mut F;
let action = Action::from(action_code); let action = Action::from(action_code);
let db_name = { let db_name = {
let c_slice = CStr::from_ptr(db_str).to_bytes(); let c_slice = CStr::from_ptr(db_str).to_bytes();
@ -251,7 +194,10 @@ impl InnerConnection {
str::from_utf8_unchecked(c_slice) str::from_utf8_unchecked(c_slice)
}; };
(*boxed_hook)(action, db_name, tbl_name, row_id); let _ = catch_unwind(|| {
let boxed_hook: *mut F = p_arg as *mut F;
(*boxed_hook)(action, db_name, tbl_name, row_id);
});
} }
let free_update_hook = if hook.is_some() { let free_update_hook = if hook.is_some() {
@ -289,23 +235,23 @@ fn free_boxed_hook<F>(p: *mut c_void) {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::Action; use super::Action;
use crate::Connection;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use Connection;
#[test] #[test]
fn test_commit_hook() { fn test_commit_hook() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
lazy_static! { lazy_static! {
static ref called: AtomicBool = AtomicBool::new(false); static ref CALLED: AtomicBool = AtomicBool::new(false);
} }
db.commit_hook(Some(|| { db.commit_hook(Some(|| {
called.store(true, Ordering::Relaxed); CALLED.store(true, Ordering::Relaxed);
false false
})); }));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;") db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
.unwrap(); .unwrap();
assert!(called.load(Ordering::Relaxed)); assert!(CALLED.load(Ordering::Relaxed));
} }
#[test] #[test]
@ -326,14 +272,14 @@ mod test {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
lazy_static! { lazy_static! {
static ref called: AtomicBool = AtomicBool::new(false); static ref CALLED: AtomicBool = AtomicBool::new(false);
} }
db.rollback_hook(Some(|| { db.rollback_hook(Some(|| {
called.store(true, Ordering::Relaxed); CALLED.store(true, Ordering::Relaxed);
})); }));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;") db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
.unwrap(); .unwrap();
assert!(called.load(Ordering::Relaxed)); assert!(CALLED.load(Ordering::Relaxed));
} }
#[test] #[test]
@ -341,17 +287,17 @@ mod test {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
lazy_static! { lazy_static! {
static ref called: AtomicBool = AtomicBool::new(false); static ref CALLED: AtomicBool = AtomicBool::new(false);
} }
db.update_hook(Some(|action, db: &str, tbl: &str, row_id| { db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
assert_eq!(Action::SQLITE_INSERT, action); assert_eq!(Action::SQLITE_INSERT, action);
assert_eq!("main", db); assert_eq!("main", db);
assert_eq!("foo", tbl); assert_eq!("foo", tbl);
assert_eq!(1, row_id); assert_eq!(1, row_id);
called.store(true, Ordering::Relaxed); CALLED.store(true, Ordering::Relaxed);
})); }));
db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap(); db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap(); db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
assert!(called.load(Ordering::Relaxed)); assert!(CALLED.load(Ordering::Relaxed));
} }
} }

409
src/inner_connection.rs Normal file
View File

@ -0,0 +1,409 @@
use std::ffi::CString;
use std::mem;
use std::os::raw::c_int;
#[cfg(feature = "load_extension")]
use std::path::Path;
use std::ptr;
use std::str;
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
use std::sync::{Arc, Mutex, Once, ONCE_INIT};
use super::ffi;
use super::str_to_cstring;
use super::{Connection, InterruptHandle, OpenFlags, Result};
use crate::error::{error_from_handle, error_from_sqlite_code, Error};
use crate::raw_statement::RawStatement;
use crate::statement::Statement;
use crate::unlock_notify;
use crate::version::version_number;
pub struct InnerConnection {
pub db: *mut ffi::sqlite3,
// It's unsafe to call `sqlite3_close` while another thread is performing
// a `sqlite3_interrupt`, and vice versa, so we take this mutex during
// those functions. This protects a copy of the `db` pointer (which is
// cleared on closing), however the main copy, `db`, is unprotected.
// Otherwise, a long running query would prevent calling interrupt, as
// interrupt would only acquire the lock after the query's completion.
interrupt_lock: Arc<Mutex<*mut ffi::sqlite3>>,
#[cfg(feature = "hooks")]
pub free_commit_hook: Option<fn(*mut ::std::os::raw::c_void)>,
#[cfg(feature = "hooks")]
pub free_rollback_hook: Option<fn(*mut ::std::os::raw::c_void)>,
#[cfg(feature = "hooks")]
pub free_update_hook: Option<fn(*mut ::std::os::raw::c_void)>,
owned: bool,
}
impl InnerConnection {
#[cfg(not(feature = "hooks"))]
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
InnerConnection {
db,
interrupt_lock: Arc::new(Mutex::new(db)),
owned,
}
}
#[cfg(feature = "hooks")]
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
InnerConnection {
db,
interrupt_lock: Arc::new(Mutex::new(db)),
free_commit_hook: None,
free_rollback_hook: None,
free_update_hook: None,
owned,
}
}
pub fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result<InnerConnection> {
#[cfg(not(feature = "bundled"))]
ensure_valid_sqlite_version();
ensure_safe_sqlite_threading_mode()?;
// Replicate the check for sane open flags from SQLite, because the check in
// SQLite itself 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
);
if (1 << (flags.bits & 0x7)) & 0x46 == 0 {
return Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_MISUSE),
None,
));
}
unsafe {
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
if r != ffi::SQLITE_OK {
let e = if db.is_null() {
error_from_sqlite_code(r, None)
} else {
let e = error_from_handle(db, r);
ffi::sqlite3_close(db);
e
};
return Err(e);
}
let r = ffi::sqlite3_busy_timeout(db, 5000);
if r != ffi::SQLITE_OK {
let e = error_from_handle(db, r);
ffi::sqlite3_close(db);
return Err(e);
}
// attempt to turn on extended results code; don't fail if we can't.
ffi::sqlite3_extended_result_codes(db, 1);
Ok(InnerConnection::new(db, true))
}
}
pub fn db(&self) -> *mut ffi::sqlite3 {
self.db
}
pub fn decode_result(&mut self, code: c_int) -> Result<()> {
InnerConnection::decode_result_raw(self.db(), code)
}
fn decode_result_raw(db: *mut ffi::sqlite3, code: c_int) -> Result<()> {
if code == ffi::SQLITE_OK {
Ok(())
} else {
Err(error_from_handle(db, code))
}
}
pub fn close(&mut self) -> Result<()> {
if self.db.is_null() {
return Ok(());
}
self.remove_hooks();
let mut shared_handle = self.interrupt_lock.lock().unwrap();
assert!(
!shared_handle.is_null(),
"Bug: Somehow interrupt_lock was cleared before the DB was closed"
);
if !self.owned {
self.db = ptr::null_mut();
return Ok(());
}
unsafe {
let r = ffi::sqlite3_close(self.db);
// Need to use _raw because _guard has a reference out, and
// decode_result takes &mut self.
let r = InnerConnection::decode_result_raw(self.db, r);
if r.is_ok() {
*shared_handle = ptr::null_mut();
self.db = ptr::null_mut();
}
r
}
}
pub fn get_interrupt_handle(&self) -> InterruptHandle {
InterruptHandle {
db_lock: Arc::clone(&self.interrupt_lock),
}
}
pub fn execute_batch(&mut self, sql: &str) -> Result<()> {
let c_sql = str_to_cstring(sql)?;
unsafe {
let r = ffi::sqlite3_exec(
self.db(),
c_sql.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut(),
);
self.decode_result(r)
}
}
#[cfg(feature = "load_extension")]
pub fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
let r = unsafe { ffi::sqlite3_enable_load_extension(self.db, onoff) };
self.decode_result(r)
}
#[cfg(feature = "load_extension")]
pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> {
use std::os::raw::c_char;
let dylib_str = super::path_to_cstring(dylib_path)?;
unsafe {
let mut errmsg: *mut c_char = mem::uninitialized();
let r = if let Some(entry_point) = entry_point {
let c_entry = str_to_cstring(entry_point)?;
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)
};
if r == ffi::SQLITE_OK {
Ok(())
} else {
let message = super::errmsg_to_string(&*errmsg);
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
Err(error_from_sqlite_code(r, Some(message)))
}
}
}
pub fn last_insert_rowid(&self) -> i64 {
unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
}
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
if sql.len() >= ::std::i32::MAX as usize {
return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
}
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let c_sql = str_to_cstring(sql)?;
let len_with_nul = (sql.len() + 1) as c_int;
let mut c_tail = ptr::null();
let r = unsafe {
if cfg!(feature = "unlock_notify") {
let mut rc;
loop {
rc = ffi::sqlite3_prepare_v2(
self.db(),
c_sql.as_ptr(),
len_with_nul,
&mut c_stmt,
&mut c_tail,
);
if !unlock_notify::is_locked(self.db, rc) {
break;
}
rc = unlock_notify::wait_for_unlock_notify(self.db);
if rc != ffi::SQLITE_OK {
break;
}
}
rc
} else {
ffi::sqlite3_prepare_v2(
self.db(),
c_sql.as_ptr(),
len_with_nul,
&mut c_stmt,
&mut c_tail,
)
}
};
if !c_tail.is_null() && unsafe { *c_tail == 0 } {
// '\0' when there is no ';' at the end
c_tail = ptr::null(); // TODO ignore spaces, comments, ... at the end
}
self.decode_result(r)
.map(|_| Statement::new(conn, RawStatement::new(c_stmt, c_tail)))
}
pub fn changes(&mut self) -> usize {
unsafe { ffi::sqlite3_changes(self.db()) as usize }
}
pub fn is_autocommit(&self) -> bool {
unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
}
#[cfg(feature = "bundled")] // 3.8.6
pub 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) {}
}
impl Drop for InnerConnection {
#[allow(unused_must_use)]
fn drop(&mut self) {
use std::thread::panicking;
if let Err(e) = self.close() {
if panicking() {
eprintln!("Error while closing SQLite connection: {:?}", e);
} else {
panic!("Error while closing SQLite connection: {:?}", e);
}
}
}
}
#[cfg(not(feature = "bundled"))]
static SQLITE_VERSION_CHECK: Once = ONCE_INIT;
#[cfg(not(feature = "bundled"))]
pub static BYPASS_VERSION_CHECK: AtomicBool = ATOMIC_BOOL_INIT;
#[cfg(not(feature = "bundled"))]
fn ensure_valid_sqlite_version() {
use crate::version::version;
SQLITE_VERSION_CHECK.call_once(|| {
let version_number = version_number();
// Check our hard floor.
if version_number < 3_006_008 {
panic!("rusqlite requires SQLite 3.6.8 or newer");
}
// Check that the major version number for runtime and buildtime match.
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()
);
}
if BYPASS_VERSION_CHECK.load(Ordering::Relaxed) {
return;
}
// 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!(
"\
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()
);
}
});
}
static SQLITE_INIT: Once = ONCE_INIT;
pub static BYPASS_SQLITE_INIT: AtomicBool = ATOMIC_BOOL_INIT;
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
// Ensure SQLite was compiled in thredsafe mode.
if unsafe { ffi::sqlite3_threadsafe() == 0 } {
return Err(Error::SqliteSingleThreadedMode);
}
// Now we know SQLite is _capable_ of being in Multi-thread of Serialized mode,
// but it's possible someone configured it to be in Single-thread mode
// before calling into us. That would mean we're exposing an unsafe API via
// a safe one (in Rust terminology), which is no good. We have two options
// to protect against this, depending on the version of SQLite we're linked
// with:
//
// 1. If we're on 3.7.0 or later, we can ask SQLite for a mutex and check for
// the magic value 8. This isn't documented, but it's what SQLite
// returns for its mutex allocation function in Single-thread mode.
// 2. If we're prior to SQLite 3.7.0, AFAIK there's no way to check the
// threading mode. The check we perform for >= 3.7.0 will segfault.
// Instead, we insist on being able to call sqlite3_config and
// sqlite3_initialize ourself, ensuring we know the threading
// mode. This will fail if someone else has already initialized SQLite
// even if they initialized it safely. That's not ideal either, which is
// why we expose bypass_sqlite_initialization above.
if version_number() >= 3_007_000 {
const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8;
let is_singlethreaded = unsafe {
let mutex_ptr = ffi::sqlite3_mutex_alloc(0);
let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC;
ffi::sqlite3_mutex_free(mutex_ptr);
is_singlethreaded
};
if is_singlethreaded {
Err(Error::SqliteSingleThreadedMode)
} else {
Ok(())
}
} else {
SQLITE_INIT.call_once(|| {
if BYPASS_SQLITE_INIT.load(Ordering::Relaxed) {
return;
}
unsafe {
let msg = "\
Could not ensure safe initialization of SQLite.
To fix this, either:
* Upgrade SQLite to at least version 3.7.0
* Ensure that SQLite has been initialized in Multi-thread or Serialized mode and call
rusqlite::bypass_sqlite_initialization() prior to your first connection attempt.";
if ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) != ffi::SQLITE_OK {
panic!(msg);
}
if ffi::sqlite3_initialize() != ffi::SQLITE_OK {
panic!(msg);
}
}
});
Ok(())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,10 +2,10 @@
use std::os::raw::c_int; use std::os::raw::c_int;
use ffi; use crate::ffi;
pub use ffi::Limit; pub use crate::ffi::Limit;
use Connection; use crate::Connection;
impl Connection { impl Connection {
/// Returns the current value of a limit. /// Returns the current value of a limit.
@ -23,8 +23,8 @@ impl Connection {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use ffi::Limit; use crate::ffi::Limit;
use Connection; use crate::Connection;
#[test] #[test]
fn test_limit() { fn test_limit() {
@ -57,13 +57,13 @@ mod test {
assert_eq!(99, db.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)); assert_eq!(99, db.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER));
// SQLITE_LIMIT_TRIGGER_DEPTH was added in SQLite 3.6.18. // SQLITE_LIMIT_TRIGGER_DEPTH was added in SQLite 3.6.18.
if ::version_number() >= 3006018 { if crate::version_number() >= 3_006_018 {
db.set_limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH, 32); db.set_limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH, 32);
assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH)); assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH));
} }
// SQLITE_LIMIT_WORKER_THREADS was added in SQLite 3.8.7. // SQLITE_LIMIT_WORKER_THREADS was added in SQLite 3.8.7.
if ::version_number() >= 3008007 { if crate::version_number() >= 3_008_007 {
db.set_limit(Limit::SQLITE_LIMIT_WORKER_THREADS, 2); db.set_limit(Limit::SQLITE_LIMIT_WORKER_THREADS, 2);
assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS)); assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS));
} }

View File

@ -1,8 +1,4 @@
use {Connection, Result}; use crate::{Connection, Result};
/// Old name for `LoadExtensionGuard`. `SqliteLoadExtensionGuard` is deprecated.
#[deprecated(since = "0.6.0", note = "Use LoadExtensionGuard instead")]
pub type SqliteLoadExtensionGuard<'conn> = LoadExtensionGuard<'conn>;
/// RAII guard temporarily enabling SQLite extensions to be loaded. /// RAII guard temporarily enabling SQLite extensions to be loaded.
/// ///
@ -12,7 +8,7 @@ pub type SqliteLoadExtensionGuard<'conn> = LoadExtensionGuard<'conn>;
/// # use rusqlite::{Connection, Result, LoadExtensionGuard}; /// # use rusqlite::{Connection, Result, LoadExtensionGuard};
/// # use std::path::{Path}; /// # use std::path::{Path};
/// fn load_my_extension(conn: &Connection) -> Result<()> { /// fn load_my_extension(conn: &Connection) -> Result<()> {
/// let _guard = try!(LoadExtensionGuard::new(conn)); /// let _guard = LoadExtensionGuard::new(conn)?;
/// ///
/// conn.load_extension(Path::new("my_sqlite_extension"), None) /// conn.load_extension(Path::new("my_sqlite_extension"), None)
/// } /// }
@ -25,7 +21,7 @@ impl<'conn> LoadExtensionGuard<'conn> {
/// Attempt to enable loading extensions. Loading extensions will be /// Attempt to enable loading extensions. Loading extensions will be
/// disabled when this guard goes out of scope. Cannot be meaningfully /// disabled when this guard goes out of scope. Cannot be meaningfully
/// nested. /// nested.
pub fn new(conn: &Connection) -> Result<LoadExtensionGuard> { pub fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
conn.load_extension_enable() conn.load_extension_enable()
.map(|_| LoadExtensionGuard { conn }) .map(|_| LoadExtensionGuard { conn })
} }

View File

@ -1,5 +1,6 @@
use super::ffi; use super::ffi;
use super::unlock_notify; use super::unlock_notify;
use super::StatementStatus;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
use std::ptr; use std::ptr;
@ -101,6 +102,11 @@ impl RawStatement {
} }
} }
pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 {
assert!(!self.0.is_null());
unsafe { ffi::sqlite3_stmt_status(self.0, status as i32, reset as i32) }
}
pub fn has_tail(&self) -> bool { pub fn has_tail(&self) -> bool {
!self.1.is_null() !self.1.is_null()
} }

View File

@ -2,7 +2,7 @@ use std::marker::PhantomData;
use std::{convert, result}; use std::{convert, result};
use super::{Error, Result, Statement}; use super::{Error, Result, Statement};
use types::{FromSql, FromSqlError, ValueRef}; use crate::types::{FromSql, FromSqlError, ValueRef};
/// An handle for the resulting rows of a query. /// An handle for the resulting rows of a query.
pub struct Rows<'stmt> { pub struct Rows<'stmt> {
@ -27,7 +27,7 @@ impl<'stmt> Rows<'stmt> {
/// This is a "streaming iterator". For a more natural interface, /// This is a "streaming iterator". For a more natural interface,
/// consider using `query_map` or `query_and_then` instead, which /// consider using `query_map` or `query_and_then` instead, which
/// return types that implement `Iterator`. /// return types that implement `Iterator`.
#[cfg_attr(feature = "cargo-clippy", allow(should_implement_trait))] // cannot implement Iterator #[allow(clippy::should_implement_trait)] // cannot implement Iterator
pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> { pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> {
self.stmt.and_then(|stmt| match stmt.step() { self.stmt.and_then(|stmt| match stmt.step() {
Ok(true) => Some(Ok(Row { Ok(true) => Some(Ok(Row {
@ -73,7 +73,7 @@ pub struct MappedRows<'stmt, F> {
impl<'stmt, T, F> MappedRows<'stmt, F> impl<'stmt, T, F> MappedRows<'stmt, F>
where where
F: FnMut(&Row) -> T, F: FnMut(&Row<'_, '_>) -> T,
{ {
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> { pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
MappedRows { rows, map: f } MappedRows { rows, map: f }
@ -82,7 +82,7 @@ where
impl<'conn, T, F> Iterator for MappedRows<'conn, F> impl<'conn, T, F> Iterator for MappedRows<'conn, F>
where where
F: FnMut(&Row) -> T, F: FnMut(&Row<'_, '_>) -> T,
{ {
type Item = Result<T>; type Item = Result<T>;
@ -103,7 +103,7 @@ pub struct AndThenRows<'stmt, F> {
impl<'stmt, T, E, F> AndThenRows<'stmt, F> impl<'stmt, T, E, F> AndThenRows<'stmt, F>
where where
F: FnMut(&Row) -> result::Result<T, E>, F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
{ {
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> { pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
AndThenRows { rows, map: f } AndThenRows { rows, map: f }
@ -113,7 +113,7 @@ where
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F> impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
where where
E: convert::From<Error>, E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>, F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
{ {
type Item = result::Result<T, E>; type Item = result::Result<T, E>;
@ -165,7 +165,7 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
/// enabled), and the underlying SQLite column is a blob whose size is not /// enabled), and the underlying SQLite column is a blob whose size is not
/// 16 bytes, `Error::InvalidColumnType` will also be returned. /// 16 bytes, `Error::InvalidColumnType` will also be returned.
pub fn get_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> { pub fn get_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
let idx = try!(idx.idx(self.stmt)); let idx = idx.idx(self.stmt)?;
let value = self.stmt.value_ref(idx); let value = self.stmt.value_ref(idx);
FromSql::column_result(value).map_err(|err| match err { FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()), FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
@ -231,12 +231,12 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
pub trait RowIndex { pub trait RowIndex {
/// Returns the index of the appropriate column, or `None` if no such /// Returns the index of the appropriate column, or `None` if no such
/// column exists. /// column exists.
fn idx(&self, stmt: &Statement) -> Result<usize>; fn idx(&self, stmt: &Statement<'_>) -> Result<usize>;
} }
impl RowIndex for usize { impl RowIndex for usize {
#[inline] #[inline]
fn idx(&self, stmt: &Statement) -> Result<usize> { fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
if *self >= stmt.column_count() { if *self >= stmt.column_count() {
Err(Error::InvalidColumnIndex(*self)) Err(Error::InvalidColumnIndex(*self))
} else { } else {
@ -247,7 +247,7 @@ impl RowIndex for usize {
impl<'a> RowIndex for &'a str { impl<'a> RowIndex for &'a str {
#[inline] #[inline]
fn idx(&self, stmt: &Statement) -> Result<usize> { fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
stmt.column_index(*self) stmt.column_index(*self)
} }
} }

894
src/session.rs Normal file
View File

@ -0,0 +1,894 @@
//! [Session Extension](https://sqlite.org/sessionintro.html)
#![allow(non_camel_case_types)]
use std::ffi::CStr;
use std::io::{Read, Write};
use std::marker::PhantomData;
use std::mem;
use std::os::raw::{c_char, c_int, c_uchar, c_void};
use std::panic::{catch_unwind, RefUnwindSafe};
use std::ptr;
use std::slice::{from_raw_parts, from_raw_parts_mut};
use fallible_streaming_iterator::FallibleStreamingIterator;
use crate::error::error_from_sqlite_code;
use crate::ffi;
use crate::hooks::Action;
use crate::types::ValueRef;
use crate::{errmsg_to_string, str_to_cstring, Connection, DatabaseName, Result};
// https://sqlite.org/session.html
/// An instance of this object is a session that can be used to record changes
/// to a database.
pub struct Session<'conn> {
phantom: PhantomData<&'conn ()>,
s: *mut ffi::sqlite3_session,
filter: Option<Box<dyn Fn(&str) -> bool>>,
}
impl<'conn> Session<'conn> {
/// Create a new session object
pub fn new(db: &'conn Connection) -> Result<Session<'conn>> {
Session::new_with_name(db, DatabaseName::Main)
}
/// Create a new session object
pub fn new_with_name(db: &'conn Connection, name: DatabaseName<'_>) -> Result<Session<'conn>> {
let name = name.to_cstring()?;
let db = db.db.borrow_mut().db;
let mut s: *mut ffi::sqlite3_session = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) });
Ok(Session {
phantom: PhantomData,
s,
filter: None,
})
}
/// Set a table filter
pub fn table_filter<F>(&mut self, filter: Option<F>)
where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
{
unsafe extern "C" fn call_boxed_closure<F>(
p_arg: *mut c_void,
tbl_str: *const c_char,
) -> c_int
where
F: Fn(&str) -> bool + RefUnwindSafe,
{
use std::ffi::CStr;
use std::str;
let boxed_filter: *mut F = p_arg as *mut F;
let tbl_name = {
let c_slice = CStr::from_ptr(tbl_str).to_bytes();
str::from_utf8_unchecked(c_slice)
};
if let Ok(true) = catch_unwind(|| (*boxed_filter)(tbl_name)) {
1
} else {
0
}
}
match filter {
Some(filter) => {
let boxed_filter = Box::new(filter);
unsafe {
ffi::sqlite3session_table_filter(
self.s,
Some(call_boxed_closure::<F>),
&*boxed_filter as *const F as *mut _,
);
}
self.filter = Some(boxed_filter);
}
_ => {
unsafe { ffi::sqlite3session_table_filter(self.s, None, ptr::null_mut()) }
self.filter = None;
}
};
}
/// Attach a table. `None` means all tables.
pub fn attach(&mut self, table: Option<&str>) -> Result<()> {
let table = if let Some(table) = table {
str_to_cstring(table)?.as_ptr()
} else {
ptr::null()
};
unsafe { check!(ffi::sqlite3session_attach(self.s, table)) };
Ok(())
}
/// Generate a Changeset
pub fn changeset(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) });
Ok(Changeset { cs, n })
}
/// Write the set of changes represented by this session to `output`.
pub fn changeset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
check!(unsafe {
ffi::sqlite3session_changeset_strm(
self.s,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
});
Ok(())
}
/// Generate a Patchset
pub fn patchset(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut ps: *mut c_void = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) });
// TODO Validate: same struct
Ok(Changeset { cs: ps, n })
}
/// Write the set of patches represented by this session to `output`.
pub fn patchset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
check!(unsafe {
ffi::sqlite3session_patchset_strm(
self.s,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
});
Ok(())
}
/// Load the difference between tables.
pub fn diff(&mut self, from: DatabaseName<'_>, table: &str) -> Result<()> {
let from = from.to_cstring()?;
let table = str_to_cstring(table)?.as_ptr();
unsafe {
let mut errmsg: *mut c_char = mem::uninitialized();
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, &mut errmsg);
if r != ffi::SQLITE_OK {
let message = errmsg_to_string(&*errmsg);
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
return Err(error_from_sqlite_code(r, Some(message)));
}
}
Ok(())
}
/// Test if a changeset has recorded any changes
pub fn is_empty(&self) -> bool {
unsafe { ffi::sqlite3session_isempty(self.s) != 0 }
}
/// Query the current state of the session
pub fn is_enabled(&self) -> bool {
unsafe { ffi::sqlite3session_enable(self.s, -1) != 0 }
}
/// Enable or disable the recording of changes
pub fn set_enabled(&mut self, enabled: bool) {
unsafe {
ffi::sqlite3session_enable(self.s, if enabled { 1 } else { 0 });
}
}
/// Query the current state of the indirect flag
pub fn is_indirect(&self) -> bool {
unsafe { ffi::sqlite3session_indirect(self.s, -1) != 0 }
}
/// Set or clear the indirect change flag
pub fn set_indirect(&mut self, indirect: bool) {
unsafe {
ffi::sqlite3session_indirect(self.s, if indirect { 1 } else { 0 });
}
}
}
impl<'conn> Drop for Session<'conn> {
fn drop(&mut self) {
if self.filter.is_some() {
self.table_filter(None::<fn(&str) -> bool>);
}
unsafe { ffi::sqlite3session_delete(self.s) };
}
}
/// Invert a changeset
pub fn invert_strm(input: &mut dyn Read, output: &mut dyn Write) -> Result<()> {
let input_ref = &input;
let output_ref = &output;
check!(unsafe {
ffi::sqlite3changeset_invert_strm(
Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
});
Ok(())
}
/// Combine two changesets
pub fn concat_strm(
input_a: &mut dyn Read,
input_b: &mut dyn Read,
output: &mut dyn Write,
) -> Result<()> {
let input_a_ref = &input_a;
let input_b_ref = &input_b;
let output_ref = &output;
check!(unsafe {
ffi::sqlite3changeset_concat_strm(
Some(x_input),
input_a_ref as *const &mut dyn Read as *mut c_void,
Some(x_input),
input_b_ref as *const &mut dyn Read as *mut c_void,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
});
Ok(())
}
/// Changeset or Patchset
pub struct Changeset {
cs: *mut c_void,
n: c_int,
}
impl Changeset {
/// Invert a changeset
pub fn invert(&self) -> Result<Changeset> {
let mut n = 0;
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs) });
Ok(Changeset { cs, n })
}
/// Create an iterator to traverse a changeset
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3changeset_start(&mut it, self.n, self.cs) });
Ok(ChangesetIter {
phantom: PhantomData,
it,
item: None,
})
}
/// Concatenate two changeset objects
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
let mut n = 0;
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs) });
Ok(Changeset { cs, n })
}
}
impl Drop for Changeset {
fn drop(&mut self) {
unsafe {
ffi::sqlite3_free(self.cs);
}
}
}
/// Cursor for iterating over the elements of a changeset or patchset.
pub struct ChangesetIter<'changeset> {
phantom: PhantomData<&'changeset ()>,
it: *mut ffi::sqlite3_changeset_iter,
item: Option<ChangesetItem>,
}
impl<'changeset> ChangesetIter<'changeset> {
/// Create an iterator on `input`
pub fn start_strm<'input>(input: &'input mut dyn Read) -> Result<ChangesetIter<'input>> {
let input_ref = &input;
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
check!(unsafe {
ffi::sqlite3changeset_start_strm(
&mut it,
Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void,
)
});
Ok(ChangesetIter {
phantom: PhantomData,
it,
item: None,
})
}
}
impl<'changeset> FallibleStreamingIterator for ChangesetIter<'changeset> {
type Error = crate::error::Error;
type Item = ChangesetItem;
fn advance(&mut self) -> Result<()> {
let rc = unsafe { ffi::sqlite3changeset_next(self.it) };
match rc {
ffi::SQLITE_ROW => {
self.item = Some(ChangesetItem { it: self.it });
Ok(())
}
ffi::SQLITE_DONE => {
self.item = None;
Ok(())
}
code => Err(error_from_sqlite_code(code, None)),
}
}
fn get(&self) -> Option<&ChangesetItem> {
self.item.as_ref()
}
}
pub struct Operation<'item> {
table_name: &'item str,
number_of_columns: i32,
code: Action,
indirect: bool,
}
impl<'item> Operation<'item> {
pub fn table_name(&self) -> &str {
self.table_name
}
pub fn number_of_columns(&self) -> i32 {
self.number_of_columns
}
pub fn code(&self) -> Action {
self.code
}
pub fn indirect(&self) -> bool {
self.indirect
}
}
impl<'changeset> Drop for ChangesetIter<'changeset> {
fn drop(&mut self) {
unsafe {
ffi::sqlite3changeset_finalize(self.it);
}
}
}
/// An item passed to a conflict-handler by `Connection::apply`,
/// or an item generated by `ChangesetIter::next`.
// TODO enum ? Delete, Insert, Update, ...
pub struct ChangesetItem {
it: *mut ffi::sqlite3_changeset_iter,
}
impl ChangesetItem {
/// Obtain conflicting row values
///
/// May only be called with an `SQLITE_CHANGESET_DATA` or
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
check!(ffi::sqlite3changeset_conflict(
self.it,
col as i32,
&mut p_value
));
Ok(ValueRef::from_value(p_value))
}
}
/// Determine the number of foreign key constraint violations
///
/// May only be called with an `SQLITE_CHANGESET_FOREIGN_KEY` conflict
/// handler callback.
pub fn fk_conflicts(&self) -> Result<i32> {
unsafe {
let mut p_out = 0;
check!(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out));
Ok(p_out)
}
}
/// Obtain new.* Values
///
/// May only be called if the type of change is either `SQLITE_UPDATE` or
/// `SQLITE_INSERT`.
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
check!(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value));
Ok(ValueRef::from_value(p_value))
}
}
/// Obtain old.* Values
///
/// May only be called if the type of change is either `SQLITE_DELETE` or
/// `SQLITE_UPDATE`.
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
check!(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value));
Ok(ValueRef::from_value(p_value))
}
}
/// Obtain the current operation
pub fn op(&self) -> Result<Operation<'_>> {
let mut number_of_columns = 0;
let mut code = 0;
let mut indirect = 0;
let tab = unsafe {
let mut pz_tab: *const c_char = mem::uninitialized();
check!(ffi::sqlite3changeset_op(
self.it,
&mut pz_tab,
&mut number_of_columns,
&mut code,
&mut indirect
));
CStr::from_ptr(pz_tab)
};
let table_name = tab.to_str()?;
Ok(Operation {
table_name,
number_of_columns,
code: Action::from(code),
indirect: indirect != 0,
})
}
/// Obtain the primary key definition of a table
pub fn pk(&self) -> Result<&[u8]> {
let mut number_of_columns = 0;
unsafe {
let mut pks: *mut c_uchar = mem::uninitialized();
check!(ffi::sqlite3changeset_pk(
self.it,
&mut pks,
&mut number_of_columns
));
Ok(from_raw_parts(pks, number_of_columns as usize))
}
}
}
/// Used to combine two or more changesets or
/// patchsets
pub struct Changegroup {
cg: *mut ffi::sqlite3_changegroup,
}
impl Changegroup {
pub fn new() -> Result<Self> {
let mut cg: *mut ffi::sqlite3_changegroup = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) });
Ok(Changegroup { cg })
}
/// Add a changeset
pub fn add(&mut self, cs: &Changeset) -> Result<()> {
check!(unsafe { ffi::sqlite3changegroup_add(self.cg, cs.n, cs.cs) });
Ok(())
}
/// Add a changeset read from `input` to this change group.
pub fn add_stream(&mut self, input: &mut dyn Read) -> Result<()> {
let input_ref = &input;
check!(unsafe {
ffi::sqlite3changegroup_add_strm(
self.cg,
Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void,
)
});
Ok(())
}
/// Obtain a composite Changeset
pub fn output(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut output: *mut c_void = unsafe { mem::uninitialized() };
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) });
Ok(Changeset { cs: output, n })
}
/// Write the combined set of changes to `output`.
pub fn output_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
check!(unsafe {
ffi::sqlite3changegroup_output_strm(
self.cg,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
});
Ok(())
}
}
impl Drop for Changegroup {
fn drop(&mut self) {
unsafe {
ffi::sqlite3changegroup_delete(self.cg);
}
}
}
impl Connection {
/// Apply a changeset to a database
pub fn apply<F, C>(&self, cs: &Changeset, filter: Option<F>, conflict: C) -> Result<()>
where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
{
let db = self.db.borrow_mut().db;
let filtered = filter.is_some();
let tuple = &mut (filter, conflict);
check!(unsafe {
if filtered {
ffi::sqlite3changeset_apply(
db,
cs.n,
cs.cs,
Some(call_filter::<F, C>),
Some(call_conflict::<F, C>),
tuple as *mut (Option<F>, C) as *mut c_void,
)
} else {
ffi::sqlite3changeset_apply(
db,
cs.n,
cs.cs,
None,
Some(call_conflict::<F, C>),
tuple as *mut (Option<F>, C) as *mut c_void,
)
}
});
Ok(())
}
/// Apply a changeset to a database
pub fn apply_strm<F, C>(
&self,
input: &mut dyn Read,
filter: Option<F>,
conflict: C,
) -> Result<()>
where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
{
let input_ref = &input;
let db = self.db.borrow_mut().db;
let filtered = filter.is_some();
let tuple = &mut (filter, conflict);
check!(unsafe {
if filtered {
ffi::sqlite3changeset_apply_strm(
db,
Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void,
Some(call_filter::<F, C>),
Some(call_conflict::<F, C>),
tuple as *mut (Option<F>, C) as *mut c_void,
)
} else {
ffi::sqlite3changeset_apply_strm(
db,
Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void,
None,
Some(call_conflict::<F, C>),
tuple as *mut (Option<F>, C) as *mut c_void,
)
}
});
Ok(())
}
}
/// Constants passed to the conflict handler
#[repr(i32)]
#[derive(Debug, PartialEq)]
pub enum ConflictType {
UNKNOWN = -1,
SQLITE_CHANGESET_DATA = ffi::SQLITE_CHANGESET_DATA,
SQLITE_CHANGESET_NOTFOUND = ffi::SQLITE_CHANGESET_NOTFOUND,
SQLITE_CHANGESET_CONFLICT = ffi::SQLITE_CHANGESET_CONFLICT,
SQLITE_CHANGESET_CONSTRAINT = ffi::SQLITE_CHANGESET_CONSTRAINT,
SQLITE_CHANGESET_FOREIGN_KEY = ffi::SQLITE_CHANGESET_FOREIGN_KEY,
}
impl From<i32> for ConflictType {
fn from(code: i32) -> ConflictType {
match code {
ffi::SQLITE_CHANGESET_DATA => ConflictType::SQLITE_CHANGESET_DATA,
ffi::SQLITE_CHANGESET_NOTFOUND => ConflictType::SQLITE_CHANGESET_NOTFOUND,
ffi::SQLITE_CHANGESET_CONFLICT => ConflictType::SQLITE_CHANGESET_CONFLICT,
ffi::SQLITE_CHANGESET_CONSTRAINT => ConflictType::SQLITE_CHANGESET_CONSTRAINT,
ffi::SQLITE_CHANGESET_FOREIGN_KEY => ConflictType::SQLITE_CHANGESET_FOREIGN_KEY,
_ => ConflictType::UNKNOWN,
}
}
}
/// Constants returned by the conflict handler
#[repr(i32)]
#[derive(Debug, PartialEq)]
pub enum ConflictAction {
SQLITE_CHANGESET_OMIT = ffi::SQLITE_CHANGESET_OMIT,
SQLITE_CHANGESET_REPLACE = ffi::SQLITE_CHANGESET_REPLACE,
SQLITE_CHANGESET_ABORT = ffi::SQLITE_CHANGESET_ABORT,
}
unsafe extern "C" fn call_filter<F, C>(p_ctx: *mut c_void, tbl_str: *const c_char) -> c_int
where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
{
use std::ffi::CStr;
use std::str;
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
let tbl_name = {
let c_slice = CStr::from_ptr(tbl_str).to_bytes();
str::from_utf8_unchecked(c_slice)
};
match *tuple {
(Some(ref filter), _) => {
if let Ok(true) = catch_unwind(|| filter(tbl_name)) {
1
} else {
0
}
}
_ => unimplemented!(),
}
}
unsafe extern "C" fn call_conflict<F, C>(
p_ctx: *mut c_void,
e_conflict: c_int,
p: *mut ffi::sqlite3_changeset_iter,
) -> c_int
where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
{
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
let conflict_type = ConflictType::from(e_conflict);
let item = ChangesetItem { it: p };
if let Ok(action) = catch_unwind(|| (*tuple).1(conflict_type, item)) {
action as c_int
} else {
ffi::SQLITE_CHANGESET_ABORT
}
}
unsafe extern "C" fn x_input(p_in: *mut c_void, data: *mut c_void, len: *mut c_int) -> c_int {
if p_in.is_null() {
return ffi::SQLITE_MISUSE;
}
let bytes: &mut [u8] = from_raw_parts_mut(data as *mut u8, len as usize);
let input = p_in as *mut &mut dyn Read;
match (*input).read(bytes) {
Ok(n) => {
*len = n as i32; // TODO Validate: n = 0 may not mean the reader will always no longer be able to
// produce bytes.
ffi::SQLITE_OK
}
Err(_) => ffi::SQLITE_IOERR_READ, // TODO check if err is a (ru)sqlite Error => propagate
}
}
unsafe extern "C" fn x_output(p_out: *mut c_void, data: *const c_void, len: c_int) -> c_int {
if p_out.is_null() {
return ffi::SQLITE_MISUSE;
}
// The sessions module never invokes an xOutput callback with the third
// parameter set to a value less than or equal to zero.
let bytes: &[u8] = from_raw_parts(data as *const u8, len as usize);
let output = p_out as *mut &mut dyn Write;
match (*output).write_all(bytes) {
Ok(_) => ffi::SQLITE_OK,
Err(_) => ffi::SQLITE_IOERR_WRITE, // TODO check if err is a (ru)sqlite Error => propagate
}
}
#[cfg(test)]
mod test {
use fallible_streaming_iterator::FallibleStreamingIterator;
use std::sync::atomic::{AtomicBool, Ordering};
use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session};
use crate::hooks::Action;
use crate::Connection;
fn one_changeset() -> Changeset {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
.unwrap();
let mut session = Session::new(&db).unwrap();
assert!(session.is_empty());
session.attach(None).unwrap();
db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
.unwrap();
session.changeset().unwrap()
}
fn one_changeset_strm() -> Vec<u8> {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
.unwrap();
let mut session = Session::new(&db).unwrap();
assert!(session.is_empty());
session.attach(None).unwrap();
db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
.unwrap();
let mut output = Vec::new();
session.changeset_strm(&mut output).unwrap();
output
}
#[test]
fn test_changeset() {
let changeset = one_changeset();
let mut iter = changeset.iter().unwrap();
let item = iter.next().unwrap();
assert!(item.is_some());
let item = item.unwrap();
let op = item.op().unwrap();
assert_eq!("foo", op.table_name());
assert_eq!(1, op.number_of_columns());
assert_eq!(Action::SQLITE_INSERT, op.code());
assert_eq!(false, op.indirect());
let pk = item.pk().unwrap();
assert_eq!(&[1], pk);
let new_value = item.new_value(0).unwrap();
assert_eq!(Ok("bar"), new_value.as_str());
}
#[test]
fn test_changeset_strm() {
let output = one_changeset_strm();
assert!(!output.is_empty());
assert_eq!(14, output.len());
let mut input = output.as_slice();
let mut iter = ChangesetIter::start_strm(&mut input).unwrap();
let item = iter.next().unwrap();
assert!(item.is_some());
}
#[test]
fn test_changeset_apply() {
let changeset = one_changeset();
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
.unwrap();
lazy_static! {
static ref CALLED: AtomicBool = AtomicBool::new(false);
}
db.apply(
&changeset,
None::<fn(&str) -> bool>,
|_conflict_type, _item| {
CALLED.store(true, Ordering::Relaxed);
ConflictAction::SQLITE_CHANGESET_OMIT
},
)
.unwrap();
assert!(!CALLED.load(Ordering::Relaxed));
let check = db
.query_row("SELECT 1 FROM foo WHERE t = ?", &["bar"], |row| {
row.get::<_, i32>(0)
})
.unwrap();
assert_eq!(1, check);
// conflict expected when same changeset applied again on the same db
db.apply(
&changeset,
None::<fn(&str) -> bool>,
|conflict_type, item| {
CALLED.store(true, Ordering::Relaxed);
assert_eq!(ConflictType::SQLITE_CHANGESET_CONFLICT, conflict_type);
let conflict = item.conflict(0).unwrap();
assert_eq!(Ok("bar"), conflict.as_str());
ConflictAction::SQLITE_CHANGESET_OMIT
},
)
.unwrap();
assert!(CALLED.load(Ordering::Relaxed));
}
#[test]
fn test_changeset_apply_strm() {
let output = one_changeset_strm();
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
.unwrap();
db.apply_strm(
&mut output.as_slice(),
None::<fn(&str) -> bool>,
|_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
)
.unwrap();
let check = db
.query_row("SELECT 1 FROM foo WHERE t = ?", &["bar"], |row| {
row.get::<_, i32>(0)
})
.unwrap();
assert_eq!(1, check);
}
#[test]
fn test_session_empty() {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
.unwrap();
let mut session = Session::new(&db).unwrap();
assert!(session.is_empty());
session.attach(None).unwrap();
db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
.unwrap();
assert!(!session.is_empty());
}
#[test]
fn test_session_set_enabled() {
let db = Connection::open_in_memory().unwrap();
let mut session = Session::new(&db).unwrap();
assert!(session.is_enabled());
session.set_enabled(false);
assert!(!session.is_enabled());
}
#[test]
fn test_session_set_indirect() {
let db = Connection::open_in_memory().unwrap();
let mut session = Session::new(&db).unwrap();
assert!(!session.is_indirect());
session.set_indirect(true);
assert!(session.is_indirect());
}
}

View File

@ -11,9 +11,9 @@ use super::str_to_cstring;
use super::{ use super::{
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef, AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
}; };
use types::{ToSql, ToSqlOutput}; use crate::types::{ToSql, ToSqlOutput};
#[cfg(feature = "array")] #[cfg(feature = "array")]
use vtab::array::{free_array, ARRAY_TYPE}; use crate::vtab::array::{free_array, ARRAY_TYPE};
/// A prepared statement. /// A prepared statement.
pub struct Statement<'conn> { pub struct Statement<'conn> {
@ -70,10 +70,10 @@ impl<'conn> Statement<'conn> {
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// fn update_rows(conn: &Connection) -> Result<()> { /// fn update_rows(conn: &Connection) -> Result<()> {
/// let mut stmt = try!(conn.prepare("UPDATE foo SET bar = 'baz' WHERE qux = ?")); /// let mut stmt = conn.prepare("UPDATE foo SET bar = 'baz' WHERE qux = ?")?;
/// ///
/// try!(stmt.execute(&[1i32])); /// stmt.execute(&[1i32])?;
/// try!(stmt.execute(&[2i32])); /// stmt.execute(&[2i32])?;
/// ///
/// Ok(()) /// Ok(())
/// } /// }
@ -89,7 +89,7 @@ impl<'conn> Statement<'conn> {
P: IntoIterator, P: IntoIterator,
P::Item: ToSql, P::Item: ToSql,
{ {
try!(self.bind_parameters(params)); self.bind_parameters(params)?;
self.execute_with_bound_parameters() self.execute_with_bound_parameters()
} }
@ -107,18 +107,29 @@ impl<'conn> Statement<'conn> {
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// fn insert(conn: &Connection) -> Result<usize> { /// fn insert(conn: &Connection) -> Result<usize> {
/// let mut stmt = try!(conn.prepare("INSERT INTO test (name) VALUES (:name)")); /// let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?;
/// stmt.execute_named(&[(":name", &"one")]) /// stmt.execute_named(&[(":name", &"one")])
/// } /// }
/// ``` /// ```
/// ///
/// Note, the `named_params` macro is provided for syntactic convenience,
/// and so the above example could also be written as:
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result, named_params};
/// fn insert(conn: &Connection) -> Result<usize> {
/// let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?;
/// stmt.execute_named(named_params!{":name": "one"})
/// }
/// ```
///
/// # Failure /// # Failure
/// ///
/// Will return `Err` if binding parameters fails, the executed statement /// Will return `Err` if binding parameters fails, the executed statement
/// returns rows (in which case `query` should be used instead), or the /// returns rows (in which case `query` should be used instead), or the
/// underling SQLite call fails. /// underling SQLite call fails.
pub fn execute_named(&mut self, params: &[(&str, &ToSql)]) -> Result<usize> { pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
try!(self.bind_parameters_named(params)); self.bind_parameters_named(params)?;
self.execute_with_bound_parameters() self.execute_with_bound_parameters()
} }
@ -140,7 +151,7 @@ impl<'conn> Statement<'conn> {
P: IntoIterator, P: IntoIterator,
P::Item: ToSql, P::Item: ToSql,
{ {
let changes = try!(self.execute(params)); let changes = self.execute(params)?;
match changes { match changes {
1 => Ok(self.conn.last_insert_rowid()), 1 => Ok(self.conn.last_insert_rowid()),
_ => Err(Error::StatementChangedRows(changes)), _ => Err(Error::StatementChangedRows(changes)),
@ -159,12 +170,12 @@ impl<'conn> Statement<'conn> {
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result, NO_PARAMS}; /// # use rusqlite::{Connection, Result, NO_PARAMS};
/// fn get_names(conn: &Connection) -> Result<Vec<String>> { /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
/// let mut stmt = try!(conn.prepare("SELECT name FROM people")); /// let mut stmt = conn.prepare("SELECT name FROM people")?;
/// let mut rows = try!(stmt.query(NO_PARAMS)); /// let mut rows = stmt.query(NO_PARAMS)?;
/// ///
/// let mut names = Vec::new(); /// let mut names = Vec::new();
/// while let Some(result_row) = rows.next() { /// while let Some(result_row) = rows.next() {
/// let row = try!(result_row); /// let row = result_row?;
/// names.push(row.get(0)); /// names.push(row.get(0));
/// } /// }
/// ///
@ -175,13 +186,13 @@ impl<'conn> Statement<'conn> {
/// ## Failure /// ## Failure
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query<'a, P>(&'a mut self, params: P) -> Result<Rows<'a>> pub fn query<P>(&mut self, params: P) -> Result<Rows<'_>>
where where
P: IntoIterator, P: IntoIterator,
P::Item: ToSql, P::Item: ToSql,
{ {
try!(self.check_readonly()); self.check_readonly()?;
try!(self.bind_parameters(params)); self.bind_parameters(params)?;
Ok(Rows::new(self)) Ok(Rows::new(self))
} }
@ -196,8 +207,23 @@ impl<'conn> Statement<'conn> {
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// fn query(conn: &Connection) -> Result<()> { /// fn query(conn: &Connection) -> Result<()> {
/// let mut stmt = try!(conn.prepare("SELECT * FROM test where name = :name")); /// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
/// let mut rows = try!(stmt.query_named(&[(":name", &"one")])); /// let mut rows = stmt.query_named(&[(":name", &"one")])?;
/// while let Some(row) = rows.next() {
/// // ...
/// }
/// Ok(())
/// }
/// ```
///
/// Note, the `named_params!` macro is provided for syntactic convenience,
/// and so the above example could also be written as:
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result, named_params};
/// fn query(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
/// let mut rows = stmt.query_named(named_params!{ ":name": "one" })?;
/// while let Some(row) = rows.next() { /// while let Some(row) = rows.next() {
/// // ... /// // ...
/// } /// }
@ -208,9 +234,9 @@ impl<'conn> Statement<'conn> {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_named<'a>(&'a mut self, params: &[(&str, &ToSql)]) -> Result<Rows<'a>> { pub fn query_named<'a>(&'a mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'a>> {
try!(self.check_readonly()); self.check_readonly()?;
try!(self.bind_parameters_named(params)); self.bind_parameters_named(params)?;
Ok(Rows::new(self)) Ok(Rows::new(self))
} }
@ -222,12 +248,12 @@ impl<'conn> Statement<'conn> {
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result, NO_PARAMS}; /// # use rusqlite::{Connection, Result, NO_PARAMS};
/// fn get_names(conn: &Connection) -> Result<Vec<String>> { /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
/// let mut stmt = try!(conn.prepare("SELECT name FROM people")); /// let mut stmt = conn.prepare("SELECT name FROM people")?;
/// let rows = try!(stmt.query_map(NO_PARAMS, |row| row.get(0))); /// let rows = stmt.query_map(NO_PARAMS, |row| row.get(0))?;
/// ///
/// let mut names = Vec::new(); /// let mut names = Vec::new();
/// for name_result in rows { /// for name_result in rows {
/// names.push(try!(name_result)); /// names.push(name_result?);
/// } /// }
/// ///
/// Ok(names) /// Ok(names)
@ -237,11 +263,11 @@ impl<'conn> Statement<'conn> {
/// ## Failure /// ## Failure
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_map<'a, T, P, F>(&'a mut self, params: P, f: F) -> Result<MappedRows<'a, F>> pub fn query_map<T, P, F>(&mut self, params: P, f: F) -> Result<MappedRows<'_, F>>
where where
P: IntoIterator, P: IntoIterator,
P::Item: ToSql, P::Item: ToSql,
F: FnMut(&Row) -> T, F: FnMut(&Row<'_, '_>) -> T,
{ {
let rows = self.query(params)?; let rows = self.query(params)?;
Ok(MappedRows::new(rows, f)) Ok(MappedRows::new(rows, f))
@ -259,12 +285,12 @@ impl<'conn> Statement<'conn> {
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// fn get_names(conn: &Connection) -> Result<Vec<String>> { /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
/// let mut stmt = try!(conn.prepare("SELECT name FROM people WHERE id = :id")); /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
/// let rows = try!(stmt.query_map_named(&[(":id", &"one")], |row| row.get(0))); /// let rows = stmt.query_map_named(&[(":id", &"one")], |row| row.get(0))?;
/// ///
/// let mut names = Vec::new(); /// let mut names = Vec::new();
/// for name_result in rows { /// for name_result in rows {
/// names.push(try!(name_result)); /// names.push(name_result?);
/// } /// }
/// ///
/// Ok(names) /// Ok(names)
@ -276,11 +302,11 @@ impl<'conn> Statement<'conn> {
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_map_named<'a, T, F>( pub fn query_map_named<'a, T, F>(
&'a mut self, &'a mut self,
params: &[(&str, &ToSql)], params: &[(&str, &dyn ToSql)],
f: F, f: F,
) -> Result<MappedRows<'a, F>> ) -> Result<MappedRows<'a, F>>
where where
F: FnMut(&Row) -> T, F: FnMut(&Row<'_, '_>) -> T,
{ {
let rows = self.query_named(params)?; let rows = self.query_named(params)?;
Ok(MappedRows::new(rows, f)) Ok(MappedRows::new(rows, f))
@ -293,16 +319,12 @@ impl<'conn> Statement<'conn> {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_and_then<'a, T, E, P, F>( pub fn query_and_then<T, E, P, F>(&mut self, params: P, f: F) -> Result<AndThenRows<'_, F>>
&'a mut self,
params: P,
f: F,
) -> Result<AndThenRows<'a, F>>
where where
P: IntoIterator, P: IntoIterator,
P::Item: ToSql, P::Item: ToSql,
E: convert::From<Error>, E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>, F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
{ {
let rows = self.query(params)?; let rows = self.query(params)?;
Ok(AndThenRows::new(rows, f)) Ok(AndThenRows::new(rows, f))
@ -330,13 +352,13 @@ impl<'conn> Statement<'conn> {
/// } /// }
/// ///
/// fn get_names(conn: &Connection) -> Result<Vec<Person>> { /// fn get_names(conn: &Connection) -> Result<Vec<Person>> {
/// let mut stmt = try!(conn.prepare("SELECT name FROM people WHERE id = :id")); /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
/// let rows = /// let rows =
/// try!(stmt.query_and_then_named(&[(":id", &"one")], |row| name_to_person(row.get(0)))); /// stmt.query_and_then_named(&[(":id", &"one")], |row| name_to_person(row.get(0)))?;
/// ///
/// let mut persons = Vec::new(); /// let mut persons = Vec::new();
/// for person_result in rows { /// for person_result in rows {
/// persons.push(try!(person_result)); /// persons.push(person_result?);
/// } /// }
/// ///
/// Ok(persons) /// Ok(persons)
@ -348,12 +370,12 @@ impl<'conn> Statement<'conn> {
/// Will return `Err` if binding parameters fails. /// Will return `Err` if binding parameters fails.
pub fn query_and_then_named<'a, T, E, F>( pub fn query_and_then_named<'a, T, E, F>(
&'a mut self, &'a mut self,
params: &[(&str, &ToSql)], params: &[(&str, &dyn ToSql)],
f: F, f: F,
) -> Result<AndThenRows<'a, F>> ) -> Result<AndThenRows<'a, F>>
where where
E: convert::From<Error>, E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>, F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
{ {
let rows = self.query_named(params)?; let rows = self.query_named(params)?;
Ok(AndThenRows::new(rows, f)) Ok(AndThenRows::new(rows, f))
@ -366,13 +388,8 @@ impl<'conn> Statement<'conn> {
P: IntoIterator, P: IntoIterator,
P::Item: ToSql, P::Item: ToSql,
{ {
let mut rows = try!(self.query(params)); let mut rows = self.query(params)?;
let exists = { let exists = rows.next().is_some();
match rows.next() {
Some(_) => true,
None => false,
}
};
Ok(exists) Ok(exists)
} }
@ -382,6 +399,10 @@ impl<'conn> Statement<'conn> {
/// If the query returns more than one row, all rows except the first are /// If the query returns more than one row, all rows except the first are
/// ignored. /// ignored.
/// ///
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
/// query truly is optional, you can call `.optional()` on the result of
/// this to get a `Result<Option<T>>`.
///
/// # Failure /// # Failure
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
@ -389,9 +410,9 @@ impl<'conn> Statement<'conn> {
where where
P: IntoIterator, P: IntoIterator,
P::Item: ToSql, P::Item: ToSql,
F: FnOnce(&Row) -> T, F: FnOnce(&Row<'_, '_>) -> T,
{ {
let mut rows = try!(self.query(params)); let mut rows = self.query(params)?;
rows.get_expected_row().map(|r| f(&r)) rows.get_expected_row().map(|r| f(&r))
} }
@ -415,7 +436,7 @@ impl<'conn> Statement<'conn> {
/// Will return Err if `name` is invalid. Will return Ok(None) if the name /// Will return Err if `name` is invalid. Will return Ok(None) if the name
/// is valid but not a bound parameter of this statement. /// is valid but not a bound parameter of this statement.
pub fn parameter_index(&self, name: &str) -> Result<Option<usize>> { pub fn parameter_index(&self, name: &str) -> Result<Option<usize>> {
let c_name = try!(str_to_cstring(name)); let c_name = str_to_cstring(name)?;
Ok(self.stmt.bind_parameter_index(&c_name)) Ok(self.stmt.bind_parameter_index(&c_name))
} }
@ -431,7 +452,7 @@ impl<'conn> Statement<'conn> {
if index > expected { if index > expected {
break; break;
} }
try!(self.bind_parameter(&p, index)); self.bind_parameter(&p, index)?;
} }
assert_eq!( assert_eq!(
index, expected, index, expected,
@ -442,10 +463,10 @@ impl<'conn> Statement<'conn> {
Ok(()) Ok(())
} }
fn bind_parameters_named(&mut self, params: &[(&str, &ToSql)]) -> Result<()> { fn bind_parameters_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<()> {
for &(name, value) in params { for &(name, value) in params {
if let Some(i) = try!(self.parameter_index(name)) { if let Some(i) = self.parameter_index(name)? {
try!(self.bind_parameter(value, i)); self.bind_parameter(value, i)?;
} else { } else {
return Err(Error::InvalidParameterName(name.into())); return Err(Error::InvalidParameterName(name.into()));
} }
@ -453,8 +474,8 @@ impl<'conn> Statement<'conn> {
Ok(()) Ok(())
} }
fn bind_parameter(&self, param: &ToSql, col: usize) -> Result<()> { fn bind_parameter(&self, param: &dyn ToSql, col: usize) -> Result<()> {
let value = try!(param.to_sql()); let value = param.to_sql()?;
let ptr = unsafe { self.stmt.ptr() }; let ptr = unsafe { self.stmt.ptr() };
let value = match value { let value = match value {
@ -489,7 +510,7 @@ impl<'conn> Statement<'conn> {
if length > ::std::i32::MAX as usize { if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG ffi::SQLITE_TOOBIG
} else { } else {
let c_str = try!(str_to_cstring(s)); let c_str = str_to_cstring(s)?;
let destructor = if length > 0 { let destructor = if length > 0 {
ffi::SQLITE_TRANSIENT() ffi::SQLITE_TRANSIENT()
} else { } else {
@ -571,6 +592,17 @@ impl<'conn> Statement<'conn> {
} }
} }
/// Get the value for one of the status counters for this statement.
pub fn get_status(&self, status: StatementStatus) -> i32 {
self.stmt.get_status(status, false)
}
/// Reset the value of one of the status counters for this statement,
/// returning the value it had before resetting.
pub fn reset_status(&self, status: StatementStatus) -> i32 {
self.stmt.get_status(status, true)
}
pub(crate) fn check_no_tail(&self) -> Result<()> { pub(crate) fn check_no_tail(&self) -> Result<()> {
if self.stmt.has_tail() { if self.stmt.has_tail() {
Err(Error::MultipleStatement) Err(Error::MultipleStatement)
@ -589,7 +621,7 @@ impl<'conn> Into<RawStatement> for Statement<'conn> {
} }
impl<'conn> fmt::Debug for Statement<'conn> { impl<'conn> fmt::Debug for Statement<'conn> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let sql = str::from_utf8(self.stmt.sql().to_bytes()); let sql = str::from_utf8(self.stmt.sql().to_bytes());
f.debug_struct("Statement") f.debug_struct("Statement")
.field("conn", self.conn) .field("conn", self.conn)
@ -607,11 +639,11 @@ impl<'conn> Drop for Statement<'conn> {
} }
impl<'conn> Statement<'conn> { impl<'conn> Statement<'conn> {
pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement { pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> {
Statement { conn, stmt } Statement { conn, stmt }
} }
pub(crate) fn value_ref(&self, col: usize) -> ValueRef { pub(crate) fn value_ref(&self, col: usize) -> ValueRef<'_> {
let raw = unsafe { self.stmt.ptr() }; let raw = unsafe { self.stmt.ptr() };
match self.stmt.column_type(col) { match self.stmt.column_type(col) {
@ -679,9 +711,36 @@ impl<'conn> Statement<'conn> {
} }
} }
/// Prepared statement status counters.
///
/// See https://www.sqlite.org/c3ref/c_stmtstatus_counter.html
/// for explanations of each.
///
/// Note that depending on your version of SQLite, all of these
/// may not be available.
#[repr(i32)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum StatementStatus {
/// Equivalent to SQLITE_STMTSTATUS_FULLSCAN_STEP
FullscanStep = 1,
/// Equivalent to SQLITE_STMTSTATUS_SORT
Sort = 2,
/// Equivalent to SQLITE_STMTSTATUS_AUTOINDEX
AutoIndex = 3,
/// Equivalent to SQLITE_STMTSTATUS_VM_STEP
VmStep = 4,
/// Equivalent to SQLITE_STMTSTATUS_REPREPARE
RePrepare = 5,
/// Equivalent to SQLITE_STMTSTATUS_RUN
Run = 6,
/// Equivalent to SQLITE_STMTSTATUS_MEMUSED
MemUsed = 99,
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use {Connection, Error, Result, NO_PARAMS}; use crate::types::ToSql;
use crate::{Connection, Error, Result, NO_PARAMS};
#[test] #[test]
fn test_execute_named() { fn test_execute_named() {
@ -803,6 +862,7 @@ mod test {
assert_eq!(1, doubled_id); assert_eq!(1, doubled_id);
// second row should be Err // second row should be Err
#[allow(clippy::match_wild_err_arm)]
match rows.next().unwrap() { match rows.next().unwrap() {
Ok(_) => panic!("invalid Ok"), Ok(_) => panic!("invalid Ok"),
Err(Error::SqliteSingleThreadedMode) => (), Err(Error::SqliteSingleThreadedMode) => (),
@ -963,4 +1023,40 @@ mod test {
stmt.bind_parameter(&1, 1).unwrap(); stmt.bind_parameter(&1, 1).unwrap();
assert_eq!(Some("SELECT 1"), stmt.expanded_sql()); assert_eq!(Some("SELECT 1"), stmt.expanded_sql());
} }
#[test]
fn test_bind_parameters() {
let db = Connection::open_in_memory().unwrap();
// dynamic slice:
db.query_row(
"SELECT ?1, ?2, ?3",
&[&1u8 as &dyn ToSql, &"one", &Some("one")],
|row| row.get::<_, u8>(0),
)
.unwrap();
// existing collection:
let data = vec![1, 2, 3];
db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, u8>(0))
.unwrap();
db.query_row("SELECT ?1, ?2, ?3", data.as_slice(), |row| {
row.get::<_, u8>(0)
})
.unwrap();
db.query_row("SELECT ?1, ?2, ?3", data, |row| row.get::<_, u8>(0))
.unwrap();
use std::collections::BTreeSet;
let data: BTreeSet<String> = ["one", "two", "three"]
.iter()
.map(|s| s.to_string())
.collect();
db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, String>(0))
.unwrap();
let data = [0; 3];
db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, u8>(0))
.unwrap();
db.query_row("SELECT ?1, ?2, ?3", data.iter(), |row| row.get::<_, u8>(0))
.unwrap();
}
} }

View File

@ -3,12 +3,13 @@
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::mem; use std::mem;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::panic::catch_unwind;
use std::ptr; use std::ptr;
use std::time::Duration; use std::time::Duration;
use super::ffi; use super::ffi;
use error::error_from_sqlite_code; use crate::error::error_from_sqlite_code;
use {Connection, Result}; use crate::{Connection, Result};
/// Set up the process-wide SQLite error logging callback. /// Set up the process-wide SQLite error logging callback.
/// This function is marked unsafe for two reasons: /// This function is marked unsafe for two reasons:
@ -27,7 +28,7 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) }; let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
let s = String::from_utf8_lossy(c_slice); let s = String::from_utf8_lossy(c_slice);
callback(err, &s); let _ = catch_unwind(|| callback(err, &s));
} }
let rc = match callback { let rc = match callback {
@ -72,7 +73,7 @@ impl Connection {
let trace_fn: fn(&str) = mem::transmute(p_arg); let trace_fn: fn(&str) = mem::transmute(p_arg);
let c_slice = CStr::from_ptr(z_sql).to_bytes(); let c_slice = CStr::from_ptr(z_sql).to_bytes();
let s = String::from_utf8_lossy(c_slice); let s = String::from_utf8_lossy(c_slice);
trace_fn(&s); let _ = catch_unwind(|| trace_fn(&s));
} }
let c = self.db.borrow_mut(); let c = self.db.borrow_mut();
@ -106,7 +107,7 @@ impl Connection {
nanoseconds / NANOS_PER_SEC, nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32, (nanoseconds % NANOS_PER_SEC) as u32,
); );
profile_fn(&s, duration); let _ = catch_unwind(|| profile_fn(&s, duration));
} }
let c = self.db.borrow_mut(); let c = self.db.borrow_mut();
@ -124,7 +125,7 @@ mod test {
use std::sync::Mutex; use std::sync::Mutex;
use std::time::Duration; use std::time::Duration;
use Connection; use crate::Connection;
#[test] #[test]
fn test_trace() { fn test_trace() {

View File

@ -1,10 +1,5 @@
use crate::{Connection, Result};
use std::ops::Deref; use std::ops::Deref;
use {Connection, Result};
/// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is
/// deprecated.
#[deprecated(since = "0.6.0", note = "Use TransactionBehavior instead")]
pub type SqliteTransactionBehavior = TransactionBehavior;
/// Options for transaction behavior. See [BEGIN /// Options for transaction behavior. See [BEGIN
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details. /// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
@ -16,7 +11,7 @@ pub enum TransactionBehavior {
} }
/// Options for how a Transaction or Savepoint should behave when it is dropped. /// Options for how a Transaction or Savepoint should behave when it is dropped.
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum DropBehavior { pub enum DropBehavior {
/// Roll back the changes. This is the default. /// Roll back the changes. This is the default.
Rollback, Rollback,
@ -32,10 +27,6 @@ pub enum DropBehavior {
Panic, Panic,
} }
/// Old name for `Transaction`. `SqliteTransaction` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Transaction instead")]
pub type SqliteTransaction<'conn> = Transaction<'conn>;
/// Represents a transaction on a database connection. /// Represents a transaction on a database connection.
/// ///
/// ## Note /// ## Note
@ -51,14 +42,15 @@ pub type SqliteTransaction<'conn> = Transaction<'conn>;
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
/// fn perform_queries(conn: &mut Connection) -> Result<()> { /// fn perform_queries(conn: &mut Connection) -> Result<()> {
/// let tx = try!(conn.transaction()); /// let tx = conn.transaction()?;
/// ///
/// try!(do_queries_part_1(&tx)); // tx causes rollback if this fails /// do_queries_part_1(&tx)?; // tx causes rollback if this fails
/// try!(do_queries_part_2(&tx)); // tx causes rollback if this fails /// do_queries_part_2(&tx)?; // tx causes rollback if this fails
/// ///
/// tx.commit() /// tx.commit()
/// } /// }
/// ``` /// ```
#[derive(Debug)]
pub struct Transaction<'conn> { pub struct Transaction<'conn> {
conn: &'conn Connection, conn: &'conn Connection,
drop_behavior: DropBehavior, drop_behavior: DropBehavior,
@ -79,10 +71,10 @@ pub struct Transaction<'conn> {
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
/// fn perform_queries(conn: &mut Connection) -> Result<()> { /// fn perform_queries(conn: &mut Connection) -> Result<()> {
/// let sp = try!(conn.savepoint()); /// let sp = conn.savepoint()?;
/// ///
/// try!(do_queries_part_1(&sp)); // sp causes rollback if this fails /// do_queries_part_1(&sp)?; // sp causes rollback if this fails
/// try!(do_queries_part_2(&sp)); // sp causes rollback if this fails /// do_queries_part_2(&sp)?; // sp causes rollback if this fails
/// ///
/// sp.commit() /// sp.commit()
/// } /// }
@ -101,7 +93,7 @@ impl<'conn> Transaction<'conn> {
/// Even though we don't mutate the connection, we take a `&mut Connection` /// Even though we don't mutate the connection, we take a `&mut Connection`
/// so as to prevent nested or concurrent transactions on the same /// so as to prevent nested or concurrent transactions on the same
/// connection. /// connection.
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction> { pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> {
let query = match behavior { let query = match behavior {
TransactionBehavior::Deferred => "BEGIN DEFERRED", TransactionBehavior::Deferred => "BEGIN DEFERRED",
TransactionBehavior::Immediate => "BEGIN IMMEDIATE", TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
@ -127,12 +119,12 @@ impl<'conn> Transaction<'conn> {
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true } /// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true }
/// fn perform_queries(conn: &mut Connection) -> Result<()> { /// fn perform_queries(conn: &mut Connection) -> Result<()> {
/// let mut tx = try!(conn.transaction()); /// let mut tx = conn.transaction()?;
/// ///
/// { /// {
/// let sp = try!(tx.savepoint()); /// let sp = tx.savepoint()?;
/// if perform_queries_part_1_succeeds(&sp) { /// if perform_queries_part_1_succeeds(&sp) {
/// try!(sp.commit()); /// sp.commit()?;
/// } /// }
/// // otherwise, sp will rollback /// // otherwise, sp will rollback
/// } /// }
@ -140,12 +132,12 @@ impl<'conn> Transaction<'conn> {
/// tx.commit() /// tx.commit()
/// } /// }
/// ``` /// ```
pub fn savepoint(&mut self) -> Result<Savepoint> { pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
Savepoint::with_depth(self.conn, 1) Savepoint::with_depth(self.conn, 1)
} }
/// Create a new savepoint with a custom savepoint name. See `savepoint()`. /// Create a new savepoint with a custom savepoint name. See `savepoint()`.
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint> { pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_depth_and_name(self.conn, 1, name) Savepoint::with_depth_and_name(self.conn, 1, name)
} }
@ -223,7 +215,7 @@ impl<'conn> Savepoint<'conn> {
conn: &Connection, conn: &Connection,
depth: u32, depth: u32,
name: T, name: T,
) -> Result<Savepoint> { ) -> Result<Savepoint<'_>> {
let name = name.into(); let name = name.into();
conn.execute_batch(&format!("SAVEPOINT {}", name)) conn.execute_batch(&format!("SAVEPOINT {}", name))
.map(|_| Savepoint { .map(|_| Savepoint {
@ -235,28 +227,28 @@ impl<'conn> Savepoint<'conn> {
}) })
} }
fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint> { fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> {
let name = format!("_rusqlite_sp_{}", depth); let name = format!("_rusqlite_sp_{}", depth);
Savepoint::with_depth_and_name(conn, depth, name) Savepoint::with_depth_and_name(conn, depth, name)
} }
/// Begin a new savepoint. Can be nested. /// Begin a new savepoint. Can be nested.
pub fn new(conn: &mut Connection) -> Result<Savepoint> { pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
Savepoint::with_depth(conn, 0) Savepoint::with_depth(conn, 0)
} }
/// Begin a new savepoint with a user-provided savepoint name. /// Begin a new savepoint with a user-provided savepoint name.
pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint> { pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_depth_and_name(conn, 0, name) Savepoint::with_depth_and_name(conn, 0, name)
} }
/// Begin a nested savepoint. /// Begin a nested savepoint.
pub fn savepoint(&mut self) -> Result<Savepoint> { pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
Savepoint::with_depth(self.conn, self.depth + 1) Savepoint::with_depth(self.conn, self.depth + 1)
} }
/// Begin a nested savepoint with a user-provided savepoint name. /// Begin a nested savepoint with a user-provided savepoint name.
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint> { pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_depth_and_name(self.conn, self.depth + 1, name) Savepoint::with_depth_and_name(self.conn, self.depth + 1, name)
} }
@ -345,10 +337,10 @@ impl Connection {
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
/// fn perform_queries(conn: &mut Connection) -> Result<()> { /// fn perform_queries(conn: &mut Connection) -> Result<()> {
/// let tx = try!(conn.transaction()); /// let tx = conn.transaction()?;
/// ///
/// try!(do_queries_part_1(&tx)); // tx causes rollback if this fails /// do_queries_part_1(&tx)?; // tx causes rollback if this fails
/// try!(do_queries_part_2(&tx)); // tx causes rollback if this fails /// do_queries_part_2(&tx)?; // tx causes rollback if this fails
/// ///
/// tx.commit() /// tx.commit()
/// } /// }
@ -357,7 +349,7 @@ impl Connection {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
pub fn transaction(&mut self) -> Result<Transaction> { pub fn transaction(&mut self) -> Result<Transaction<'_>> {
Transaction::new(self, TransactionBehavior::Deferred) Transaction::new(self, TransactionBehavior::Deferred)
} }
@ -371,7 +363,7 @@ impl Connection {
pub fn transaction_with_behavior( pub fn transaction_with_behavior(
&mut self, &mut self,
behavior: TransactionBehavior, behavior: TransactionBehavior,
) -> Result<Transaction> { ) -> Result<Transaction<'_>> {
Transaction::new(self, behavior) Transaction::new(self, behavior)
} }
@ -388,10 +380,10 @@ impl Connection {
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
/// fn perform_queries(conn: &mut Connection) -> Result<()> { /// fn perform_queries(conn: &mut Connection) -> Result<()> {
/// let sp = try!(conn.savepoint()); /// let sp = conn.savepoint()?;
/// ///
/// try!(do_queries_part_1(&sp)); // sp causes rollback if this fails /// do_queries_part_1(&sp)?; // sp causes rollback if this fails
/// try!(do_queries_part_2(&sp)); // sp causes rollback if this fails /// do_queries_part_2(&sp)?; // sp causes rollback if this fails
/// ///
/// sp.commit() /// sp.commit()
/// } /// }
@ -400,7 +392,7 @@ impl Connection {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
pub fn savepoint(&mut self) -> Result<Savepoint> { pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
Savepoint::new(self) Savepoint::new(self)
} }
@ -411,7 +403,7 @@ impl Connection {
/// # Failure /// # Failure
/// ///
/// Will return `Err` if the underlying SQLite call fails. /// Will return `Err` if the underlying SQLite call fails.
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint> { pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_name(self, name) Savepoint::with_name(self, name)
} }
} }
@ -419,7 +411,7 @@ impl Connection {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::DropBehavior; use super::DropBehavior;
use {Connection, NO_PARAMS}; use crate::{Connection, NO_PARAMS};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -564,6 +556,16 @@ mod test {
assert_current_sum(8, &db); assert_current_sum(8, &db);
} }
#[test]
fn test_rc() {
use std::rc::Rc;
let mut conn = Connection::open_in_memory().unwrap();
let rc_txn = Rc::new(conn.transaction().unwrap());
// This will compile only if Transaction is Debug
Rc::try_unwrap(rc_txn).unwrap();
}
fn insert(x: i32, conn: &Connection) { fn insert(x: i32, conn: &Connection) {
conn.execute("INSERT INTO foo VALUES(?)", &[x]).unwrap(); conn.execute("INSERT INTO foo VALUES(?)", &[x]).unwrap();
} }

View File

@ -1,16 +1,16 @@
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types. //! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
extern crate chrono; use chrono;
use std::borrow::Cow; use std::borrow::Cow;
use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use Result; use crate::Result;
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD" /// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
impl ToSql for NaiveDate { impl ToSql for NaiveDate {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self.format("%Y-%m-%d").to_string(); let date_str = self.format("%Y-%m-%d").to_string();
Ok(ToSqlOutput::from(date_str)) Ok(ToSqlOutput::from(date_str))
} }
@ -18,7 +18,7 @@ impl ToSql for NaiveDate {
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone. /// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
impl FromSql for NaiveDate { impl FromSql for NaiveDate {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value value
.as_str() .as_str()
.and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") { .and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
@ -30,7 +30,7 @@ impl FromSql for NaiveDate {
/// ISO 8601 time without timezone => "HH:MM:SS.SSS" /// ISO 8601 time without timezone => "HH:MM:SS.SSS"
impl ToSql for NaiveTime { impl ToSql for NaiveTime {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self.format("%H:%M:%S%.f").to_string(); let date_str = self.format("%H:%M:%S%.f").to_string();
Ok(ToSqlOutput::from(date_str)) Ok(ToSqlOutput::from(date_str))
} }
@ -38,7 +38,7 @@ impl ToSql for NaiveTime {
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone. /// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
impl FromSql for NaiveTime { impl FromSql for NaiveTime {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| { value.as_str().and_then(|s| {
let fmt = match s.len() { let fmt = match s.len() {
5 => "%H:%M", 5 => "%H:%M",
@ -56,7 +56,7 @@ impl FromSql for NaiveTime {
/// ISO 8601 combined date and time without timezone => /// ISO 8601 combined date and time without timezone =>
/// "YYYY-MM-DD HH:MM:SS.SSS" /// "YYYY-MM-DD HH:MM:SS.SSS"
impl ToSql for NaiveDateTime { impl ToSql for NaiveDateTime {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string(); let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
Ok(ToSqlOutput::from(date_str)) Ok(ToSqlOutput::from(date_str))
} }
@ -66,7 +66,7 @@ impl ToSql for NaiveDateTime {
/// and time without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" /// and time without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS"
/// also supported) /// also supported)
impl FromSql for NaiveDateTime { impl FromSql for NaiveDateTime {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| { value.as_str().and_then(|s| {
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' { let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%Y-%m-%dT%H:%M:%S%.f" "%Y-%m-%dT%H:%M:%S%.f"
@ -85,17 +85,17 @@ impl FromSql for NaiveDateTime {
/// Date and time with time zone => UTC RFC3339 timestamp /// Date and time with time zone => UTC RFC3339 timestamp
/// ("YYYY-MM-DDTHH:MM:SS.SSS+00:00"). /// ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
impl<Tz: TimeZone> ToSql for DateTime<Tz> { impl<Tz: TimeZone> ToSql for DateTime<Tz> {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.with_timezone(&Utc).to_rfc3339())) Ok(ToSqlOutput::from(self.with_timezone(&Utc).to_rfc3339()))
} }
} }
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`. /// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`.
impl FromSql for DateTime<Utc> { impl FromSql for DateTime<Utc> {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
{ {
// Try to parse value as rfc3339 first. // Try to parse value as rfc3339 first.
let s = try!(value.as_str()); let s = value.as_str()?;
// If timestamp looks space-separated, make a copy and replace it with 'T'. // If timestamp looks space-separated, make a copy and replace it with 'T'.
let s = if s.len() >= 11 && s.as_bytes()[10] == b' ' { let s = if s.len() >= 11 && s.as_bytes()[10] == b' ' {
@ -121,8 +121,8 @@ impl FromSql for DateTime<Utc> {
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`. /// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`.
impl FromSql for DateTime<Local> { impl FromSql for DateTime<Local> {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let utc_dt = try!(DateTime::<Utc>::column_result(value)); let utc_dt = DateTime::<Utc>::column_result(value)?;
Ok(utc_dt.with_timezone(&Local)) Ok(utc_dt.with_timezone(&Local))
} }
} }
@ -132,7 +132,7 @@ mod test {
use super::chrono::{ use super::chrono::{
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc, DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
}; };
use {Connection, NO_PARAMS}; use crate::{Connection, Result, NO_PARAMS};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -263,4 +263,21 @@ mod test {
.unwrap(); .unwrap();
assert_eq!(local, v); assert_eq!(local, v);
} }
#[test]
fn test_sqlite_functions() {
let db = checked_memory_handle();
let result: Result<NaiveTime> =
db.query_row("SELECT CURRENT_TIME", NO_PARAMS, |r| r.get(0));
assert!(result.is_ok());
let result: Result<NaiveDate> =
db.query_row("SELECT CURRENT_DATE", NO_PARAMS, |r| r.get(0));
assert!(result.is_ok());
let result: Result<NaiveDateTime> =
db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
assert!(result.is_ok());
let result: Result<DateTime<Utc>> =
db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
assert!(result.is_ok());
}
} }

View File

@ -19,11 +19,23 @@ pub enum FromSqlError {
InvalidI128Size(usize), InvalidI128Size(usize),
/// An error case available for implementors of the `FromSql` trait. /// An error case available for implementors of the `FromSql` trait.
Other(Box<Error + Send + Sync>), Other(Box<dyn Error + Send + Sync>),
}
impl PartialEq for FromSqlError {
fn eq(&self, other: &FromSqlError) -> bool {
match (self, other) {
(FromSqlError::InvalidType, FromSqlError::InvalidType) => true,
(FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
#[cfg(feature = "i128_blob")]
(FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
(_, _) => false,
}
}
} }
impl fmt::Display for FromSqlError { impl fmt::Display for FromSqlError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
FromSqlError::InvalidType => write!(f, "Invalid type"), FromSqlError::InvalidType => write!(f, "Invalid type"),
FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i), FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i),
@ -47,8 +59,9 @@ impl Error for FromSqlError {
} }
} }
#[cfg_attr(feature = "clippy", allow(match_same_arms))] #[allow(clippy::match_same_arms)]
fn cause(&self) -> Option<&Error> { #[allow(deprecated)]
fn cause(&self) -> Option<&dyn Error> {
match *self { match *self {
FromSqlError::Other(ref err) => err.cause(), FromSqlError::Other(ref err) => err.cause(),
FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None, FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None,
@ -73,11 +86,11 @@ pub type FromSqlResult<T> = Result<T, FromSqlError>;
/// fetching values as i64 and then doing the interpretation themselves or by /// fetching values as i64 and then doing the interpretation themselves or by
/// defining a newtype and implementing `FromSql`/`ToSql` for it. /// defining a newtype and implementing `FromSql`/`ToSql` for it.
pub trait FromSql: Sized { pub trait FromSql: Sized {
fn column_result(value: ValueRef) -> FromSqlResult<Self>; fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
} }
impl FromSql for isize { impl FromSql for isize {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
i64::column_result(value).and_then(|i| { i64::column_result(value).and_then(|i| {
if i < isize::min_value() as i64 || i > isize::max_value() as i64 { if i < isize::min_value() as i64 || i > isize::max_value() as i64 {
Err(FromSqlError::OutOfRange(i)) Err(FromSqlError::OutOfRange(i))
@ -91,7 +104,7 @@ impl FromSql for isize {
macro_rules! from_sql_integral( macro_rules! from_sql_integral(
($t:ident) => ( ($t:ident) => (
impl FromSql for $t { impl FromSql for $t {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
i64::column_result(value).and_then(|i| { i64::column_result(value).and_then(|i| {
if i < i64::from($t::min_value()) || i > i64::from($t::max_value()) { if i < i64::from($t::min_value()) || i > i64::from($t::max_value()) {
Err(FromSqlError::OutOfRange(i)) Err(FromSqlError::OutOfRange(i))
@ -112,13 +125,13 @@ from_sql_integral!(u16);
from_sql_integral!(u32); from_sql_integral!(u32);
impl FromSql for i64 { impl FromSql for i64 {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_i64() value.as_i64()
} }
} }
impl FromSql for f64 { impl FromSql for f64 {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value { match value {
ValueRef::Integer(i) => Ok(i as f64), ValueRef::Integer(i) => Ok(i as f64),
ValueRef::Real(f) => Ok(f), ValueRef::Real(f) => Ok(f),
@ -128,7 +141,7 @@ impl FromSql for f64 {
} }
impl FromSql for bool { impl FromSql for bool {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
i64::column_result(value).map(|i| match i { i64::column_result(value).map(|i| match i {
0 => false, 0 => false,
_ => true, _ => true,
@ -137,20 +150,20 @@ impl FromSql for bool {
} }
impl FromSql for String { impl FromSql for String {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(|s| s.to_string()) value.as_str().map(|s| s.to_string())
} }
} }
impl FromSql for Vec<u8> { impl FromSql for Vec<u8> {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_blob().map(|b| b.to_vec()) value.as_blob().map(|b| b.to_vec())
} }
} }
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
impl FromSql for i128 { impl FromSql for i128 {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
use byteorder::{BigEndian, ByteOrder}; use byteorder::{BigEndian, ByteOrder};
value.as_blob().and_then(|bytes| { value.as_blob().and_then(|bytes| {
@ -164,7 +177,7 @@ impl FromSql for i128 {
} }
impl<T: FromSql> FromSql for Option<T> { impl<T: FromSql> FromSql for Option<T> {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value { match value {
ValueRef::Null => Ok(None), ValueRef::Null => Ok(None),
_ => FromSql::column_result(value).map(Some), _ => FromSql::column_result(value).map(Some),
@ -173,7 +186,7 @@ impl<T: FromSql> FromSql for Option<T> {
} }
impl FromSql for Value { impl FromSql for Value {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
Ok(value.into()) Ok(value.into())
} }
} }
@ -181,7 +194,7 @@ impl FromSql for Value {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::FromSql; use super::FromSql;
use {Connection, Error}; use crate::{Connection, Error};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
Connection::open_in_memory().unwrap() Connection::open_in_memory().unwrap()
@ -219,11 +232,11 @@ mod test {
check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]); check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
check_ranges::<i32>( check_ranges::<i32>(
&db, &db,
&[-2147483649, 2147483648], &[-2_147_483_649, 2_147_483_648],
&[-2147483648, -1, 0, 1, 2147483647], &[-2_147_483_648, -1, 0, 1, 2_147_483_647],
); );
check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]); check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]); check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
check_ranges::<u32>(&db, &[-2, -1, 4294967296], &[0, 1, 4294967295]); check_ranges::<u32>(&db, &[-2, -1, 4_294_967_296], &[0, 1, 4_294_967_295]);
} }
} }

View File

@ -19,9 +19,6 @@
//! store timespecs as `f64`s: //! store timespecs as `f64`s:
//! //!
//! ```rust //! ```rust
//! extern crate rusqlite;
//! extern crate time;
//!
//! use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; //! use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
//! use rusqlite::Result; //! use rusqlite::Result;
//! //!
@ -77,7 +74,6 @@ mod value_ref;
/// ## Example /// ## Example
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # extern crate rusqlite;
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// # use rusqlite::types::{Null}; /// # use rusqlite::types::{Null};
/// fn main() {} /// fn main() {}
@ -98,7 +94,7 @@ pub enum Type {
} }
impl fmt::Display for Type { impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
Type::Null => write!(f, "Null"), Type::Null => write!(f, "Null"),
Type::Integer => write!(f, "Integer"), Type::Integer => write!(f, "Integer"),
@ -111,12 +107,12 @@ impl fmt::Display for Type {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
extern crate time; use time;
use super::Value; use super::Value;
use crate::{Connection, Error, NO_PARAMS};
use std::f64::EPSILON; use std::f64::EPSILON;
use std::os::raw::{c_double, c_int}; use std::os::raw::{c_double, c_int};
use {Connection, Error, NO_PARAMS};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -227,6 +223,7 @@ mod test {
} }
#[test] #[test]
#[allow(clippy::cyclomatic_complexity)]
fn test_mismatched_types() { fn test_mismatched_types() {
fn is_invalid_column_type(err: Error) -> bool { fn is_invalid_column_type(err: Error) -> bool {
match err { match err {

View File

@ -1,21 +1,21 @@
//! `ToSql` and `FromSql` implementation for JSON `Value`. //! `ToSql` and `FromSql` implementation for JSON `Value`.
extern crate serde_json; use serde_json;
use self::serde_json::Value; use self::serde_json::Value;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use Result; use crate::Result;
/// Serialize JSON `Value` to text. /// Serialize JSON `Value` to text.
impl ToSql for Value { impl ToSql for Value {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap())) Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
} }
} }
/// Deserialize text/blob to JSON `Value`. /// Deserialize text/blob to JSON `Value`.
impl FromSql for Value { impl FromSql for Value {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value { match value {
ValueRef::Text(s) => serde_json::from_str(s), ValueRef::Text(s) => serde_json::from_str(s),
ValueRef::Blob(b) => serde_json::from_slice(b), ValueRef::Blob(b) => serde_json::from_slice(b),
@ -28,8 +28,8 @@ impl FromSql for Value {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::serde_json; use super::serde_json;
use types::ToSql; use crate::types::ToSql;
use {Connection, NO_PARAMS}; use crate::{Connection, NO_PARAMS};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -46,7 +46,7 @@ mod test {
let data: serde_json::Value = serde_json::from_str(json).unwrap(); let data: serde_json::Value = serde_json::from_str(json).unwrap();
db.execute( db.execute(
"INSERT INTO foo (t, b) VALUES (?, ?)", "INSERT INTO foo (t, b) VALUES (?, ?)",
&[&data as &ToSql, &json.as_bytes()], &[&data as &dyn ToSql, &json.as_bytes()],
) )
.unwrap(); .unwrap();

View File

@ -1,13 +1,14 @@
extern crate time; use time;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use Result; use crate::Result;
const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S";
const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ"; const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ";
const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z"; const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
impl ToSql for time::Timespec { impl ToSql for time::Timespec {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let time_string = time::at_utc(*self) let time_string = time::at_utc(*self)
.strftime(SQLITE_DATETIME_FMT) .strftime(SQLITE_DATETIME_FMT)
.unwrap() .unwrap()
@ -17,14 +18,17 @@ impl ToSql for time::Timespec {
} }
impl FromSql for time::Timespec { impl FromSql for time::Timespec {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value value
.as_str() .as_str()
.and_then(|s| { .and_then(|s| {
time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| { match s.len() {
time::strptime(s, SQLITE_DATETIME_FMT_LEGACY) 19 => time::strptime(s, CURRENT_TIMESTAMP_FMT),
.or_else(|_| Err(FromSqlError::Other(Box::new(err)))) _ => time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| {
}) time::strptime(s, SQLITE_DATETIME_FMT_LEGACY).or_else(|_| Err(err))
}),
}
.or_else(|err| Err(FromSqlError::Other(Box::new(err))))
}) })
.map(|tm| tm.to_timespec()) .map(|tm| tm.to_timespec())
} }
@ -33,7 +37,7 @@ impl FromSql for time::Timespec {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::time; use super::time;
use {Connection, NO_PARAMS}; use crate::{Connection, Result, NO_PARAMS};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -50,10 +54,10 @@ mod test {
ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM 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(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(1_500_391_124, 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(2_000_000_000, 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(3_000_000_000, 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_000_000, 0)); //November 20, 2286
for ts in ts_vec { for ts in ts_vec {
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap(); db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
@ -67,4 +71,12 @@ mod test {
assert_eq!(from, ts); assert_eq!(from, ts);
} }
} }
#[test]
fn test_sqlite_functions() {
let db = checked_memory_handle();
let result: Result<time::Timespec> =
db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
assert!(result.is_ok());
}
} }

View File

@ -1,8 +1,8 @@
use super::{Null, Value, ValueRef}; use super::{Null, Value, ValueRef};
use std::borrow::Cow;
#[cfg(feature = "array")] #[cfg(feature = "array")]
use vtab::array::Array; use crate::vtab::array::Array;
use Result; use crate::Result;
use std::borrow::Cow;
/// `ToSqlOutput` represents the possible output types for implementors of the /// `ToSqlOutput` represents the possible output types for implementors of the
/// `ToSql` trait. /// `ToSql` trait.
@ -66,7 +66,7 @@ from_value!(Vec<u8>);
from_value!(i128); from_value!(i128);
impl<'a> ToSql for ToSqlOutput<'a> { impl<'a> ToSql for ToSqlOutput<'a> {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(match *self { Ok(match *self {
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v), ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)), ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
@ -81,7 +81,7 @@ impl<'a> ToSql for ToSqlOutput<'a> {
/// A trait for types that can be converted into SQLite values. /// A trait for types that can be converted into SQLite values.
pub trait ToSql { pub trait ToSql {
fn to_sql(&self) -> Result<ToSqlOutput>; fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
} }
// We should be able to use a generic impl like this: // We should be able to use a generic impl like this:
@ -99,7 +99,7 @@ pub trait ToSql {
macro_rules! to_sql_self( macro_rules! to_sql_self(
($t:ty) => ( ($t:ty) => (
impl ToSql for $t { impl ToSql for $t {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(*self)) Ok(ToSqlOutput::from(*self))
} }
} }
@ -125,43 +125,43 @@ impl<'a, T: ?Sized> ToSql for &'a T
where where
T: ToSql, T: ToSql,
{ {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
(*self).to_sql() (*self).to_sql()
} }
} }
impl ToSql for String { impl ToSql for String {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.as_str())) Ok(ToSqlOutput::from(self.as_str()))
} }
} }
impl ToSql for str { impl ToSql for str {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self)) Ok(ToSqlOutput::from(self))
} }
} }
impl ToSql for Vec<u8> { impl ToSql for Vec<u8> {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.as_slice())) Ok(ToSqlOutput::from(self.as_slice()))
} }
} }
impl ToSql for [u8] { impl ToSql for [u8] {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self)) Ok(ToSqlOutput::from(self))
} }
} }
impl ToSql for Value { impl ToSql for Value {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self)) Ok(ToSqlOutput::from(self))
} }
} }
impl<T: ToSql> ToSql for Option<T> { impl<T: ToSql> ToSql for Option<T> {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
match *self { match *self {
None => Ok(ToSqlOutput::from(Null)), None => Ok(ToSqlOutput::from(Null)),
Some(ref t) => t.to_sql(), Some(ref t) => t.to_sql(),
@ -170,7 +170,7 @@ impl<T: ToSql> ToSql for Option<T> {
} }
impl<'a> ToSql for Cow<'a, str> { impl<'a> ToSql for Cow<'a, str> {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.as_ref())) Ok(ToSqlOutput::from(self.as_ref()))
} }
} }
@ -207,8 +207,8 @@ mod test {
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
#[test] #[test]
fn test_i128() { fn test_i128() {
use crate::{Connection, NO_PARAMS};
use std::i128; use std::i128;
use {Connection, NO_PARAMS};
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)") db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")
.unwrap(); .unwrap();

View File

@ -1,5 +1,5 @@
use super::{Type, Value}; use super::{Type, Value};
use types::{FromSqlError, FromSqlResult}; use crate::types::{FromSqlError, FromSqlResult};
/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the /// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the
/// memory backing this value is owned by SQLite. /// memory backing this value is owned by SQLite.
@ -70,7 +70,7 @@ impl<'a> ValueRef<'a> {
} }
impl<'a> From<ValueRef<'a>> for Value { impl<'a> From<ValueRef<'a>> for Value {
fn from(borrowed: ValueRef) -> Value { fn from(borrowed: ValueRef<'_>) -> Value {
match borrowed { match borrowed {
ValueRef::Null => Value::Null, ValueRef::Null => Value::Null,
ValueRef::Integer(i) => Value::Integer(i), ValueRef::Integer(i) => Value::Integer(i),
@ -82,13 +82,13 @@ impl<'a> From<ValueRef<'a>> for Value {
} }
impl<'a> From<&'a str> for ValueRef<'a> { impl<'a> From<&'a str> for ValueRef<'a> {
fn from(s: &str) -> ValueRef { fn from(s: &str) -> ValueRef<'_> {
ValueRef::Text(s) ValueRef::Text(s)
} }
} }
impl<'a> From<&'a [u8]> for ValueRef<'a> { impl<'a> From<&'a [u8]> for ValueRef<'a> {
fn from(s: &[u8]) -> ValueRef { fn from(s: &[u8]) -> ValueRef<'_> {
ValueRef::Blob(s) ValueRef::Blob(s)
} }
} }
@ -104,3 +104,56 @@ impl<'a> From<&'a Value> for ValueRef<'a> {
} }
} }
} }
#[cfg(any(feature = "functions", feature = "session", feature = "vtab"))]
impl<'a> ValueRef<'a> {
pub(crate) unsafe fn from_value(value: *mut crate::ffi::sqlite3_value) -> ValueRef<'a> {
use crate::ffi;
use std::ffi::CStr;
use std::os::raw::c_char;
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"),
}
}
}

View File

@ -4,9 +4,11 @@ use std::os::raw::c_int;
#[cfg(feature = "unlock_notify")] #[cfg(feature = "unlock_notify")]
use std::os::raw::c_void; use std::os::raw::c_void;
#[cfg(feature = "unlock_notify")] #[cfg(feature = "unlock_notify")]
use std::panic::catch_unwind;
#[cfg(feature = "unlock_notify")]
use std::sync::{Condvar, Mutex}; use std::sync::{Condvar, Mutex};
use ffi; use crate::ffi;
#[cfg(feature = "unlock_notify")] #[cfg(feature = "unlock_notify")]
struct UnlockNotification { struct UnlockNotification {
@ -42,8 +44,10 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) {
use std::slice::from_raw_parts; use std::slice::from_raw_parts;
let args = from_raw_parts(ap_arg, n_arg as usize); let args = from_raw_parts(ap_arg, n_arg as usize);
for arg in args { for arg in args {
let un: &mut UnlockNotification = &mut *(*arg as *mut UnlockNotification); let _ = catch_unwind(|| {
un.fired(); let un: &mut UnlockNotification = &mut *(*arg as *mut UnlockNotification);
un.fired()
});
} }
} }
@ -99,10 +103,10 @@ pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int {
#[cfg(feature = "unlock_notify")] #[cfg(feature = "unlock_notify")]
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior, NO_PARAMS};
use std::sync::mpsc::sync_channel; use std::sync::mpsc::sync_channel;
use std::thread; use std::thread;
use std::time; use std::time;
use {Connection, OpenFlags, Result, Transaction, TransactionBehavior, NO_PARAMS};
#[test] #[test]
fn test_unlock_notify() { fn test_unlock_notify() {

View File

@ -1,4 +1,4 @@
use ffi; use crate::ffi;
use std::ffi::CStr; use std::ffi::CStr;
/// Returns the SQLite version as an integer; e.g., `3016002` for version /// Returns the SQLite version as an integer; e.g., `3016002` for version

View File

@ -5,13 +5,13 @@ use std::default::Default;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::rc::Rc; use std::rc::Rc;
use ffi; use crate::ffi;
use types::{ToSql, ToSqlOutput, Value}; use crate::types::{ToSql, ToSqlOutput, Value};
use vtab::{ use crate::vtab::{
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection, eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
VTabCursor, Values, VTabCursor, Values,
}; };
use {Connection, Result}; use crate::{Connection, Result};
// http://sqlite.org/bindptr.html // http://sqlite.org/bindptr.html
@ -24,7 +24,7 @@ pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
pub type Array = Rc<Vec<Value>>; pub type Array = Rc<Vec<Value>>;
impl ToSql for Array { impl ToSql for Array {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::Array(self.clone())) Ok(ToSqlOutput::Array(self.clone()))
} }
} }
@ -129,9 +129,9 @@ impl ArrayTabCursor {
} }
} }
impl VTabCursor for ArrayTabCursor { impl VTabCursor for ArrayTabCursor {
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values) -> Result<()> { fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> {
if idx_num > 0 { if idx_num > 0 {
self.ptr = try!(args.get_array(0)); self.ptr = args.get_array(0)?;
} else { } else {
self.ptr = None; self.ptr = None;
} }
@ -169,10 +169,10 @@ impl VTabCursor for ArrayTabCursor {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::types::Value;
use crate::vtab::array;
use crate::Connection;
use std::rc::Rc; use std::rc::Rc;
use types::Value;
use vtab::array;
use Connection;
#[test] #[test]
fn test_array_module() { fn test_array_module() {
@ -180,7 +180,7 @@ mod test {
array::load_module(&db).unwrap(); array::load_module(&db).unwrap();
let v = vec![1i64, 2, 3, 4]; let v = vec![1i64, 2, 3, 4];
let values = v.into_iter().map(|i| Value::from(i)).collect(); let values = v.into_iter().map(Value::from).collect();
let ptr = Rc::new(values); let ptr = Rc::new(values);
{ {
let mut stmt = db.prepare("SELECT value from rarray(?);").unwrap(); let mut stmt = db.prepare("SELECT value from rarray(?);").unwrap();

View File

@ -1,20 +1,20 @@
//! CSV Virtual Table. //! CSV Virtual Table.
//! //!
//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C extension. //! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C extension.
extern crate csv; use csv;
use std::fs::File; use std::fs::File;
use std::os::raw::c_int; use std::os::raw::c_int;
use std::path::Path; use std::path::Path;
use std::result; use std::result;
use std::str; use std::str;
use ffi; use crate::ffi;
use types::Null; use crate::types::Null;
use vtab::{ use crate::vtab::{
dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo, dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
Module, VTab, VTabConnection, VTabCursor, Values, Module, VTab, VTabConnection, VTabCursor, Values,
}; };
use {Connection, Error, Result}; use crate::{Connection, Error, Result};
/// Register the "csv" module. /// Register the "csv" module.
/// ```sql /// ```sql
@ -60,7 +60,7 @@ impl CSVTab {
} }
fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> { fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
let arg = try!(str::from_utf8(c_slice)).trim(); let arg = str::from_utf8(c_slice)?.trim();
let mut split = arg.split('='); let mut split = arg.split('=');
if let Some(key) = split.next() { if let Some(key) = split.next() {
if let Some(value) = split.next() { if let Some(value) = split.next() {
@ -107,7 +107,7 @@ impl VTab for CSVTab {
let args = &args[3..]; let args = &args[3..];
for c_slice in args { for c_slice in args {
let (param, value) = try!(CSVTab::parameter(c_slice)); let (param, value) = CSVTab::parameter(c_slice)?;
match param { match param {
"filename" => { "filename" => {
if !Path::new(value).exists() { if !Path::new(value).exists() {
@ -189,10 +189,10 @@ impl VTab for CSVTab {
let mut cols: Vec<String> = Vec::new(); let mut cols: Vec<String> = Vec::new();
if vtab.has_headers || (n_col.is_none() && schema.is_none()) { if vtab.has_headers || (n_col.is_none() && schema.is_none()) {
let mut reader = try!(vtab.reader()); let mut reader = vtab.reader()?;
if vtab.has_headers { if vtab.has_headers {
{ {
let headers = try!(reader.headers()); let headers = reader.headers()?;
// headers ignored if cols is not empty // headers ignored if cols is not empty
if n_col.is_none() && schema.is_none() { if n_col.is_none() && schema.is_none() {
cols = headers cols = headers
@ -204,7 +204,7 @@ impl VTab for CSVTab {
vtab.offset_first_row = reader.position().clone(); vtab.offset_first_row = reader.position().clone();
} else { } else {
let mut record = csv::ByteRecord::new(); let mut record = csv::ByteRecord::new();
if try!(reader.read_byte_record(&mut record)) { if reader.read_byte_record(&mut record)? {
for (i, _) in record.iter().enumerate() { for (i, _) in record.iter().enumerate() {
cols.push(format!("c{}", i)); cols.push(format!("c{}", i));
} }
@ -245,7 +245,7 @@ impl VTab for CSVTab {
} }
fn open(&self) -> Result<CSVTabCursor> { fn open(&self) -> Result<CSVTabCursor> {
Ok(CSVTabCursor::new(try!(self.reader()))) Ok(CSVTabCursor::new(self.reader()?))
} }
} }
@ -285,10 +285,15 @@ impl CSVTabCursor {
impl VTabCursor for CSVTabCursor { impl VTabCursor for CSVTabCursor {
// Only a full table scan is supported. So `filter` simply rewinds to // Only a full table scan is supported. So `filter` simply rewinds to
// the beginning. // the beginning.
fn filter(&mut self, _idx_num: c_int, _idx_str: Option<&str>, _args: &Values) -> Result<()> { 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(); let offset_first_row = self.vtab().offset_first_row.clone();
try!(self.reader.seek(offset_first_row)); self.reader.seek(offset_first_row)?;
} }
self.row_number = 0; self.row_number = 0;
self.next() self.next()
@ -301,7 +306,7 @@ impl VTabCursor for CSVTabCursor {
return Ok(()); return Ok(());
} }
self.eof = !try!(self.reader.read_record(&mut self.cols)); self.eof = !self.reader.read_record(&mut self.cols)?;
} }
self.row_number += 1; self.row_number += 1;
@ -340,8 +345,8 @@ impl From<csv::Error> for Error {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use vtab::csvtab; use crate::vtab::csvtab;
use {Connection, Result, NO_PARAMS}; use crate::{Connection, Result, NO_PARAMS};
#[test] #[test]
fn test_csv_module() { fn test_csv_module() {
@ -361,7 +366,7 @@ mod test {
.query_map(NO_PARAMS, |row| row.get::<_, i32>(0)) .query_map(NO_PARAMS, |row| row.get::<_, i32>(0))
.unwrap() .unwrap()
.collect(); .collect();
let sum = ids.unwrap().iter().fold(0, |acc, &id| acc + id); let sum = ids.unwrap().iter().sum::<i32>();
assert_eq!(sum, 15); assert_eq!(sum, 15);
} }
db.execute_batch("DROP TABLE vtab").unwrap(); db.execute_batch("DROP TABLE vtab").unwrap();

View File

@ -17,12 +17,12 @@ use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::ptr;
use std::slice; use std::slice;
use context::set_result; use crate::context::set_result;
use error::error_from_sqlite_code; use crate::error::error_from_sqlite_code;
use ffi; use crate::ffi;
pub use ffi::{sqlite3_vtab, sqlite3_vtab_cursor}; pub use crate::ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
use types::{FromSql, FromSqlError, ToSql, ValueRef}; use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
use {str_to_cstring, Connection, Error, InnerConnection, Result}; use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
// let conn: Connection = ...; // let conn: Connection = ...;
// let mod: Module = ...; // VTab builder // let mod: Module = ...; // VTab builder
@ -99,6 +99,8 @@ pub fn read_only_module<T: CreateVTab>(version: c_int) -> Module<T> {
xSavepoint: None, xSavepoint: None,
xRelease: None, xRelease: None,
xRollbackTo: None, xRollbackTo: None,
#[cfg(any(feature = "bundled", feature = "vtab_v3"))]
xShadowName: None,
}; };
Module { Module {
base: ffi_module, base: ffi_module,
@ -137,6 +139,8 @@ pub fn eponymous_only_module<T: VTab>(version: c_int) -> Module<T> {
xSavepoint: None, xSavepoint: None,
xRelease: None, xRelease: None,
xRollbackTo: None, xRollbackTo: None,
#[cfg(any(feature = "bundled", feature = "vtab_v3"))]
xShadowName: None,
}; };
Module { Module {
base: ffi_module, base: ffi_module,
@ -250,7 +254,7 @@ pub struct IndexInfo(*mut ffi::sqlite3_index_info);
impl IndexInfo { impl IndexInfo {
/// Record WHERE clause constraints. /// Record WHERE clause constraints.
pub fn constraints(&self) -> IndexConstraintIter { pub fn constraints(&self) -> IndexConstraintIter<'_> {
let constraints = let constraints =
unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) }; unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
IndexConstraintIter { IndexConstraintIter {
@ -259,7 +263,7 @@ impl IndexInfo {
} }
/// Information about the ORDER BY clause. /// Information about the ORDER BY clause.
pub fn order_bys(&self) -> OrderByIter { pub fn order_bys(&self) -> OrderByIter<'_> {
let order_bys = let order_bys =
unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) }; unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) };
OrderByIter { OrderByIter {
@ -272,7 +276,7 @@ impl IndexInfo {
unsafe { (*self.0).nOrderBy as usize } unsafe { (*self.0).nOrderBy as usize }
} }
pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage { pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage<'_> {
let constraint_usages = unsafe { let constraint_usages = unsafe {
slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize) slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
}; };
@ -412,7 +416,7 @@ impl<'a> OrderBy<'a> {
pub trait VTabCursor: Sized { pub trait VTabCursor: Sized {
/// Begin a search of a virtual table. /// Begin a search of a virtual table.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xfilter_method)) /// (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<()>; 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`. /// Advance cursor to the next row of a result set initiated by `filter`.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method)) /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method))
fn next(&mut self) -> Result<()>; fn next(&mut self) -> Result<()>;
@ -467,6 +471,8 @@ impl<'a> Values<'a> {
Error::FromSqlConversionFailure(idx, value.data_type(), err) Error::FromSqlConversionFailure(idx, value.data_type(), err)
} }
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()),
}) })
} }
@ -474,7 +480,7 @@ impl<'a> Values<'a> {
// So it seems not possible to enhance `ValueRef::from_value`. // So it seems not possible to enhance `ValueRef::from_value`.
#[cfg(feature = "array")] #[cfg(feature = "array")]
pub(crate) fn get_array(&self, idx: usize) -> Result<Option<array::Array>> { pub(crate) fn get_array(&self, idx: usize) -> Result<Option<array::Array>> {
use types::Value; use crate::types::Value;
let arg = self.args[idx]; let arg = self.args[idx];
let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) }; let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) };
if ptr.is_null() { if ptr.is_null() {
@ -489,7 +495,7 @@ impl<'a> Values<'a> {
} }
} }
pub fn iter(&self) -> ValueIter { pub fn iter(&self) -> ValueIter<'_> {
ValueIter { ValueIter {
iter: self.args.iter(), iter: self.args.iter(),
} }
@ -544,7 +550,7 @@ impl InnerConnection {
module: &Module<T>, module: &Module<T>,
aux: Option<T::Aux>, aux: Option<T::Aux>,
) -> Result<()> { ) -> Result<()> {
let c_name = try!(str_to_cstring(module_name)); let c_name = str_to_cstring(module_name)?;
let r = match aux { let r = match aux {
Some(aux) => { Some(aux) => {
let boxed_aux: *mut T::Aux = Box::into_raw(Box::new(aux)); let boxed_aux: *mut T::Aux = Box::into_raw(Box::new(aux));
@ -573,7 +579,7 @@ impl InnerConnection {
} }
/// Escape double-quote (`"`) character occurences by doubling them (`""`). /// Escape double-quote (`"`) character occurences by doubling them (`""`).
pub fn escape_double_quote(identifier: &str) -> Cow<str> { pub fn escape_double_quote(identifier: &str) -> Cow<'_, str> {
if identifier.contains('"') { if identifier.contains('"') {
// escape quote by doubling them // escape quote by doubling them
Owned(identifier.replace("\"", "\"\"")) Owned(identifier.replace("\"", "\"\""))

View File

@ -4,13 +4,13 @@
use std::default::Default; use std::default::Default;
use std::os::raw::c_int; use std::os::raw::c_int;
use ffi; use crate::ffi;
use types::Type; use crate::types::Type;
use vtab::{ use crate::vtab::{
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection, eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
VTabCursor, Values, VTabCursor, Values,
}; };
use {Connection, Result}; use crate::{Connection, Result};
/// Register the "generate_series" module. /// Register the "generate_series" module.
pub fn load_module(conn: &Connection) -> Result<()> { pub fn load_module(conn: &Connection) -> Result<()> {
@ -184,23 +184,23 @@ impl SeriesTabCursor {
} }
} }
impl VTabCursor for SeriesTabCursor { impl VTabCursor for SeriesTabCursor {
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values) -> Result<()> { 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 idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
let mut i = 0; let mut i = 0;
if idx_num.contains(QueryPlanFlags::START) { if idx_num.contains(QueryPlanFlags::START) {
self.min_value = try!(args.get(i)); self.min_value = args.get(i)?;
i += 1; i += 1;
} else { } else {
self.min_value = 0; self.min_value = 0;
} }
if idx_num.contains(QueryPlanFlags::STOP) { if idx_num.contains(QueryPlanFlags::STOP) {
self.max_value = try!(args.get(i)); self.max_value = args.get(i)?;
i += 1; i += 1;
} else { } else {
self.max_value = 0xffff_ffff; self.max_value = 0xffff_ffff;
} }
if idx_num.contains(QueryPlanFlags::STEP) { if idx_num.contains(QueryPlanFlags::STEP) {
self.step = try!(args.get(i)); self.step = args.get(i)?;
if self.step < 1 { if self.step < 1 {
self.step = 1; self.step = 1;
} }
@ -263,14 +263,14 @@ impl VTabCursor for SeriesTabCursor {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use ffi; use crate::ffi;
use vtab::series; use crate::vtab::series;
use {Connection, NO_PARAMS}; use crate::{Connection, NO_PARAMS};
#[test] #[test]
fn test_series_module() { fn test_series_module() {
let version = unsafe { ffi::sqlite3_libversion_number() }; let version = unsafe { ffi::sqlite3_libversion_number() };
if version < 3008012 { if version < 3_008_012 {
return; return;
} }

View File

@ -5,7 +5,6 @@
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
extern crate rusqlite;
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
fn main() { fn main() {

View File

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

View File

@ -1,8 +1,5 @@
//! Ensure Virtual tables can be declared outside `rusqlite` crate. //! Ensure Virtual tables can be declared outside `rusqlite` crate.
#[cfg(feature = "vtab")]
extern crate rusqlite;
#[cfg(feature = "vtab")] #[cfg(feature = "vtab")]
#[test] #[test]
fn test_dummy_module() { fn test_dummy_module() {
@ -61,7 +58,7 @@ fn test_dummy_module() {
&mut self, &mut self,
_idx_num: c_int, _idx_num: c_int,
_idx_str: Option<&str>, _idx_str: Option<&str>,
_args: &Values, _args: &Values<'_>,
) -> Result<()> { ) -> Result<()> {
self.row_id = 1; self.row_id = 1;
Ok(()) Ok(())
@ -91,14 +88,14 @@ fn test_dummy_module() {
.unwrap(); .unwrap();
let version = version_number(); let version = version_number();
if version < 3008012 { if version < 3_008_012 {
return; return;
} }
let mut s = db.prepare("SELECT * FROM dummy()").unwrap(); let mut s = db.prepare("SELECT * FROM dummy()").unwrap();
let dummy = s let dummy = s
.query_row(&[] as &[&ToSql], |row| row.get::<_, i32>(0)) .query_row(&[] as &[&dyn ToSql], |row| row.get::<_, i32>(0))
.unwrap(); .unwrap();
assert_eq!(1, dummy); assert_eq!(1, dummy);
} }