mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-22 16:29:20 +08:00
Merge remote-tracking branch 'upstream/master' into pr/open-with-vfs
# Conflicts: # src/inner_connection.rs
This commit is contained in:
commit
9e17a0b28e
20
.travis.yml
20
.travis.yml
@ -28,11 +28,9 @@ script:
|
||||
- cargo build --features sqlcipher
|
||||
- cargo build --features "bundled sqlcipher"
|
||||
- cargo test
|
||||
- cargo test --features backup
|
||||
- cargo test --features blob
|
||||
- cargo test --features functions
|
||||
- cargo test --features hooks
|
||||
- cargo test --features limits
|
||||
- cargo test --features "backup blob extra_check"
|
||||
- cargo test --features "collation functions"
|
||||
- cargo test --features "hooks limits"
|
||||
- cargo test --features load_extension
|
||||
- cargo test --features trace
|
||||
- cargo test --features chrono
|
||||
@ -42,9 +40,9 @@ script:
|
||||
- cargo test --features sqlcipher
|
||||
- cargo test --features i128_blob
|
||||
- cargo test --features uuid
|
||||
- cargo test --features "unlock_notify bundled"
|
||||
- cargo test --features "array bundled csvtab 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 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 csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled buildtime_bindgen"
|
||||
- cargo test --features "bundled unlock_notify window"
|
||||
- cargo test --features "array bundled csvtab series 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 collation 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 bundled"
|
||||
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension session serde_json trace url uuid vtab bundled buildtime_bindgen"
|
||||
|
25
Cargo.toml
25
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rusqlite"
|
||||
version = "0.18.0"
|
||||
version = "0.21.0"
|
||||
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
||||
edition = "2018"
|
||||
description = "Ergonomic wrapper for SQLite"
|
||||
@ -28,11 +28,12 @@ load_extension = []
|
||||
backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
|
||||
# sqlite3_blob_reopen: 3.7.4
|
||||
blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
||||
collation = []
|
||||
# sqlite3_create_function_v2: 3.7.3 (2010-10-08)
|
||||
functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
||||
# sqlite3_log: 3.6.23 (2010-03-09)
|
||||
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"]
|
||||
limits = []
|
||||
hooks = []
|
||||
@ -48,6 +49,13 @@ csvtab = ["csv", "vtab"]
|
||||
array = ["vtab"]
|
||||
# session extension: 3.13.0
|
||||
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]
|
||||
time = "0.1.0"
|
||||
@ -56,23 +64,24 @@ lru-cache = "0.1"
|
||||
chrono = { version = "0.4", optional = true }
|
||||
serde_json = { 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 }
|
||||
byteorder = { version = "1.2", features = ["i128"], optional = true }
|
||||
fallible-iterator = "0.2"
|
||||
fallible-streaming-iterator = "0.1"
|
||||
memchr = "2.2.0"
|
||||
uuid = { version = "0.7", optional = true }
|
||||
uuid = { version = "0.8", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3"
|
||||
tempfile = "3.1.0"
|
||||
lazy_static = "1.0"
|
||||
regex = "1.0"
|
||||
uuid = { version = "0.7", features = ["v4"] }
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
unicase = "2.4.0"
|
||||
|
||||
[dependencies.libsqlite3-sys]
|
||||
path = "libsqlite3-sys"
|
||||
version = "0.14"
|
||||
version = "0.18"
|
||||
|
||||
[[test]]
|
||||
name = "config_log"
|
||||
@ -85,7 +94,7 @@ name = "deny_single_threaded_sqlite_config"
|
||||
name = "vtab"
|
||||
|
||||
[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
|
||||
no-default-features = true
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
|
31
README.md
31
README.md
@ -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).
|
||||
|
||||
```rust
|
||||
use rusqlite::types::ToSql;
|
||||
use rusqlite::{Connection, Result, NO_PARAMS};
|
||||
use rusqlite::{params, Connection, Result};
|
||||
use time::Timespec;
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -32,7 +31,7 @@ fn main() -> Result<()> {
|
||||
time_created TEXT NOT NULL,
|
||||
data BLOB
|
||||
)",
|
||||
NO_PARAMS,
|
||||
params![],
|
||||
)?;
|
||||
let me = Person {
|
||||
id: 0,
|
||||
@ -43,18 +42,18 @@ fn main() -> Result<()> {
|
||||
conn.execute(
|
||||
"INSERT INTO person (name, time_created, data)
|
||||
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
|
||||
.prepare("SELECT id, name, time_created, data FROM person")?;
|
||||
let person_iter = stmt
|
||||
.query_map(NO_PARAMS, |row| Ok(Person {
|
||||
let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person")?;
|
||||
let person_iter = stmt.query_map(params![], |row| {
|
||||
Ok(Person {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
time_created: row.get(2)?,
|
||||
data: row.get(3)?,
|
||||
}))?;
|
||||
})
|
||||
})?;
|
||||
|
||||
for person in person_iter {
|
||||
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:
|
||||
|
||||
* 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
|
||||
is currently SQLite 3.27.2 (as of `rusqlite` 0.18.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:
|
||||
is currently SQLite 3.30.1 (as of `rusqlite` 0.21.0 / `libsqlite3-sys`
|
||||
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]
|
||||
version = "0.18.0"
|
||||
version = "0.21.0"
|
||||
features = ["bundled"]
|
||||
```
|
||||
* 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
|
||||
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
|
||||
|
||||
John Gallagher, johnkgallagher@gmail.com
|
||||
|
@ -2,9 +2,9 @@ environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
MSYS2_BITS: 64
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
VCPKG_DEFAULT_TRIPLET: x64-windows
|
||||
VCPKGRS_DYNAMIC: 1
|
||||
# - TARGET: x86_64-pc-windows-msvc
|
||||
# VCPKG_DEFAULT_TRIPLET: x64-windows
|
||||
# VCPKGRS_DYNAMIC: 1
|
||||
# - TARGET: x86_64-pc-windows-msvc
|
||||
# VCPKG_DEFAULT_TRIPLET: x64-windows-static
|
||||
# RUSTFLAGS: -Ctarget-feature=+crt-static
|
||||
@ -33,7 +33,7 @@ build: false
|
||||
test_script:
|
||||
- cargo test --lib --verbose
|
||||
- cargo test --lib --verbose --features bundled
|
||||
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace"
|
||||
- cargo test --lib --features "backup blob chrono 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 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"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.14.0"
|
||||
version = "0.18.0"
|
||||
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
||||
edition = "2018"
|
||||
repository = "https://github.com/jgallagher/rusqlite"
|
||||
@ -13,13 +13,17 @@ categories = ["external-ffi-bindings"]
|
||||
|
||||
[features]
|
||||
default = ["min_sqlite_version_3_6_8"]
|
||||
bundled = ["cc"]
|
||||
bundled = ["cc", "bundled_bindings"]
|
||||
bundled-windows = ["cc", "bundled_bindings"]
|
||||
buildtime_bindgen = ["bindgen", "pkg-config", "vcpkg"]
|
||||
sqlcipher = []
|
||||
min_sqlite_version_3_6_8 = ["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_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
|
||||
unlock_notify = []
|
||||
# 3.13.0
|
||||
@ -28,7 +32,7 @@ preupdate_hook = []
|
||||
session = ["preupdate_hook"]
|
||||
|
||||
[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 }
|
||||
cc = { version = "1.0", optional = true }
|
||||
|
||||
|
@ -5,7 +5,10 @@ fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_path = Path::new(&out_dir).join("bindgen.rs");
|
||||
if cfg!(feature = "sqlcipher") {
|
||||
if cfg!(feature = "bundled") {
|
||||
if cfg!(any(
|
||||
feature = "bundled",
|
||||
all(windows, feature = "bundled-windows")
|
||||
)) {
|
||||
println!(
|
||||
"cargo:warning={}",
|
||||
"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)
|
||||
} else {
|
||||
// This can't be `cfg!` without always requiring our `mod build_bundled` (and thus `cc`)
|
||||
#[cfg(feature = "bundled")]
|
||||
// This can't be `cfg!` without always requiring our `mod build_bundled` (and
|
||||
// thus `cc`)
|
||||
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bundled")]
|
||||
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
|
||||
mod build_bundled {
|
||||
use cc;
|
||||
use std::env;
|
||||
use std::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_STAT2")
|
||||
.flag("-DSQLITE_ENABLE_STAT4")
|
||||
.flag("-DSQLITE_HAVE_ISNAN")
|
||||
.flag("-DSQLITE_SOUNDEX")
|
||||
.flag("-DSQLITE_THREADSAFE=1")
|
||||
.flag("-DSQLITE_USE_URI")
|
||||
.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") {
|
||||
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
|
||||
}
|
||||
@ -79,6 +102,17 @@ mod build_bundled {
|
||||
if cfg!(feature = "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");
|
||||
|
||||
println!("cargo:lib_dir={}", out_dir);
|
||||
@ -104,10 +138,12 @@ impl From<HeaderLocation> for String {
|
||||
match header {
|
||||
HeaderLocation::FromEnvironment => {
|
||||
let prefix = env_prefix();
|
||||
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!(
|
||||
"{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
|
||||
prefix, prefix
|
||||
));
|
||||
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
|
||||
prefix, prefix
|
||||
)
|
||||
});
|
||||
header.push_str("/sqlite3.h");
|
||||
header
|
||||
}
|
||||
@ -129,15 +165,18 @@ mod build_linked {
|
||||
|
||||
pub fn main(_out_dir: &str, out_path: &Path) {
|
||||
let header = find_sqlite();
|
||||
if cfg!(feature = "bundled") && !cfg!(feature = "buildtime_bindgen") {
|
||||
// We can only get here if `bundled` and `sqlcipher` were both
|
||||
// specified (and `builtime_bindgen` was not). In order to keep
|
||||
// `rusqlite` relatively clean we hide the fact that `bundled` can
|
||||
// be ignored in some cases, and just use the bundled bindings, even
|
||||
// though the library we found might not match their version.
|
||||
// Ideally we'd perform a version check here, but doing so is rather
|
||||
// tricky, since we might not have access to executables (and
|
||||
// moreover, we might be cross compiling).
|
||||
if cfg!(any(
|
||||
feature = "bundled_bindings",
|
||||
feature = "bundled",
|
||||
all(windows, feature = "bundled-windows")
|
||||
)) && !cfg!(feature = "buildtime_bindgen")
|
||||
{
|
||||
// Generally means the `bundled_bindings` feature is enabled
|
||||
// (there's also an edge case where we get here involving
|
||||
// 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)
|
||||
.expect("Could not copy bindings to output directory");
|
||||
} else {
|
||||
@ -160,18 +199,22 @@ mod build_linked {
|
||||
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={}_STATIC", env_prefix());
|
||||
if cfg!(target_os = "windows") {
|
||||
println!("cargo:rerun-if-env-changed=PATH");
|
||||
}
|
||||
if cfg!(all(feature = "vcpkg", target_env = "msvc")) {
|
||||
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.
|
||||
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
|
||||
// Try to use pkg-config to determine link commands
|
||||
let pkgconfig_path = Path::new(&dir).join("pkgconfig");
|
||||
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.
|
||||
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
|
||||
println!("cargo:rustc-link-search={}", dir);
|
||||
@ -260,8 +303,8 @@ mod bindings {
|
||||
mod bindings {
|
||||
use bindgen;
|
||||
|
||||
use self::bindgen::callbacks::{IntKind, ParseCallbacks};
|
||||
use super::HeaderLocation;
|
||||
use bindgen::callbacks::{IntKind, ParseCallbacks};
|
||||
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
@ -300,7 +343,7 @@ mod bindings {
|
||||
|
||||
bindings
|
||||
.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))
|
||||
.expect("could not write output of bindgen");
|
||||
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
|
||||
@ -319,10 +362,10 @@ mod bindings {
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.open(out_path.clone())
|
||||
.expect(&format!("Could not write to {:?}", out_path));
|
||||
.open(out_path)
|
||||
.unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
|
||||
|
||||
file.write_all(output.as_bytes())
|
||||
.expect(&format!("Could not write to {:?}", out_path));
|
||||
.unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
|
||||
}
|
||||
}
|
||||
|
131
libsqlite3-sys/sqlite3/bindgen_bundled_version.rs
vendored
131
libsqlite3-sys/sqlite3/bindgen_bundled_version.rs
vendored
@ -1,10 +1,10 @@
|
||||
/* automatically generated by rust-bindgen */
|
||||
|
||||
pub const __GNUC_VA_LIST: i32 = 1;
|
||||
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.28.0\0";
|
||||
pub const SQLITE_VERSION_NUMBER: i32 = 3028000;
|
||||
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.31.0\0";
|
||||
pub const SQLITE_VERSION_NUMBER: i32 = 3031000;
|
||||
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_ERROR: i32 = 1;
|
||||
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_CONVPATH: i32 = 1038;
|
||||
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_SEQUENCE: i32 = 523;
|
||||
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_VTAB: i32 = 2323;
|
||||
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_ROLLBACK: i32 = 539;
|
||||
pub const SQLITE_WARNING_AUTOINDEX: i32 = 284;
|
||||
pub const SQLITE_AUTH_USER: i32 = 279;
|
||||
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_READWRITE: i32 = 2;
|
||||
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_PRIVATECACHE: i32 = 262144;
|
||||
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_ATOMIC512: i32 = 2;
|
||||
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_DATA_VERSION: i32 = 35;
|
||||
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_SET_LOCKPROXYFILE: i32 = 3;
|
||||
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_DEFENSIVE: i32 = 1010;
|
||||
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_IGNORE: i32 = 2;
|
||||
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_UTF16_ALIGNED: i32 = 8;
|
||||
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_TEMP_DIRECTORY_TYPE: i32 = 2;
|
||||
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_IMPOSTER: i32 = 25;
|
||||
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_PAGECACHE_USED: i32 = 1;
|
||||
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_TRUNCATE: i32 = 3;
|
||||
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_FAIL: i32 = 3;
|
||||
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 __gnuc_va_list = __builtin_va_list;
|
||||
extern "C" {
|
||||
#[link_name = "\u{1}sqlite3_version"]
|
||||
pub static mut sqlite3_version: [::std::os::raw::c_char; 0usize];
|
||||
}
|
||||
extern "C" {
|
||||
@ -1396,7 +1414,7 @@ extern "C" {
|
||||
extern "C" {
|
||||
pub fn sqlite3_vmprintf(
|
||||
arg1: *const ::std::os::raw::c_char,
|
||||
arg2: *mut __va_list_tag,
|
||||
arg2: va_list,
|
||||
) -> *mut ::std::os::raw::c_char;
|
||||
}
|
||||
extern "C" {
|
||||
@ -1412,7 +1430,7 @@ extern "C" {
|
||||
arg1: ::std::os::raw::c_int,
|
||||
arg2: *mut ::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;
|
||||
}
|
||||
extern "C" {
|
||||
@ -1554,6 +1572,27 @@ extern "C" {
|
||||
arg3: 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" {
|
||||
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;
|
||||
}
|
||||
extern "C" {
|
||||
#[link_name = "\u{1}sqlite3_temp_directory"]
|
||||
pub static mut sqlite3_temp_directory: *mut ::std::os::raw::c_char;
|
||||
}
|
||||
extern "C" {
|
||||
#[link_name = "\u{1}sqlite3_data_directory"]
|
||||
pub static mut sqlite3_data_directory: *mut ::std::os::raw::c_char;
|
||||
}
|
||||
extern "C" {
|
||||
@ -2421,6 +2458,9 @@ extern "C" {
|
||||
extern "C" {
|
||||
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" {
|
||||
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)>,
|
||||
) -> ::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)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct sqlite3_vtab {
|
||||
@ -3575,7 +3621,7 @@ extern "C" {
|
||||
pub fn sqlite3_str_vappendf(
|
||||
arg1: *mut sqlite3_str,
|
||||
zFormat: *const ::std::os::raw::c_char,
|
||||
arg2: *mut __va_list_tag,
|
||||
arg2: va_list,
|
||||
);
|
||||
}
|
||||
extern "C" {
|
||||
@ -5171,65 +5217,4 @@ fn bindgen_test_layout_fts5_api() {
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __builtin_va_list = [__va_list_tag; 1usize];
|
||||
#[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)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __builtin_va_list = *mut ::std::os::raw::c_char;
|
||||
|
19795
libsqlite3-sys/sqlite3/sqlite3.c
vendored
19795
libsqlite3-sys/sqlite3/sqlite3.c
vendored
File diff suppressed because it is too large
Load Diff
629
libsqlite3-sys/sqlite3/sqlite3.h
vendored
629
libsqlite3-sys/sqlite3/sqlite3.h
vendored
File diff suppressed because it is too large
Load Diff
16
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
16
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
@ -322,6 +322,14 @@ struct sqlite3_api_routines {
|
||||
/* Version 3.28.0 and later */
|
||||
int (*stmt_isexplain)(sqlite3_stmt*);
|
||||
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 */
|
||||
#define sqlite3_stmt_isexplain sqlite3_api->isexplain
|
||||
#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) */
|
||||
|
||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||
|
@ -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_CONVPATH: c_int = (super::SQLITE_IOERR | (26 << 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_VTAB: c_int = (super::SQLITE_LOCKED | (2 << 8));
|
||||
const SQLITE_BUSY_RECOVERY: c_int = (super::SQLITE_BUSY | (1 << 8));
|
||||
const SQLITE_BUSY_SNAPSHOT: c_int = (super::SQLITE_BUSY | (2 << 8));
|
||||
const SQLITE_CANTOPEN_NOTEMPDIR: c_int = (super::SQLITE_CANTOPEN | (1 << 8));
|
||||
@ -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_CONVPATH: c_int = (super::SQLITE_CANTOPEN | (4 << 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_CANTLOCK: c_int = (super::SQLITE_READONLY | (2 << 8));
|
||||
const SQLITE_READONLY_ROLLBACK: c_int = (super::SQLITE_READONLY | (3 << 8));
|
||||
const SQLITE_READONLY_DBMOVED: c_int = (super::SQLITE_READONLY | (4 << 8));
|
||||
const SQLITE_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_CONSTRAINT_CHECK: c_int = (super::SQLITE_CONSTRAINT | (1 << 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_CONVPATH => "cygwin_conv_path() system call failed",
|
||||
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_VTAB => "SQLITE_LOCKED_VTAB",
|
||||
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_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_CONVPATH => "cygwin_conv_path() system call failed",
|
||||
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_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_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_CONSTRAINT_CHECK => "A CHECK constraint failed",
|
||||
SQLITE_CONSTRAINT_COMMITHOOK => "Commit hook caused rollback",
|
||||
|
@ -8,7 +8,7 @@ use std::mem;
|
||||
mod error;
|
||||
|
||||
pub fn SQLITE_STATIC() -> sqlite3_destructor_type {
|
||||
Some(unsafe { mem::transmute(0isize) })
|
||||
None
|
||||
}
|
||||
|
||||
pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type {
|
||||
@ -49,7 +49,11 @@ pub enum Limit {
|
||||
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_usage = sqlite3_index_info_sqlite3_index_constraint_usage;
|
||||
|
@ -4,8 +4,8 @@ cd $SCRIPT_DIR
|
||||
export SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
|
||||
|
||||
# Download and extract amalgamation
|
||||
SQLITE=sqlite-amalgamation-3280000
|
||||
curl -O http://sqlite.org/2019/$SQLITE.zip
|
||||
SQLITE=sqlite-amalgamation-3310000
|
||||
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.h > $SQLITE3_LIB_DIR/sqlite3.h
|
||||
unzip -p $SQLITE.zip $SQLITE/sqlite3ext.h > $SQLITE3_LIB_DIR/sqlite3ext.h
|
||||
|
17
src/busy.rs
17
src/busy.rs
@ -1,4 +1,5 @@
|
||||
///! Busy handler (when the database is locked)
|
||||
use std::convert::TryInto;
|
||||
use std::mem;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
use std::panic::catch_unwind;
|
||||
@ -21,12 +22,13 @@ impl Connection {
|
||||
/// (using `busy_handler`) prior to calling this routine, that other
|
||||
/// busy handler is cleared.
|
||||
pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
|
||||
let ms = timeout
|
||||
let ms: i32 = timeout
|
||||
.as_secs()
|
||||
.checked_mul(1000)
|
||||
.and_then(|t| t.checked_add(timeout.subsec_millis().into()))
|
||||
.and_then(|t| t.try_into().ok())
|
||||
.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.
|
||||
@ -75,18 +77,17 @@ impl InnerConnection {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use self::tempdir::TempDir;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::sync_channel;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use tempdir;
|
||||
use tempfile;
|
||||
|
||||
use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS};
|
||||
|
||||
#[test]
|
||||
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 mut db1 = Connection::open(&path).unwrap();
|
||||
@ -107,7 +108,7 @@ mod test {
|
||||
#[test]
|
||||
#[ignore] // FIXME: unstable
|
||||
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 db2 = Connection::open(&path).unwrap();
|
||||
@ -137,7 +138,7 @@ mod test {
|
||||
#[test]
|
||||
#[ignore] // FIXME: unstable
|
||||
fn test_busy_handler() {
|
||||
lazy_static! {
|
||||
lazy_static::lazy_static! {
|
||||
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
fn busy_handler(_: i32) -> bool {
|
||||
@ -146,7 +147,7 @@ mod test {
|
||||
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 db2 = Connection::open(&path).unwrap();
|
||||
|
11
src/cache.rs
11
src/cache.rs
@ -135,9 +135,12 @@ impl StatementCache {
|
||||
|
||||
// Return a statement to the cache.
|
||||
fn cache_stmt(&self, stmt: RawStatement) {
|
||||
if stmt.is_null() {
|
||||
return;
|
||||
}
|
||||
let mut cache = self.0.borrow_mut();
|
||||
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()
|
||||
.to_string();
|
||||
cache.insert(sql, stmt);
|
||||
@ -339,4 +342,10 @@ mod test {
|
||||
}
|
||||
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
209
src/collation.rs
Normal 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);
|
||||
}
|
||||
}
|
105
src/column.rs
105
src/column.rs
@ -27,8 +27,7 @@ impl Statement<'_> {
|
||||
let n = self.column_count();
|
||||
let mut cols = Vec::with_capacity(n as usize);
|
||||
for i in 0..n {
|
||||
let slice = self.stmt.column_name(i);
|
||||
let s = str::from_utf8(slice.to_bytes()).unwrap();
|
||||
let s = self.column_name_unwrap(i);
|
||||
cols.push(s);
|
||||
}
|
||||
cols
|
||||
@ -40,6 +39,30 @@ impl Statement<'_> {
|
||||
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.
|
||||
///
|
||||
/// 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 n = self.column_count();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -61,14 +86,15 @@ impl Statement<'_> {
|
||||
}
|
||||
|
||||
/// 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 mut cols = Vec::with_capacity(n as usize);
|
||||
for i in 0..n {
|
||||
let slice = self.stmt.column_name(i);
|
||||
let name = str::from_utf8(slice.to_bytes()).unwrap();
|
||||
let name = self.column_name_unwrap(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
|
||||
@ -86,20 +112,45 @@ impl<'stmt> Rows<'stmt> {
|
||||
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.
|
||||
pub fn columns(&self) -> Option<Vec<Column<'stmt>>> {
|
||||
pub fn columns(&self) -> Option<Vec<Column>> {
|
||||
self.stmt.map(Statement::columns)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
pub fn column_count(&self) -> usize {
|
||||
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.
|
||||
pub fn columns(&self) -> Vec<Column<'stmt>> {
|
||||
pub fn columns(&self) -> Vec<Column> {
|
||||
self.stmt.columns()
|
||||
}
|
||||
}
|
||||
@ -125,4 +176,40 @@ mod test {
|
||||
&[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,19 @@ pub enum DbConfig {
|
||||
SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003,
|
||||
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // 3.12.0
|
||||
//SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005,
|
||||
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006,
|
||||
SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0
|
||||
SQLITE_DBCONFIG_TRIGGER_EQP = 1008,
|
||||
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006, // 3.16.2
|
||||
SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0
|
||||
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
|
||||
//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 {
|
||||
|
92
src/error.rs
92
src/error.rs
@ -1,3 +1,4 @@
|
||||
use crate::types::FromSqlError;
|
||||
use crate::types::Type;
|
||||
use crate::{errmsg_to_string, ffi};
|
||||
use std::error;
|
||||
@ -59,7 +60,7 @@ pub enum Error {
|
||||
/// Error when the value of a particular column is requested, but the type
|
||||
/// of the result in that column cannot be converted to the requested
|
||||
/// Rust type.
|
||||
InvalidColumnType(usize, Type),
|
||||
InvalidColumnType(usize, String, Type),
|
||||
|
||||
/// Error when a query that was expected to insert one row did not insert
|
||||
/// any or insert many.
|
||||
@ -99,6 +100,9 @@ pub enum Error {
|
||||
/// of a different type than what had been stored using `Context::set_aux`.
|
||||
#[cfg(feature = "functions")]
|
||||
GetAuxWrongType,
|
||||
|
||||
/// Error when the SQL contains multiple statements.
|
||||
MultipleStatement,
|
||||
}
|
||||
|
||||
impl PartialEq for Error {
|
||||
@ -117,8 +121,8 @@ impl PartialEq for Error {
|
||||
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
|
||||
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
|
||||
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
|
||||
(Error::InvalidColumnType(i1, t1), Error::InvalidColumnType(i2, t2)) => {
|
||||
i1 == i2 && t1 == t2
|
||||
(Error::InvalidColumnType(i1, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
|
||||
i1 == i2 && t1 == t2 && n1 == n2
|
||||
}
|
||||
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
|
||||
#[cfg(feature = "functions")]
|
||||
@ -138,7 +142,7 @@ impl PartialEq for Error {
|
||||
(Error::UnwindingPanic, Error::UnwindingPanic) => true,
|
||||
#[cfg(feature = "functions")]
|
||||
(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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
@ -164,13 +194,23 @@ impl fmt::Display for Error {
|
||||
f,
|
||||
"SQLite was compiled or configured for single-threaded use only"
|
||||
),
|
||||
Error::FromSqlConversionFailure(i, ref t, ref err) => write!(
|
||||
f,
|
||||
"Conversion error from type {} at index: {}, {}",
|
||||
t, i, err
|
||||
),
|
||||
Error::FromSqlConversionFailure(i, ref t, ref err) => {
|
||||
if i != UNKNOWN_COLUMN {
|
||||
write!(
|
||||
f,
|
||||
"Conversion error from type {} at index: {}, {}",
|
||||
t, i, err
|
||||
)
|
||||
} else {
|
||||
err.fmt(f)
|
||||
}
|
||||
}
|
||||
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::NulError(ref err) => err.fmt(f),
|
||||
@ -182,9 +222,11 @@ impl fmt::Display for Error {
|
||||
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
||||
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
|
||||
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
|
||||
Error::InvalidColumnType(i, ref t) => {
|
||||
write!(f, "Invalid column type {} at index: {}", t, i)
|
||||
}
|
||||
Error::InvalidColumnType(i, ref name, ref t) => write!(
|
||||
f,
|
||||
"Invalid column type {} at index: {}, name: {}",
|
||||
t, i, name
|
||||
),
|
||||
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
@ -205,11 +247,13 @@ impl fmt::Display for Error {
|
||||
Error::UnwindingPanic => write!(f, "unwinding panic"),
|
||||
#[cfg(feature = "functions")]
|
||||
Error::GetAuxWrongType => write!(f, "get_aux called with wrong type"),
|
||||
Error::MultipleStatement => write!(f, "Multiple statements provided"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
#[allow(deprecated)]
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
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"
|
||||
}
|
||||
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::InvalidParameterName(_) => "invalid parameter name",
|
||||
Error::NulError(ref err) => err.description(),
|
||||
@ -229,13 +273,13 @@ impl error::Error for Error {
|
||||
Error::QueryReturnedNoRows => "query returned no rows",
|
||||
Error::InvalidColumnIndex(_) => "invalid column index",
|
||||
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",
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
|
||||
Error::InvalidFunctionParameterType(..) => "invalid function parameter type",
|
||||
#[cfg(feature = "vtab")]
|
||||
Error::InvalidFilterParameterType(_, _) => "invalid filter parameter type",
|
||||
Error::InvalidFilterParameterType(..) => "invalid filter parameter type",
|
||||
#[cfg(feature = "functions")]
|
||||
Error::UserFunctionError(ref err) => err.description(),
|
||||
Error::ToSqlConversionFailure(ref err) => err.description(),
|
||||
@ -246,6 +290,7 @@ impl error::Error for Error {
|
||||
Error::UnwindingPanic => "unwinding panic",
|
||||
#[cfg(feature = "functions")]
|
||||
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::NulError(ref err) => Some(err),
|
||||
|
||||
Error::IntegralValueOutOfRange(_, _)
|
||||
Error::IntegralValueOutOfRange(..)
|
||||
| Error::SqliteSingleThreadedMode
|
||||
| Error::InvalidParameterName(_)
|
||||
| Error::ExecuteReturnedResults
|
||||
| Error::QueryReturnedNoRows
|
||||
| Error::InvalidColumnIndex(_)
|
||||
| Error::InvalidColumnName(_)
|
||||
| Error::InvalidColumnType(_, _)
|
||||
| Error::InvalidColumnType(..)
|
||||
| Error::InvalidPath(_)
|
||||
| Error::StatementChangedRows(_)
|
||||
| Error::InvalidQuery => None,
|
||||
| Error::InvalidQuery
|
||||
| Error::MultipleStatement => None,
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
Error::InvalidFunctionParameterType(_, _) => None,
|
||||
Error::InvalidFunctionParameterType(..) => None,
|
||||
#[cfg(feature = "vtab")]
|
||||
Error::InvalidFilterParameterType(_, _) => None,
|
||||
Error::InvalidFilterParameterType(..) => None,
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
Error::UserFunctionError(ref err) => Some(&**err),
|
||||
@ -309,7 +355,7 @@ macro_rules! check {
|
||||
($funcall:expr) => {{
|
||||
let rc = $funcall;
|
||||
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());
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
655
src/functions.rs
655
src/functions.rs
@ -10,28 +10,47 @@
|
||||
//!
|
||||
//! ```rust
|
||||
//! use regex::Regex;
|
||||
//! use rusqlite::functions::FunctionFlags;
|
||||
//! use rusqlite::{Connection, Error, Result, NO_PARAMS};
|
||||
//! use std::collections::HashMap;
|
||||
//!
|
||||
//! fn add_regexp_function(db: &Connection) -> Result<()> {
|
||||
//! let mut cached_regexes = HashMap::new();
|
||||
//! db.create_scalar_function("regexp", 2, true, move |ctx| {
|
||||
//! 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(®ex_s) {
|
||||
//! Ok(r) => vac.insert(r),
|
||||
//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
||||
//! },
|
||||
//! }
|
||||
//! };
|
||||
//! db.create_scalar_function(
|
||||
//! "regexp",
|
||||
//! 2,
|
||||
//! FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||
//! move |ctx| {
|
||||
//! assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
||||
//!
|
||||
//! let text = ctx.get::<String>(1)?;
|
||||
//! Ok(regex.is_match(&text))
|
||||
//! })
|
||||
//! let saved_re: Option<&Regex> = ctx.get_aux(0)?;
|
||||
//! 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<()> {
|
||||
@ -48,7 +67,6 @@
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
use std::error::Error as StdError;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe};
|
||||
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.
|
||||
// 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.
|
||||
#[cfg(feature = "bundled")]
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
fn constraint_error_code() -> i32 {
|
||||
ffi::SQLITE_CONSTRAINT_FUNCTION
|
||||
}
|
||||
#[cfg(not(feature = "bundled"))]
|
||||
#[cfg(not(feature = "modern_sqlite"))]
|
||||
fn constraint_error_code() -> i32 {
|
||||
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());
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -213,6 +231,44 @@ where
|
||||
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 {
|
||||
/// Attach a user-defined scalar function to this database connection.
|
||||
///
|
||||
@ -228,11 +284,17 @@ impl Connection {
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rusqlite::{Connection, Result, NO_PARAMS};
|
||||
/// # use rusqlite::functions::FunctionFlags;
|
||||
/// fn scalar_function_example(db: Connection) -> Result<()> {
|
||||
/// db.create_scalar_function("halve", 1, true, |ctx| {
|
||||
/// let value = ctx.get::<f64>(0)?;
|
||||
/// Ok(value / 2f64)
|
||||
/// })?;
|
||||
/// db.create_scalar_function(
|
||||
/// "halve",
|
||||
/// 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))?;
|
||||
/// assert_eq!(six_halved, 3f64);
|
||||
@ -247,7 +309,7 @@ impl Connection {
|
||||
&self,
|
||||
fn_name: &str,
|
||||
n_arg: c_int,
|
||||
deterministic: bool,
|
||||
flags: FunctionFlags,
|
||||
x_func: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
@ -256,7 +318,7 @@ impl Connection {
|
||||
{
|
||||
self.db
|
||||
.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.
|
||||
@ -268,7 +330,7 @@ impl Connection {
|
||||
&self,
|
||||
fn_name: &str,
|
||||
n_arg: c_int,
|
||||
deterministic: bool,
|
||||
flags: FunctionFlags,
|
||||
aggr: D,
|
||||
) -> Result<()>
|
||||
where
|
||||
@ -278,7 +340,25 @@ impl Connection {
|
||||
{
|
||||
self.db
|
||||
.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.
|
||||
@ -299,7 +379,7 @@ impl InnerConnection {
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
n_arg: c_int,
|
||||
deterministic: bool,
|
||||
flags: FunctionFlags,
|
||||
x_func: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
@ -341,16 +421,12 @@ impl InnerConnection {
|
||||
|
||||
let boxed_f: *mut F = Box::into_raw(Box::new(x_func));
|
||||
let c_name = str_to_cstring(fn_name)?;
|
||||
let mut flags = ffi::SQLITE_UTF8;
|
||||
if deterministic {
|
||||
flags |= ffi::SQLITE_DETERMINISTIC;
|
||||
}
|
||||
let r = unsafe {
|
||||
ffi::sqlite3_create_function_v2(
|
||||
self.db(),
|
||||
c_name.as_ptr(),
|
||||
n_arg,
|
||||
flags,
|
||||
flags.bits(),
|
||||
boxed_f as *mut c_void,
|
||||
Some(call_boxed_closure::<F, T>),
|
||||
None,
|
||||
@ -365,7 +441,7 @@ impl InnerConnection {
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
n_arg: c_int,
|
||||
deterministic: bool,
|
||||
flags: FunctionFlags,
|
||||
aggr: D,
|
||||
) -> Result<()>
|
||||
where
|
||||
@ -373,117 +449,14 @@ impl InnerConnection {
|
||||
D: Aggregate<A, T>,
|
||||
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 c_name = str_to_cstring(fn_name)?;
|
||||
let mut flags = ffi::SQLITE_UTF8;
|
||||
if deterministic {
|
||||
flags |= ffi::SQLITE_DETERMINISTIC;
|
||||
}
|
||||
let r = unsafe {
|
||||
ffi::sqlite3_create_function_v2(
|
||||
self.db(),
|
||||
c_name.as_ptr(),
|
||||
n_arg,
|
||||
flags,
|
||||
flags.bits(),
|
||||
boxed_aggr as *mut c_void,
|
||||
None,
|
||||
Some(call_boxed_step::<A, D, T>),
|
||||
@ -494,6 +467,38 @@ impl InnerConnection {
|
||||
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<()> {
|
||||
let c_name = str_to_cstring(fn_name)?;
|
||||
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)]
|
||||
mod test {
|
||||
use regex;
|
||||
|
||||
use self::regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use regex::Regex;
|
||||
use std::f64::EPSILON;
|
||||
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};
|
||||
|
||||
fn half(ctx: &Context<'_>) -> Result<c_double> {
|
||||
@ -534,7 +721,13 @@ mod test {
|
||||
#[test]
|
||||
fn test_function_half() {
|
||||
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));
|
||||
|
||||
assert!((3f64 - result.unwrap()).abs() < EPSILON);
|
||||
@ -543,7 +736,13 @@ mod test {
|
||||
#[test]
|
||||
fn test_remove_function() {
|
||||
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));
|
||||
assert!((3f64 - result.unwrap()).abs() < EPSILON);
|
||||
|
||||
@ -600,63 +799,14 @@ mod test {
|
||||
END;",
|
||||
)
|
||||
.unwrap();
|
||||
db.create_scalar_function("regexp", 2, true, regexp_with_auxilliary)
|
||||
.unwrap();
|
||||
|
||||
let result: Result<bool> =
|
||||
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| {
|
||||
r.get(0)
|
||||
});
|
||||
|
||||
assert_eq!(true, result.unwrap());
|
||||
|
||||
let result: Result<i64> = db.query_row(
|
||||
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
|
||||
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;",
|
||||
db.create_scalar_function(
|
||||
"regexp",
|
||||
2,
|
||||
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||
regexp_with_auxilliary,
|
||||
)
|
||||
.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(®ex_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> =
|
||||
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| {
|
||||
r.get(0)
|
||||
@ -676,16 +826,21 @@ mod test {
|
||||
#[test]
|
||||
fn test_varargs_function() {
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
db.create_scalar_function("my_concat", -1, true, |ctx| {
|
||||
let mut ret = String::new();
|
||||
db.create_scalar_function(
|
||||
"my_concat",
|
||||
-1,
|
||||
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||
|ctx| {
|
||||
let mut ret = String::new();
|
||||
|
||||
for idx in 0..ctx.len() {
|
||||
let s = ctx.get::<String>(idx)?;
|
||||
ret.push_str(&s);
|
||||
}
|
||||
for idx in 0..ctx.len() {
|
||||
let s = ctx.get::<String>(idx)?;
|
||||
ret.push_str(&s);
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
})
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for &(expected, query) in &[
|
||||
@ -701,7 +856,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_get_aux_type_checking() {
|
||||
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)? {
|
||||
ctx.set_aux::<i64>(0, 100);
|
||||
} else {
|
||||
@ -759,8 +914,13 @@ mod test {
|
||||
#[test]
|
||||
fn test_sum() {
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
db.create_aggregate_function("my_sum", 1, true, Sum)
|
||||
.unwrap();
|
||||
db.create_aggregate_function(
|
||||
"my_sum",
|
||||
1,
|
||||
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||
Sum,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 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)";
|
||||
@ -782,8 +942,13 @@ mod test {
|
||||
#[test]
|
||||
fn test_count() {
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
db.create_aggregate_function("my_count", -1, true, Count)
|
||||
.unwrap();
|
||||
db.create_aggregate_function(
|
||||
"my_count",
|
||||
-1,
|
||||
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||
Count,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 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)";
|
||||
@ -794,4 +959,64 @@ mod test {
|
||||
let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -236,6 +236,7 @@ fn free_boxed_hook<F>(p: *mut c_void) {
|
||||
mod test {
|
||||
use super::Action;
|
||||
use crate::Connection;
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
#[test]
|
||||
|
@ -1,12 +1,12 @@
|
||||
use std::ffi::CString;
|
||||
use std::mem;
|
||||
use std::os::raw::c_int;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
#[cfg(feature = "load_extension")]
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
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::{str_for_sqlite, str_to_cstring};
|
||||
@ -37,6 +37,7 @@ pub struct InnerConnection {
|
||||
|
||||
impl InnerConnection {
|
||||
#[cfg(not(feature = "hooks"))]
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
||||
InnerConnection {
|
||||
db,
|
||||
@ -46,6 +47,7 @@ impl InnerConnection {
|
||||
}
|
||||
|
||||
#[cfg(feature = "hooks")]
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
||||
InnerConnection {
|
||||
db,
|
||||
@ -83,13 +85,28 @@ impl InnerConnection {
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
|
||||
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), z_vfs);
|
||||
let mut db = MaybeUninit::uninit();
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
e
|
||||
};
|
||||
@ -126,6 +143,7 @@ impl InnerConnection {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
pub fn close(&mut self) -> Result<()> {
|
||||
if self.db.is_null() {
|
||||
return Ok(());
|
||||
@ -181,25 +199,29 @@ impl InnerConnection {
|
||||
|
||||
#[cfg(feature = "load_extension")]
|
||||
pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> {
|
||||
use std::os::raw::c_char;
|
||||
|
||||
let dylib_str = super::path_to_cstring(dylib_path)?;
|
||||
unsafe {
|
||||
let mut errmsg: *mut c_char = mem::uninitialized();
|
||||
let mut errmsg = MaybeUninit::uninit();
|
||||
let r = if let Some(entry_point) = entry_point {
|
||||
let c_entry = str_to_cstring(entry_point)?;
|
||||
ffi::sqlite3_load_extension(
|
||||
self.db,
|
||||
dylib_str.as_ptr(),
|
||||
c_entry.as_ptr(),
|
||||
&mut errmsg,
|
||||
errmsg.as_mut_ptr(),
|
||||
)
|
||||
} 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 {
|
||||
Ok(())
|
||||
} else {
|
||||
let errmsg: *mut c_char = errmsg.assume_init();
|
||||
let message = super::errmsg_to_string(&*errmsg);
|
||||
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
||||
Err(error_from_sqlite_code(r, Some(message)))
|
||||
@ -212,8 +234,9 @@ impl InnerConnection {
|
||||
}
|
||||
|
||||
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 (c_sql, len, _) = str_for_sqlite(sql)?;
|
||||
let mut c_stmt = MaybeUninit::uninit();
|
||||
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
|
||||
let mut c_tail = MaybeUninit::uninit();
|
||||
let r = unsafe {
|
||||
if cfg!(feature = "unlock_notify") {
|
||||
let mut rc;
|
||||
@ -222,8 +245,8 @@ impl InnerConnection {
|
||||
self.db(),
|
||||
c_sql,
|
||||
len,
|
||||
&mut c_stmt,
|
||||
ptr::null_mut(),
|
||||
c_stmt.as_mut_ptr(),
|
||||
c_tail.as_mut_ptr(),
|
||||
);
|
||||
if !unlock_notify::is_locked(self.db, rc) {
|
||||
break;
|
||||
@ -235,11 +258,24 @@ impl InnerConnection {
|
||||
}
|
||||
rc
|
||||
} 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)
|
||||
.map(|_| Statement::new(conn, RawStatement::new(c_stmt)))
|
||||
// If there is an error, *ppStmt is set to NULL.
|
||||
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 {
|
||||
@ -250,7 +286,7 @@ impl InnerConnection {
|
||||
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 {
|
||||
let db = self.db();
|
||||
unsafe {
|
||||
@ -285,7 +321,7 @@ impl Drop for InnerConnection {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "bundled"))]
|
||||
static SQLITE_VERSION_CHECK: Once = ONCE_INIT;
|
||||
static SQLITE_VERSION_CHECK: Once = Once::new();
|
||||
#[cfg(not(feature = "bundled"))]
|
||||
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);
|
||||
|
||||
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
||||
|
189
src/lib.rs
189
src/lib.rs
@ -2,7 +2,6 @@
|
||||
//! expose an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
|
||||
//!
|
||||
//! ```rust
|
||||
//! use rusqlite::types::ToSql;
|
||||
//! use rusqlite::{params, Connection, Result};
|
||||
//! use time::Timespec;
|
||||
//!
|
||||
@ -58,12 +57,6 @@
|
||||
|
||||
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::convert;
|
||||
use std::default::Default;
|
||||
@ -105,6 +98,8 @@ pub mod backup;
|
||||
pub mod blob;
|
||||
mod busy;
|
||||
mod cache;
|
||||
#[cfg(feature = "collation")]
|
||||
mod collation;
|
||||
mod column;
|
||||
pub mod config;
|
||||
#[cfg(any(feature = "functions", feature = "vtab"))]
|
||||
@ -165,7 +160,7 @@ macro_rules! params {
|
||||
$crate::NO_PARAMS
|
||||
};
|
||||
($($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 string was empty (in which case it's `SQLITE_STATIC`, and the ptr is
|
||||
/// 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())?;
|
||||
if memchr::memchr(0, s.as_bytes()).is_none() {
|
||||
if memchr::memchr(0, s).is_none() {
|
||||
let (ptr, dtor_info) = if len != 0 {
|
||||
(s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT())
|
||||
} else {
|
||||
@ -300,7 +295,7 @@ pub enum DatabaseName<'a> {
|
||||
feature = "backup",
|
||||
feature = "blob",
|
||||
feature = "session",
|
||||
feature = "bundled"
|
||||
feature = "modern_sqlite"
|
||||
))]
|
||||
impl DatabaseName<'_> {
|
||||
fn to_cstring(&self) -> Result<CString> {
|
||||
@ -336,6 +331,16 @@ impl Connection {
|
||||
/// OpenFlags::SQLITE_OPEN_READ_WRITE |
|
||||
/// 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
|
||||
///
|
||||
/// Will return `Err` if `path` cannot be converted to a C-compatible
|
||||
@ -481,7 +486,8 @@ impl Connection {
|
||||
P: IntoIterator,
|
||||
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
|
||||
@ -507,8 +513,10 @@ impl Connection {
|
||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||
/// or if the underlying SQLite call fails.
|
||||
pub fn execute_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
||||
self.prepare(sql)
|
||||
.and_then(|mut stmt| stmt.execute_named(params))
|
||||
self.prepare(sql).and_then(|mut stmt| {
|
||||
stmt.check_no_tail()
|
||||
.and_then(|_| stmt.execute_named(params))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the SQLite rowid of the most recent successful INSERT.
|
||||
@ -553,6 +561,7 @@ impl Connection {
|
||||
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||
{
|
||||
let mut stmt = self.prepare(sql)?;
|
||||
stmt.check_no_tail()?;
|
||||
stmt.query_row(params, f)
|
||||
}
|
||||
|
||||
@ -575,9 +584,8 @@ impl Connection {
|
||||
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||
{
|
||||
let mut stmt = self.prepare(sql)?;
|
||||
let mut rows = stmt.query_named(params)?;
|
||||
|
||||
rows.get_expected_row().and_then(|r| f(&r))
|
||||
stmt.check_no_tail()?;
|
||||
stmt.query_row_named(params, f)
|
||||
}
|
||||
|
||||
/// Convenience method to execute a query that is expected to return a
|
||||
@ -613,6 +621,7 @@ impl Connection {
|
||||
E: convert::From<Error>,
|
||||
{
|
||||
let mut stmt = self.prepare(sql)?;
|
||||
stmt.check_no_tail()?;
|
||||
let mut rows = stmt.query(params)?;
|
||||
|
||||
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
|
||||
/// [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
|
||||
/// safety of this `Connection`.
|
||||
pub unsafe fn handle(&self) -> *mut ffi::sqlite3 {
|
||||
@ -775,7 +788,7 @@ impl Connection {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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 = "See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details."]
|
||||
#[repr(C)]
|
||||
@ -826,8 +839,11 @@ impl Default for OpenFlags {
|
||||
/// If you are encountering that panic _and_ can ensure that SQLite has been
|
||||
/// initialized in either multi-thread or serialized mode, call this function
|
||||
/// prior to attempting to open a connection and rusqlite's initialization
|
||||
/// process will by skipped. This
|
||||
/// function is unsafe because if you call it and SQLite has actually been
|
||||
/// process will by skipped.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because if you call it and SQLite has actually been
|
||||
/// configured to run in single-thread mode,
|
||||
/// you may enounter memory errors or data corruption or any number of terrible
|
||||
/// 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
|
||||
/// 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
|
||||
/// that are not present in the runtime
|
||||
/// version. If you are sure the runtime version is compatible with the
|
||||
/// that are not present in the runtime version.
|
||||
///
|
||||
/// # 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
|
||||
/// calling this function before
|
||||
/// your first connection attempt.
|
||||
/// calling this function before your first connection attempt.
|
||||
pub unsafe fn bypass_sqlite_version_check() {
|
||||
#[cfg(not(feature = "bundled"))]
|
||||
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> {
|
||||
let db_name = DatabaseName::Main.to_cstring().unwrap();
|
||||
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)
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "bundled"))]
|
||||
#[cfg(not(feature = "modern_sqlite"))]
|
||||
unsafe fn db_filename(_: *mut ffi::sqlite3) -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use self::tempdir::TempDir;
|
||||
pub use super::*;
|
||||
use super::*;
|
||||
use crate::ffi;
|
||||
use fallible_iterator::FallibleIterator;
|
||||
pub use std::error::Error as StdError;
|
||||
pub use std::fmt;
|
||||
use tempdir;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use tempfile;
|
||||
|
||||
// this function is never called, but is still type checked; in
|
||||
// particular, calls with specific instantiations will require
|
||||
@ -913,7 +930,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_concurrent_transactions_busy_commit() {
|
||||
use std::time::Duration;
|
||||
let tmp = TempDir::new("locked").unwrap();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let path = tmp.path().join("transactions.db3");
|
||||
|
||||
Connection::open(&path)
|
||||
@ -925,8 +942,9 @@ mod test {
|
||||
)
|
||||
.expect("create temp db");
|
||||
|
||||
let mut db1 = Connection::open(&path).unwrap();
|
||||
let mut db2 = Connection::open(&path).unwrap();
|
||||
let mut db1 =
|
||||
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();
|
||||
db2.busy_timeout(Duration::from_millis(0)).unwrap();
|
||||
@ -958,7 +976,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
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");
|
||||
|
||||
{
|
||||
@ -985,6 +1003,26 @@ mod test {
|
||||
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]
|
||||
fn test_close_retry() {
|
||||
let db = checked_memory_handle();
|
||||
@ -994,23 +1032,25 @@ mod test {
|
||||
// statement first.
|
||||
let raw_stmt = {
|
||||
use super::str_to_cstring;
|
||||
use std::mem;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr;
|
||||
|
||||
let raw_db = db.db.borrow_mut().db;
|
||||
let sql = "SELECT 1";
|
||||
let mut raw_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
|
||||
let mut raw_stmt = MaybeUninit::uninit();
|
||||
let cstring = str_to_cstring(sql).unwrap();
|
||||
let rc = unsafe {
|
||||
ffi::sqlite3_prepare_v2(
|
||||
raw_db,
|
||||
str_to_cstring(sql).unwrap().as_ptr(),
|
||||
cstring.as_ptr(),
|
||||
(sql.len() + 1) as c_int,
|
||||
&mut raw_stmt,
|
||||
raw_stmt.as_mut_ptr(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
assert_eq!(rc, ffi::SQLITE_OK);
|
||||
let raw_stmt: *mut ffi::sqlite3_stmt = unsafe { raw_stmt.assume_init() };
|
||||
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]
|
||||
fn test_prepare_column_names() {
|
||||
let db = checked_memory_handle();
|
||||
@ -1284,7 +1340,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "bundled")]
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
fn test_is_busy() {
|
||||
let db = checked_memory_handle();
|
||||
assert!(!db.is_busy());
|
||||
@ -1313,11 +1369,11 @@ mod test {
|
||||
fn test_notnull_constraint_error() {
|
||||
// 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.
|
||||
#[cfg(feature = "bundled")]
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
fn check_extended_code(extended_code: c_int) {
|
||||
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) {}
|
||||
|
||||
let db = checked_memory_handle();
|
||||
@ -1352,10 +1408,15 @@ mod test {
|
||||
|
||||
let interrupt_handle = db.get_interrupt_handle();
|
||||
|
||||
db.create_scalar_function("interrupt", 0, false, move |_| {
|
||||
interrupt_handle.interrupt();
|
||||
Ok(0)
|
||||
})
|
||||
db.create_scalar_function(
|
||||
"interrupt",
|
||||
0,
|
||||
crate::functions::FunctionFlags::default(),
|
||||
move |_| {
|
||||
interrupt_handle.interrupt();
|
||||
Ok(0)
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut stmt = db
|
||||
@ -1367,7 +1428,6 @@ mod test {
|
||||
match result.unwrap_err() {
|
||||
Error::SqliteFailure(err, _) => {
|
||||
assert_eq!(err.code, ErrorCode::OperationInterrupted);
|
||||
return;
|
||||
}
|
||||
err => {
|
||||
panic!("Unexpected error {}", err);
|
||||
@ -1437,8 +1497,8 @@ mod test {
|
||||
impl fmt::Display for CustomError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> {
|
||||
match *self {
|
||||
CustomError::SomeError => write!(f, "{}", self.description()),
|
||||
CustomError::Sqlite(ref se) => write!(f, "{}: {}", self.description(), se),
|
||||
CustomError::SomeError => write!(f, "my custom error"),
|
||||
CustomError::Sqlite(ref se) => write!(f, "my custom error: {}", se),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1504,7 +1564,7 @@ mod test {
|
||||
.collect();
|
||||
|
||||
match bad_type.unwrap_err() {
|
||||
Error::InvalidColumnType(_, _) => (),
|
||||
Error::InvalidColumnType(..) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
@ -1559,7 +1619,7 @@ mod test {
|
||||
.collect();
|
||||
|
||||
match bad_type.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (),
|
||||
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
@ -1616,7 +1676,7 @@ mod test {
|
||||
});
|
||||
|
||||
match bad_type.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (),
|
||||
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
@ -1665,5 +1725,26 @@ mod test {
|
||||
})
|
||||
.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ impl Sql {
|
||||
self.push_real(r);
|
||||
}
|
||||
ValueRef::Text(s) => {
|
||||
let s = std::str::from_utf8(s)?;
|
||||
self.push_string_literal(s);
|
||||
}
|
||||
_ => {
|
||||
@ -325,7 +326,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "bundled")]
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
fn pragma_func_query_value() {
|
||||
use crate::NO_PARAMS;
|
||||
|
||||
@ -378,7 +379,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "bundled")]
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
fn pragma_func() {
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap();
|
||||
|
@ -7,11 +7,15 @@ use std::ptr;
|
||||
|
||||
// Private newtype for raw sqlite3_stmts that finalize themselves when dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct RawStatement(*mut ffi::sqlite3_stmt);
|
||||
pub struct RawStatement(*mut ffi::sqlite3_stmt, bool);
|
||||
|
||||
impl RawStatement {
|
||||
pub fn new(stmt: *mut ffi::sqlite3_stmt) -> RawStatement {
|
||||
RawStatement(stmt)
|
||||
pub fn new(stmt: *mut ffi::sqlite3_stmt, tail: bool) -> RawStatement {
|
||||
RawStatement(stmt, tail)
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.0.is_null()
|
||||
}
|
||||
|
||||
pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt {
|
||||
@ -37,8 +41,21 @@ impl RawStatement {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn column_name(&self, idx: usize) -> &CStr {
|
||||
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx as c_int)) }
|
||||
pub fn column_name(&self, idx: usize) -> Option<&CStr> {
|
||||
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 {
|
||||
@ -82,8 +99,12 @@ impl RawStatement {
|
||||
unsafe { ffi::sqlite3_clear_bindings(self.0) }
|
||||
}
|
||||
|
||||
pub fn sql(&self) -> &CStr {
|
||||
unsafe { CStr::from_ptr(ffi::sqlite3_sql(self.0)) }
|
||||
pub fn sql(&self) -> Option<&CStr> {
|
||||
if self.0.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { CStr::from_ptr(ffi::sqlite3_sql(self.0)) })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(mut self) -> c_int {
|
||||
@ -96,20 +117,19 @@ impl RawStatement {
|
||||
r
|
||||
}
|
||||
|
||||
#[cfg(feature = "bundled")]
|
||||
#[cfg(feature = "modern_sqlite")] // 3.7.4
|
||||
pub fn readonly(&self) -> bool {
|
||||
unsafe { ffi::sqlite3_stmt_readonly(self.0) != 0 }
|
||||
}
|
||||
|
||||
#[cfg(feature = "bundled")]
|
||||
pub fn expanded_sql(&self) -> Option<&CStr> {
|
||||
unsafe {
|
||||
let ptr = ffi::sqlite3_expanded_sql(self.0);
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(CStr::from_ptr(ptr))
|
||||
}
|
||||
/// `CStr` must be freed
|
||||
#[cfg(feature = "modern_sqlite")] // 3.14.0
|
||||
pub unsafe fn expanded_sql(&self) -> Option<&CStr> {
|
||||
let ptr = ffi::sqlite3_expanded_sql(self.0);
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(CStr::from_ptr(ptr))
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,6 +137,10 @@ impl RawStatement {
|
||||
assert!(!self.0.is_null());
|
||||
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 {
|
||||
|
18
src/row.rs
18
src/row.rs
@ -223,15 +223,27 @@ impl<'stmt> Row<'stmt> {
|
||||
let idx = idx.idx(self.stmt)?;
|
||||
let value = self.stmt.value_ref(idx);
|
||||
FromSql::column_result(value).map_err(|err| match err {
|
||||
FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
|
||||
FromSqlError::InvalidType => Error::InvalidColumnType(
|
||||
idx,
|
||||
self.stmt.column_name_unwrap(idx).into(),
|
||||
value.data_type(),
|
||||
),
|
||||
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
||||
FromSqlError::Other(err) => {
|
||||
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
|
||||
}
|
||||
#[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")]
|
||||
FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(idx, value.data_type()),
|
||||
FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(
|
||||
idx,
|
||||
self.stmt.column_name_unwrap(idx).into(),
|
||||
value.data_type(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
106
src/session.rs
106
src/session.rs
@ -4,7 +4,7 @@
|
||||
use std::ffi::CStr;
|
||||
use std::io::{Read, Write};
|
||||
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::panic::{catch_unwind, RefUnwindSafe};
|
||||
use std::ptr;
|
||||
@ -43,8 +43,9 @@ impl Session<'_> {
|
||||
|
||||
let db = db.db.borrow_mut().db;
|
||||
|
||||
let mut s: *mut ffi::sqlite3_session = unsafe { mem::uninitialized() };
|
||||
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) });
|
||||
let mut s = MaybeUninit::uninit();
|
||||
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 {
|
||||
phantom: PhantomData,
|
||||
@ -65,7 +66,6 @@ impl Session<'_> {
|
||||
where
|
||||
F: Fn(&str) -> bool + RefUnwindSafe,
|
||||
{
|
||||
use std::ffi::CStr;
|
||||
use std::str;
|
||||
|
||||
let boxed_filter: *mut F = p_arg as *mut F;
|
||||
@ -113,8 +113,9 @@ impl Session<'_> {
|
||||
/// Generate a Changeset
|
||||
pub fn changeset(&mut self) -> Result<Changeset> {
|
||||
let mut n = 0;
|
||||
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
||||
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) });
|
||||
let mut cs = MaybeUninit::uninit();
|
||||
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 })
|
||||
}
|
||||
|
||||
@ -134,8 +135,9 @@ impl Session<'_> {
|
||||
/// Generate a Patchset
|
||||
pub fn patchset(&mut self) -> Result<Changeset> {
|
||||
let mut n = 0;
|
||||
let mut ps: *mut c_void = unsafe { mem::uninitialized() };
|
||||
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) });
|
||||
let mut ps = MaybeUninit::uninit();
|
||||
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
|
||||
Ok(Changeset { cs: ps, n })
|
||||
}
|
||||
@ -158,9 +160,10 @@ impl Session<'_> {
|
||||
let from = from.to_cstring()?;
|
||||
let table = str_to_cstring(table)?.as_ptr();
|
||||
unsafe {
|
||||
let mut errmsg: *mut c_char = mem::uninitialized();
|
||||
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, &mut errmsg);
|
||||
let mut errmsg = MaybeUninit::uninit();
|
||||
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, errmsg.as_mut_ptr());
|
||||
if r != ffi::SQLITE_OK {
|
||||
let errmsg: *mut c_char = errmsg.assume_init();
|
||||
let message = errmsg_to_string(&*errmsg);
|
||||
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
||||
return Err(error_from_sqlite_code(r, Some(message)));
|
||||
@ -255,15 +258,17 @@ impl Changeset {
|
||||
/// Invert a changeset
|
||||
pub fn invert(&self) -> Result<Changeset> {
|
||||
let mut n = 0;
|
||||
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
||||
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs) });
|
||||
let mut cs = MaybeUninit::uninit();
|
||||
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 })
|
||||
}
|
||||
|
||||
/// Create an iterator to traverse a changeset
|
||||
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
|
||||
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
|
||||
check!(unsafe { ffi::sqlite3changeset_start(&mut it, self.n, self.cs) });
|
||||
let mut it = MaybeUninit::uninit();
|
||||
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 {
|
||||
phantom: PhantomData,
|
||||
it,
|
||||
@ -274,8 +279,11 @@ impl Changeset {
|
||||
/// Concatenate two changeset objects
|
||||
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
|
||||
let mut n = 0;
|
||||
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
||||
check!(unsafe { ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs) });
|
||||
let mut cs = MaybeUninit::uninit();
|
||||
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 })
|
||||
}
|
||||
}
|
||||
@ -297,16 +305,16 @@ pub struct ChangesetIter<'changeset> {
|
||||
|
||||
impl ChangesetIter<'_> {
|
||||
/// Create an iterator on `input`
|
||||
pub fn start_strm<'input>(input: &'input mut dyn Read) -> Result<ChangesetIter<'input>> {
|
||||
let input_ref = &input;
|
||||
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
|
||||
pub fn start_strm<'input>(input: &&'input mut dyn Read) -> Result<ChangesetIter<'input>> {
|
||||
let mut it = MaybeUninit::uninit();
|
||||
check!(unsafe {
|
||||
ffi::sqlite3changeset_start_strm(
|
||||
&mut it,
|
||||
it.as_mut_ptr(),
|
||||
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 {
|
||||
phantom: PhantomData,
|
||||
it,
|
||||
@ -386,12 +394,13 @@ impl ChangesetItem {
|
||||
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
|
||||
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||
unsafe {
|
||||
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
||||
let mut p_value = MaybeUninit::uninit();
|
||||
check!(ffi::sqlite3changeset_conflict(
|
||||
self.it,
|
||||
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))
|
||||
}
|
||||
}
|
||||
@ -414,8 +423,13 @@ impl ChangesetItem {
|
||||
/// `SQLITE_INSERT`.
|
||||
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||
unsafe {
|
||||
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
||||
check!(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value));
|
||||
let mut p_value = MaybeUninit::uninit();
|
||||
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))
|
||||
}
|
||||
}
|
||||
@ -426,8 +440,13 @@ impl ChangesetItem {
|
||||
/// `SQLITE_UPDATE`.
|
||||
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||
unsafe {
|
||||
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
||||
check!(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value));
|
||||
let mut p_value = MaybeUninit::uninit();
|
||||
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))
|
||||
}
|
||||
}
|
||||
@ -438,14 +457,15 @@ impl ChangesetItem {
|
||||
let mut code = 0;
|
||||
let mut indirect = 0;
|
||||
let tab = unsafe {
|
||||
let mut pz_tab: *const c_char = mem::uninitialized();
|
||||
let mut pz_tab = MaybeUninit::uninit();
|
||||
check!(ffi::sqlite3changeset_op(
|
||||
self.it,
|
||||
&mut pz_tab,
|
||||
pz_tab.as_mut_ptr(),
|
||||
&mut number_of_columns,
|
||||
&mut code,
|
||||
&mut indirect
|
||||
));
|
||||
let pz_tab: *const c_char = pz_tab.assume_init();
|
||||
CStr::from_ptr(pz_tab)
|
||||
};
|
||||
let table_name = tab.to_str()?;
|
||||
@ -461,12 +481,13 @@ impl ChangesetItem {
|
||||
pub fn pk(&self) -> Result<&[u8]> {
|
||||
let mut number_of_columns = 0;
|
||||
unsafe {
|
||||
let mut pks: *mut c_uchar = mem::uninitialized();
|
||||
let mut pks = MaybeUninit::uninit();
|
||||
check!(ffi::sqlite3changeset_pk(
|
||||
self.it,
|
||||
&mut pks,
|
||||
pks.as_mut_ptr(),
|
||||
&mut number_of_columns
|
||||
));
|
||||
let pks: *mut c_uchar = pks.assume_init();
|
||||
Ok(from_raw_parts(pks, number_of_columns as usize))
|
||||
}
|
||||
}
|
||||
@ -480,8 +501,9 @@ pub struct Changegroup {
|
||||
|
||||
impl Changegroup {
|
||||
pub fn new() -> Result<Self> {
|
||||
let mut cg: *mut ffi::sqlite3_changegroup = unsafe { mem::uninitialized() };
|
||||
check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) });
|
||||
let mut cg = MaybeUninit::uninit();
|
||||
check!(unsafe { ffi::sqlite3changegroup_new(cg.as_mut_ptr()) });
|
||||
let cg: *mut ffi::sqlite3_changegroup = unsafe { cg.assume_init() };
|
||||
Ok(Changegroup { cg })
|
||||
}
|
||||
|
||||
@ -507,8 +529,9 @@ impl Changegroup {
|
||||
/// Obtain a composite Changeset
|
||||
pub fn output(&mut self) -> Result<Changeset> {
|
||||
let mut n = 0;
|
||||
let mut output: *mut c_void = unsafe { mem::uninitialized() };
|
||||
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) });
|
||||
let mut output = MaybeUninit::uninit();
|
||||
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 })
|
||||
}
|
||||
|
||||
@ -648,7 +671,6 @@ where
|
||||
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
|
||||
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
|
||||
{
|
||||
use std::ffi::CStr;
|
||||
use std::str;
|
||||
|
||||
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
|
||||
@ -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() {
|
||||
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;
|
||||
match (*input).read(bytes) {
|
||||
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)]
|
||||
mod test {
|
||||
use fallible_streaming_iterator::FallibleStreamingIterator;
|
||||
use std::io::Read;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session};
|
||||
@ -785,8 +808,8 @@ mod test {
|
||||
assert!(!output.is_empty());
|
||||
assert_eq!(14, output.len());
|
||||
|
||||
let mut input = output.as_slice();
|
||||
let mut iter = ChangesetIter::start_strm(&mut input).unwrap();
|
||||
let input: &mut dyn Read = &mut output.as_slice();
|
||||
let mut iter = ChangesetIter::start_strm(&input).unwrap();
|
||||
let item = iter.next().unwrap();
|
||||
assert!(item.is_some());
|
||||
}
|
||||
@ -799,7 +822,7 @@ mod test {
|
||||
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
||||
.unwrap();
|
||||
|
||||
lazy_static! {
|
||||
lazy_static::lazy_static! {
|
||||
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
db.apply(
|
||||
@ -844,8 +867,9 @@ mod test {
|
||||
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
||||
.unwrap();
|
||||
|
||||
let mut input = output.as_slice();
|
||||
db.apply_strm(
|
||||
&mut output.as_slice(),
|
||||
&mut input,
|
||||
None::<fn(&str) -> bool>,
|
||||
|_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
|
||||
)
|
||||
|
149
src/statement.rs
149
src/statement.rs
@ -45,7 +45,7 @@ impl Statement<'_> {
|
||||
///
|
||||
/// Will return `Err` if binding parameters fails, the executed statement
|
||||
/// returns rows (in which case `query` should be used instead), or the
|
||||
/// underling SQLite call fails.
|
||||
/// underlying SQLite call fails.
|
||||
pub fn execute<P>(&mut self, params: P) -> Result<usize>
|
||||
where
|
||||
P: IntoIterator,
|
||||
@ -89,7 +89,7 @@ impl Statement<'_> {
|
||||
///
|
||||
/// Will return `Err` if binding parameters fails, the executed statement
|
||||
/// returns rows (in which case `query` should be used instead), or the
|
||||
/// underling SQLite call fails.
|
||||
/// underlying SQLite call fails.
|
||||
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
||||
self.bind_parameters_named(params)?;
|
||||
self.execute_with_bound_parameters()
|
||||
@ -378,6 +378,29 @@ impl Statement<'_> {
|
||||
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.
|
||||
///
|
||||
/// Functionally equivalent to the `Drop` implementation, but allows
|
||||
@ -488,6 +511,7 @@ impl Statement<'_> {
|
||||
}
|
||||
|
||||
fn execute_with_bound_parameters(&mut self) -> Result<usize> {
|
||||
self.check_update()?;
|
||||
let r = self.stmt.step();
|
||||
self.stmt.reset();
|
||||
match r {
|
||||
@ -504,18 +528,18 @@ impl Statement<'_> {
|
||||
}
|
||||
|
||||
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);
|
||||
self.conn.decode_result(stmt.finalize())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "bundled"))]
|
||||
#[cfg(not(feature = "modern_sqlite"))]
|
||||
#[inline]
|
||||
fn check_readonly(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "bundled")]
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
#[inline]
|
||||
fn check_readonly(&self) -> Result<()> {
|
||||
/*if !self.stmt.readonly() { does not work for PRAGMA
|
||||
@ -524,14 +548,43 @@ impl Statement<'_> {
|
||||
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
|
||||
/// bound parameters expanded.
|
||||
#[cfg(feature = "bundled")]
|
||||
pub fn expanded_sql(&self) -> Option<&str> {
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
pub fn expanded_sql(&self) -> Option<String> {
|
||||
unsafe {
|
||||
self.stmt
|
||||
.expanded_sql()
|
||||
.map(|s| str::from_utf8_unchecked(s.to_bytes()))
|
||||
match self.stmt.expanded_sql() {
|
||||
Some(s) => {
|
||||
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 {
|
||||
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<'_> {
|
||||
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);
|
||||
stmt
|
||||
}
|
||||
@ -557,7 +625,11 @@ impl Into<RawStatement> for Statement<'_> {
|
||||
|
||||
impl fmt::Debug for Statement<'_> {
|
||||
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")
|
||||
.field("conn", self.conn)
|
||||
.field("stmt", &self.stmt)
|
||||
@ -599,10 +671,7 @@ impl Statement<'_> {
|
||||
CStr::from_ptr(text as *const c_char)
|
||||
};
|
||||
|
||||
// sqlite3_column_text returns UTF8 data, so our unwrap here should be fine.
|
||||
let s = s
|
||||
.to_str()
|
||||
.expect("sqlite3_column_text returned invalid UTF-8");
|
||||
let s = s.to_bytes();
|
||||
ValueRef::Text(s)
|
||||
}
|
||||
ffi::SQLITE_BLOB => {
|
||||
@ -716,14 +785,13 @@ mod test {
|
||||
.unwrap();
|
||||
stmt.execute_named(&[(":name", &"one")]).unwrap();
|
||||
|
||||
let mut stmt = db
|
||||
.prepare("SELECT COUNT(*) FROM test WHERE name = :name")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
1i32,
|
||||
db.query_row_named::<i32, _>(
|
||||
"SELECT COUNT(*) FROM test WHERE name = :name",
|
||||
&[(":name", &"one")],
|
||||
|r| r.get(0)
|
||||
)
|
||||
.unwrap()
|
||||
stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@ -951,12 +1019,12 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "bundled")]
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
fn test_expanded_sql() {
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
let stmt = db.prepare("SELECT ?").unwrap();
|
||||
stmt.bind_parameter(&1, 1).unwrap();
|
||||
assert_eq!(Some("SELECT 1"), stmt.expanded_sql());
|
||||
assert_eq!(Some("SELECT 1".to_owned()), stmt.expanded_sql());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -983,7 +1051,7 @@ mod test {
|
||||
use std::collections::BTreeSet;
|
||||
let data: BTreeSet<String> = ["one", "two", "three"]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.map(|s| (*s).to_string())
|
||||
.collect();
|
||||
db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, String>(0))
|
||||
.unwrap();
|
||||
@ -994,4 +1062,35 @@ mod test {
|
||||
db.query_row("SELECT ?1, ?2, ?3", data.iter(), |row| row.get::<_, u8>(0))
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,9 @@ use crate::error::error_from_sqlite_code;
|
||||
use crate::{Connection, Result};
|
||||
|
||||
/// Set up the process-wide SQLite error logging callback.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is marked unsafe for two reasons:
|
||||
///
|
||||
/// * The function is not threadsafe. No other SQLite calls may be made while
|
||||
@ -122,6 +125,7 @@ impl Connection {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
|
||||
use chrono;
|
||||
|
||||
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::Result;
|
||||
@ -54,7 +53,7 @@ impl FromSql for NaiveTime {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
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)]
|
||||
mod test {
|
||||
use super::chrono::{
|
||||
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
|
||||
};
|
||||
use crate::{Connection, Result, NO_PARAMS};
|
||||
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
||||
|
||||
fn checked_memory_handle() -> Connection {
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
|
@ -36,7 +36,7 @@ impl PartialEq for FromSqlError {
|
||||
(FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
|
||||
#[cfg(feature = "uuid")]
|
||||
(FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
|
||||
(_, _) => false,
|
||||
(..) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -60,6 +60,7 @@ impl fmt::Display for FromSqlError {
|
||||
}
|
||||
|
||||
impl Error for FromSqlError {
|
||||
#[allow(deprecated)]
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
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> {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_blob().map(|b| b.to_vec())
|
||||
|
@ -42,10 +42,6 @@
|
||||
//! 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`
|
||||
@ -78,7 +74,7 @@ mod value_ref;
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// # use rusqlite::types::{Null};
|
||||
/// fn main() {}
|
||||
///
|
||||
/// fn insert_null(conn: &Connection) -> Result<usize> {
|
||||
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[Null])
|
||||
/// }
|
||||
@ -229,7 +225,7 @@ mod test {
|
||||
fn test_mismatched_types() {
|
||||
fn is_invalid_column_type(err: Error) -> bool {
|
||||
match err {
|
||||
Error::InvalidColumnType(_, _) => true,
|
||||
Error::InvalidColumnType(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
//! `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::Result;
|
||||
@ -17,7 +16,7 @@ impl ToSql for Value {
|
||||
impl FromSql for Value {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
ValueRef::Text(s) => serde_json::from_str(s),
|
||||
ValueRef::Text(s) => serde_json::from_slice(s),
|
||||
ValueRef::Blob(b) => serde_json::from_slice(b),
|
||||
_ => return Err(FromSqlError::InvalidType),
|
||||
}
|
||||
@ -27,9 +26,9 @@ impl FromSql for Value {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::serde_json;
|
||||
use crate::types::ToSql;
|
||||
use crate::{Connection, NO_PARAMS};
|
||||
use serde_json;
|
||||
|
||||
fn checked_memory_handle() -> Connection {
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
|
@ -36,8 +36,8 @@ impl FromSql for time::Timespec {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::time;
|
||||
use crate::{Connection, Result, NO_PARAMS};
|
||||
use time;
|
||||
|
||||
fn checked_memory_handle() -> Connection {
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
|
@ -14,10 +14,12 @@ impl ToSql for Url {
|
||||
impl FromSql for Url {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
ValueRef::Text(s) => Url::parse(s),
|
||||
_ => return Err(FromSqlError::InvalidType),
|
||||
ValueRef::Text(s) => {
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
pub fn data_type(&self) -> Type {
|
||||
match *self {
|
||||
|
@ -14,7 +14,7 @@ pub enum ValueRef<'a> {
|
||||
/// The value is a floating point number.
|
||||
Real(f64),
|
||||
/// The value is a text string.
|
||||
Text(&'a str),
|
||||
Text(&'a [u8]),
|
||||
/// The value is a blob of data
|
||||
Blob(&'a [u8]),
|
||||
}
|
||||
@ -54,7 +54,9 @@ impl<'a> ValueRef<'a> {
|
||||
/// `Err(Error::InvalidColumnType)`.
|
||||
pub fn as_str(&self) -> FromSqlResult<&'a str> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
@ -75,7 +77,10 @@ impl From<ValueRef<'_>> for Value {
|
||||
ValueRef::Null => Value::Null,
|
||||
ValueRef::Integer(i) => Value::Integer(i),
|
||||
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()),
|
||||
}
|
||||
}
|
||||
@ -83,7 +88,7 @@ impl From<ValueRef<'_>> for Value {
|
||||
|
||||
impl<'a> From<&'a str> for ValueRef<'a> {
|
||||
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::Integer(i) => ValueRef::Integer(i),
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"))]
|
||||
impl<'a> 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);
|
||||
|
||||
// sqlite3_value_text returns UTF8 data, so our unwrap here should be fine.
|
||||
let s = s
|
||||
.to_str()
|
||||
.expect("sqlite3_value_text returned invalid UTF-8");
|
||||
let s = s.to_bytes();
|
||||
ValueRef::Text(s)
|
||||
}
|
||||
ffi::SQLITE_BLOB => {
|
||||
|
@ -17,6 +17,7 @@ struct UnlockNotification {
|
||||
}
|
||||
|
||||
#[cfg(feature = "unlock_notify")]
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
impl UnlockNotification {
|
||||
fn new() -> UnlockNotification {
|
||||
UnlockNotification {
|
||||
|
@ -35,7 +35,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
|
||||
conn.create_module("rarray", &ARRAY_MODULE, aux)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
lazy_static::lazy_static! {
|
||||
static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1);
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
|
||||
conn.create_module("csv", &CSV_MODULE, aux)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
lazy_static::lazy_static! {
|
||||
static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1);
|
||||
}
|
||||
|
||||
@ -235,7 +235,7 @@ impl VTab for CSVTab {
|
||||
schema = Some(sql);
|
||||
}
|
||||
|
||||
Ok((schema.unwrap().to_owned(), vtab))
|
||||
Ok((schema.unwrap(), vtab))
|
||||
}
|
||||
|
||||
// Only a forward full table scan is supported.
|
||||
@ -338,8 +338,7 @@ impl VTabCursor for CSVTabCursor {
|
||||
|
||||
impl From<csv::Error> for Error {
|
||||
fn from(err: csv::Error) -> Error {
|
||||
use std::error::Error as StdError;
|
||||
Error::ModuleError(String::from(err.description()))
|
||||
Error::ModuleError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
115
src/vtab/mod.rs
115
src/vtab/mod.rs
@ -67,8 +67,17 @@ pub struct Module<T: VTab> {
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: VTab> Send 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.
|
||||
///
|
||||
/// 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,
|
||||
xRelease: None,
|
||||
xRollbackTo: None,
|
||||
#[cfg(any(feature = "bundled", feature = "vtab_v3"))]
|
||||
xShadowName: None,
|
||||
..zeroed_module()
|
||||
};
|
||||
Module {
|
||||
base: ffi_module,
|
||||
@ -139,8 +147,7 @@ pub fn eponymous_only_module<T: VTab>(version: c_int) -> Module<T> {
|
||||
xSavepoint: None,
|
||||
xRelease: None,
|
||||
xRollbackTo: None,
|
||||
#[cfg(any(feature = "bundled", feature = "vtab_v3"))]
|
||||
xShadowName: None,
|
||||
..zeroed_module()
|
||||
};
|
||||
Module {
|
||||
base: ffi_module,
|
||||
@ -161,7 +168,11 @@ impl VTabConnection {
|
||||
///
|
||||
/// You should not need to use this function. If you do need to, please
|
||||
/// [open an issue on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe
|
||||
/// your use case. This function is unsafe because it gives you raw access
|
||||
/// 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
|
||||
/// safety of this `Connection`.
|
||||
pub unsafe fn handle(&mut self) -> *mut ffi::sqlite3 {
|
||||
@ -233,16 +244,46 @@ pub trait CreateVTab: VTab {
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[doc = "Index constraint operator."]
|
||||
#[repr(C)]
|
||||
pub struct IndexConstraintOp: ::std::os::raw::c_uchar {
|
||||
const SQLITE_INDEX_CONSTRAINT_EQ = 2;
|
||||
const SQLITE_INDEX_CONSTRAINT_GT = 4;
|
||||
const SQLITE_INDEX_CONSTRAINT_LE = 8;
|
||||
const SQLITE_INDEX_CONSTRAINT_LT = 16;
|
||||
const SQLITE_INDEX_CONSTRAINT_GE = 32;
|
||||
const SQLITE_INDEX_CONSTRAINT_MATCH = 64;
|
||||
///Index constraint operator.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[allow(non_snake_case, non_camel_case_types)]
|
||||
pub enum IndexConstraintOp {
|
||||
SQLITE_INDEX_CONSTRAINT_EQ,
|
||||
SQLITE_INDEX_CONSTRAINT_GT,
|
||||
SQLITE_INDEX_CONSTRAINT_LE,
|
||||
SQLITE_INDEX_CONSTRAINT_LT,
|
||||
SQLITE_INDEX_CONSTRAINT_GE,
|
||||
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
|
||||
#[cfg(feature = "bundled")] // SQLite >= 3.8.2
|
||||
/// Estimated number of rows returned.
|
||||
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.8.2
|
||||
pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
|
||||
unsafe {
|
||||
(*self.0).estimatedRows = estimated_rows;
|
||||
@ -345,7 +386,7 @@ impl IndexConstraint<'_> {
|
||||
|
||||
/// Constraint operator
|
||||
pub fn operator(&self) -> IndexConstraintOp {
|
||||
IndexConstraintOp::from_bits_truncate(self.0.op)
|
||||
IndexConstraintOp::from(self.0.op)
|
||||
}
|
||||
|
||||
/// True if this constraint is usable
|
||||
@ -472,7 +513,9 @@ impl Values<'_> {
|
||||
}
|
||||
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
||||
#[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")]
|
||||
FromSqlError::InvalidUuidSize(_) => {
|
||||
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
|
||||
@ -643,9 +686,7 @@ unsafe extern "C" fn rust_create<T>(
|
||||
where
|
||||
T: CreateVTab,
|
||||
{
|
||||
use std::error::Error as StdError;
|
||||
use std::ffi::CStr;
|
||||
use std::slice;
|
||||
|
||||
let mut conn = VTabConnection(db);
|
||||
let aux = aux as *mut T::Aux;
|
||||
@ -664,12 +705,12 @@ where
|
||||
ffi::SQLITE_OK
|
||||
} else {
|
||||
let err = error_from_sqlite_code(rc, None);
|
||||
*err_msg = mprintf(err.description());
|
||||
*err_msg = mprintf(&err.to_string());
|
||||
rc
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
*err_msg = mprintf(err.description());
|
||||
*err_msg = mprintf(&err.to_string());
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
},
|
||||
@ -680,7 +721,7 @@ where
|
||||
err.extended_code
|
||||
}
|
||||
Err(err) => {
|
||||
*err_msg = mprintf(err.description());
|
||||
*err_msg = mprintf(&err.to_string());
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
}
|
||||
@ -697,9 +738,7 @@ unsafe extern "C" fn rust_connect<T>(
|
||||
where
|
||||
T: VTab,
|
||||
{
|
||||
use std::error::Error as StdError;
|
||||
use std::ffi::CStr;
|
||||
use std::slice;
|
||||
|
||||
let mut conn = VTabConnection(db);
|
||||
let aux = aux as *mut T::Aux;
|
||||
@ -718,12 +757,12 @@ where
|
||||
ffi::SQLITE_OK
|
||||
} else {
|
||||
let err = error_from_sqlite_code(rc, None);
|
||||
*err_msg = mprintf(err.description());
|
||||
*err_msg = mprintf(&err.to_string());
|
||||
rc
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
*err_msg = mprintf(err.description());
|
||||
*err_msg = mprintf(&err.to_string());
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
},
|
||||
@ -734,7 +773,7 @@ where
|
||||
err.extended_code
|
||||
}
|
||||
Err(err) => {
|
||||
*err_msg = mprintf(err.description());
|
||||
*err_msg = mprintf(&err.to_string());
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
}
|
||||
@ -747,7 +786,6 @@ unsafe extern "C" fn rust_best_index<T>(
|
||||
where
|
||||
T: VTab,
|
||||
{
|
||||
use std::error::Error as StdError;
|
||||
let vt = vtab as *mut T;
|
||||
let mut idx_info = IndexInfo(info);
|
||||
match (*vt).best_index(&mut idx_info) {
|
||||
@ -759,7 +797,7 @@ where
|
||||
err.extended_code
|
||||
}
|
||||
Err(err) => {
|
||||
set_err_msg(vtab, err.description());
|
||||
set_err_msg(vtab, &err.to_string());
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
}
|
||||
@ -781,7 +819,6 @@ unsafe extern "C" fn rust_destroy<T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
|
||||
where
|
||||
T: CreateVTab,
|
||||
{
|
||||
use std::error::Error as StdError;
|
||||
if vtab.is_null() {
|
||||
return ffi::SQLITE_OK;
|
||||
}
|
||||
@ -798,7 +835,7 @@ where
|
||||
err.extended_code
|
||||
}
|
||||
Err(err) => {
|
||||
set_err_msg(vtab, err.description());
|
||||
set_err_msg(vtab, &err.to_string());
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
}
|
||||
@ -811,7 +848,6 @@ unsafe extern "C" fn rust_open<T>(
|
||||
where
|
||||
T: VTab,
|
||||
{
|
||||
use std::error::Error as StdError;
|
||||
let vt = vtab as *mut T;
|
||||
match (*vt).open() {
|
||||
Ok(cursor) => {
|
||||
@ -826,7 +862,7 @@ where
|
||||
err.extended_code
|
||||
}
|
||||
Err(err) => {
|
||||
set_err_msg(vtab, err.description());
|
||||
set_err_msg(vtab, &err.to_string());
|
||||
ffi::SQLITE_ERROR
|
||||
}
|
||||
}
|
||||
@ -852,7 +888,6 @@ where
|
||||
C: VTabCursor,
|
||||
{
|
||||
use std::ffi::CStr;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
let idx_name = if idx_str.is_null() {
|
||||
None
|
||||
@ -915,7 +950,6 @@ where
|
||||
/// Virtual table cursors can set an error message by assigning a string to
|
||||
/// `zErrMsg`.
|
||||
unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
|
||||
use std::error::Error as StdError;
|
||||
match result {
|
||||
Ok(_) => ffi::SQLITE_OK,
|
||||
Err(Error::SqliteFailure(err, s)) => {
|
||||
@ -925,7 +959,7 @@ unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<
|
||||
err.extended_code
|
||||
}
|
||||
Err(err) => {
|
||||
set_err_msg((*cursor).pVtab, err.description());
|
||||
set_err_msg((*cursor).pVtab, &err.to_string());
|
||||
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
|
||||
/// error message and return the error code.
|
||||
unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int {
|
||||
use std::error::Error as StdError;
|
||||
match result {
|
||||
Ok(_) => ffi::SQLITE_OK,
|
||||
Err(Error::SqliteFailure(err, s)) => {
|
||||
@ -965,7 +998,7 @@ unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) ->
|
||||
}
|
||||
Err(err) => {
|
||||
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::SQLITE_ERROR
|
||||
@ -985,7 +1018,7 @@ fn mprintf(err_msg: &str) -> *mut c_char {
|
||||
pub mod array;
|
||||
#[cfg(feature = "csvtab")]
|
||||
pub mod csvtab;
|
||||
#[cfg(feature = "bundled")]
|
||||
#[cfg(feature = "series")]
|
||||
pub mod series; // SQLite >= 3.9.0
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -18,7 +18,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ const SERIES_COLUMN_START: c_int = 1;
|
||||
const SERIES_COLUMN_STOP: c_int = 2;
|
||||
const SERIES_COLUMN_STEP: c_int = 3;
|
||||
|
||||
bitflags! {
|
||||
bitflags::bitflags! {
|
||||
#[repr(C)]
|
||||
struct QueryPlanFlags: ::std::os::raw::c_int {
|
||||
// start = $value -- constraint exists
|
||||
|
@ -2,12 +2,9 @@
|
||||
//! function affects SQLite process-wide and so is not safe to run as a normal
|
||||
//! #[test] in the library.
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
fn main() {
|
||||
use lazy_static::lazy_static;
|
||||
use std::os::raw::c_int;
|
||||
use std::sync::Mutex;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user