Merge remote-tracking branch 'upstream/master' into pr/open-with-vfs

# Conflicts:
#	src/inner_connection.rs
This commit is contained in:
gwenn 2020-02-09 11:58:49 +01:00
commit 9e17a0b28e
43 changed files with 14816 additions and 7924 deletions

View File

@ -28,11 +28,9 @@ script:
- cargo build --features sqlcipher - cargo build --features sqlcipher
- cargo build --features "bundled sqlcipher" - cargo build --features "bundled sqlcipher"
- cargo test - cargo test
- cargo test --features backup - cargo test --features "backup blob extra_check"
- cargo test --features blob - cargo test --features "collation functions"
- cargo test --features functions - cargo test --features "hooks limits"
- cargo test --features hooks
- cargo test --features limits
- cargo test --features load_extension - cargo test --features load_extension
- cargo test --features trace - cargo test --features trace
- cargo test --features chrono - cargo test --features chrono
@ -42,9 +40,9 @@ script:
- cargo test --features sqlcipher - cargo test --features sqlcipher
- cargo test --features i128_blob - cargo test --features i128_blob
- cargo test --features uuid - cargo test --features uuid
- cargo test --features "unlock_notify bundled" - cargo test --features "bundled unlock_notify window"
- cargo test --features "array bundled csvtab vtab" - cargo test --features "array bundled csvtab series vtab"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url uuid vtab" - cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url uuid vtab buildtime_bindgen" - cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab buildtime_bindgen"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled" - cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled buildtime_bindgen" - cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension session serde_json trace url uuid vtab bundled buildtime_bindgen"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rusqlite" name = "rusqlite"
version = "0.18.0" version = "0.21.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"] authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
edition = "2018" edition = "2018"
description = "Ergonomic wrapper for SQLite" description = "Ergonomic wrapper for SQLite"
@ -28,11 +28,12 @@ load_extension = []
backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"] backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
# sqlite3_blob_reopen: 3.7.4 # sqlite3_blob_reopen: 3.7.4
blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"] blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
collation = []
# sqlite3_create_function_v2: 3.7.3 (2010-10-08) # sqlite3_create_function_v2: 3.7.3 (2010-10-08)
functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"] functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
# sqlite3_log: 3.6.23 (2010-03-09) # sqlite3_log: 3.6.23 (2010-03-09)
trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"] trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
bundled = ["libsqlite3-sys/bundled"] bundled = ["libsqlite3-sys/bundled", "modern_sqlite"]
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"] buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
limits = [] limits = []
hooks = [] hooks = []
@ -48,6 +49,13 @@ csvtab = ["csv", "vtab"]
array = ["vtab"] array = ["vtab"]
# session extension: 3.13.0 # session extension: 3.13.0
session = ["libsqlite3-sys/session", "hooks"] session = ["libsqlite3-sys/session", "hooks"]
# window functions: 3.25.0
window = ["functions"]
# 3.9.0
series = ["vtab"]
# check for invalid query.
extra_check = []
modern_sqlite = ["libsqlite3-sys/bundled_bindings"]
[dependencies] [dependencies]
time = "0.1.0" time = "0.1.0"
@ -56,23 +64,24 @@ lru-cache = "0.1"
chrono = { version = "0.4", optional = true } chrono = { version = "0.4", optional = true }
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true }
csv = { version = "1.0", optional = true } csv = { version = "1.0", optional = true }
url = { version = "1.7", optional = true } url = { version = "2.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-iterator = "0.2" fallible-iterator = "0.2"
fallible-streaming-iterator = "0.1" fallible-streaming-iterator = "0.1"
memchr = "2.2.0" memchr = "2.2.0"
uuid = { version = "0.7", optional = true } uuid = { version = "0.8", optional = true }
[dev-dependencies] [dev-dependencies]
tempdir = "0.3" tempfile = "3.1.0"
lazy_static = "1.0" lazy_static = "1.0"
regex = "1.0" regex = "1.0"
uuid = { version = "0.7", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }
unicase = "2.4.0"
[dependencies.libsqlite3-sys] [dependencies.libsqlite3-sys]
path = "libsqlite3-sys" path = "libsqlite3-sys"
version = "0.14" version = "0.18"
[[test]] [[test]]
name = "config_log" name = "config_log"
@ -85,7 +94,7 @@ name = "deny_single_threaded_sqlite_config"
name = "vtab" name = "vtab"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace", "url", "vtab" ] features = [ "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "trace", "url", "vtab", "window", "modern_sqlite" ]
all-features = false all-features = false
no-default-features = true no-default-features = true
default-target = "x86_64-unknown-linux-gnu" default-target = "x86_64-unknown-linux-gnu"

View File

