mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 00:39: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 sqlcipher
|
||||||
- cargo build --features "bundled sqlcipher"
|
- cargo build --features "bundled sqlcipher"
|
||||||
- cargo test
|
- cargo test
|
||||||
- cargo test --features backup
|
- cargo test --features "backup blob extra_check"
|
||||||
- cargo test --features blob
|
- cargo test --features "collation functions"
|
||||||
- cargo test --features functions
|
- cargo test --features "hooks limits"
|
||||||
- cargo test --features hooks
|
|
||||||
- cargo test --features limits
|
|
||||||
- cargo test --features load_extension
|
- cargo test --features load_extension
|
||||||
- cargo test --features trace
|
- cargo test --features trace
|
||||||
- cargo test --features chrono
|
- cargo test --features chrono
|
||||||
@ -42,9 +40,9 @@ script:
|
|||||||
- cargo test --features sqlcipher
|
- cargo test --features sqlcipher
|
||||||
- cargo test --features i128_blob
|
- cargo test --features i128_blob
|
||||||
- cargo test --features uuid
|
- cargo test --features uuid
|
||||||
- cargo test --features "unlock_notify bundled"
|
- cargo test --features "bundled unlock_notify window"
|
||||||
- cargo test --features "array bundled csvtab vtab"
|
- cargo test --features "array bundled csvtab series vtab"
|
||||||
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url uuid vtab"
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab"
|
||||||
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url uuid vtab buildtime_bindgen"
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab buildtime_bindgen"
|
||||||
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled"
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled"
|
||||||
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled buildtime_bindgen"
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension session serde_json trace url uuid vtab bundled buildtime_bindgen"
|
||||||
|
25
Cargo.toml
25
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.18.0"
|
version = "0.21.0"
|
||||||
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Ergonomic wrapper for SQLite"
|
description = "Ergonomic wrapper for SQLite"
|
||||||
@ -28,11 +28,12 @@ load_extension = []
|
|||||||
backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
|
backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
|
||||||
# sqlite3_blob_reopen: 3.7.4
|
# sqlite3_blob_reopen: 3.7.4
|
||||||
blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
||||||
|
collation = []
|
||||||
# sqlite3_create_function_v2: 3.7.3 (2010-10-08)
|
# sqlite3_create_function_v2: 3.7.3 (2010-10-08)
|
||||||
functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
||||||
# sqlite3_log: 3.6.23 (2010-03-09)
|
# sqlite3_log: 3.6.23 (2010-03-09)
|
||||||
trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
|
trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
|
||||||
bundled = ["libsqlite3-sys/bundled"]
|
bundled = ["libsqlite3-sys/bundled", "modern_sqlite"]
|
||||||
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
|
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
|
||||||
limits = []
|
limits = []
|
||||||
hooks = []
|
hooks = []
|
||||||
@ -48,6 +49,13 @@ csvtab = ["csv", "vtab"]
|
|||||||
array = ["vtab"]
|
array = ["vtab"]
|
||||||
# session extension: 3.13.0
|
# session extension: 3.13.0
|
||||||
session = ["libsqlite3-sys/session", "hooks"]
|
session = ["libsqlite3-sys/session", "hooks"]
|
||||||
|
# window functions: 3.25.0
|
||||||
|
window = ["functions"]
|
||||||
|
# 3.9.0
|
||||||
|
series = ["vtab"]
|
||||||
|
# check for invalid query.
|
||||||
|
extra_check = []
|
||||||
|
modern_sqlite = ["libsqlite3-sys/bundled_bindings"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
time = "0.1.0"
|
time = "0.1.0"
|
||||||
@ -56,23 +64,24 @@ lru-cache = "0.1"
|
|||||||
chrono = { version = "0.4", optional = true }
|
chrono = { version = "0.4", optional = true }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0", optional = true }
|
||||||
csv = { version = "1.0", optional = true }
|
csv = { version = "1.0", optional = true }
|
||||||
url = { version = "1.7", optional = true }
|
url = { version = "2.0", optional = true }
|
||||||
lazy_static = { version = "1.0", optional = true }
|
lazy_static = { version = "1.0", optional = true }
|
||||||
byteorder = { version = "1.2", features = ["i128"], optional = true }
|
byteorder = { version = "1.2", features = ["i128"], optional = true }
|
||||||
fallible-iterator = "0.2"
|
fallible-iterator = "0.2"
|
||||||
fallible-streaming-iterator = "0.1"
|
fallible-streaming-iterator = "0.1"
|
||||||
memchr = "2.2.0"
|
memchr = "2.2.0"
|
||||||
uuid = { version = "0.7", optional = true }
|
uuid = { version = "0.8", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3"
|
tempfile = "3.1.0"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
regex = "1.0"
|
regex = "1.0"
|
||||||
uuid = { version = "0.7", features = ["v4"] }
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
|
unicase = "2.4.0"
|
||||||
|
|
||||||
[dependencies.libsqlite3-sys]
|
[dependencies.libsqlite3-sys]
|
||||||
path = "libsqlite3-sys"
|
path = "libsqlite3-sys"
|
||||||
version = "0.14"
|
version = "0.18"
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "config_log"
|
name = "config_log"
|
||||||
@ -85,7 +94,7 @@ name = "deny_single_threaded_sqlite_config"
|
|||||||
name = "vtab"
|
name = "vtab"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace", "url", "vtab" ]
|
features = [ "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "trace", "url", "vtab", "window", "modern_sqlite" ]
|
||||||
all-features = false
|
all-features = false
|
||||||
no-default-features = true
|
no-default-features = true
|
||||||
default-target = "x86_64-unknown-linux-gnu"
|
default-target = "x86_64-unknown-linux-gnu"
|
||||||
|
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).
|
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rusqlite::types::ToSql;
|
use rusqlite::{params, Connection, Result};
|
||||||
use rusqlite::{Connection, Result, NO_PARAMS};
|
|
||||||
use time::Timespec;
|
use time::Timespec;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -32,7 +31,7 @@ fn main() -> Result<()> {
|
|||||||
time_created TEXT NOT NULL,
|
time_created TEXT NOT NULL,
|
||||||
data BLOB
|
data BLOB
|
||||||
)",
|
)",
|
||||||
NO_PARAMS,
|
params![],
|
||||||
)?;
|
)?;
|
||||||
let me = Person {
|
let me = Person {
|
||||||
id: 0,
|
id: 0,
|
||||||
@ -43,18 +42,18 @@ fn main() -> Result<()> {
|
|||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO person (name, time_created, data)
|
"INSERT INTO person (name, time_created, data)
|
||||||
VALUES (?1, ?2, ?3)",
|
VALUES (?1, ?2, ?3)",
|
||||||
&[&me.name as &ToSql, &me.time_created, &me.data],
|
params![me.name, me.time_created, me.data],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut stmt = conn
|
let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person")?;
|
||||||
.prepare("SELECT id, name, time_created, data FROM person")?;
|
let person_iter = stmt.query_map(params![], |row| {
|
||||||
let person_iter = stmt
|
Ok(Person {
|
||||||
.query_map(NO_PARAMS, |row| Ok(Person {
|
|
||||||
id: row.get(0)?,
|
id: row.get(0)?,
|
||||||
name: row.get(1)?,
|
name: row.get(1)?,
|
||||||
time_created: row.get(2)?,
|
time_created: row.get(2)?,
|
||||||
data: row.get(3)?,
|
data: row.get(3)?,
|
||||||
}))?;
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
for person in person_iter {
|
for person in person_iter {
|
||||||
println!("Found person {:?}", person.unwrap());
|
println!("Found person {:?}", person.unwrap());
|
||||||
@ -118,13 +117,13 @@ declarations for SQLite's C API. By default, `libsqlite3-sys` attempts to find a
|
|||||||
You can adjust this behavior in a number of ways:
|
You can adjust this behavior in a number of ways:
|
||||||
|
|
||||||
* If you use the `bundled` feature, `libsqlite3-sys` will use the
|
* If you use the `bundled` feature, `libsqlite3-sys` will use the
|
||||||
[gcc](https://crates.io/crates/gcc) crate to compile SQLite from source and
|
[cc](https://crates.io/crates/cc) crate to compile SQLite from source and
|
||||||
link against that. This source is embedded in the `libsqlite3-sys` crate and
|
link against that. This source is embedded in the `libsqlite3-sys` crate and
|
||||||
is currently SQLite 3.27.2 (as of `rusqlite` 0.18.0 / `libsqlite3-sys`
|
is currently SQLite 3.30.1 (as of `rusqlite` 0.21.0 / `libsqlite3-sys`
|
||||||
0.14.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
|
0.17.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
|
||||||
```
|
```
|
||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
version = "0.18.0"
|
version = "0.21.0"
|
||||||
features = ["bundled"]
|
features = ["bundled"]
|
||||||
```
|
```
|
||||||
* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite
|
* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite
|
||||||
@ -166,6 +165,12 @@ bundled version of SQLite. If you need other specific pregenerated binding
|
|||||||
versions, please file an issue. If you want to run `bindgen` at buildtime to
|
versions, please file an issue. If you want to run `bindgen` at buildtime to
|
||||||
produce your own bindings, use the `buildtime_bindgen` Cargo feature.
|
produce your own bindings, use the `buildtime_bindgen` Cargo feature.
|
||||||
|
|
||||||
|
If you enable the `modern_sqlite` feature, we'll use the bindings we would have
|
||||||
|
included with the bundled build. You generally should have `buildtime_bindgen`
|
||||||
|
enabled if you turn this on, as otherwise you'll need to keep the version of
|
||||||
|
SQLite you link with in sync with what rusqlite would have bundled, (usually the
|
||||||
|
most recent release of sqlite). Failing to do this will cause a runtime error.
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
||||||
John Gallagher, johnkgallagher@gmail.com
|
John Gallagher, johnkgallagher@gmail.com
|
||||||
|
@ -2,9 +2,9 @@ environment:
|
|||||||
matrix:
|
matrix:
|
||||||
- TARGET: x86_64-pc-windows-gnu
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
MSYS2_BITS: 64
|
MSYS2_BITS: 64
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
# - TARGET: x86_64-pc-windows-msvc
|
||||||
VCPKG_DEFAULT_TRIPLET: x64-windows
|
# VCPKG_DEFAULT_TRIPLET: x64-windows
|
||||||
VCPKGRS_DYNAMIC: 1
|
# VCPKGRS_DYNAMIC: 1
|
||||||
# - TARGET: x86_64-pc-windows-msvc
|
# - TARGET: x86_64-pc-windows-msvc
|
||||||
# VCPKG_DEFAULT_TRIPLET: x64-windows-static
|
# VCPKG_DEFAULT_TRIPLET: x64-windows-static
|
||||||
# RUSTFLAGS: -Ctarget-feature=+crt-static
|
# RUSTFLAGS: -Ctarget-feature=+crt-static
|
||||||
@ -33,7 +33,7 @@ build: false
|
|||||||
test_script:
|
test_script:
|
||||||
- cargo test --lib --verbose
|
- cargo test --lib --verbose
|
||||||
- cargo test --lib --verbose --features bundled
|
- cargo test --lib --verbose --features bundled
|
||||||
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace"
|
- cargo test --lib --features "backup blob chrono collation functions hooks limits load_extension serde_json trace"
|
||||||
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
|
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
|
||||||
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
|
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
|
||||||
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
|
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.14.0"
|
version = "0.18.0"
|
||||||
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
repository = "https://github.com/jgallagher/rusqlite"
|
repository = "https://github.com/jgallagher/rusqlite"
|
||||||
@ -13,13 +13,17 @@ categories = ["external-ffi-bindings"]
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["min_sqlite_version_3_6_8"]
|
default = ["min_sqlite_version_3_6_8"]
|
||||||
bundled = ["cc"]
|
bundled = ["cc", "bundled_bindings"]
|
||||||
|
bundled-windows = ["cc", "bundled_bindings"]
|
||||||
buildtime_bindgen = ["bindgen", "pkg-config", "vcpkg"]
|
buildtime_bindgen = ["bindgen", "pkg-config", "vcpkg"]
|
||||||
sqlcipher = []
|
sqlcipher = []
|
||||||
min_sqlite_version_3_6_8 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_6_8 = ["pkg-config", "vcpkg"]
|
||||||
min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"]
|
||||||
min_sqlite_version_3_7_7 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_7_7 = ["pkg-config", "vcpkg"]
|
||||||
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
|
||||||
|
# Bundle only the bindings file. Note that this does nothing if
|
||||||
|
# `buildtime_bindgen` is enabled.
|
||||||
|
bundled_bindings = []
|
||||||
# sqlite3_unlock_notify >= 3.6.12
|
# sqlite3_unlock_notify >= 3.6.12
|
||||||
unlock_notify = []
|
unlock_notify = []
|
||||||
# 3.13.0
|
# 3.13.0
|
||||||
@ -28,7 +32,7 @@ preupdate_hook = []
|
|||||||
session = ["preupdate_hook"]
|
session = ["preupdate_hook"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = { version = "0.49", optional = true }
|
bindgen = { version = "0.53", optional = true, default-features = false, features = ["runtime"] }
|
||||||
pkg-config = { version = "0.3", optional = true }
|
pkg-config = { version = "0.3", optional = true }
|
||||||
cc = { version = "1.0", optional = true }
|
cc = { version = "1.0", optional = true }
|
||||||
|
|
||||||
|
@ -5,7 +5,10 @@ fn main() {
|
|||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
let out_dir = env::var("OUT_DIR").unwrap();
|
||||||
let out_path = Path::new(&out_dir).join("bindgen.rs");
|
let out_path = Path::new(&out_dir).join("bindgen.rs");
|
||||||
if cfg!(feature = "sqlcipher") {
|
if cfg!(feature = "sqlcipher") {
|
||||||
if cfg!(feature = "bundled") {
|
if cfg!(any(
|
||||||
|
feature = "bundled",
|
||||||
|
all(windows, feature = "bundled-windows")
|
||||||
|
)) {
|
||||||
println!(
|
println!(
|
||||||
"cargo:warning={}",
|
"cargo:warning={}",
|
||||||
"Builds with bundled SQLCipher are not supported. Searching for SQLCipher to link against. \
|
"Builds with bundled SQLCipher are not supported. Searching for SQLCipher to link against. \
|
||||||
@ -13,21 +16,23 @@ fn main() {
|
|||||||
}
|
}
|
||||||
build_linked::main(&out_dir, &out_path)
|
build_linked::main(&out_dir, &out_path)
|
||||||
} else {
|
} else {
|
||||||
// This can't be `cfg!` without always requiring our `mod build_bundled` (and thus `cc`)
|
// This can't be `cfg!` without always requiring our `mod build_bundled` (and
|
||||||
#[cfg(feature = "bundled")]
|
// thus `cc`)
|
||||||
|
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
|
||||||
{
|
{
|
||||||
build_bundled::main(&out_dir, &out_path)
|
build_bundled::main(&out_dir, &out_path)
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(any(feature = "bundled", all(windows, feature = "bundled-windows"))))]
|
||||||
{
|
{
|
||||||
build_linked::main(&out_dir, &out_path)
|
build_linked::main(&out_dir, &out_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
|
||||||
mod build_bundled {
|
mod build_bundled {
|
||||||
use cc;
|
use cc;
|
||||||
|
use std::env;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub fn main(out_dir: &str, out_path: &Path) {
|
pub fn main(out_dir: &str, out_path: &Path) {
|
||||||
@ -65,11 +70,29 @@ mod build_bundled {
|
|||||||
.flag("-DSQLITE_ENABLE_RTREE")
|
.flag("-DSQLITE_ENABLE_RTREE")
|
||||||
.flag("-DSQLITE_ENABLE_STAT2")
|
.flag("-DSQLITE_ENABLE_STAT2")
|
||||||
.flag("-DSQLITE_ENABLE_STAT4")
|
.flag("-DSQLITE_ENABLE_STAT4")
|
||||||
.flag("-DSQLITE_HAVE_ISNAN")
|
|
||||||
.flag("-DSQLITE_SOUNDEX")
|
.flag("-DSQLITE_SOUNDEX")
|
||||||
.flag("-DSQLITE_THREADSAFE=1")
|
.flag("-DSQLITE_THREADSAFE=1")
|
||||||
.flag("-DSQLITE_USE_URI")
|
.flag("-DSQLITE_USE_URI")
|
||||||
.flag("-DHAVE_USLEEP=1");
|
.flag("-DHAVE_USLEEP=1");
|
||||||
|
// Older versions of visual studio don't support c99 (including isnan), which
|
||||||
|
// causes a build failure when the linker fails to find the `isnan`
|
||||||
|
// function. `sqlite` provides its own implmentation, using the fact
|
||||||
|
// that x != x when x is NaN.
|
||||||
|
//
|
||||||
|
// There may be other platforms that don't support `isnan`, they should be
|
||||||
|
// tested for here.
|
||||||
|
if cfg!(target_env = "msvc") {
|
||||||
|
use cc::windows_registry::{find_vs_version, VsVers};
|
||||||
|
let vs_has_nan = match find_vs_version() {
|
||||||
|
Ok(ver) => ver != VsVers::Vs12,
|
||||||
|
Err(_msg) => false,
|
||||||
|
};
|
||||||
|
if vs_has_nan {
|
||||||
|
cfg.flag("-DSQLITE_HAVE_ISNAN");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.flag("-DSQLITE_HAVE_ISNAN");
|
||||||
|
}
|
||||||
if cfg!(feature = "unlock_notify") {
|
if cfg!(feature = "unlock_notify") {
|
||||||
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
|
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
|
||||||
}
|
}
|
||||||
@ -79,6 +102,17 @@ mod build_bundled {
|
|||||||
if cfg!(feature = "session") {
|
if cfg!(feature = "session") {
|
||||||
cfg.flag("-DSQLITE_ENABLE_SESSION");
|
cfg.flag("-DSQLITE_ENABLE_SESSION");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(limit) = env::var("SQLITE_MAX_VARIABLE_NUMBER") {
|
||||||
|
cfg.flag(&format!("-DSQLITE_MAX_VARIABLE_NUMBER={}", limit));
|
||||||
|
}
|
||||||
|
println!("cargo:rerun-if-env-changed=SQLITE_MAX_VARIABLE_NUMBER");
|
||||||
|
|
||||||
|
if let Ok(limit) = env::var("SQLITE_MAX_EXPR_DEPTH") {
|
||||||
|
cfg.flag(&format!("-DSQLITE_MAX_EXPR_DEPTH={}", limit));
|
||||||
|
}
|
||||||
|
println!("cargo:rerun-if-env-changed=SQLITE_MAX_EXPR_DEPTH");
|
||||||
|
|
||||||
cfg.compile("libsqlite3.a");
|
cfg.compile("libsqlite3.a");
|
||||||
|
|
||||||
println!("cargo:lib_dir={}", out_dir);
|
println!("cargo:lib_dir={}", out_dir);
|
||||||
@ -104,10 +138,12 @@ impl From<HeaderLocation> for String {
|
|||||||
match header {
|
match header {
|
||||||
HeaderLocation::FromEnvironment => {
|
HeaderLocation::FromEnvironment => {
|
||||||
let prefix = env_prefix();
|
let prefix = env_prefix();
|
||||||
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!(
|
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).unwrap_or_else(|_| {
|
||||||
|
panic!(
|
||||||
"{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
|
"{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
|
||||||
prefix, prefix
|
prefix, prefix
|
||||||
));
|
)
|
||||||
|
});
|
||||||
header.push_str("/sqlite3.h");
|
header.push_str("/sqlite3.h");
|
||||||
header
|
header
|
||||||
}
|
}
|
||||||
@ -129,15 +165,18 @@ mod build_linked {
|
|||||||
|
|
||||||
pub fn main(_out_dir: &str, out_path: &Path) {
|
pub fn main(_out_dir: &str, out_path: &Path) {
|
||||||
let header = find_sqlite();
|
let header = find_sqlite();
|
||||||
if cfg!(feature = "bundled") && !cfg!(feature = "buildtime_bindgen") {
|
if cfg!(any(
|
||||||
// We can only get here if `bundled` and `sqlcipher` were both
|
feature = "bundled_bindings",
|
||||||
// specified (and `builtime_bindgen` was not). In order to keep
|
feature = "bundled",
|
||||||
// `rusqlite` relatively clean we hide the fact that `bundled` can
|
all(windows, feature = "bundled-windows")
|
||||||
// be ignored in some cases, and just use the bundled bindings, even
|
)) && !cfg!(feature = "buildtime_bindgen")
|
||||||
// though the library we found might not match their version.
|
{
|
||||||
// Ideally we'd perform a version check here, but doing so is rather
|
// Generally means the `bundled_bindings` feature is enabled
|
||||||
// tricky, since we might not have access to executables (and
|
// (there's also an edge case where we get here involving
|
||||||
// moreover, we might be cross compiling).
|
// sqlcipher). In either case most users are better off with turning
|
||||||
|
// on buildtime_bindgen instead, but this is still supported as we
|
||||||
|
// have runtime version checks and there are good reasons to not
|
||||||
|
// want to run bindgen.
|
||||||
std::fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
|
std::fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
|
||||||
.expect("Could not copy bindings to output directory");
|
.expect("Could not copy bindings to output directory");
|
||||||
} else {
|
} else {
|
||||||
@ -160,18 +199,22 @@ mod build_linked {
|
|||||||
println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix());
|
println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix());
|
||||||
println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix());
|
println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix());
|
||||||
println!("cargo:rerun-if-env-changed={}_STATIC", env_prefix());
|
println!("cargo:rerun-if-env-changed={}_STATIC", env_prefix());
|
||||||
if cfg!(target_os = "windows") {
|
|
||||||
println!("cargo:rerun-if-env-changed=PATH");
|
|
||||||
}
|
|
||||||
if cfg!(all(feature = "vcpkg", target_env = "msvc")) {
|
if cfg!(all(feature = "vcpkg", target_env = "msvc")) {
|
||||||
println!("cargo:rerun-if-env-changed=VCPKGRS_DYNAMIC");
|
println!("cargo:rerun-if-env-changed=VCPKGRS_DYNAMIC");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dependents can access `DEP_SQLITE3_LINK_TARGET` (`sqlite3` being the
|
||||||
|
// `links=` value in our Cargo.toml) to get this value. This might be
|
||||||
|
// useful if you need to ensure whatever crypto library sqlcipher relies
|
||||||
|
// on is available, for example.
|
||||||
|
println!("cargo:link-target={}", link_lib);
|
||||||
|
|
||||||
// Allow users to specify where to find SQLite.
|
// Allow users to specify where to find SQLite.
|
||||||
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
|
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
|
||||||
// Try to use pkg-config to determine link commands
|
// Try to use pkg-config to determine link commands
|
||||||
let pkgconfig_path = Path::new(&dir).join("pkgconfig");
|
let pkgconfig_path = Path::new(&dir).join("pkgconfig");
|
||||||
env::set_var("PKG_CONFIG_PATH", pkgconfig_path);
|
env::set_var("PKG_CONFIG_PATH", pkgconfig_path);
|
||||||
if let Err(_) = pkg_config::Config::new().probe(link_lib) {
|
if pkg_config::Config::new().probe(link_lib).is_err() {
|
||||||
// Otherwise just emit the bare minimum link commands.
|
// Otherwise just emit the bare minimum link commands.
|
||||||
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
|
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
|
||||||
println!("cargo:rustc-link-search={}", dir);
|
println!("cargo:rustc-link-search={}", dir);
|
||||||
@ -260,8 +303,8 @@ mod bindings {
|
|||||||
mod bindings {
|
mod bindings {
|
||||||
use bindgen;
|
use bindgen;
|
||||||
|
|
||||||
use self::bindgen::callbacks::{IntKind, ParseCallbacks};
|
|
||||||
use super::HeaderLocation;
|
use super::HeaderLocation;
|
||||||
|
use bindgen::callbacks::{IntKind, ParseCallbacks};
|
||||||
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@ -300,7 +343,7 @@ mod bindings {
|
|||||||
|
|
||||||
bindings
|
bindings
|
||||||
.generate()
|
.generate()
|
||||||
.expect(&format!("could not run bindgen on header {}", header))
|
.unwrap_or_else(|_| panic!("could not run bindgen on header {}", header))
|
||||||
.write(Box::new(&mut output))
|
.write(Box::new(&mut output))
|
||||||
.expect("could not write output of bindgen");
|
.expect("could not write output of bindgen");
|
||||||
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
|
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
|
||||||
@ -319,10 +362,10 @@ mod bindings {
|
|||||||
.write(true)
|
.write(true)
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.open(out_path.clone())
|
.open(out_path)
|
||||||
.expect(&format!("Could not write to {:?}", out_path));
|
.unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
|
||||||
|
|
||||||
file.write_all(output.as_bytes())
|
file.write_all(output.as_bytes())
|
||||||
.expect(&format!("Could not write to {:?}", out_path));
|
.unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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 */
|
/* automatically generated by rust-bindgen */
|
||||||
|
|
||||||
pub const __GNUC_VA_LIST: i32 = 1;
|
pub const __GNUC_VA_LIST: i32 = 1;
|
||||||
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.28.0\0";
|
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.31.0\0";
|
||||||
pub const SQLITE_VERSION_NUMBER: i32 = 3028000;
|
pub const SQLITE_VERSION_NUMBER: i32 = 3031000;
|
||||||
pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] =
|
pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] =
|
||||||
b"2019-04-16 19:49:53 884b4b7e502b4e991677b53971277adfaf0a04a284f8e483e2553d0f83156b50\0";
|
b"2020-01-22 18:38:59 f6affdd41608946fcfcea914ece149038a8b25a62bbe719ed2561c649b86d824\0";
|
||||||
pub const SQLITE_OK: i32 = 0;
|
pub const SQLITE_OK: i32 = 0;
|
||||||
pub const SQLITE_ERROR: i32 = 1;
|
pub const SQLITE_ERROR: i32 = 1;
|
||||||
pub const SQLITE_INTERNAL: i32 = 2;
|
pub const SQLITE_INTERNAL: i32 = 2;
|
||||||
@ -79,6 +79,7 @@ pub const SQLITE_CANTOPEN_ISDIR: i32 = 526;
|
|||||||
pub const SQLITE_CANTOPEN_FULLPATH: i32 = 782;
|
pub const SQLITE_CANTOPEN_FULLPATH: i32 = 782;
|
||||||
pub const SQLITE_CANTOPEN_CONVPATH: i32 = 1038;
|
pub const SQLITE_CANTOPEN_CONVPATH: i32 = 1038;
|
||||||
pub const SQLITE_CANTOPEN_DIRTYWAL: i32 = 1294;
|
pub const SQLITE_CANTOPEN_DIRTYWAL: i32 = 1294;
|
||||||
|
pub const SQLITE_CANTOPEN_SYMLINK: i32 = 1550;
|
||||||
pub const SQLITE_CORRUPT_VTAB: i32 = 267;
|
pub const SQLITE_CORRUPT_VTAB: i32 = 267;
|
||||||
pub const SQLITE_CORRUPT_SEQUENCE: i32 = 523;
|
pub const SQLITE_CORRUPT_SEQUENCE: i32 = 523;
|
||||||
pub const SQLITE_READONLY_RECOVERY: i32 = 264;
|
pub const SQLITE_READONLY_RECOVERY: i32 = 264;
|
||||||
@ -98,11 +99,13 @@ pub const SQLITE_CONSTRAINT_TRIGGER: i32 = 1811;
|
|||||||
pub const SQLITE_CONSTRAINT_UNIQUE: i32 = 2067;
|
pub const SQLITE_CONSTRAINT_UNIQUE: i32 = 2067;
|
||||||
pub const SQLITE_CONSTRAINT_VTAB: i32 = 2323;
|
pub const SQLITE_CONSTRAINT_VTAB: i32 = 2323;
|
||||||
pub const SQLITE_CONSTRAINT_ROWID: i32 = 2579;
|
pub const SQLITE_CONSTRAINT_ROWID: i32 = 2579;
|
||||||
|
pub const SQLITE_CONSTRAINT_PINNED: i32 = 2835;
|
||||||
pub const SQLITE_NOTICE_RECOVER_WAL: i32 = 283;
|
pub const SQLITE_NOTICE_RECOVER_WAL: i32 = 283;
|
||||||
pub const SQLITE_NOTICE_RECOVER_ROLLBACK: i32 = 539;
|
pub const SQLITE_NOTICE_RECOVER_ROLLBACK: i32 = 539;
|
||||||
pub const SQLITE_WARNING_AUTOINDEX: i32 = 284;
|
pub const SQLITE_WARNING_AUTOINDEX: i32 = 284;
|
||||||
pub const SQLITE_AUTH_USER: i32 = 279;
|
pub const SQLITE_AUTH_USER: i32 = 279;
|
||||||
pub const SQLITE_OK_LOAD_PERMANENTLY: i32 = 256;
|
pub const SQLITE_OK_LOAD_PERMANENTLY: i32 = 256;
|
||||||
|
pub const SQLITE_OK_SYMLINK: i32 = 512;
|
||||||
pub const SQLITE_OPEN_READONLY: i32 = 1;
|
pub const SQLITE_OPEN_READONLY: i32 = 1;
|
||||||
pub const SQLITE_OPEN_READWRITE: i32 = 2;
|
pub const SQLITE_OPEN_READWRITE: i32 = 2;
|
||||||
pub const SQLITE_OPEN_CREATE: i32 = 4;
|
pub const SQLITE_OPEN_CREATE: i32 = 4;
|
||||||
@ -123,6 +126,7 @@ pub const SQLITE_OPEN_FULLMUTEX: i32 = 65536;
|
|||||||
pub const SQLITE_OPEN_SHAREDCACHE: i32 = 131072;
|
pub const SQLITE_OPEN_SHAREDCACHE: i32 = 131072;
|
||||||
pub const SQLITE_OPEN_PRIVATECACHE: i32 = 262144;
|
pub const SQLITE_OPEN_PRIVATECACHE: i32 = 262144;
|
||||||
pub const SQLITE_OPEN_WAL: i32 = 524288;
|
pub const SQLITE_OPEN_WAL: i32 = 524288;
|
||||||
|
pub const SQLITE_OPEN_NOFOLLOW: i32 = 16777216;
|
||||||
pub const SQLITE_IOCAP_ATOMIC: i32 = 1;
|
pub const SQLITE_IOCAP_ATOMIC: i32 = 1;
|
||||||
pub const SQLITE_IOCAP_ATOMIC512: i32 = 2;
|
pub const SQLITE_IOCAP_ATOMIC512: i32 = 2;
|
||||||
pub const SQLITE_IOCAP_ATOMIC1K: i32 = 4;
|
pub const SQLITE_IOCAP_ATOMIC1K: i32 = 4;
|
||||||
@ -181,6 +185,7 @@ pub const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: i32 = 33;
|
|||||||
pub const SQLITE_FCNTL_LOCK_TIMEOUT: i32 = 34;
|
pub const SQLITE_FCNTL_LOCK_TIMEOUT: i32 = 34;
|
||||||
pub const SQLITE_FCNTL_DATA_VERSION: i32 = 35;
|
pub const SQLITE_FCNTL_DATA_VERSION: i32 = 35;
|
||||||
pub const SQLITE_FCNTL_SIZE_LIMIT: i32 = 36;
|
pub const SQLITE_FCNTL_SIZE_LIMIT: i32 = 36;
|
||||||
|
pub const SQLITE_FCNTL_CKPT_DONE: i32 = 37;
|
||||||
pub const SQLITE_GET_LOCKPROXYFILE: i32 = 2;
|
pub const SQLITE_GET_LOCKPROXYFILE: i32 = 2;
|
||||||
pub const SQLITE_SET_LOCKPROXYFILE: i32 = 3;
|
pub const SQLITE_SET_LOCKPROXYFILE: i32 = 3;
|
||||||
pub const SQLITE_LAST_ERRNO: i32 = 4;
|
pub const SQLITE_LAST_ERRNO: i32 = 4;
|
||||||
@ -232,7 +237,13 @@ pub const SQLITE_DBCONFIG_TRIGGER_EQP: i32 = 1008;
|
|||||||
pub const SQLITE_DBCONFIG_RESET_DATABASE: i32 = 1009;
|
pub const SQLITE_DBCONFIG_RESET_DATABASE: i32 = 1009;
|
||||||
pub const SQLITE_DBCONFIG_DEFENSIVE: i32 = 1010;
|
pub const SQLITE_DBCONFIG_DEFENSIVE: i32 = 1010;
|
||||||
pub const SQLITE_DBCONFIG_WRITABLE_SCHEMA: i32 = 1011;
|
pub const SQLITE_DBCONFIG_WRITABLE_SCHEMA: i32 = 1011;
|
||||||
pub const SQLITE_DBCONFIG_MAX: i32 = 1011;
|
pub const SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: i32 = 1012;
|
||||||
|
pub const SQLITE_DBCONFIG_DQS_DML: i32 = 1013;
|
||||||
|
pub const SQLITE_DBCONFIG_DQS_DDL: i32 = 1014;
|
||||||
|
pub const SQLITE_DBCONFIG_ENABLE_VIEW: i32 = 1015;
|
||||||
|
pub const SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: i32 = 1016;
|
||||||
|
pub const SQLITE_DBCONFIG_TRUSTED_SCHEMA: i32 = 1017;
|
||||||
|
pub const SQLITE_DBCONFIG_MAX: i32 = 1017;
|
||||||
pub const SQLITE_DENY: i32 = 1;
|
pub const SQLITE_DENY: i32 = 1;
|
||||||
pub const SQLITE_IGNORE: i32 = 2;
|
pub const SQLITE_IGNORE: i32 = 2;
|
||||||
pub const SQLITE_CREATE_INDEX: i32 = 1;
|
pub const SQLITE_CREATE_INDEX: i32 = 1;
|
||||||
@ -301,6 +312,9 @@ pub const SQLITE_UTF16: i32 = 4;
|
|||||||
pub const SQLITE_ANY: i32 = 5;
|
pub const SQLITE_ANY: i32 = 5;
|
||||||
pub const SQLITE_UTF16_ALIGNED: i32 = 8;
|
pub const SQLITE_UTF16_ALIGNED: i32 = 8;
|
||||||
pub const SQLITE_DETERMINISTIC: i32 = 2048;
|
pub const SQLITE_DETERMINISTIC: i32 = 2048;
|
||||||
|
pub const SQLITE_DIRECTONLY: i32 = 524288;
|
||||||
|
pub const SQLITE_SUBTYPE: i32 = 1048576;
|
||||||
|
pub const SQLITE_INNOCUOUS: i32 = 2097152;
|
||||||
pub const SQLITE_WIN32_DATA_DIRECTORY_TYPE: i32 = 1;
|
pub const SQLITE_WIN32_DATA_DIRECTORY_TYPE: i32 = 1;
|
||||||
pub const SQLITE_WIN32_TEMP_DIRECTORY_TYPE: i32 = 2;
|
pub const SQLITE_WIN32_TEMP_DIRECTORY_TYPE: i32 = 2;
|
||||||
pub const SQLITE_INDEX_SCAN_UNIQUE: i32 = 1;
|
pub const SQLITE_INDEX_SCAN_UNIQUE: i32 = 1;
|
||||||
@ -360,7 +374,10 @@ pub const SQLITE_TESTCTRL_ISINIT: i32 = 23;
|
|||||||
pub const SQLITE_TESTCTRL_SORTER_MMAP: i32 = 24;
|
pub const SQLITE_TESTCTRL_SORTER_MMAP: i32 = 24;
|
||||||
pub const SQLITE_TESTCTRL_IMPOSTER: i32 = 25;
|
pub const SQLITE_TESTCTRL_IMPOSTER: i32 = 25;
|
||||||
pub const SQLITE_TESTCTRL_PARSER_COVERAGE: i32 = 26;
|
pub const SQLITE_TESTCTRL_PARSER_COVERAGE: i32 = 26;
|
||||||
pub const SQLITE_TESTCTRL_LAST: i32 = 26;
|
pub const SQLITE_TESTCTRL_RESULT_INTREAL: i32 = 27;
|
||||||
|
pub const SQLITE_TESTCTRL_PRNG_SEED: i32 = 28;
|
||||||
|
pub const SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS: i32 = 29;
|
||||||
|
pub const SQLITE_TESTCTRL_LAST: i32 = 29;
|
||||||
pub const SQLITE_STATUS_MEMORY_USED: i32 = 0;
|
pub const SQLITE_STATUS_MEMORY_USED: i32 = 0;
|
||||||
pub const SQLITE_STATUS_PAGECACHE_USED: i32 = 1;
|
pub const SQLITE_STATUS_PAGECACHE_USED: i32 = 1;
|
||||||
pub const SQLITE_STATUS_PAGECACHE_OVERFLOW: i32 = 2;
|
pub const SQLITE_STATUS_PAGECACHE_OVERFLOW: i32 = 2;
|
||||||
@ -397,6 +414,8 @@ pub const SQLITE_CHECKPOINT_FULL: i32 = 1;
|
|||||||
pub const SQLITE_CHECKPOINT_RESTART: i32 = 2;
|
pub const SQLITE_CHECKPOINT_RESTART: i32 = 2;
|
||||||
pub const SQLITE_CHECKPOINT_TRUNCATE: i32 = 3;
|
pub const SQLITE_CHECKPOINT_TRUNCATE: i32 = 3;
|
||||||
pub const SQLITE_VTAB_CONSTRAINT_SUPPORT: i32 = 1;
|
pub const SQLITE_VTAB_CONSTRAINT_SUPPORT: i32 = 1;
|
||||||
|
pub const SQLITE_VTAB_INNOCUOUS: i32 = 2;
|
||||||
|
pub const SQLITE_VTAB_DIRECTONLY: i32 = 3;
|
||||||
pub const SQLITE_ROLLBACK: i32 = 1;
|
pub const SQLITE_ROLLBACK: i32 = 1;
|
||||||
pub const SQLITE_FAIL: i32 = 3;
|
pub const SQLITE_FAIL: i32 = 3;
|
||||||
pub const SQLITE_REPLACE: i32 = 5;
|
pub const SQLITE_REPLACE: i32 = 5;
|
||||||
@ -421,7 +440,6 @@ pub const FTS5_TOKEN_COLOCATED: i32 = 1;
|
|||||||
pub type va_list = __builtin_va_list;
|
pub type va_list = __builtin_va_list;
|
||||||
pub type __gnuc_va_list = __builtin_va_list;
|
pub type __gnuc_va_list = __builtin_va_list;
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[link_name = "\u{1}sqlite3_version"]
|
|
||||||
pub static mut sqlite3_version: [::std::os::raw::c_char; 0usize];
|
pub static mut sqlite3_version: [::std::os::raw::c_char; 0usize];
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -1396,7 +1414,7 @@ extern "C" {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_vmprintf(
|
pub fn sqlite3_vmprintf(
|
||||||
arg1: *const ::std::os::raw::c_char,
|
arg1: *const ::std::os::raw::c_char,
|
||||||
arg2: *mut __va_list_tag,
|
arg2: va_list,
|
||||||
) -> *mut ::std::os::raw::c_char;
|
) -> *mut ::std::os::raw::c_char;
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -1412,7 +1430,7 @@ extern "C" {
|
|||||||
arg1: ::std::os::raw::c_int,
|
arg1: ::std::os::raw::c_int,
|
||||||
arg2: *mut ::std::os::raw::c_char,
|
arg2: *mut ::std::os::raw::c_char,
|
||||||
arg3: *const ::std::os::raw::c_char,
|
arg3: *const ::std::os::raw::c_char,
|
||||||
arg4: *mut __va_list_tag,
|
arg4: va_list,
|
||||||
) -> *mut ::std::os::raw::c_char;
|
) -> *mut ::std::os::raw::c_char;
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -1554,6 +1572,27 @@ extern "C" {
|
|||||||
arg3: sqlite3_int64,
|
arg3: sqlite3_int64,
|
||||||
) -> sqlite3_int64;
|
) -> sqlite3_int64;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_uri_key(
|
||||||
|
zFilename: *const ::std::os::raw::c_char,
|
||||||
|
N: ::std::os::raw::c_int,
|
||||||
|
) -> *const ::std::os::raw::c_char;
|
||||||
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_filename_database(
|
||||||
|
arg1: *const ::std::os::raw::c_char,
|
||||||
|
) -> *const ::std::os::raw::c_char;
|
||||||
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_filename_journal(
|
||||||
|
arg1: *const ::std::os::raw::c_char,
|
||||||
|
) -> *const ::std::os::raw::c_char;
|
||||||
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_filename_wal(
|
||||||
|
arg1: *const ::std::os::raw::c_char,
|
||||||
|
) -> *const ::std::os::raw::c_char;
|
||||||
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_errcode(db: *mut sqlite3) -> ::std::os::raw::c_int;
|
pub fn sqlite3_errcode(db: *mut sqlite3) -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
@ -2332,11 +2371,9 @@ extern "C" {
|
|||||||
pub fn sqlite3_sleep(arg1: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
|
pub fn sqlite3_sleep(arg1: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[link_name = "\u{1}sqlite3_temp_directory"]
|
|
||||||
pub static mut sqlite3_temp_directory: *mut ::std::os::raw::c_char;
|
pub static mut sqlite3_temp_directory: *mut ::std::os::raw::c_char;
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[link_name = "\u{1}sqlite3_data_directory"]
|
|
||||||
pub static mut sqlite3_data_directory: *mut ::std::os::raw::c_char;
|
pub static mut sqlite3_data_directory: *mut ::std::os::raw::c_char;
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -2421,6 +2458,9 @@ extern "C" {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_soft_heap_limit64(N: sqlite3_int64) -> sqlite3_int64;
|
pub fn sqlite3_soft_heap_limit64(N: sqlite3_int64) -> sqlite3_int64;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_hard_heap_limit64(N: sqlite3_int64) -> sqlite3_int64;
|
||||||
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_soft_heap_limit(N: ::std::os::raw::c_int);
|
pub fn sqlite3_soft_heap_limit(N: ::std::os::raw::c_int);
|
||||||
}
|
}
|
||||||
@ -3221,6 +3261,12 @@ extern "C" {
|
|||||||
xDestroy: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
xDestroy: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
||||||
) -> ::std::os::raw::c_int;
|
) -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_drop_modules(
|
||||||
|
db: *mut sqlite3,
|
||||||
|
azKeep: *mut *const ::std::os::raw::c_char,
|
||||||
|
) -> ::std::os::raw::c_int;
|
||||||
|
}
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct sqlite3_vtab {
|
pub struct sqlite3_vtab {
|
||||||
@ -3575,7 +3621,7 @@ extern "C" {
|
|||||||
pub fn sqlite3_str_vappendf(
|
pub fn sqlite3_str_vappendf(
|
||||||
arg1: *mut sqlite3_str,
|
arg1: *mut sqlite3_str,
|
||||||
zFormat: *const ::std::os::raw::c_char,
|
zFormat: *const ::std::os::raw::c_char,
|
||||||
arg2: *mut __va_list_tag,
|
arg2: va_list,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -5171,65 +5217,4 @@ fn bindgen_test_layout_fts5_api() {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
pub type __builtin_va_list = [__va_list_tag; 1usize];
|
pub type __builtin_va_list = *mut ::std::os::raw::c_char;
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub struct __va_list_tag {
|
|
||||||
pub gp_offset: ::std::os::raw::c_uint,
|
|
||||||
pub fp_offset: ::std::os::raw::c_uint,
|
|
||||||
pub overflow_arg_area: *mut ::std::os::raw::c_void,
|
|
||||||
pub reg_save_area: *mut ::std::os::raw::c_void,
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn bindgen_test_layout___va_list_tag() {
|
|
||||||
assert_eq!(
|
|
||||||
::std::mem::size_of::<__va_list_tag>(),
|
|
||||||
24usize,
|
|
||||||
concat!("Size of: ", stringify!(__va_list_tag))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
::std::mem::align_of::<__va_list_tag>(),
|
|
||||||
8usize,
|
|
||||||
concat!("Alignment of ", stringify!(__va_list_tag))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
unsafe { &(*(::std::ptr::null::<__va_list_tag>())).gp_offset as *const _ as usize },
|
|
||||||
0usize,
|
|
||||||
concat!(
|
|
||||||
"Offset of field: ",
|
|
||||||
stringify!(__va_list_tag),
|
|
||||||
"::",
|
|
||||||
stringify!(gp_offset)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
unsafe { &(*(::std::ptr::null::<__va_list_tag>())).fp_offset as *const _ as usize },
|
|
||||||
4usize,
|
|
||||||
concat!(
|
|
||||||
"Offset of field: ",
|
|
||||||
stringify!(__va_list_tag),
|
|
||||||
"::",
|
|
||||||
stringify!(fp_offset)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
unsafe { &(*(::std::ptr::null::<__va_list_tag>())).overflow_arg_area as *const _ as usize },
|
|
||||||
8usize,
|
|
||||||
concat!(
|
|
||||||
"Offset of field: ",
|
|
||||||
stringify!(__va_list_tag),
|
|
||||||
"::",
|
|
||||||
stringify!(overflow_arg_area)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
unsafe { &(*(::std::ptr::null::<__va_list_tag>())).reg_save_area as *const _ as usize },
|
|
||||||
16usize,
|
|
||||||
concat!(
|
|
||||||
"Offset of field: ",
|
|
||||||
stringify!(__va_list_tag),
|
|
||||||
"::",
|
|
||||||
stringify!(reg_save_area)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
18893
libsqlite3-sys/sqlite3/sqlite3.c
vendored
18893
libsqlite3-sys/sqlite3/sqlite3.c
vendored
File diff suppressed because it is too large
Load Diff
623
libsqlite3-sys/sqlite3/sqlite3.h
vendored
623
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 */
|
/* Version 3.28.0 and later */
|
||||||
int (*stmt_isexplain)(sqlite3_stmt*);
|
int (*stmt_isexplain)(sqlite3_stmt*);
|
||||||
int (*value_frombind)(sqlite3_value*);
|
int (*value_frombind)(sqlite3_value*);
|
||||||
|
/* Version 3.30.0 and later */
|
||||||
|
int (*drop_modules)(sqlite3*,const char**);
|
||||||
|
/* Version 3.31.0 and later */
|
||||||
|
sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64);
|
||||||
|
const char *(*uri_key)(const char*,int);
|
||||||
|
const char *(*filename_database)(const char*);
|
||||||
|
const char *(*filename_journal)(const char*);
|
||||||
|
const char *(*filename_wal)(const char*);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -614,6 +622,14 @@ typedef int (*sqlite3_loadext_entry)(
|
|||||||
/* Version 3.28.0 and later */
|
/* Version 3.28.0 and later */
|
||||||
#define sqlite3_stmt_isexplain sqlite3_api->isexplain
|
#define sqlite3_stmt_isexplain sqlite3_api->isexplain
|
||||||
#define sqlite3_value_frombind sqlite3_api->frombind
|
#define sqlite3_value_frombind sqlite3_api->frombind
|
||||||
|
/* Version 3.30.0 and later */
|
||||||
|
#define sqlite3_drop_modules sqlite3_api->drop_modules
|
||||||
|
/* Version 3.31.0 andn later */
|
||||||
|
#define sqlite3_hard_heap_limit64 sqlite3_api->hard_heap_limit64
|
||||||
|
#define sqlite3_uri_key sqlite3_api->uri_key
|
||||||
|
#define sqlite3_filename_database sqlite3_api->filename_database
|
||||||
|
#define sqlite3_filename_journal sqlite3_api->filename_journal
|
||||||
|
#define sqlite3_filename_wal sqlite3_api->filename_wal
|
||||||
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
||||||
|
|
||||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||||
|
@ -135,7 +135,12 @@ const SQLITE_IOERR_MMAP: c_int = (super::SQLITE_IOERR | (24 << 8));
|
|||||||
const SQLITE_IOERR_GETTEMPPATH: c_int = (super::SQLITE_IOERR | (25 << 8));
|
const SQLITE_IOERR_GETTEMPPATH: c_int = (super::SQLITE_IOERR | (25 << 8));
|
||||||
const SQLITE_IOERR_CONVPATH: c_int = (super::SQLITE_IOERR | (26 << 8));
|
const SQLITE_IOERR_CONVPATH: c_int = (super::SQLITE_IOERR | (26 << 8));
|
||||||
const SQLITE_IOERR_VNODE: c_int = (super::SQLITE_IOERR | (27 << 8));
|
const SQLITE_IOERR_VNODE: c_int = (super::SQLITE_IOERR | (27 << 8));
|
||||||
|
const SQLITE_IOERR_AUTH: c_int = (super::SQLITE_IOERR | (28 << 8));
|
||||||
|
const SQLITE_IOERR_BEGIN_ATOMIC: c_int = (super::SQLITE_IOERR | (29 << 8));
|
||||||
|
const SQLITE_IOERR_COMMIT_ATOMIC: c_int = (super::SQLITE_IOERR | (30 << 8));
|
||||||
|
const SQLITE_IOERR_ROLLBACK_ATOMIC: c_int = (super::SQLITE_IOERR | (31 << 8));
|
||||||
const SQLITE_LOCKED_SHAREDCACHE: c_int = (super::SQLITE_LOCKED | (1 << 8));
|
const SQLITE_LOCKED_SHAREDCACHE: c_int = (super::SQLITE_LOCKED | (1 << 8));
|
||||||
|
const SQLITE_LOCKED_VTAB: c_int = (super::SQLITE_LOCKED | (2 << 8));
|
||||||
const SQLITE_BUSY_RECOVERY: c_int = (super::SQLITE_BUSY | (1 << 8));
|
const SQLITE_BUSY_RECOVERY: c_int = (super::SQLITE_BUSY | (1 << 8));
|
||||||
const SQLITE_BUSY_SNAPSHOT: c_int = (super::SQLITE_BUSY | (2 << 8));
|
const SQLITE_BUSY_SNAPSHOT: c_int = (super::SQLITE_BUSY | (2 << 8));
|
||||||
const SQLITE_CANTOPEN_NOTEMPDIR: c_int = (super::SQLITE_CANTOPEN | (1 << 8));
|
const SQLITE_CANTOPEN_NOTEMPDIR: c_int = (super::SQLITE_CANTOPEN | (1 << 8));
|
||||||
@ -143,10 +148,13 @@ const SQLITE_CANTOPEN_ISDIR: c_int = (super::SQLITE_CANTOPEN | (2 << 8));
|
|||||||
const SQLITE_CANTOPEN_FULLPATH: c_int = (super::SQLITE_CANTOPEN | (3 << 8));
|
const SQLITE_CANTOPEN_FULLPATH: c_int = (super::SQLITE_CANTOPEN | (3 << 8));
|
||||||
const SQLITE_CANTOPEN_CONVPATH: c_int = (super::SQLITE_CANTOPEN | (4 << 8));
|
const SQLITE_CANTOPEN_CONVPATH: c_int = (super::SQLITE_CANTOPEN | (4 << 8));
|
||||||
const SQLITE_CORRUPT_VTAB: c_int = (super::SQLITE_CORRUPT | (1 << 8));
|
const SQLITE_CORRUPT_VTAB: c_int = (super::SQLITE_CORRUPT | (1 << 8));
|
||||||
|
const SQLITE_CORRUPT_SEQUENCE: c_int = (super::SQLITE_CORRUPT | (2 << 8));
|
||||||
const SQLITE_READONLY_RECOVERY: c_int = (super::SQLITE_READONLY | (1 << 8));
|
const SQLITE_READONLY_RECOVERY: c_int = (super::SQLITE_READONLY | (1 << 8));
|
||||||
const SQLITE_READONLY_CANTLOCK: c_int = (super::SQLITE_READONLY | (2 << 8));
|
const SQLITE_READONLY_CANTLOCK: c_int = (super::SQLITE_READONLY | (2 << 8));
|
||||||
const SQLITE_READONLY_ROLLBACK: c_int = (super::SQLITE_READONLY | (3 << 8));
|
const SQLITE_READONLY_ROLLBACK: c_int = (super::SQLITE_READONLY | (3 << 8));
|
||||||
const SQLITE_READONLY_DBMOVED: c_int = (super::SQLITE_READONLY | (4 << 8));
|
const SQLITE_READONLY_DBMOVED: c_int = (super::SQLITE_READONLY | (4 << 8));
|
||||||
|
const SQLITE_READONLY_CANTINIT: c_int = (super::SQLITE_READONLY | (5 << 8));
|
||||||
|
const SQLITE_READONLY_DIRECTORY: c_int = (super::SQLITE_READONLY | (6 << 8));
|
||||||
const SQLITE_ABORT_ROLLBACK: c_int = (super::SQLITE_ABORT | (2 << 8));
|
const SQLITE_ABORT_ROLLBACK: c_int = (super::SQLITE_ABORT | (2 << 8));
|
||||||
const SQLITE_CONSTRAINT_CHECK: c_int = (super::SQLITE_CONSTRAINT | (1 << 8));
|
const SQLITE_CONSTRAINT_CHECK: c_int = (super::SQLITE_CONSTRAINT | (1 << 8));
|
||||||
const SQLITE_CONSTRAINT_COMMITHOOK: c_int = (super::SQLITE_CONSTRAINT | (2 << 8));
|
const SQLITE_CONSTRAINT_COMMITHOOK: c_int = (super::SQLITE_CONSTRAINT | (2 << 8));
|
||||||
@ -224,7 +232,12 @@ pub fn code_to_str(code: c_int) -> &'static str {
|
|||||||
SQLITE_IOERR_GETTEMPPATH => "VFS is unable to determine a suitable directory for temporary files",
|
SQLITE_IOERR_GETTEMPPATH => "VFS is unable to determine a suitable directory for temporary files",
|
||||||
SQLITE_IOERR_CONVPATH => "cygwin_conv_path() system call failed",
|
SQLITE_IOERR_CONVPATH => "cygwin_conv_path() system call failed",
|
||||||
SQLITE_IOERR_VNODE => "SQLITE_IOERR_VNODE", // not documented?
|
SQLITE_IOERR_VNODE => "SQLITE_IOERR_VNODE", // not documented?
|
||||||
|
SQLITE_IOERR_AUTH => "SQLITE_IOERR_AUTH",
|
||||||
|
SQLITE_IOERR_BEGIN_ATOMIC => "SQLITE_IOERR_BEGIN_ATOMIC",
|
||||||
|
SQLITE_IOERR_COMMIT_ATOMIC => "SQLITE_IOERR_COMMIT_ATOMIC",
|
||||||
|
SQLITE_IOERR_ROLLBACK_ATOMIC => "SQLITE_IOERR_ROLLBACK_ATOMIC",
|
||||||
SQLITE_LOCKED_SHAREDCACHE => "Locking conflict due to another connection with a shared cache",
|
SQLITE_LOCKED_SHAREDCACHE => "Locking conflict due to another connection with a shared cache",
|
||||||
|
SQLITE_LOCKED_VTAB => "SQLITE_LOCKED_VTAB",
|
||||||
SQLITE_BUSY_RECOVERY => "Another process is recovering a WAL mode database file",
|
SQLITE_BUSY_RECOVERY => "Another process is recovering a WAL mode database file",
|
||||||
SQLITE_BUSY_SNAPSHOT => "Cannot promote read transaction to write transaction because of writes by another connection",
|
SQLITE_BUSY_SNAPSHOT => "Cannot promote read transaction to write transaction because of writes by another connection",
|
||||||
SQLITE_CANTOPEN_NOTEMPDIR => "SQLITE_CANTOPEN_NOTEMPDIR", // no longer used
|
SQLITE_CANTOPEN_NOTEMPDIR => "SQLITE_CANTOPEN_NOTEMPDIR", // no longer used
|
||||||
@ -232,10 +245,13 @@ pub fn code_to_str(code: c_int) -> &'static str {
|
|||||||
SQLITE_CANTOPEN_FULLPATH => "Unable to convert filename into full pathname",
|
SQLITE_CANTOPEN_FULLPATH => "Unable to convert filename into full pathname",
|
||||||
SQLITE_CANTOPEN_CONVPATH => "cygwin_conv_path() system call failed",
|
SQLITE_CANTOPEN_CONVPATH => "cygwin_conv_path() system call failed",
|
||||||
SQLITE_CORRUPT_VTAB => "Content in the virtual table is corrupt",
|
SQLITE_CORRUPT_VTAB => "Content in the virtual table is corrupt",
|
||||||
|
SQLITE_CORRUPT_SEQUENCE => "SQLITE_CORRUPT_SEQUENCE",
|
||||||
SQLITE_READONLY_RECOVERY => "WAL mode database file needs recovery (requires write access)",
|
SQLITE_READONLY_RECOVERY => "WAL mode database file needs recovery (requires write access)",
|
||||||
SQLITE_READONLY_CANTLOCK => "Shared-memory file associated with WAL mode database is read-only",
|
SQLITE_READONLY_CANTLOCK => "Shared-memory file associated with WAL mode database is read-only",
|
||||||
SQLITE_READONLY_ROLLBACK => "Database has hot journal that must be rolled back (requires write access)",
|
SQLITE_READONLY_ROLLBACK => "Database has hot journal that must be rolled back (requires write access)",
|
||||||
SQLITE_READONLY_DBMOVED => "Database cannot be modified because database file has moved",
|
SQLITE_READONLY_DBMOVED => "Database cannot be modified because database file has moved",
|
||||||
|
SQLITE_READONLY_CANTINIT => "SQLITE_READONLY_CANTINIT",
|
||||||
|
SQLITE_READONLY_DIRECTORY => "SQLITE_READONLY_DIRECTORY",
|
||||||
SQLITE_ABORT_ROLLBACK => "Transaction was rolled back",
|
SQLITE_ABORT_ROLLBACK => "Transaction was rolled back",
|
||||||
SQLITE_CONSTRAINT_CHECK => "A CHECK constraint failed",
|
SQLITE_CONSTRAINT_CHECK => "A CHECK constraint failed",
|
||||||
SQLITE_CONSTRAINT_COMMITHOOK => "Commit hook caused rollback",
|
SQLITE_CONSTRAINT_COMMITHOOK => "Commit hook caused rollback",
|
||||||
|
@ -8,7 +8,7 @@ use std::mem;
|
|||||||
mod error;
|
mod error;
|
||||||
|
|
||||||
pub fn SQLITE_STATIC() -> sqlite3_destructor_type {
|
pub fn SQLITE_STATIC() -> sqlite3_destructor_type {
|
||||||
Some(unsafe { mem::transmute(0isize) })
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type {
|
pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type {
|
||||||
@ -49,7 +49,11 @@ pub enum Limit {
|
|||||||
SQLITE_LIMIT_WORKER_THREADS = 11,
|
SQLITE_LIMIT_WORKER_THREADS = 11,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::all)]
|
||||||
|
mod bindings {
|
||||||
include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
|
include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
|
||||||
|
}
|
||||||
|
pub use bindings::*;
|
||||||
|
|
||||||
pub type sqlite3_index_constraint = sqlite3_index_info_sqlite3_index_constraint;
|
pub type sqlite3_index_constraint = sqlite3_index_info_sqlite3_index_constraint;
|
||||||
pub type sqlite3_index_constraint_usage = sqlite3_index_info_sqlite3_index_constraint_usage;
|
pub type sqlite3_index_constraint_usage = sqlite3_index_info_sqlite3_index_constraint_usage;
|
||||||
|
@ -4,8 +4,8 @@ cd $SCRIPT_DIR
|
|||||||
export SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
|
export SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
|
||||||
|
|
||||||
# Download and extract amalgamation
|
# Download and extract amalgamation
|
||||||
SQLITE=sqlite-amalgamation-3280000
|
SQLITE=sqlite-amalgamation-3310000
|
||||||
curl -O http://sqlite.org/2019/$SQLITE.zip
|
curl -O https://sqlite.org/2020/$SQLITE.zip
|
||||||
unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c
|
unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c
|
||||||
unzip -p $SQLITE.zip $SQLITE/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h
|
unzip -p $SQLITE.zip $SQLITE/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h
|
||||||
unzip -p $SQLITE.zip $SQLITE/sqlite3ext.h > $SQLITE3_LIB_DIR/sqlite3ext.h
|
unzip -p $SQLITE.zip $SQLITE/sqlite3ext.h > $SQLITE3_LIB_DIR/sqlite3ext.h
|
||||||
|
17
src/busy.rs
17
src/busy.rs
@ -1,4 +1,5 @@
|
|||||||
///! Busy handler (when the database is locked)
|
///! Busy handler (when the database is locked)
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::os::raw::{c_int, c_void};
|
use std::os::raw::{c_int, c_void};
|
||||||
use std::panic::catch_unwind;
|
use std::panic::catch_unwind;
|
||||||
@ -21,12 +22,13 @@ impl Connection {
|
|||||||
/// (using `busy_handler`) prior to calling this routine, that other
|
/// (using `busy_handler`) prior to calling this routine, that other
|
||||||
/// busy handler is cleared.
|
/// busy handler is cleared.
|
||||||
pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
|
pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
|
||||||
let ms = timeout
|
let ms: i32 = timeout
|
||||||
.as_secs()
|
.as_secs()
|
||||||
.checked_mul(1000)
|
.checked_mul(1000)
|
||||||
.and_then(|t| t.checked_add(timeout.subsec_millis().into()))
|
.and_then(|t| t.checked_add(timeout.subsec_millis().into()))
|
||||||
|
.and_then(|t| t.try_into().ok())
|
||||||
.expect("too big");
|
.expect("too big");
|
||||||
self.db.borrow_mut().busy_timeout(ms as i32)
|
self.db.borrow_mut().busy_timeout(ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a callback to handle `SQLITE_BUSY` errors.
|
/// Register a callback to handle `SQLITE_BUSY` errors.
|
||||||
@ -75,18 +77,17 @@ impl InnerConnection {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use self::tempdir::TempDir;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::mpsc::sync_channel;
|
use std::sync::mpsc::sync_channel;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tempdir;
|
use tempfile;
|
||||||
|
|
||||||
use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS};
|
use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_busy() {
|
fn test_default_busy() {
|
||||||
let temp_dir = TempDir::new("test_default_busy").unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let path = temp_dir.path().join("test.db3");
|
let path = temp_dir.path().join("test.db3");
|
||||||
|
|
||||||
let mut db1 = Connection::open(&path).unwrap();
|
let mut db1 = Connection::open(&path).unwrap();
|
||||||
@ -107,7 +108,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore] // FIXME: unstable
|
#[ignore] // FIXME: unstable
|
||||||
fn test_busy_timeout() {
|
fn test_busy_timeout() {
|
||||||
let temp_dir = TempDir::new("test_busy_timeout").unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let path = temp_dir.path().join("test.db3");
|
let path = temp_dir.path().join("test.db3");
|
||||||
|
|
||||||
let db2 = Connection::open(&path).unwrap();
|
let db2 = Connection::open(&path).unwrap();
|
||||||
@ -137,7 +138,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore] // FIXME: unstable
|
#[ignore] // FIXME: unstable
|
||||||
fn test_busy_handler() {
|
fn test_busy_handler() {
|
||||||
lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
fn busy_handler(_: i32) -> bool {
|
fn busy_handler(_: i32) -> bool {
|
||||||
@ -146,7 +147,7 @@ mod test {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
let temp_dir = TempDir::new("test_busy_handler").unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let path = temp_dir.path().join("test.db3");
|
let path = temp_dir.path().join("test.db3");
|
||||||
|
|
||||||
let db2 = Connection::open(&path).unwrap();
|
let db2 = Connection::open(&path).unwrap();
|
||||||
|
11
src/cache.rs
11
src/cache.rs
@ -135,9 +135,12 @@ impl StatementCache {
|
|||||||
|
|
||||||
// Return a statement to the cache.
|
// Return a statement to the cache.
|
||||||
fn cache_stmt(&self, stmt: RawStatement) {
|
fn cache_stmt(&self, stmt: RawStatement) {
|
||||||
|
if stmt.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let mut cache = self.0.borrow_mut();
|
let mut cache = self.0.borrow_mut();
|
||||||
stmt.clear_bindings();
|
stmt.clear_bindings();
|
||||||
let sql = String::from_utf8_lossy(stmt.sql().to_bytes())
|
let sql = String::from_utf8_lossy(stmt.sql().unwrap().to_bytes())
|
||||||
.trim()
|
.trim()
|
||||||
.to_string();
|
.to_string();
|
||||||
cache.insert(sql, stmt);
|
cache.insert(sql, stmt);
|
||||||
@ -339,4 +342,10 @@ mod test {
|
|||||||
}
|
}
|
||||||
assert_eq!(1, cache.len());
|
assert_eq!(1, cache.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_stmt() {
|
||||||
|
let conn = Connection::open_in_memory().unwrap();
|
||||||
|
conn.prepare_cached("").unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
209
src/collation.rs
Normal file
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 n = self.column_count();
|
||||||
let mut cols = Vec::with_capacity(n as usize);
|
let mut cols = Vec::with_capacity(n as usize);
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
let slice = self.stmt.column_name(i);
|
let s = self.column_name_unwrap(i);
|
||||||
let s = str::from_utf8(slice.to_bytes()).unwrap();
|
|
||||||
cols.push(s);
|
cols.push(s);
|
||||||
}
|
}
|
||||||
cols
|
cols
|
||||||
@ -40,6 +39,30 @@ impl Statement<'_> {
|
|||||||
self.stmt.column_count()
|
self.stmt.column_count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn column_name_unwrap(&self, col: usize) -> &str {
|
||||||
|
// Just panic if the bounds are wrong for now, we never call this
|
||||||
|
// without checking first.
|
||||||
|
self.column_name(col).expect("Column out of bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the name assigned to a particular column in the result set
|
||||||
|
/// returned by the prepared statement.
|
||||||
|
///
|
||||||
|
/// ## Failure
|
||||||
|
///
|
||||||
|
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
|
||||||
|
/// column range for this row.
|
||||||
|
///
|
||||||
|
/// Panics when column name is not valid UTF-8.
|
||||||
|
pub fn column_name(&self, col: usize) -> Result<&str> {
|
||||||
|
self.stmt
|
||||||
|
.column_name(col)
|
||||||
|
.ok_or(Error::InvalidColumnIndex(col))
|
||||||
|
.map(|slice| {
|
||||||
|
str::from_utf8(slice.to_bytes()).expect("Invalid UTF-8 sequence in column name")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the column index in the result set for a given column name.
|
/// Returns the column index in the result set for a given column name.
|
||||||
///
|
///
|
||||||
/// If there is no AS clause then the name of the column is unspecified and
|
/// If there is no AS clause then the name of the column is unspecified and
|
||||||
@ -53,7 +76,9 @@ impl Statement<'_> {
|
|||||||
let bytes = name.as_bytes();
|
let bytes = name.as_bytes();
|
||||||
let n = self.column_count();
|
let n = self.column_count();
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).to_bytes()) {
|
// Note: `column_name` is only fallible if `i` is out of bounds,
|
||||||
|
// which we've already checked.
|
||||||
|
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
|
||||||
return Ok(i);
|
return Ok(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,14 +86,15 @@ impl Statement<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a slice describing the columns of the result of the query.
|
/// Returns a slice describing the columns of the result of the query.
|
||||||
pub fn columns<'stmt>(&'stmt self) -> Vec<Column<'stmt>> {
|
pub fn columns(&self) -> Vec<Column> {
|
||||||
let n = self.column_count();
|
let n = self.column_count();
|
||||||
let mut cols = Vec::with_capacity(n as usize);
|
let mut cols = Vec::with_capacity(n as usize);
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
let slice = self.stmt.column_name(i);
|
let name = self.column_name_unwrap(i);
|
||||||
let name = str::from_utf8(slice.to_bytes()).unwrap();
|
|
||||||
let slice = self.stmt.column_decltype(i);
|
let slice = self.stmt.column_decltype(i);
|
||||||
let decl_type = slice.map(|s| str::from_utf8(s.to_bytes()).unwrap());
|
let decl_type = slice.map(|s| {
|
||||||
|
str::from_utf8(s.to_bytes()).expect("Invalid UTF-8 sequence in column declaration")
|
||||||
|
});
|
||||||
cols.push(Column { name, decl_type });
|
cols.push(Column { name, decl_type });
|
||||||
}
|
}
|
||||||
cols
|
cols
|
||||||
@ -86,20 +112,45 @@ impl<'stmt> Rows<'stmt> {
|
|||||||
self.stmt.map(Statement::column_count)
|
self.stmt.map(Statement::column_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the name of the column.
|
||||||
|
pub fn column_name(&self, col: usize) -> Option<Result<&str>> {
|
||||||
|
self.stmt.map(|stmt| stmt.column_name(col))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the index of the column.
|
||||||
|
pub fn column_index(&self, name: &str) -> Option<Result<usize>> {
|
||||||
|
self.stmt.map(|stmt| stmt.column_index(name))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a slice describing the columns of the Rows.
|
/// Returns a slice describing the columns of the Rows.
|
||||||
pub fn columns(&self) -> Option<Vec<Column<'stmt>>> {
|
pub fn columns(&self) -> Option<Vec<Column>> {
|
||||||
self.stmt.map(Statement::columns)
|
self.stmt.map(Statement::columns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stmt> Row<'stmt> {
|
impl<'stmt> Row<'stmt> {
|
||||||
|
/// Get all the column names of the Row.
|
||||||
|
pub fn column_names(&self) -> Vec<&str> {
|
||||||
|
self.stmt.column_names()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the number of columns in the current row.
|
/// Return the number of columns in the current row.
|
||||||
pub fn column_count(&self) -> usize {
|
pub fn column_count(&self) -> usize {
|
||||||
self.stmt.column_count()
|
self.stmt.column_count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the name of the column.
|
||||||
|
pub fn column_name(&self, col: usize) -> Result<&str> {
|
||||||
|
self.stmt.column_name(col)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the index of the column.
|
||||||
|
pub fn column_index(&self, name: &str) -> Result<usize> {
|
||||||
|
self.stmt.column_index(name)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a slice describing the columns of the Row.
|
/// Returns a slice describing the columns of the Row.
|
||||||
pub fn columns(&self) -> Vec<Column<'stmt>> {
|
pub fn columns(&self) -> Vec<Column> {
|
||||||
self.stmt.columns()
|
self.stmt.columns()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,4 +176,40 @@ mod test {
|
|||||||
&[Some("text"), Some("text"), Some("text"),]
|
&[Some("text"), Some("text"), Some("text"),]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_column_name_in_error() {
|
||||||
|
use crate::{types::Type, Error};
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch(
|
||||||
|
"BEGIN;
|
||||||
|
CREATE TABLE foo(x INTEGER, y TEXT);
|
||||||
|
INSERT INTO foo VALUES(4, NULL);
|
||||||
|
END;",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut stmt = db.prepare("SELECT x as renamed, y FROM foo").unwrap();
|
||||||
|
let mut rows = stmt.query(crate::NO_PARAMS).unwrap();
|
||||||
|
let row = rows.next().unwrap().unwrap();
|
||||||
|
match row.get::<_, String>(0).unwrap_err() {
|
||||||
|
Error::InvalidColumnType(idx, name, ty) => {
|
||||||
|
assert_eq!(idx, 0);
|
||||||
|
assert_eq!(name, "renamed");
|
||||||
|
assert_eq!(ty, Type::Integer);
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
panic!("Unexpected error type: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match row.get::<_, String>("y").unwrap_err() {
|
||||||
|
Error::InvalidColumnType(idx, name, ty) => {
|
||||||
|
assert_eq!(idx, 1);
|
||||||
|
assert_eq!(name, "y");
|
||||||
|
assert_eq!(ty, Type::Null);
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
panic!("Unexpected error type: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,19 @@ pub enum DbConfig {
|
|||||||
SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003,
|
SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003,
|
||||||
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // 3.12.0
|
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // 3.12.0
|
||||||
//SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005,
|
//SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005,
|
||||||
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006,
|
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006, // 3.16.2
|
||||||
SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0
|
SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0
|
||||||
SQLITE_DBCONFIG_TRIGGER_EQP = 1008,
|
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
|
||||||
//SQLITE_DBCONFIG_RESET_DATABASE = 1009,
|
//SQLITE_DBCONFIG_RESET_DATABASE = 1009,
|
||||||
SQLITE_DBCONFIG_DEFENSIVE = 1010,
|
SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0
|
||||||
|
#[cfg(feature = "modern_sqlite")]
|
||||||
|
SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011, // 3.28.0
|
||||||
|
#[cfg(feature = "modern_sqlite")]
|
||||||
|
SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012, // 3.29
|
||||||
|
#[cfg(feature = "modern_sqlite")]
|
||||||
|
SQLITE_DBCONFIG_DQS_DML = 1013, // 3.29.0
|
||||||
|
#[cfg(feature = "modern_sqlite")]
|
||||||
|
SQLITE_DBCONFIG_DQS_DDL = 1014, // 3.29.0
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
|
84
src/error.rs
84
src/error.rs
@ -1,3 +1,4 @@
|
|||||||
|
use crate::types::FromSqlError;
|
||||||
use crate::types::Type;
|
use crate::types::Type;
|
||||||
use crate::{errmsg_to_string, ffi};
|
use crate::{errmsg_to_string, ffi};
|
||||||
use std::error;
|
use std::error;
|
||||||
@ -59,7 +60,7 @@ pub enum Error {
|
|||||||
/// Error when the value of a particular column is requested, but the type
|
/// Error when the value of a particular column is requested, but the type
|
||||||
/// of the result in that column cannot be converted to the requested
|
/// of the result in that column cannot be converted to the requested
|
||||||
/// Rust type.
|
/// Rust type.
|
||||||
InvalidColumnType(usize, Type),
|
InvalidColumnType(usize, String, Type),
|
||||||
|
|
||||||
/// Error when a query that was expected to insert one row did not insert
|
/// Error when a query that was expected to insert one row did not insert
|
||||||
/// any or insert many.
|
/// any or insert many.
|
||||||
@ -99,6 +100,9 @@ pub enum Error {
|
|||||||
/// of a different type than what had been stored using `Context::set_aux`.
|
/// of a different type than what had been stored using `Context::set_aux`.
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
GetAuxWrongType,
|
GetAuxWrongType,
|
||||||
|
|
||||||
|
/// Error when the SQL contains multiple statements.
|
||||||
|
MultipleStatement,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Error {
|
impl PartialEq for Error {
|
||||||
@ -117,8 +121,8 @@ impl PartialEq for Error {
|
|||||||
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
|
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
|
||||||
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
|
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
|
||||||
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
|
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
|
||||||
(Error::InvalidColumnType(i1, t1), Error::InvalidColumnType(i2, t2)) => {
|
(Error::InvalidColumnType(i1, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
|
||||||
i1 == i2 && t1 == t2
|
i1 == i2 && t1 == t2 && n1 == n2
|
||||||
}
|
}
|
||||||
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
|
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
@ -138,7 +142,7 @@ impl PartialEq for Error {
|
|||||||
(Error::UnwindingPanic, Error::UnwindingPanic) => true,
|
(Error::UnwindingPanic, Error::UnwindingPanic) => true,
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
(Error::GetAuxWrongType, Error::GetAuxWrongType) => true,
|
(Error::GetAuxWrongType, Error::GetAuxWrongType) => true,
|
||||||
(_, _) => false,
|
(..) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,6 +159,32 @@ impl From<::std::ffi::NulError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UNKNOWN_COLUMN: usize = std::usize::MAX;
|
||||||
|
|
||||||
|
/// The conversion isn't precise, but it's convenient to have it
|
||||||
|
/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
|
||||||
|
impl From<FromSqlError> for Error {
|
||||||
|
fn from(err: FromSqlError) -> Error {
|
||||||
|
// The error type requires index and type fields, but they aren't known in this
|
||||||
|
// context.
|
||||||
|
match err {
|
||||||
|
FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
FromSqlError::InvalidI128Size(_) => {
|
||||||
|
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(_) => {
|
||||||
|
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
|
||||||
|
}
|
||||||
|
FromSqlError::Other(source) => {
|
||||||
|
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source)
|
||||||
|
}
|
||||||
|
_ => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
@ -164,13 +194,23 @@ impl fmt::Display for Error {
|
|||||||
f,
|
f,
|
||||||
"SQLite was compiled or configured for single-threaded use only"
|
"SQLite was compiled or configured for single-threaded use only"
|
||||||
),
|
),
|
||||||
Error::FromSqlConversionFailure(i, ref t, ref err) => write!(
|
Error::FromSqlConversionFailure(i, ref t, ref err) => {
|
||||||
|
if i != UNKNOWN_COLUMN {
|
||||||
|
write!(
|
||||||
f,
|
f,
|
||||||
"Conversion error from type {} at index: {}, {}",
|
"Conversion error from type {} at index: {}, {}",
|
||||||
t, i, err
|
t, i, err
|
||||||
),
|
)
|
||||||
|
} else {
|
||||||
|
err.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
Error::IntegralValueOutOfRange(col, val) => {
|
Error::IntegralValueOutOfRange(col, val) => {
|
||||||
|
if col != UNKNOWN_COLUMN {
|
||||||
write!(f, "Integer {} out of range at index {}", val, col)
|
write!(f, "Integer {} out of range at index {}", val, col)
|
||||||
|
} else {
|
||||||
|
write!(f, "Integer {} out of range", val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Error::Utf8Error(ref err) => err.fmt(f),
|
Error::Utf8Error(ref err) => err.fmt(f),
|
||||||
Error::NulError(ref err) => err.fmt(f),
|
Error::NulError(ref err) => err.fmt(f),
|
||||||
@ -182,9 +222,11 @@ impl fmt::Display for Error {
|
|||||||
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
||||||
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
|
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
|
||||||
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
|
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
|
||||||
Error::InvalidColumnType(i, ref t) => {
|
Error::InvalidColumnType(i, ref name, ref t) => write!(
|
||||||
write!(f, "Invalid column type {} at index: {}", t, i)
|
f,
|
||||||
}
|
"Invalid column type {} at index: {}, name: {}",
|
||||||
|
t, i, name
|
||||||
|
),
|
||||||
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
|
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
@ -205,11 +247,13 @@ impl fmt::Display for Error {
|
|||||||
Error::UnwindingPanic => write!(f, "unwinding panic"),
|
Error::UnwindingPanic => write!(f, "unwinding panic"),
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::GetAuxWrongType => write!(f, "get_aux called with wrong type"),
|
Error::GetAuxWrongType => write!(f, "get_aux called with wrong type"),
|
||||||
|
Error::MultipleStatement => write!(f, "Multiple statements provided"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for Error {
|
impl error::Error for Error {
|
||||||
|
#[allow(deprecated)]
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
Error::SqliteFailure(ref err, None) => err.description(),
|
Error::SqliteFailure(ref err, None) => err.description(),
|
||||||
@ -218,7 +262,7 @@ impl error::Error for Error {
|
|||||||
"SQLite was compiled or configured for single-threaded use only"
|
"SQLite was compiled or configured for single-threaded use only"
|
||||||
}
|
}
|
||||||
Error::FromSqlConversionFailure(_, _, ref err) => err.description(),
|
Error::FromSqlConversionFailure(_, _, ref err) => err.description(),
|
||||||
Error::IntegralValueOutOfRange(_, _) => "integral value out of range of requested type",
|
Error::IntegralValueOutOfRange(..) => "integral value out of range of requested type",
|
||||||
Error::Utf8Error(ref err) => err.description(),
|
Error::Utf8Error(ref err) => err.description(),
|
||||||
Error::InvalidParameterName(_) => "invalid parameter name",
|
Error::InvalidParameterName(_) => "invalid parameter name",
|
||||||
Error::NulError(ref err) => err.description(),
|
Error::NulError(ref err) => err.description(),
|
||||||
@ -229,13 +273,13 @@ impl error::Error for Error {
|
|||||||
Error::QueryReturnedNoRows => "query returned no rows",
|
Error::QueryReturnedNoRows => "query returned no rows",
|
||||||
Error::InvalidColumnIndex(_) => "invalid column index",
|
Error::InvalidColumnIndex(_) => "invalid column index",
|
||||||
Error::InvalidColumnName(_) => "invalid column name",
|
Error::InvalidColumnName(_) => "invalid column name",
|
||||||
Error::InvalidColumnType(_, _) => "invalid column type",
|
Error::InvalidColumnType(..) => "invalid column type",
|
||||||
Error::StatementChangedRows(_) => "query inserted zero or more than one row",
|
Error::StatementChangedRows(_) => "query inserted zero or more than one row",
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
|
Error::InvalidFunctionParameterType(..) => "invalid function parameter type",
|
||||||
#[cfg(feature = "vtab")]
|
#[cfg(feature = "vtab")]
|
||||||
Error::InvalidFilterParameterType(_, _) => "invalid filter parameter type",
|
Error::InvalidFilterParameterType(..) => "invalid filter parameter type",
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::UserFunctionError(ref err) => err.description(),
|
Error::UserFunctionError(ref err) => err.description(),
|
||||||
Error::ToSqlConversionFailure(ref err) => err.description(),
|
Error::ToSqlConversionFailure(ref err) => err.description(),
|
||||||
@ -246,6 +290,7 @@ impl error::Error for Error {
|
|||||||
Error::UnwindingPanic => "unwinding panic",
|
Error::UnwindingPanic => "unwinding panic",
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::GetAuxWrongType => "get_aux called with wrong type",
|
Error::GetAuxWrongType => "get_aux called with wrong type",
|
||||||
|
Error::MultipleStatement => "multiple statements provided",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,22 +300,23 @@ impl error::Error for Error {
|
|||||||
Error::Utf8Error(ref err) => Some(err),
|
Error::Utf8Error(ref err) => Some(err),
|
||||||
Error::NulError(ref err) => Some(err),
|
Error::NulError(ref err) => Some(err),
|
||||||
|
|
||||||
Error::IntegralValueOutOfRange(_, _)
|
Error::IntegralValueOutOfRange(..)
|
||||||
| Error::SqliteSingleThreadedMode
|
| Error::SqliteSingleThreadedMode
|
||||||
| Error::InvalidParameterName(_)
|
| Error::InvalidParameterName(_)
|
||||||
| Error::ExecuteReturnedResults
|
| Error::ExecuteReturnedResults
|
||||||
| Error::QueryReturnedNoRows
|
| Error::QueryReturnedNoRows
|
||||||
| Error::InvalidColumnIndex(_)
|
| Error::InvalidColumnIndex(_)
|
||||||
| Error::InvalidColumnName(_)
|
| Error::InvalidColumnName(_)
|
||||||
| Error::InvalidColumnType(_, _)
|
| Error::InvalidColumnType(..)
|
||||||
| Error::InvalidPath(_)
|
| Error::InvalidPath(_)
|
||||||
| Error::StatementChangedRows(_)
|
| Error::StatementChangedRows(_)
|
||||||
| Error::InvalidQuery => None,
|
| Error::InvalidQuery
|
||||||
|
| Error::MultipleStatement => None,
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::InvalidFunctionParameterType(_, _) => None,
|
Error::InvalidFunctionParameterType(..) => None,
|
||||||
#[cfg(feature = "vtab")]
|
#[cfg(feature = "vtab")]
|
||||||
Error::InvalidFilterParameterType(_, _) => None,
|
Error::InvalidFilterParameterType(..) => None,
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::UserFunctionError(ref err) => Some(&**err),
|
Error::UserFunctionError(ref err) => Some(&**err),
|
||||||
@ -309,7 +355,7 @@ macro_rules! check {
|
|||||||
($funcall:expr) => {{
|
($funcall:expr) => {{
|
||||||
let rc = $funcall;
|
let rc = $funcall;
|
||||||
if rc != crate::ffi::SQLITE_OK {
|
if rc != crate::ffi::SQLITE_OK {
|
||||||
Err(crate::error::error_from_sqlite_code(rc, None))?;
|
return Err(crate::error::error_from_sqlite_code(rc, None).into());
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
497
src/functions.rs
497
src/functions.rs
@ -10,28 +10,47 @@
|
|||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use regex::Regex;
|
//! use regex::Regex;
|
||||||
|
//! use rusqlite::functions::FunctionFlags;
|
||||||
//! use rusqlite::{Connection, Error, Result, NO_PARAMS};
|
//! use rusqlite::{Connection, Error, Result, NO_PARAMS};
|
||||||
//! use std::collections::HashMap;
|
|
||||||
//!
|
//!
|
||||||
//! fn add_regexp_function(db: &Connection) -> Result<()> {
|
//! fn add_regexp_function(db: &Connection) -> Result<()> {
|
||||||
//! let mut cached_regexes = HashMap::new();
|
//! db.create_scalar_function(
|
||||||
//! db.create_scalar_function("regexp", 2, true, move |ctx| {
|
//! "regexp",
|
||||||
//! let regex_s = ctx.get::<String>(0)?;
|
//! 2,
|
||||||
//! let entry = cached_regexes.entry(regex_s.clone());
|
//! FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
//! let regex = {
|
//! move |ctx| {
|
||||||
//! use std::collections::hash_map::Entry::{Occupied, Vacant};
|
//! assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
||||||
//! match entry {
|
//!
|
||||||
//! Occupied(occ) => occ.into_mut(),
|
//! let saved_re: Option<&Regex> = ctx.get_aux(0)?;
|
||||||
//! Vacant(vac) => match Regex::new(®ex_s) {
|
//! let new_re = match saved_re {
|
||||||
//! Ok(r) => vac.insert(r),
|
//! None => {
|
||||||
|
//! let s = ctx.get::<String>(0)?;
|
||||||
|
//! match Regex::new(&s) {
|
||||||
|
//! Ok(r) => Some(r),
|
||||||
//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
||||||
//! },
|
|
||||||
//! }
|
//! }
|
||||||
|
//! }
|
||||||
|
//! Some(_) => None,
|
||||||
//! };
|
//! };
|
||||||
//!
|
//!
|
||||||
//! let text = ctx.get::<String>(1)?;
|
//! let is_match = {
|
||||||
//! Ok(regex.is_match(&text))
|
//! let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap());
|
||||||
//! })
|
//!
|
||||||
|
//! let text = ctx
|
||||||
|
//! .get_raw(1)
|
||||||
|
//! .as_str()
|
||||||
|
//! .map_err(|e| Error::UserFunctionError(e.into()))?;
|
||||||
|
//!
|
||||||
|
//! re.is_match(text)
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! if let Some(re) = new_re {
|
||||||
|
//! ctx.set_aux(0, re);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! Ok(is_match)
|
||||||
|
//! },
|
||||||
|
//! )
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<()> {
|
//! fn main() -> Result<()> {
|
||||||
@ -48,7 +67,6 @@
|
|||||||
//! Ok(())
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use std::error::Error as StdError;
|
|
||||||
use std::os::raw::{c_int, c_void};
|
use std::os::raw::{c_int, c_void};
|
||||||
use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe};
|
use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
@ -68,11 +86,11 @@ unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
|
|||||||
// an explicit feature check for that, and this doesn't really warrant one.
|
// an explicit feature check for that, and this doesn't really warrant one.
|
||||||
// We'll use the extended code if we're on the bundled version (since it's
|
// We'll use the extended code if we're on the bundled version (since it's
|
||||||
// at least 3.17.0) and the normal constraint error code if not.
|
// at least 3.17.0) and the normal constraint error code if not.
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "modern_sqlite")]
|
||||||
fn constraint_error_code() -> i32 {
|
fn constraint_error_code() -> i32 {
|
||||||
ffi::SQLITE_CONSTRAINT_FUNCTION
|
ffi::SQLITE_CONSTRAINT_FUNCTION
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "modern_sqlite"))]
|
||||||
fn constraint_error_code() -> i32 {
|
fn constraint_error_code() -> i32 {
|
||||||
ffi::SQLITE_CONSTRAINT
|
ffi::SQLITE_CONSTRAINT
|
||||||
}
|
}
|
||||||
@ -86,7 +104,7 @@ unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
ffi::sqlite3_result_error_code(ctx, constraint_error_code());
|
ffi::sqlite3_result_error_code(ctx, constraint_error_code());
|
||||||
if let Ok(cstr) = str_to_cstring(err.description()) {
|
if let Ok(cstr) = str_to_cstring(&err.to_string()) {
|
||||||
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
|
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,6 +231,44 @@ where
|
|||||||
fn finalize(&self, _: Option<A>) -> Result<T>;
|
fn finalize(&self, _: Option<A>) -> Result<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// WindowAggregate is the callback interface for user-defined aggregate window
|
||||||
|
/// function.
|
||||||
|
#[cfg(feature = "window")]
|
||||||
|
pub trait WindowAggregate<A, T>: Aggregate<A, T>
|
||||||
|
where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
|
T: ToSql,
|
||||||
|
{
|
||||||
|
/// Returns the current value of the aggregate. Unlike xFinal, the
|
||||||
|
/// implementation should not delete any context.
|
||||||
|
fn value(&self, _: Option<&A>) -> Result<T>;
|
||||||
|
|
||||||
|
/// Removes a row from the current window.
|
||||||
|
fn inverse(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
#[doc = "Function Flags."]
|
||||||
|
#[doc = "See [sqlite3_create_function](https://sqlite.org/c3ref/create_function.html) for details."]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FunctionFlags: ::std::os::raw::c_int {
|
||||||
|
const SQLITE_UTF8 = ffi::SQLITE_UTF8;
|
||||||
|
const SQLITE_UTF16LE = ffi::SQLITE_UTF16LE;
|
||||||
|
const SQLITE_UTF16BE = ffi::SQLITE_UTF16BE;
|
||||||
|
const SQLITE_UTF16 = ffi::SQLITE_UTF16;
|
||||||
|
const SQLITE_DETERMINISTIC = ffi::SQLITE_DETERMINISTIC;
|
||||||
|
const SQLITE_DIRECTONLY = 0x0000_0008_0000; // 3.30.0
|
||||||
|
const SQLITE_SUBTYPE = 0x0000_0010_0000; // 3.30.0
|
||||||
|
const SQLITE_INNOCUOUS = 0x0000_0020_0000; // 3.31.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FunctionFlags {
|
||||||
|
fn default() -> FunctionFlags {
|
||||||
|
FunctionFlags::SQLITE_UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Attach a user-defined scalar function to this database connection.
|
/// Attach a user-defined scalar function to this database connection.
|
||||||
///
|
///
|
||||||
@ -228,11 +284,17 @@ impl Connection {
|
|||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use rusqlite::{Connection, Result, NO_PARAMS};
|
/// # use rusqlite::{Connection, Result, NO_PARAMS};
|
||||||
|
/// # use rusqlite::functions::FunctionFlags;
|
||||||
/// fn scalar_function_example(db: Connection) -> Result<()> {
|
/// fn scalar_function_example(db: Connection) -> Result<()> {
|
||||||
/// db.create_scalar_function("halve", 1, true, |ctx| {
|
/// db.create_scalar_function(
|
||||||
|
/// "halve",
|
||||||
|
/// 1,
|
||||||
|
/// FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
|
/// |ctx| {
|
||||||
/// let value = ctx.get::<f64>(0)?;
|
/// let value = ctx.get::<f64>(0)?;
|
||||||
/// Ok(value / 2f64)
|
/// Ok(value / 2f64)
|
||||||
/// })?;
|
/// },
|
||||||
|
/// )?;
|
||||||
///
|
///
|
||||||
/// let six_halved: f64 = db.query_row("SELECT halve(6)", NO_PARAMS, |r| r.get(0))?;
|
/// let six_halved: f64 = db.query_row("SELECT halve(6)", NO_PARAMS, |r| r.get(0))?;
|
||||||
/// assert_eq!(six_halved, 3f64);
|
/// assert_eq!(six_halved, 3f64);
|
||||||
@ -247,7 +309,7 @@ impl Connection {
|
|||||||
&self,
|
&self,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
n_arg: c_int,
|
n_arg: c_int,
|
||||||
deterministic: bool,
|
flags: FunctionFlags,
|
||||||
x_func: F,
|
x_func: F,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
@ -256,7 +318,7 @@ impl Connection {
|
|||||||
{
|
{
|
||||||
self.db
|
self.db
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.create_scalar_function(fn_name, n_arg, deterministic, x_func)
|
.create_scalar_function(fn_name, n_arg, flags, x_func)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach a user-defined aggregate function to this database connection.
|
/// Attach a user-defined aggregate function to this database connection.
|
||||||
@ -268,7 +330,7 @@ impl Connection {
|
|||||||
&self,
|
&self,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
n_arg: c_int,
|
n_arg: c_int,
|
||||||
deterministic: bool,
|
flags: FunctionFlags,
|
||||||
aggr: D,
|
aggr: D,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
@ -278,7 +340,25 @@ impl Connection {
|
|||||||
{
|
{
|
||||||
self.db
|
self.db
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.create_aggregate_function(fn_name, n_arg, deterministic, aggr)
|
.create_aggregate_function(fn_name, n_arg, flags, aggr)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "window")]
|
||||||
|
pub fn create_window_function<A, W, T>(
|
||||||
|
&self,
|
||||||
|
fn_name: &str,
|
||||||
|
n_arg: c_int,
|
||||||
|
flags: FunctionFlags,
|
||||||
|
aggr: W,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
|
W: WindowAggregate<A, T>,
|
||||||
|
T: ToSql,
|
||||||
|
{
|
||||||
|
self.db
|
||||||
|
.borrow_mut()
|
||||||
|
.create_window_function(fn_name, n_arg, flags, aggr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a user-defined function from this database connection.
|
/// Removes a user-defined function from this database connection.
|
||||||
@ -299,7 +379,7 @@ impl InnerConnection {
|
|||||||
&mut self,
|
&mut self,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
n_arg: c_int,
|
n_arg: c_int,
|
||||||
deterministic: bool,
|
flags: FunctionFlags,
|
||||||
x_func: F,
|
x_func: F,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
@ -341,16 +421,12 @@ impl InnerConnection {
|
|||||||
|
|
||||||
let boxed_f: *mut F = Box::into_raw(Box::new(x_func));
|
let boxed_f: *mut F = Box::into_raw(Box::new(x_func));
|
||||||
let c_name = str_to_cstring(fn_name)?;
|
let c_name = str_to_cstring(fn_name)?;
|
||||||
let mut flags = ffi::SQLITE_UTF8;
|
|
||||||
if deterministic {
|
|
||||||
flags |= ffi::SQLITE_DETERMINISTIC;
|
|
||||||
}
|
|
||||||
let r = unsafe {
|
let r = unsafe {
|
||||||
ffi::sqlite3_create_function_v2(
|
ffi::sqlite3_create_function_v2(
|
||||||
self.db(),
|
self.db(),
|
||||||
c_name.as_ptr(),
|
c_name.as_ptr(),
|
||||||
n_arg,
|
n_arg,
|
||||||
flags,
|
flags.bits(),
|
||||||
boxed_f as *mut c_void,
|
boxed_f as *mut c_void,
|
||||||
Some(call_boxed_closure::<F, T>),
|
Some(call_boxed_closure::<F, T>),
|
||||||
None,
|
None,
|
||||||
@ -365,7 +441,7 @@ impl InnerConnection {
|
|||||||
&mut self,
|
&mut self,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
n_arg: c_int,
|
n_arg: c_int,
|
||||||
deterministic: bool,
|
flags: FunctionFlags,
|
||||||
aggr: D,
|
aggr: D,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
@ -373,10 +449,76 @@ impl InnerConnection {
|
|||||||
D: Aggregate<A, T>,
|
D: Aggregate<A, T>,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
unsafe fn aggregate_context<A>(
|
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
|
||||||
ctx: *mut sqlite3_context,
|
let c_name = str_to_cstring(fn_name)?;
|
||||||
bytes: usize,
|
let r = unsafe {
|
||||||
) -> Option<*mut *mut A> {
|
ffi::sqlite3_create_function_v2(
|
||||||
|
self.db(),
|
||||||
|
c_name.as_ptr(),
|
||||||
|
n_arg,
|
||||||
|
flags.bits(),
|
||||||
|
boxed_aggr as *mut c_void,
|
||||||
|
None,
|
||||||
|
Some(call_boxed_step::<A, D, T>),
|
||||||
|
Some(call_boxed_final::<A, D, T>),
|
||||||
|
Some(free_boxed_value::<D>),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
ffi::sqlite3_create_function_v2(
|
||||||
|
self.db(),
|
||||||
|
c_name.as_ptr(),
|
||||||
|
n_arg,
|
||||||
|
ffi::SQLITE_UTF8,
|
||||||
|
ptr::null_mut(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
|
||||||
if pac.is_null() {
|
if pac.is_null() {
|
||||||
return None;
|
return None;
|
||||||
@ -429,6 +571,49 @@ impl InnerConnection {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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)
|
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
|
||||||
where
|
where
|
||||||
A: RefUnwindSafe + UnwindSafe,
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
@ -472,57 +657,59 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
|
#[cfg(feature = "window")]
|
||||||
let c_name = str_to_cstring(fn_name)?;
|
unsafe extern "C" fn call_boxed_value<A, W, T>(ctx: *mut sqlite3_context)
|
||||||
let mut flags = ffi::SQLITE_UTF8;
|
where
|
||||||
if deterministic {
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
flags |= ffi::SQLITE_DETERMINISTIC;
|
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)
|
||||||
}
|
}
|
||||||
let r = unsafe {
|
}
|
||||||
ffi::sqlite3_create_function_v2(
|
None => None,
|
||||||
self.db(),
|
|
||||||
c_name.as_ptr(),
|
|
||||||
n_arg,
|
|
||||||
flags,
|
|
||||||
boxed_aggr as *mut c_void,
|
|
||||||
None,
|
|
||||||
Some(call_boxed_step::<A, D, T>),
|
|
||||||
Some(call_boxed_final::<A, D, T>),
|
|
||||||
Some(free_boxed_value::<D>),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
self.decode_result(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
|
let r = catch_unwind(|| {
|
||||||
let c_name = str_to_cstring(fn_name)?;
|
let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
|
||||||
let r = unsafe {
|
assert!(
|
||||||
ffi::sqlite3_create_function_v2(
|
!boxed_aggr.is_null(),
|
||||||
self.db(),
|
"Internal error - null aggregate pointer"
|
||||||
c_name.as_ptr(),
|
);
|
||||||
n_arg,
|
(*boxed_aggr).value(a)
|
||||||
ffi::SQLITE_UTF8,
|
});
|
||||||
ptr::null_mut(),
|
let t = match r {
|
||||||
None,
|
Err(_) => {
|
||||||
None,
|
report_error(ctx, &Error::UnwindingPanic);
|
||||||
None,
|
return;
|
||||||
None,
|
}
|
||||||
)
|
Ok(r) => r,
|
||||||
};
|
};
|
||||||
self.decode_result(r)
|
let t = t.as_ref().map(|t| ToSql::to_sql(t));
|
||||||
|
match t {
|
||||||
|
Ok(Ok(ref value)) => set_result(ctx, value),
|
||||||
|
Ok(Err(err)) => report_error(ctx, &err),
|
||||||
|
Err(err) => report_error(ctx, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use self::regex::Regex;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::f64::EPSILON;
|
use std::f64::EPSILON;
|
||||||
use std::os::raw::c_double;
|
use std::os::raw::c_double;
|
||||||
|
|
||||||
use crate::functions::{Aggregate, Context};
|
#[cfg(feature = "window")]
|
||||||
|
use crate::functions::WindowAggregate;
|
||||||
|
use crate::functions::{Aggregate, Context, FunctionFlags};
|
||||||
use crate::{Connection, Error, Result, NO_PARAMS};
|
use crate::{Connection, Error, Result, NO_PARAMS};
|
||||||
|
|
||||||
fn half(ctx: &Context<'_>) -> Result<c_double> {
|
fn half(ctx: &Context<'_>) -> Result<c_double> {
|
||||||
@ -534,7 +721,13 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_function_half() {
|
fn test_function_half() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
db.create_scalar_function("half", 1, true, half).unwrap();
|
db.create_scalar_function(
|
||||||
|
"half",
|
||||||
|
1,
|
||||||
|
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
|
half,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
|
let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
|
||||||
|
|
||||||
assert!((3f64 - result.unwrap()).abs() < EPSILON);
|
assert!((3f64 - result.unwrap()).abs() < EPSILON);
|
||||||
@ -543,7 +736,13 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_remove_function() {
|
fn test_remove_function() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
db.create_scalar_function("half", 1, true, half).unwrap();
|
db.create_scalar_function(
|
||||||
|
"half",
|
||||||
|
1,
|
||||||
|
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
|
half,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
|
let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
|
||||||
assert!((3f64 - result.unwrap()).abs() < EPSILON);
|
assert!((3f64 - result.unwrap()).abs() < EPSILON);
|
||||||
|
|
||||||
@ -600,63 +799,14 @@ mod test {
|
|||||||
END;",
|
END;",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
db.create_scalar_function("regexp", 2, true, regexp_with_auxilliary)
|
db.create_scalar_function(
|
||||||
.unwrap();
|
"regexp",
|
||||||
|
2,
|
||||||
let result: Result<bool> =
|
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| {
|
regexp_with_auxilliary,
|
||||||
r.get(0)
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(true, result.unwrap());
|
|
||||||
|
|
||||||
let result: Result<i64> = db.query_row(
|
|
||||||
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
|
|
||||||
NO_PARAMS,
|
|
||||||
|r| r.get(0),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(2, result.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_function_regexp_with_hashmap_cache() {
|
|
||||||
let db = Connection::open_in_memory().unwrap();
|
|
||||||
db.execute_batch(
|
|
||||||
"BEGIN;
|
|
||||||
CREATE TABLE foo (x string);
|
|
||||||
INSERT INTO foo VALUES ('lisa');
|
|
||||||
INSERT INTO foo VALUES ('lXsi');
|
|
||||||
INSERT INTO foo VALUES ('lisX');
|
|
||||||
END;",
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// This implementation of a regexp scalar function uses a captured HashMap
|
|
||||||
// to keep cached regular expressions around (even across multiple queries)
|
|
||||||
// until the function is removed.
|
|
||||||
let mut cached_regexes = HashMap::new();
|
|
||||||
db.create_scalar_function("regexp", 2, true, move |ctx| {
|
|
||||||
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
|
||||||
|
|
||||||
let regex_s = ctx.get::<String>(0)?;
|
|
||||||
let entry = cached_regexes.entry(regex_s.clone());
|
|
||||||
let regex = {
|
|
||||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
|
||||||
match entry {
|
|
||||||
Occupied(occ) => occ.into_mut(),
|
|
||||||
Vacant(vac) => match Regex::new(®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> =
|
let result: Result<bool> =
|
||||||
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| {
|
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| {
|
||||||
r.get(0)
|
r.get(0)
|
||||||
@ -676,7 +826,11 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_varargs_function() {
|
fn test_varargs_function() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
db.create_scalar_function("my_concat", -1, true, |ctx| {
|
db.create_scalar_function(
|
||||||
|
"my_concat",
|
||||||
|
-1,
|
||||||
|
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
|
|ctx| {
|
||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
|
|
||||||
for idx in 0..ctx.len() {
|
for idx in 0..ctx.len() {
|
||||||
@ -685,7 +839,8 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for &(expected, query) in &[
|
for &(expected, query) in &[
|
||||||
@ -701,7 +856,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_get_aux_type_checking() {
|
fn test_get_aux_type_checking() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
db.create_scalar_function("example", 2, false, |ctx| {
|
db.create_scalar_function("example", 2, FunctionFlags::default(), |ctx| {
|
||||||
if !ctx.get::<bool>(1)? {
|
if !ctx.get::<bool>(1)? {
|
||||||
ctx.set_aux::<i64>(0, 100);
|
ctx.set_aux::<i64>(0, 100);
|
||||||
} else {
|
} else {
|
||||||
@ -759,7 +914,12 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_sum() {
|
fn test_sum() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
db.create_aggregate_function("my_sum", 1, true, Sum)
|
db.create_aggregate_function(
|
||||||
|
"my_sum",
|
||||||
|
1,
|
||||||
|
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
|
Sum,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// sum should return NULL when given no columns (contrast with count below)
|
// sum should return NULL when given no columns (contrast with count below)
|
||||||
@ -782,7 +942,12 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_count() {
|
fn test_count() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
db.create_aggregate_function("my_count", -1, true, Count)
|
db.create_aggregate_function(
|
||||||
|
"my_count",
|
||||||
|
-1,
|
||||||
|
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
|
Count,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// count should return 0 when given no columns (contrast with sum above)
|
// count should return 0 when given no columns (contrast with sum above)
|
||||||
@ -794,4 +959,64 @@ mod test {
|
|||||||
let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
|
let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
|
||||||
assert_eq!(2, result);
|
assert_eq!(2, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "window")]
|
||||||
|
impl WindowAggregate<i64, Option<i64>> for Sum {
|
||||||
|
fn inverse(&self, ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
|
||||||
|
*sum -= ctx.get::<i64>(0)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self, sum: Option<&i64>) -> Result<Option<i64>> {
|
||||||
|
Ok(sum.copied())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "window")]
|
||||||
|
fn test_window() {
|
||||||
|
use fallible_iterator::FallibleIterator;
|
||||||
|
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.create_window_function(
|
||||||
|
"sumint",
|
||||||
|
1,
|
||||||
|
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||||
|
Sum,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
db.execute_batch(
|
||||||
|
"CREATE TABLE t3(x, y);
|
||||||
|
INSERT INTO t3 VALUES('a', 4),
|
||||||
|
('b', 5),
|
||||||
|
('c', 3),
|
||||||
|
('d', 8),
|
||||||
|
('e', 1);",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut stmt = db
|
||||||
|
.prepare(
|
||||||
|
"SELECT x, sumint(y) OVER (
|
||||||
|
ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
|
||||||
|
) AS sum_y
|
||||||
|
FROM t3 ORDER BY x;",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let results: Vec<(String, i64)> = stmt
|
||||||
|
.query(NO_PARAMS)
|
||||||
|
.unwrap()
|
||||||
|
.map(|row| Ok((row.get("x")?, row.get("sum_y")?)))
|
||||||
|
.collect()
|
||||||
|
.unwrap();
|
||||||
|
let expected = vec![
|
||||||
|
("a".to_owned(), 9),
|
||||||
|
("b".to_owned(), 12),
|
||||||
|
("c".to_owned(), 16),
|
||||||
|
("d".to_owned(), 12),
|
||||||
|
("e".to_owned(), 9),
|
||||||
|
];
|
||||||
|
assert_eq!(expected, results);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,6 +236,7 @@ fn free_boxed_hook<F>(p: *mut c_void) {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::Action;
|
use super::Action;
|
||||||
use crate::Connection;
|
use crate::Connection;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::mem;
|
use std::mem::MaybeUninit;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::{c_char, c_int};
|
||||||
#[cfg(feature = "load_extension")]
|
#[cfg(feature = "load_extension")]
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Mutex, Once, ONCE_INIT};
|
use std::sync::{Arc, Mutex, Once};
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
use super::{str_for_sqlite, str_to_cstring};
|
use super::{str_for_sqlite, str_to_cstring};
|
||||||
@ -37,6 +37,7 @@ pub struct InnerConnection {
|
|||||||
|
|
||||||
impl InnerConnection {
|
impl InnerConnection {
|
||||||
#[cfg(not(feature = "hooks"))]
|
#[cfg(not(feature = "hooks"))]
|
||||||
|
#[allow(clippy::mutex_atomic)]
|
||||||
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
||||||
InnerConnection {
|
InnerConnection {
|
||||||
db,
|
db,
|
||||||
@ -46,6 +47,7 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "hooks")]
|
#[cfg(feature = "hooks")]
|
||||||
|
#[allow(clippy::mutex_atomic)]
|
||||||
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
||||||
InnerConnection {
|
InnerConnection {
|
||||||
db,
|
db,
|
||||||
@ -83,13 +85,28 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
|
let mut db = MaybeUninit::uninit();
|
||||||
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), z_vfs);
|
let r =
|
||||||
|
ffi::sqlite3_open_v2(c_path.as_ptr(), db.as_mut_ptr(), flags.bits(), z_vfs);
|
||||||
|
let db: *mut ffi::sqlite3 = db.assume_init();
|
||||||
if r != ffi::SQLITE_OK {
|
if r != ffi::SQLITE_OK {
|
||||||
let e = if db.is_null() {
|
let e = if db.is_null() {
|
||||||
error_from_sqlite_code(r, None)
|
error_from_sqlite_code(r, Some(c_path.to_string_lossy().to_string()))
|
||||||
} else {
|
} else {
|
||||||
let e = error_from_handle(db, r);
|
let mut e = error_from_handle(db, r);
|
||||||
|
if let Error::SqliteFailure(
|
||||||
|
ffi::Error {
|
||||||
|
code: ffi::ErrorCode::CannotOpen,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
Some(msg),
|
||||||
|
) = e
|
||||||
|
{
|
||||||
|
e = Error::SqliteFailure(
|
||||||
|
ffi::Error::new(r),
|
||||||
|
Some(format!("{}: {}", msg, c_path.to_string_lossy())),
|
||||||
|
);
|
||||||
|
}
|
||||||
ffi::sqlite3_close(db);
|
ffi::sqlite3_close(db);
|
||||||
e
|
e
|
||||||
};
|
};
|
||||||
@ -126,6 +143,7 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::mutex_atomic)]
|
||||||
pub fn close(&mut self) -> Result<()> {
|
pub fn close(&mut self) -> Result<()> {
|
||||||
if self.db.is_null() {
|
if self.db.is_null() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -181,25 +199,29 @@ impl InnerConnection {
|
|||||||
|
|
||||||
#[cfg(feature = "load_extension")]
|
#[cfg(feature = "load_extension")]
|
||||||
pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> {
|
pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> {
|
||||||
use std::os::raw::c_char;
|
|
||||||
|
|
||||||
let dylib_str = super::path_to_cstring(dylib_path)?;
|
let dylib_str = super::path_to_cstring(dylib_path)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut errmsg: *mut c_char = mem::uninitialized();
|
let mut errmsg = MaybeUninit::uninit();
|
||||||
let r = if let Some(entry_point) = entry_point {
|
let r = if let Some(entry_point) = entry_point {
|
||||||
let c_entry = str_to_cstring(entry_point)?;
|
let c_entry = str_to_cstring(entry_point)?;
|
||||||
ffi::sqlite3_load_extension(
|
ffi::sqlite3_load_extension(
|
||||||
self.db,
|
self.db,
|
||||||
dylib_str.as_ptr(),
|
dylib_str.as_ptr(),
|
||||||
c_entry.as_ptr(),
|
c_entry.as_ptr(),
|
||||||
&mut errmsg,
|
errmsg.as_mut_ptr(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg)
|
ffi::sqlite3_load_extension(
|
||||||
|
self.db,
|
||||||
|
dylib_str.as_ptr(),
|
||||||
|
ptr::null(),
|
||||||
|
errmsg.as_mut_ptr(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
if r == ffi::SQLITE_OK {
|
if r == ffi::SQLITE_OK {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
let errmsg: *mut c_char = errmsg.assume_init();
|
||||||
let message = super::errmsg_to_string(&*errmsg);
|
let message = super::errmsg_to_string(&*errmsg);
|
||||||
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
||||||
Err(error_from_sqlite_code(r, Some(message)))
|
Err(error_from_sqlite_code(r, Some(message)))
|
||||||
@ -212,8 +234,9 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
|
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
|
||||||
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
|
let mut c_stmt = MaybeUninit::uninit();
|
||||||
let (c_sql, len, _) = str_for_sqlite(sql)?;
|
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
|
||||||
|
let mut c_tail = MaybeUninit::uninit();
|
||||||
let r = unsafe {
|
let r = unsafe {
|
||||||
if cfg!(feature = "unlock_notify") {
|
if cfg!(feature = "unlock_notify") {
|
||||||
let mut rc;
|
let mut rc;
|
||||||
@ -222,8 +245,8 @@ impl InnerConnection {
|
|||||||
self.db(),
|
self.db(),
|
||||||
c_sql,
|
c_sql,
|
||||||
len,
|
len,
|
||||||
&mut c_stmt,
|
c_stmt.as_mut_ptr(),
|
||||||
ptr::null_mut(),
|
c_tail.as_mut_ptr(),
|
||||||
);
|
);
|
||||||
if !unlock_notify::is_locked(self.db, rc) {
|
if !unlock_notify::is_locked(self.db, rc) {
|
||||||
break;
|
break;
|
||||||
@ -235,11 +258,24 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
rc
|
rc
|
||||||
} else {
|
} else {
|
||||||
ffi::sqlite3_prepare_v2(self.db(), c_sql, len, &mut c_stmt, ptr::null_mut())
|
ffi::sqlite3_prepare_v2(
|
||||||
|
self.db(),
|
||||||
|
c_sql,
|
||||||
|
len,
|
||||||
|
c_stmt.as_mut_ptr(),
|
||||||
|
c_tail.as_mut_ptr(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.decode_result(r)
|
// If there is an error, *ppStmt is set to NULL.
|
||||||
.map(|_| Statement::new(conn, RawStatement::new(c_stmt)))
|
self.decode_result(r)?;
|
||||||
|
// If the input text contains no SQL (if the input is an empty string or a
|
||||||
|
// comment) then *ppStmt is set to NULL.
|
||||||
|
let c_stmt: *mut ffi::sqlite3_stmt = unsafe { c_stmt.assume_init() };
|
||||||
|
let c_tail: *const c_char = unsafe { c_tail.assume_init() };
|
||||||
|
// TODO ignore spaces, comments, ... at the end
|
||||||
|
let tail = !c_tail.is_null() && unsafe { c_tail != c_sql.offset(len as isize) };
|
||||||
|
Ok(Statement::new(conn, RawStatement::new(c_stmt, tail)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn changes(&mut self) -> usize {
|
pub fn changes(&mut self) -> usize {
|
||||||
@ -250,7 +286,7 @@ impl InnerConnection {
|
|||||||
unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
|
unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bundled")] // 3.8.6
|
#[cfg(feature = "modern_sqlite")] // 3.8.6
|
||||||
pub fn is_busy(&self) -> bool {
|
pub fn is_busy(&self) -> bool {
|
||||||
let db = self.db();
|
let db = self.db();
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -285,7 +321,7 @@ impl Drop for InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "bundled"))]
|
||||||
static SQLITE_VERSION_CHECK: Once = ONCE_INIT;
|
static SQLITE_VERSION_CHECK: Once = Once::new();
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "bundled"))]
|
||||||
pub static BYPASS_VERSION_CHECK: AtomicBool = AtomicBool::new(false);
|
pub static BYPASS_VERSION_CHECK: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
@ -333,7 +369,7 @@ rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fi
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static SQLITE_INIT: Once = ONCE_INIT;
|
static SQLITE_INIT: Once = Once::new();
|
||||||
pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false);
|
pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
||||||
|
185
src/lib.rs
185
src/lib.rs
@ -2,7 +2,6 @@
|
|||||||
//! expose an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
|
//! expose an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use rusqlite::types::ToSql;
|
|
||||||
//! use rusqlite::{params, Connection, Result};
|
//! use rusqlite::{params, Connection, Result};
|
||||||
//! use time::Timespec;
|
//! use time::Timespec;
|
||||||
//!
|
//!
|
||||||
@ -58,12 +57,6 @@
|
|||||||
|
|
||||||
pub use libsqlite3_sys as ffi;
|
pub use libsqlite3_sys as ffi;
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate bitflags;
|
|
||||||
#[cfg(any(test, feature = "vtab"))]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::convert;
|
use std::convert;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
@ -105,6 +98,8 @@ pub mod backup;
|
|||||||
pub mod blob;
|
pub mod blob;
|
||||||
mod busy;
|
mod busy;
|
||||||
mod cache;
|
mod cache;
|
||||||
|
#[cfg(feature = "collation")]
|
||||||
|
mod collation;
|
||||||
mod column;
|
mod column;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
#[cfg(any(feature = "functions", feature = "vtab"))]
|
#[cfg(any(feature = "functions", feature = "vtab"))]
|
||||||
@ -165,7 +160,7 @@ macro_rules! params {
|
|||||||
$crate::NO_PARAMS
|
$crate::NO_PARAMS
|
||||||
};
|
};
|
||||||
($($param:expr),+ $(,)?) => {
|
($($param:expr),+ $(,)?) => {
|
||||||
&[$(&$param as &dyn $crate::ToSql),+]
|
&[$(&$param as &dyn $crate::ToSql),+] as &[&dyn $crate::ToSql]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,9 +241,9 @@ fn str_to_cstring(s: &str) -> Result<CString> {
|
|||||||
/// The `sqlite3_destructor_type` item is always `SQLITE_TRANSIENT` unless
|
/// The `sqlite3_destructor_type` item is always `SQLITE_TRANSIENT` unless
|
||||||
/// the string was empty (in which case it's `SQLITE_STATIC`, and the ptr is
|
/// the string was empty (in which case it's `SQLITE_STATIC`, and the ptr is
|
||||||
/// static).
|
/// static).
|
||||||
fn str_for_sqlite(s: &str) -> Result<(*const c_char, c_int, ffi::sqlite3_destructor_type)> {
|
fn str_for_sqlite(s: &[u8]) -> Result<(*const c_char, c_int, ffi::sqlite3_destructor_type)> {
|
||||||
let len = len_as_c_int(s.len())?;
|
let len = len_as_c_int(s.len())?;
|
||||||
if memchr::memchr(0, s.as_bytes()).is_none() {
|
if memchr::memchr(0, s).is_none() {
|
||||||
let (ptr, dtor_info) = if len != 0 {
|
let (ptr, dtor_info) = if len != 0 {
|
||||||
(s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT())
|
(s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT())
|
||||||
} else {
|
} else {
|
||||||
@ -300,7 +295,7 @@ pub enum DatabaseName<'a> {
|
|||||||
feature = "backup",
|
feature = "backup",
|
||||||
feature = "blob",
|
feature = "blob",
|
||||||
feature = "session",
|
feature = "session",
|
||||||
feature = "bundled"
|
feature = "modern_sqlite"
|
||||||
))]
|
))]
|
||||||
impl DatabaseName<'_> {
|
impl DatabaseName<'_> {
|
||||||
fn to_cstring(&self) -> Result<CString> {
|
fn to_cstring(&self) -> Result<CString> {
|
||||||
@ -336,6 +331,16 @@ impl Connection {
|
|||||||
/// OpenFlags::SQLITE_OPEN_READ_WRITE |
|
/// OpenFlags::SQLITE_OPEN_READ_WRITE |
|
||||||
/// OpenFlags::SQLITE_OPEN_CREATE)`.
|
/// OpenFlags::SQLITE_OPEN_CREATE)`.
|
||||||
///
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use rusqlite::{Connection, Result};
|
||||||
|
/// fn open_my_db() -> Result<()> {
|
||||||
|
/// let path = "./my_db.db3";
|
||||||
|
/// let db = Connection::open(&path)?;
|
||||||
|
/// println!("{}", db.is_autocommit());
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if `path` cannot be converted to a C-compatible
|
/// Will return `Err` if `path` cannot be converted to a C-compatible
|
||||||
@ -481,7 +486,8 @@ impl Connection {
|
|||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
{
|
{
|
||||||
self.prepare(sql).and_then(|mut stmt| stmt.execute(params))
|
self.prepare(sql)
|
||||||
|
.and_then(|mut stmt| stmt.check_no_tail().and_then(|_| stmt.execute(params)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience method to prepare and execute a single SQL statement with
|
/// Convenience method to prepare and execute a single SQL statement with
|
||||||
@ -507,8 +513,10 @@ impl Connection {
|
|||||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||||
/// or if the underlying SQLite call fails.
|
/// or if the underlying SQLite call fails.
|
||||||
pub fn execute_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
pub fn execute_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
||||||
self.prepare(sql)
|
self.prepare(sql).and_then(|mut stmt| {
|
||||||
.and_then(|mut stmt| stmt.execute_named(params))
|
stmt.check_no_tail()
|
||||||
|
.and_then(|_| stmt.execute_named(params))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the SQLite rowid of the most recent successful INSERT.
|
/// Get the SQLite rowid of the most recent successful INSERT.
|
||||||
@ -553,6 +561,7 @@ impl Connection {
|
|||||||
F: FnOnce(&Row<'_>) -> Result<T>,
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
{
|
{
|
||||||
let mut stmt = self.prepare(sql)?;
|
let mut stmt = self.prepare(sql)?;
|
||||||
|
stmt.check_no_tail()?;
|
||||||
stmt.query_row(params, f)
|
stmt.query_row(params, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -575,9 +584,8 @@ impl Connection {
|
|||||||
F: FnOnce(&Row<'_>) -> Result<T>,
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
{
|
{
|
||||||
let mut stmt = self.prepare(sql)?;
|
let mut stmt = self.prepare(sql)?;
|
||||||
let mut rows = stmt.query_named(params)?;
|
stmt.check_no_tail()?;
|
||||||
|
stmt.query_row_named(params, f)
|
||||||
rows.get_expected_row().and_then(|r| f(&r))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience method to execute a query that is expected to return a
|
/// Convenience method to execute a query that is expected to return a
|
||||||
@ -613,6 +621,7 @@ impl Connection {
|
|||||||
E: convert::From<Error>,
|
E: convert::From<Error>,
|
||||||
{
|
{
|
||||||
let mut stmt = self.prepare(sql)?;
|
let mut stmt = self.prepare(sql)?;
|
||||||
|
stmt.check_no_tail()?;
|
||||||
let mut rows = stmt.query(params)?;
|
let mut rows = stmt.query(params)?;
|
||||||
|
|
||||||
rows.get_expected_row().map_err(E::from).and_then(|r| f(&r))
|
rows.get_expected_row().map_err(E::from).and_then(|r| f(&r))
|
||||||
@ -730,7 +739,11 @@ impl Connection {
|
|||||||
///
|
///
|
||||||
/// You should not need to use this function. If you do need to, please
|
/// You should not need to use this function. If you do need to, please
|
||||||
/// [open an issue on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe
|
/// [open an issue on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe
|
||||||
/// your use case. This function is unsafe because it gives you raw access
|
/// your use case.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function is unsafe because it gives you raw access
|
||||||
/// to the SQLite connection, and what you do with it could impact the
|
/// to the SQLite connection, and what you do with it could impact the
|
||||||
/// safety of this `Connection`.
|
/// safety of this `Connection`.
|
||||||
pub unsafe fn handle(&self) -> *mut ffi::sqlite3 {
|
pub unsafe fn handle(&self) -> *mut ffi::sqlite3 {
|
||||||
@ -775,7 +788,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if all associated prepared statements have been reset.
|
/// Determine if all associated prepared statements have been reset.
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "modern_sqlite")] // 3.8.6
|
||||||
pub fn is_busy(&self) -> bool {
|
pub fn is_busy(&self) -> bool {
|
||||||
self.db.borrow().is_busy()
|
self.db.borrow().is_busy()
|
||||||
}
|
}
|
||||||
@ -789,7 +802,7 @@ impl fmt::Debug for Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags::bitflags! {
|
||||||
#[doc = "Flags for opening SQLite database connections."]
|
#[doc = "Flags for opening SQLite database connections."]
|
||||||
#[doc = "See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details."]
|
#[doc = "See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details."]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@ -826,8 +839,11 @@ impl Default for OpenFlags {
|
|||||||
/// If you are encountering that panic _and_ can ensure that SQLite has been
|
/// If you are encountering that panic _and_ can ensure that SQLite has been
|
||||||
/// initialized in either multi-thread or serialized mode, call this function
|
/// initialized in either multi-thread or serialized mode, call this function
|
||||||
/// prior to attempting to open a connection and rusqlite's initialization
|
/// prior to attempting to open a connection and rusqlite's initialization
|
||||||
/// process will by skipped. This
|
/// process will by skipped.
|
||||||
/// function is unsafe because if you call it and SQLite has actually been
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function is unsafe because if you call it and SQLite has actually been
|
||||||
/// configured to run in single-thread mode,
|
/// configured to run in single-thread mode,
|
||||||
/// you may enounter memory errors or data corruption or any number of terrible
|
/// you may enounter memory errors or data corruption or any number of terrible
|
||||||
/// things that should not be possible when you're using Rust.
|
/// things that should not be possible when you're using Rust.
|
||||||
@ -838,11 +854,13 @@ pub unsafe fn bypass_sqlite_initialization() {
|
|||||||
/// rusqlite performs a one-time check that the runtime SQLite version is at
|
/// rusqlite performs a one-time check that the runtime SQLite version is at
|
||||||
/// least as new as the version of SQLite found when rusqlite was built.
|
/// least as new as the version of SQLite found when rusqlite was built.
|
||||||
/// Bypassing this check may be dangerous; e.g., if you use features of SQLite
|
/// Bypassing this check may be dangerous; e.g., if you use features of SQLite
|
||||||
/// that are not present in the runtime
|
/// that are not present in the runtime version.
|
||||||
/// version. If you are sure the runtime version is compatible with the
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// If you are sure the runtime version is compatible with the
|
||||||
/// build-time version for your usage, you can bypass the version check by
|
/// build-time version for your usage, you can bypass the version check by
|
||||||
/// calling this function before
|
/// calling this function before your first connection attempt.
|
||||||
/// your first connection attempt.
|
|
||||||
pub unsafe fn bypass_sqlite_version_check() {
|
pub unsafe fn bypass_sqlite_version_check() {
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "bundled"))]
|
||||||
inner_connection::BYPASS_VERSION_CHECK.store(true, Ordering::Relaxed);
|
inner_connection::BYPASS_VERSION_CHECK.store(true, Ordering::Relaxed);
|
||||||
@ -867,7 +885,7 @@ impl InterruptHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bundled")] // 3.7.10
|
#[cfg(feature = "modern_sqlite")] // 3.7.10
|
||||||
unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
|
unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
|
||||||
let db_name = DatabaseName::Main.to_cstring().unwrap();
|
let db_name = DatabaseName::Main.to_cstring().unwrap();
|
||||||
let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
|
let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
|
||||||
@ -877,20 +895,19 @@ unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
|
|||||||
CStr::from_ptr(db_filename).to_str().ok().map(PathBuf::from)
|
CStr::from_ptr(db_filename).to_str().ok().map(PathBuf::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "modern_sqlite"))]
|
||||||
unsafe fn db_filename(_: *mut ffi::sqlite3) -> Option<PathBuf> {
|
unsafe fn db_filename(_: *mut ffi::sqlite3) -> Option<PathBuf> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use self::tempdir::TempDir;
|
use super::*;
|
||||||
pub use super::*;
|
|
||||||
use crate::ffi;
|
use crate::ffi;
|
||||||
use fallible_iterator::FallibleIterator;
|
use fallible_iterator::FallibleIterator;
|
||||||
pub use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
pub use std::fmt;
|
use std::fmt;
|
||||||
use tempdir;
|
use tempfile;
|
||||||
|
|
||||||
// this function is never called, but is still type checked; in
|
// this function is never called, but is still type checked; in
|
||||||
// particular, calls with specific instantiations will require
|
// particular, calls with specific instantiations will require
|
||||||
@ -913,7 +930,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_concurrent_transactions_busy_commit() {
|
fn test_concurrent_transactions_busy_commit() {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
let tmp = TempDir::new("locked").unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let path = tmp.path().join("transactions.db3");
|
let path = tmp.path().join("transactions.db3");
|
||||||
|
|
||||||
Connection::open(&path)
|
Connection::open(&path)
|
||||||
@ -925,8 +942,9 @@ mod test {
|
|||||||
)
|
)
|
||||||
.expect("create temp db");
|
.expect("create temp db");
|
||||||
|
|
||||||
let mut db1 = Connection::open(&path).unwrap();
|
let mut db1 =
|
||||||
let mut db2 = Connection::open(&path).unwrap();
|
Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE).unwrap();
|
||||||
|
let mut db2 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY).unwrap();
|
||||||
|
|
||||||
db1.busy_timeout(Duration::from_millis(0)).unwrap();
|
db1.busy_timeout(Duration::from_millis(0)).unwrap();
|
||||||
db2.busy_timeout(Duration::from_millis(0)).unwrap();
|
db2.busy_timeout(Duration::from_millis(0)).unwrap();
|
||||||
@ -958,7 +976,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_persistence() {
|
fn test_persistence() {
|
||||||
let temp_dir = TempDir::new("test_open_file").unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let path = temp_dir.path().join("test.db3");
|
let path = temp_dir.path().join("test.db3");
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -985,6 +1003,26 @@ mod test {
|
|||||||
assert!(db.close().is_ok());
|
assert!(db.close().is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_open_failure() {
|
||||||
|
let filename = "no_such_file.db";
|
||||||
|
let result = Connection::open_with_flags(filename, OpenFlags::SQLITE_OPEN_READ_ONLY);
|
||||||
|
assert!(!result.is_ok());
|
||||||
|
let err = result.err().unwrap();
|
||||||
|
if let Error::SqliteFailure(e, Some(msg)) = err {
|
||||||
|
assert_eq!(ErrorCode::CannotOpen, e.code);
|
||||||
|
assert_eq!(ffi::SQLITE_CANTOPEN, e.extended_code);
|
||||||
|
assert!(
|
||||||
|
msg.contains(filename),
|
||||||
|
"error message '{}' does not contain '{}'",
|
||||||
|
msg,
|
||||||
|
filename
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("SqliteFailure expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_close_retry() {
|
fn test_close_retry() {
|
||||||
let db = checked_memory_handle();
|
let db = checked_memory_handle();
|
||||||
@ -994,23 +1032,25 @@ mod test {
|
|||||||
// statement first.
|
// statement first.
|
||||||
let raw_stmt = {
|
let raw_stmt = {
|
||||||
use super::str_to_cstring;
|
use super::str_to_cstring;
|
||||||
use std::mem;
|
use std::mem::MaybeUninit;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
let raw_db = db.db.borrow_mut().db;
|
let raw_db = db.db.borrow_mut().db;
|
||||||
let sql = "SELECT 1";
|
let sql = "SELECT 1";
|
||||||
let mut raw_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
|
let mut raw_stmt = MaybeUninit::uninit();
|
||||||
|
let cstring = str_to_cstring(sql).unwrap();
|
||||||
let rc = unsafe {
|
let rc = unsafe {
|
||||||
ffi::sqlite3_prepare_v2(
|
ffi::sqlite3_prepare_v2(
|
||||||
raw_db,
|
raw_db,
|
||||||
str_to_cstring(sql).unwrap().as_ptr(),
|
cstring.as_ptr(),
|
||||||
(sql.len() + 1) as c_int,
|
(sql.len() + 1) as c_int,
|
||||||
&mut raw_stmt,
|
raw_stmt.as_mut_ptr(),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
assert_eq!(rc, ffi::SQLITE_OK);
|
assert_eq!(rc, ffi::SQLITE_OK);
|
||||||
|
let raw_stmt: *mut ffi::sqlite3_stmt = unsafe { raw_stmt.assume_init() };
|
||||||
raw_stmt
|
raw_stmt
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1087,6 +1127,22 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "extra_check")]
|
||||||
|
fn test_execute_multiple() {
|
||||||
|
let db = checked_memory_handle();
|
||||||
|
let err = db
|
||||||
|
.execute(
|
||||||
|
"CREATE TABLE foo(x INTEGER); CREATE TABLE foo(x INTEGER)",
|
||||||
|
NO_PARAMS,
|
||||||
|
)
|
||||||
|
.unwrap_err();
|
||||||
|
match err {
|
||||||
|
Error::MultipleStatement => (),
|
||||||
|
_ => panic!("Unexpected error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_prepare_column_names() {
|
fn test_prepare_column_names() {
|
||||||
let db = checked_memory_handle();
|
let db = checked_memory_handle();
|
||||||
@ -1284,7 +1340,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "modern_sqlite")]
|
||||||
fn test_is_busy() {
|
fn test_is_busy() {
|
||||||
let db = checked_memory_handle();
|
let db = checked_memory_handle();
|
||||||
assert!(!db.is_busy());
|
assert!(!db.is_busy());
|
||||||
@ -1313,11 +1369,11 @@ mod test {
|
|||||||
fn test_notnull_constraint_error() {
|
fn test_notnull_constraint_error() {
|
||||||
// extended error codes for constraints were added in SQLite 3.7.16; if we're
|
// extended error codes for constraints were added in SQLite 3.7.16; if we're
|
||||||
// running on our bundled version, we know the extended error code exists.
|
// running on our bundled version, we know the extended error code exists.
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "modern_sqlite")]
|
||||||
fn check_extended_code(extended_code: c_int) {
|
fn check_extended_code(extended_code: c_int) {
|
||||||
assert_eq!(extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL);
|
assert_eq!(extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL);
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "modern_sqlite"))]
|
||||||
fn check_extended_code(_extended_code: c_int) {}
|
fn check_extended_code(_extended_code: c_int) {}
|
||||||
|
|
||||||
let db = checked_memory_handle();
|
let db = checked_memory_handle();
|
||||||
@ -1352,10 +1408,15 @@ mod test {
|
|||||||
|
|
||||||
let interrupt_handle = db.get_interrupt_handle();
|
let interrupt_handle = db.get_interrupt_handle();
|
||||||
|
|
||||||
db.create_scalar_function("interrupt", 0, false, move |_| {
|
db.create_scalar_function(
|
||||||
|
"interrupt",
|
||||||
|
0,
|
||||||
|
crate::functions::FunctionFlags::default(),
|
||||||
|
move |_| {
|
||||||
interrupt_handle.interrupt();
|
interrupt_handle.interrupt();
|
||||||
Ok(0)
|
Ok(0)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut stmt = db
|
let mut stmt = db
|
||||||
@ -1367,7 +1428,6 @@ mod test {
|
|||||||
match result.unwrap_err() {
|
match result.unwrap_err() {
|
||||||
Error::SqliteFailure(err, _) => {
|
Error::SqliteFailure(err, _) => {
|
||||||
assert_eq!(err.code, ErrorCode::OperationInterrupted);
|
assert_eq!(err.code, ErrorCode::OperationInterrupted);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
err => {
|
err => {
|
||||||
panic!("Unexpected error {}", err);
|
panic!("Unexpected error {}", err);
|
||||||
@ -1437,8 +1497,8 @@ mod test {
|
|||||||
impl fmt::Display for CustomError {
|
impl fmt::Display for CustomError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
CustomError::SomeError => write!(f, "{}", self.description()),
|
CustomError::SomeError => write!(f, "my custom error"),
|
||||||
CustomError::Sqlite(ref se) => write!(f, "{}: {}", self.description(), se),
|
CustomError::Sqlite(ref se) => write!(f, "my custom error: {}", se),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1504,7 +1564,7 @@ mod test {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match bad_type.unwrap_err() {
|
match bad_type.unwrap_err() {
|
||||||
Error::InvalidColumnType(_, _) => (),
|
Error::InvalidColumnType(..) => (),
|
||||||
err => panic!("Unexpected error {}", err),
|
err => panic!("Unexpected error {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1559,7 +1619,7 @@ mod test {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match bad_type.unwrap_err() {
|
match bad_type.unwrap_err() {
|
||||||
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (),
|
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
|
||||||
err => panic!("Unexpected error {}", err),
|
err => panic!("Unexpected error {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1616,7 +1676,7 @@ mod test {
|
|||||||
});
|
});
|
||||||
|
|
||||||
match bad_type.unwrap_err() {
|
match bad_type.unwrap_err() {
|
||||||
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (),
|
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
|
||||||
err => panic!("Unexpected error {}", err),
|
err => panic!("Unexpected error {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1665,5 +1725,26 @@ mod test {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_params() {
|
||||||
|
let db = checked_memory_handle();
|
||||||
|
db.query_row(
|
||||||
|
"SELECT
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?;",
|
||||||
|
params![
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
],
|
||||||
|
|r| {
|
||||||
|
assert_eq!(1, r.get_unwrap::<_, i32>(0));
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ impl Sql {
|
|||||||
self.push_real(r);
|
self.push_real(r);
|
||||||
}
|
}
|
||||||
ValueRef::Text(s) => {
|
ValueRef::Text(s) => {
|
||||||
|
let s = std::str::from_utf8(s)?;
|
||||||
self.push_string_literal(s);
|
self.push_string_literal(s);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -325,7 +326,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "modern_sqlite")]
|
||||||
fn pragma_func_query_value() {
|
fn pragma_func_query_value() {
|
||||||
use crate::NO_PARAMS;
|
use crate::NO_PARAMS;
|
||||||
|
|
||||||
@ -378,7 +379,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "modern_sqlite")]
|
||||||
fn pragma_func() {
|
fn pragma_func() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap();
|
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap();
|
||||||
|
@ -7,11 +7,15 @@ use std::ptr;
|
|||||||
|
|
||||||
// Private newtype for raw sqlite3_stmts that finalize themselves when dropped.
|
// Private newtype for raw sqlite3_stmts that finalize themselves when dropped.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RawStatement(*mut ffi::sqlite3_stmt);
|
pub struct RawStatement(*mut ffi::sqlite3_stmt, bool);
|
||||||
|
|
||||||
impl RawStatement {
|
impl RawStatement {
|
||||||
pub fn new(stmt: *mut ffi::sqlite3_stmt) -> RawStatement {
|
pub fn new(stmt: *mut ffi::sqlite3_stmt, tail: bool) -> RawStatement {
|
||||||
RawStatement(stmt)
|
RawStatement(stmt, tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_null(&self) -> bool {
|
||||||
|
self.0.is_null()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt {
|
pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt {
|
||||||
@ -37,8 +41,21 @@ impl RawStatement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn column_name(&self, idx: usize) -> &CStr {
|
pub fn column_name(&self, idx: usize) -> Option<&CStr> {
|
||||||
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx as c_int)) }
|
let idx = idx as c_int;
|
||||||
|
if idx < 0 || idx >= self.column_count() as c_int {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let ptr = ffi::sqlite3_column_name(self.0, idx);
|
||||||
|
// If ptr is null here, it's an OOM, so there's probably nothing
|
||||||
|
// meaningful we can do. Just assert instead of returning None.
|
||||||
|
assert!(
|
||||||
|
!ptr.is_null(),
|
||||||
|
"Null pointer from sqlite3_column_name: Out of memory?"
|
||||||
|
);
|
||||||
|
Some(CStr::from_ptr(ptr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&self) -> c_int {
|
pub fn step(&self) -> c_int {
|
||||||
@ -82,8 +99,12 @@ impl RawStatement {
|
|||||||
unsafe { ffi::sqlite3_clear_bindings(self.0) }
|
unsafe { ffi::sqlite3_clear_bindings(self.0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sql(&self) -> &CStr {
|
pub fn sql(&self) -> Option<&CStr> {
|
||||||
unsafe { CStr::from_ptr(ffi::sqlite3_sql(self.0)) }
|
if self.0.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(unsafe { CStr::from_ptr(ffi::sqlite3_sql(self.0)) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finalize(mut self) -> c_int {
|
pub fn finalize(mut self) -> c_int {
|
||||||
@ -96,14 +117,14 @@ impl RawStatement {
|
|||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "modern_sqlite")] // 3.7.4
|
||||||
pub fn readonly(&self) -> bool {
|
pub fn readonly(&self) -> bool {
|
||||||
unsafe { ffi::sqlite3_stmt_readonly(self.0) != 0 }
|
unsafe { ffi::sqlite3_stmt_readonly(self.0) != 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bundled")]
|
/// `CStr` must be freed
|
||||||
pub fn expanded_sql(&self) -> Option<&CStr> {
|
#[cfg(feature = "modern_sqlite")] // 3.14.0
|
||||||
unsafe {
|
pub unsafe fn expanded_sql(&self) -> Option<&CStr> {
|
||||||
let ptr = ffi::sqlite3_expanded_sql(self.0);
|
let ptr = ffi::sqlite3_expanded_sql(self.0);
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
None
|
None
|
||||||
@ -111,12 +132,15 @@ impl RawStatement {
|
|||||||
Some(CStr::from_ptr(ptr))
|
Some(CStr::from_ptr(ptr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 {
|
pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 {
|
||||||
assert!(!self.0.is_null());
|
assert!(!self.0.is_null());
|
||||||
unsafe { ffi::sqlite3_stmt_status(self.0, status as i32, reset as i32) }
|
unsafe { ffi::sqlite3_stmt_status(self.0, status as i32, reset as i32) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_tail(&self) -> bool {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for RawStatement {
|
impl Drop for RawStatement {
|
||||||
|
18
src/row.rs
18
src/row.rs
@ -223,15 +223,27 @@ impl<'stmt> Row<'stmt> {
|
|||||||
let idx = idx.idx(self.stmt)?;
|
let idx = idx.idx(self.stmt)?;
|
||||||
let value = self.stmt.value_ref(idx);
|
let value = self.stmt.value_ref(idx);
|
||||||
FromSql::column_result(value).map_err(|err| match err {
|
FromSql::column_result(value).map_err(|err| match err {
|
||||||
FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
|
FromSqlError::InvalidType => Error::InvalidColumnType(
|
||||||
|
idx,
|
||||||
|
self.stmt.column_name_unwrap(idx).into(),
|
||||||
|
value.data_type(),
|
||||||
|
),
|
||||||
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
||||||
FromSqlError::Other(err) => {
|
FromSqlError::Other(err) => {
|
||||||
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
|
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()),
|
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(
|
||||||
|
idx,
|
||||||
|
self.stmt.column_name_unwrap(idx).into(),
|
||||||
|
value.data_type(),
|
||||||
|
),
|
||||||
#[cfg(feature = "uuid")]
|
#[cfg(feature = "uuid")]
|
||||||
FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(idx, value.data_type()),
|
FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(
|
||||||
|
idx,
|
||||||
|
self.stmt.column_name_unwrap(idx).into(),
|
||||||
|
value.data_type(),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
106
src/session.rs
106
src/session.rs
@ -4,7 +4,7 @@
|
|||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem;
|
use std::mem::MaybeUninit;
|
||||||
use std::os::raw::{c_char, c_int, c_uchar, c_void};
|
use std::os::raw::{c_char, c_int, c_uchar, c_void};
|
||||||
use std::panic::{catch_unwind, RefUnwindSafe};
|
use std::panic::{catch_unwind, RefUnwindSafe};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
@ -43,8 +43,9 @@ impl Session<'_> {
|
|||||||
|
|
||||||
let db = db.db.borrow_mut().db;
|
let db = db.db.borrow_mut().db;
|
||||||
|
|
||||||
let mut s: *mut ffi::sqlite3_session = unsafe { mem::uninitialized() };
|
let mut s = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) });
|
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), s.as_mut_ptr()) });
|
||||||
|
let s: *mut ffi::sqlite3_session = unsafe { s.assume_init() };
|
||||||
|
|
||||||
Ok(Session {
|
Ok(Session {
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
@ -65,7 +66,6 @@ impl Session<'_> {
|
|||||||
where
|
where
|
||||||
F: Fn(&str) -> bool + RefUnwindSafe,
|
F: Fn(&str) -> bool + RefUnwindSafe,
|
||||||
{
|
{
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
let boxed_filter: *mut F = p_arg as *mut F;
|
let boxed_filter: *mut F = p_arg as *mut F;
|
||||||
@ -113,8 +113,9 @@ impl Session<'_> {
|
|||||||
/// Generate a Changeset
|
/// Generate a Changeset
|
||||||
pub fn changeset(&mut self) -> Result<Changeset> {
|
pub fn changeset(&mut self) -> Result<Changeset> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
let mut cs = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) });
|
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, cs.as_mut_ptr()) });
|
||||||
|
let cs: *mut c_void = unsafe { cs.assume_init() };
|
||||||
Ok(Changeset { cs, n })
|
Ok(Changeset { cs, n })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,8 +135,9 @@ impl Session<'_> {
|
|||||||
/// Generate a Patchset
|
/// Generate a Patchset
|
||||||
pub fn patchset(&mut self) -> Result<Changeset> {
|
pub fn patchset(&mut self) -> Result<Changeset> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut ps: *mut c_void = unsafe { mem::uninitialized() };
|
let mut ps = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) });
|
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, ps.as_mut_ptr()) });
|
||||||
|
let ps: *mut c_void = unsafe { ps.assume_init() };
|
||||||
// TODO Validate: same struct
|
// TODO Validate: same struct
|
||||||
Ok(Changeset { cs: ps, n })
|
Ok(Changeset { cs: ps, n })
|
||||||
}
|
}
|
||||||
@ -158,9 +160,10 @@ impl Session<'_> {
|
|||||||
let from = from.to_cstring()?;
|
let from = from.to_cstring()?;
|
||||||
let table = str_to_cstring(table)?.as_ptr();
|
let table = str_to_cstring(table)?.as_ptr();
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut errmsg: *mut c_char = mem::uninitialized();
|
let mut errmsg = MaybeUninit::uninit();
|
||||||
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, &mut errmsg);
|
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, errmsg.as_mut_ptr());
|
||||||
if r != ffi::SQLITE_OK {
|
if r != ffi::SQLITE_OK {
|
||||||
|
let errmsg: *mut c_char = errmsg.assume_init();
|
||||||
let message = errmsg_to_string(&*errmsg);
|
let message = errmsg_to_string(&*errmsg);
|
||||||
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
||||||
return Err(error_from_sqlite_code(r, Some(message)));
|
return Err(error_from_sqlite_code(r, Some(message)));
|
||||||
@ -255,15 +258,17 @@ impl Changeset {
|
|||||||
/// Invert a changeset
|
/// Invert a changeset
|
||||||
pub fn invert(&self) -> Result<Changeset> {
|
pub fn invert(&self) -> Result<Changeset> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
let mut cs = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs) });
|
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, cs.as_mut_ptr()) });
|
||||||
|
let cs: *mut c_void = unsafe { cs.assume_init() };
|
||||||
Ok(Changeset { cs, n })
|
Ok(Changeset { cs, n })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an iterator to traverse a changeset
|
/// Create an iterator to traverse a changeset
|
||||||
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
|
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
|
||||||
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
|
let mut it = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3changeset_start(&mut it, self.n, self.cs) });
|
check!(unsafe { ffi::sqlite3changeset_start(it.as_mut_ptr(), self.n, self.cs) });
|
||||||
|
let it: *mut ffi::sqlite3_changeset_iter = unsafe { it.assume_init() };
|
||||||
Ok(ChangesetIter {
|
Ok(ChangesetIter {
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
it,
|
it,
|
||||||
@ -274,8 +279,11 @@ impl Changeset {
|
|||||||
/// Concatenate two changeset objects
|
/// Concatenate two changeset objects
|
||||||
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
|
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
let mut cs = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs) });
|
check!(unsafe {
|
||||||
|
ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, cs.as_mut_ptr())
|
||||||
|
});
|
||||||
|
let cs: *mut c_void = unsafe { cs.assume_init() };
|
||||||
Ok(Changeset { cs, n })
|
Ok(Changeset { cs, n })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,16 +305,16 @@ pub struct ChangesetIter<'changeset> {
|
|||||||
|
|
||||||
impl ChangesetIter<'_> {
|
impl ChangesetIter<'_> {
|
||||||
/// Create an iterator on `input`
|
/// Create an iterator on `input`
|
||||||
pub fn start_strm<'input>(input: &'input mut dyn Read) -> Result<ChangesetIter<'input>> {
|
pub fn start_strm<'input>(input: &&'input mut dyn Read) -> Result<ChangesetIter<'input>> {
|
||||||
let input_ref = &input;
|
let mut it = MaybeUninit::uninit();
|
||||||
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
|
|
||||||
check!(unsafe {
|
check!(unsafe {
|
||||||
ffi::sqlite3changeset_start_strm(
|
ffi::sqlite3changeset_start_strm(
|
||||||
&mut it,
|
it.as_mut_ptr(),
|
||||||
Some(x_input),
|
Some(x_input),
|
||||||
input_ref as *const &mut dyn Read as *mut c_void,
|
input as *const &mut dyn Read as *mut c_void,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
let it: *mut ffi::sqlite3_changeset_iter = unsafe { it.assume_init() };
|
||||||
Ok(ChangesetIter {
|
Ok(ChangesetIter {
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
it,
|
it,
|
||||||
@ -386,12 +394,13 @@ impl ChangesetItem {
|
|||||||
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
|
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
|
||||||
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
|
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
let mut p_value = MaybeUninit::uninit();
|
||||||
check!(ffi::sqlite3changeset_conflict(
|
check!(ffi::sqlite3changeset_conflict(
|
||||||
self.it,
|
self.it,
|
||||||
col as i32,
|
col as i32,
|
||||||
&mut p_value
|
p_value.as_mut_ptr()
|
||||||
));
|
));
|
||||||
|
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
|
||||||
Ok(ValueRef::from_value(p_value))
|
Ok(ValueRef::from_value(p_value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -414,8 +423,13 @@ impl ChangesetItem {
|
|||||||
/// `SQLITE_INSERT`.
|
/// `SQLITE_INSERT`.
|
||||||
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
let mut p_value = MaybeUninit::uninit();
|
||||||
check!(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value));
|
check!(ffi::sqlite3changeset_new(
|
||||||
|
self.it,
|
||||||
|
col as i32,
|
||||||
|
p_value.as_mut_ptr()
|
||||||
|
));
|
||||||
|
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
|
||||||
Ok(ValueRef::from_value(p_value))
|
Ok(ValueRef::from_value(p_value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -426,8 +440,13 @@ impl ChangesetItem {
|
|||||||
/// `SQLITE_UPDATE`.
|
/// `SQLITE_UPDATE`.
|
||||||
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
let mut p_value = MaybeUninit::uninit();
|
||||||
check!(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value));
|
check!(ffi::sqlite3changeset_old(
|
||||||
|
self.it,
|
||||||
|
col as i32,
|
||||||
|
p_value.as_mut_ptr()
|
||||||
|
));
|
||||||
|
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
|
||||||
Ok(ValueRef::from_value(p_value))
|
Ok(ValueRef::from_value(p_value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -438,14 +457,15 @@ impl ChangesetItem {
|
|||||||
let mut code = 0;
|
let mut code = 0;
|
||||||
let mut indirect = 0;
|
let mut indirect = 0;
|
||||||
let tab = unsafe {
|
let tab = unsafe {
|
||||||
let mut pz_tab: *const c_char = mem::uninitialized();
|
let mut pz_tab = MaybeUninit::uninit();
|
||||||
check!(ffi::sqlite3changeset_op(
|
check!(ffi::sqlite3changeset_op(
|
||||||
self.it,
|
self.it,
|
||||||
&mut pz_tab,
|
pz_tab.as_mut_ptr(),
|
||||||
&mut number_of_columns,
|
&mut number_of_columns,
|
||||||
&mut code,
|
&mut code,
|
||||||
&mut indirect
|
&mut indirect
|
||||||
));
|
));
|
||||||
|
let pz_tab: *const c_char = pz_tab.assume_init();
|
||||||
CStr::from_ptr(pz_tab)
|
CStr::from_ptr(pz_tab)
|
||||||
};
|
};
|
||||||
let table_name = tab.to_str()?;
|
let table_name = tab.to_str()?;
|
||||||
@ -461,12 +481,13 @@ impl ChangesetItem {
|
|||||||
pub fn pk(&self) -> Result<&[u8]> {
|
pub fn pk(&self) -> Result<&[u8]> {
|
||||||
let mut number_of_columns = 0;
|
let mut number_of_columns = 0;
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut pks: *mut c_uchar = mem::uninitialized();
|
let mut pks = MaybeUninit::uninit();
|
||||||
check!(ffi::sqlite3changeset_pk(
|
check!(ffi::sqlite3changeset_pk(
|
||||||
self.it,
|
self.it,
|
||||||
&mut pks,
|
pks.as_mut_ptr(),
|
||||||
&mut number_of_columns
|
&mut number_of_columns
|
||||||
));
|
));
|
||||||
|
let pks: *mut c_uchar = pks.assume_init();
|
||||||
Ok(from_raw_parts(pks, number_of_columns as usize))
|
Ok(from_raw_parts(pks, number_of_columns as usize))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -480,8 +501,9 @@ pub struct Changegroup {
|
|||||||
|
|
||||||
impl Changegroup {
|
impl Changegroup {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let mut cg: *mut ffi::sqlite3_changegroup = unsafe { mem::uninitialized() };
|
let mut cg = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) });
|
check!(unsafe { ffi::sqlite3changegroup_new(cg.as_mut_ptr()) });
|
||||||
|
let cg: *mut ffi::sqlite3_changegroup = unsafe { cg.assume_init() };
|
||||||
Ok(Changegroup { cg })
|
Ok(Changegroup { cg })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -507,8 +529,9 @@ impl Changegroup {
|
|||||||
/// Obtain a composite Changeset
|
/// Obtain a composite Changeset
|
||||||
pub fn output(&mut self) -> Result<Changeset> {
|
pub fn output(&mut self) -> Result<Changeset> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
let mut output: *mut c_void = unsafe { mem::uninitialized() };
|
let mut output = MaybeUninit::uninit();
|
||||||
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) });
|
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, output.as_mut_ptr()) });
|
||||||
|
let output: *mut c_void = unsafe { output.assume_init() };
|
||||||
Ok(Changeset { cs: output, n })
|
Ok(Changeset { cs: output, n })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -648,7 +671,6 @@ where
|
|||||||
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
|
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
|
||||||
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
|
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
|
||||||
{
|
{
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
|
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
|
||||||
@ -691,7 +713,7 @@ unsafe extern "C" fn x_input(p_in: *mut c_void, data: *mut c_void, len: *mut c_i
|
|||||||
if p_in.is_null() {
|
if p_in.is_null() {
|
||||||
return ffi::SQLITE_MISUSE;
|
return ffi::SQLITE_MISUSE;
|
||||||
}
|
}
|
||||||
let bytes: &mut [u8] = from_raw_parts_mut(data as *mut u8, len as usize);
|
let bytes: &mut [u8] = from_raw_parts_mut(data as *mut u8, *len as usize);
|
||||||
let input = p_in as *mut &mut dyn Read;
|
let input = p_in as *mut &mut dyn Read;
|
||||||
match (*input).read(bytes) {
|
match (*input).read(bytes) {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
@ -720,6 +742,7 @@ unsafe extern "C" fn x_output(p_out: *mut c_void, data: *const c_void, len: c_in
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use fallible_streaming_iterator::FallibleStreamingIterator;
|
use fallible_streaming_iterator::FallibleStreamingIterator;
|
||||||
|
use std::io::Read;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session};
|
use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session};
|
||||||
@ -785,8 +808,8 @@ mod test {
|
|||||||
assert!(!output.is_empty());
|
assert!(!output.is_empty());
|
||||||
assert_eq!(14, output.len());
|
assert_eq!(14, output.len());
|
||||||
|
|
||||||
let mut input = output.as_slice();
|
let input: &mut dyn Read = &mut output.as_slice();
|
||||||
let mut iter = ChangesetIter::start_strm(&mut input).unwrap();
|
let mut iter = ChangesetIter::start_strm(&input).unwrap();
|
||||||
let item = iter.next().unwrap();
|
let item = iter.next().unwrap();
|
||||||
assert!(item.is_some());
|
assert!(item.is_some());
|
||||||
}
|
}
|
||||||
@ -799,7 +822,7 @@ mod test {
|
|||||||
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
db.apply(
|
db.apply(
|
||||||
@ -844,8 +867,9 @@ mod test {
|
|||||||
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let mut input = output.as_slice();
|
||||||
db.apply_strm(
|
db.apply_strm(
|
||||||
&mut output.as_slice(),
|
&mut input,
|
||||||
None::<fn(&str) -> bool>,
|
None::<fn(&str) -> bool>,
|
||||||
|_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
|
|_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
|
||||||
)
|
)
|
||||||
|
147
src/statement.rs
147
src/statement.rs
@ -45,7 +45,7 @@ impl Statement<'_> {
|
|||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails, the executed statement
|
/// Will return `Err` if binding parameters fails, the executed statement
|
||||||
/// returns rows (in which case `query` should be used instead), or the
|
/// returns rows (in which case `query` should be used instead), or the
|
||||||
/// underling SQLite call fails.
|
/// underlying SQLite call fails.
|
||||||
pub fn execute<P>(&mut self, params: P) -> Result<usize>
|
pub fn execute<P>(&mut self, params: P) -> Result<usize>
|
||||||
where
|
where
|
||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
@ -89,7 +89,7 @@ impl Statement<'_> {
|
|||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails, the executed statement
|
/// Will return `Err` if binding parameters fails, the executed statement
|
||||||
/// returns rows (in which case `query` should be used instead), or the
|
/// returns rows (in which case `query` should be used instead), or the
|
||||||
/// underling SQLite call fails.
|
/// underlying SQLite call fails.
|
||||||
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
||||||
self.bind_parameters_named(params)?;
|
self.bind_parameters_named(params)?;
|
||||||
self.execute_with_bound_parameters()
|
self.execute_with_bound_parameters()
|
||||||
@ -378,6 +378,29 @@ impl Statement<'_> {
|
|||||||
rows.get_expected_row().and_then(|r| f(&r))
|
rows.get_expected_row().and_then(|r| f(&r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience method to execute a query with named parameter(s) that is
|
||||||
|
/// expected to return a single row.
|
||||||
|
///
|
||||||
|
/// If the query returns more than one row, all rows except the first are
|
||||||
|
/// ignored.
|
||||||
|
///
|
||||||
|
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
|
||||||
|
/// query truly is optional, you can call `.optional()` on the result of
|
||||||
|
/// this to get a `Result<Option<T>>`.
|
||||||
|
///
|
||||||
|
/// # Failure
|
||||||
|
///
|
||||||
|
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||||
|
/// or if the underlying SQLite call fails.
|
||||||
|
pub fn query_row_named<T, F>(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
|
{
|
||||||
|
let mut rows = self.query_named(params)?;
|
||||||
|
|
||||||
|
rows.get_expected_row().and_then(|r| f(&r))
|
||||||
|
}
|
||||||
|
|
||||||
/// Consumes the statement.
|
/// Consumes the statement.
|
||||||
///
|
///
|
||||||
/// Functionally equivalent to the `Drop` implementation, but allows
|
/// Functionally equivalent to the `Drop` implementation, but allows
|
||||||
@ -488,6 +511,7 @@ impl Statement<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn execute_with_bound_parameters(&mut self) -> Result<usize> {
|
fn execute_with_bound_parameters(&mut self) -> Result<usize> {
|
||||||
|
self.check_update()?;
|
||||||
let r = self.stmt.step();
|
let r = self.stmt.step();
|
||||||
self.stmt.reset();
|
self.stmt.reset();
|
||||||
match r {
|
match r {
|
||||||
@ -504,18 +528,18 @@ impl Statement<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn finalize_(&mut self) -> Result<()> {
|
fn finalize_(&mut self) -> Result<()> {
|
||||||
let mut stmt = RawStatement::new(ptr::null_mut());
|
let mut stmt = RawStatement::new(ptr::null_mut(), false);
|
||||||
mem::swap(&mut stmt, &mut self.stmt);
|
mem::swap(&mut stmt, &mut self.stmt);
|
||||||
self.conn.decode_result(stmt.finalize())
|
self.conn.decode_result(stmt.finalize())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "modern_sqlite"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn check_readonly(&self) -> Result<()> {
|
fn check_readonly(&self) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "modern_sqlite")]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn check_readonly(&self) -> Result<()> {
|
fn check_readonly(&self) -> Result<()> {
|
||||||
/*if !self.stmt.readonly() { does not work for PRAGMA
|
/*if !self.stmt.readonly() { does not work for PRAGMA
|
||||||
@ -524,14 +548,43 @@ impl Statement<'_> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "modern_sqlite", feature = "extra_check"))]
|
||||||
|
#[inline]
|
||||||
|
fn check_update(&self) -> Result<()> {
|
||||||
|
if self.column_count() > 0 || self.stmt.readonly() {
|
||||||
|
return Err(Error::ExecuteReturnedResults);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(not(feature = "modern_sqlite"), feature = "extra_check"))]
|
||||||
|
#[inline]
|
||||||
|
fn check_update(&self) -> Result<()> {
|
||||||
|
if self.column_count() > 0 {
|
||||||
|
return Err(Error::ExecuteReturnedResults);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "extra_check"))]
|
||||||
|
#[inline]
|
||||||
|
fn check_update(&self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a string containing the SQL text of prepared statement with
|
/// Returns a string containing the SQL text of prepared statement with
|
||||||
/// bound parameters expanded.
|
/// bound parameters expanded.
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "modern_sqlite")]
|
||||||
pub fn expanded_sql(&self) -> Option<&str> {
|
pub fn expanded_sql(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.stmt
|
match self.stmt.expanded_sql() {
|
||||||
.expanded_sql()
|
Some(s) => {
|
||||||
.map(|s| str::from_utf8_unchecked(s.to_bytes()))
|
let sql = str::from_utf8_unchecked(s.to_bytes()).to_owned();
|
||||||
|
ffi::sqlite3_free(s.as_ptr() as *mut _);
|
||||||
|
Some(sql)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,11 +598,26 @@ impl Statement<'_> {
|
|||||||
pub fn reset_status(&self, status: StatementStatus) -> i32 {
|
pub fn reset_status(&self, status: StatementStatus) -> i32 {
|
||||||
self.stmt.get_status(status, true)
|
self.stmt.get_status(status, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_check")]
|
||||||
|
pub(crate) fn check_no_tail(&self) -> Result<()> {
|
||||||
|
if self.stmt.has_tail() {
|
||||||
|
Err(Error::MultipleStatement)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "extra_check"))]
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn check_no_tail(&self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<RawStatement> for Statement<'_> {
|
impl Into<RawStatement> for Statement<'_> {
|
||||||
fn into(mut self) -> RawStatement {
|
fn into(mut self) -> RawStatement {
|
||||||
let mut stmt = RawStatement::new(ptr::null_mut());
|
let mut stmt = RawStatement::new(ptr::null_mut(), false);
|
||||||
mem::swap(&mut stmt, &mut self.stmt);
|
mem::swap(&mut stmt, &mut self.stmt);
|
||||||
stmt
|
stmt
|
||||||
}
|
}
|
||||||
@ -557,7 +625,11 @@ impl Into<RawStatement> for Statement<'_> {
|
|||||||
|
|
||||||
impl fmt::Debug for Statement<'_> {
|
impl fmt::Debug for Statement<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let sql = str::from_utf8(self.stmt.sql().to_bytes());
|
let sql = if self.stmt.is_null() {
|
||||||
|
Ok("")
|
||||||
|
} else {
|
||||||
|
str::from_utf8(self.stmt.sql().unwrap().to_bytes())
|
||||||
|
};
|
||||||
f.debug_struct("Statement")
|
f.debug_struct("Statement")
|
||||||
.field("conn", self.conn)
|
.field("conn", self.conn)
|
||||||
.field("stmt", &self.stmt)
|
.field("stmt", &self.stmt)
|
||||||
@ -599,10 +671,7 @@ impl Statement<'_> {
|
|||||||
CStr::from_ptr(text as *const c_char)
|
CStr::from_ptr(text as *const c_char)
|
||||||
};
|
};
|
||||||
|
|
||||||
// sqlite3_column_text returns UTF8 data, so our unwrap here should be fine.
|
let s = s.to_bytes();
|
||||||
let s = s
|
|
||||||
.to_str()
|
|
||||||
.expect("sqlite3_column_text returned invalid UTF-8");
|
|
||||||
ValueRef::Text(s)
|
ValueRef::Text(s)
|
||||||
}
|
}
|
||||||
ffi::SQLITE_BLOB => {
|
ffi::SQLITE_BLOB => {
|
||||||
@ -716,13 +785,12 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
stmt.execute_named(&[(":name", &"one")]).unwrap();
|
stmt.execute_named(&[(":name", &"one")]).unwrap();
|
||||||
|
|
||||||
|
let mut stmt = db
|
||||||
|
.prepare("SELECT COUNT(*) FROM test WHERE name = :name")
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
1i32,
|
1i32,
|
||||||
db.query_row_named::<i32, _>(
|
stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))
|
||||||
"SELECT COUNT(*) FROM test WHERE name = :name",
|
|
||||||
&[(":name", &"one")],
|
|
||||||
|r| r.get(0)
|
|
||||||
)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -951,12 +1019,12 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "modern_sqlite")]
|
||||||
fn test_expanded_sql() {
|
fn test_expanded_sql() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
let stmt = db.prepare("SELECT ?").unwrap();
|
let stmt = db.prepare("SELECT ?").unwrap();
|
||||||
stmt.bind_parameter(&1, 1).unwrap();
|
stmt.bind_parameter(&1, 1).unwrap();
|
||||||
assert_eq!(Some("SELECT 1"), stmt.expanded_sql());
|
assert_eq!(Some("SELECT 1".to_owned()), stmt.expanded_sql());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -983,7 +1051,7 @@ mod test {
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
let data: BTreeSet<String> = ["one", "two", "three"]
|
let data: BTreeSet<String> = ["one", "two", "three"]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.to_string())
|
.map(|s| (*s).to_string())
|
||||||
.collect();
|
.collect();
|
||||||
db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, String>(0))
|
db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, String>(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -994,4 +1062,35 @@ mod test {
|
|||||||
db.query_row("SELECT ?1, ?2, ?3", data.iter(), |row| row.get::<_, u8>(0))
|
db.query_row("SELECT ?1, ?2, ?3", data.iter(), |row| row.get::<_, u8>(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_stmt() {
|
||||||
|
let conn = Connection::open_in_memory().unwrap();
|
||||||
|
let mut stmt = conn.prepare("").unwrap();
|
||||||
|
assert_eq!(0, stmt.column_count());
|
||||||
|
assert!(stmt.parameter_index("test").is_ok());
|
||||||
|
assert!(stmt.step().is_err());
|
||||||
|
stmt.reset();
|
||||||
|
assert!(stmt.execute(NO_PARAMS).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_comment_stmt() {
|
||||||
|
let conn = Connection::open_in_memory().unwrap();
|
||||||
|
conn.prepare("/*SELECT 1;*/").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_comment_and_sql_stmt() {
|
||||||
|
let conn = Connection::open_in_memory().unwrap();
|
||||||
|
let stmt = conn.prepare("/*...*/ SELECT 1;").unwrap();
|
||||||
|
assert_eq!(1, stmt.column_count());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_semi_colon_stmt() {
|
||||||
|
let conn = Connection::open_in_memory().unwrap();
|
||||||
|
let stmt = conn.prepare(";").unwrap();
|
||||||
|
assert_eq!(0, stmt.column_count());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,9 @@ use crate::error::error_from_sqlite_code;
|
|||||||
use crate::{Connection, Result};
|
use crate::{Connection, Result};
|
||||||
|
|
||||||
/// Set up the process-wide SQLite error logging callback.
|
/// Set up the process-wide SQLite error logging callback.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
/// This function is marked unsafe for two reasons:
|
/// This function is marked unsafe for two reasons:
|
||||||
///
|
///
|
||||||
/// * The function is not threadsafe. No other SQLite calls may be made while
|
/// * The function is not threadsafe. No other SQLite calls may be made while
|
||||||
@ -122,6 +125,7 @@ impl Connection {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
|
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
|
||||||
use chrono;
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
||||||
|
|
||||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
@ -54,7 +53,7 @@ impl FromSql for NaiveTime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// ISO 8601 combined date and time without timezone =>
|
/// ISO 8601 combined date and time without timezone =>
|
||||||
/// "YYYY-MM-DD HH:MM:SS.SSS"
|
/// "YYYY-MM-DDTHH:MM:SS.SSS"
|
||||||
impl ToSql for NaiveDateTime {
|
impl ToSql for NaiveDateTime {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
|
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
|
||||||
@ -129,10 +128,8 @@ impl FromSql for DateTime<Local> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::chrono::{
|
|
||||||
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
|
|
||||||
};
|
|
||||||
use crate::{Connection, Result, NO_PARAMS};
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
@ -36,7 +36,7 @@ impl PartialEq for FromSqlError {
|
|||||||
(FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
|
(FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
|
||||||
#[cfg(feature = "uuid")]
|
#[cfg(feature = "uuid")]
|
||||||
(FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
|
(FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
|
||||||
(_, _) => false,
|
(..) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +60,7 @@ impl fmt::Display for FromSqlError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Error for FromSqlError {
|
impl Error for FromSqlError {
|
||||||
|
#[allow(deprecated)]
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
FromSqlError::InvalidType => "invalid type",
|
FromSqlError::InvalidType => "invalid type",
|
||||||
@ -166,6 +167,24 @@ impl FromSql for String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromSql for Box<str> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
value.as_str().map(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql for std::rc::Rc<str> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
value.as_str().map(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql for std::sync::Arc<str> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
value.as_str().map(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromSql for Vec<u8> {
|
impl FromSql for Vec<u8> {
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
value.as_blob().map(|b| b.to_vec())
|
value.as_blob().map(|b| b.to_vec())
|
||||||
|
@ -42,10 +42,6 @@
|
|||||||
//! Ok(as_f64.into())
|
//! Ok(as_f64.into())
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
|
||||||
//! # // Prevent this doc test from being wrapped in a `fn main()` so that it
|
|
||||||
//! # // will compile.
|
|
||||||
//! # fn main() {}
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T`
|
//! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T`
|
||||||
@ -78,7 +74,7 @@ mod value_ref;
|
|||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use rusqlite::{Connection, Result};
|
/// # use rusqlite::{Connection, Result};
|
||||||
/// # use rusqlite::types::{Null};
|
/// # use rusqlite::types::{Null};
|
||||||
/// fn main() {}
|
///
|
||||||
/// fn insert_null(conn: &Connection) -> Result<usize> {
|
/// fn insert_null(conn: &Connection) -> Result<usize> {
|
||||||
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[Null])
|
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[Null])
|
||||||
/// }
|
/// }
|
||||||
@ -229,7 +225,7 @@ mod test {
|
|||||||
fn test_mismatched_types() {
|
fn test_mismatched_types() {
|
||||||
fn is_invalid_column_type(err: Error) -> bool {
|
fn is_invalid_column_type(err: Error) -> bool {
|
||||||
match err {
|
match err {
|
||||||
Error::InvalidColumnType(_, _) => true,
|
Error::InvalidColumnType(..) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
//! `ToSql` and `FromSql` implementation for JSON `Value`.
|
//! `ToSql` and `FromSql` implementation for JSON `Value`.
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
use self::serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
@ -17,7 +16,7 @@ impl ToSql for Value {
|
|||||||
impl FromSql for Value {
|
impl FromSql for Value {
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
ValueRef::Text(s) => serde_json::from_str(s),
|
ValueRef::Text(s) => serde_json::from_slice(s),
|
||||||
ValueRef::Blob(b) => serde_json::from_slice(b),
|
ValueRef::Blob(b) => serde_json::from_slice(b),
|
||||||
_ => return Err(FromSqlError::InvalidType),
|
_ => return Err(FromSqlError::InvalidType),
|
||||||
}
|
}
|
||||||
@ -27,9 +26,9 @@ impl FromSql for Value {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::serde_json;
|
|
||||||
use crate::types::ToSql;
|
use crate::types::ToSql;
|
||||||
use crate::{Connection, NO_PARAMS};
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
@ -36,8 +36,8 @@ impl FromSql for time::Timespec {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::time;
|
|
||||||
use crate::{Connection, Result, NO_PARAMS};
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
use time;
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
@ -14,10 +14,12 @@ impl ToSql for Url {
|
|||||||
impl FromSql for Url {
|
impl FromSql for Url {
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
ValueRef::Text(s) => Url::parse(s),
|
ValueRef::Text(s) => {
|
||||||
_ => return Err(FromSqlError::InvalidType),
|
let s = std::str::from_utf8(s).map_err(|e| FromSqlError::Other(Box::new(e)))?;
|
||||||
|
Url::parse(s).map_err(|e| FromSqlError::Other(Box::new(e)))
|
||||||
|
}
|
||||||
|
_ => Err(FromSqlError::InvalidType),
|
||||||
}
|
}
|
||||||
.map_err(|err| FromSqlError::Other(Box::new(err)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +96,18 @@ impl From<Vec<u8>> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> From<Option<T>> for Value
|
||||||
|
where
|
||||||
|
T: Into<Value>,
|
||||||
|
{
|
||||||
|
fn from(v: Option<T>) -> Value {
|
||||||
|
match v {
|
||||||
|
Some(x) => x.into(),
|
||||||
|
None => Value::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
pub fn data_type(&self) -> Type {
|
pub fn data_type(&self) -> Type {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -14,7 +14,7 @@ pub enum ValueRef<'a> {
|
|||||||
/// The value is a floating point number.
|
/// The value is a floating point number.
|
||||||
Real(f64),
|
Real(f64),
|
||||||
/// The value is a text string.
|
/// The value is a text string.
|
||||||
Text(&'a str),
|
Text(&'a [u8]),
|
||||||
/// The value is a blob of data
|
/// The value is a blob of data
|
||||||
Blob(&'a [u8]),
|
Blob(&'a [u8]),
|
||||||
}
|
}
|
||||||
@ -54,7 +54,9 @@ impl<'a> ValueRef<'a> {
|
|||||||
/// `Err(Error::InvalidColumnType)`.
|
/// `Err(Error::InvalidColumnType)`.
|
||||||
pub fn as_str(&self) -> FromSqlResult<&'a str> {
|
pub fn as_str(&self) -> FromSqlResult<&'a str> {
|
||||||
match *self {
|
match *self {
|
||||||
ValueRef::Text(t) => Ok(t),
|
ValueRef::Text(t) => {
|
||||||
|
std::str::from_utf8(t).map_err(|e| FromSqlError::Other(Box::new(e)))
|
||||||
|
}
|
||||||
_ => Err(FromSqlError::InvalidType),
|
_ => Err(FromSqlError::InvalidType),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +77,10 @@ impl From<ValueRef<'_>> for Value {
|
|||||||
ValueRef::Null => Value::Null,
|
ValueRef::Null => Value::Null,
|
||||||
ValueRef::Integer(i) => Value::Integer(i),
|
ValueRef::Integer(i) => Value::Integer(i),
|
||||||
ValueRef::Real(r) => Value::Real(r),
|
ValueRef::Real(r) => Value::Real(r),
|
||||||
ValueRef::Text(s) => Value::Text(s.to_string()),
|
ValueRef::Text(s) => {
|
||||||
|
let s = std::str::from_utf8(s).expect("invalid UTF-8");
|
||||||
|
Value::Text(s.to_string())
|
||||||
|
}
|
||||||
ValueRef::Blob(b) => Value::Blob(b.to_vec()),
|
ValueRef::Blob(b) => Value::Blob(b.to_vec()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +88,7 @@ impl From<ValueRef<'_>> for Value {
|
|||||||
|
|
||||||
impl<'a> From<&'a str> for ValueRef<'a> {
|
impl<'a> From<&'a str> for ValueRef<'a> {
|
||||||
fn from(s: &str) -> ValueRef<'_> {
|
fn from(s: &str) -> ValueRef<'_> {
|
||||||
ValueRef::Text(s)
|
ValueRef::Text(s.as_bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,12 +104,24 @@ impl<'a> From<&'a Value> for ValueRef<'a> {
|
|||||||
Value::Null => ValueRef::Null,
|
Value::Null => ValueRef::Null,
|
||||||
Value::Integer(i) => ValueRef::Integer(i),
|
Value::Integer(i) => ValueRef::Integer(i),
|
||||||
Value::Real(r) => ValueRef::Real(r),
|
Value::Real(r) => ValueRef::Real(r),
|
||||||
Value::Text(ref s) => ValueRef::Text(s),
|
Value::Text(ref s) => ValueRef::Text(s.as_bytes()),
|
||||||
Value::Blob(ref b) => ValueRef::Blob(b),
|
Value::Blob(ref b) => ValueRef::Blob(b),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T> From<Option<T>> for ValueRef<'a>
|
||||||
|
where
|
||||||
|
T: Into<ValueRef<'a>>,
|
||||||
|
{
|
||||||
|
fn from(s: Option<T>) -> ValueRef<'a> {
|
||||||
|
match s {
|
||||||
|
Some(x) => x.into(),
|
||||||
|
None => ValueRef::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "functions", feature = "session", feature = "vtab"))]
|
#[cfg(any(feature = "functions", feature = "session", feature = "vtab"))]
|
||||||
impl<'a> ValueRef<'a> {
|
impl<'a> ValueRef<'a> {
|
||||||
pub(crate) unsafe fn from_value(value: *mut crate::ffi::sqlite3_value) -> ValueRef<'a> {
|
pub(crate) unsafe fn from_value(value: *mut crate::ffi::sqlite3_value) -> ValueRef<'a> {
|
||||||
@ -125,10 +142,7 @@ impl<'a> ValueRef<'a> {
|
|||||||
);
|
);
|
||||||
let s = CStr::from_ptr(text as *const c_char);
|
let s = CStr::from_ptr(text as *const c_char);
|
||||||
|
|
||||||
// sqlite3_value_text returns UTF8 data, so our unwrap here should be fine.
|
let s = s.to_bytes();
|
||||||
let s = s
|
|
||||||
.to_str()
|
|
||||||
.expect("sqlite3_value_text returned invalid UTF-8");
|
|
||||||
ValueRef::Text(s)
|
ValueRef::Text(s)
|
||||||
}
|
}
|
||||||
ffi::SQLITE_BLOB => {
|
ffi::SQLITE_BLOB => {
|
||||||
|
@ -17,6 +17,7 @@ struct UnlockNotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "unlock_notify")]
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
#[allow(clippy::mutex_atomic)]
|
||||||
impl UnlockNotification {
|
impl UnlockNotification {
|
||||||
fn new() -> UnlockNotification {
|
fn new() -> UnlockNotification {
|
||||||
UnlockNotification {
|
UnlockNotification {
|
||||||
|
@ -35,7 +35,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
|
|||||||
conn.create_module("rarray", &ARRAY_MODULE, aux)
|
conn.create_module("rarray", &ARRAY_MODULE, aux)
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1);
|
static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
|
|||||||
conn.create_module("csv", &CSV_MODULE, aux)
|
conn.create_module("csv", &CSV_MODULE, aux)
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1);
|
static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ impl VTab for CSVTab {
|
|||||||
schema = Some(sql);
|
schema = Some(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((schema.unwrap().to_owned(), vtab))
|
Ok((schema.unwrap(), vtab))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only a forward full table scan is supported.
|
// Only a forward full table scan is supported.
|
||||||
@ -338,8 +338,7 @@ impl VTabCursor for CSVTabCursor {
|
|||||||
|
|
||||||
impl From<csv::Error> for Error {
|
impl From<csv::Error> for Error {
|
||||||
fn from(err: csv::Error) -> Error {
|
fn from(err: csv::Error) -> Error {
|
||||||
use std::error::Error as StdError;
|
Error::ModuleError(err.to_string())
|
||||||
Error::ModuleError(String::from(err.description()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
115
src/vtab/mod.rs
115
src/vtab/mod.rs
@ -67,8 +67,17 @@ pub struct Module<T: VTab> {
|
|||||||
phantom: PhantomData<T>,
|
phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: VTab> Send for Module<T> {}
|
||||||
unsafe impl<T: VTab> Sync for Module<T> {}
|
unsafe impl<T: VTab> Sync for Module<T> {}
|
||||||
|
|
||||||
|
// Used as a trailing initializer for sqlite3_module -- this way we avoid having
|
||||||
|
// the build fail if buildtime_bindgen is on, our bindings have
|
||||||
|
// `sqlite3_module::xShadowName`, but vtab_v3 wasn't specified.
|
||||||
|
fn zeroed_module() -> ffi::sqlite3_module {
|
||||||
|
// This is safe, as bindgen-generated structs are allowed to be zeroed.
|
||||||
|
unsafe { std::mem::MaybeUninit::zeroed().assume_init() }
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a read-only virtual table implementation.
|
/// Create a read-only virtual table implementation.
|
||||||
///
|
///
|
||||||
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
|
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
|
||||||
@ -99,8 +108,7 @@ pub fn read_only_module<T: CreateVTab>(version: c_int) -> Module<T> {
|
|||||||
xSavepoint: None,
|
xSavepoint: None,
|
||||||
xRelease: None,
|
xRelease: None,
|
||||||
xRollbackTo: None,
|
xRollbackTo: None,
|
||||||
#[cfg(any(feature = "bundled", feature = "vtab_v3"))]
|
..zeroed_module()
|
||||||
xShadowName: None,
|
|
||||||
};
|
};
|
||||||
Module {
|
Module {
|
||||||
base: ffi_module,
|
base: ffi_module,
|
||||||
@ -139,8 +147,7 @@ pub fn eponymous_only_module<T: VTab>(version: c_int) -> Module<T> {
|
|||||||
xSavepoint: None,
|
xSavepoint: None,
|
||||||
xRelease: None,
|
xRelease: None,
|
||||||
xRollbackTo: None,
|
xRollbackTo: None,
|
||||||
#[cfg(any(feature = "bundled", feature = "vtab_v3"))]
|
..zeroed_module()
|
||||||
xShadowName: None,
|
|
||||||
};
|
};
|
||||||
Module {
|
Module {
|
||||||
base: ffi_module,
|
base: ffi_module,
|
||||||
@ -161,7 +168,11 @@ impl VTabConnection {
|
|||||||
///
|
///
|
||||||
/// You should not need to use this function. If you do need to, please
|
/// You should not need to use this function. If you do need to, please
|
||||||
/// [open an issue on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe
|
/// [open an issue on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe
|
||||||
/// your use case. This function is unsafe because it gives you raw access
|
/// your use case.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function is unsafe because it gives you raw access
|
||||||
/// to the SQLite connection, and what you do with it could impact the
|
/// to the SQLite connection, and what you do with it could impact the
|
||||||
/// safety of this `Connection`.
|
/// safety of this `Connection`.
|
||||||
pub unsafe fn handle(&mut self) -> *mut ffi::sqlite3 {
|
pub unsafe fn handle(&mut self) -> *mut ffi::sqlite3 {
|
||||||
@ -233,16 +244,46 @@ pub trait CreateVTab: VTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
///Index constraint operator.
|
||||||
#[doc = "Index constraint operator."]
|
#[derive(Debug, PartialEq)]
|
||||||
#[repr(C)]
|
#[allow(non_snake_case, non_camel_case_types)]
|
||||||
pub struct IndexConstraintOp: ::std::os::raw::c_uchar {
|
pub enum IndexConstraintOp {
|
||||||
const SQLITE_INDEX_CONSTRAINT_EQ = 2;
|
SQLITE_INDEX_CONSTRAINT_EQ,
|
||||||
const SQLITE_INDEX_CONSTRAINT_GT = 4;
|
SQLITE_INDEX_CONSTRAINT_GT,
|
||||||
const SQLITE_INDEX_CONSTRAINT_LE = 8;
|
SQLITE_INDEX_CONSTRAINT_LE,
|
||||||
const SQLITE_INDEX_CONSTRAINT_LT = 16;
|
SQLITE_INDEX_CONSTRAINT_LT,
|
||||||
const SQLITE_INDEX_CONSTRAINT_GE = 32;
|
SQLITE_INDEX_CONSTRAINT_GE,
|
||||||
const SQLITE_INDEX_CONSTRAINT_MATCH = 64;
|
SQLITE_INDEX_CONSTRAINT_MATCH,
|
||||||
|
SQLITE_INDEX_CONSTRAINT_LIKE, // 3.10.0
|
||||||
|
SQLITE_INDEX_CONSTRAINT_GLOB, // 3.10.0
|
||||||
|
SQLITE_INDEX_CONSTRAINT_REGEXP, // 3.10.0
|
||||||
|
SQLITE_INDEX_CONSTRAINT_NE, // 3.21.0
|
||||||
|
SQLITE_INDEX_CONSTRAINT_ISNOT, // 3.21.0
|
||||||
|
SQLITE_INDEX_CONSTRAINT_ISNOTNULL, // 3.21.0
|
||||||
|
SQLITE_INDEX_CONSTRAINT_ISNULL, // 3.21.0
|
||||||
|
SQLITE_INDEX_CONSTRAINT_IS, // 3.21.0
|
||||||
|
SQLITE_INDEX_CONSTRAINT_FUNCTION(u8), // 3.25.0
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for IndexConstraintOp {
|
||||||
|
fn from(code: u8) -> IndexConstraintOp {
|
||||||
|
match code {
|
||||||
|
2 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ,
|
||||||
|
4 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GT,
|
||||||
|
8 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LE,
|
||||||
|
16 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LT,
|
||||||
|
32 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GE,
|
||||||
|
64 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_MATCH,
|
||||||
|
65 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIKE,
|
||||||
|
66 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GLOB,
|
||||||
|
67 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_REGEXP,
|
||||||
|
68 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_NE,
|
||||||
|
69 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOT,
|
||||||
|
70 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOTNULL,
|
||||||
|
71 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNULL,
|
||||||
|
72 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_IS,
|
||||||
|
v => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_FUNCTION(v),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,8 +345,8 @@ impl IndexInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Estimated number of rows returned
|
/// Estimated number of rows returned.
|
||||||
#[cfg(feature = "bundled")] // SQLite >= 3.8.2
|
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.8.2
|
||||||
pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
|
pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.0).estimatedRows = estimated_rows;
|
(*self.0).estimatedRows = estimated_rows;
|
||||||
@ -345,7 +386,7 @@ impl IndexConstraint<'_> {
|
|||||||
|
|
||||||
/// Constraint operator
|
/// Constraint operator
|
||||||
pub fn operator(&self) -> IndexConstraintOp {
|
pub fn operator(&self) -> IndexConstraintOp {
|
||||||
IndexConstraintOp::from_bits_truncate(self.0.op)
|
IndexConstraintOp::from(self.0.op)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True if this constraint is usable
|
/// True if this constraint is usable
|
||||||
@ -472,7 +513,9 @@ impl Values<'_> {
|
|||||||
}
|
}
|
||||||
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()),
|
FromSqlError::InvalidI128Size(_) => {
|
||||||
|
Error::InvalidColumnType(idx, idx.to_string(), value.data_type())
|
||||||
|
}
|
||||||
#[cfg(feature = "uuid")]
|
#[cfg(feature = "uuid")]
|
||||||
FromSqlError::InvalidUuidSize(_) => {
|
FromSqlError::InvalidUuidSize(_) => {
|
||||||
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
|
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
|
||||||
@ -643,9 +686,7 @@ unsafe extern "C" fn rust_create<T>(
|
|||||||
where
|
where
|
||||||
T: CreateVTab,
|
T: CreateVTab,
|
||||||
{
|
{
|
||||||
use std::error::Error as StdError;
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::slice;
|
|
||||||
|
|
||||||
let mut conn = VTabConnection(db);
|
let mut conn = VTabConnection(db);
|
||||||
let aux = aux as *mut T::Aux;
|
let aux = aux as *mut T::Aux;
|
||||||
@ -664,12 +705,12 @@ where
|
|||||||
ffi::SQLITE_OK
|
ffi::SQLITE_OK
|
||||||
} else {
|
} else {
|
||||||
let err = error_from_sqlite_code(rc, None);
|
let err = error_from_sqlite_code(rc, None);
|
||||||
*err_msg = mprintf(err.description());
|
*err_msg = mprintf(&err.to_string());
|
||||||
rc
|
rc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
*err_msg = mprintf(err.description());
|
*err_msg = mprintf(&err.to_string());
|
||||||
ffi::SQLITE_ERROR
|
ffi::SQLITE_ERROR
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -680,7 +721,7 @@ where
|
|||||||
err.extended_code
|
err.extended_code
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
*err_msg = mprintf(err.description());
|
*err_msg = mprintf(&err.to_string());
|
||||||
ffi::SQLITE_ERROR
|
ffi::SQLITE_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -697,9 +738,7 @@ unsafe extern "C" fn rust_connect<T>(
|
|||||||
where
|
where
|
||||||
T: VTab,
|
T: VTab,
|
||||||
{
|
{
|
||||||
use std::error::Error as StdError;
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::slice;
|
|
||||||
|
|
||||||
let mut conn = VTabConnection(db);
|
let mut conn = VTabConnection(db);
|
||||||
let aux = aux as *mut T::Aux;
|
let aux = aux as *mut T::Aux;
|
||||||
@ -718,12 +757,12 @@ where
|
|||||||
ffi::SQLITE_OK
|
ffi::SQLITE_OK
|
||||||
} else {
|
} else {
|
||||||
let err = error_from_sqlite_code(rc, None);
|
let err = error_from_sqlite_code(rc, None);
|
||||||
*err_msg = mprintf(err.description());
|
*err_msg = mprintf(&err.to_string());
|
||||||
rc
|
rc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
*err_msg = mprintf(err.description());
|
*err_msg = mprintf(&err.to_string());
|
||||||
ffi::SQLITE_ERROR
|
ffi::SQLITE_ERROR
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -734,7 +773,7 @@ where
|
|||||||
err.extended_code
|
err.extended_code
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
*err_msg = mprintf(err.description());
|
*err_msg = mprintf(&err.to_string());
|
||||||
ffi::SQLITE_ERROR
|
ffi::SQLITE_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -747,7 +786,6 @@ unsafe extern "C" fn rust_best_index<T>(
|
|||||||
where
|
where
|
||||||
T: VTab,
|
T: VTab,
|
||||||
{
|
{
|
||||||
use std::error::Error as StdError;
|
|
||||||
let vt = vtab as *mut T;
|
let vt = vtab as *mut T;
|
||||||
let mut idx_info = IndexInfo(info);
|
let mut idx_info = IndexInfo(info);
|
||||||
match (*vt).best_index(&mut idx_info) {
|
match (*vt).best_index(&mut idx_info) {
|
||||||
@ -759,7 +797,7 @@ where
|
|||||||
err.extended_code
|
err.extended_code
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
set_err_msg(vtab, err.description());
|
set_err_msg(vtab, &err.to_string());
|
||||||
ffi::SQLITE_ERROR
|
ffi::SQLITE_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -781,7 +819,6 @@ unsafe extern "C" fn rust_destroy<T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
|
|||||||
where
|
where
|
||||||
T: CreateVTab,
|
T: CreateVTab,
|
||||||
{
|
{
|
||||||
use std::error::Error as StdError;
|
|
||||||
if vtab.is_null() {
|
if vtab.is_null() {
|
||||||
return ffi::SQLITE_OK;
|
return ffi::SQLITE_OK;
|
||||||
}
|
}
|
||||||
@ -798,7 +835,7 @@ where
|
|||||||
err.extended_code
|
err.extended_code
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
set_err_msg(vtab, err.description());
|
set_err_msg(vtab, &err.to_string());
|
||||||
ffi::SQLITE_ERROR
|
ffi::SQLITE_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -811,7 +848,6 @@ unsafe extern "C" fn rust_open<T>(
|
|||||||
where
|
where
|
||||||
T: VTab,
|
T: VTab,
|
||||||
{
|
{
|
||||||
use std::error::Error as StdError;
|
|
||||||
let vt = vtab as *mut T;
|
let vt = vtab as *mut T;
|
||||||
match (*vt).open() {
|
match (*vt).open() {
|
||||||
Ok(cursor) => {
|
Ok(cursor) => {
|
||||||
@ -826,7 +862,7 @@ where
|
|||||||
err.extended_code
|
err.extended_code
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
set_err_msg(vtab, err.description());
|
set_err_msg(vtab, &err.to_string());
|
||||||
ffi::SQLITE_ERROR
|
ffi::SQLITE_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -852,7 +888,6 @@ where
|
|||||||
C: VTabCursor,
|
C: VTabCursor,
|
||||||
{
|
{
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::slice;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
let idx_name = if idx_str.is_null() {
|
let idx_name = if idx_str.is_null() {
|
||||||
None
|
None
|
||||||
@ -915,7 +950,6 @@ where
|
|||||||
/// Virtual table cursors can set an error message by assigning a string to
|
/// Virtual table cursors can set an error message by assigning a string to
|
||||||
/// `zErrMsg`.
|
/// `zErrMsg`.
|
||||||
unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
|
unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
|
||||||
use std::error::Error as StdError;
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => ffi::SQLITE_OK,
|
Ok(_) => ffi::SQLITE_OK,
|
||||||
Err(Error::SqliteFailure(err, s)) => {
|
Err(Error::SqliteFailure(err, s)) => {
|
||||||
@ -925,7 +959,7 @@ unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<
|
|||||||
err.extended_code
|
err.extended_code
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
set_err_msg((*cursor).pVtab, err.description());
|
set_err_msg((*cursor).pVtab, &err.to_string());
|
||||||
ffi::SQLITE_ERROR
|
ffi::SQLITE_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -943,7 +977,6 @@ unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
|
|||||||
/// To raise an error, the `column` method should use this method to set the
|
/// To raise an error, the `column` method should use this method to set the
|
||||||
/// error message and return the error code.
|
/// error message and return the error code.
|
||||||
unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int {
|
unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int {
|
||||||
use std::error::Error as StdError;
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => ffi::SQLITE_OK,
|
Ok(_) => ffi::SQLITE_OK,
|
||||||
Err(Error::SqliteFailure(err, s)) => {
|
Err(Error::SqliteFailure(err, s)) => {
|
||||||
@ -965,7 +998,7 @@ unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) ->
|
|||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_ERROR);
|
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_ERROR);
|
||||||
if let Ok(cstr) = str_to_cstring(err.description()) {
|
if let Ok(cstr) = str_to_cstring(&err.to_string()) {
|
||||||
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
|
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
|
||||||
}
|
}
|
||||||
ffi::SQLITE_ERROR
|
ffi::SQLITE_ERROR
|
||||||
@ -985,7 +1018,7 @@ fn mprintf(err_msg: &str) -> *mut c_char {
|
|||||||
pub mod array;
|
pub mod array;
|
||||||
#[cfg(feature = "csvtab")]
|
#[cfg(feature = "csvtab")]
|
||||||
pub mod csvtab;
|
pub mod csvtab;
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "series")]
|
||||||
pub mod series; // SQLite >= 3.9.0
|
pub mod series; // SQLite >= 3.9.0
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -18,7 +18,7 @@ pub fn load_module(conn: &Connection) -> Result<()> {
|
|||||||
conn.create_module("generate_series", &SERIES_MODULE, aux)
|
conn.create_module("generate_series", &SERIES_MODULE, aux)
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref SERIES_MODULE: Module<SeriesTab> = eponymous_only_module::<SeriesTab>(1);
|
static ref SERIES_MODULE: Module<SeriesTab> = eponymous_only_module::<SeriesTab>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ const SERIES_COLUMN_START: c_int = 1;
|
|||||||
const SERIES_COLUMN_STOP: c_int = 2;
|
const SERIES_COLUMN_STOP: c_int = 2;
|
||||||
const SERIES_COLUMN_STEP: c_int = 3;
|
const SERIES_COLUMN_STEP: c_int = 3;
|
||||||
|
|
||||||
bitflags! {
|
bitflags::bitflags! {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct QueryPlanFlags: ::std::os::raw::c_int {
|
struct QueryPlanFlags: ::std::os::raw::c_int {
|
||||||
// start = $value -- constraint exists
|
// start = $value -- constraint exists
|
||||||
|
@ -2,12 +2,9 @@
|
|||||||
//! function affects SQLite process-wide and so is not safe to run as a normal
|
//! function affects SQLite process-wide and so is not safe to run as a normal
|
||||||
//! #[test] in the library.
|
//! #[test] in the library.
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
fn main() {
|
fn main() {
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user