@ -10,8 +10,7 @@ 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
use rusqlite::types::ToSql; use rusqlite::{params, Connection, Result};
use rusqlite::{Connection, Result, NO_PARAMS};
use time::Timespec; use time::Timespec;
#[derive(Debug)] #[derive(Debug)]
@ -32,7 +31,7 @@ fn main() -> Result<()> {
time_created TEXT NOT NULL, time_created TEXT NOT NULL,
data BLOB data BLOB
)", )",
NO_PARAMS, params![],
)?; )?;
let me = Person { let me = Person {
id: 0, id: 0,
@ -43,18 +42,18 @@ fn main() -> Result<()> {
conn.execute( conn.execute(
"INSERT INTO person (name, time_created, data) "INSERT INTO person (name, time_created, data)
VALUES (?1, ?2, ?3)", VALUES (?1, ?2, ?3)",
&[&me.name as &ToSql, &me.time_created, &me.data], params![me.name, me.time_created, me.data],
)?; )?;
let mut stmt = conn let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person")?;
.prepare("SELECT id, name, time_created, data FROM person")?; let person_iter = stmt.query_map(params![], |row| {
let person_iter = stmt Ok(Person {
.query_map(NO_PARAMS, |row| Ok(Person {
id: row.get(0)?, id: row.get(0)?,
name: row.get(1)?, name: row.get(1)?,
time_created: row.get(2)?, time_created: row.get(2)?,
data: row.get(3)?, data: row.get(3)?,
}))?; })
})?;
for person in person_iter { for person in person_iter {
println!("Found person {:?}", person.unwrap()); println!("Found person {:?}", person.unwrap());
@ -118,13 +117,13 @@ declarations for SQLite's C API. By default, `libsqlite3-sys` attempts to find a
You can adjust this behavior in a number of ways: 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 [cc](https://crates.io/crates/cc) 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.27.2 (as of `rusqlite` 0.18.0 / `libsqlite3-sys` is currently SQLite 3.30.1 (as of `rusqlite` 0.21.0 / `libsqlite3-sys`
0.14.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.17.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.18.0" version = "0.21.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
@ -166,6 +165,12 @@ bundled version of SQLite. If you need other specific pregenerated binding
versions, please file an issue. If you want to run `bindgen` at buildtime to versions, please file an issue. If you want to run `bindgen` at buildtime to
produce your own bindings, use the `buildtime_bindgen` Cargo feature. produce your own bindings, use the `buildtime_bindgen` Cargo feature.
If you enable the `modern_sqlite` feature, we'll use the bindings we would have
included with the bundled build. You generally should have `buildtime_bindgen`
enabled if you turn this on, as otherwise you'll need to keep the version of
SQLite you link with in sync with what rusqlite would have bundled, (usually the
most recent release of sqlite). Failing to do this will cause a runtime error.
## Author ## Author
John Gallagher, johnkgallagher@gmail.com John Gallagher, johnkgallagher@gmail.com

View File

@ -2,9 +2,9 @@ environment:
matrix: matrix:
- TARGET: x86_64-pc-windows-gnu - TARGET: x86_64-pc-windows-gnu
MSYS2_BITS: 64 MSYS2_BITS: 64
- 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
@ -33,7 +33,7 @@ build: false
test_script: test_script:
- cargo test --lib --verbose - cargo test --lib --verbose
- cargo test --lib --verbose --features bundled - cargo test --lib --verbose --features bundled
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace" - cargo test --lib --features "backup blob chrono collation functions hooks limits load_extension serde_json trace"
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen" - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled" - cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen" - cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.14.0" version = "0.18.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"] authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
edition = "2018" edition = "2018"
repository = "https://github.com/jgallagher/rusqlite" repository = "https://github.com/jgallagher/rusqlite"
@ -13,13 +13,17 @@ categories = ["external-ffi-bindings"]
[features] [features]
default = ["min_sqlite_version_3_6_8"] default = ["min_sqlite_version_3_6_8"]
bundled = ["cc"] bundled = ["cc", "bundled_bindings"]
bundled-windows = ["cc", "bundled_bindings"]
buildtime_bindgen = ["bindgen", "pkg-config", "vcpkg"] buildtime_bindgen = ["bindgen", "pkg-config", "vcpkg"]
sqlcipher = [] 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"] min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
# Bundle only the bindings file. Note that this does nothing if
# `buildtime_bindgen` is enabled.
bundled_bindings = []
# sqlite3_unlock_notify >= 3.6.12 # sqlite3_unlock_notify >= 3.6.12
unlock_notify = [] unlock_notify = []
# 3.13.0 # 3.13.0
@ -28,7 +32,7 @@ preupdate_hook = []
session = ["preupdate_hook"] session = ["preupdate_hook"]
[build-dependencies] [build-dependencies]
bindgen = { version = "0.49", optional = true } bindgen = { version = "0.53", optional = true, default-features = false, features = ["runtime"] }
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 }

View File

@ -5,7 +5,10 @@ fn main() {
let out_dir = env::var("OUT_DIR").unwrap(); let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("bindgen.rs"); let out_path = Path::new(&out_dir).join("bindgen.rs");
if cfg!(feature = "sqlcipher") { if cfg!(feature = "sqlcipher") {
if cfg!(feature = "bundled") { if cfg!(any(
feature = "bundled",
all(windows, feature = "bundled-windows")
)) {
println!( println!(
"cargo:warning={}", "cargo:warning={}",
"Builds with bundled SQLCipher are not supported. Searching for SQLCipher to link against. \ "Builds with bundled SQLCipher are not supported. Searching for SQLCipher to link against. \
@ -13,21 +16,23 @@ fn main() {
} }
build_linked::main(&out_dir, &out_path) build_linked::main(&out_dir, &out_path)
} else { } else {
// This can't be `cfg!` without always requiring our `mod build_bundled` (and thus `cc`) // This can't be `cfg!` without always requiring our `mod build_bundled` (and
#[cfg(feature = "bundled")] // thus `cc`)
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
{ {
build_bundled::main(&out_dir, &out_path) build_bundled::main(&out_dir, &out_path)
} }
#[cfg(not(feature = "bundled"))] #[cfg(not(any(feature = "bundled", all(windows, feature = "bundled-windows"))))]
{ {
build_linked::main(&out_dir, &out_path) build_linked::main(&out_dir, &out_path)
} }
} }
} }
#[cfg(feature = "bundled")] #[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
mod build_bundled { mod build_bundled {
use cc; use cc;
use std::env;
use std::path::Path; use std::path::Path;
pub fn main(out_dir: &str, out_path: &Path) { pub fn main(out_dir: &str, out_path: &Path) {
@ -65,11 +70,29 @@ mod build_bundled {
.flag("-DSQLITE_ENABLE_RTREE") .flag("-DSQLITE_ENABLE_RTREE")
.flag("-DSQLITE_ENABLE_STAT2") .flag("-DSQLITE_ENABLE_STAT2")
.flag("-DSQLITE_ENABLE_STAT4") .flag("-DSQLITE_ENABLE_STAT4")
.flag("-DSQLITE_HAVE_ISNAN")
.flag("-DSQLITE_SOUNDEX") .flag("-DSQLITE_SOUNDEX")
.flag("-DSQLITE_THREADSAFE=1") .flag("-DSQLITE_THREADSAFE=1")
.flag("-DSQLITE_USE_URI") .flag("-DSQLITE_USE_URI")
.flag("-DHAVE_USLEEP=1"); .flag("-DHAVE_USLEEP=1");
// Older versions of visual studio don't support c99 (including isnan), which
// causes a build failure when the linker fails to find the `isnan`
// function. `sqlite` provides its own implmentation, using the fact
// that x != x when x is NaN.
//
// There may be other platforms that don't support `isnan`, they should be
// tested for here.
if cfg!(target_env = "msvc") {
use cc::windows_registry::{find_vs_version, VsVers};
let vs_has_nan = match find_vs_version() {
Ok(ver) => ver != VsVers::Vs12,
Err(_msg) => false,
};
if vs_has_nan {
cfg.flag("-DSQLITE_HAVE_ISNAN");
}
} else {
cfg.flag("-DSQLITE_HAVE_ISNAN");
}
if cfg!(feature = "unlock_notify") { if cfg!(feature = "unlock_notify") {
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY"); cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
} }
@ -79,6 +102,17 @@ mod build_bundled {
if cfg!(feature = "session") { if cfg!(feature = "session") {
cfg.flag("-DSQLITE_ENABLE_SESSION"); cfg.flag("-DSQLITE_ENABLE_SESSION");
} }
if let Ok(limit) = env::var("SQLITE_MAX_VARIABLE_NUMBER") {
cfg.flag(&format!("-DSQLITE_MAX_VARIABLE_NUMBER={}", limit));
}
println!("cargo:rerun-if-env-changed=SQLITE_MAX_VARIABLE_NUMBER");
if let Ok(limit) = env::var("SQLITE_MAX_EXPR_DEPTH") {
cfg.flag(&format!("-DSQLITE_MAX_EXPR_DEPTH={}", limit));
}
println!("cargo:rerun-if-env-changed=SQLITE_MAX_EXPR_DEPTH");
cfg.compile("libsqlite3.a"); cfg.compile("libsqlite3.a");
println!("cargo:lib_dir={}", out_dir); println!("cargo:lib_dir={}", out_dir);
@ -104,10 +138,12 @@ impl From<HeaderLocation> for String {
match header { match header {
HeaderLocation::FromEnvironment => { HeaderLocation::FromEnvironment => {
let prefix = env_prefix(); let prefix = env_prefix();
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!( let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).unwrap_or_else(|_| {
"{}_INCLUDE_DIR must be set if {}_LIB_DIR is set", panic!(
prefix, prefix "{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
)); prefix, prefix
)
});
header.push_str("/sqlite3.h"); header.push_str("/sqlite3.h");
header header
} }
@ -129,15 +165,18 @@ mod build_linked {
pub fn main(_out_dir: &str, out_path: &Path) { pub fn main(_out_dir: &str, out_path: &Path) {
let header = find_sqlite(); let header = find_sqlite();
if cfg!(feature = "bundled") && !cfg!(feature = "buildtime_bindgen") { if cfg!(any(
// We can only get here if `bundled` and `sqlcipher` were both feature = "bundled_bindings",
// specified (and `builtime_bindgen` was not). In order to keep feature = "bundled",
// `rusqlite` relatively clean we hide the fact that `bundled` can all(windows, feature = "bundled-windows")
// be ignored in some cases, and just use the bundled bindings, even )) && !cfg!(feature = "buildtime_bindgen")
// though the library we found might not match their version. {
// Ideally we'd perform a version check here, but doing so is rather // Generally means the `bundled_bindings` feature is enabled
// tricky, since we might not have access to executables (and // (there's also an edge case where we get here involving
// moreover, we might be cross compiling). // sqlcipher). In either case most users are better off with turning
// on buildtime_bindgen instead, but this is still supported as we
// have runtime version checks and there are good reasons to not
// want to run bindgen.
std::fs::copy("sqlite3/bindgen_bundled_version.rs", out_path) std::fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
.expect("Could not copy bindings to output directory"); .expect("Could not copy bindings to output directory");
} else { } else {
@ -160,18 +199,22 @@ mod build_linked {
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()); println!("cargo:rerun-if-env-changed={}_STATIC", env_prefix());
if cfg!(target_os = "windows") {
println!("cargo:rerun-if-env-changed=PATH");
}
if cfg!(all(feature = "vcpkg", target_env = "msvc")) { if cfg!(all(feature = "vcpkg", target_env = "msvc")) {
println!("cargo:rerun-if-env-changed=VCPKGRS_DYNAMIC"); println!("cargo:rerun-if-env-changed=VCPKGRS_DYNAMIC");
} }
// dependents can access `DEP_SQLITE3_LINK_TARGET` (`sqlite3` being the
// `links=` value in our Cargo.toml) to get this value. This might be
// useful if you need to ensure whatever crypto library sqlcipher relies
// on is available, for example.
println!("cargo:link-target={}", link_lib);
// 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())) {
// Try to use pkg-config to determine link commands // Try to use pkg-config to determine link commands
let pkgconfig_path = Path::new(&dir).join("pkgconfig"); let pkgconfig_path = Path::new(&dir).join("pkgconfig");
env::set_var("PKG_CONFIG_PATH", pkgconfig_path); env::set_var("PKG_CONFIG_PATH", pkgconfig_path);
if let Err(_) = pkg_config::Config::new().probe(link_lib) { if pkg_config::Config::new().probe(link_lib).is_err() {
// Otherwise just emit the bare minimum link commands. // Otherwise just emit the bare minimum link commands.
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib); println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
println!("cargo:rustc-link-search={}", dir); println!("cargo:rustc-link-search={}", dir);
@ -260,8 +303,8 @@ mod bindings {
mod bindings { mod bindings {
use bindgen; use bindgen;
use self::bindgen::callbacks::{IntKind, ParseCallbacks};
use super::HeaderLocation; use super::HeaderLocation;
use bindgen::callbacks::{IntKind, ParseCallbacks};
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Write; use std::io::Write;
@ -300,7 +343,7 @@ mod bindings {
bindings bindings
.generate() .generate()
.expect(&format!("could not run bindgen on header {}", header)) .unwrap_or_else(|_| panic!("could not run bindgen on header {}", header))
.write(Box::new(&mut output)) .write(Box::new(&mut output))
.expect("could not write output of bindgen"); .expect("could not write output of bindgen");
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!"); let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
@ -319,10 +362,10 @@ mod bindings {
.write(true) .write(true)
.truncate(true) .truncate(true)
.create(true) .create(true)
.open(out_path.clone()) .open(out_path)
.expect(&format!("Could not write to {:?}", out_path)); .unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
file.write_all(output.as_bytes()) file.write_all(output.as_bytes())
.expect(&format!("Could not write to {:?}", out_path)); .unwrap_or_else(|_| panic!("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.28.0\0"; pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.31.0\0";
pub const SQLITE_VERSION_NUMBER: i32 = 3028000; pub const SQLITE_VERSION_NUMBER: i32 = 3031000;
pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] = pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] =
b"2019-04-16 19:49:53 884b4b7e502b4e991677b53971277adfaf0a04a284f8e483e2553d0f83156b50\0"; b"2020-01-22 18:38:59 f6affdd41608946fcfcea914ece149038a8b25a62bbe719ed2561c649b86d824\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;
@ -79,6 +79,7 @@ pub const SQLITE_CANTOPEN_ISDIR: i32 = 526;
pub const SQLITE_CANTOPEN_FULLPATH: i32 = 782; pub const SQLITE_CANTOPEN_FULLPATH: i32 = 782;
pub const SQLITE_CANTOPEN_CONVPATH: i32 = 1038; pub const SQLITE_CANTOPEN_CONVPATH: i32 = 1038;
pub const SQLITE_CANTOPEN_DIRTYWAL: i32 = 1294; pub const SQLITE_CANTOPEN_DIRTYWAL: i32 = 1294;
pub const SQLITE_CANTOPEN_SYMLINK: i32 = 1550;
pub const SQLITE_CORRUPT_VTAB: i32 = 267; pub const SQLITE_CORRUPT_VTAB: i32 = 267;
pub const SQLITE_CORRUPT_SEQUENCE: i32 = 523; pub const SQLITE_CORRUPT_SEQUENCE: i32 = 523;
pub const SQLITE_READONLY_RECOVERY: i32 = 264; pub const SQLITE_READONLY_RECOVERY: i32 = 264;
@ -98,11 +99,13 @@ pub const SQLITE_CONSTRAINT_TRIGGER: i32 = 1811;
pub const SQLITE_CONSTRAINT_UNIQUE: i32 = 2067; pub const SQLITE_CONSTRAINT_UNIQUE: i32 = 2067;
pub const SQLITE_CONSTRAINT_VTAB: i32 = 2323; pub const SQLITE_CONSTRAINT_VTAB: i32 = 2323;
pub const SQLITE_CONSTRAINT_ROWID: i32 = 2579; pub const SQLITE_CONSTRAINT_ROWID: i32 = 2579;
pub const SQLITE_CONSTRAINT_PINNED: i32 = 2835;
pub const SQLITE_NOTICE_RECOVER_WAL: i32 = 283; pub const SQLITE_NOTICE_RECOVER_WAL: i32 = 283;
pub const SQLITE_NOTICE_RECOVER_ROLLBACK: i32 = 539; pub const SQLITE_NOTICE_RECOVER_ROLLBACK: i32 = 539;
pub const SQLITE_WARNING_AUTOINDEX: i32 = 284; pub const SQLITE_WARNING_AUTOINDEX: i32 = 284;
pub const SQLITE_AUTH_USER: i32 = 279; pub const SQLITE_AUTH_USER: i32 = 279;
pub const SQLITE_OK_LOAD_PERMANENTLY: i32 = 256; pub const SQLITE_OK_LOAD_PERMANENTLY: i32 = 256;
pub const SQLITE_OK_SYMLINK: i32 = 512;
pub const SQLITE_OPEN_READONLY: i32 = 1; pub const SQLITE_OPEN_READONLY: i32 = 1;
pub const SQLITE_OPEN_READWRITE: i32 = 2; pub const SQLITE_OPEN_READWRITE: i32 = 2;
pub const SQLITE_OPEN_CREATE: i32 = 4; pub const SQLITE_OPEN_CREATE: i32 = 4;
@ -123,6 +126,7 @@ pub const SQLITE_OPEN_FULLMUTEX: i32 = 65536;
pub const SQLITE_OPEN_SHAREDCACHE: i32 = 131072; pub const SQLITE_OPEN_SHAREDCACHE: i32 = 131072;
pub const SQLITE_OPEN_PRIVATECACHE: i32 = 262144; pub const SQLITE_OPEN_PRIVATECACHE: i32 = 262144;
pub const SQLITE_OPEN_WAL: i32 = 524288; pub const SQLITE_OPEN_WAL: i32 = 524288;
pub const SQLITE_OPEN_NOFOLLOW: i32 = 16777216;
pub const SQLITE_IOCAP_ATOMIC: i32 = 1; pub const SQLITE_IOCAP_ATOMIC: i32 = 1;
pub const SQLITE_IOCAP_ATOMIC512: i32 = 2; pub const SQLITE_IOCAP_ATOMIC512: i32 = 2;
pub const SQLITE_IOCAP_ATOMIC1K: i32 = 4; pub const SQLITE_IOCAP_ATOMIC1K: i32 = 4;
@ -181,6 +185,7 @@ pub const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: i32 = 33;
pub const SQLITE_FCNTL_LOCK_TIMEOUT: i32 = 34; pub const SQLITE_FCNTL_LOCK_TIMEOUT: i32 = 34;
pub const SQLITE_FCNTL_DATA_VERSION: i32 = 35; pub const SQLITE_FCNTL_DATA_VERSION: i32 = 35;
pub const SQLITE_FCNTL_SIZE_LIMIT: i32 = 36; pub const SQLITE_FCNTL_SIZE_LIMIT: i32 = 36;
pub const SQLITE_FCNTL_CKPT_DONE: i32 = 37;
pub const SQLITE_GET_LOCKPROXYFILE: i32 = 2; pub const SQLITE_GET_LOCKPROXYFILE: i32 = 2;
pub const SQLITE_SET_LOCKPROXYFILE: i32 = 3; pub const SQLITE_SET_LOCKPROXYFILE: i32 = 3;
pub const SQLITE_LAST_ERRNO: i32 = 4; pub const SQLITE_LAST_ERRNO: i32 = 4;
@ -232,7 +237,13 @@ 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_DEFENSIVE: i32 = 1010; pub const SQLITE_DBCONFIG_DEFENSIVE: i32 = 1010;
pub const SQLITE_DBCONFIG_WRITABLE_SCHEMA: i32 = 1011; pub const SQLITE_DBCONFIG_WRITABLE_SCHEMA: i32 = 1011;
pub const SQLITE_DBCONFIG_MAX: i32 = 1011; pub const SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: i32 = 1012;
pub const SQLITE_DBCONFIG_DQS_DML: i32 = 1013;
pub const SQLITE_DBCONFIG_DQS_DDL: i32 = 1014;
pub const SQLITE_DBCONFIG_ENABLE_VIEW: i32 = 1015;
pub const SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: i32 = 1016;
pub const SQLITE_DBCONFIG_TRUSTED_SCHEMA: i32 = 1017;
pub const SQLITE_DBCONFIG_MAX: i32 = 1017;
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;
@ -301,6 +312,9 @@ pub const SQLITE_UTF16: i32 = 4;
pub const SQLITE_ANY: i32 = 5; pub const SQLITE_ANY: i32 = 5;
pub const SQLITE_UTF16_ALIGNED: i32 = 8; pub const SQLITE_UTF16_ALIGNED: i32 = 8;
pub const SQLITE_DETERMINISTIC: i32 = 2048; pub const SQLITE_DETERMINISTIC: i32 = 2048;
pub const SQLITE_DIRECTONLY: i32 = 524288;
pub const SQLITE_SUBTYPE: i32 = 1048576;
pub const SQLITE_INNOCUOUS: i32 = 2097152;
pub const SQLITE_WIN32_DATA_DIRECTORY_TYPE: i32 = 1; pub const SQLITE_WIN32_DATA_DIRECTORY_TYPE: i32 = 1;
pub const SQLITE_WIN32_TEMP_DIRECTORY_TYPE: i32 = 2; pub const SQLITE_WIN32_TEMP_DIRECTORY_TYPE: i32 = 2;
pub const SQLITE_INDEX_SCAN_UNIQUE: i32 = 1; pub const SQLITE_INDEX_SCAN_UNIQUE: i32 = 1;
@ -360,7 +374,10 @@ pub const SQLITE_TESTCTRL_ISINIT: i32 = 23;
pub const SQLITE_TESTCTRL_SORTER_MMAP: i32 = 24; pub const SQLITE_TESTCTRL_SORTER_MMAP: i32 = 24;
pub const SQLITE_TESTCTRL_IMPOSTER: i32 = 25; pub const SQLITE_TESTCTRL_IMPOSTER: i32 = 25;
pub const SQLITE_TESTCTRL_PARSER_COVERAGE: i32 = 26; pub const SQLITE_TESTCTRL_PARSER_COVERAGE: i32 = 26;
pub const SQLITE_TESTCTRL_LAST: i32 = 26; pub const SQLITE_TESTCTRL_RESULT_INTREAL: i32 = 27;
pub const SQLITE_TESTCTRL_PRNG_SEED: i32 = 28;
pub const SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS: i32 = 29;
pub const SQLITE_TESTCTRL_LAST: i32 = 29;
pub const SQLITE_STATUS_MEMORY_USED: i32 = 0; pub const SQLITE_STATUS_MEMORY_USED: i32 = 0;
pub const SQLITE_STATUS_PAGECACHE_USED: i32 = 1; pub const SQLITE_STATUS_PAGECACHE_USED: i32 = 1;
pub const SQLITE_STATUS_PAGECACHE_OVERFLOW: i32 = 2; pub const SQLITE_STATUS_PAGECACHE_OVERFLOW: i32 = 2;
@ -397,6 +414,8 @@ pub const SQLITE_CHECKPOINT_FULL: i32 = 1;
pub const SQLITE_CHECKPOINT_RESTART: i32 = 2; pub const SQLITE_CHECKPOINT_RESTART: i32 = 2;
pub const SQLITE_CHECKPOINT_TRUNCATE: i32 = 3; pub const SQLITE_CHECKPOINT_TRUNCATE: i32 = 3;
pub const SQLITE_VTAB_CONSTRAINT_SUPPORT: i32 = 1; pub const SQLITE_VTAB_CONSTRAINT_SUPPORT: i32 = 1;
pub const SQLITE_VTAB_INNOCUOUS: i32 = 2;
pub const SQLITE_VTAB_DIRECTONLY: i32 = 3;
pub const SQLITE_ROLLBACK: i32 = 1; pub const SQLITE_ROLLBACK: i32 = 1;
pub const SQLITE_FAIL: i32 = 3; pub const SQLITE_FAIL: i32 = 3;
pub const SQLITE_REPLACE: i32 = 5; pub const SQLITE_REPLACE: i32 = 5;
@ -421,7 +440,6 @@ pub const FTS5_TOKEN_COLOCATED: i32 = 1;
pub type va_list = __builtin_va_list; pub type va_list = __builtin_va_list;
pub type __gnuc_va_list = __builtin_va_list; pub type __gnuc_va_list = __builtin_va_list;
extern "C" { extern "C" {
#[link_name = "\u{1}sqlite3_version"]
pub static mut sqlite3_version: [::std::os::raw::c_char; 0usize]; pub static mut sqlite3_version: [::std::os::raw::c_char; 0usize];
} }
extern "C" { extern "C" {
@ -1396,7 +1414,7 @@ extern "C" {
extern "C" { extern "C" {
pub fn sqlite3_vmprintf( pub fn sqlite3_vmprintf(
arg1: *const ::std::os::raw::c_char, arg1: *const ::std::os::raw::c_char,
arg2: *mut __va_list_tag, arg2: va_list,
) -> *mut ::std::os::raw::c_char; ) -> *mut ::std::os::raw::c_char;
} }
extern "C" { extern "C" {
@ -1412,7 +1430,7 @@ extern "C" {
arg1: ::std::os::raw::c_int, arg1: ::std::os::raw::c_int,
arg2: *mut ::std::os::raw::c_char, arg2: *mut ::std::os::raw::c_char,
arg3: *const ::std::os::raw::c_char, arg3: *const ::std::os::raw::c_char,
arg4: *mut __va_list_tag, arg4: va_list,
) -> *mut ::std::os::raw::c_char; ) -> *mut ::std::os::raw::c_char;
} }
extern "C" { extern "C" {
@ -1554,6 +1572,27 @@ extern "C" {
arg3: sqlite3_int64, arg3: sqlite3_int64,
) -> sqlite3_int64; ) -> sqlite3_int64;
} }
extern "C" {
pub fn sqlite3_uri_key(
zFilename: *const ::std::os::raw::c_char,
N: ::std::os::raw::c_int,
) -> *const ::std::os::raw::c_char;
}
extern "C" {
pub fn sqlite3_filename_database(
arg1: *const ::std::os::raw::c_char,
) -> *const ::std::os::raw::c_char;
}
extern "C" {
pub fn sqlite3_filename_journal(
arg1: *const ::std::os::raw::c_char,
) -> *const ::std::os::raw::c_char;
}
extern "C" {
pub fn sqlite3_filename_wal(
arg1: *const ::std::os::raw::c_char,
) -> *const ::std::os::raw::c_char;
}
extern "C" { extern "C" {
pub fn sqlite3_errcode(db: *mut sqlite3) -> ::std::os::raw::c_int; pub fn sqlite3_errcode(db: *mut sqlite3) -> ::std::os::raw::c_int;
} }
@ -2332,11 +2371,9 @@ extern "C" {
pub fn sqlite3_sleep(arg1: ::std::os::raw::c_int) -> ::std::os::raw::c_int; pub fn sqlite3_sleep(arg1: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
} }
extern "C" { extern "C" {
#[link_name = "\u{1}sqlite3_temp_directory"]
pub static mut sqlite3_temp_directory: *mut ::std::os::raw::c_char; pub static mut sqlite3_temp_directory: *mut ::std::os::raw::c_char;
} }
extern "C" { extern "C" {
#[link_name = "\u{1}sqlite3_data_directory"]
pub static mut sqlite3_data_directory: *mut ::std::os::raw::c_char; pub static mut sqlite3_data_directory: *mut ::std::os::raw::c_char;
} }
extern "C" { extern "C" {
@ -2421,6 +2458,9 @@ extern "C" {
extern "C" { extern "C" {
pub fn sqlite3_soft_heap_limit64(N: sqlite3_int64) -> sqlite3_int64; pub fn sqlite3_soft_heap_limit64(N: sqlite3_int64) -> sqlite3_int64;
} }
extern "C" {
pub fn sqlite3_hard_heap_limit64(N: sqlite3_int64) -> sqlite3_int64;
}
extern "C" { extern "C" {
pub fn sqlite3_soft_heap_limit(N: ::std::os::raw::c_int); pub fn sqlite3_soft_heap_limit(N: ::std::os::raw::c_int);
} }
@ -3221,6 +3261,12 @@ extern "C" {
xDestroy: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>, xDestroy: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
) -> ::std::os::raw::c_int; ) -> ::std::os::raw::c_int;
} }
extern "C" {
pub fn sqlite3_drop_modules(
db: *mut sqlite3,
azKeep: *mut *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int;
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct sqlite3_vtab { pub struct sqlite3_vtab {
@ -3575,7 +3621,7 @@ extern "C" {
pub fn sqlite3_str_vappendf( pub fn sqlite3_str_vappendf(
arg1: *mut sqlite3_str, arg1: *mut sqlite3_str,
zFormat: *const ::std::os::raw::c_char, zFormat: *const ::std::os::raw::c_char,
arg2: *mut __va_list_tag, arg2: va_list,
); );
} }
extern "C" { extern "C" {
@ -5171,65 +5217,4 @@ fn bindgen_test_layout_fts5_api() {
) )
); );
} }
pub type __builtin_va_list = [__va_list_tag; 1usize]; pub type __builtin_va_list = *mut ::std::os::raw::c_char;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct __va_list_tag {
pub gp_offset: ::std::os::raw::c_uint,
pub fp_offset: ::std::os::raw::c_uint,
pub overflow_arg_area: *mut ::std::os::raw::c_void,
pub reg_save_area: *mut ::std::os::raw::c_void,
}
#[test]
fn bindgen_test_layout___va_list_tag() {
assert_eq!(
::std::mem::size_of::<__va_list_tag>(),
24usize,
concat!("Size of: ", stringify!(__va_list_tag))
);
assert_eq!(
::std::mem::align_of::<__va_list_tag>(),
8usize,
concat!("Alignment of ", stringify!(__va_list_tag))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__va_list_tag>())).gp_offset as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__va_list_tag),
"::",
stringify!(gp_offset)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__va_list_tag>())).fp_offset as *const _ as usize },
4usize,
concat!(
"Offset of field: ",
stringify!(__va_list_tag),
"::",
stringify!(fp_offset)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__va_list_tag>())).overflow_arg_area as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(__va_list_tag),
"::",
stringify!(overflow_arg_area)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__va_list_tag>())).reg_save_area as *const _ as usize },
16usize,
concat!(
"Offset of field: ",
stringify!(__va_list_tag),
"::",
stringify!(reg_save_area)
)
);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -322,6 +322,14 @@ struct sqlite3_api_routines {
/* Version 3.28.0 and later */ /* Version 3.28.0 and later */
int (*stmt_isexplain)(sqlite3_stmt*); int (*stmt_isexplain)(sqlite3_stmt*);
int (*value_frombind)(sqlite3_value*); int (*value_frombind)(sqlite3_value*);
/* Version 3.30.0 and later */
int (*drop_modules)(sqlite3*,const char**);
/* Version 3.31.0 and later */
sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64);
const char *(*uri_key)(const char*,int);
const char *(*filename_database)(const char*);
const char *(*filename_journal)(const char*);
const char *(*filename_wal)(const char*);
}; };
/* /*
@ -614,6 +622,14 @@ typedef int (*sqlite3_loadext_entry)(
/* Version 3.28.0 and later */ /* Version 3.28.0 and later */
#define sqlite3_stmt_isexplain sqlite3_api->isexplain #define sqlite3_stmt_isexplain sqlite3_api->isexplain
#define sqlite3_value_frombind sqlite3_api->frombind #define sqlite3_value_frombind sqlite3_api->frombind
/* Version 3.30.0 and later */
#define sqlite3_drop_modules sqlite3_api->drop_modules
/* Version 3.31.0 andn later */
#define sqlite3_hard_heap_limit64 sqlite3_api->hard_heap_limit64
#define sqlite3_uri_key sqlite3_api->uri_key
#define sqlite3_filename_database sqlite3_api->filename_database
#define sqlite3_filename_journal sqlite3_api->filename_journal
#define sqlite3_filename_wal sqlite3_api->filename_wal
#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

@ -135,7 +135,12 @@ const SQLITE_IOERR_MMAP: c_int = (super::SQLITE_IOERR | (24 << 8));
const SQLITE_IOERR_GETTEMPPATH: c_int = (super::SQLITE_IOERR | (25 << 8)); const SQLITE_IOERR_GETTEMPPATH: c_int = (super::SQLITE_IOERR | (25 << 8));
const SQLITE_IOERR_CONVPATH: c_int = (super::SQLITE_IOERR | (26 << 8)); const SQLITE_IOERR_CONVPATH: c_int = (super::SQLITE_IOERR | (26 << 8));
const SQLITE_IOERR_VNODE: c_int = (super::SQLITE_IOERR | (27 << 8)); const SQLITE_IOERR_VNODE: c_int = (super::SQLITE_IOERR | (27 << 8));
const SQLITE_IOERR_AUTH: c_int = (super::SQLITE_IOERR | (28 << 8));
const SQLITE_IOERR_BEGIN_ATOMIC: c_int = (super::SQLITE_IOERR | (29 << 8));
const SQLITE_IOERR_COMMIT_ATOMIC: c_int = (super::SQLITE_IOERR | (30 << 8));
const SQLITE_IOERR_ROLLBACK_ATOMIC: c_int = (super::SQLITE_IOERR | (31 << 8));
const SQLITE_LOCKED_SHAREDCACHE: c_int = (super::SQLITE_LOCKED | (1 << 8)); const SQLITE_LOCKED_SHAREDCACHE: c_int = (super::SQLITE_LOCKED | (1 << 8));
const SQLITE_LOCKED_VTAB: c_int = (super::SQLITE_LOCKED | (2 << 8));
const SQLITE_BUSY_RECOVERY: c_int = (super::SQLITE_BUSY | (1 << 8)); const SQLITE_BUSY_RECOVERY: c_int = (super::SQLITE_BUSY | (1 << 8));
const SQLITE_BUSY_SNAPSHOT: c_int = (super::SQLITE_BUSY | (2 << 8)); const SQLITE_BUSY_SNAPSHOT: c_int = (super::SQLITE_BUSY | (2 << 8));
const SQLITE_CANTOPEN_NOTEMPDIR: c_int = (super::SQLITE_CANTOPEN | (1 << 8)); const SQLITE_CANTOPEN_NOTEMPDIR: c_int = (super::SQLITE_CANTOPEN | (1 << 8));
@ -143,10 +148,13 @@ const SQLITE_CANTOPEN_ISDIR: c_int = (super::SQLITE_CANTOPEN | (2 << 8));
const SQLITE_CANTOPEN_FULLPATH: c_int = (super::SQLITE_CANTOPEN | (3 << 8)); const SQLITE_CANTOPEN_FULLPATH: c_int = (super::SQLITE_CANTOPEN | (3 << 8));
const SQLITE_CANTOPEN_CONVPATH: c_int = (super::SQLITE_CANTOPEN | (4 << 8)); const SQLITE_CANTOPEN_CONVPATH: c_int = (super::SQLITE_CANTOPEN | (4 << 8));
const SQLITE_CORRUPT_VTAB: c_int = (super::SQLITE_CORRUPT | (1 << 8)); const SQLITE_CORRUPT_VTAB: c_int = (super::SQLITE_CORRUPT | (1 << 8));
const SQLITE_CORRUPT_SEQUENCE: c_int = (super::SQLITE_CORRUPT | (2 << 8));
const SQLITE_READONLY_RECOVERY: c_int = (super::SQLITE_READONLY | (1 << 8)); const SQLITE_READONLY_RECOVERY: c_int = (super::SQLITE_READONLY | (1 << 8));
const SQLITE_READONLY_CANTLOCK: c_int = (super::SQLITE_READONLY | (2 << 8)); const SQLITE_READONLY_CANTLOCK: c_int = (super::SQLITE_READONLY | (2 << 8));
const SQLITE_READONLY_ROLLBACK: c_int = (super::SQLITE_READONLY | (3 << 8)); const SQLITE_READONLY_ROLLBACK: c_int = (super::SQLITE_READONLY | (3 << 8));
const SQLITE_READONLY_DBMOVED: c_int = (super::SQLITE_READONLY | (4 << 8)); const SQLITE_READONLY_DBMOVED: c_int = (super::SQLITE_READONLY | (4 << 8));
const SQLITE_READONLY_CANTINIT: c_int = (super::SQLITE_READONLY | (5 << 8));
const SQLITE_READONLY_DIRECTORY: c_int = (super::SQLITE_READONLY | (6 << 8));
const SQLITE_ABORT_ROLLBACK: c_int = (super::SQLITE_ABORT | (2 << 8)); const SQLITE_ABORT_ROLLBACK: c_int = (super::SQLITE_ABORT | (2 << 8));
const SQLITE_CONSTRAINT_CHECK: c_int = (super::SQLITE_CONSTRAINT | (1 << 8)); const SQLITE_CONSTRAINT_CHECK: c_int = (super::SQLITE_CONSTRAINT | (1 << 8));
const SQLITE_CONSTRAINT_COMMITHOOK: c_int = (super::SQLITE_CONSTRAINT | (2 << 8)); const SQLITE_CONSTRAINT_COMMITHOOK: c_int = (super::SQLITE_CONSTRAINT | (2 << 8));
@ -224,7 +232,12 @@ pub fn code_to_str(code: c_int) -> &'static str {
SQLITE_IOERR_GETTEMPPATH => "VFS is unable to determine a suitable directory for temporary files", SQLITE_IOERR_GETTEMPPATH => "VFS is unable to determine a suitable directory for temporary files",
SQLITE_IOERR_CONVPATH => "cygwin_conv_path() system call failed", SQLITE_IOERR_CONVPATH => "cygwin_conv_path() system call failed",
SQLITE_IOERR_VNODE => "SQLITE_IOERR_VNODE", // not documented? SQLITE_IOERR_VNODE => "SQLITE_IOERR_VNODE", // not documented?
SQLITE_IOERR_AUTH => "SQLITE_IOERR_AUTH",
SQLITE_IOERR_BEGIN_ATOMIC => "SQLITE_IOERR_BEGIN_ATOMIC",
SQLITE_IOERR_COMMIT_ATOMIC => "SQLITE_IOERR_COMMIT_ATOMIC",
SQLITE_IOERR_ROLLBACK_ATOMIC => "SQLITE_IOERR_ROLLBACK_ATOMIC",
SQLITE_LOCKED_SHAREDCACHE => "Locking conflict due to another connection with a shared cache", SQLITE_LOCKED_SHAREDCACHE => "Locking conflict due to another connection with a shared cache",
SQLITE_LOCKED_VTAB => "SQLITE_LOCKED_VTAB",
SQLITE_BUSY_RECOVERY => "Another process is recovering a WAL mode database file", SQLITE_BUSY_RECOVERY => "Another process is recovering a WAL mode database file",
SQLITE_BUSY_SNAPSHOT => "Cannot promote read transaction to write transaction because of writes by another connection", SQLITE_BUSY_SNAPSHOT => "Cannot promote read transaction to write transaction because of writes by another connection",
SQLITE_CANTOPEN_NOTEMPDIR => "SQLITE_CANTOPEN_NOTEMPDIR", // no longer used SQLITE_CANTOPEN_NOTEMPDIR => "SQLITE_CANTOPEN_NOTEMPDIR", // no longer used
@ -232,10 +245,13 @@ pub fn code_to_str(code: c_int) -> &'static str {
SQLITE_CANTOPEN_FULLPATH => "Unable to convert filename into full pathname", SQLITE_CANTOPEN_FULLPATH => "Unable to convert filename into full pathname",
SQLITE_CANTOPEN_CONVPATH => "cygwin_conv_path() system call failed", SQLITE_CANTOPEN_CONVPATH => "cygwin_conv_path() system call failed",
SQLITE_CORRUPT_VTAB => "Content in the virtual table is corrupt", SQLITE_CORRUPT_VTAB => "Content in the virtual table is corrupt",
SQLITE_CORRUPT_SEQUENCE => "SQLITE_CORRUPT_SEQUENCE",
SQLITE_READONLY_RECOVERY => "WAL mode database file needs recovery (requires write access)", SQLITE_READONLY_RECOVERY => "WAL mode database file needs recovery (requires write access)",
SQLITE_READONLY_CANTLOCK => "Shared-memory file associated with WAL mode database is read-only", SQLITE_READONLY_CANTLOCK => "Shared-memory file associated with WAL mode database is read-only",
SQLITE_READONLY_ROLLBACK => "Database has hot journal that must be rolled back (requires write access)", SQLITE_READONLY_ROLLBACK => "Database has hot journal that must be rolled back (requires write access)",
SQLITE_READONLY_DBMOVED => "Database cannot be modified because database file has moved", SQLITE_READONLY_DBMOVED => "Database cannot be modified because database file has moved",
SQLITE_READONLY_CANTINIT => "SQLITE_READONLY_CANTINIT",
SQLITE_READONLY_DIRECTORY => "SQLITE_READONLY_DIRECTORY",
SQLITE_ABORT_ROLLBACK => "Transaction was rolled back", SQLITE_ABORT_ROLLBACK => "Transaction was rolled back",
SQLITE_CONSTRAINT_CHECK => "A CHECK constraint failed", SQLITE_CONSTRAINT_CHECK => "A CHECK constraint failed",
SQLITE_CONSTRAINT_COMMITHOOK => "Commit hook caused rollback", SQLITE_CONSTRAINT_COMMITHOOK => "Commit hook caused rollback",

View File

@ -8,7 +8,7 @@ use std::mem;
mod error; mod error;
pub fn SQLITE_STATIC() -> sqlite3_destructor_type { pub fn SQLITE_STATIC() -> sqlite3_destructor_type {
Some(unsafe { mem::transmute(0isize) }) None
} }
pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type { pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type {
@ -49,7 +49,11 @@ pub enum Limit {
SQLITE_LIMIT_WORKER_THREADS = 11, SQLITE_LIMIT_WORKER_THREADS = 11,
} }
include!(concat!(env!("OUT_DIR"), "/bindgen.rs")); #[allow(clippy::all)]
mod bindings {
include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
}
pub use bindings::*;
pub type sqlite3_index_constraint = sqlite3_index_info_sqlite3_index_constraint; pub type sqlite3_index_constraint = sqlite3_index_info_sqlite3_index_constraint;
pub type sqlite3_index_constraint_usage = sqlite3_index_info_sqlite3_index_constraint_usage; pub type sqlite3_index_constraint_usage = sqlite3_index_info_sqlite3_index_constraint_usage;

View File

@ -4,8 +4,8 @@ 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-3280000 SQLITE=sqlite-amalgamation-3310000
curl -O http://sqlite.org/2019/$SQLITE.zip curl -O https://sqlite.org/2020/$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
unzip -p $SQLITE.zip $SQLITE/sqlite3ext.h > $SQLITE3_LIB_DIR/sqlite3ext.h unzip -p $SQLITE.zip $SQLITE/sqlite3ext.h > $SQLITE3_LIB_DIR/sqlite3ext.h

View File

@ -1,4 +1,5 @@
///! Busy handler (when the database is locked) ///! Busy handler (when the database is locked)
use std::convert::TryInto;
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::panic::catch_unwind;
@ -21,12 +22,13 @@ impl Connection {
/// (using `busy_handler`) prior to calling this routine, that other /// (using `busy_handler`) prior to calling this routine, that other
/// busy handler is cleared. /// busy handler is cleared.
pub fn busy_timeout(&self, timeout: Duration) -> Result<()> { pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
let ms = timeout let ms: i32 = timeout
.as_secs() .as_secs()
.checked_mul(1000) .checked_mul(1000)
.and_then(|t| t.checked_add(timeout.subsec_millis().into())) .and_then(|t| t.checked_add(timeout.subsec_millis().into()))
.and_then(|t| t.try_into().ok())
.expect("too big"); .expect("too big");
self.db.borrow_mut().busy_timeout(ms as i32) self.db.borrow_mut().busy_timeout(ms)
} }
/// Register a callback to handle `SQLITE_BUSY` errors. /// Register a callback to handle `SQLITE_BUSY` errors.
@ -75,18 +77,17 @@ impl InnerConnection {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
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 tempfile;
use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS}; use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS};
#[test] #[test]
fn test_default_busy() { fn test_default_busy() {
let temp_dir = TempDir::new("test_default_busy").unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.path().join("test.db3"); let path = temp_dir.path().join("test.db3");
let mut db1 = Connection::open(&path).unwrap(); let mut db1 = Connection::open(&path).unwrap();
@ -107,7 +108,7 @@ mod test {
#[test] #[test]
#[ignore] // FIXME: unstable #[ignore] // FIXME: unstable
fn test_busy_timeout() { fn test_busy_timeout() {
let temp_dir = TempDir::new("test_busy_timeout").unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.path().join("test.db3"); let path = temp_dir.path().join("test.db3");
let db2 = Connection::open(&path).unwrap(); let db2 = Connection::open(&path).unwrap();
@ -137,7 +138,7 @@ mod test {
#[test] #[test]
#[ignore] // FIXME: unstable #[ignore] // FIXME: unstable
fn test_busy_handler() { fn test_busy_handler() {
lazy_static! { lazy_static::lazy_static! {
static ref CALLED: AtomicBool = AtomicBool::new(false); static ref CALLED: AtomicBool = AtomicBool::new(false);
} }
fn busy_handler(_: i32) -> bool { fn busy_handler(_: i32) -> bool {
@ -146,7 +147,7 @@ mod test {
true true
} }
let temp_dir = TempDir::new("test_busy_handler").unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.path().join("test.db3"); let path = temp_dir.path().join("test.db3");
let db2 = Connection::open(&path).unwrap(); let db2 = Connection::open(&path).unwrap();

View File

@ -135,9 +135,12 @@ impl StatementCache {
// Return a statement to the cache. // Return a statement to the cache.
fn cache_stmt(&self, stmt: RawStatement) { fn cache_stmt(&self, stmt: RawStatement) {
if stmt.is_null() {
return;
}
let mut cache = self.0.borrow_mut(); let mut cache = self.0.borrow_mut();
stmt.clear_bindings(); stmt.clear_bindings();
let sql = String::from_utf8_lossy(stmt.sql().to_bytes()) let sql = String::from_utf8_lossy(stmt.sql().unwrap().to_bytes())
.trim() .trim()
.to_string(); .to_string();
cache.insert(sql, stmt); cache.insert(sql, stmt);
@ -339,4 +342,10 @@ mod test {
} }
assert_eq!(1, cache.len()); assert_eq!(1, cache.len());
} }
#[test]
fn test_empty_stmt() {
let conn = Connection::open_in_memory().unwrap();
conn.prepare_cached("").unwrap();
}
} }

209
src/collation.rs Normal file
View File

@ -0,0 +1,209 @@
//! Add, remove, or modify a collation
use std::cmp::Ordering;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, UnwindSafe};
use std::ptr;
use std::slice;
use crate::ffi;
use crate::{str_to_cstring, Connection, InnerConnection, Result};
// FIXME copy/paste from function.rs
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
drop(Box::from_raw(p as *mut T));
}
impl Connection {
/// Add or modify a collation.
pub fn create_collation<C>(&self, collation_name: &str, x_compare: C) -> Result<()>
where
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
{
self.db
.borrow_mut()
.create_collation(collation_name, x_compare)
}
/// Collation needed callback
pub fn collation_needed(
&self,
x_coll_needed: fn(&Connection, &str) -> Result<()>,
) -> Result<()> {
self.db.borrow_mut().collation_needed(x_coll_needed)
}
/// Remove collation.
pub fn remove_collation(&self, collation_name: &str) -> Result<()> {
self.db.borrow_mut().remove_collation(collation_name)
}
}
impl InnerConnection {
fn create_collation<C>(&mut self, collation_name: &str, x_compare: C) -> Result<()>
where
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
{
unsafe extern "C" fn call_boxed_closure<C>(
arg1: *mut c_void,
arg2: c_int,
arg3: *const c_void,
arg4: c_int,
arg5: *const c_void,
) -> c_int
where
C: Fn(&str, &str) -> Ordering,
{
use std::str;
let r = catch_unwind(|| {
let boxed_f: *mut C = arg1 as *mut C;
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
let s1 = {
let c_slice = slice::from_raw_parts(arg3 as *const u8, arg2 as usize);
str::from_utf8_unchecked(c_slice)
};
let s2 = {
let c_slice = slice::from_raw_parts(arg5 as *const u8, arg4 as usize);
str::from_utf8_unchecked(c_slice)
};
(*boxed_f)(s1, s2)
});
let t = match r {
Err(_) => {
return -1; // FIXME How ?
}
Ok(r) => r,
};
match t {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
}
}
let boxed_f: *mut C = Box::into_raw(Box::new(x_compare));
let c_name = str_to_cstring(collation_name)?;
let flags = ffi::SQLITE_UTF8;
let r = unsafe {
ffi::sqlite3_create_collation_v2(
self.db(),
c_name.as_ptr(),
flags,
boxed_f as *mut c_void,
Some(call_boxed_closure::<C>),
Some(free_boxed_value::<C>),
)
};
self.decode_result(r)
}
fn collation_needed(
&mut self,
x_coll_needed: fn(&Connection, &str) -> Result<()>,
) -> Result<()> {
use std::mem;
unsafe extern "C" fn collation_needed_callback(
arg1: *mut c_void,
arg2: *mut ffi::sqlite3,
e_text_rep: c_int,
arg3: *const c_char,
) {
use std::ffi::CStr;
use std::str;
if e_text_rep != ffi::SQLITE_UTF8 {
// TODO: validate
return;
}
let callback: fn(&Connection, &str) -> Result<()> = mem::transmute(arg1);
if catch_unwind(|| {
let conn = Connection::from_handle(arg2).unwrap();
let collation_name = {
let c_slice = CStr::from_ptr(arg3).to_bytes();
str::from_utf8_unchecked(c_slice)
};
callback(&conn, collation_name)
})
.is_err()
{
return; // FIXME How ?
}
}
let r = unsafe {
ffi::sqlite3_collation_needed(
self.db(),
mem::transmute(x_coll_needed),
Some(collation_needed_callback),
)
};
self.decode_result(r)
}
fn remove_collation(&mut self, collation_name: &str) -> Result<()> {
let c_name = str_to_cstring(collation_name)?;
let r = unsafe {
ffi::sqlite3_create_collation_v2(
self.db(),
c_name.as_ptr(),
ffi::SQLITE_UTF8,
ptr::null_mut(),
None,
None,
)
};
self.decode_result(r)
}
}
#[cfg(test)]
mod test {
use crate::{Connection, Result, NO_PARAMS};
use fallible_streaming_iterator::FallibleStreamingIterator;
use std::cmp::Ordering;
use unicase::UniCase;
fn unicase_compare(s1: &str, s2: &str) -> Ordering {
UniCase::new(s1).cmp(&UniCase::new(s2))
}
#[test]
fn test_unicase() {
let db = Connection::open_in_memory().unwrap();
db.create_collation("unicase", unicase_compare).unwrap();
collate(db);
}
fn collate(db: Connection) {
db.execute_batch(
"CREATE TABLE foo (bar);
INSERT INTO foo (bar) VALUES ('Maße');
INSERT INTO foo (bar) VALUES ('MASSE');",
)
.unwrap();
let mut stmt = db
.prepare("SELECT DISTINCT bar COLLATE unicase FROM foo ORDER BY 1")
.unwrap();
let rows = stmt.query(NO_PARAMS).unwrap();
assert_eq!(rows.count().unwrap(), 1);
}
fn collation_needed(db: &Connection, collation_name: &str) -> Result<()> {
if "unicase" == collation_name {
db.create_collation(collation_name, unicase_compare)
} else {
Ok(())
}
}
#[test]
fn test_collation_needed() {
let db = Connection::open_in_memory().unwrap();
db.collation_needed(collation_needed).unwrap();
collate(db);
}
}

View File

@ -27,8 +27,7 @@ impl Statement<'_> {
let n = self.column_count(); let n = self.column_count();
let mut cols = Vec::with_capacity(n as usize); let mut cols = Vec::with_capacity(n as usize);
for i in 0..n { for i in 0..n {
let slice = self.stmt.column_name(i); let s = self.column_name_unwrap(i);
let s = str::from_utf8(slice.to_bytes()).unwrap();
cols.push(s); cols.push(s);
} }
cols cols
@ -40,6 +39,30 @@ impl Statement<'_> {
self.stmt.column_count() self.stmt.column_count()
} }
pub(crate) fn column_name_unwrap(&self, col: usize) -> &str {
// Just panic if the bounds are wrong for now, we never call this
// without checking first.
self.column_name(col).expect("Column out of bounds")
}
/// Returns the name assigned to a particular column in the result set
/// returned by the prepared statement.
///
/// ## Failure
///
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
/// column range for this row.
///
/// Panics when column name is not valid UTF-8.
pub fn column_name(&self, col: usize) -> Result<&str> {
self.stmt
.column_name(col)
.ok_or(Error::InvalidColumnIndex(col))
.map(|slice| {
str::from_utf8(slice.to_bytes()).expect("Invalid UTF-8 sequence in column name")
})
}
/// Returns the column index in the result set for a given column name. /// Returns the column index in the result set for a given column name.
/// ///
/// If there is no AS clause then the name of the column is unspecified and /// If there is no AS clause then the name of the column is unspecified and
@ -53,7 +76,9 @@ impl Statement<'_> {
let bytes = name.as_bytes(); let bytes = name.as_bytes();
let n = self.column_count(); let n = self.column_count();
for i in 0..n { for i in 0..n {
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).to_bytes()) { // Note: `column_name` is only fallible if `i` is out of bounds,
// which we've already checked.
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
return Ok(i); return Ok(i);
} }
} }
@ -61,14 +86,15 @@ impl Statement<'_> {
} }
/// Returns a slice describing the columns of the result of the query. /// Returns a slice describing the columns of the result of the query.
pub fn columns<'stmt>(&'stmt self) -> Vec<Column<'stmt>> { pub fn columns(&self) -> Vec<Column> {
let n = self.column_count(); let n = self.column_count();
let mut cols = Vec::with_capacity(n as usize); let mut cols = Vec::with_capacity(n as usize);
for i in 0..n { for i in 0..n {
let slice = self.stmt.column_name(i); let name = self.column_name_unwrap(i);
let name = str::from_utf8(slice.to_bytes()).unwrap();
let slice = self.stmt.column_decltype(i); let slice = self.stmt.column_decltype(i);
let decl_type = slice.map(|s| str::from_utf8(s.to_bytes()).unwrap()); let decl_type = slice.map(|s| {
str::from_utf8(s.to_bytes()).expect("Invalid UTF-8 sequence in column declaration")
});
cols.push(Column { name, decl_type }); cols.push(Column { name, decl_type });
} }
cols cols
@ -86,20 +112,45 @@ impl<'stmt> Rows<'stmt> {
self.stmt.map(Statement::column_count) self.stmt.map(Statement::column_count)
} }
/// Return the name of the column.
pub fn column_name(&self, col: usize) -> Option<Result<&str>> {
self.stmt.map(|stmt| stmt.column_name(col))
}
/// Return the index of the column.
pub fn column_index(&self, name: &str) -> Option<Result<usize>> {
self.stmt.map(|stmt| stmt.column_index(name))
}
/// Returns a slice describing the columns of the Rows. /// Returns a slice describing the columns of the Rows.
pub fn columns(&self) -> Option<Vec<Column<'stmt>>> { pub fn columns(&self) -> Option<Vec<Column>> {
self.stmt.map(Statement::columns) self.stmt.map(Statement::columns)
} }
} }
impl<'stmt> Row<'stmt> { impl<'stmt> Row<'stmt> {
/// Get all the column names of the Row.
pub fn column_names(&self) -> Vec<&str> {
self.stmt.column_names()
}
/// Return the number of columns in the current row. /// Return the number of columns in the current row.
pub fn column_count(&self) -> usize { pub fn column_count(&self) -> usize {
self.stmt.column_count() self.stmt.column_count()
} }
/// Return the name of the column.
pub fn column_name(&self, col: usize) -> Result<&str> {
self.stmt.column_name(col)
}
/// Return the index of the column.
pub fn column_index(&self, name: &str) -> Result<usize> {
self.stmt.column_index(name)
}
/// Returns a slice describing the columns of the Row. /// Returns a slice describing the columns of the Row.
pub fn columns(&self) -> Vec<Column<'stmt>> { pub fn columns(&self) -> Vec<Column> {
self.stmt.columns() self.stmt.columns()
} }
} }
@ -125,4 +176,40 @@ mod test {
&[Some("text"), Some("text"), Some("text"),] &[Some("text"), Some("text"), Some("text"),]
); );
} }
#[test]
fn test_column_name_in_error() {
use crate::{types::Type, Error};
let db = Connection::open_in_memory().unwrap();
db.execute_batch(
"BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, NULL);
END;",
)
.unwrap();
let mut stmt = db.prepare("SELECT x as renamed, y FROM foo").unwrap();
let mut rows = stmt.query(crate::NO_PARAMS).unwrap();
let row = rows.next().unwrap().unwrap();
match row.get::<_, String>(0).unwrap_err() {
Error::InvalidColumnType(idx, name, ty) => {
assert_eq!(idx, 0);
assert_eq!(name, "renamed");
assert_eq!(ty, Type::Integer);
}
e => {
panic!("Unexpected error type: {:?}", e);
}
}
match row.get::<_, String>("y").unwrap_err() {
Error::InvalidColumnType(idx, name, ty) => {
assert_eq!(idx, 1);
assert_eq!(name, "y");
assert_eq!(ty, Type::Null);
}
e => {
panic!("Unexpected error type: {:?}", e);
}
}
}
} }

View File

@ -15,11 +15,19 @@ pub enum DbConfig {
SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003, SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003,
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // 3.12.0 SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // 3.12.0
//SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005, //SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005,
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006, // 3.16.2
SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0 SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
//SQLITE_DBCONFIG_RESET_DATABASE = 1009, //SQLITE_DBCONFIG_RESET_DATABASE = 1009,
SQLITE_DBCONFIG_DEFENSIVE = 1010, SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0
#[cfg(feature = "modern_sqlite")]
SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011, // 3.28.0
#[cfg(feature = "modern_sqlite")]
SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012, // 3.29
#[cfg(feature = "modern_sqlite")]
SQLITE_DBCONFIG_DQS_DML = 1013, // 3.29.0
#[cfg(feature = "modern_sqlite")]
SQLITE_DBCONFIG_DQS_DDL = 1014, // 3.29.0
} }
impl Connection { impl Connection {

View File

@ -1,3 +1,4 @@
use crate::types::FromSqlError;
use crate::types::Type; use crate::types::Type;
use crate::{errmsg_to_string, ffi}; use crate::{errmsg_to_string, ffi};
use std::error; use std::error;
@ -59,7 +60,7 @@ pub enum Error {
/// Error when the value of a particular column is requested, but the type /// Error when the value of a particular column is requested, but the type
/// of the result in that column cannot be converted to the requested /// of the result in that column cannot be converted to the requested
/// Rust type. /// Rust type.
InvalidColumnType(usize, Type), InvalidColumnType(usize, String, Type),
/// Error when a query that was expected to insert one row did not insert /// Error when a query that was expected to insert one row did not insert
/// any or insert many. /// any or insert many.
@ -99,6 +100,9 @@ pub enum Error {
/// of a different type than what had been stored using `Context::set_aux`. /// of a different type than what had been stored using `Context::set_aux`.
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
GetAuxWrongType, GetAuxWrongType,
/// Error when the SQL contains multiple statements.
MultipleStatement,
} }
impl PartialEq for Error { impl PartialEq for Error {
@ -117,8 +121,8 @@ impl PartialEq for Error {
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true, (Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2, (Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2, (Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
(Error::InvalidColumnType(i1, t1), Error::InvalidColumnType(i2, t2)) => { (Error::InvalidColumnType(i1, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
i1 == i2 && t1 == t2 i1 == i2 && t1 == t2 && n1 == n2
} }
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2, (Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
@ -138,7 +142,7 @@ impl PartialEq for Error {
(Error::UnwindingPanic, Error::UnwindingPanic) => true, (Error::UnwindingPanic, Error::UnwindingPanic) => true,
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
(Error::GetAuxWrongType, Error::GetAuxWrongType) => true, (Error::GetAuxWrongType, Error::GetAuxWrongType) => true,
(_, _) => false, (..) => false,
} }
} }
} }
@ -155,6 +159,32 @@ impl From<::std::ffi::NulError> for Error {
} }
} }
const UNKNOWN_COLUMN: usize = std::usize::MAX;
/// The conversion isn't precise, but it's convenient to have it
/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
impl From<FromSqlError> for Error {
fn from(err: FromSqlError) -> Error {
// The error type requires index and type fields, but they aren't known in this
// context.
match err {
FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => {
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
}
#[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => {
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
}
FromSqlError::Other(source) => {
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source)
}
_ => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
}
}
}
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 {
@ -164,13 +194,23 @@ impl fmt::Display for Error {
f, f,
"SQLite was compiled or configured for single-threaded use only" "SQLite was compiled or configured for single-threaded use only"
), ),
Error::FromSqlConversionFailure(i, ref t, ref err) => write!( Error::FromSqlConversionFailure(i, ref t, ref err) => {
f, if i != UNKNOWN_COLUMN {
"Conversion error from type {} at index: {}, {}", write!(
t, i, err f,
), "Conversion error from type {} at index: {}, {}",
t, i, err
)
} else {
err.fmt(f)
}
}
Error::IntegralValueOutOfRange(col, val) => { Error::IntegralValueOutOfRange(col, val) => {
write!(f, "Integer {} out of range at index {}", val, col) if col != UNKNOWN_COLUMN {
write!(f, "Integer {} out of range at index {}", val, col)
} else {
write!(f, "Integer {} out of range", val)
}
} }
Error::Utf8Error(ref err) => err.fmt(f), Error::Utf8Error(ref err) => err.fmt(f),
Error::NulError(ref err) => err.fmt(f), Error::NulError(ref err) => err.fmt(f),
@ -182,9 +222,11 @@ impl fmt::Display for Error {
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"), Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i), Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name), Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
Error::InvalidColumnType(i, ref t) => { Error::InvalidColumnType(i, ref name, ref t) => write!(
write!(f, "Invalid column type {} at index: {}", t, i) f,
} "Invalid column type {} at index: {}, name: {}",
t, i, name
),
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i), Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
@ -205,11 +247,13 @@ impl fmt::Display for Error {
Error::UnwindingPanic => write!(f, "unwinding panic"), Error::UnwindingPanic => write!(f, "unwinding panic"),
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::GetAuxWrongType => write!(f, "get_aux called with wrong type"), Error::GetAuxWrongType => write!(f, "get_aux called with wrong type"),
Error::MultipleStatement => write!(f, "Multiple statements provided"),
} }
} }
} }
impl error::Error for Error { impl error::Error for Error {
#[allow(deprecated)]
fn description(&self) -> &str { fn description(&self) -> &str {
match *self { match *self {
Error::SqliteFailure(ref err, None) => err.description(), Error::SqliteFailure(ref err, None) => err.description(),
@ -218,7 +262,7 @@ impl error::Error for Error {
"SQLite was compiled or configured for single-threaded use only" "SQLite was compiled or configured for single-threaded use only"
} }
Error::FromSqlConversionFailure(_, _, ref err) => err.description(), Error::FromSqlConversionFailure(_, _, ref err) => err.description(),
Error::IntegralValueOutOfRange(_, _) => "integral value out of range of requested type", Error::IntegralValueOutOfRange(..) => "integral value out of range of requested type",
Error::Utf8Error(ref err) => err.description(), Error::Utf8Error(ref err) => err.description(),
Error::InvalidParameterName(_) => "invalid parameter name", Error::InvalidParameterName(_) => "invalid parameter name",
Error::NulError(ref err) => err.description(), Error::NulError(ref err) => err.description(),
@ -229,13 +273,13 @@ impl error::Error for Error {
Error::QueryReturnedNoRows => "query returned no rows", Error::QueryReturnedNoRows => "query returned no rows",
Error::InvalidColumnIndex(_) => "invalid column index", Error::InvalidColumnIndex(_) => "invalid column index",
Error::InvalidColumnName(_) => "invalid column name", Error::InvalidColumnName(_) => "invalid column name",
Error::InvalidColumnType(_, _) => "invalid column type", Error::InvalidColumnType(..) => "invalid column type",
Error::StatementChangedRows(_) => "query inserted zero or more than one row", Error::StatementChangedRows(_) => "query inserted zero or more than one row",
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type", Error::InvalidFunctionParameterType(..) => "invalid function parameter type",
#[cfg(feature = "vtab")] #[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(_, _) => "invalid filter parameter type", Error::InvalidFilterParameterType(..) => "invalid filter parameter type",
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => err.description(), Error::UserFunctionError(ref err) => err.description(),
Error::ToSqlConversionFailure(ref err) => err.description(), Error::ToSqlConversionFailure(ref err) => err.description(),
@ -246,6 +290,7 @@ impl error::Error for Error {
Error::UnwindingPanic => "unwinding panic", Error::UnwindingPanic => "unwinding panic",
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::GetAuxWrongType => "get_aux called with wrong type", Error::GetAuxWrongType => "get_aux called with wrong type",
Error::MultipleStatement => "multiple statements provided",
} }
} }
@ -255,22 +300,23 @@ impl error::Error for Error {
Error::Utf8Error(ref err) => Some(err), Error::Utf8Error(ref err) => Some(err),
Error::NulError(ref err) => Some(err), Error::NulError(ref err) => Some(err),
Error::IntegralValueOutOfRange(_, _) Error::IntegralValueOutOfRange(..)
| Error::SqliteSingleThreadedMode | Error::SqliteSingleThreadedMode
| Error::InvalidParameterName(_) | Error::InvalidParameterName(_)
| Error::ExecuteReturnedResults | Error::ExecuteReturnedResults
| Error::QueryReturnedNoRows | Error::QueryReturnedNoRows
| Error::InvalidColumnIndex(_) | Error::InvalidColumnIndex(_)
| Error::InvalidColumnName(_) | Error::InvalidColumnName(_)
| Error::InvalidColumnType(_, _) | Error::InvalidColumnType(..)
| Error::InvalidPath(_) | Error::InvalidPath(_)
| Error::StatementChangedRows(_) | Error::StatementChangedRows(_)
| Error::InvalidQuery => None, | Error::InvalidQuery
| Error::MultipleStatement => None,
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => None, Error::InvalidFunctionParameterType(..) => None,
#[cfg(feature = "vtab")] #[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(_, _) => None, Error::InvalidFilterParameterType(..) => None,
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => Some(&**err), Error::UserFunctionError(ref err) => Some(&**err),
@ -309,7 +355,7 @@ macro_rules! check {
($funcall:expr) => {{ ($funcall:expr) => {{
let rc = $funcall; let rc = $funcall;
if rc != crate::ffi::SQLITE_OK { if rc != crate::ffi::SQLITE_OK {
Err(crate::error::error_from_sqlite_code(rc, None))?; return Err(crate::error::error_from_sqlite_code(rc, None).into());
} }
}}; }};
} }

View File

@ -10,28 +10,47 @@
//! //!
//! ```rust //! ```rust
//! use regex::Regex; //! use regex::Regex;
//! use rusqlite::functions::FunctionFlags;
//! use rusqlite::{Connection, Error, Result, NO_PARAMS}; //! use rusqlite::{Connection, Error, Result, NO_PARAMS};
//! use std::collections::HashMap;
//! //!
//! fn add_regexp_function(db: &Connection) -> Result<()> { //! fn add_regexp_function(db: &Connection) -> Result<()> {
//! let mut cached_regexes = HashMap::new(); //! db.create_scalar_function(
//! db.create_scalar_function("regexp", 2, true, move |ctx| { //! "regexp",
//! let regex_s = ctx.get::<String>(0)?; //! 2,
//! let entry = cached_regexes.entry(regex_s.clone()); //! FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
//! let regex = { //! move |ctx| {
//! use std::collections::hash_map::Entry::{Occupied, Vacant}; //! assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
//! match entry {
//! Occupied(occ) => occ.into_mut(),
//! Vacant(vac) => match Regex::new(&regex_s) {
//! Ok(r) => vac.insert(r),
//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
//! },
//! }
//! };
//! //!
//! let text = ctx.get::<String>(1)?; //! let saved_re: Option<&Regex> = ctx.get_aux(0)?;
//! Ok(regex.is_match(&text)) //! let new_re = match saved_re {
//! }) //! None => {
//! let s = ctx.get::<String>(0)?;
//! match Regex::new(&s) {
//! Ok(r) => Some(r),
//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
//! }
//! }
//! Some(_) => None,
//! };
//!
//! let is_match = {
//! let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap());
//!
//! let text = ctx
//! .get_raw(1)
//! .as_str()
//! .map_err(|e| Error::UserFunctionError(e.into()))?;
//!
//! re.is_match(text)
//! };
//!
//! if let Some(re) = new_re {
//! ctx.set_aux(0, re);
//! }
//!
//! Ok(is_match)
//! },
//! )
//! } //! }
//! //!
//! fn main() -> Result<()> { //! fn main() -> Result<()> {
@ -48,7 +67,6 @@
//! Ok(()) //! Ok(())
//! } //! }
//! ``` //! ```
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::panic::{catch_unwind, RefUnwindSafe, UnwindSafe};
use std::ptr; use std::ptr;
@ -68,11 +86,11 @@ unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
// an explicit feature check for that, and this doesn't really warrant one. // an explicit feature check for that, and this doesn't really warrant one.
// We'll use the extended code if we're on the bundled version (since it's // We'll use the extended code if we're on the bundled version (since it's
// at least 3.17.0) and the normal constraint error code if not. // at least 3.17.0) and the normal constraint error code if not.
#[cfg(feature = "bundled")] #[cfg(feature = "modern_sqlite")]
fn constraint_error_code() -> i32 { fn constraint_error_code() -> i32 {
ffi::SQLITE_CONSTRAINT_FUNCTION ffi::SQLITE_CONSTRAINT_FUNCTION
} }
#[cfg(not(feature = "bundled"))] #[cfg(not(feature = "modern_sqlite"))]
fn constraint_error_code() -> i32 { fn constraint_error_code() -> i32 {
ffi::SQLITE_CONSTRAINT ffi::SQLITE_CONSTRAINT
} }
@ -86,7 +104,7 @@ unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
} }
_ => { _ => {
ffi::sqlite3_result_error_code(ctx, constraint_error_code()); ffi::sqlite3_result_error_code(ctx, constraint_error_code());
if let Ok(cstr) = str_to_cstring(err.description()) { if let Ok(cstr) = str_to_cstring(&err.to_string()) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1); ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
} }
} }
@ -213,6 +231,44 @@ where
fn finalize(&self, _: Option<A>) -> Result<T>; fn finalize(&self, _: Option<A>) -> Result<T>;
} }
/// WindowAggregate is the callback interface for user-defined aggregate window
/// function.
#[cfg(feature = "window")]
pub trait WindowAggregate<A, T>: Aggregate<A, T>
where
A: RefUnwindSafe + UnwindSafe,
T: ToSql,
{
/// Returns the current value of the aggregate. Unlike xFinal, the
/// implementation should not delete any context.
fn value(&self, _: Option<&A>) -> Result<T>;
/// Removes a row from the current window.
fn inverse(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
}
bitflags::bitflags! {
#[doc = "Function Flags."]
#[doc = "See [sqlite3_create_function](https://sqlite.org/c3ref/create_function.html) for details."]
#[repr(C)]
pub struct FunctionFlags: ::std::os::raw::c_int {
const SQLITE_UTF8 = ffi::SQLITE_UTF8;
const SQLITE_UTF16LE = ffi::SQLITE_UTF16LE;
const SQLITE_UTF16BE = ffi::SQLITE_UTF16BE;
const SQLITE_UTF16 = ffi::SQLITE_UTF16;
const SQLITE_DETERMINISTIC = ffi::SQLITE_DETERMINISTIC;
const SQLITE_DIRECTONLY = 0x0000_0008_0000; // 3.30.0
const SQLITE_SUBTYPE = 0x0000_0010_0000; // 3.30.0
const SQLITE_INNOCUOUS = 0x0000_0020_0000; // 3.31.0
}
}
impl Default for FunctionFlags {
fn default() -> FunctionFlags {
FunctionFlags::SQLITE_UTF8
}
}
impl Connection { impl Connection {
/// Attach a user-defined scalar function to this database connection. /// Attach a user-defined scalar function to this database connection.
/// ///
@ -228,11 +284,17 @@ impl Connection {
/// ///
/// ```rust /// ```rust
/// # use rusqlite::{Connection, Result, NO_PARAMS}; /// # use rusqlite::{Connection, Result, NO_PARAMS};
/// # use rusqlite::functions::FunctionFlags;
/// fn scalar_function_example(db: Connection) -> Result<()> { /// fn scalar_function_example(db: Connection) -> Result<()> {
/// db.create_scalar_function("halve", 1, true, |ctx| { /// db.create_scalar_function(
/// let value = ctx.get::<f64>(0)?; /// "halve",
/// Ok(value / 2f64) /// 1,
/// })?; /// FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
/// |ctx| {
/// let value = ctx.get::<f64>(0)?;
/// Ok(value / 2f64)
/// },
/// )?;
/// ///
/// let six_halved: f64 = 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);
@ -247,7 +309,7 @@ impl Connection {
&self, &self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
deterministic: bool, flags: FunctionFlags,
x_func: F, x_func: F,
) -> Result<()> ) -> Result<()>
where where
@ -256,7 +318,7 @@ impl Connection {
{ {
self.db self.db
.borrow_mut() .borrow_mut()
.create_scalar_function(fn_name, n_arg, deterministic, x_func) .create_scalar_function(fn_name, n_arg, flags, x_func)
} }
/// Attach a user-defined aggregate function to this database connection. /// Attach a user-defined aggregate function to this database connection.
@ -268,7 +330,7 @@ impl Connection {
&self, &self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
deterministic: bool, flags: FunctionFlags,
aggr: D, aggr: D,
) -> Result<()> ) -> Result<()>
where where
@ -278,7 +340,25 @@ impl Connection {
{ {
self.db self.db
.borrow_mut() .borrow_mut()
.create_aggregate_function(fn_name, n_arg, deterministic, aggr) .create_aggregate_function(fn_name, n_arg, flags, aggr)
}
#[cfg(feature = "window")]
pub fn create_window_function<A, W, T>(
&self,
fn_name: &str,
n_arg: c_int,
flags: FunctionFlags,
aggr: W,
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
W: WindowAggregate<A, T>,
T: ToSql,
{
self.db
.borrow_mut()
.create_window_function(fn_name, n_arg, flags, aggr)
} }
/// Removes a user-defined function from this database connection. /// Removes a user-defined function from this database connection.
@ -299,7 +379,7 @@ impl InnerConnection {
&mut self, &mut self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
deterministic: bool, flags: FunctionFlags,
x_func: F, x_func: F,
) -> Result<()> ) -> Result<()>
where where
@ -341,16 +421,12 @@ impl InnerConnection {
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 = str_to_cstring(fn_name)?; let c_name = str_to_cstring(fn_name)?;
let mut flags = ffi::SQLITE_UTF8;
if deterministic {
flags |= ffi::SQLITE_DETERMINISTIC;
}
let r = unsafe { let r = unsafe {
ffi::sqlite3_create_function_v2( ffi::sqlite3_create_function_v2(
self.db(), self.db(),
c_name.as_ptr(), c_name.as_ptr(),
n_arg, n_arg,
flags, flags.bits(),
boxed_f as *mut c_void, boxed_f as *mut c_void,
Some(call_boxed_closure::<F, T>), Some(call_boxed_closure::<F, T>),
None, None,
@ -365,7 +441,7 @@ impl InnerConnection {
&mut self, &mut self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
deterministic: bool, flags: FunctionFlags,
aggr: D, aggr: D,
) -> Result<()> ) -> Result<()>
where where
@ -373,117 +449,14 @@ impl InnerConnection {
D: Aggregate<A, T>, D: Aggregate<A, T>,
T: ToSql, T: ToSql,
{ {
unsafe fn aggregate_context<A>(
ctx: *mut sqlite3_context,
bytes: usize,
) -> Option<*mut *mut A> {
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
if pac.is_null() {
return None;
}
Some(pac)
}
unsafe extern "C" fn call_boxed_step<A, D, T>(
ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value,
) where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>,
T: ToSql,
{
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
Some(pac) => pac,
None => {
ffi::sqlite3_result_error_nomem(ctx);
return;
}
};
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"
);
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 {
Ok(_) => {}
Err(err) => report_error(ctx, &err),
};
}
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>,
T: ToSql,
{
// 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.
let a: Option<A> = match aggregate_context(ctx, 0) {
Some(pac) => {
if (*pac as *mut A).is_null() {
None
} else {
let a = Box::from_raw(*pac);
Some(*a)
}
}
None => None,
};
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));
match t {
Ok(Ok(ref value)) => set_result(ctx, value),
Ok(Err(err)) => report_error(ctx, &err),
Err(err) => report_error(ctx, err),
}
}
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 = str_to_cstring(fn_name)?; let c_name = str_to_cstring(fn_name)?;
let mut flags = ffi::SQLITE_UTF8;
if deterministic {
flags |= ffi::SQLITE_DETERMINISTIC;
}
let r = unsafe { let r = unsafe {
ffi::sqlite3_create_function_v2( ffi::sqlite3_create_function_v2(
self.db(), self.db(),
c_name.as_ptr(), c_name.as_ptr(),
n_arg, n_arg,
flags, flags.bits(),
boxed_aggr as *mut c_void, boxed_aggr as *mut c_void,
None, None,
Some(call_boxed_step::<A, D, T>), Some(call_boxed_step::<A, D, T>),
@ -494,6 +467,38 @@ impl InnerConnection {
self.decode_result(r) self.decode_result(r)
} }
#[cfg(feature = "window")]
fn create_window_function<A, W, T>(
&mut self,
fn_name: &str,
n_arg: c_int,
flags: FunctionFlags,
aggr: W,
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
W: WindowAggregate<A, T>,
T: ToSql,
{
let boxed_aggr: *mut W = Box::into_raw(Box::new(aggr));
let c_name = str_to_cstring(fn_name)?;
let r = unsafe {
ffi::sqlite3_create_window_function(
self.db(),
c_name.as_ptr(),
n_arg,
flags.bits(),
boxed_aggr as *mut c_void,
Some(call_boxed_step::<A, W, T>),
Some(call_boxed_final::<A, W, T>),
Some(call_boxed_value::<A, W, T>),
Some(call_boxed_inverse::<A, W, T>),
Some(free_boxed_value::<W>),
)
};
self.decode_result(r)
}
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 = str_to_cstring(fn_name)?; let c_name = str_to_cstring(fn_name)?;
let r = unsafe { let r = unsafe {
@ -513,16 +518,198 @@ impl InnerConnection {
} }
} }
unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context, bytes: usize) -> Option<*mut *mut A> {
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
if pac.is_null() {
return None;
}
Some(pac)
}
unsafe extern "C" fn call_boxed_step<A, D, T>(
ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value,
) where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>,
T: ToSql,
{
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
Some(pac) => pac,
None => {
ffi::sqlite3_result_error_nomem(ctx);
return;
}
};
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"
);
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 {
Ok(_) => {}
Err(err) => report_error(ctx, &err),
};
}
#[cfg(feature = "window")]
unsafe extern "C" fn call_boxed_inverse<A, W, T>(
ctx: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value,
) where
A: RefUnwindSafe + UnwindSafe,
W: WindowAggregate<A, T>,
T: ToSql,
{
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
Some(pac) => pac,
None => {
ffi::sqlite3_result_error_nomem(ctx);
return;
}
};
let r = catch_unwind(|| {
let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
let mut ctx = Context {
ctx,
args: slice::from_raw_parts(argv, argc as usize),
};
(*boxed_aggr).inverse(&mut ctx, &mut **pac)
});
let r = match r {
Err(_) => {
report_error(ctx, &Error::UnwindingPanic);
return;
}
Ok(r) => r,
};
match r {
Ok(_) => {}
Err(err) => report_error(ctx, &err),
};
}
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
where
A: RefUnwindSafe + UnwindSafe,
D: Aggregate<A, T>,
T: ToSql,
{
// 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.
let a: Option<A> = match aggregate_context(ctx, 0) {
Some(pac) => {
if (*pac as *mut A).is_null() {
None
} else {
let a = Box::from_raw(*pac);
Some(*a)
}
}
None => None,
};
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));
match t {
Ok(Ok(ref value)) => set_result(ctx, value),
Ok(Err(err)) => report_error(ctx, &err),
Err(err) => report_error(ctx, err),
}
}
#[cfg(feature = "window")]
unsafe extern "C" fn call_boxed_value<A, W, T>(ctx: *mut sqlite3_context)
where
A: RefUnwindSafe + UnwindSafe,
W: WindowAggregate<A, T>,
T: ToSql,
{
// Within the xValue callback, it is customary to set N=0 in calls to
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
let a: Option<&A> = match aggregate_context(ctx, 0) {
Some(pac) => {
if (*pac as *mut A).is_null() {
None
} else {
let a = &**pac;
Some(a)
}
}
None => None,
};
let r = catch_unwind(|| {
let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
(*boxed_aggr).value(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));
match t {
Ok(Ok(ref value)) => set_result(ctx, value),
Ok(Err(err)) => report_error(ctx, &err),
Err(err) => report_error(ctx, err),
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use regex; use regex::Regex;
use self::regex::Regex;
use std::collections::HashMap;
use std::f64::EPSILON; use std::f64::EPSILON;
use std::os::raw::c_double; use std::os::raw::c_double;
use crate::functions::{Aggregate, Context}; #[cfg(feature = "window")]
use crate::functions::WindowAggregate;
use crate::functions::{Aggregate, Context, FunctionFlags};
use crate::{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> {
@ -534,7 +721,13 @@ mod test {
#[test] #[test]
fn test_function_half() { fn test_function_half() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("half", 1, true, half).unwrap(); db.create_scalar_function(
"half",
1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
half,
)
.unwrap();
let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0)); let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
assert!((3f64 - result.unwrap()).abs() < EPSILON); assert!((3f64 - result.unwrap()).abs() < EPSILON);
@ -543,7 +736,13 @@ mod test {
#[test] #[test]
fn test_remove_function() { fn test_remove_function() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("half", 1, true, half).unwrap(); db.create_scalar_function(
"half",
1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
half,
)
.unwrap();
let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0)); let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
assert!((3f64 - result.unwrap()).abs() < EPSILON); assert!((3f64 - result.unwrap()).abs() < EPSILON);
@ -600,63 +799,14 @@ mod test {
END;", END;",
) )
.unwrap(); .unwrap();
db.create_scalar_function("regexp", 2, true, regexp_with_auxilliary) db.create_scalar_function(
.unwrap(); "regexp",
2,
let result: Result<bool> = FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| { regexp_with_auxilliary,
r.get(0)
});
assert_eq!(true, result.unwrap());
let result: Result<i64> = db.query_row(
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
NO_PARAMS,
|r| r.get(0),
);
assert_eq!(2, result.unwrap());
}
#[test]
fn test_function_regexp_with_hashmap_cache() {
let db = Connection::open_in_memory().unwrap();
db.execute_batch(
"BEGIN;
CREATE TABLE foo (x string);
INSERT INTO foo VALUES ('lisa');
INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX');
END;",
) )
.unwrap(); .unwrap();
// This implementation of a regexp scalar function uses a captured HashMap
// to keep cached regular expressions around (even across multiple queries)
// until the function is removed.
let mut cached_regexes = HashMap::new();
db.create_scalar_function("regexp", 2, true, move |ctx| {
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
let regex_s = ctx.get::<String>(0)?;
let entry = cached_regexes.entry(regex_s.clone());
let regex = {
use std::collections::hash_map::Entry::{Occupied, Vacant};
match entry {
Occupied(occ) => occ.into_mut(),
Vacant(vac) => match Regex::new(&regex_s) {
Ok(r) => vac.insert(r),
Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
},
}
};
let text = ctx.get::<String>(1)?;
Ok(regex.is_match(&text))
})
.unwrap();
let result: Result<bool> = let result: Result<bool> =
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| { db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| {
r.get(0) r.get(0)
@ -676,16 +826,21 @@ mod test {
#[test] #[test]
fn test_varargs_function() { fn test_varargs_function() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("my_concat", -1, true, |ctx| { db.create_scalar_function(
let mut ret = String::new(); "my_concat",
-1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|ctx| {
let mut ret = String::new();
for idx in 0..ctx.len() { for idx in 0..ctx.len() {
let s = ctx.get::<String>(idx)?; let s = ctx.get::<String>(idx)?;
ret.push_str(&s); ret.push_str(&s);
} }
Ok(ret) Ok(ret)
}) },
)
.unwrap(); .unwrap();
for &(expected, query) in &[ for &(expected, query) in &[
@ -701,7 +856,7 @@ mod test {
#[test] #[test]
fn test_get_aux_type_checking() { fn test_get_aux_type_checking() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("example", 2, false, |ctx| { db.create_scalar_function("example", 2, FunctionFlags::default(), |ctx| {
if !ctx.get::<bool>(1)? { if !ctx.get::<bool>(1)? {
ctx.set_aux::<i64>(0, 100); ctx.set_aux::<i64>(0, 100);
} else { } else {
@ -759,8 +914,13 @@ mod test {
#[test] #[test]
fn test_sum() { fn test_sum() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.create_aggregate_function("my_sum", 1, true, Sum) db.create_aggregate_function(
.unwrap(); "my_sum",
1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
Sum,
)
.unwrap();
// sum should return NULL when given no columns (contrast with count below) // sum should return NULL when given no columns (contrast with count below)
let no_result = "SELECT my_sum(i) FROM (SELECT 2 AS i WHERE 1 <> 1)"; let no_result = "SELECT my_sum(i) FROM (SELECT 2 AS i WHERE 1 <> 1)";
@ -782,8 +942,13 @@ mod test {
#[test] #[test]
fn test_count() { fn test_count() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
db.create_aggregate_function("my_count", -1, true, Count) db.create_aggregate_function(
.unwrap(); "my_count",
-1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
Count,
)
.unwrap();
// count should return 0 when given no columns (contrast with sum above) // count should return 0 when given no columns (contrast with sum above)
let no_result = "SELECT my_count(i) FROM (SELECT 2 AS i WHERE 1 <> 1)"; let no_result = "SELECT my_count(i) FROM (SELECT 2 AS i WHERE 1 <> 1)";
@ -794,4 +959,64 @@ mod test {
let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap(); let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
assert_eq!(2, result); assert_eq!(2, result);
} }
#[cfg(feature = "window")]
impl WindowAggregate<i64, Option<i64>> for Sum {
fn inverse(&self, ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
*sum -= ctx.get::<i64>(0)?;
Ok(())
}
fn value(&self, sum: Option<&i64>) -> Result<Option<i64>> {
Ok(sum.copied())
}
}
#[test]
#[cfg(feature = "window")]
fn test_window() {
use fallible_iterator::FallibleIterator;
let db = Connection::open_in_memory().unwrap();
db.create_window_function(
"sumint",
1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
Sum,
)
.unwrap();
db.execute_batch(
"CREATE TABLE t3(x, y);
INSERT INTO t3 VALUES('a', 4),
('b', 5),
('c', 3),
('d', 8),
('e', 1);",
)
.unwrap();
let mut stmt = db
.prepare(
"SELECT x, sumint(y) OVER (
ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS sum_y
FROM t3 ORDER BY x;",
)
.unwrap();
let results: Vec<(String, i64)> = stmt
.query(NO_PARAMS)
.unwrap()
.map(|row| Ok((row.get("x")?, row.get("sum_y")?)))
.collect()
.unwrap();
let expected = vec![
("a".to_owned(), 9),
("b".to_owned(), 12),
("c".to_owned(), 16),
("d".to_owned(), 12),
("e".to_owned(), 9),
];
assert_eq!(expected, results);
}
} }

View File

@ -236,6 +236,7 @@ fn free_boxed_hook<F>(p: *mut c_void) {
mod test { mod test {
use super::Action; use super::Action;
use crate::Connection; use crate::Connection;
use lazy_static::lazy_static;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
#[test] #[test]

View File

@ -1,12 +1,12 @@
use std::ffi::CString; use std::ffi::CString;
use std::mem; use std::mem::MaybeUninit;
use std::os::raw::c_int; use std::os::raw::{c_char, c_int};
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
use std::path::Path; use std::path::Path;
use std::ptr; use std::ptr;
use std::str; use std::str;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Once, ONCE_INIT}; use std::sync::{Arc, Mutex, Once};
use super::ffi; use super::ffi;
use super::{str_for_sqlite, str_to_cstring}; use super::{str_for_sqlite, str_to_cstring};
@ -37,6 +37,7 @@ pub struct InnerConnection {
impl InnerConnection { impl InnerConnection {
#[cfg(not(feature = "hooks"))] #[cfg(not(feature = "hooks"))]
#[allow(clippy::mutex_atomic)]
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection { pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
InnerConnection { InnerConnection {
db, db,
@ -46,6 +47,7 @@ impl InnerConnection {
} }
#[cfg(feature = "hooks")] #[cfg(feature = "hooks")]
#[allow(clippy::mutex_atomic)]
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection { pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
InnerConnection { InnerConnection {
db, db,
@ -83,13 +85,28 @@ impl InnerConnection {
} }
unsafe { unsafe {
let mut db: *mut ffi::sqlite3 = mem::uninitialized(); let mut db = MaybeUninit::uninit();
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), z_vfs); let r =
ffi::sqlite3_open_v2(c_path.as_ptr(), db.as_mut_ptr(), flags.bits(), z_vfs);
let db: *mut ffi::sqlite3 = db.assume_init();
if r != ffi::SQLITE_OK { if r != ffi::SQLITE_OK {
let e = if db.is_null() { let e = if db.is_null() {
error_from_sqlite_code(r, None) error_from_sqlite_code(r, Some(c_path.to_string_lossy().to_string()))
} else { } else {
let e = error_from_handle(db, r); let mut e = error_from_handle(db, r);
if let Error::SqliteFailure(
ffi::Error {
code: ffi::ErrorCode::CannotOpen,
..
},
Some(msg),
) = e
{
e = Error::SqliteFailure(
ffi::Error::new(r),
Some(format!("{}: {}", msg, c_path.to_string_lossy())),
);
}
ffi::sqlite3_close(db); ffi::sqlite3_close(db);
e e
}; };
@ -126,6 +143,7 @@ impl InnerConnection {
} }
} }
#[allow(clippy::mutex_atomic)]
pub fn close(&mut self) -> Result<()> { pub fn close(&mut self) -> Result<()> {
if self.db.is_null() { if self.db.is_null() {
return Ok(()); return Ok(());
@ -181,25 +199,29 @@ impl InnerConnection {
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> { 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)?; let dylib_str = super::path_to_cstring(dylib_path)?;
unsafe { unsafe {
let mut errmsg: *mut c_char = mem::uninitialized(); let mut errmsg = MaybeUninit::uninit();
let r = if let Some(entry_point) = entry_point { let r = if let Some(entry_point) = entry_point {
let c_entry = str_to_cstring(entry_point)?; let c_entry = str_to_cstring(entry_point)?;
ffi::sqlite3_load_extension( ffi::sqlite3_load_extension(
self.db, self.db,
dylib_str.as_ptr(), dylib_str.as_ptr(),
c_entry.as_ptr(), c_entry.as_ptr(),
&mut errmsg, errmsg.as_mut_ptr(),
) )
} else { } else {
ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg) ffi::sqlite3_load_extension(
self.db,
dylib_str.as_ptr(),
ptr::null(),
errmsg.as_mut_ptr(),
)
}; };
if r == ffi::SQLITE_OK { if r == ffi::SQLITE_OK {
Ok(()) Ok(())
} else { } else {
let errmsg: *mut c_char = errmsg.assume_init();
let message = super::errmsg_to_string(&*errmsg); let message = super::errmsg_to_string(&*errmsg);
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void); ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
Err(error_from_sqlite_code(r, Some(message))) Err(error_from_sqlite_code(r, Some(message)))
@ -212,8 +234,9 @@ impl InnerConnection {
} }
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> { pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let mut c_stmt = MaybeUninit::uninit();
let (c_sql, len, _) = str_for_sqlite(sql)?; let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
let mut c_tail = MaybeUninit::uninit();
let r = unsafe { let r = unsafe {
if cfg!(feature = "unlock_notify") { if cfg!(feature = "unlock_notify") {
let mut rc; let mut rc;
@ -222,8 +245,8 @@ impl InnerConnection {
self.db(), self.db(),
c_sql, c_sql,
len, len,
&mut c_stmt, c_stmt.as_mut_ptr(),
ptr::null_mut(), c_tail.as_mut_ptr(),
); );
if !unlock_notify::is_locked(self.db, rc) { if !unlock_notify::is_locked(self.db, rc) {
break; break;
@ -235,11 +258,24 @@ impl InnerConnection {
} }
rc rc
} else { } else {
ffi::sqlite3_prepare_v2(self.db(), c_sql, len, &mut c_stmt, ptr::null_mut()) ffi::sqlite3_prepare_v2(
self.db(),
c_sql,
len,
c_stmt.as_mut_ptr(),
c_tail.as_mut_ptr(),
)
} }
}; };
self.decode_result(r) // If there is an error, *ppStmt is set to NULL.
.map(|_| Statement::new(conn, RawStatement::new(c_stmt))) self.decode_result(r)?;
// If the input text contains no SQL (if the input is an empty string or a
// comment) then *ppStmt is set to NULL.
let c_stmt: *mut ffi::sqlite3_stmt = unsafe { c_stmt.assume_init() };
let c_tail: *const c_char = unsafe { c_tail.assume_init() };
// TODO ignore spaces, comments, ... at the end
let tail = !c_tail.is_null() && unsafe { c_tail != c_sql.offset(len as isize) };
Ok(Statement::new(conn, RawStatement::new(c_stmt, tail)))
} }
pub fn changes(&mut self) -> usize { pub fn changes(&mut self) -> usize {
@ -250,7 +286,7 @@ impl InnerConnection {
unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 } unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
} }
#[cfg(feature = "bundled")] // 3.8.6 #[cfg(feature = "modern_sqlite")] // 3.8.6
pub fn is_busy(&self) -> bool { pub fn is_busy(&self) -> bool {
let db = self.db(); let db = self.db();
unsafe { unsafe {
@ -285,7 +321,7 @@ impl Drop for InnerConnection {
} }
#[cfg(not(feature = "bundled"))] #[cfg(not(feature = "bundled"))]
static SQLITE_VERSION_CHECK: Once = ONCE_INIT; static SQLITE_VERSION_CHECK: Once = Once::new();
#[cfg(not(feature = "bundled"))] #[cfg(not(feature = "bundled"))]
pub static BYPASS_VERSION_CHECK: AtomicBool = AtomicBool::new(false); pub static BYPASS_VERSION_CHECK: AtomicBool = AtomicBool::new(false);
@ -333,7 +369,7 @@ rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fi
}); });
} }
static SQLITE_INIT: Once = ONCE_INIT; static SQLITE_INIT: Once = Once::new();
pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false); pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false);
fn ensure_safe_sqlite_threading_mode() -> Result<()> { fn ensure_safe_sqlite_threading_mode() -> Result<()> {

View File

@ -2,7 +2,6 @@
//! expose an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). //! expose an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
//! //!
//! ```rust //! ```rust
//! use rusqlite::types::ToSql;
//! use rusqlite::{params, Connection, Result}; //! use rusqlite::{params, Connection, Result};
//! use time::Timespec; //! use time::Timespec;
//! //!
@ -58,12 +57,6 @@
pub use libsqlite3_sys as ffi; pub use libsqlite3_sys as ffi;
#[macro_use]
extern crate bitflags;
#[cfg(any(test, feature = "vtab"))]
#[macro_use]
extern crate lazy_static;
use std::cell::RefCell; use std::cell::RefCell;
use std::convert; use std::convert;
use std::default::Default; use std::default::Default;
@ -105,6 +98,8 @@ pub mod backup;
pub mod blob; pub mod blob;
mod busy; mod busy;
mod cache; mod cache;
#[cfg(feature = "collation")]
mod collation;
mod column; mod column;
pub mod config; pub mod config;
#[cfg(any(feature = "functions", feature = "vtab"))] #[cfg(any(feature = "functions", feature = "vtab"))]
@ -165,7 +160,7 @@ macro_rules! params {
$crate::NO_PARAMS $crate::NO_PARAMS
}; };
($($param:expr),+ $(,)?) => { ($($param:expr),+ $(,)?) => {
&[$(&$param as &dyn $crate::ToSql),+] &[$(&$param as &dyn $crate::ToSql),+] as &[&dyn $crate::ToSql]
}; };
} }
@ -246,9 +241,9 @@ fn str_to_cstring(s: &str) -> Result<CString> {
/// The `sqlite3_destructor_type` item is always `SQLITE_TRANSIENT` unless /// The `sqlite3_destructor_type` item is always `SQLITE_TRANSIENT` unless
/// the string was empty (in which case it's `SQLITE_STATIC`, and the ptr is /// the string was empty (in which case it's `SQLITE_STATIC`, and the ptr is
/// static). /// static).
fn str_for_sqlite(s: &str) -> Result<(*const c_char, c_int, ffi::sqlite3_destructor_type)> { fn str_for_sqlite(s: &[u8]) -> Result<(*const c_char, c_int, ffi::sqlite3_destructor_type)> {
let len = len_as_c_int(s.len())?; let len = len_as_c_int(s.len())?;
if memchr::memchr(0, s.as_bytes()).is_none() { if memchr::memchr(0, s).is_none() {
let (ptr, dtor_info) = if len != 0 { let (ptr, dtor_info) = if len != 0 {
(s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT()) (s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT())
} else { } else {
@ -300,7 +295,7 @@ pub enum DatabaseName<'a> {
feature = "backup", feature = "backup",
feature = "blob", feature = "blob",
feature = "session", feature = "session",
feature = "bundled" feature = "modern_sqlite"
))] ))]
impl DatabaseName<'_> { impl DatabaseName<'_> {
fn to_cstring(&self) -> Result<CString> { fn to_cstring(&self) -> Result<CString> {
@ -336,6 +331,16 @@ impl Connection {
/// OpenFlags::SQLITE_OPEN_READ_WRITE | /// OpenFlags::SQLITE_OPEN_READ_WRITE |
/// OpenFlags::SQLITE_OPEN_CREATE)`. /// OpenFlags::SQLITE_OPEN_CREATE)`.
/// ///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn open_my_db() -> Result<()> {
/// let path = "./my_db.db3";
/// let db = Connection::open(&path)?;
/// println!("{}", db.is_autocommit());
/// Ok(())
/// }
/// ```
///
/// # Failure /// # Failure
/// ///
/// Will return `Err` if `path` cannot be converted to a C-compatible /// Will return `Err` if `path` cannot be converted to a C-compatible
@ -481,7 +486,8 @@ impl Connection {
P: IntoIterator, P: IntoIterator,
P::Item: ToSql, P::Item: ToSql,
{ {
self.prepare(sql).and_then(|mut stmt| stmt.execute(params)) self.prepare(sql)
.and_then(|mut stmt| stmt.check_no_tail().and_then(|_| stmt.execute(params)))
} }
/// Convenience method to prepare and execute a single SQL statement with /// Convenience method to prepare and execute a single SQL statement with
@ -507,8 +513,10 @@ 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 execute_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<usize> { pub fn execute_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
self.prepare(sql) self.prepare(sql).and_then(|mut stmt| {
.and_then(|mut stmt| stmt.execute_named(params)) stmt.check_no_tail()
.and_then(|_| stmt.execute_named(params))
})
} }
/// Get the SQLite rowid of the most recent successful INSERT. /// Get the SQLite rowid of the most recent successful INSERT.
@ -553,6 +561,7 @@ impl Connection {
F: FnOnce(&Row<'_>) -> Result<T>, F: FnOnce(&Row<'_>) -> Result<T>,
{ {
let mut stmt = self.prepare(sql)?; let mut stmt = self.prepare(sql)?;
stmt.check_no_tail()?;
stmt.query_row(params, f) stmt.query_row(params, f)
} }
@ -575,9 +584,8 @@ impl Connection {
F: FnOnce(&Row<'_>) -> Result<T>, F: FnOnce(&Row<'_>) -> Result<T>,
{ {
let mut stmt = self.prepare(sql)?; let mut stmt = self.prepare(sql)?;
let mut rows = stmt.query_named(params)?; stmt.check_no_tail()?;
stmt.query_row_named(params, f)
rows.get_expected_row().and_then(|r| f(&r))
} }
/// Convenience method to execute a query that is expected to return a /// Convenience method to execute a query that is expected to return a
@ -613,6 +621,7 @@ impl Connection {
E: convert::From<Error>, E: convert::From<Error>,
{ {
let mut stmt = self.prepare(sql)?; let mut stmt = self.prepare(sql)?;
stmt.check_no_tail()?;
let mut rows = stmt.query(params)?; let mut rows = stmt.query(params)?;
rows.get_expected_row().map_err(E::from).and_then(|r| f(&r)) rows.get_expected_row().map_err(E::from).and_then(|r| f(&r))
@ -730,7 +739,11 @@ impl Connection {
/// ///
/// You should not need to use this function. If you do need to, please /// You should not need to use this function. If you do need to, please
/// [open an issue on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe /// [open an issue on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe
/// your use case. This function is unsafe because it gives you raw access /// your use case.
///
/// # Safety
///
/// This function is unsafe because it gives you raw access
/// to the SQLite connection, and what you do with it could impact the /// to the SQLite connection, and what you do with it could impact the
/// safety of this `Connection`. /// safety of this `Connection`.
pub unsafe fn handle(&self) -> *mut ffi::sqlite3 { pub unsafe fn handle(&self) -> *mut ffi::sqlite3 {
@ -775,7 +788,7 @@ impl Connection {
} }
/// Determine if all associated prepared statements have been reset. /// Determine if all associated prepared statements have been reset.
#[cfg(feature = "bundled")] #[cfg(feature = "modern_sqlite")] // 3.8.6
pub fn is_busy(&self) -> bool { pub fn is_busy(&self) -> bool {
self.db.borrow().is_busy() self.db.borrow().is_busy()
} }
@ -789,7 +802,7 @@ impl fmt::Debug for Connection {
} }
} }
bitflags! { bitflags::bitflags! {
#[doc = "Flags for opening SQLite database connections."] #[doc = "Flags for opening SQLite database connections."]
#[doc = "See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details."] #[doc = "See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details."]
#[repr(C)] #[repr(C)]
@ -826,8 +839,11 @@ impl Default for OpenFlags {
/// If you are encountering that panic _and_ can ensure that SQLite has been /// If you are encountering that panic _and_ can ensure that SQLite has been
/// initialized in either multi-thread or serialized mode, call this function /// initialized in either multi-thread or serialized mode, call this function
/// prior to attempting to open a connection and rusqlite's initialization /// prior to attempting to open a connection and rusqlite's initialization
/// process will by skipped. This /// process will by skipped.
/// function is unsafe because if you call it and SQLite has actually been ///
/// # Safety
///
/// This function is unsafe because if you call it and SQLite has actually been
/// configured to run in single-thread mode, /// configured to run in single-thread mode,
/// you may enounter memory errors or data corruption or any number of terrible /// you may enounter memory errors or data corruption or any number of terrible
/// things that should not be possible when you're using Rust. /// things that should not be possible when you're using Rust.
@ -838,11 +854,13 @@ pub unsafe fn bypass_sqlite_initialization() {
/// rusqlite performs a one-time check that the runtime SQLite version is at /// rusqlite performs a one-time check that the runtime SQLite version is at
/// least as new as the version of SQLite found when rusqlite was built. /// least as new as the version of SQLite found when rusqlite was built.
/// Bypassing this check may be dangerous; e.g., if you use features of SQLite /// Bypassing this check may be dangerous; e.g., if you use features of SQLite
/// that are not present in the runtime /// that are not present in the runtime version.
/// version. If you are sure the runtime version is compatible with the ///
/// # Safety
///
/// If you are sure the runtime version is compatible with the
/// build-time version for your usage, you can bypass the version check by /// build-time version for your usage, you can bypass the version check by
/// calling this function before /// calling this function before your first connection attempt.
/// your first connection attempt.
pub unsafe fn bypass_sqlite_version_check() { pub unsafe fn bypass_sqlite_version_check() {
#[cfg(not(feature = "bundled"))] #[cfg(not(feature = "bundled"))]
inner_connection::BYPASS_VERSION_CHECK.store(true, Ordering::Relaxed); inner_connection::BYPASS_VERSION_CHECK.store(true, Ordering::Relaxed);
@ -867,7 +885,7 @@ impl InterruptHandle {
} }
} }
#[cfg(feature = "bundled")] // 3.7.10 #[cfg(feature = "modern_sqlite")] // 3.7.10
unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> { unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
let db_name = DatabaseName::Main.to_cstring().unwrap(); let db_name = DatabaseName::Main.to_cstring().unwrap();
let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr()); let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
@ -877,20 +895,19 @@ unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
CStr::from_ptr(db_filename).to_str().ok().map(PathBuf::from) CStr::from_ptr(db_filename).to_str().ok().map(PathBuf::from)
} }
} }
#[cfg(not(feature = "bundled"))] #[cfg(not(feature = "modern_sqlite"))]
unsafe fn db_filename(_: *mut ffi::sqlite3) -> Option<PathBuf> { unsafe fn db_filename(_: *mut ffi::sqlite3) -> Option<PathBuf> {
None None
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use self::tempdir::TempDir; use super::*;
pub use super::*;
use crate::ffi; use crate::ffi;
use fallible_iterator::FallibleIterator; use fallible_iterator::FallibleIterator;
pub use std::error::Error as StdError; use std::error::Error as StdError;
pub use std::fmt; use std::fmt;
use tempdir; use tempfile;
// this function is never called, but is still type checked; in // this function is never called, but is still type checked; in
// particular, calls with specific instantiations will require // particular, calls with specific instantiations will require
@ -913,7 +930,7 @@ mod test {
#[test] #[test]
fn test_concurrent_transactions_busy_commit() { fn test_concurrent_transactions_busy_commit() {
use std::time::Duration; use std::time::Duration;
let tmp = TempDir::new("locked").unwrap(); let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("transactions.db3"); let path = tmp.path().join("transactions.db3");
Connection::open(&path) Connection::open(&path)
@ -925,8 +942,9 @@ mod test {
) )
.expect("create temp db"); .expect("create temp db");
let mut db1 = Connection::open(&path).unwrap(); let mut db1 =
let mut db2 = Connection::open(&path).unwrap(); Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE).unwrap();
let mut db2 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY).unwrap();
db1.busy_timeout(Duration::from_millis(0)).unwrap(); db1.busy_timeout(Duration::from_millis(0)).unwrap();
db2.busy_timeout(Duration::from_millis(0)).unwrap(); db2.busy_timeout(Duration::from_millis(0)).unwrap();
@ -958,7 +976,7 @@ mod test {
#[test] #[test]
fn test_persistence() { fn test_persistence() {
let temp_dir = TempDir::new("test_open_file").unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.path().join("test.db3"); let path = temp_dir.path().join("test.db3");
{ {
@ -985,6 +1003,26 @@ mod test {
assert!(db.close().is_ok()); assert!(db.close().is_ok());
} }
#[test]
fn test_open_failure() {
let filename = "no_such_file.db";
let result = Connection::open_with_flags(filename, OpenFlags::SQLITE_OPEN_READ_ONLY);
assert!(!result.is_ok());
let err = result.err().unwrap();
if let Error::SqliteFailure(e, Some(msg)) = err {
assert_eq!(ErrorCode::CannotOpen, e.code);
assert_eq!(ffi::SQLITE_CANTOPEN, e.extended_code);
assert!(
msg.contains(filename),
"error message '{}' does not contain '{}'",
msg,
filename
);
} else {
panic!("SqliteFailure expected");
}
}
#[test] #[test]
fn test_close_retry() { fn test_close_retry() {
let db = checked_memory_handle(); let db = checked_memory_handle();
@ -994,23 +1032,25 @@ mod test {
// statement first. // statement first.
let raw_stmt = { let raw_stmt = {
use super::str_to_cstring; use super::str_to_cstring;
use std::mem; use std::mem::MaybeUninit;
use std::os::raw::c_int; use std::os::raw::c_int;
use std::ptr; use std::ptr;
let raw_db = db.db.borrow_mut().db; let raw_db = db.db.borrow_mut().db;
let sql = "SELECT 1"; let sql = "SELECT 1";
let mut raw_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let mut raw_stmt = MaybeUninit::uninit();
let cstring = str_to_cstring(sql).unwrap();
let rc = unsafe { let rc = unsafe {
ffi::sqlite3_prepare_v2( ffi::sqlite3_prepare_v2(
raw_db, raw_db,
str_to_cstring(sql).unwrap().as_ptr(), cstring.as_ptr(),
(sql.len() + 1) as c_int, (sql.len() + 1) as c_int,
&mut raw_stmt, raw_stmt.as_mut_ptr(),
ptr::null_mut(), ptr::null_mut(),
) )
}; };
assert_eq!(rc, ffi::SQLITE_OK); assert_eq!(rc, ffi::SQLITE_OK);
let raw_stmt: *mut ffi::sqlite3_stmt = unsafe { raw_stmt.assume_init() };
raw_stmt raw_stmt
}; };
@ -1087,6 +1127,22 @@ mod test {
} }
} }
#[test]
#[cfg(feature = "extra_check")]
fn test_execute_multiple() {
let db = checked_memory_handle();
let err = db
.execute(
"CREATE TABLE foo(x INTEGER); CREATE TABLE foo(x INTEGER)",
NO_PARAMS,
)
.unwrap_err();
match err {
Error::MultipleStatement => (),
_ => panic!("Unexpected error: {}", err),
}
}
#[test] #[test]
fn test_prepare_column_names() { fn test_prepare_column_names() {
let db = checked_memory_handle(); let db = checked_memory_handle();
@ -1284,7 +1340,7 @@ mod test {
} }
#[test] #[test]
#[cfg(feature = "bundled")] #[cfg(feature = "modern_sqlite")]
fn test_is_busy() { fn test_is_busy() {
let db = checked_memory_handle(); let db = checked_memory_handle();
assert!(!db.is_busy()); assert!(!db.is_busy());
@ -1313,11 +1369,11 @@ mod test {
fn test_notnull_constraint_error() { fn test_notnull_constraint_error() {
// extended error codes for constraints were added in SQLite 3.7.16; if we're // extended error codes for constraints were added in SQLite 3.7.16; if we're
// running on our bundled version, we know the extended error code exists. // running on our bundled version, we know the extended error code exists.
#[cfg(feature = "bundled")] #[cfg(feature = "modern_sqlite")]
fn check_extended_code(extended_code: c_int) { fn check_extended_code(extended_code: c_int) {
assert_eq!(extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL); assert_eq!(extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL);
} }
#[cfg(not(feature = "bundled"))] #[cfg(not(feature = "modern_sqlite"))]
fn check_extended_code(_extended_code: c_int) {} fn check_extended_code(_extended_code: c_int) {}
let db = checked_memory_handle(); let db = checked_memory_handle();
@ -1352,10 +1408,15 @@ mod test {
let interrupt_handle = db.get_interrupt_handle(); let interrupt_handle = db.get_interrupt_handle();
db.create_scalar_function("interrupt", 0, false, move |_| { db.create_scalar_function(
interrupt_handle.interrupt(); "interrupt",
Ok(0) 0,
}) crate::functions::FunctionFlags::default(),
move |_| {
interrupt_handle.interrupt();
Ok(0)
},
)
.unwrap(); .unwrap();
let mut stmt = db let mut stmt = db
@ -1367,7 +1428,6 @@ mod test {
match result.unwrap_err() { match result.unwrap_err() {
Error::SqliteFailure(err, _) => { Error::SqliteFailure(err, _) => {
assert_eq!(err.code, ErrorCode::OperationInterrupted); assert_eq!(err.code, ErrorCode::OperationInterrupted);
return;
} }
err => { err => {
panic!("Unexpected error {}", err); panic!("Unexpected error {}", err);
@ -1437,8 +1497,8 @@ mod test {
impl fmt::Display for CustomError { impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> {
match *self { match *self {
CustomError::SomeError => write!(f, "{}", self.description()), CustomError::SomeError => write!(f, "my custom error"),
CustomError::Sqlite(ref se) => write!(f, "{}: {}", self.description(), se), CustomError::Sqlite(ref se) => write!(f, "my custom error: {}", se),
} }
} }
} }
@ -1504,7 +1564,7 @@ mod test {
.collect(); .collect();
match bad_type.unwrap_err() { match bad_type.unwrap_err() {
Error::InvalidColumnType(_, _) => (), Error::InvalidColumnType(..) => (),
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
@ -1559,7 +1619,7 @@ mod test {
.collect(); .collect();
match bad_type.unwrap_err() { match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (), CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
@ -1616,7 +1676,7 @@ mod test {
}); });
match bad_type.unwrap_err() { match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (), CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
@ -1665,5 +1725,26 @@ mod test {
}) })
.unwrap(); .unwrap();
} }
#[test]
fn test_params() {
let db = checked_memory_handle();
db.query_row(
"SELECT
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?;",
params![
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
],
|r| {
assert_eq!(1, r.get_unwrap::<_, i32>(0));
Ok(())
},
)
.unwrap();
}
} }
} }

View File

@ -86,6 +86,7 @@ impl Sql {
self.push_real(r); self.push_real(r);
} }
ValueRef::Text(s) => { ValueRef::Text(s) => {
let s = std::str::from_utf8(s)?;
self.push_string_literal(s); self.push_string_literal(s);
} }
_ => { _ => {
@ -325,7 +326,7 @@ mod test {
} }
#[test] #[test]
#[cfg(feature = "bundled")] #[cfg(feature = "modern_sqlite")]
fn pragma_func_query_value() { fn pragma_func_query_value() {
use crate::NO_PARAMS; use crate::NO_PARAMS;
@ -378,7 +379,7 @@ mod test {
} }
#[test] #[test]
#[cfg(feature = "bundled")] #[cfg(feature = "modern_sqlite")]
fn pragma_func() { fn pragma_func() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap(); let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap();

View File

@ -7,11 +7,15 @@ use std::ptr;
// Private newtype for raw sqlite3_stmts that finalize themselves when dropped. // Private newtype for raw sqlite3_stmts that finalize themselves when dropped.
#[derive(Debug)] #[derive(Debug)]
pub struct RawStatement(*mut ffi::sqlite3_stmt); pub struct RawStatement(*mut ffi::sqlite3_stmt, bool);
impl RawStatement { impl RawStatement {
pub fn new(stmt: *mut ffi::sqlite3_stmt) -> RawStatement { pub fn new(stmt: *mut ffi::sqlite3_stmt, tail: bool) -> RawStatement {
RawStatement(stmt) RawStatement(stmt, tail)
}
pub fn is_null(&self) -> bool {
self.0.is_null()
} }
pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt { pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt {
@ -37,8 +41,21 @@ impl RawStatement {
} }
} }
pub fn column_name(&self, idx: usize) -> &CStr { pub fn column_name(&self, idx: usize) -> Option<&CStr> {
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx as c_int)) } let idx = idx as c_int;
if idx < 0 || idx >= self.column_count() as c_int {
return None;
}
unsafe {
let ptr = ffi::sqlite3_column_name(self.0, idx);
// If ptr is null here, it's an OOM, so there's probably nothing
// meaningful we can do. Just assert instead of returning None.
assert!(
!ptr.is_null(),
"Null pointer from sqlite3_column_name: Out of memory?"
);
Some(CStr::from_ptr(ptr))
}
} }
pub fn step(&self) -> c_int { pub fn step(&self) -> c_int {
@ -82,8 +99,12 @@ impl RawStatement {
unsafe { ffi::sqlite3_clear_bindings(self.0) } unsafe { ffi::sqlite3_clear_bindings(self.0) }
} }
pub fn sql(&self) -> &CStr { pub fn sql(&self) -> Option<&CStr> {
unsafe { CStr::from_ptr(ffi::sqlite3_sql(self.0)) } if self.0.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ffi::sqlite3_sql(self.0)) })
}
} }
pub fn finalize(mut self) -> c_int { pub fn finalize(mut self) -> c_int {
@ -96,20 +117,19 @@ impl RawStatement {
r r
} }
#[cfg(feature = "bundled")] #[cfg(feature = "modern_sqlite")] // 3.7.4
pub fn readonly(&self) -> bool { pub fn readonly(&self) -> bool {
unsafe { ffi::sqlite3_stmt_readonly(self.0) != 0 } unsafe { ffi::sqlite3_stmt_readonly(self.0) != 0 }
} }
#[cfg(feature = "bundled")] /// `CStr` must be freed
pub fn expanded_sql(&self) -> Option<&CStr> { #[cfg(feature = "modern_sqlite")] // 3.14.0
unsafe { pub unsafe fn expanded_sql(&self) -> Option<&CStr> {
let ptr = ffi::sqlite3_expanded_sql(self.0); let ptr = ffi::sqlite3_expanded_sql(self.0);
if ptr.is_null() { if ptr.is_null() {
None None
} else { } else {
Some(CStr::from_ptr(ptr)) Some(CStr::from_ptr(ptr))
}
} }
} }
@ -117,6 +137,10 @@ impl RawStatement {
assert!(!self.0.is_null()); assert!(!self.0.is_null());
unsafe { ffi::sqlite3_stmt_status(self.0, status as i32, reset as i32) } unsafe { ffi::sqlite3_stmt_status(self.0, status as i32, reset as i32) }
} }
pub fn has_tail(&self) -> bool {
self.1
}
} }
impl Drop for RawStatement { impl Drop for RawStatement {

View File

@ -223,15 +223,27 @@ impl<'stmt> Row<'stmt> {
let idx = 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,
self.stmt.column_name_unwrap(idx).into(),
value.data_type(),
),
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
FromSqlError::Other(err) => { FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err) Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
} }
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()), FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(
idx,
self.stmt.column_name_unwrap(idx).into(),
value.data_type(),
),
#[cfg(feature = "uuid")] #[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(idx, value.data_type()), FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(
idx,
self.stmt.column_name_unwrap(idx).into(),
value.data_type(),
),
}) })
} }

View File

@ -4,7 +4,7 @@
use std::ffi::CStr; use std::ffi::CStr;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem; use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int, c_uchar, c_void}; use std::os::raw::{c_char, c_int, c_uchar, c_void};
use std::panic::{catch_unwind, RefUnwindSafe}; use std::panic::{catch_unwind, RefUnwindSafe};
use std::ptr; use std::ptr;
@ -43,8 +43,9 @@ impl Session<'_> {
let db = db.db.borrow_mut().db; let db = db.db.borrow_mut().db;
let mut s: *mut ffi::sqlite3_session = unsafe { mem::uninitialized() }; let mut s = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) }); check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), s.as_mut_ptr()) });
let s: *mut ffi::sqlite3_session = unsafe { s.assume_init() };
Ok(Session { Ok(Session {
phantom: PhantomData, phantom: PhantomData,
@ -65,7 +66,6 @@ impl Session<'_> {
where where
F: Fn(&str) -> bool + RefUnwindSafe, F: Fn(&str) -> bool + RefUnwindSafe,
{ {
use std::ffi::CStr;
use std::str; use std::str;
let boxed_filter: *mut F = p_arg as *mut F; let boxed_filter: *mut F = p_arg as *mut F;
@ -113,8 +113,9 @@ impl Session<'_> {
/// Generate a Changeset /// Generate a Changeset
pub fn changeset(&mut self) -> Result<Changeset> { pub fn changeset(&mut self) -> Result<Changeset> {
let mut n = 0; let mut n = 0;
let mut cs: *mut c_void = unsafe { mem::uninitialized() }; let mut cs = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) }); check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, cs.as_mut_ptr()) });
let cs: *mut c_void = unsafe { cs.assume_init() };
Ok(Changeset { cs, n }) Ok(Changeset { cs, n })
} }
@ -134,8 +135,9 @@ impl Session<'_> {
/// Generate a Patchset /// Generate a Patchset
pub fn patchset(&mut self) -> Result<Changeset> { pub fn patchset(&mut self) -> Result<Changeset> {
let mut n = 0; let mut n = 0;
let mut ps: *mut c_void = unsafe { mem::uninitialized() }; let mut ps = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) }); check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, ps.as_mut_ptr()) });
let ps: *mut c_void = unsafe { ps.assume_init() };
// TODO Validate: same struct // TODO Validate: same struct
Ok(Changeset { cs: ps, n }) Ok(Changeset { cs: ps, n })
} }
@ -158,9 +160,10 @@ impl Session<'_> {
let from = from.to_cstring()?; let from = from.to_cstring()?;
let table = str_to_cstring(table)?.as_ptr(); let table = str_to_cstring(table)?.as_ptr();
unsafe { unsafe {
let mut errmsg: *mut c_char = mem::uninitialized(); let mut errmsg = MaybeUninit::uninit();
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, &mut errmsg); let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, errmsg.as_mut_ptr());
if r != ffi::SQLITE_OK { if r != ffi::SQLITE_OK {
let errmsg: *mut c_char = errmsg.assume_init();
let message = errmsg_to_string(&*errmsg); let message = errmsg_to_string(&*errmsg);
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void); ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
return Err(error_from_sqlite_code(r, Some(message))); return Err(error_from_sqlite_code(r, Some(message)));
@ -255,15 +258,17 @@ impl Changeset {
/// Invert a changeset /// Invert a changeset
pub fn invert(&self) -> Result<Changeset> { pub fn invert(&self) -> Result<Changeset> {
let mut n = 0; let mut n = 0;
let mut cs: *mut c_void = unsafe { mem::uninitialized() }; let mut cs = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs) }); check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, cs.as_mut_ptr()) });
let cs: *mut c_void = unsafe { cs.assume_init() };
Ok(Changeset { cs, n }) Ok(Changeset { cs, n })
} }
/// Create an iterator to traverse a changeset /// Create an iterator to traverse a changeset
pub fn iter(&self) -> Result<ChangesetIter<'_>> { pub fn iter(&self) -> Result<ChangesetIter<'_>> {
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() }; let mut it = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3changeset_start(&mut it, self.n, self.cs) }); check!(unsafe { ffi::sqlite3changeset_start(it.as_mut_ptr(), self.n, self.cs) });
let it: *mut ffi::sqlite3_changeset_iter = unsafe { it.assume_init() };
Ok(ChangesetIter { Ok(ChangesetIter {
phantom: PhantomData, phantom: PhantomData,
it, it,
@ -274,8 +279,11 @@ impl Changeset {
/// Concatenate two changeset objects /// Concatenate two changeset objects
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> { pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
let mut n = 0; let mut n = 0;
let mut cs: *mut c_void = unsafe { mem::uninitialized() }; let mut cs = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs) }); check!(unsafe {
ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, cs.as_mut_ptr())
});
let cs: *mut c_void = unsafe { cs.assume_init() };
Ok(Changeset { cs, n }) Ok(Changeset { cs, n })
} }
} }
@ -297,16 +305,16 @@ pub struct ChangesetIter<'changeset> {
impl ChangesetIter<'_> { impl ChangesetIter<'_> {
/// Create an iterator on `input` /// Create an iterator on `input`
pub fn start_strm<'input>(input: &'input mut dyn Read) -> Result<ChangesetIter<'input>> { pub fn start_strm<'input>(input: &&'input mut dyn Read) -> Result<ChangesetIter<'input>> {
let input_ref = &input; let mut it = MaybeUninit::uninit();
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
check!(unsafe { check!(unsafe {
ffi::sqlite3changeset_start_strm( ffi::sqlite3changeset_start_strm(
&mut it, it.as_mut_ptr(),
Some(x_input), Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void, input as *const &mut dyn Read as *mut c_void,
) )
}); });
let it: *mut ffi::sqlite3_changeset_iter = unsafe { it.assume_init() };
Ok(ChangesetIter { Ok(ChangesetIter {
phantom: PhantomData, phantom: PhantomData,
it, it,
@ -386,12 +394,13 @@ impl ChangesetItem {
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback. /// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> { pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe { unsafe {
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized(); let mut p_value = MaybeUninit::uninit();
check!(ffi::sqlite3changeset_conflict( check!(ffi::sqlite3changeset_conflict(
self.it, self.it,
col as i32, col as i32,
&mut p_value p_value.as_mut_ptr()
)); ));
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
Ok(ValueRef::from_value(p_value)) Ok(ValueRef::from_value(p_value))
} }
} }
@ -414,8 +423,13 @@ impl ChangesetItem {
/// `SQLITE_INSERT`. /// `SQLITE_INSERT`.
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> { pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe { unsafe {
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized(); let mut p_value = MaybeUninit::uninit();
check!(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value)); check!(ffi::sqlite3changeset_new(
self.it,
col as i32,
p_value.as_mut_ptr()
));
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
Ok(ValueRef::from_value(p_value)) Ok(ValueRef::from_value(p_value))
} }
} }
@ -426,8 +440,13 @@ impl ChangesetItem {
/// `SQLITE_UPDATE`. /// `SQLITE_UPDATE`.
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> { pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe { unsafe {
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized(); let mut p_value = MaybeUninit::uninit();
check!(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value)); check!(ffi::sqlite3changeset_old(
self.it,
col as i32,
p_value.as_mut_ptr()
));
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
Ok(ValueRef::from_value(p_value)) Ok(ValueRef::from_value(p_value))
} }
} }
@ -438,14 +457,15 @@ impl ChangesetItem {
let mut code = 0; let mut code = 0;
let mut indirect = 0; let mut indirect = 0;
let tab = unsafe { let tab = unsafe {
let mut pz_tab: *const c_char = mem::uninitialized(); let mut pz_tab = MaybeUninit::uninit();
check!(ffi::sqlite3changeset_op( check!(ffi::sqlite3changeset_op(
self.it, self.it,
&mut pz_tab, pz_tab.as_mut_ptr(),
&mut number_of_columns, &mut number_of_columns,
&mut code, &mut code,
&mut indirect &mut indirect
)); ));
let pz_tab: *const c_char = pz_tab.assume_init();
CStr::from_ptr(pz_tab) CStr::from_ptr(pz_tab)
}; };
let table_name = tab.to_str()?; let table_name = tab.to_str()?;
@ -461,12 +481,13 @@ impl ChangesetItem {
pub fn pk(&self) -> Result<&[u8]> { pub fn pk(&self) -> Result<&[u8]> {
let mut number_of_columns = 0; let mut number_of_columns = 0;
unsafe { unsafe {
let mut pks: *mut c_uchar = mem::uninitialized(); let mut pks = MaybeUninit::uninit();
check!(ffi::sqlite3changeset_pk( check!(ffi::sqlite3changeset_pk(
self.it, self.it,
&mut pks, pks.as_mut_ptr(),
&mut number_of_columns &mut number_of_columns
)); ));
let pks: *mut c_uchar = pks.assume_init();
Ok(from_raw_parts(pks, number_of_columns as usize)) Ok(from_raw_parts(pks, number_of_columns as usize))
} }
} }
@ -480,8 +501,9 @@ pub struct Changegroup {
impl Changegroup { impl Changegroup {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let mut cg: *mut ffi::sqlite3_changegroup = unsafe { mem::uninitialized() }; let mut cg = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) }); check!(unsafe { ffi::sqlite3changegroup_new(cg.as_mut_ptr()) });
let cg: *mut ffi::sqlite3_changegroup = unsafe { cg.assume_init() };
Ok(Changegroup { cg }) Ok(Changegroup { cg })
} }
@ -507,8 +529,9 @@ impl Changegroup {
/// Obtain a composite Changeset /// Obtain a composite Changeset
pub fn output(&mut self) -> Result<Changeset> { pub fn output(&mut self) -> Result<Changeset> {
let mut n = 0; let mut n = 0;
let mut output: *mut c_void = unsafe { mem::uninitialized() }; let mut output = MaybeUninit::uninit();
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) }); check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, output.as_mut_ptr()) });
let output: *mut c_void = unsafe { output.assume_init() };
Ok(Changeset { cs: output, n }) Ok(Changeset { cs: output, n })
} }
@ -648,7 +671,6 @@ where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static, F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static, C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
{ {
use std::ffi::CStr;
use std::str; use std::str;
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C); let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
@ -691,7 +713,7 @@ unsafe extern "C" fn x_input(p_in: *mut c_void, data: *mut c_void, len: *mut c_i
if p_in.is_null() { if p_in.is_null() {
return ffi::SQLITE_MISUSE; return ffi::SQLITE_MISUSE;
} }
let bytes: &mut [u8] = from_raw_parts_mut(data as *mut u8, len as usize); let bytes: &mut [u8] = from_raw_parts_mut(data as *mut u8, *len as usize);
let input = p_in as *mut &mut dyn Read; let input = p_in as *mut &mut dyn Read;
match (*input).read(bytes) { match (*input).read(bytes) {
Ok(n) => { Ok(n) => {
@ -720,6 +742,7 @@ unsafe extern "C" fn x_output(p_out: *mut c_void, data: *const c_void, len: c_in
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use fallible_streaming_iterator::FallibleStreamingIterator; use fallible_streaming_iterator::FallibleStreamingIterator;
use std::io::Read;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session}; use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session};
@ -785,8 +808,8 @@ mod test {
assert!(!output.is_empty()); assert!(!output.is_empty());
assert_eq!(14, output.len()); assert_eq!(14, output.len());
let mut input = output.as_slice(); let input: &mut dyn Read = &mut output.as_slice();
let mut iter = ChangesetIter::start_strm(&mut input).unwrap(); let mut iter = ChangesetIter::start_strm(&input).unwrap();
let item = iter.next().unwrap(); let item = iter.next().unwrap();
assert!(item.is_some()); assert!(item.is_some());
} }
@ -799,7 +822,7 @@ mod test {
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);") db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
.unwrap(); .unwrap();
lazy_static! { lazy_static::lazy_static! {
static ref CALLED: AtomicBool = AtomicBool::new(false); static ref CALLED: AtomicBool = AtomicBool::new(false);
} }
db.apply( db.apply(
@ -844,8 +867,9 @@ mod test {
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);") db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
.unwrap(); .unwrap();
let mut input = output.as_slice();
db.apply_strm( db.apply_strm(
&mut output.as_slice(), &mut input,
None::<fn(&str) -> bool>, None::<fn(&str) -> bool>,
|_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT, |_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
) )

View File

@ -45,7 +45,7 @@ impl Statement<'_> {
/// ///
/// 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. /// underlying SQLite call fails.
pub fn execute<P>(&mut self, params: P) -> Result<usize> pub fn execute<P>(&mut self, params: P) -> Result<usize>
where where
P: IntoIterator, P: IntoIterator,
@ -89,7 +89,7 @@ impl Statement<'_> {
/// ///
/// 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. /// underlying SQLite call fails.
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> { pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
self.bind_parameters_named(params)?; self.bind_parameters_named(params)?;
self.execute_with_bound_parameters() self.execute_with_bound_parameters()
@ -378,6 +378,29 @@ impl Statement<'_> {
rows.get_expected_row().and_then(|r| f(&r)) rows.get_expected_row().and_then(|r| f(&r))
} }
/// Convenience method to execute a query with named parameter(s) that is
/// expected to return a single row.
///
/// If the query returns more than one row, all rows except the first are
/// ignored.
///
/// 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
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
pub fn query_row_named<T, F>(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
where
F: FnOnce(&Row<'_>) -> Result<T>,
{
let mut rows = self.query_named(params)?;
rows.get_expected_row().and_then(|r| f(&r))
}
/// Consumes the statement. /// Consumes the statement.
/// ///
/// Functionally equivalent to the `Drop` implementation, but allows /// Functionally equivalent to the `Drop` implementation, but allows
@ -488,6 +511,7 @@ impl Statement<'_> {
} }
fn execute_with_bound_parameters(&mut self) -> Result<usize> { fn execute_with_bound_parameters(&mut self) -> Result<usize> {
self.check_update()?;
let r = self.stmt.step(); let r = self.stmt.step();
self.stmt.reset(); self.stmt.reset();
match r { match r {
@ -504,18 +528,18 @@ impl Statement<'_> {
} }
fn finalize_(&mut self) -> Result<()> { fn finalize_(&mut self) -> Result<()> {
let mut stmt = RawStatement::new(ptr::null_mut()); let mut stmt = RawStatement::new(ptr::null_mut(), false);
mem::swap(&mut stmt, &mut self.stmt); mem::swap(&mut stmt, &mut self.stmt);
self.conn.decode_result(stmt.finalize()) self.conn.decode_result(stmt.finalize())
} }
#[cfg(not(feature = "bundled"))] #[cfg(not(feature = "modern_sqlite"))]
#[inline] #[inline]
fn check_readonly(&self) -> Result<()> { fn check_readonly(&self) -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(feature = "bundled")] #[cfg(feature = "modern_sqlite")]
#[inline] #[inline]
fn check_readonly(&self) -> Result<()> { fn check_readonly(&self) -> Result<()> {
/*if !self.stmt.readonly() { does not work for PRAGMA /*if !self.stmt.readonly() { does not work for PRAGMA
@ -524,14 +548,43 @@ impl Statement<'_> {
Ok(()) Ok(())
} }
#[cfg(all(feature = "modern_sqlite", feature = "extra_check"))]
#[inline]
fn check_update(&self) -> Result<()> {
if self.column_count() > 0 || self.stmt.readonly() {
return Err(Error::ExecuteReturnedResults);
}
Ok(())
}
#[cfg(all(not(feature = "modern_sqlite"), feature = "extra_check"))]
#[inline]
fn check_update(&self) -> Result<()> {
if self.column_count() > 0 {
return Err(Error::ExecuteReturnedResults);
}
Ok(())
}
#[cfg(not(feature = "extra_check"))]
#[inline]
fn check_update(&self) -> Result<()> {
Ok(())
}
/// Returns a string containing the SQL text of prepared statement with /// Returns a string containing the SQL text of prepared statement with
/// bound parameters expanded. /// bound parameters expanded.
#[cfg(feature = "bundled")] #[cfg(feature = "modern_sqlite")]
pub fn expanded_sql(&self) -> Option<&str> { pub fn expanded_sql(&self) -> Option<String> {
unsafe { unsafe {
self.stmt match self.stmt.expanded_sql() {
.expanded_sql() Some(s) => {
.map(|s| str::from_utf8_unchecked(s.to_bytes())) let sql = str::from_utf8_unchecked(s.to_bytes()).to_owned();
ffi::sqlite3_free(s.as_ptr() as *mut _);
Some(sql)
}
_ => None,
}
} }
} }
@ -545,11 +598,26 @@ impl Statement<'_> {
pub fn reset_status(&self, status: StatementStatus) -> i32 { pub fn reset_status(&self, status: StatementStatus) -> i32 {
self.stmt.get_status(status, true) self.stmt.get_status(status, true)
} }
#[cfg(feature = "extra_check")]
pub(crate) fn check_no_tail(&self) -> Result<()> {
if self.stmt.has_tail() {
Err(Error::MultipleStatement)
} else {
Ok(())
}
}
#[cfg(not(feature = "extra_check"))]
#[inline]
pub(crate) fn check_no_tail(&self) -> Result<()> {
Ok(())
}
} }
impl Into<RawStatement> for Statement<'_> { impl Into<RawStatement> for Statement<'_> {
fn into(mut self) -> RawStatement { fn into(mut self) -> RawStatement {
let mut stmt = RawStatement::new(ptr::null_mut()); let mut stmt = RawStatement::new(ptr::null_mut(), false);
mem::swap(&mut stmt, &mut self.stmt); mem::swap(&mut stmt, &mut self.stmt);
stmt stmt
} }
@ -557,7 +625,11 @@ impl Into<RawStatement> for Statement<'_> {
impl fmt::Debug for Statement<'_> { impl fmt::Debug for Statement<'_> {
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 = if self.stmt.is_null() {
Ok("")
} else {
str::from_utf8(self.stmt.sql().unwrap().to_bytes())
};
f.debug_struct("Statement") f.debug_struct("Statement")
.field("conn", self.conn) .field("conn", self.conn)
.field("stmt", &self.stmt) .field("stmt", &self.stmt)
@ -599,10 +671,7 @@ impl Statement<'_> {
CStr::from_ptr(text as *const c_char) CStr::from_ptr(text as *const c_char)
}; };
// sqlite3_column_text returns UTF8 data, so our unwrap here should be fine. let s = s.to_bytes();
let s = s
.to_str()
.expect("sqlite3_column_text returned invalid UTF-8");
ValueRef::Text(s) ValueRef::Text(s)
} }
ffi::SQLITE_BLOB => { ffi::SQLITE_BLOB => {
@ -716,14 +785,13 @@ mod test {
.unwrap(); .unwrap();
stmt.execute_named(&[(":name", &"one")]).unwrap(); stmt.execute_named(&[(":name", &"one")]).unwrap();
let mut stmt = db
.prepare("SELECT COUNT(*) FROM test WHERE name = :name")
.unwrap();
assert_eq!( assert_eq!(
1i32, 1i32,
db.query_row_named::<i32, _>( stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))
"SELECT COUNT(*) FROM test WHERE name = :name", .unwrap()
&[(":name", &"one")],
|r| r.get(0)
)
.unwrap()
); );
} }
@ -951,12 +1019,12 @@ mod test {
} }
#[test] #[test]
#[cfg(feature = "bundled")] #[cfg(feature = "modern_sqlite")]
fn test_expanded_sql() { fn test_expanded_sql() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
let stmt = db.prepare("SELECT ?").unwrap(); let stmt = db.prepare("SELECT ?").unwrap();
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".to_owned()), stmt.expanded_sql());
} }
#[test] #[test]
@ -983,7 +1051,7 @@ mod test {
use std::collections::BTreeSet; use std::collections::BTreeSet;
let data: BTreeSet<String> = ["one", "two", "three"] let data: BTreeSet<String> = ["one", "two", "three"]
.iter() .iter()
.map(|s| s.to_string()) .map(|s| (*s).to_string())
.collect(); .collect();
db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, String>(0)) db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, String>(0))
.unwrap(); .unwrap();
@ -994,4 +1062,35 @@ mod test {
db.query_row("SELECT ?1, ?2, ?3", data.iter(), |row| row.get::<_, u8>(0)) db.query_row("SELECT ?1, ?2, ?3", data.iter(), |row| row.get::<_, u8>(0))
.unwrap(); .unwrap();
} }
#[test]
fn test_empty_stmt() {
let conn = Connection::open_in_memory().unwrap();
let mut stmt = conn.prepare("").unwrap();
assert_eq!(0, stmt.column_count());
assert!(stmt.parameter_index("test").is_ok());
assert!(stmt.step().is_err());
stmt.reset();
assert!(stmt.execute(NO_PARAMS).is_err());
}
#[test]
fn test_comment_stmt() {
let conn = Connection::open_in_memory().unwrap();
conn.prepare("/*SELECT 1;*/").unwrap();
}
#[test]
fn test_comment_and_sql_stmt() {
let conn = Connection::open_in_memory().unwrap();
let stmt = conn.prepare("/*...*/ SELECT 1;").unwrap();
assert_eq!(1, stmt.column_count());
}
#[test]
fn test_semi_colon_stmt() {
let conn = Connection::open_in_memory().unwrap();
let stmt = conn.prepare(";").unwrap();
assert_eq!(0, stmt.column_count());
}
} }

View File

@ -12,6 +12,9 @@ use crate::error::error_from_sqlite_code;
use crate::{Connection, Result}; use crate::{Connection, Result};
/// Set up the process-wide SQLite error logging callback. /// Set up the process-wide SQLite error logging callback.
///
/// # Safety
///
/// This function is marked unsafe for two reasons: /// This function is marked unsafe for two reasons:
/// ///
/// * The function is not threadsafe. No other SQLite calls may be made while /// * The function is not threadsafe. No other SQLite calls may be made while
@ -122,6 +125,7 @@ impl Connection {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use lazy_static::lazy_static;
use std::sync::Mutex; use std::sync::Mutex;
use std::time::Duration; use std::time::Duration;

View File

@ -1,9 +1,8 @@
//! 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.
use chrono;
use std::borrow::Cow; use std::borrow::Cow;
use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result; use crate::Result;
@ -54,7 +53,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-DDTHH: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();
@ -129,10 +128,8 @@ impl FromSql for DateTime<Local> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::chrono::{
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
};
use crate::{Connection, Result, NO_PARAMS}; use crate::{Connection, Result, NO_PARAMS};
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();

View File

@ -36,7 +36,7 @@ impl PartialEq for FromSqlError {
(FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2, (FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
#[cfg(feature = "uuid")] #[cfg(feature = "uuid")]
(FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2, (FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
(_, _) => false, (..) => false,
} }
} }
} }
@ -60,6 +60,7 @@ impl fmt::Display for FromSqlError {
} }
impl Error for FromSqlError { impl Error for FromSqlError {
#[allow(deprecated)]
fn description(&self) -> &str { fn description(&self) -> &str {
match *self { match *self {
FromSqlError::InvalidType => "invalid type", FromSqlError::InvalidType => "invalid type",
@ -166,6 +167,24 @@ impl FromSql for String {
} }
} }
impl FromSql for Box<str> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(Into::into)
}
}
impl FromSql for std::rc::Rc<str> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(Into::into)
}
}
impl FromSql for std::sync::Arc<str> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(Into::into)
}
}
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())

View File

@ -42,10 +42,6 @@
//! Ok(as_f64.into()) //! Ok(as_f64.into())
//! } //! }
//! } //! }
//!
//! # // Prevent this doc test from being wrapped in a `fn main()` so that it
//! # // will compile.
//! # fn main() {}
//! ``` //! ```
//! //!
//! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T` //! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T`
@ -78,7 +74,7 @@ mod value_ref;
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result}; /// # use rusqlite::{Connection, Result};
/// # use rusqlite::types::{Null}; /// # use rusqlite::types::{Null};
/// fn main() {} ///
/// fn insert_null(conn: &Connection) -> Result<usize> { /// fn insert_null(conn: &Connection) -> Result<usize> {
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[Null]) /// conn.execute("INSERT INTO people (name) VALUES (?)", &[Null])
/// } /// }
@ -229,7 +225,7 @@ mod test {
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 {
Error::InvalidColumnType(_, _) => true, Error::InvalidColumnType(..) => true,
_ => false, _ => false,
} }
} }

View File

@ -1,7 +1,6 @@
//! `ToSql` and `FromSql` implementation for JSON `Value`. //! `ToSql` and `FromSql` implementation for JSON `Value`.
use serde_json;
use self::serde_json::Value; use serde_json::Value;
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result; use crate::Result;
@ -17,7 +16,7 @@ impl ToSql for 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_slice(s),
ValueRef::Blob(b) => serde_json::from_slice(b), ValueRef::Blob(b) => serde_json::from_slice(b),
_ => return Err(FromSqlError::InvalidType), _ => return Err(FromSqlError::InvalidType),
} }
@ -27,9 +26,9 @@ impl FromSql for Value {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::serde_json;
use crate::types::ToSql; use crate::types::ToSql;
use crate::{Connection, NO_PARAMS}; use crate::{Connection, NO_PARAMS};
use serde_json;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();

View File

@ -36,8 +36,8 @@ impl FromSql for time::Timespec {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::time;
use crate::{Connection, Result, NO_PARAMS}; use crate::{Connection, Result, NO_PARAMS};
use time;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();

View File

@ -14,10 +14,12 @@ impl ToSql for Url {
impl FromSql for Url { impl FromSql for Url {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value { match value {
ValueRef::Text(s) => Url::parse(s), ValueRef::Text(s) => {
_ => return Err(FromSqlError::InvalidType), let s = std::str::from_utf8(s).map_err(|e| FromSqlError::Other(Box::new(e)))?;
Url::parse(s).map_err(|e| FromSqlError::Other(Box::new(e)))
}
_ => Err(FromSqlError::InvalidType),
} }
.map_err(|err| FromSqlError::Other(Box::new(err)))
} }
} }

View File

@ -96,6 +96,18 @@ impl From<Vec<u8>> for Value {
} }
} }
impl<T> From<Option<T>> for Value
where
T: Into<Value>,
{
fn from(v: Option<T>) -> Value {
match v {
Some(x) => x.into(),
None => Value::Null,
}
}
}
impl Value { impl Value {
pub fn data_type(&self) -> Type { pub fn data_type(&self) -> Type {
match *self { match *self {

View File

@ -14,7 +14,7 @@ pub enum ValueRef<'a> {
/// The value is a floating point number. /// The value is a floating point number.
Real(f64), Real(f64),
/// The value is a text string. /// The value is a text string.
Text(&'a str), Text(&'a [u8]),
/// The value is a blob of data /// The value is a blob of data
Blob(&'a [u8]), Blob(&'a [u8]),
} }
@ -54,7 +54,9 @@ impl<'a> ValueRef<'a> {
/// `Err(Error::InvalidColumnType)`. /// `Err(Error::InvalidColumnType)`.
pub fn as_str(&self) -> FromSqlResult<&'a str> { pub fn as_str(&self) -> FromSqlResult<&'a str> {
match *self { match *self {
ValueRef::Text(t) => Ok(t), ValueRef::Text(t) => {
std::str::from_utf8(t).map_err(|e| FromSqlError::Other(Box::new(e)))
}
_ => Err(FromSqlError::InvalidType), _ => Err(FromSqlError::InvalidType),
} }
} }
@ -75,7 +77,10 @@ impl From<ValueRef<'_>> for Value {
ValueRef::Null => Value::Null, ValueRef::Null => Value::Null,
ValueRef::Integer(i) => Value::Integer(i), ValueRef::Integer(i) => Value::Integer(i),
ValueRef::Real(r) => Value::Real(r), ValueRef::Real(r) => Value::Real(r),
ValueRef::Text(s) => Value::Text(s.to_string()), ValueRef::Text(s) => {
let s = std::str::from_utf8(s).expect("invalid UTF-8");
Value::Text(s.to_string())
}
ValueRef::Blob(b) => Value::Blob(b.to_vec()), ValueRef::Blob(b) => Value::Blob(b.to_vec()),
} }
} }
@ -83,7 +88,7 @@ impl From<ValueRef<'_>> 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.as_bytes())
} }
} }
@ -99,12 +104,24 @@ impl<'a> From<&'a Value> for ValueRef<'a> {
Value::Null => ValueRef::Null, Value::Null => ValueRef::Null,
Value::Integer(i) => ValueRef::Integer(i), Value::Integer(i) => ValueRef::Integer(i),
Value::Real(r) => ValueRef::Real(r), Value::Real(r) => ValueRef::Real(r),
Value::Text(ref s) => ValueRef::Text(s), Value::Text(ref s) => ValueRef::Text(s.as_bytes()),
Value::Blob(ref b) => ValueRef::Blob(b), Value::Blob(ref b) => ValueRef::Blob(b),
} }
} }
} }
impl<'a, T> From<Option<T>> for ValueRef<'a>
where
T: Into<ValueRef<'a>>,
{
fn from(s: Option<T>) -> ValueRef<'a> {
match s {
Some(x) => x.into(),
None => ValueRef::Null,
}
}
}
#[cfg(any(feature = "functions", feature = "session", feature = "vtab"))] #[cfg(any(feature = "functions", feature = "session", feature = "vtab"))]
impl<'a> ValueRef<'a> { impl<'a> ValueRef<'a> {
pub(crate) unsafe fn from_value(value: *mut crate::ffi::sqlite3_value) -> ValueRef<'a> { pub(crate) unsafe fn from_value(value: *mut crate::ffi::sqlite3_value) -> ValueRef<'a> {
@ -125,10 +142,7 @@ impl<'a> ValueRef<'a> {
); );
let s = CStr::from_ptr(text as *const c_char); 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_bytes();
let s = s
.to_str()
.expect("sqlite3_value_text returned invalid UTF-8");
ValueRef::Text(s) ValueRef::Text(s)
} }
ffi::SQLITE_BLOB => { ffi::SQLITE_BLOB => {

View File

@ -17,6 +17,7 @@ struct UnlockNotification {
} }
#[cfg(feature = "unlock_notify")] #[cfg(feature = "unlock_notify")]
#[allow(clippy::mutex_atomic)]
impl UnlockNotification { impl UnlockNotification {
fn new() -> UnlockNotification { fn new() -> UnlockNotification {
UnlockNotification { UnlockNotification {

View File

@ -35,7 +35,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
conn.create_module("rarray", &ARRAY_MODULE, aux) conn.create_module("rarray", &ARRAY_MODULE, aux)
} }
lazy_static! { lazy_static::lazy_static! {
static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1); static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1);
} }

View File

@ -32,7 +32,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
conn.create_module("csv", &CSV_MODULE, aux) conn.create_module("csv", &CSV_MODULE, aux)
} }
lazy_static! { lazy_static::lazy_static! {
static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1); static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1);
} }
@ -235,7 +235,7 @@ impl VTab for CSVTab {
schema = Some(sql); schema = Some(sql);
} }
Ok((schema.unwrap().to_owned(), vtab)) Ok((schema.unwrap(), vtab))
} }
// Only a forward full table scan is supported. // Only a forward full table scan is supported.
@ -338,8 +338,7 @@ impl VTabCursor for CSVTabCursor {
impl From<csv::Error> for Error { impl From<csv::Error> for Error {
fn from(err: csv::Error) -> Error { fn from(err: csv::Error) -> Error {
use std::error::Error as StdError; Error::ModuleError(err.to_string())
Error::ModuleError(String::from(err.description()))
} }
} }

View File

@ -67,8 +67,17 @@ pub struct Module<T: VTab> {
phantom: PhantomData<T>, phantom: PhantomData<T>,
} }
unsafe impl<T: VTab> Send for Module<T> {}
unsafe impl<T: VTab> Sync for Module<T> {} unsafe impl<T: VTab> Sync for Module<T> {}
// Used as a trailing initializer for sqlite3_module -- this way we avoid having
// the build fail if buildtime_bindgen is on, our bindings have
// `sqlite3_module::xShadowName`, but vtab_v3 wasn't specified.
fn zeroed_module() -> ffi::sqlite3_module {
// This is safe, as bindgen-generated structs are allowed to be zeroed.
unsafe { std::mem::MaybeUninit::zeroed().assume_init() }
}
/// Create a read-only virtual table implementation. /// Create a read-only virtual table implementation.
/// ///
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations). /// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
@ -99,8 +108,7 @@ 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"))] ..zeroed_module()
xShadowName: None,
}; };
Module { Module {
base: ffi_module, base: ffi_module,
@ -139,8 +147,7 @@ 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"))] ..zeroed_module()
xShadowName: None,
}; };
Module { Module {
base: ffi_module, base: ffi_module,
@ -161,7 +168,11 @@ impl VTabConnection {
/// ///
/// You should not need to use this function. If you do need to, please /// You should not need to use this function. If you do need to, please
/// [open an issue on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe /// [open an issue on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe
/// your use case. This function is unsafe because it gives you raw access /// your use case.
///
/// # Safety
///
/// This function is unsafe because it gives you raw access
/// to the SQLite connection, and what you do with it could impact the /// to the SQLite connection, and what you do with it could impact the
/// safety of this `Connection`. /// safety of this `Connection`.
pub unsafe fn handle(&mut self) -> *mut ffi::sqlite3 { pub unsafe fn handle(&mut self) -> *mut ffi::sqlite3 {
@ -233,16 +244,46 @@ pub trait CreateVTab: VTab {
} }
} }
bitflags! { ///Index constraint operator.
#[doc = "Index constraint operator."] #[derive(Debug, PartialEq)]
#[repr(C)] #[allow(non_snake_case, non_camel_case_types)]
pub struct IndexConstraintOp: ::std::os::raw::c_uchar { pub enum IndexConstraintOp {
const SQLITE_INDEX_CONSTRAINT_EQ = 2; SQLITE_INDEX_CONSTRAINT_EQ,
const SQLITE_INDEX_CONSTRAINT_GT = 4; SQLITE_INDEX_CONSTRAINT_GT,
const SQLITE_INDEX_CONSTRAINT_LE = 8; SQLITE_INDEX_CONSTRAINT_LE,
const SQLITE_INDEX_CONSTRAINT_LT = 16; SQLITE_INDEX_CONSTRAINT_LT,
const SQLITE_INDEX_CONSTRAINT_GE = 32; SQLITE_INDEX_CONSTRAINT_GE,
const SQLITE_INDEX_CONSTRAINT_MATCH = 64; SQLITE_INDEX_CONSTRAINT_MATCH,
SQLITE_INDEX_CONSTRAINT_LIKE, // 3.10.0
SQLITE_INDEX_CONSTRAINT_GLOB, // 3.10.0
SQLITE_INDEX_CONSTRAINT_REGEXP, // 3.10.0
SQLITE_INDEX_CONSTRAINT_NE, // 3.21.0
SQLITE_INDEX_CONSTRAINT_ISNOT, // 3.21.0
SQLITE_INDEX_CONSTRAINT_ISNOTNULL, // 3.21.0
SQLITE_INDEX_CONSTRAINT_ISNULL, // 3.21.0
SQLITE_INDEX_CONSTRAINT_IS, // 3.21.0
SQLITE_INDEX_CONSTRAINT_FUNCTION(u8), // 3.25.0
}
impl From<u8> for IndexConstraintOp {
fn from(code: u8) -> IndexConstraintOp {
match code {
2 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ,
4 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GT,
8 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LE,
16 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LT,
32 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GE,
64 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_MATCH,
65 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIKE,
66 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GLOB,
67 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_REGEXP,
68 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_NE,
69 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOT,
70 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOTNULL,
71 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNULL,
72 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_IS,
v => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_FUNCTION(v),
}
} }
} }
@ -304,8 +345,8 @@ impl IndexInfo {
} }
} }
/// Estimated number of rows returned /// Estimated number of rows returned.
#[cfg(feature = "bundled")] // SQLite >= 3.8.2 #[cfg(feature = "modern_sqlite")] // SQLite >= 3.8.2
pub fn set_estimated_rows(&mut self, estimated_rows: i64) { pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
unsafe { unsafe {
(*self.0).estimatedRows = estimated_rows; (*self.0).estimatedRows = estimated_rows;
@ -345,7 +386,7 @@ impl IndexConstraint<'_> {
/// Constraint operator /// Constraint operator
pub fn operator(&self) -> IndexConstraintOp { pub fn operator(&self) -> IndexConstraintOp {
IndexConstraintOp::from_bits_truncate(self.0.op) IndexConstraintOp::from(self.0.op)
} }
/// True if this constraint is usable /// True if this constraint is usable
@ -472,7 +513,9 @@ impl Values<'_> {
} }
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()), FromSqlError::InvalidI128Size(_) => {
Error::InvalidColumnType(idx, idx.to_string(), value.data_type())
}
#[cfg(feature = "uuid")] #[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => { FromSqlError::InvalidUuidSize(_) => {
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err)) Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
@ -643,9 +686,7 @@ unsafe extern "C" fn rust_create<T>(
where where
T: CreateVTab, T: CreateVTab,
{ {
use std::error::Error as StdError;
use std::ffi::CStr; use std::ffi::CStr;
use std::slice;
let mut conn = VTabConnection(db); let mut conn = VTabConnection(db);
let aux = aux as *mut T::Aux; let aux = aux as *mut T::Aux;
@ -664,12 +705,12 @@ where
ffi::SQLITE_OK ffi::SQLITE_OK
} else { } else {
let err = error_from_sqlite_code(rc, None); let err = error_from_sqlite_code(rc, None);
*err_msg = mprintf(err.description()); *err_msg = mprintf(&err.to_string());
rc rc
} }
} }
Err(err) => { Err(err) => {
*err_msg = mprintf(err.description()); *err_msg = mprintf(&err.to_string());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
} }
}, },
@ -680,7 +721,7 @@ where
err.extended_code err.extended_code
} }
Err(err) => { Err(err) => {
*err_msg = mprintf(err.description()); *err_msg = mprintf(&err.to_string());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
} }
} }
@ -697,9 +738,7 @@ unsafe extern "C" fn rust_connect<T>(
where where
T: VTab, T: VTab,
{ {
use std::error::Error as StdError;
use std::ffi::CStr; use std::ffi::CStr;
use std::slice;
let mut conn = VTabConnection(db); let mut conn = VTabConnection(db);
let aux = aux as *mut T::Aux; let aux = aux as *mut T::Aux;
@ -718,12 +757,12 @@ where
ffi::SQLITE_OK ffi::SQLITE_OK
} else { } else {
let err = error_from_sqlite_code(rc, None); let err = error_from_sqlite_code(rc, None);
*err_msg = mprintf(err.description()); *err_msg = mprintf(&err.to_string());
rc rc
} }
} }
Err(err) => { Err(err) => {
*err_msg = mprintf(err.description()); *err_msg = mprintf(&err.to_string());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
} }
}, },
@ -734,7 +773,7 @@ where
err.extended_code err.extended_code
} }
Err(err) => { Err(err) => {
*err_msg = mprintf(err.description()); *err_msg = mprintf(&err.to_string());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
} }
} }
@ -747,7 +786,6 @@ unsafe extern "C" fn rust_best_index<T>(
where where
T: VTab, T: VTab,
{ {
use std::error::Error as StdError;
let vt = vtab as *mut T; let vt = vtab as *mut T;
let mut idx_info = IndexInfo(info); let mut idx_info = IndexInfo(info);
match (*vt).best_index(&mut idx_info) { match (*vt).best_index(&mut idx_info) {
@ -759,7 +797,7 @@ where
err.extended_code err.extended_code
} }
Err(err) => { Err(err) => {
set_err_msg(vtab, err.description()); set_err_msg(vtab, &err.to_string());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
} }
} }
@ -781,7 +819,6 @@ unsafe extern "C" fn rust_destroy<T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
where where
T: CreateVTab, T: CreateVTab,
{ {
use std::error::Error as StdError;
if vtab.is_null() { if vtab.is_null() {
return ffi::SQLITE_OK; return ffi::SQLITE_OK;
} }
@ -798,7 +835,7 @@ where
err.extended_code err.extended_code
} }
Err(err) => { Err(err) => {
set_err_msg(vtab, err.description()); set_err_msg(vtab, &err.to_string());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
} }
} }
@ -811,7 +848,6 @@ unsafe extern "C" fn rust_open<T>(
where where
T: VTab, T: VTab,
{ {
use std::error::Error as StdError;
let vt = vtab as *mut T; let vt = vtab as *mut T;
match (*vt).open() { match (*vt).open() {
Ok(cursor) => { Ok(cursor) => {
@ -826,7 +862,7 @@ where
err.extended_code err.extended_code
} }
Err(err) => { Err(err) => {
set_err_msg(vtab, err.description()); set_err_msg(vtab, &err.to_string());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
} }
} }
@ -852,7 +888,6 @@ where
C: VTabCursor, C: VTabCursor,
{ {
use std::ffi::CStr; use std::ffi::CStr;
use std::slice;
use std::str; use std::str;
let idx_name = if idx_str.is_null() { let idx_name = if idx_str.is_null() {
None None
@ -915,7 +950,6 @@ where
/// Virtual table cursors can set an error message by assigning a string to /// Virtual table cursors can set an error message by assigning a string to
/// `zErrMsg`. /// `zErrMsg`.
unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int { unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
use std::error::Error as StdError;
match result { match result {
Ok(_) => ffi::SQLITE_OK, Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => { Err(Error::SqliteFailure(err, s)) => {
@ -925,7 +959,7 @@ unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<
err.extended_code err.extended_code
} }
Err(err) => { Err(err) => {
set_err_msg((*cursor).pVtab, err.description()); set_err_msg((*cursor).pVtab, &err.to_string());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
} }
} }
@ -943,7 +977,6 @@ unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
/// To raise an error, the `column` method should use this method to set the /// To raise an error, the `column` method should use this method to set the
/// error message and return the error code. /// error message and return the error code.
unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int { unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int {
use std::error::Error as StdError;
match result { match result {
Ok(_) => ffi::SQLITE_OK, Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => { Err(Error::SqliteFailure(err, s)) => {
@ -965,7 +998,7 @@ unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) ->
} }
Err(err) => { Err(err) => {
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_ERROR); ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_ERROR);
if let Ok(cstr) = str_to_cstring(err.description()) { if let Ok(cstr) = str_to_cstring(&err.to_string()) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1); ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
} }
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
@ -985,7 +1018,7 @@ fn mprintf(err_msg: &str) -> *mut c_char {
pub mod array; pub mod array;
#[cfg(feature = "csvtab")] #[cfg(feature = "csvtab")]
pub mod csvtab; pub mod csvtab;
#[cfg(feature = "bundled")] #[cfg(feature = "series")]
pub mod series; // SQLite >= 3.9.0 pub mod series; // SQLite >= 3.9.0
#[cfg(test)] #[cfg(test)]

View File

@ -18,7 +18,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
conn.create_module("generate_series", &SERIES_MODULE, aux) conn.create_module("generate_series", &SERIES_MODULE, aux)
} }
lazy_static! { lazy_static::lazy_static! {
static ref SERIES_MODULE: Module<SeriesTab> = eponymous_only_module::<SeriesTab>(1); static ref SERIES_MODULE: Module<SeriesTab> = eponymous_only_module::<SeriesTab>(1);
} }
@ -28,7 +28,7 @@ const SERIES_COLUMN_START: c_int = 1;
const SERIES_COLUMN_STOP: c_int = 2; const SERIES_COLUMN_STOP: c_int = 2;
const SERIES_COLUMN_STEP: c_int = 3; const SERIES_COLUMN_STEP: c_int = 3;
bitflags! { bitflags::bitflags! {
#[repr(C)] #[repr(C)]
struct QueryPlanFlags: ::std::os::raw::c_int { struct QueryPlanFlags: ::std::os::raw::c_int {
// start = $value -- constraint exists // start = $value -- constraint exists

View File

@ -2,12 +2,9 @@
//! function affects SQLite process-wide and so is not safe to run as a normal //! function affects SQLite process-wide and so is not safe to run as a normal
//! #[test] in the library. //! #[test] in the library.
#[cfg(feature = "trace")]
#[macro_use]
extern crate lazy_static;
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
fn main() { fn main() {
use lazy_static::lazy_static;
use std::os::raw::c_int; use std::os::raw::c_int;
use std::sync::Mutex; use std::sync::Mutex;