mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 09:09:19 +08:00
Merge remote-tracking branch 'jgallagher/master' into tail
This commit is contained in:
commit
e8e76852bd
@ -8,6 +8,7 @@ rust:
|
|||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
|
|
||||||
|
13
Cargo.toml
13
Cargo.toml
@ -1,7 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.15.0"
|
version = "0.16.0"
|
||||||
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
||||||
|
edition = "2018"
|
||||||
description = "Ergonomic wrapper for SQLite"
|
description = "Ergonomic wrapper for SQLite"
|
||||||
repository = "https://github.com/jgallagher/rusqlite"
|
repository = "https://github.com/jgallagher/rusqlite"
|
||||||
documentation = "http://docs.rs/rusqlite/"
|
documentation = "http://docs.rs/rusqlite/"
|
||||||
@ -18,6 +19,9 @@ maintenance = { status = "actively-developed" }
|
|||||||
[lib]
|
[lib]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["libsqlite3-sys"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
load_extension = []
|
load_extension = []
|
||||||
# hot-backup interface: 3.6.11 (2009-02-18)
|
# hot-backup interface: 3.6.11 (2009-02-18)
|
||||||
@ -37,9 +41,13 @@ sqlcipher = ["libsqlite3-sys/sqlcipher"]
|
|||||||
unlock_notify = ["libsqlite3-sys/unlock_notify"]
|
unlock_notify = ["libsqlite3-sys/unlock_notify"]
|
||||||
# xSavepoint, xRelease and xRollbackTo: 3.7.7 (2011-06-23)
|
# xSavepoint, xRelease and xRollbackTo: 3.7.7 (2011-06-23)
|
||||||
vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"]
|
vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"]
|
||||||
|
# xShadowName: 3.26.0
|
||||||
|
vtab_v3 = ["vtab"]
|
||||||
csvtab = ["csv", "vtab"]
|
csvtab = ["csv", "vtab"]
|
||||||
# pointer passing interfaces: 3.20.0
|
# pointer passing interfaces: 3.20.0
|
||||||
array = ["vtab"]
|
array = ["vtab"]
|
||||||
|
# session extension: 3.13.0
|
||||||
|
session = ["libsqlite3-sys/session", "hooks", "fallible-streaming-iterator"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
time = "0.1.0"
|
time = "0.1.0"
|
||||||
@ -50,6 +58,7 @@ serde_json = { version = "1.0", optional = true }
|
|||||||
csv = { version = "1.0", optional = true }
|
csv = { version = "1.0", optional = true }
|
||||||
lazy_static = { version = "1.0", optional = true }
|
lazy_static = { version = "1.0", optional = true }
|
||||||
byteorder = { version = "1.2", features = ["i128"], optional = true }
|
byteorder = { version = "1.2", features = ["i128"], optional = true }
|
||||||
|
fallible-streaming-iterator = { version = "0.1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
@ -58,7 +67,7 @@ regex = "1.0"
|
|||||||
|
|
||||||
[dependencies.libsqlite3-sys]
|
[dependencies.libsqlite3-sys]
|
||||||
path = "libsqlite3-sys"
|
path = "libsqlite3-sys"
|
||||||
version = "0.10"
|
version = "0.12"
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "config_log"
|
name = "config_log"
|
||||||
|
30
README.md
30
README.md
@ -10,9 +10,6 @@ Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expo
|
|||||||
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
|
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern crate rusqlite;
|
|
||||||
extern crate time;
|
|
||||||
|
|
||||||
use rusqlite::types::ToSql;
|
use rusqlite::types::ToSql;
|
||||||
use rusqlite::{Connection, NO_PARAMS};
|
use rusqlite::{Connection, NO_PARAMS};
|
||||||
use time::Timespec;
|
use time::Timespec;
|
||||||
@ -77,26 +74,26 @@ newer SQLite version; see details below.
|
|||||||
Rusqlite provides several features that are behind [Cargo
|
Rusqlite provides several features that are behind [Cargo
|
||||||
features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are:
|
features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are:
|
||||||
|
|
||||||
* [`load_extension`](https://docs.rs/rusqlite/0.15.0/rusqlite/struct.LoadExtensionGuard.html)
|
* [`load_extension`](https://docs.rs/rusqlite/0.16.0/rusqlite/struct.LoadExtensionGuard.html)
|
||||||
allows loading dynamic library-based SQLite extensions.
|
allows loading dynamic library-based SQLite extensions.
|
||||||
* [`backup`](https://docs.rs/rusqlite/0.15.0/rusqlite/backup/index.html)
|
* [`backup`](https://docs.rs/rusqlite/0.16.0/rusqlite/backup/index.html)
|
||||||
allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later.
|
allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later.
|
||||||
* [`functions`](https://docs.rs/rusqlite/0.15.0/rusqlite/functions/index.html)
|
* [`functions`](https://docs.rs/rusqlite/0.16.0/rusqlite/functions/index.html)
|
||||||
allows you to load Rust closures into SQLite connections for use in queries.
|
allows you to load Rust closures into SQLite connections for use in queries.
|
||||||
Note: This feature requires SQLite 3.7.3 or later.
|
Note: This feature requires SQLite 3.7.3 or later.
|
||||||
* [`trace`](https://docs.rs/rusqlite/0.15.0/rusqlite/trace/index.html)
|
* [`trace`](https://docs.rs/rusqlite/0.16.0/rusqlite/trace/index.html)
|
||||||
allows hooks into SQLite's tracing and profiling APIs. Note: This feature
|
allows hooks into SQLite's tracing and profiling APIs. Note: This feature
|
||||||
requires SQLite 3.6.23 or later.
|
requires SQLite 3.6.23 or later.
|
||||||
* [`blob`](https://docs.rs/rusqlite/0.15.0/rusqlite/blob/index.html)
|
* [`blob`](https://docs.rs/rusqlite/0.16.0/rusqlite/blob/index.html)
|
||||||
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature
|
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature
|
||||||
requires SQLite 3.7.4 or later.
|
requires SQLite 3.7.4 or later.
|
||||||
* [`limits`](https://docs.rs/rusqlite/0.15.0/rusqlite/struct.Connection.html#method.limit)
|
* [`limits`](https://docs.rs/rusqlite/0.16.0/rusqlite/struct.Connection.html#method.limit)
|
||||||
allows you to set and retrieve SQLite's per connection limits.
|
allows you to set and retrieve SQLite's per connection limits.
|
||||||
* `chrono` implements [`FromSql`](https://docs.rs/rusqlite/0.15.0/rusqlite/types/trait.FromSql.html)
|
* `chrono` implements [`FromSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.FromSql.html)
|
||||||
and [`ToSql`](https://docs.rs/rusqlite/0.15.0/rusqlite/types/trait.ToSql.html) for various
|
and [`ToSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.ToSql.html) for various
|
||||||
types from the [`chrono` crate](https://crates.io/crates/chrono).
|
types from the [`chrono` crate](https://crates.io/crates/chrono).
|
||||||
* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/0.15.0/rusqlite/types/trait.FromSql.html)
|
* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.FromSql.html)
|
||||||
and [`ToSql`](https://docs.rs/rusqlite/0.15.0/rusqlite/types/trait.ToSql.html) for the
|
and [`ToSql`](https://docs.rs/rusqlite/0.16.0/rusqlite/types/trait.ToSql.html) for the
|
||||||
`Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json).
|
`Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json).
|
||||||
* `bundled` uses a bundled version of sqlite3. This is a good option for cases where linking to sqlite3 is complicated, such as Windows.
|
* `bundled` uses a bundled version of sqlite3. This is a good option for cases where linking to sqlite3 is complicated, such as Windows.
|
||||||
* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`.
|
* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`.
|
||||||
@ -106,6 +103,7 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
|
|||||||
* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
|
* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
|
||||||
* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function.
|
* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function.
|
||||||
* `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected.
|
* `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected.
|
||||||
|
* [`session`](https://sqlite.org/sessionintro.html), Session module extension.
|
||||||
|
|
||||||
## Notes on building rusqlite and libsqlite3-sys
|
## Notes on building rusqlite and libsqlite3-sys
|
||||||
|
|
||||||
@ -118,11 +116,11 @@ You can adjust this behavior in a number of ways:
|
|||||||
* If you use the `bundled` feature, `libsqlite3-sys` will use the
|
* If you use the `bundled` feature, `libsqlite3-sys` will use the
|
||||||
[gcc](https://crates.io/crates/gcc) crate to compile SQLite from source and
|
[gcc](https://crates.io/crates/gcc) crate to compile SQLite from source and
|
||||||
link against that. This source is embedded in the `libsqlite3-sys` crate and
|
link against that. This source is embedded in the `libsqlite3-sys` crate and
|
||||||
is currently SQLite 3.25.2 (as of `rusqlite` 0.15.0 / `libsqlite3-sys`
|
is currently SQLite 3.26.0 (as of `rusqlite` 0.16.0 / `libsqlite3-sys`
|
||||||
0.10.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
|
0.11.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
|
||||||
```
|
```
|
||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
version = "0.15.0"
|
version = "0.16.0"
|
||||||
features = ["bundled"]
|
features = ["bundled"]
|
||||||
```
|
```
|
||||||
* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite
|
* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite
|
||||||
|
@ -5,9 +5,9 @@ environment:
|
|||||||
- TARGET: x86_64-pc-windows-msvc
|
- TARGET: x86_64-pc-windows-msvc
|
||||||
VCPKG_DEFAULT_TRIPLET: x64-windows
|
VCPKG_DEFAULT_TRIPLET: x64-windows
|
||||||
VCPKGRS_DYNAMIC: 1
|
VCPKGRS_DYNAMIC: 1
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
# - TARGET: x86_64-pc-windows-msvc
|
||||||
VCPKG_DEFAULT_TRIPLET: x64-windows-static
|
# VCPKG_DEFAULT_TRIPLET: x64-windows-static
|
||||||
RUSTFLAGS: -Ctarget-feature=+crt-static
|
# RUSTFLAGS: -Ctarget-feature=+crt-static
|
||||||
install:
|
install:
|
||||||
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||||
- rustup-init.exe -y --default-host %TARGET%
|
- rustup-init.exe -y --default-host %TARGET%
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
extern crate rusqlite;
|
|
||||||
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.10.0"
|
version = "0.12.0"
|
||||||
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
|
||||||
|
edition = "2018"
|
||||||
repository = "https://github.com/jgallagher/rusqlite"
|
repository = "https://github.com/jgallagher/rusqlite"
|
||||||
description = "Native bindings to the libsqlite3 library"
|
description = "Native bindings to the libsqlite3 library"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@ -18,11 +19,16 @@ sqlcipher = []
|
|||||||
min_sqlite_version_3_6_8 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_6_8 = ["pkg-config", "vcpkg"]
|
||||||
min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"]
|
||||||
min_sqlite_version_3_7_7 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_7_7 = ["pkg-config", "vcpkg"]
|
||||||
|
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
|
||||||
# sqlite3_unlock_notify >= 3.6.12
|
# sqlite3_unlock_notify >= 3.6.12
|
||||||
unlock_notify = []
|
unlock_notify = []
|
||||||
|
# 3.13.0
|
||||||
|
preupdate_hook = []
|
||||||
|
# 3.13.0
|
||||||
|
session = ["preupdate_hook"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = { version = "0.43", optional = true }
|
bindgen = { version = "0.47", optional = true }
|
||||||
pkg-config = { version = "0.3", optional = true }
|
pkg-config = { version = "0.3", optional = true }
|
||||||
cc = { version = "1.0", optional = true }
|
cc = { version = "1.0", optional = true }
|
||||||
|
|
||||||
|
2297
libsqlite3-sys/bindgen-bindings/bindgen_3.7.16.rs
Normal file
2297
libsqlite3-sys/bindgen-bindings/bindgen_3.7.16.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,22 +1,34 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
build::main();
|
let out_dir = env::var("OUT_DIR").unwrap();
|
||||||
|
let out_path = Path::new(&out_dir).join("bindgen.rs");
|
||||||
|
build::main(&out_dir, &out_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(feature = "bundled")]
|
||||||
mod build {
|
mod build {
|
||||||
extern crate cc;
|
use cc;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{env, fs};
|
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main(out_dir: &str, out_path: &Path) {
|
||||||
if cfg!(feature = "sqlcipher") {
|
if cfg!(feature = "sqlcipher") {
|
||||||
panic!("Builds with bundled SQLCipher are not supported");
|
panic!("Builds with bundled SQLCipher are not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
#[cfg(feature = "buildtime_bindgen")]
|
||||||
let out_path = Path::new(&out_dir).join("bindgen.rs");
|
{
|
||||||
fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
|
use super::{bindings, HeaderLocation};
|
||||||
.expect("Could not copy bindings to output directory");
|
let header = HeaderLocation::FromPath("sqlite3/sqlite3.h".to_owned());
|
||||||
|
bindings::write_to_out_dir(header, out_path);
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "buildtime_bindgen"))]
|
||||||
|
{
|
||||||
|
use std::fs;
|
||||||
|
fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
|
||||||
|
.expect("Could not copy bindings to output directory");
|
||||||
|
}
|
||||||
|
|
||||||
let mut cfg = cc::Build::new();
|
let mut cfg = cc::Build::new();
|
||||||
cfg.file("sqlite3/sqlite3.c")
|
cfg.file("sqlite3/sqlite3.c")
|
||||||
@ -42,62 +54,87 @@ mod build {
|
|||||||
if cfg!(feature = "unlock_notify") {
|
if cfg!(feature = "unlock_notify") {
|
||||||
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
|
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
|
||||||
}
|
}
|
||||||
|
if cfg!(feature = "preupdate_hook") {
|
||||||
|
cfg.flag("-DSQLITE_ENABLE_PREUPDATE_HOOK");
|
||||||
|
}
|
||||||
|
if cfg!(feature = "session") {
|
||||||
|
cfg.flag("-DSQLITE_ENABLE_SESSION");
|
||||||
|
}
|
||||||
cfg.compile("libsqlite3.a");
|
cfg.compile("libsqlite3.a");
|
||||||
|
|
||||||
println!("cargo:lib_dir={}", out_dir);
|
println!("cargo:lib_dir={}", out_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn env_prefix() -> &'static str {
|
||||||
|
if cfg!(feature = "sqlcipher") {
|
||||||
|
"SQLCIPHER"
|
||||||
|
} else {
|
||||||
|
"SQLITE3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HeaderLocation {
|
||||||
|
FromEnvironment,
|
||||||
|
Wrapper,
|
||||||
|
FromPath(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HeaderLocation> for String {
|
||||||
|
fn from(header: HeaderLocation) -> String {
|
||||||
|
match header {
|
||||||
|
HeaderLocation::FromEnvironment => {
|
||||||
|
let prefix = env_prefix();
|
||||||
|
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!(
|
||||||
|
"{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
|
||||||
|
prefix, prefix
|
||||||
|
));
|
||||||
|
header.push_str("/sqlite3.h");
|
||||||
|
header
|
||||||
|
}
|
||||||
|
HeaderLocation::Wrapper => "wrapper.h".into(),
|
||||||
|
HeaderLocation::FromPath(path) => path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
#[cfg(not(feature = "bundled"))]
|
||||||
mod build {
|
mod build {
|
||||||
extern crate pkg_config;
|
use pkg_config;
|
||||||
|
|
||||||
#[cfg(all(feature = "vcpkg", target_env = "msvc"))]
|
#[cfg(all(feature = "vcpkg", target_env = "msvc"))]
|
||||||
extern crate vcpkg;
|
extern crate vcpkg;
|
||||||
|
|
||||||
|
use super::{bindings, env_prefix, HeaderLocation};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
pub enum HeaderLocation {
|
pub fn main(_out_dir: &str, out_path: &Path) {
|
||||||
FromEnvironment,
|
let header = find_sqlite();
|
||||||
Wrapper,
|
bindings::write_to_out_dir(header, out_path);
|
||||||
FromPath(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<HeaderLocation> for String {
|
fn find_link_mode() -> &'static str {
|
||||||
fn from(header: HeaderLocation) -> String {
|
// If the user specifies SQLITE_STATIC (or SQLCIPHER_STATIC), do static
|
||||||
match header {
|
// linking, unless it's explicitly set to 0.
|
||||||
HeaderLocation::FromEnvironment => {
|
match &env::var(format!("{}_STATIC", env_prefix())) {
|
||||||
let prefix = env_prefix();
|
Ok(v) if v != "0" => "static",
|
||||||
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!(
|
_ => "dylib",
|
||||||
"{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
|
|
||||||
prefix, prefix
|
|
||||||
));
|
|
||||||
header.push_str("/sqlite3.h");
|
|
||||||
header
|
|
||||||
}
|
|
||||||
HeaderLocation::Wrapper => "wrapper.h".into(),
|
|
||||||
HeaderLocation::FromPath(path) => path,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let header = find_sqlite();
|
|
||||||
bindings::write_to_out_dir(header);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prints the necessary cargo link commands and returns the path to the header.
|
// Prints the necessary cargo link commands and returns the path to the header.
|
||||||
fn find_sqlite() -> HeaderLocation {
|
fn find_sqlite() -> HeaderLocation {
|
||||||
let link_lib = link_lib();
|
let link_lib = link_lib();
|
||||||
|
|
||||||
println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix());
|
println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix());
|
||||||
println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix());
|
println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix());
|
||||||
|
println!("cargo:rerun-if-env-changed={}_STATIC", env_prefix());
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
println!("cargo:rerun-if-env-changed=PATH");
|
println!("cargo:rerun-if-env-changed=PATH");
|
||||||
}
|
}
|
||||||
// Allow users to specify where to find SQLite.
|
// Allow users to specify where to find SQLite.
|
||||||
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
|
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
|
||||||
println!("cargo:rustc-link-lib={}", link_lib);
|
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
|
||||||
println!("cargo:rustc-link-search={}", dir);
|
println!("cargo:rustc-link-search={}", dir);
|
||||||
return HeaderLocation::FromEnvironment;
|
return HeaderLocation::FromEnvironment;
|
||||||
}
|
}
|
||||||
@ -122,9 +159,9 @@ mod build {
|
|||||||
Err(_) => {
|
Err(_) => {
|
||||||
// No env var set and pkg-config couldn't help; just output the link-lib
|
// No env var set and pkg-config couldn't help; just output the link-lib
|
||||||
// request and hope that the library exists on the system paths. We used to
|
// request and hope that the library exists on the system paths. We used to
|
||||||
// output /usr/lib explicitly, but that can introduce other linking problems; see
|
// output /usr/lib explicitly, but that can introduce other linking problems;
|
||||||
// https://github.com/jgallagher/rusqlite/issues/207.
|
// see https://github.com/jgallagher/rusqlite/issues/207.
|
||||||
println!("cargo:rustc-link-lib={}", link_lib);
|
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
|
||||||
HeaderLocation::Wrapper
|
HeaderLocation::Wrapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,14 +184,6 @@ mod build {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn env_prefix() -> &'static str {
|
|
||||||
if cfg!(feature = "sqlcipher") {
|
|
||||||
"SQLCIPHER"
|
|
||||||
} else {
|
|
||||||
"SQLITE3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn link_lib() -> &'static str {
|
fn link_lib() -> &'static str {
|
||||||
if cfg!(feature = "sqlcipher") {
|
if cfg!(feature = "sqlcipher") {
|
||||||
"sqlcipher"
|
"sqlcipher"
|
||||||
@ -162,88 +191,98 @@ mod build {
|
|||||||
"sqlite3"
|
"sqlite3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "buildtime_bindgen"))]
|
#[cfg(all(not(feature = "buildtime_bindgen"), not(feature = "bundled")))]
|
||||||
mod bindings {
|
mod bindings {
|
||||||
use super::HeaderLocation;
|
use super::HeaderLocation;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::fs;
|
||||||
use std::{env, fs};
|
use std::path::Path;
|
||||||
|
|
||||||
static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[
|
static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[
|
||||||
"bindgen-bindings/bindgen_3.6.8.rs",
|
"bindgen-bindings/bindgen_3.6.8.rs",
|
||||||
#[cfg(feature = "min_sqlite_version_3_6_23")]
|
#[cfg(feature = "min_sqlite_version_3_6_23")]
|
||||||
"bindgen-bindings/bindgen_3.6.23.rs",
|
"bindgen-bindings/bindgen_3.6.23.rs",
|
||||||
#[cfg(feature = "min_sqlite_version_3_7_7")]
|
#[cfg(feature = "min_sqlite_version_3_7_7")]
|
||||||
"bindgen-bindings/bindgen_3.7.7.rs",
|
"bindgen-bindings/bindgen_3.7.7.rs",
|
||||||
];
|
#[cfg(feature = "min_sqlite_version_3_7_16")]
|
||||||
|
"bindgen-bindings/bindgen_3.7.16.rs",
|
||||||
|
];
|
||||||
|
|
||||||
pub fn write_to_out_dir(_header: HeaderLocation) {
|
pub fn write_to_out_dir(_header: HeaderLocation, out_path: &Path) {
|
||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
let in_path = PREBUILT_BINDGEN_PATHS[PREBUILT_BINDGEN_PATHS.len() - 1];
|
||||||
let out_path = Path::new(&out_dir).join("bindgen.rs");
|
fs::copy(in_path, out_path).expect("Could not copy bindings to output directory");
|
||||||
let in_path = PREBUILT_BINDGEN_PATHS[PREBUILT_BINDGEN_PATHS.len() - 1];
|
}
|
||||||
fs::copy(in_path, out_path).expect("Could not copy bindings to output directory");
|
}
|
||||||
}
|
|
||||||
}
|
#[cfg(feature = "buildtime_bindgen")]
|
||||||
|
mod bindings {
|
||||||
#[cfg(feature = "buildtime_bindgen")]
|
use bindgen;
|
||||||
mod bindings {
|
|
||||||
extern crate bindgen;
|
use self::bindgen::callbacks::{IntKind, ParseCallbacks};
|
||||||
|
use super::HeaderLocation;
|
||||||
use self::bindgen::callbacks::{IntKind, ParseCallbacks};
|
|
||||||
use super::HeaderLocation;
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::Write;
|
||||||
use std::env;
|
use std::path::Path;
|
||||||
use std::fs::OpenOptions;
|
|
||||||
use std::io::Write;
|
#[derive(Debug)]
|
||||||
use std::path::Path;
|
struct SqliteTypeChooser;
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl ParseCallbacks for SqliteTypeChooser {
|
||||||
struct SqliteTypeChooser;
|
fn int_macro(&self, _name: &str, value: i64) -> Option<IntKind> {
|
||||||
|
if value >= i32::min_value() as i64 && value <= i32::max_value() as i64 {
|
||||||
impl ParseCallbacks for SqliteTypeChooser {
|
Some(IntKind::I32)
|
||||||
fn int_macro(&self, _name: &str, value: i64) -> Option<IntKind> {
|
} else {
|
||||||
if value >= i32::min_value() as i64 && value <= i32::max_value() as i64 {
|
None
|
||||||
Some(IntKind::I32)
|
}
|
||||||
} else {
|
}
|
||||||
None
|
}
|
||||||
}
|
|
||||||
}
|
pub fn write_to_out_dir(header: HeaderLocation, out_path: &Path) {
|
||||||
}
|
let header: String = header.into();
|
||||||
|
let mut output = Vec::new();
|
||||||
pub fn write_to_out_dir(header: HeaderLocation) {
|
let mut bindings = bindgen::builder()
|
||||||
let header: String = header.into();
|
.header(header.clone())
|
||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
.parse_callbacks(Box::new(SqliteTypeChooser))
|
||||||
let mut output = Vec::new();
|
.rustfmt_bindings(true);
|
||||||
bindgen::builder()
|
|
||||||
.header(header.clone())
|
if cfg!(feature = "unlock_notify") {
|
||||||
.parse_callbacks(Box::new(SqliteTypeChooser))
|
bindings = bindings.clang_arg("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
|
||||||
.rustfmt_bindings(true)
|
}
|
||||||
.generate()
|
if cfg!(feature = "preupdate_hook") {
|
||||||
.expect(&format!("could not run bindgen on header {}", header))
|
bindings = bindings.clang_arg("-DSQLITE_ENABLE_PREUPDATE_HOOK");
|
||||||
.write(Box::new(&mut output))
|
}
|
||||||
.expect("could not write output of bindgen");
|
if cfg!(feature = "session") {
|
||||||
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
|
bindings = bindings.clang_arg("-DSQLITE_ENABLE_SESSION");
|
||||||
|
}
|
||||||
// rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it can. This flag
|
|
||||||
// was added in SQLite 3.8.3, but oring it in in prior versions of SQLite is harmless. We
|
bindings
|
||||||
// don't want to not build just because this flag is missing (e.g., if we're linking against
|
.generate()
|
||||||
// SQLite 3.7.x), so append the flag manually if it isn't present in bindgen's output.
|
.expect(&format!("could not run bindgen on header {}", header))
|
||||||
if !output.contains("pub const SQLITE_DETERMINISTIC") {
|
.write(Box::new(&mut output))
|
||||||
output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n");
|
.expect("could not write output of bindgen");
|
||||||
}
|
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
|
||||||
|
|
||||||
let path = Path::new(&out_dir).join("bindgen.rs");
|
// rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it
|
||||||
|
// can. This flag was added in SQLite 3.8.3, but oring it in in prior
|
||||||
let mut file = OpenOptions::new()
|
// versions of SQLite is harmless. We don't want to not build just
|
||||||
.write(true)
|
// because this flag is missing (e.g., if we're linking against
|
||||||
.truncate(true)
|
// SQLite 3.7.x), so append the flag manually if it isn't present in bindgen's
|
||||||
.create(true)
|
// output.
|
||||||
.open(path.clone())
|
if !output.contains("pub const SQLITE_DETERMINISTIC") {
|
||||||
.expect(&format!("Could not write to {:?}", path));
|
output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n");
|
||||||
|
}
|
||||||
file.write_all(output.as_bytes())
|
|
||||||
.expect(&format!("Could not write to {:?}", path));
|
let mut file = OpenOptions::new()
|
||||||
}
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
|
.create(true)
|
||||||
|
.open(out_path.clone())
|
||||||
|
.expect(&format!("Could not write to {:?}", out_path));
|
||||||
|
|
||||||
|
file.write_all(output.as_bytes())
|
||||||
|
.expect(&format!("Could not write to {:?}", out_path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
191
libsqlite3-sys/sqlite3/bindgen_bundled_version.rs
vendored
191
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.25.2\0";
|
pub const SQLITE_VERSION: &'static [u8; 7usize] = b"3.26.0\0";
|
||||||
pub const SQLITE_VERSION_NUMBER: i32 = 3025002;
|
pub const SQLITE_VERSION_NUMBER: i32 = 3026000;
|
||||||
pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] =
|
pub const SQLITE_SOURCE_ID: &'static [u8; 85usize] =
|
||||||
b"2018-09-25 19:08:10 fb90e7189ae6d62e77ba3a308ca5d683f90bbe633cf681865365b8e92792d1c7\0";
|
b"2018-12-01 12:34:55 bf8c1b2b7a5960c282e543b9c293686dccff272512d08865f4600fb58238b4f9\0";
|
||||||
pub const SQLITE_OK: i32 = 0;
|
pub const SQLITE_OK: i32 = 0;
|
||||||
pub const SQLITE_ERROR: i32 = 1;
|
pub const SQLITE_ERROR: i32 = 1;
|
||||||
pub const SQLITE_INTERNAL: i32 = 2;
|
pub const SQLITE_INTERNAL: i32 = 2;
|
||||||
@ -228,7 +228,8 @@ pub const SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: i32 = 1006;
|
|||||||
pub const SQLITE_DBCONFIG_ENABLE_QPSG: i32 = 1007;
|
pub const SQLITE_DBCONFIG_ENABLE_QPSG: i32 = 1007;
|
||||||
pub const SQLITE_DBCONFIG_TRIGGER_EQP: i32 = 1008;
|
pub const SQLITE_DBCONFIG_TRIGGER_EQP: i32 = 1008;
|
||||||
pub const SQLITE_DBCONFIG_RESET_DATABASE: i32 = 1009;
|
pub const SQLITE_DBCONFIG_RESET_DATABASE: i32 = 1009;
|
||||||
pub const SQLITE_DBCONFIG_MAX: i32 = 1009;
|
pub const SQLITE_DBCONFIG_DEFENSIVE: i32 = 1010;
|
||||||
|
pub const SQLITE_DBCONFIG_MAX: i32 = 1010;
|
||||||
pub const SQLITE_DENY: i32 = 1;
|
pub const SQLITE_DENY: i32 = 1;
|
||||||
pub const SQLITE_IGNORE: i32 = 2;
|
pub const SQLITE_IGNORE: i32 = 2;
|
||||||
pub const SQLITE_CREATE_INDEX: i32 = 1;
|
pub const SQLITE_CREATE_INDEX: i32 = 1;
|
||||||
@ -282,6 +283,7 @@ pub const SQLITE_LIMIT_VARIABLE_NUMBER: i32 = 9;
|
|||||||
pub const SQLITE_LIMIT_TRIGGER_DEPTH: i32 = 10;
|
pub const SQLITE_LIMIT_TRIGGER_DEPTH: i32 = 10;
|
||||||
pub const SQLITE_LIMIT_WORKER_THREADS: i32 = 11;
|
pub const SQLITE_LIMIT_WORKER_THREADS: i32 = 11;
|
||||||
pub const SQLITE_PREPARE_PERSISTENT: i32 = 1;
|
pub const SQLITE_PREPARE_PERSISTENT: i32 = 1;
|
||||||
|
pub const SQLITE_PREPARE_NORMALIZE: i32 = 2;
|
||||||
pub const SQLITE_INTEGER: i32 = 1;
|
pub const SQLITE_INTEGER: i32 = 1;
|
||||||
pub const SQLITE_FLOAT: i32 = 2;
|
pub const SQLITE_FLOAT: i32 = 2;
|
||||||
pub const SQLITE_BLOB: i32 = 4;
|
pub const SQLITE_BLOB: i32 = 4;
|
||||||
@ -343,6 +345,7 @@ pub const SQLITE_TESTCTRL_RESERVE: i32 = 14;
|
|||||||
pub const SQLITE_TESTCTRL_OPTIMIZATIONS: i32 = 15;
|
pub const SQLITE_TESTCTRL_OPTIMIZATIONS: i32 = 15;
|
||||||
pub const SQLITE_TESTCTRL_ISKEYWORD: i32 = 16;
|
pub const SQLITE_TESTCTRL_ISKEYWORD: i32 = 16;
|
||||||
pub const SQLITE_TESTCTRL_SCRATCHMALLOC: i32 = 17;
|
pub const SQLITE_TESTCTRL_SCRATCHMALLOC: i32 = 17;
|
||||||
|
pub const SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: i32 = 17;
|
||||||
pub const SQLITE_TESTCTRL_LOCALTIME_FAULT: i32 = 18;
|
pub const SQLITE_TESTCTRL_LOCALTIME_FAULT: i32 = 18;
|
||||||
pub const SQLITE_TESTCTRL_EXPLAIN_STMT: i32 = 19;
|
pub const SQLITE_TESTCTRL_EXPLAIN_STMT: i32 = 19;
|
||||||
pub const SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD: i32 = 19;
|
pub const SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD: i32 = 19;
|
||||||
@ -531,24 +534,34 @@ pub struct sqlite3_io_methods {
|
|||||||
unsafe extern "C" fn(arg1: *mut sqlite3_file, size: sqlite3_int64) -> ::std::os::raw::c_int,
|
unsafe extern "C" fn(arg1: *mut sqlite3_file, size: sqlite3_int64) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xSync: ::std::option::Option<
|
pub xSync: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_file, flags: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut sqlite3_file,
|
||||||
|
flags: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xFileSize: ::std::option::Option<
|
pub xFileSize: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_file, pSize: *mut sqlite3_int64)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut sqlite3_file,
|
||||||
|
pSize: *mut sqlite3_int64,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xLock: ::std::option::Option<
|
pub xLock: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_file, arg2: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut sqlite3_file,
|
||||||
|
arg2: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xUnlock: ::std::option::Option<
|
pub xUnlock: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_file, arg2: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut sqlite3_file,
|
||||||
|
arg2: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xCheckReservedLock: ::std::option::Option<
|
pub xCheckReservedLock: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_file, pResOut: *mut ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut sqlite3_file,
|
||||||
|
pResOut: *mut ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xFileControl: ::std::option::Option<
|
pub xFileControl: ::std::option::Option<
|
||||||
unsafe extern "C" fn(
|
unsafe extern "C" fn(
|
||||||
@ -582,8 +595,10 @@ pub struct sqlite3_io_methods {
|
|||||||
>,
|
>,
|
||||||
pub xShmBarrier: ::std::option::Option<unsafe extern "C" fn(arg1: *mut sqlite3_file)>,
|
pub xShmBarrier: ::std::option::Option<unsafe extern "C" fn(arg1: *mut sqlite3_file)>,
|
||||||
pub xShmUnmap: ::std::option::Option<
|
pub xShmUnmap: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_file, deleteFlag: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut sqlite3_file,
|
||||||
|
deleteFlag: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xFetch: ::std::option::Option<
|
pub xFetch: ::std::option::Option<
|
||||||
unsafe extern "C" fn(
|
unsafe extern "C" fn(
|
||||||
@ -862,8 +877,10 @@ pub struct sqlite3_vfs {
|
|||||||
) -> ::std::os::raw::c_int,
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xDlOpen: ::std::option::Option<
|
pub xDlOpen: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, zFilename: *const ::std::os::raw::c_char)
|
unsafe extern "C" fn(
|
||||||
-> *mut ::std::os::raw::c_void,
|
arg1: *mut sqlite3_vfs,
|
||||||
|
zFilename: *const ::std::os::raw::c_char,
|
||||||
|
) -> *mut ::std::os::raw::c_void,
|
||||||
>,
|
>,
|
||||||
pub xDlError: ::std::option::Option<
|
pub xDlError: ::std::option::Option<
|
||||||
unsafe extern "C" fn(
|
unsafe extern "C" fn(
|
||||||
@ -896,8 +913,10 @@ pub struct sqlite3_vfs {
|
|||||||
) -> ::std::os::raw::c_int,
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xSleep: ::std::option::Option<
|
pub xSleep: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, microseconds: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut sqlite3_vfs,
|
||||||
|
microseconds: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xCurrentTime: ::std::option::Option<
|
pub xCurrentTime: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, arg2: *mut f64) -> ::std::os::raw::c_int,
|
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, arg2: *mut f64) -> ::std::os::raw::c_int,
|
||||||
@ -910,8 +929,10 @@ pub struct sqlite3_vfs {
|
|||||||
) -> ::std::os::raw::c_int,
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xCurrentTimeInt64: ::std::option::Option<
|
pub xCurrentTimeInt64: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, arg2: *mut sqlite3_int64)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut sqlite3_vfs,
|
||||||
|
arg2: *mut sqlite3_int64,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xSetSystemCall: ::std::option::Option<
|
pub xSetSystemCall: ::std::option::Option<
|
||||||
unsafe extern "C" fn(
|
unsafe extern "C" fn(
|
||||||
@ -921,12 +942,16 @@ pub struct sqlite3_vfs {
|
|||||||
) -> ::std::os::raw::c_int,
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xGetSystemCall: ::std::option::Option<
|
pub xGetSystemCall: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, zName: *const ::std::os::raw::c_char)
|
unsafe extern "C" fn(
|
||||||
-> sqlite3_syscall_ptr,
|
arg1: *mut sqlite3_vfs,
|
||||||
|
zName: *const ::std::os::raw::c_char,
|
||||||
|
) -> sqlite3_syscall_ptr,
|
||||||
>,
|
>,
|
||||||
pub xNextSystemCall: ::std::option::Option<
|
pub xNextSystemCall: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_vfs, zName: *const ::std::os::raw::c_char)
|
unsafe extern "C" fn(
|
||||||
-> *const ::std::os::raw::c_char,
|
arg1: *mut sqlite3_vfs,
|
||||||
|
zName: *const ::std::os::raw::c_char,
|
||||||
|
) -> *const ::std::os::raw::c_char,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
@ -1192,8 +1217,10 @@ pub struct sqlite3_mem_methods {
|
|||||||
>,
|
>,
|
||||||
pub xFree: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
pub xFree: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
||||||
pub xRealloc: ::std::option::Option<
|
pub xRealloc: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void, arg2: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> *mut ::std::os::raw::c_void,
|
arg1: *mut ::std::os::raw::c_void,
|
||||||
|
arg2: ::std::os::raw::c_int,
|
||||||
|
) -> *mut ::std::os::raw::c_void,
|
||||||
>,
|
>,
|
||||||
pub xSize: ::std::option::Option<
|
pub xSize: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int,
|
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int,
|
||||||
@ -1331,8 +1358,10 @@ extern "C" {
|
|||||||
pub fn sqlite3_busy_handler(
|
pub fn sqlite3_busy_handler(
|
||||||
arg1: *mut sqlite3,
|
arg1: *mut sqlite3,
|
||||||
arg2: ::std::option::Option<
|
arg2: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void, arg2: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut ::std::os::raw::c_void,
|
||||||
|
arg2: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
arg3: *mut ::std::os::raw::c_void,
|
arg3: *mut ::std::os::raw::c_void,
|
||||||
) -> ::std::os::raw::c_int;
|
) -> ::std::os::raw::c_int;
|
||||||
@ -1610,6 +1639,9 @@ extern "C" {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_expanded_sql(pStmt: *mut sqlite3_stmt) -> *mut ::std::os::raw::c_char;
|
pub fn sqlite3_expanded_sql(pStmt: *mut sqlite3_stmt) -> *mut ::std::os::raw::c_char;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_normalized_sql(pStmt: *mut sqlite3_stmt) -> *const ::std::os::raw::c_char;
|
||||||
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_stmt_readonly(pStmt: *mut sqlite3_stmt) -> ::std::os::raw::c_int;
|
pub fn sqlite3_stmt_readonly(pStmt: *mut sqlite3_stmt) -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
@ -2447,8 +2479,10 @@ pub struct sqlite3_module {
|
|||||||
) -> ::std::os::raw::c_int,
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xBestIndex: ::std::option::Option<
|
pub xBestIndex: ::std::option::Option<
|
||||||
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab, arg1: *mut sqlite3_index_info)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
pVTab: *mut sqlite3_vtab,
|
||||||
|
arg1: *mut sqlite3_index_info,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xDisconnect: ::std::option::Option<
|
pub xDisconnect: ::std::option::Option<
|
||||||
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab) -> ::std::os::raw::c_int,
|
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab) -> ::std::os::raw::c_int,
|
||||||
@ -2457,8 +2491,10 @@ pub struct sqlite3_module {
|
|||||||
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab) -> ::std::os::raw::c_int,
|
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xOpen: ::std::option::Option<
|
pub xOpen: ::std::option::Option<
|
||||||
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab, ppCursor: *mut *mut sqlite3_vtab_cursor)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
pVTab: *mut sqlite3_vtab,
|
||||||
|
ppCursor: *mut *mut sqlite3_vtab_cursor,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xClose: ::std::option::Option<
|
pub xClose: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_vtab_cursor) -> ::std::os::raw::c_int,
|
unsafe extern "C" fn(arg1: *mut sqlite3_vtab_cursor) -> ::std::os::raw::c_int,
|
||||||
@ -2486,8 +2522,10 @@ pub struct sqlite3_module {
|
|||||||
) -> ::std::os::raw::c_int,
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xRowid: ::std::option::Option<
|
pub xRowid: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_vtab_cursor, pRowid: *mut sqlite3_int64)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut sqlite3_vtab_cursor,
|
||||||
|
pRowid: *mut sqlite3_int64,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xUpdate: ::std::option::Option<
|
pub xUpdate: ::std::option::Option<
|
||||||
unsafe extern "C" fn(
|
unsafe extern "C" fn(
|
||||||
@ -2525,27 +2563,38 @@ pub struct sqlite3_module {
|
|||||||
) -> ::std::os::raw::c_int,
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xRename: ::std::option::Option<
|
pub xRename: ::std::option::Option<
|
||||||
unsafe extern "C" fn(pVtab: *mut sqlite3_vtab, zNew: *const ::std::os::raw::c_char)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
pVtab: *mut sqlite3_vtab,
|
||||||
|
zNew: *const ::std::os::raw::c_char,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xSavepoint: ::std::option::Option<
|
pub xSavepoint: ::std::option::Option<
|
||||||
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab, arg1: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
pVTab: *mut sqlite3_vtab,
|
||||||
|
arg1: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xRelease: ::std::option::Option<
|
pub xRelease: ::std::option::Option<
|
||||||
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab, arg1: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
pVTab: *mut sqlite3_vtab,
|
||||||
|
arg1: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xRollbackTo: ::std::option::Option<
|
pub xRollbackTo: ::std::option::Option<
|
||||||
unsafe extern "C" fn(pVTab: *mut sqlite3_vtab, arg1: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
pVTab: *mut sqlite3_vtab,
|
||||||
|
arg1: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
|
>,
|
||||||
|
pub xShadowName: ::std::option::Option<
|
||||||
|
unsafe extern "C" fn(arg1: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn bindgen_test_layout_sqlite3_module() {
|
fn bindgen_test_layout_sqlite3_module() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
::std::mem::size_of::<sqlite3_module>(),
|
::std::mem::size_of::<sqlite3_module>(),
|
||||||
184usize,
|
192usize,
|
||||||
concat!("Size of: ", stringify!(sqlite3_module))
|
concat!("Size of: ", stringify!(sqlite3_module))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -2783,6 +2832,16 @@ fn bindgen_test_layout_sqlite3_module() {
|
|||||||
stringify!(xRollbackTo)
|
stringify!(xRollbackTo)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<sqlite3_module>())).xShadowName as *const _ as usize },
|
||||||
|
184usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(sqlite3_module),
|
||||||
|
"::",
|
||||||
|
stringify!(xShadowName)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@ -3828,8 +3887,10 @@ pub struct sqlite3_pcache_methods {
|
|||||||
>,
|
>,
|
||||||
pub xShutdown: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
pub xShutdown: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
||||||
pub xCreate: ::std::option::Option<
|
pub xCreate: ::std::option::Option<
|
||||||
unsafe extern "C" fn(szPage: ::std::os::raw::c_int, bPurgeable: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> *mut sqlite3_pcache,
|
szPage: ::std::os::raw::c_int,
|
||||||
|
bPurgeable: ::std::os::raw::c_int,
|
||||||
|
) -> *mut sqlite3_pcache,
|
||||||
>,
|
>,
|
||||||
pub xCachesize: ::std::option::Option<
|
pub xCachesize: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut sqlite3_pcache, nCachesize: ::std::os::raw::c_int),
|
unsafe extern "C" fn(arg1: *mut sqlite3_pcache, nCachesize: ::std::os::raw::c_int),
|
||||||
@ -4586,8 +4647,10 @@ pub struct Fts5ExtensionApi {
|
|||||||
unsafe extern "C" fn(arg1: *mut Fts5Context) -> ::std::os::raw::c_int,
|
unsafe extern "C" fn(arg1: *mut Fts5Context) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xRowCount: ::std::option::Option<
|
pub xRowCount: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut Fts5Context, pnRow: *mut sqlite3_int64)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut Fts5Context,
|
||||||
|
pnRow: *mut sqlite3_int64,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xColumnTotalSize: ::std::option::Option<
|
pub xColumnTotalSize: ::std::option::Option<
|
||||||
unsafe extern "C" fn(
|
unsafe extern "C" fn(
|
||||||
@ -4618,12 +4681,16 @@ pub struct Fts5ExtensionApi {
|
|||||||
unsafe extern "C" fn(arg1: *mut Fts5Context) -> ::std::os::raw::c_int,
|
unsafe extern "C" fn(arg1: *mut Fts5Context) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xPhraseSize: ::std::option::Option<
|
pub xPhraseSize: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut Fts5Context, iPhrase: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut Fts5Context,
|
||||||
|
iPhrase: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xInstCount: ::std::option::Option<
|
pub xInstCount: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut Fts5Context, pnInst: *mut ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> ::std::os::raw::c_int,
|
arg1: *mut Fts5Context,
|
||||||
|
pnInst: *mut ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xInst: ::std::option::Option<
|
pub xInst: ::std::option::Option<
|
||||||
unsafe extern "C" fn(
|
unsafe extern "C" fn(
|
||||||
@ -4669,14 +4736,14 @@ pub struct Fts5ExtensionApi {
|
|||||||
unsafe extern "C" fn(
|
unsafe extern "C" fn(
|
||||||
arg1: *mut Fts5Context,
|
arg1: *mut Fts5Context,
|
||||||
pAux: *mut ::std::os::raw::c_void,
|
pAux: *mut ::std::os::raw::c_void,
|
||||||
xDelete: ::std::option::Option<
|
xDelete: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
||||||
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void),
|
|
||||||
>,
|
|
||||||
) -> ::std::os::raw::c_int,
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xGetAuxdata: ::std::option::Option<
|
pub xGetAuxdata: ::std::option::Option<
|
||||||
unsafe extern "C" fn(arg1: *mut Fts5Context, bClear: ::std::os::raw::c_int)
|
unsafe extern "C" fn(
|
||||||
-> *mut ::std::os::raw::c_void,
|
arg1: *mut Fts5Context,
|
||||||
|
bClear: ::std::os::raw::c_int,
|
||||||
|
) -> *mut ::std::os::raw::c_void,
|
||||||
>,
|
>,
|
||||||
pub xPhraseFirst: ::std::option::Option<
|
pub xPhraseFirst: ::std::option::Option<
|
||||||
unsafe extern "C" fn(
|
unsafe extern "C" fn(
|
||||||
@ -5020,9 +5087,7 @@ pub struct fts5_api {
|
|||||||
zName: *const ::std::os::raw::c_char,
|
zName: *const ::std::os::raw::c_char,
|
||||||
pContext: *mut ::std::os::raw::c_void,
|
pContext: *mut ::std::os::raw::c_void,
|
||||||
pTokenizer: *mut fts5_tokenizer,
|
pTokenizer: *mut fts5_tokenizer,
|
||||||
xDestroy: ::std::option::Option<
|
xDestroy: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
||||||
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void),
|
|
||||||
>,
|
|
||||||
) -> ::std::os::raw::c_int,
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
pub xFindTokenizer: ::std::option::Option<
|
pub xFindTokenizer: ::std::option::Option<
|
||||||
@ -5039,9 +5104,7 @@ pub struct fts5_api {
|
|||||||
zName: *const ::std::os::raw::c_char,
|
zName: *const ::std::os::raw::c_char,
|
||||||
pContext: *mut ::std::os::raw::c_void,
|
pContext: *mut ::std::os::raw::c_void,
|
||||||
xFunction: fts5_extension_function,
|
xFunction: fts5_extension_function,
|
||||||
xDestroy: ::std::option::Option<
|
xDestroy: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
||||||
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void),
|
|
||||||
>,
|
|
||||||
) -> ::std::os::raw::c_int,
|
) -> ::std::os::raw::c_int,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
2660
libsqlite3-sys/sqlite3/sqlite3.c
vendored
2660
libsqlite3-sys/sqlite3/sqlite3.c
vendored
File diff suppressed because it is too large
Load Diff
142
libsqlite3-sys/sqlite3/sqlite3.h
vendored
142
libsqlite3-sys/sqlite3/sqlite3.h
vendored
@ -123,9 +123,9 @@ extern "C" {
|
|||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.25.2"
|
#define SQLITE_VERSION "3.26.0"
|
||||||
#define SQLITE_VERSION_NUMBER 3025002
|
#define SQLITE_VERSION_NUMBER 3026000
|
||||||
#define SQLITE_SOURCE_ID "2018-09-25 19:08:10 fb90e7189ae6d62e77ba3a308ca5d683f90bbe633cf681865365b8e92792d1c7"
|
#define SQLITE_SOURCE_ID "2018-12-01 12:34:55 bf8c1b2b7a5960c282e543b9c293686dccff272512d08865f4600fb58238b4f9"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
@ -2017,6 +2017,7 @@ struct sqlite3_mem_methods {
|
|||||||
** is invoked.
|
** is invoked.
|
||||||
**
|
**
|
||||||
** <dl>
|
** <dl>
|
||||||
|
** [[SQLITE_DBCONFIG_LOOKASIDE]]
|
||||||
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
|
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
|
||||||
** <dd> ^This option takes three additional arguments that determine the
|
** <dd> ^This option takes three additional arguments that determine the
|
||||||
** [lookaside memory allocator] configuration for the [database connection].
|
** [lookaside memory allocator] configuration for the [database connection].
|
||||||
@ -2039,6 +2040,7 @@ struct sqlite3_mem_methods {
|
|||||||
** memory is in use leaves the configuration unchanged and returns
|
** memory is in use leaves the configuration unchanged and returns
|
||||||
** [SQLITE_BUSY].)^</dd>
|
** [SQLITE_BUSY].)^</dd>
|
||||||
**
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
|
||||||
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
|
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
|
||||||
** <dd> ^This option is used to enable or disable the enforcement of
|
** <dd> ^This option is used to enable or disable the enforcement of
|
||||||
** [foreign key constraints]. There should be two additional arguments.
|
** [foreign key constraints]. There should be two additional arguments.
|
||||||
@ -2049,6 +2051,7 @@ struct sqlite3_mem_methods {
|
|||||||
** following this call. The second parameter may be a NULL pointer, in
|
** following this call. The second parameter may be a NULL pointer, in
|
||||||
** which case the FK enforcement setting is not reported back. </dd>
|
** which case the FK enforcement setting is not reported back. </dd>
|
||||||
**
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_ENABLE_TRIGGER]]
|
||||||
** <dt>SQLITE_DBCONFIG_ENABLE_TRIGGER</dt>
|
** <dt>SQLITE_DBCONFIG_ENABLE_TRIGGER</dt>
|
||||||
** <dd> ^This option is used to enable or disable [CREATE TRIGGER | triggers].
|
** <dd> ^This option is used to enable or disable [CREATE TRIGGER | triggers].
|
||||||
** There should be two additional arguments.
|
** There should be two additional arguments.
|
||||||
@ -2059,6 +2062,7 @@ struct sqlite3_mem_methods {
|
|||||||
** following this call. The second parameter may be a NULL pointer, in
|
** following this call. The second parameter may be a NULL pointer, in
|
||||||
** which case the trigger setting is not reported back. </dd>
|
** which case the trigger setting is not reported back. </dd>
|
||||||
**
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
|
||||||
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
|
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
|
||||||
** <dd> ^This option is used to enable or disable the two-argument
|
** <dd> ^This option is used to enable or disable the two-argument
|
||||||
** version of the [fts3_tokenizer()] function which is part of the
|
** version of the [fts3_tokenizer()] function which is part of the
|
||||||
@ -2072,6 +2076,7 @@ struct sqlite3_mem_methods {
|
|||||||
** following this call. The second parameter may be a NULL pointer, in
|
** following this call. The second parameter may be a NULL pointer, in
|
||||||
** which case the new setting is not reported back. </dd>
|
** which case the new setting is not reported back. </dd>
|
||||||
**
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]]
|
||||||
** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt>
|
** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt>
|
||||||
** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()]
|
** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()]
|
||||||
** interface independently of the [load_extension()] SQL function.
|
** interface independently of the [load_extension()] SQL function.
|
||||||
@ -2089,7 +2094,7 @@ struct sqlite3_mem_methods {
|
|||||||
** be a NULL pointer, in which case the new setting is not reported back.
|
** be a NULL pointer, in which case the new setting is not reported back.
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
** <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
|
** [[SQLITE_DBCONFIG_MAINDBNAME]] <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
|
||||||
** <dd> ^This option is used to change the name of the "main" database
|
** <dd> ^This option is used to change the name of the "main" database
|
||||||
** schema. ^The sole argument is a pointer to a constant UTF8 string
|
** schema. ^The sole argument is a pointer to a constant UTF8 string
|
||||||
** which will become the new schema name in place of "main". ^SQLite
|
** which will become the new schema name in place of "main". ^SQLite
|
||||||
@ -2098,6 +2103,7 @@ struct sqlite3_mem_methods {
|
|||||||
** until after the database connection closes.
|
** until after the database connection closes.
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]]
|
||||||
** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt>
|
** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt>
|
||||||
** <dd> Usually, when a database in wal mode is closed or detached from a
|
** <dd> Usually, when a database in wal mode is closed or detached from a
|
||||||
** database handle, SQLite checks if this will mean that there are now no
|
** database handle, SQLite checks if this will mean that there are now no
|
||||||
@ -2111,7 +2117,7 @@ struct sqlite3_mem_methods {
|
|||||||
** have been disabled - 0 if they are not disabled, 1 if they are.
|
** have been disabled - 0 if they are not disabled, 1 if they are.
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
** <dt>SQLITE_DBCONFIG_ENABLE_QPSG</dt>
|
** [[SQLITE_DBCONFIG_ENABLE_QPSG]] <dt>SQLITE_DBCONFIG_ENABLE_QPSG</dt>
|
||||||
** <dd>^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates
|
** <dd>^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates
|
||||||
** the [query planner stability guarantee] (QPSG). When the QPSG is active,
|
** the [query planner stability guarantee] (QPSG). When the QPSG is active,
|
||||||
** a single SQL query statement will always use the same algorithm regardless
|
** a single SQL query statement will always use the same algorithm regardless
|
||||||
@ -2127,7 +2133,7 @@ struct sqlite3_mem_methods {
|
|||||||
** following this call.
|
** following this call.
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
** <dt>SQLITE_DBCONFIG_TRIGGER_EQP</dt>
|
** [[SQLITE_DBCONFIG_TRIGGER_EQP]] <dt>SQLITE_DBCONFIG_TRIGGER_EQP</dt>
|
||||||
** <dd> By default, the output of EXPLAIN QUERY PLAN commands does not
|
** <dd> By default, the output of EXPLAIN QUERY PLAN commands does not
|
||||||
** include output for any operations performed by trigger programs. This
|
** include output for any operations performed by trigger programs. This
|
||||||
** option is used to set or clear (the default) a flag that governs this
|
** option is used to set or clear (the default) a flag that governs this
|
||||||
@ -2139,7 +2145,7 @@ struct sqlite3_mem_methods {
|
|||||||
** it is not disabled, 1 if it is.
|
** it is not disabled, 1 if it is.
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
** <dt>SQLITE_DBCONFIG_RESET_DATABASE</dt>
|
** [[SQLITE_DBCONFIG_RESET_DATABASE]] <dt>SQLITE_DBCONFIG_RESET_DATABASE</dt>
|
||||||
** <dd> Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run
|
** <dd> Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run
|
||||||
** [VACUUM] in order to reset a database back to an empty database
|
** [VACUUM] in order to reset a database back to an empty database
|
||||||
** with no schema and no content. The following process works even for
|
** with no schema and no content. The following process works even for
|
||||||
@ -2158,6 +2164,18 @@ struct sqlite3_mem_methods {
|
|||||||
** Because resetting a database is destructive and irreversible, the
|
** Because resetting a database is destructive and irreversible, the
|
||||||
** process requires the use of this obscure API and multiple steps to help
|
** process requires the use of this obscure API and multiple steps to help
|
||||||
** ensure that it does not happen by accident.
|
** ensure that it does not happen by accident.
|
||||||
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_DEFENSIVE]] <dt>SQLITE_DBCONFIG_DEFENSIVE</dt>
|
||||||
|
** <dd>The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the
|
||||||
|
** "defensive" flag for a database connection. When the defensive
|
||||||
|
** flag is enabled, language features that allow ordinary SQL to
|
||||||
|
** deliberately corrupt the database file are disabled. The disabled
|
||||||
|
** features include but are not limited to the following:
|
||||||
|
** <ul>
|
||||||
|
** <li> The [PRAGMA writable_schema=ON] statement.
|
||||||
|
** <li> Writes to the [sqlite_dbpage] virtual table.
|
||||||
|
** <li> Direct writes to [shadow tables].
|
||||||
|
** </ul>
|
||||||
** </dd>
|
** </dd>
|
||||||
** </dl>
|
** </dl>
|
||||||
*/
|
*/
|
||||||
@ -2171,7 +2189,8 @@ struct sqlite3_mem_methods {
|
|||||||
#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */
|
#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */
|
||||||
#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */
|
#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */
|
||||||
#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */
|
#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */
|
||||||
#define SQLITE_DBCONFIG_MAX 1009 /* Largest DBCONFIG */
|
#define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */
|
||||||
|
#define SQLITE_DBCONFIG_MAX 1010 /* Largest DBCONFIG */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Enable Or Disable Extended Result Codes
|
** CAPI3REF: Enable Or Disable Extended Result Codes
|
||||||
@ -3609,9 +3628,19 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
|
|||||||
** on this hint by avoiding the use of [lookaside memory] so as not to
|
** on this hint by avoiding the use of [lookaside memory] so as not to
|
||||||
** deplete the limited store of lookaside memory. Future versions of
|
** deplete the limited store of lookaside memory. Future versions of
|
||||||
** SQLite may act on this hint differently.
|
** SQLite may act on this hint differently.
|
||||||
|
**
|
||||||
|
** [[SQLITE_PREPARE_NORMALIZE]] ^(<dt>SQLITE_PREPARE_NORMALIZE</dt>
|
||||||
|
** <dd>The SQLITE_PREPARE_NORMALIZE flag indicates that a normalized
|
||||||
|
** representation of the SQL statement should be calculated and then
|
||||||
|
** associated with the prepared statement, which can be obtained via
|
||||||
|
** the [sqlite3_normalized_sql()] interface.)^ The semantics used to
|
||||||
|
** normalize a SQL statement are unspecified and subject to change.
|
||||||
|
** At a minimum, literal values will be replaced with suitable
|
||||||
|
** placeholders.
|
||||||
** </dl>
|
** </dl>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_PREPARE_PERSISTENT 0x01
|
#define SQLITE_PREPARE_PERSISTENT 0x01
|
||||||
|
#define SQLITE_PREPARE_NORMALIZE 0x02
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Compiling An SQL Statement
|
** CAPI3REF: Compiling An SQL Statement
|
||||||
@ -3769,6 +3798,11 @@ SQLITE_API int sqlite3_prepare16_v3(
|
|||||||
** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8
|
** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8
|
||||||
** string containing the SQL text of prepared statement P with
|
** string containing the SQL text of prepared statement P with
|
||||||
** [bound parameters] expanded.
|
** [bound parameters] expanded.
|
||||||
|
** ^The sqlite3_normalized_sql(P) interface returns a pointer to a UTF-8
|
||||||
|
** string containing the normalized SQL text of prepared statement P. The
|
||||||
|
** semantics used to normalize a SQL statement are unspecified and subject
|
||||||
|
** to change. At a minimum, literal values will be replaced with suitable
|
||||||
|
** placeholders.
|
||||||
**
|
**
|
||||||
** ^(For example, if a prepared statement is created using the SQL
|
** ^(For example, if a prepared statement is created using the SQL
|
||||||
** text "SELECT $abc,:xyz" and if parameter $abc is bound to integer 2345
|
** text "SELECT $abc,:xyz" and if parameter $abc is bound to integer 2345
|
||||||
@ -3784,14 +3818,16 @@ SQLITE_API int sqlite3_prepare16_v3(
|
|||||||
** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time
|
** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time
|
||||||
** option causes sqlite3_expanded_sql() to always return NULL.
|
** option causes sqlite3_expanded_sql() to always return NULL.
|
||||||
**
|
**
|
||||||
** ^The string returned by sqlite3_sql(P) is managed by SQLite and is
|
** ^The strings returned by sqlite3_sql(P) and sqlite3_normalized_sql(P)
|
||||||
** automatically freed when the prepared statement is finalized.
|
** are managed by SQLite and are automatically freed when the prepared
|
||||||
|
** statement is finalized.
|
||||||
** ^The string returned by sqlite3_expanded_sql(P), on the other hand,
|
** ^The string returned by sqlite3_expanded_sql(P), on the other hand,
|
||||||
** is obtained from [sqlite3_malloc()] and must be free by the application
|
** is obtained from [sqlite3_malloc()] and must be free by the application
|
||||||
** by passing it to [sqlite3_free()].
|
** by passing it to [sqlite3_free()].
|
||||||
*/
|
*/
|
||||||
SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt);
|
SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt);
|
||||||
SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt);
|
SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt);
|
||||||
|
SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Determine If An SQL Statement Writes The Database
|
** CAPI3REF: Determine If An SQL Statement Writes The Database
|
||||||
@ -6281,6 +6317,9 @@ struct sqlite3_module {
|
|||||||
int (*xSavepoint)(sqlite3_vtab *pVTab, int);
|
int (*xSavepoint)(sqlite3_vtab *pVTab, int);
|
||||||
int (*xRelease)(sqlite3_vtab *pVTab, int);
|
int (*xRelease)(sqlite3_vtab *pVTab, int);
|
||||||
int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
|
int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
|
||||||
|
/* The methods above are in versions 1 and 2 of the sqlite_module object.
|
||||||
|
** Those below are for version 3 and greater. */
|
||||||
|
int (*xShadowName)(const char*);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -7203,6 +7242,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
|||||||
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
|
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
|
||||||
#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */
|
#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */
|
||||||
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
|
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
|
||||||
|
#define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17
|
||||||
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
|
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
|
||||||
#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */
|
#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */
|
||||||
#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19
|
#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19
|
||||||
@ -8615,6 +8655,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
|||||||
** can use to customize and optimize their behavior.
|
** can use to customize and optimize their behavior.
|
||||||
**
|
**
|
||||||
** <dl>
|
** <dl>
|
||||||
|
** [[SQLITE_VTAB_CONSTRAINT_SUPPORT]]
|
||||||
** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT
|
** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT
|
||||||
** <dd>Calls of the form
|
** <dd>Calls of the form
|
||||||
** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported,
|
** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported,
|
||||||
@ -9384,7 +9425,7 @@ struct sqlite3_rtree_query_info {
|
|||||||
sqlite3_int64 iRowid; /* Rowid for current entry */
|
sqlite3_int64 iRowid; /* Rowid for current entry */
|
||||||
sqlite3_rtree_dbl rParentScore; /* Score of parent node */
|
sqlite3_rtree_dbl rParentScore; /* Score of parent node */
|
||||||
int eParentWithin; /* Visibility of parent node */
|
int eParentWithin; /* Visibility of parent node */
|
||||||
int eWithin; /* OUT: Visiblity */
|
int eWithin; /* OUT: Visibility */
|
||||||
sqlite3_rtree_dbl rScore; /* OUT: Write the score here */
|
sqlite3_rtree_dbl rScore; /* OUT: Write the score here */
|
||||||
/* The following fields are only available in 3.8.11 and later */
|
/* The following fields are only available in 3.8.11 and later */
|
||||||
sqlite3_value **apSqlParam; /* Original SQL values of parameters */
|
sqlite3_value **apSqlParam; /* Original SQL values of parameters */
|
||||||
@ -9880,12 +9921,38 @@ SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession);
|
|||||||
** consecutively. There is no chance that the iterator will visit a change
|
** consecutively. There is no chance that the iterator will visit a change
|
||||||
** the applies to table X, then one for table Y, and then later on visit
|
** the applies to table X, then one for table Y, and then later on visit
|
||||||
** another change for table X.
|
** another change for table X.
|
||||||
|
**
|
||||||
|
** The behavior of sqlite3changeset_start_v2() and its streaming equivalent
|
||||||
|
** may be modified by passing a combination of
|
||||||
|
** [SQLITE_CHANGESETSTART_INVERT | supported flags] as the 4th parameter.
|
||||||
|
**
|
||||||
|
** Note that the sqlite3changeset_start_v2() API is still <b>experimental</b>
|
||||||
|
** and therefore subject to change.
|
||||||
*/
|
*/
|
||||||
SQLITE_API int sqlite3changeset_start(
|
SQLITE_API int sqlite3changeset_start(
|
||||||
sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
|
sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
|
||||||
int nChangeset, /* Size of changeset blob in bytes */
|
int nChangeset, /* Size of changeset blob in bytes */
|
||||||
void *pChangeset /* Pointer to blob containing changeset */
|
void *pChangeset /* Pointer to blob containing changeset */
|
||||||
);
|
);
|
||||||
|
SQLITE_API int sqlite3changeset_start_v2(
|
||||||
|
sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
|
||||||
|
int nChangeset, /* Size of changeset blob in bytes */
|
||||||
|
void *pChangeset, /* Pointer to blob containing changeset */
|
||||||
|
int flags /* SESSION_CHANGESETSTART_* flags */
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** CAPI3REF: Flags for sqlite3changeset_start_v2
|
||||||
|
**
|
||||||
|
** The following flags may passed via the 4th parameter to
|
||||||
|
** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]:
|
||||||
|
**
|
||||||
|
** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
|
||||||
|
** Invert the changeset while iterating through it. This is equivalent to
|
||||||
|
** inverting a changeset using sqlite3changeset_invert() before applying it.
|
||||||
|
** It is an error to specify this flag with a patchset.
|
||||||
|
*/
|
||||||
|
#define SQLITE_CHANGESETSTART_INVERT 0x0002
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -10540,7 +10607,7 @@ SQLITE_API int sqlite3changeset_apply_v2(
|
|||||||
),
|
),
|
||||||
void *pCtx, /* First argument passed to xConflict */
|
void *pCtx, /* First argument passed to xConflict */
|
||||||
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
|
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
|
||||||
int flags /* Combination of SESSION_APPLY_* flags */
|
int flags /* SESSION_CHANGESETAPPLY_* flags */
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -10558,8 +10625,14 @@ SQLITE_API int sqlite3changeset_apply_v2(
|
|||||||
** causes the sessions module to omit this savepoint. In this case, if the
|
** causes the sessions module to omit this savepoint. In this case, if the
|
||||||
** caller has an open transaction or savepoint when apply_v2() is called,
|
** caller has an open transaction or savepoint when apply_v2() is called,
|
||||||
** it may revert the partially applied changeset by rolling it back.
|
** it may revert the partially applied changeset by rolling it back.
|
||||||
|
**
|
||||||
|
** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
|
||||||
|
** Invert the changeset before applying it. This is equivalent to inverting
|
||||||
|
** a changeset using sqlite3changeset_invert() before applying it. It is
|
||||||
|
** an error to specify this flag with a patchset.
|
||||||
*/
|
*/
|
||||||
#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
|
#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
|
||||||
|
#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Constants Passed To The Conflict Handler
|
** CAPI3REF: Constants Passed To The Conflict Handler
|
||||||
@ -10953,6 +11026,12 @@ SQLITE_API int sqlite3changeset_start_strm(
|
|||||||
int (*xInput)(void *pIn, void *pData, int *pnData),
|
int (*xInput)(void *pIn, void *pData, int *pnData),
|
||||||
void *pIn
|
void *pIn
|
||||||
);
|
);
|
||||||
|
SQLITE_API int sqlite3changeset_start_v2_strm(
|
||||||
|
sqlite3_changeset_iter **pp,
|
||||||
|
int (*xInput)(void *pIn, void *pData, int *pnData),
|
||||||
|
void *pIn,
|
||||||
|
int flags
|
||||||
|
);
|
||||||
SQLITE_API int sqlite3session_changeset_strm(
|
SQLITE_API int sqlite3session_changeset_strm(
|
||||||
sqlite3_session *pSession,
|
sqlite3_session *pSession,
|
||||||
int (*xOutput)(void *pOut, const void *pData, int nData),
|
int (*xOutput)(void *pOut, const void *pData, int nData),
|
||||||
@ -10979,6 +11058,45 @@ SQLITE_API int sqlite3rebaser_rebase_strm(
|
|||||||
void *pOut
|
void *pOut
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** CAPI3REF: Configure global parameters
|
||||||
|
**
|
||||||
|
** The sqlite3session_config() interface is used to make global configuration
|
||||||
|
** changes to the sessions module in order to tune it to the specific needs
|
||||||
|
** of the application.
|
||||||
|
**
|
||||||
|
** The sqlite3session_config() interface is not threadsafe. If it is invoked
|
||||||
|
** while any other thread is inside any other sessions method then the
|
||||||
|
** results are undefined. Furthermore, if it is invoked after any sessions
|
||||||
|
** related objects have been created, the results are also undefined.
|
||||||
|
**
|
||||||
|
** The first argument to the sqlite3session_config() function must be one
|
||||||
|
** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The
|
||||||
|
** interpretation of the (void*) value passed as the second parameter and
|
||||||
|
** the effect of calling this function depends on the value of the first
|
||||||
|
** parameter.
|
||||||
|
**
|
||||||
|
** <dl>
|
||||||
|
** <dt>SQLITE_SESSION_CONFIG_STRMSIZE<dd>
|
||||||
|
** By default, the sessions module streaming interfaces attempt to input
|
||||||
|
** and output data in approximately 1 KiB chunks. This operand may be used
|
||||||
|
** to set and query the value of this configuration setting. The pointer
|
||||||
|
** passed as the second argument must point to a value of type (int).
|
||||||
|
** If this value is greater than 0, it is used as the new streaming data
|
||||||
|
** chunk size for both input and output. Before returning, the (int) value
|
||||||
|
** pointed to by pArg is set to the final value of the streaming interface
|
||||||
|
** chunk size.
|
||||||
|
** </dl>
|
||||||
|
**
|
||||||
|
** This function returns SQLITE_OK if successful, or an SQLite error code
|
||||||
|
** otherwise.
|
||||||
|
*/
|
||||||
|
SQLITE_API int sqlite3session_config(int op, void *pArg);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** CAPI3REF: Values for sqlite3session_config().
|
||||||
|
*/
|
||||||
|
#define SQLITE_SESSION_CONFIG_STRMSIZE 1
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Make sure we can call this stuff from C++.
|
** Make sure we can call this stuff from C++.
|
||||||
|
5
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
5
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
@ -310,12 +310,15 @@ struct sqlite3_api_routines {
|
|||||||
int (*str_errcode)(sqlite3_str*);
|
int (*str_errcode)(sqlite3_str*);
|
||||||
int (*str_length)(sqlite3_str*);
|
int (*str_length)(sqlite3_str*);
|
||||||
char *(*str_value)(sqlite3_str*);
|
char *(*str_value)(sqlite3_str*);
|
||||||
|
/* Version 3.25.0 and later */
|
||||||
int (*create_window_function)(sqlite3*,const char*,int,int,void*,
|
int (*create_window_function)(sqlite3*,const char*,int,int,void*,
|
||||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
||||||
void (*xFinal)(sqlite3_context*),
|
void (*xFinal)(sqlite3_context*),
|
||||||
void (*xValue)(sqlite3_context*),
|
void (*xValue)(sqlite3_context*),
|
||||||
void (*xInv)(sqlite3_context*,int,sqlite3_value**),
|
void (*xInv)(sqlite3_context*,int,sqlite3_value**),
|
||||||
void(*xDestroy)(void*));
|
void(*xDestroy)(void*));
|
||||||
|
/* Version 3.26.0 and later */
|
||||||
|
const char *(*normalized_sql)(sqlite3_stmt*);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -603,6 +606,8 @@ typedef int (*sqlite3_loadext_entry)(
|
|||||||
#define sqlite3_str_value sqlite3_api->str_value
|
#define sqlite3_str_value sqlite3_api->str_value
|
||||||
/* Version 3.25.0 and later */
|
/* Version 3.25.0 and later */
|
||||||
#define sqlite3_create_window_function sqlite3_api->create_window_function
|
#define sqlite3_create_window_function sqlite3_api->create_window_function
|
||||||
|
/* Version 3.26.0 and later */
|
||||||
|
#define sqlite3_normalized_sql sqlite3_api->normalized_sql
|
||||||
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
||||||
|
|
||||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||||
|
@ -98,7 +98,7 @@ impl Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Error code {}: {}",
|
"Error code {}: {}",
|
||||||
@ -115,9 +115,10 @@ impl error::Error for Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Result codes.
|
// Result codes.
|
||||||
// Note: These are not public because our bindgen bindings export whichever constants are present
|
// Note: These are not public because our bindgen bindings export whichever
|
||||||
// in the current version of SQLite. We repeat them here so we don't have to worry about which
|
// constants are present in the current version of SQLite. We repeat them here
|
||||||
// version of SQLite added which constants, and we only use them to implement code_to_str below.
|
// so we don't have to worry about which version of SQLite added which
|
||||||
|
// constants, and we only use them to implement code_to_str below.
|
||||||
|
|
||||||
const SQLITE_NOTICE: c_int = 27;
|
const SQLITE_NOTICE: c_int = 27;
|
||||||
const SQLITE_WARNING: c_int = 28;
|
const SQLITE_WARNING: c_int = 28;
|
||||||
|
@ -16,32 +16,36 @@ pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Run-Time Limit Categories
|
/// Run-Time Limit Categories
|
||||||
#[repr(C)]
|
#[repr(i32)]
|
||||||
pub enum Limit {
|
pub enum Limit {
|
||||||
/// The maximum size of any string or BLOB or table row, in bytes.
|
/// The maximum size of any string or BLOB or table row, in bytes.
|
||||||
SQLITE_LIMIT_LENGTH = SQLITE_LIMIT_LENGTH as isize,
|
SQLITE_LIMIT_LENGTH = SQLITE_LIMIT_LENGTH,
|
||||||
/// The maximum length of an SQL statement, in bytes.
|
/// The maximum length of an SQL statement, in bytes.
|
||||||
SQLITE_LIMIT_SQL_LENGTH = SQLITE_LIMIT_SQL_LENGTH as isize,
|
SQLITE_LIMIT_SQL_LENGTH = SQLITE_LIMIT_SQL_LENGTH,
|
||||||
/// The maximum number of columns in a table definition or in the result set of a SELECT
|
/// The maximum number of columns in a table definition or in the result set
|
||||||
/// or the maximum number of columns in an index or in an ORDER BY or GROUP BY clause.
|
/// of a SELECT or the maximum number of columns in an index or in an
|
||||||
SQLITE_LIMIT_COLUMN = SQLITE_LIMIT_COLUMN as isize,
|
/// ORDER BY or GROUP BY clause.
|
||||||
|
SQLITE_LIMIT_COLUMN = SQLITE_LIMIT_COLUMN,
|
||||||
/// The maximum depth of the parse tree on any expression.
|
/// The maximum depth of the parse tree on any expression.
|
||||||
SQLITE_LIMIT_EXPR_DEPTH = SQLITE_LIMIT_EXPR_DEPTH as isize,
|
SQLITE_LIMIT_EXPR_DEPTH = SQLITE_LIMIT_EXPR_DEPTH,
|
||||||
/// The maximum number of terms in a compound SELECT statement.
|
/// The maximum number of terms in a compound SELECT statement.
|
||||||
SQLITE_LIMIT_COMPOUND_SELECT = SQLITE_LIMIT_COMPOUND_SELECT as isize,
|
SQLITE_LIMIT_COMPOUND_SELECT = SQLITE_LIMIT_COMPOUND_SELECT,
|
||||||
/// The maximum number of instructions in a virtual machine program used to implement an SQL statement.
|
/// The maximum number of instructions in a virtual machine program used to
|
||||||
SQLITE_LIMIT_VDBE_OP = SQLITE_LIMIT_VDBE_OP as isize,
|
/// implement an SQL statement.
|
||||||
|
SQLITE_LIMIT_VDBE_OP = SQLITE_LIMIT_VDBE_OP,
|
||||||
/// The maximum number of arguments on a function.
|
/// The maximum number of arguments on a function.
|
||||||
SQLITE_LIMIT_FUNCTION_ARG = SQLITE_LIMIT_FUNCTION_ARG as isize,
|
SQLITE_LIMIT_FUNCTION_ARG = SQLITE_LIMIT_FUNCTION_ARG,
|
||||||
/// The maximum number of attached databases.
|
/// The maximum number of attached databases.
|
||||||
SQLITE_LIMIT_ATTACHED = SQLITE_LIMIT_ATTACHED as isize,
|
SQLITE_LIMIT_ATTACHED = SQLITE_LIMIT_ATTACHED,
|
||||||
/// The maximum length of the pattern argument to the LIKE or GLOB operators.
|
/// The maximum length of the pattern argument to the LIKE or GLOB
|
||||||
SQLITE_LIMIT_LIKE_PATTERN_LENGTH = SQLITE_LIMIT_LIKE_PATTERN_LENGTH as isize,
|
/// operators.
|
||||||
|
SQLITE_LIMIT_LIKE_PATTERN_LENGTH = SQLITE_LIMIT_LIKE_PATTERN_LENGTH,
|
||||||
/// The maximum index number of any parameter in an SQL statement.
|
/// The maximum index number of any parameter in an SQL statement.
|
||||||
SQLITE_LIMIT_VARIABLE_NUMBER = SQLITE_LIMIT_VARIABLE_NUMBER as isize,
|
SQLITE_LIMIT_VARIABLE_NUMBER = SQLITE_LIMIT_VARIABLE_NUMBER,
|
||||||
/// The maximum depth of recursion for triggers.
|
/// The maximum depth of recursion for triggers.
|
||||||
SQLITE_LIMIT_TRIGGER_DEPTH = 10,
|
SQLITE_LIMIT_TRIGGER_DEPTH = 10,
|
||||||
/// The maximum number of auxiliary worker threads that a single prepared statement may start.
|
/// The maximum number of auxiliary worker threads that a single prepared
|
||||||
|
/// statement may start.
|
||||||
SQLITE_LIMIT_WORKER_THREADS = 11,
|
SQLITE_LIMIT_WORKER_THREADS = 11,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ cd $SCRIPT_DIR
|
|||||||
export SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
|
export SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
|
||||||
|
|
||||||
# Download and extract amalgamation
|
# Download and extract amalgamation
|
||||||
SQLITE=sqlite-amalgamation-3250200
|
SQLITE=sqlite-amalgamation-3260000
|
||||||
curl -O http://sqlite.org/2018/$SQLITE.zip
|
curl -O http://sqlite.org/2018/$SQLITE.zip
|
||||||
unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c
|
unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c
|
||||||
unzip -p $SQLITE.zip $SQLITE/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h
|
unzip -p $SQLITE.zip $SQLITE/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h
|
||||||
@ -22,5 +22,5 @@ find $SCRIPT_DIR/target -type f -name bindgen.rs -exec cp {} $SQLITE3_LIB_DIR/bi
|
|||||||
# Sanity check
|
# Sanity check
|
||||||
cd $SCRIPT_DIR/..
|
cd $SCRIPT_DIR/..
|
||||||
cargo update
|
cargo update
|
||||||
cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled"
|
cargo test --features "backup blob chrono functions limits load_extension serde_json trace vtab bundled"
|
||||||
echo 'You should increment the version in libsqlite3-sys/Cargo.toml'
|
echo 'You should increment the version in libsqlite3-sys/Cargo.toml'
|
||||||
|
@ -23,8 +23,8 @@
|
|||||||
//! dst: P,
|
//! dst: P,
|
||||||
//! progress: fn(backup::Progress),
|
//! progress: fn(backup::Progress),
|
||||||
//! ) -> Result<()> {
|
//! ) -> Result<()> {
|
||||||
//! let mut dst = try!(Connection::open(dst));
|
//! let mut dst = Connection::open(dst)?;
|
||||||
//! let backup = try!(backup::Backup::new(src, &mut dst));
|
//! let backup = backup::Backup::new(src, &mut dst)?;
|
||||||
//! backup.run_to_completion(5, time::Duration::from_millis(250), Some(progress))
|
//! backup.run_to_completion(5, time::Duration::from_millis(250), Some(progress))
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
@ -37,10 +37,10 @@ use std::os::raw::c_int;
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
|
|
||||||
use error::{error_from_handle, error_from_sqlite_code};
|
use crate::error::{error_from_handle, error_from_sqlite_code};
|
||||||
use {Connection, DatabaseName, Result};
|
use crate::{Connection, DatabaseName, Result};
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Back up the `name` database to the given destination path.
|
/// Back up the `name` database to the given destination path.
|
||||||
@ -57,22 +57,17 @@ impl Connection {
|
|||||||
/// or if the backup fails.
|
/// or if the backup fails.
|
||||||
pub fn backup<P: AsRef<Path>>(
|
pub fn backup<P: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
name: DatabaseName,
|
name: DatabaseName<'_>,
|
||||||
dst_path: P,
|
dst_path: P,
|
||||||
progress: Option<fn(Progress)>,
|
progress: Option<fn(Progress)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use self::StepResult::{Busy, Done, Locked, More};
|
use self::StepResult::{Busy, Done, Locked, More};
|
||||||
let mut dst = try!(Connection::open(dst_path));
|
let mut dst = Connection::open(dst_path)?;
|
||||||
let backup = try!(Backup::new_with_names(
|
let backup = Backup::new_with_names(self, name, &mut dst, DatabaseName::Main)?;
|
||||||
self,
|
|
||||||
name,
|
|
||||||
&mut dst,
|
|
||||||
DatabaseName::Main
|
|
||||||
));
|
|
||||||
|
|
||||||
let mut r = More;
|
let mut r = More;
|
||||||
while r == More {
|
while r == More {
|
||||||
r = try!(backup.step(100));
|
r = backup.step(100)?;
|
||||||
if let Some(f) = progress {
|
if let Some(f) = progress {
|
||||||
f(backup.progress());
|
f(backup.progress());
|
||||||
}
|
}
|
||||||
@ -100,18 +95,18 @@ impl Connection {
|
|||||||
/// or if the restore fails.
|
/// or if the restore fails.
|
||||||
pub fn restore<P: AsRef<Path>, F: Fn(Progress)>(
|
pub fn restore<P: AsRef<Path>, F: Fn(Progress)>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: DatabaseName,
|
name: DatabaseName<'_>,
|
||||||
src_path: P,
|
src_path: P,
|
||||||
progress: Option<F>,
|
progress: Option<F>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use self::StepResult::{Busy, Done, Locked, More};
|
use self::StepResult::{Busy, Done, Locked, More};
|
||||||
let src = try!(Connection::open(src_path));
|
let src = Connection::open(src_path)?;
|
||||||
let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name));
|
let restore = Backup::new_with_names(&src, DatabaseName::Main, self, name)?;
|
||||||
|
|
||||||
let mut r = More;
|
let mut r = More;
|
||||||
let mut busy_count = 0i32;
|
let mut busy_count = 0i32;
|
||||||
'restore_loop: while r == More || r == Busy {
|
'restore_loop: while r == More || r == Busy {
|
||||||
r = try!(restore.step(100));
|
r = restore.step(100)?;
|
||||||
if let Some(ref f) = progress {
|
if let Some(ref f) = progress {
|
||||||
f(restore.progress());
|
f(restore.progress());
|
||||||
}
|
}
|
||||||
@ -197,12 +192,12 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
/// `NULL`.
|
/// `NULL`.
|
||||||
pub fn new_with_names(
|
pub fn new_with_names(
|
||||||
from: &'a Connection,
|
from: &'a Connection,
|
||||||
from_name: DatabaseName,
|
from_name: DatabaseName<'_>,
|
||||||
to: &'b mut Connection,
|
to: &'b mut Connection,
|
||||||
to_name: DatabaseName,
|
to_name: DatabaseName<'_>,
|
||||||
) -> Result<Backup<'a, 'b>> {
|
) -> Result<Backup<'a, 'b>> {
|
||||||
let to_name = try!(to_name.to_cstring());
|
let to_name = to_name.to_cstring()?;
|
||||||
let from_name = try!(from_name.to_cstring());
|
let from_name = from_name.to_cstring()?;
|
||||||
|
|
||||||
let to_db = to.db.borrow_mut().db;
|
let to_db = to.db.borrow_mut().db;
|
||||||
|
|
||||||
@ -287,7 +282,7 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
assert!(pages_per_step > 0, "pages_per_step must be positive");
|
assert!(pages_per_step > 0, "pages_per_step must be positive");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let r = try!(self.step(pages_per_step));
|
let r = self.step(pages_per_step)?;
|
||||||
if let Some(progress) = progress {
|
if let Some(progress) = progress {
|
||||||
progress(self.progress())
|
progress(self.progress())
|
||||||
}
|
}
|
||||||
@ -308,8 +303,8 @@ impl<'a, 'b> Drop for Backup<'a, 'b> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::Backup;
|
use super::Backup;
|
||||||
|
use crate::{Connection, DatabaseName, NO_PARAMS};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use {Connection, DatabaseName, NO_PARAMS};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_backup() {
|
fn test_backup() {
|
||||||
|
21
src/blob.rs
21
src/blob.rs
@ -14,9 +14,6 @@
|
|||||||
//! ## Example
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! extern crate libsqlite3_sys;
|
|
||||||
//! extern crate rusqlite;
|
|
||||||
//!
|
|
||||||
//! use rusqlite::blob::ZeroBlob;
|
//! use rusqlite::blob::ZeroBlob;
|
||||||
//! use rusqlite::{Connection, DatabaseName, NO_PARAMS};
|
//! use rusqlite::{Connection, DatabaseName, NO_PARAMS};
|
||||||
//! use std::io::{Read, Seek, SeekFrom, Write};
|
//! use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
@ -64,7 +61,7 @@ use std::ptr;
|
|||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
use super::types::{ToSql, ToSqlOutput};
|
use super::types::{ToSql, ToSqlOutput};
|
||||||
use {Connection, DatabaseName, Result};
|
use crate::{Connection, DatabaseName, Result};
|
||||||
|
|
||||||
/// Handle to an open BLOB.
|
/// Handle to an open BLOB.
|
||||||
pub struct Blob<'conn> {
|
pub struct Blob<'conn> {
|
||||||
@ -84,7 +81,7 @@ impl Connection {
|
|||||||
/// fails.
|
/// fails.
|
||||||
pub fn blob_open<'a>(
|
pub fn blob_open<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
db: DatabaseName,
|
db: DatabaseName<'_>,
|
||||||
table: &str,
|
table: &str,
|
||||||
column: &str,
|
column: &str,
|
||||||
row_id: i64,
|
row_id: i64,
|
||||||
@ -92,9 +89,9 @@ impl Connection {
|
|||||||
) -> Result<Blob<'a>> {
|
) -> Result<Blob<'a>> {
|
||||||
let mut c = self.db.borrow_mut();
|
let mut c = self.db.borrow_mut();
|
||||||
let mut blob = ptr::null_mut();
|
let mut blob = ptr::null_mut();
|
||||||
let db = try!(db.to_cstring());
|
let db = db.to_cstring()?;
|
||||||
let table = try!(super::str_to_cstring(table));
|
let table = super::str_to_cstring(table)?;
|
||||||
let column = try!(super::str_to_cstring(column));
|
let column = super::str_to_cstring(column)?;
|
||||||
let rc = unsafe {
|
let rc = unsafe {
|
||||||
ffi::sqlite3_blob_open(
|
ffi::sqlite3_blob_open(
|
||||||
c.db(),
|
c.db(),
|
||||||
@ -254,7 +251,7 @@ impl<'conn> Drop for Blob<'conn> {
|
|||||||
pub struct ZeroBlob(pub i32);
|
pub struct ZeroBlob(pub i32);
|
||||||
|
|
||||||
impl ToSql for ZeroBlob {
|
impl ToSql for ZeroBlob {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
let ZeroBlob(length) = *self;
|
let ZeroBlob(length) = *self;
|
||||||
Ok(ToSqlOutput::ZeroBlob(length))
|
Ok(ToSqlOutput::ZeroBlob(length))
|
||||||
}
|
}
|
||||||
@ -262,16 +259,16 @@ impl ToSql for ZeroBlob {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::{Connection, DatabaseName, Result};
|
||||||
use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
||||||
use {Connection, DatabaseName, Result};
|
|
||||||
|
|
||||||
fn db_with_test_blob() -> Result<(Connection, i64)> {
|
fn db_with_test_blob() -> Result<(Connection, i64)> {
|
||||||
let db = try!(Connection::open_in_memory());
|
let db = Connection::open_in_memory()?;
|
||||||
let sql = "BEGIN;
|
let sql = "BEGIN;
|
||||||
CREATE TABLE test (content BLOB);
|
CREATE TABLE test (content BLOB);
|
||||||
INSERT INTO test VALUES (ZEROBLOB(10));
|
INSERT INTO test VALUES (ZEROBLOB(10));
|
||||||
END;";
|
END;";
|
||||||
try!(db.execute_batch(sql));
|
db.execute_batch(sql)?;
|
||||||
let rowid = db.last_insert_rowid();
|
let rowid = db.last_insert_rowid();
|
||||||
Ok((db, rowid))
|
Ok((db, rowid))
|
||||||
}
|
}
|
||||||
|
11
src/busy.rs
11
src/busy.rs
@ -1,11 +1,12 @@
|
|||||||
///! Busy handler (when the database is locked)
|
///! Busy handler (when the database is locked)
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::os::raw::{c_int, c_void};
|
use std::os::raw::{c_int, c_void};
|
||||||
|
use std::panic::catch_unwind;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
use {Connection, InnerConnection, Result};
|
use crate::{Connection, InnerConnection, Result};
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Set a busy handler that sleeps for a specified amount of time when a
|
/// Set a busy handler that sleeps for a specified amount of time when a
|
||||||
@ -48,7 +49,7 @@ impl Connection {
|
|||||||
pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> {
|
pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> {
|
||||||
unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int {
|
unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int {
|
||||||
let handler_fn: fn(i32) -> bool = mem::transmute(p_arg);
|
let handler_fn: fn(i32) -> bool = mem::transmute(p_arg);
|
||||||
if handler_fn(count) {
|
if let Ok(true) = catch_unwind(|| handler_fn(count)) {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
@ -74,14 +75,14 @@ impl InnerConnection {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
extern crate tempdir;
|
|
||||||
use self::tempdir::TempDir;
|
use self::tempdir::TempDir;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::mpsc::sync_channel;
|
use std::sync::mpsc::sync_channel;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use tempdir;
|
||||||
|
|
||||||
use {Connection, Error, ErrorCode, TransactionBehavior, NO_PARAMS};
|
use crate::{Connection, Error, ErrorCode, TransactionBehavior, NO_PARAMS};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_busy() {
|
fn test_default_busy() {
|
||||||
|
16
src/cache.rs
16
src/cache.rs
@ -1,10 +1,10 @@
|
|||||||
//! Prepared statements cache for faster execution.
|
//! Prepared statements cache for faster execution.
|
||||||
|
|
||||||
|
use crate::raw_statement::RawStatement;
|
||||||
|
use crate::{Connection, Result, Statement};
|
||||||
use lru_cache::LruCache;
|
use lru_cache::LruCache;
|
||||||
use raw_statement::RawStatement;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use {Connection, Result, Statement};
|
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Prepare a SQL statement for execution, returning a previously prepared
|
/// Prepare a SQL statement for execution, returning a previously prepared
|
||||||
@ -16,14 +16,14 @@ impl Connection {
|
|||||||
/// # use rusqlite::{Connection, Result};
|
/// # use rusqlite::{Connection, Result};
|
||||||
/// fn insert_new_people(conn: &Connection) -> Result<()> {
|
/// fn insert_new_people(conn: &Connection) -> Result<()> {
|
||||||
/// {
|
/// {
|
||||||
/// let mut stmt = try!(conn.prepare_cached("INSERT INTO People (name) VALUES (?)"));
|
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
|
||||||
/// try!(stmt.execute(&["Joe Smith"]));
|
/// stmt.execute(&["Joe Smith"])?;
|
||||||
/// }
|
/// }
|
||||||
/// {
|
/// {
|
||||||
/// // This will return the same underlying SQLite statement handle without
|
/// // This will return the same underlying SQLite statement handle without
|
||||||
/// // having to prepare it again.
|
/// // having to prepare it again.
|
||||||
/// let mut stmt = try!(conn.prepare_cached("INSERT INTO People (name) VALUES (?)"));
|
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
|
||||||
/// try!(stmt.execute(&["Bob Jones"]));
|
/// stmt.execute(&["Bob Jones"])?;
|
||||||
/// }
|
/// }
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
@ -33,7 +33,7 @@ impl Connection {
|
|||||||
///
|
///
|
||||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||||
/// or if the underlying SQLite call fails.
|
/// or if the underlying SQLite call fails.
|
||||||
pub fn prepare_cached<'a>(&'a self, sql: &str) -> Result<CachedStatement<'a>> {
|
pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>> {
|
||||||
self.cache.get(self, sql)
|
self.cache.get(self, sql)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ impl StatementCache {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::StatementCache;
|
use super::StatementCache;
|
||||||
use {Connection, NO_PARAMS};
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
|
||||||
impl StatementCache {
|
impl StatementCache {
|
||||||
fn clear(&self) {
|
fn clear(&self) {
|
||||||
|
@ -1,69 +1,18 @@
|
|||||||
//! Code related to `sqlite3_context` common to `functions` and `vtab` modules.
|
//! Code related to `sqlite3_context` common to `functions` and `vtab` modules.
|
||||||
|
|
||||||
use std::ffi::CStr;
|
use std::os::raw::{c_int, c_void};
|
||||||
use std::os::raw::{c_char, c_int, c_void};
|
|
||||||
#[cfg(feature = "array")]
|
#[cfg(feature = "array")]
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
use ffi::sqlite3_context;
|
use crate::ffi::sqlite3_context;
|
||||||
use ffi::sqlite3_value;
|
|
||||||
|
|
||||||
use str_to_cstring;
|
use crate::str_to_cstring;
|
||||||
use types::{ToSqlOutput, ValueRef};
|
use crate::types::{ToSqlOutput, ValueRef};
|
||||||
#[cfg(feature = "array")]
|
#[cfg(feature = "array")]
|
||||||
use vtab::array::{free_array, ARRAY_TYPE};
|
use crate::vtab::array::{free_array, ARRAY_TYPE};
|
||||||
|
|
||||||
impl<'a> ValueRef<'a> {
|
pub(crate) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<'_>) {
|
||||||
pub(crate) unsafe fn from_value(value: *mut sqlite3_value) -> ValueRef<'a> {
|
|
||||||
use std::slice::from_raw_parts;
|
|
||||||
|
|
||||||
match ffi::sqlite3_value_type(value) {
|
|
||||||
ffi::SQLITE_NULL => ValueRef::Null,
|
|
||||||
ffi::SQLITE_INTEGER => ValueRef::Integer(ffi::sqlite3_value_int64(value)),
|
|
||||||
ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
|
|
||||||
ffi::SQLITE_TEXT => {
|
|
||||||
let text = ffi::sqlite3_value_text(value);
|
|
||||||
assert!(
|
|
||||||
!text.is_null(),
|
|
||||||
"unexpected SQLITE_TEXT value type with NULL data"
|
|
||||||
);
|
|
||||||
let s = CStr::from_ptr(text as *const c_char);
|
|
||||||
|
|
||||||
// sqlite3_value_text returns UTF8 data, so our unwrap here should be fine.
|
|
||||||
let s = s
|
|
||||||
.to_str()
|
|
||||||
.expect("sqlite3_value_text returned invalid UTF-8");
|
|
||||||
ValueRef::Text(s)
|
|
||||||
}
|
|
||||||
ffi::SQLITE_BLOB => {
|
|
||||||
let (blob, len) = (
|
|
||||||
ffi::sqlite3_value_blob(value),
|
|
||||||
ffi::sqlite3_value_bytes(value),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
len >= 0,
|
|
||||||
"unexpected negative return from sqlite3_value_bytes"
|
|
||||||
);
|
|
||||||
if len > 0 {
|
|
||||||
assert!(
|
|
||||||
!blob.is_null(),
|
|
||||||
"unexpected SQLITE_BLOB value type with NULL data"
|
|
||||||
);
|
|
||||||
ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
|
|
||||||
} else {
|
|
||||||
// The return value from sqlite3_value_blob() for a zero-length BLOB
|
|
||||||
// is a NULL pointer.
|
|
||||||
ValueRef::Blob(&[])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!("sqlite3_value_type returned invalid value"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a>) {
|
|
||||||
let value = match *result {
|
let value = match *result {
|
||||||
ToSqlOutput::Borrowed(v) => v,
|
ToSqlOutput::Borrowed(v) => v,
|
||||||
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
|
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
|
||||||
|
94
src/error.rs
94
src/error.rs
@ -1,18 +1,14 @@
|
|||||||
|
use crate::types::Type;
|
||||||
|
use crate::{errmsg_to_string, ffi};
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str;
|
use std::str;
|
||||||
use types::Type;
|
|
||||||
use {errmsg_to_string, ffi};
|
|
||||||
|
|
||||||
/// Old name for `Error`. `SqliteError` is deprecated.
|
|
||||||
#[deprecated(since = "0.6.0", note = "Use Error instead")]
|
|
||||||
pub type SqliteError = Error;
|
|
||||||
|
|
||||||
/// Enum listing possible errors from rusqlite.
|
/// Enum listing possible errors from rusqlite.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(enum_variant_names)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// An error from an underlying SQLite call.
|
/// An error from an underlying SQLite call.
|
||||||
SqliteFailure(ffi::Error, Option<String>),
|
SqliteFailure(ffi::Error, Option<String>),
|
||||||
@ -23,7 +19,7 @@ pub enum Error {
|
|||||||
|
|
||||||
/// Error when the value of a particular column is requested, but it cannot
|
/// Error when the value of a particular column is requested, but it cannot
|
||||||
/// be converted to the requested Rust type.
|
/// be converted to the requested Rust type.
|
||||||
FromSqlConversionFailure(usize, Type, Box<error::Error + Send + Sync>),
|
FromSqlConversionFailure(usize, Type, Box<dyn error::Error + Send + Sync>),
|
||||||
|
|
||||||
/// Error when SQLite gives us an integral value outside the range of the
|
/// Error when SQLite gives us an integral value outside the range of the
|
||||||
/// requested type (e.g., trying to get the value 1000 into a `u8`).
|
/// requested type (e.g., trying to get the value 1000 into a `u8`).
|
||||||
@ -82,10 +78,10 @@ pub enum Error {
|
|||||||
/// `create_scalar_function`).
|
/// `create_scalar_function`).
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
UserFunctionError(Box<error::Error + Send + Sync>),
|
UserFunctionError(Box<dyn error::Error + Send + Sync>),
|
||||||
|
|
||||||
/// Error available for the implementors of the `ToSql` trait.
|
/// Error available for the implementors of the `ToSql` trait.
|
||||||
ToSqlConversionFailure(Box<error::Error + Send + Sync>),
|
ToSqlConversionFailure(Box<dyn error::Error + Send + Sync>),
|
||||||
|
|
||||||
/// Error when the SQL is not a `SELECT`, is not read-only.
|
/// Error when the SQL is not a `SELECT`, is not read-only.
|
||||||
InvalidQuery,
|
InvalidQuery,
|
||||||
@ -96,8 +92,57 @@ pub enum Error {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
ModuleError(String),
|
ModuleError(String),
|
||||||
|
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
UnwindingPanic,
|
||||||
|
|
||||||
|
/// An error returned when `Context::get_aux` attempts to retrieve data
|
||||||
|
/// of a different type than what had been stored using `Context::set_aux`.
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
GetAuxWrongType,
|
||||||
|
|
||||||
/// Error when the SQL contains multiple statements.
|
/// Error when the SQL contains multiple statements.
|
||||||
MultipleStatement,
|
MultipleStatement,}
|
||||||
|
|
||||||
|
impl PartialEq for Error {
|
||||||
|
fn eq(&self, other: &Error) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Error::SqliteFailure(e1, s1), Error::SqliteFailure(e2, s2)) => e1 == e2 && s1 == s2,
|
||||||
|
(Error::SqliteSingleThreadedMode, Error::SqliteSingleThreadedMode) => true,
|
||||||
|
(Error::IntegralValueOutOfRange(i1, n1), Error::IntegralValueOutOfRange(i2, n2)) => {
|
||||||
|
i1 == i2 && n1 == n2
|
||||||
|
}
|
||||||
|
(Error::Utf8Error(e1), Error::Utf8Error(e2)) => e1 == e2,
|
||||||
|
(Error::NulError(e1), Error::NulError(e2)) => e1 == e2,
|
||||||
|
(Error::InvalidParameterName(n1), Error::InvalidParameterName(n2)) => n1 == n2,
|
||||||
|
(Error::InvalidPath(p1), Error::InvalidPath(p2)) => p1 == p2,
|
||||||
|
(Error::ExecuteReturnedResults, Error::ExecuteReturnedResults) => true,
|
||||||
|
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
|
||||||
|
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
|
||||||
|
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
|
||||||
|
(Error::InvalidColumnType(i1, t1), Error::InvalidColumnType(i2, t2)) => {
|
||||||
|
i1 == i2 && t1 == t2
|
||||||
|
}
|
||||||
|
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
(
|
||||||
|
Error::InvalidFunctionParameterType(i1, t1),
|
||||||
|
Error::InvalidFunctionParameterType(i2, t2),
|
||||||
|
) => i1 == i2 && t1 == t2,
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
(
|
||||||
|
Error::InvalidFilterParameterType(i1, t1),
|
||||||
|
Error::InvalidFilterParameterType(i2, t2),
|
||||||
|
) => i1 == i2 && t1 == t2,
|
||||||
|
(Error::InvalidQuery, Error::InvalidQuery) => true,
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
(Error::ModuleError(s1), Error::ModuleError(s2)) => s1 == s2,
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
(Error::UnwindingPanic, Error::UnwindingPanic) => true,
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
(Error::GetAuxWrongType, Error::GetAuxWrongType) => true,
|
||||||
|
(_, _) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<str::Utf8Error> for Error {
|
impl From<str::Utf8Error> for Error {
|
||||||
@ -113,7 +158,7 @@ impl From<::std::ffi::NulError> for Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
Error::SqliteFailure(ref err, None) => err.fmt(f),
|
Error::SqliteFailure(ref err, None) => err.fmt(f),
|
||||||
Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
|
Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
|
||||||
@ -158,6 +203,10 @@ impl fmt::Display for Error {
|
|||||||
Error::InvalidQuery => write!(f, "Query is not read-only"),
|
Error::InvalidQuery => write!(f, "Query is not read-only"),
|
||||||
#[cfg(feature = "vtab")]
|
#[cfg(feature = "vtab")]
|
||||||
Error::ModuleError(ref desc) => write!(f, "{}", desc),
|
Error::ModuleError(ref desc) => write!(f, "{}", desc),
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
Error::UnwindingPanic => write!(f, "unwinding panic"),
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
Error::GetAuxWrongType => write!(f, "get_aux called with wrong type"),
|
||||||
Error::MultipleStatement => write!(f, "Multiple statements provided"),
|
Error::MultipleStatement => write!(f, "Multiple statements provided"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,11 +245,15 @@ impl error::Error for Error {
|
|||||||
Error::InvalidQuery => "query is not read-only",
|
Error::InvalidQuery => "query is not read-only",
|
||||||
#[cfg(feature = "vtab")]
|
#[cfg(feature = "vtab")]
|
||||||
Error::ModuleError(ref desc) => desc,
|
Error::ModuleError(ref desc) => desc,
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
Error::UnwindingPanic => "unwinding panic",
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
Error::GetAuxWrongType => "get_aux called with wrong type",
|
||||||
Error::MultipleStatement => "multiple statements provided",
|
Error::MultipleStatement => "multiple statements provided",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cause(&self) -> Option<&error::Error> {
|
fn cause(&self) -> Option<&dyn error::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
Error::SqliteFailure(ref err, _) => Some(err),
|
Error::SqliteFailure(ref err, _) => Some(err),
|
||||||
Error::Utf8Error(ref err) => Some(err),
|
Error::Utf8Error(ref err) => Some(err),
|
||||||
@ -232,6 +285,12 @@ impl error::Error for Error {
|
|||||||
|
|
||||||
#[cfg(feature = "vtab")]
|
#[cfg(feature = "vtab")]
|
||||||
Error::ModuleError(_) => None,
|
Error::ModuleError(_) => None,
|
||||||
|
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
Error::UnwindingPanic => None,
|
||||||
|
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
Error::GetAuxWrongType => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,3 +309,12 @@ pub fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error {
|
|||||||
};
|
};
|
||||||
error_from_sqlite_code(code, message)
|
error_from_sqlite_code(code, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! check {
|
||||||
|
($funcall:expr) => {{
|
||||||
|
let rc = $funcall;
|
||||||
|
if rc != crate::ffi::SQLITE_OK {
|
||||||
|
Err(crate::error::error_from_sqlite_code(rc, None))?;
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
230
src/functions.rs
230
src/functions.rs
@ -9,10 +9,6 @@
|
|||||||
//! module.
|
//! module.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! extern crate libsqlite3_sys;
|
|
||||||
//! extern crate rusqlite;
|
|
||||||
//! extern crate regex;
|
|
||||||
//!
|
|
||||||
//! use regex::Regex;
|
//! use regex::Regex;
|
||||||
//! use rusqlite::{Connection, Error, Result, NO_PARAMS};
|
//! use rusqlite::{Connection, Error, Result, NO_PARAMS};
|
||||||
//! use std::collections::HashMap;
|
//! use std::collections::HashMap;
|
||||||
@ -20,7 +16,7 @@
|
|||||||
//! fn add_regexp_function(db: &Connection) -> Result<()> {
|
//! fn add_regexp_function(db: &Connection) -> Result<()> {
|
||||||
//! let mut cached_regexes = HashMap::new();
|
//! let mut cached_regexes = HashMap::new();
|
||||||
//! db.create_scalar_function("regexp", 2, true, move |ctx| {
|
//! db.create_scalar_function("regexp", 2, true, move |ctx| {
|
||||||
//! let regex_s = try!(ctx.get::<String>(0));
|
//! let regex_s = ctx.get::<String>(0)?;
|
||||||
//! let entry = cached_regexes.entry(regex_s.clone());
|
//! let entry = cached_regexes.entry(regex_s.clone());
|
||||||
//! let regex = {
|
//! let regex = {
|
||||||
//! use std::collections::hash_map::Entry::{Occupied, Vacant};
|
//! use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||||
@ -33,7 +29,7 @@
|
|||||||
//! }
|
//! }
|
||||||
//! };
|
//! };
|
||||||
//!
|
//!
|
||||||
//! let text = try!(ctx.get::<String>(1));
|
//! let text = ctx.get::<String>(1)?;
|
||||||
//! Ok(regex.is_match(&text))
|
//! Ok(regex.is_match(&text))
|
||||||
//! })
|
//! })
|
||||||
//! }
|
//! }
|
||||||
@ -55,17 +51,18 @@
|
|||||||
//! ```
|
//! ```
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::os::raw::{c_int, c_void};
|
use std::os::raw::{c_int, c_void};
|
||||||
|
use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
use ffi::sqlite3_context;
|
use crate::ffi::sqlite3_context;
|
||||||
use ffi::sqlite3_value;
|
use crate::ffi::sqlite3_value;
|
||||||
|
|
||||||
use context::set_result;
|
use crate::context::set_result;
|
||||||
use types::{FromSql, FromSqlError, ToSql, ValueRef};
|
use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
|
||||||
|
|
||||||
use {str_to_cstring, Connection, Error, InnerConnection, Result};
|
use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
|
||||||
|
|
||||||
unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
|
unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
|
||||||
// Extended constraint error codes were added in SQLite 3.7.16. We don't have
|
// Extended constraint error codes were added in SQLite 3.7.16. We don't have
|
||||||
@ -137,6 +134,10 @@ impl<'a> Context<'a> {
|
|||||||
FromSqlError::Other(err) => {
|
FromSqlError::Other(err) => {
|
||||||
Error::FromSqlConversionFailure(idx, value.data_type(), err)
|
Error::FromSqlConversionFailure(idx, value.data_type(), err)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
FromSqlError::InvalidI128Size(_) => {
|
||||||
|
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,32 +154,32 @@ impl<'a> Context<'a> {
|
|||||||
/// Sets the auxilliary data associated with a particular parameter. See
|
/// Sets the auxilliary data associated with a particular parameter. See
|
||||||
/// https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of
|
/// https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of
|
||||||
/// this feature, or the unit tests of this module for an example.
|
/// this feature, or the unit tests of this module for an example.
|
||||||
pub fn set_aux<T>(&self, arg: c_int, value: T) {
|
pub fn set_aux<T: 'static>(&self, arg: c_int, value: T) {
|
||||||
let boxed = Box::into_raw(Box::new(value));
|
let boxed = Box::into_raw(Box::new((std::any::TypeId::of::<T>(), value)));
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::sqlite3_set_auxdata(
|
ffi::sqlite3_set_auxdata(
|
||||||
self.ctx,
|
self.ctx,
|
||||||
arg,
|
arg,
|
||||||
boxed as *mut c_void,
|
boxed as *mut c_void,
|
||||||
Some(free_boxed_value::<T>),
|
Some(free_boxed_value::<(std::any::TypeId, T)>),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the auxilliary data that was associated with a given parameter
|
/// Gets the auxilliary data that was associated with a given parameter
|
||||||
/// via `set_aux`. Returns `None` if no data has been associated.
|
/// via `set_aux`. Returns `Ok(None)` if no data has been associated,
|
||||||
///
|
/// and .
|
||||||
/// # Unsafety
|
pub fn get_aux<T: 'static>(&self, arg: c_int) -> Result<Option<&T>> {
|
||||||
///
|
let p = unsafe { ffi::sqlite3_get_auxdata(self.ctx, arg) as *mut (std::any::TypeId, T) };
|
||||||
/// This function is unsafe as there is no guarantee that the type `T`
|
|
||||||
/// requested matches the type `T` that was provided to `set_aux`. The
|
|
||||||
/// types must be identical.
|
|
||||||
pub unsafe fn get_aux<T>(&self, arg: c_int) -> Option<&T> {
|
|
||||||
let p = ffi::sqlite3_get_auxdata(self.ctx, arg) as *mut T;
|
|
||||||
if p.is_null() {
|
if p.is_null() {
|
||||||
None
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
Some(&*p)
|
let id_val = unsafe { &*p };
|
||||||
|
if std::any::TypeId::of::<T>() != id_val.0 {
|
||||||
|
Err(Error::GetAuxWrongType)
|
||||||
|
} else {
|
||||||
|
Ok(Some(&id_val.1))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,6 +190,7 @@ impl<'a> Context<'a> {
|
|||||||
/// result. Implementations should be stateless.
|
/// result. Implementations should be stateless.
|
||||||
pub trait Aggregate<A, T>
|
pub trait Aggregate<A, T>
|
||||||
where
|
where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
/// Initializes the aggregation context. Will be called prior to the first
|
/// Initializes the aggregation context. Will be called prior to the first
|
||||||
@ -198,14 +200,14 @@ where
|
|||||||
|
|
||||||
/// "step" function called once for each row in an aggregate group. May be
|
/// "step" function called once for each row in an aggregate group. May be
|
||||||
/// called 0 times if there are no rows.
|
/// called 0 times if there are no rows.
|
||||||
fn step(&self, &mut Context, &mut A) -> Result<()>;
|
fn step(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
|
||||||
|
|
||||||
/// Computes and returns the final result. Will be called exactly once for
|
/// Computes and returns the final result. Will be called exactly once for
|
||||||
/// each invocation of the function. If `step()` was called at least
|
/// each invocation of the function. If `step()` was called at least
|
||||||
/// once, will be given `Some(A)` (the same `A` as was created by
|
/// once, will be given `Some(A)` (the same `A` as was created by
|
||||||
/// `init` and given to `step`); if `step()` was not called (because
|
/// `init` and given to `step`); if `step()` was not called (because
|
||||||
/// the function is running against 0 rows), will be given `None`.
|
/// the function is running against 0 rows), will be given `None`.
|
||||||
fn finalize(&self, Option<A>) -> Result<T>;
|
fn finalize(&self, _: Option<A>) -> Result<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
@ -224,12 +226,12 @@ impl Connection {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use rusqlite::{Connection, Result, NO_PARAMS};
|
/// # use rusqlite::{Connection, Result, NO_PARAMS};
|
||||||
/// fn scalar_function_example(db: Connection) -> Result<()> {
|
/// fn scalar_function_example(db: Connection) -> Result<()> {
|
||||||
/// try!(db.create_scalar_function("halve", 1, true, |ctx| {
|
/// db.create_scalar_function("halve", 1, true, |ctx| {
|
||||||
/// let value = try!(ctx.get::<f64>(0));
|
/// let value = ctx.get::<f64>(0)?;
|
||||||
/// Ok(value / 2f64)
|
/// Ok(value / 2f64)
|
||||||
/// }));
|
/// })?;
|
||||||
///
|
///
|
||||||
/// let six_halved: f64 = try!(db.query_row("SELECT halve(6)", NO_PARAMS, |r| r.get(0)));
|
/// let six_halved: f64 = db.query_row("SELECT halve(6)", NO_PARAMS, |r| r.get(0))?;
|
||||||
/// assert_eq!(six_halved, 3f64);
|
/// assert_eq!(six_halved, 3f64);
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
@ -246,7 +248,7 @@ impl Connection {
|
|||||||
x_func: F,
|
x_func: F,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
F: FnMut(&Context) -> Result<T> + Send + 'static,
|
F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'static,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
self.db
|
self.db
|
||||||
@ -267,6 +269,7 @@ impl Connection {
|
|||||||
aggr: D,
|
aggr: D,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
D: Aggregate<A, T>,
|
D: Aggregate<A, T>,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
@ -297,7 +300,7 @@ impl InnerConnection {
|
|||||||
x_func: F,
|
x_func: F,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
F: FnMut(&Context) -> Result<T> + Send + 'static,
|
F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'static,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
unsafe extern "C" fn call_boxed_closure<F, T>(
|
unsafe extern "C" fn call_boxed_closure<F, T>(
|
||||||
@ -305,28 +308,36 @@ impl InnerConnection {
|
|||||||
argc: c_int,
|
argc: c_int,
|
||||||
argv: *mut *mut sqlite3_value,
|
argv: *mut *mut sqlite3_value,
|
||||||
) where
|
) where
|
||||||
F: FnMut(&Context) -> Result<T>,
|
F: FnMut(&Context<'_>) -> Result<T>,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
let ctx = Context {
|
let r = catch_unwind(|| {
|
||||||
ctx,
|
let boxed_f: *mut F = ffi::sqlite3_user_data(ctx) as *mut F;
|
||||||
args: slice::from_raw_parts(argv, argc as usize),
|
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
|
||||||
|
let ctx = Context {
|
||||||
|
ctx,
|
||||||
|
args: slice::from_raw_parts(argv, argc as usize),
|
||||||
|
};
|
||||||
|
(*boxed_f)(&ctx)
|
||||||
|
});
|
||||||
|
let t = match r {
|
||||||
|
Err(_) => {
|
||||||
|
report_error(ctx, &Error::UnwindingPanic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(r) => r,
|
||||||
};
|
};
|
||||||
let boxed_f: *mut F = ffi::sqlite3_user_data(ctx.ctx) as *mut F;
|
|
||||||
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
|
|
||||||
|
|
||||||
let t = (*boxed_f)(&ctx);
|
|
||||||
let t = t.as_ref().map(|t| ToSql::to_sql(t));
|
let t = t.as_ref().map(|t| ToSql::to_sql(t));
|
||||||
|
|
||||||
match t {
|
match t {
|
||||||
Ok(Ok(ref value)) => set_result(ctx.ctx, value),
|
Ok(Ok(ref value)) => set_result(ctx, value),
|
||||||
Ok(Err(err)) => report_error(ctx.ctx, &err),
|
Ok(Err(err)) => report_error(ctx, &err),
|
||||||
Err(err) => report_error(ctx.ctx, err),
|
Err(err) => report_error(ctx, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let boxed_f: *mut F = Box::into_raw(Box::new(x_func));
|
let boxed_f: *mut F = Box::into_raw(Box::new(x_func));
|
||||||
let c_name = try!(str_to_cstring(fn_name));
|
let c_name = str_to_cstring(fn_name)?;
|
||||||
let mut flags = ffi::SQLITE_UTF8;
|
let mut flags = ffi::SQLITE_UTF8;
|
||||||
if deterministic {
|
if deterministic {
|
||||||
flags |= ffi::SQLITE_DETERMINISTIC;
|
flags |= ffi::SQLITE_DETERMINISTIC;
|
||||||
@ -355,6 +366,7 @@ impl InnerConnection {
|
|||||||
aggr: D,
|
aggr: D,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
D: Aggregate<A, T>,
|
D: Aggregate<A, T>,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
@ -374,15 +386,10 @@ impl InnerConnection {
|
|||||||
argc: c_int,
|
argc: c_int,
|
||||||
argv: *mut *mut sqlite3_value,
|
argv: *mut *mut sqlite3_value,
|
||||||
) where
|
) where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
D: Aggregate<A, T>,
|
D: Aggregate<A, T>,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
|
|
||||||
assert!(
|
|
||||||
!boxed_aggr.is_null(),
|
|
||||||
"Internal error - null aggregate pointer"
|
|
||||||
);
|
|
||||||
|
|
||||||
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
|
let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
|
||||||
Some(pac) => pac,
|
Some(pac) => pac,
|
||||||
None => {
|
None => {
|
||||||
@ -391,32 +398,40 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (*pac as *mut A).is_null() {
|
let r = catch_unwind(|| {
|
||||||
*pac = Box::into_raw(Box::new((*boxed_aggr).init()));
|
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
|
||||||
}
|
assert!(
|
||||||
|
!boxed_aggr.is_null(),
|
||||||
let mut ctx = Context {
|
"Internal error - null aggregate pointer"
|
||||||
ctx,
|
);
|
||||||
args: slice::from_raw_parts(argv, argc as usize),
|
if (*pac as *mut A).is_null() {
|
||||||
|
*pac = Box::into_raw(Box::new((*boxed_aggr).init()));
|
||||||
|
}
|
||||||
|
let mut ctx = Context {
|
||||||
|
ctx,
|
||||||
|
args: slice::from_raw_parts(argv, argc as usize),
|
||||||
|
};
|
||||||
|
(*boxed_aggr).step(&mut ctx, &mut **pac)
|
||||||
|
});
|
||||||
|
let r = match r {
|
||||||
|
Err(_) => {
|
||||||
|
report_error(ctx, &Error::UnwindingPanic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(r) => r,
|
||||||
};
|
};
|
||||||
|
match r {
|
||||||
match (*boxed_aggr).step(&mut ctx, &mut **pac) {
|
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(err) => report_error(ctx.ctx, &err),
|
Err(err) => report_error(ctx, &err),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
|
unsafe extern "C" fn call_boxed_final<A, D, T>(ctx: *mut sqlite3_context)
|
||||||
where
|
where
|
||||||
|
A: RefUnwindSafe + UnwindSafe,
|
||||||
D: Aggregate<A, T>,
|
D: Aggregate<A, T>,
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
|
|
||||||
assert!(
|
|
||||||
!boxed_aggr.is_null(),
|
|
||||||
"Internal error - null aggregate pointer"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Within the xFinal callback, it is customary to set N=0 in calls to
|
// Within the xFinal callback, it is customary to set N=0 in calls to
|
||||||
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
|
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
|
||||||
let a: Option<A> = match aggregate_context(ctx, 0) {
|
let a: Option<A> = match aggregate_context(ctx, 0) {
|
||||||
@ -431,7 +446,21 @@ impl InnerConnection {
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let t = (*boxed_aggr).finalize(a);
|
let r = catch_unwind(|| {
|
||||||
|
let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
|
||||||
|
assert!(
|
||||||
|
!boxed_aggr.is_null(),
|
||||||
|
"Internal error - null aggregate pointer"
|
||||||
|
);
|
||||||
|
(*boxed_aggr).finalize(a)
|
||||||
|
});
|
||||||
|
let t = match r {
|
||||||
|
Err(_) => {
|
||||||
|
report_error(ctx, &Error::UnwindingPanic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(r) => r,
|
||||||
|
};
|
||||||
let t = t.as_ref().map(|t| ToSql::to_sql(t));
|
let t = t.as_ref().map(|t| ToSql::to_sql(t));
|
||||||
match t {
|
match t {
|
||||||
Ok(Ok(ref value)) => set_result(ctx, value),
|
Ok(Ok(ref value)) => set_result(ctx, value),
|
||||||
@ -441,7 +470,7 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
|
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
|
||||||
let c_name = try!(str_to_cstring(fn_name));
|
let c_name = str_to_cstring(fn_name)?;
|
||||||
let mut flags = ffi::SQLITE_UTF8;
|
let mut flags = ffi::SQLITE_UTF8;
|
||||||
if deterministic {
|
if deterministic {
|
||||||
flags |= ffi::SQLITE_DETERMINISTIC;
|
flags |= ffi::SQLITE_DETERMINISTIC;
|
||||||
@ -463,7 +492,7 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
|
fn remove_function(&mut self, fn_name: &str, n_arg: c_int) -> Result<()> {
|
||||||
let c_name = try!(str_to_cstring(fn_name));
|
let c_name = str_to_cstring(fn_name)?;
|
||||||
let r = unsafe {
|
let r = unsafe {
|
||||||
ffi::sqlite3_create_function_v2(
|
ffi::sqlite3_create_function_v2(
|
||||||
self.db(),
|
self.db(),
|
||||||
@ -483,19 +512,19 @@ impl InnerConnection {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
extern crate regex;
|
use regex;
|
||||||
|
|
||||||
use self::regex::Regex;
|
use self::regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::f64::EPSILON;
|
use std::f64::EPSILON;
|
||||||
use std::os::raw::c_double;
|
use std::os::raw::c_double;
|
||||||
|
|
||||||
use functions::{Aggregate, Context};
|
use crate::functions::{Aggregate, Context};
|
||||||
use {Connection, Error, Result, NO_PARAMS};
|
use crate::{Connection, Error, Result, NO_PARAMS};
|
||||||
|
|
||||||
fn half(ctx: &Context) -> Result<c_double> {
|
fn half(ctx: &Context<'_>) -> Result<c_double> {
|
||||||
assert!(ctx.len() == 1, "called with unexpected number of arguments");
|
assert_eq!(ctx.len(), 1, "called with unexpected number of arguments");
|
||||||
let value = try!(ctx.get::<c_double>(0));
|
let value = ctx.get::<c_double>(0)?;
|
||||||
Ok(value / 2f64)
|
Ok(value / 2f64)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,13 +552,13 @@ mod test {
|
|||||||
// This implementation of a regexp scalar function uses SQLite's auxilliary data
|
// This implementation of a regexp scalar function uses SQLite's auxilliary data
|
||||||
// (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular
|
// (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular
|
||||||
// expression multiple times within one query.
|
// expression multiple times within one query.
|
||||||
fn regexp_with_auxilliary(ctx: &Context) -> Result<bool> {
|
fn regexp_with_auxilliary(ctx: &Context<'_>) -> Result<bool> {
|
||||||
assert!(ctx.len() == 2, "called with unexpected number of arguments");
|
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
||||||
|
|
||||||
let saved_re: Option<&Regex> = unsafe { ctx.get_aux(0) };
|
let saved_re: Option<&Regex> = ctx.get_aux(0)?;
|
||||||
let new_re = match saved_re {
|
let new_re = match saved_re {
|
||||||
None => {
|
None => {
|
||||||
let s = try!(ctx.get::<String>(0));
|
let s = ctx.get::<String>(0)?;
|
||||||
match Regex::new(&s) {
|
match Regex::new(&s) {
|
||||||
Ok(r) => Some(r),
|
Ok(r) => Some(r),
|
||||||
Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
||||||
@ -605,9 +634,9 @@ mod test {
|
|||||||
// until the function is removed.
|
// until the function is removed.
|
||||||
let mut cached_regexes = HashMap::new();
|
let mut cached_regexes = HashMap::new();
|
||||||
db.create_scalar_function("regexp", 2, true, move |ctx| {
|
db.create_scalar_function("regexp", 2, true, move |ctx| {
|
||||||
assert!(ctx.len() == 2, "called with unexpected number of arguments");
|
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
|
||||||
|
|
||||||
let regex_s = try!(ctx.get::<String>(0));
|
let regex_s = ctx.get::<String>(0)?;
|
||||||
let entry = cached_regexes.entry(regex_s.clone());
|
let entry = cached_regexes.entry(regex_s.clone());
|
||||||
let regex = {
|
let regex = {
|
||||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||||
@ -620,7 +649,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let text = try!(ctx.get::<String>(1));
|
let text = ctx.get::<String>(1)?;
|
||||||
Ok(regex.is_match(&text))
|
Ok(regex.is_match(&text))
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -648,7 +677,7 @@ mod test {
|
|||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
|
|
||||||
for idx in 0..ctx.len() {
|
for idx in 0..ctx.len() {
|
||||||
let s = try!(ctx.get::<String>(idx));
|
let s = ctx.get::<String>(idx)?;
|
||||||
ret.push_str(&s);
|
ret.push_str(&s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -666,6 +695,31 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_aux_type_checking() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.create_scalar_function("example", 2, false, |ctx| {
|
||||||
|
if !ctx.get::<bool>(1)? {
|
||||||
|
ctx.set_aux::<i64>(0, 100);
|
||||||
|
} else {
|
||||||
|
assert_eq!(ctx.get_aux::<String>(0), Err(Error::GetAuxWrongType));
|
||||||
|
assert_eq!(ctx.get_aux::<i64>(0), Ok(Some(&100)));
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res: bool = db
|
||||||
|
.query_row(
|
||||||
|
"SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)",
|
||||||
|
NO_PARAMS,
|
||||||
|
|r| r.get(0),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// Doesn't actually matter, we'll assert in the function if there's a problem.
|
||||||
|
assert!(res);
|
||||||
|
}
|
||||||
|
|
||||||
struct Sum;
|
struct Sum;
|
||||||
struct Count;
|
struct Count;
|
||||||
|
|
||||||
@ -674,8 +728,8 @@ mod test {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step(&self, ctx: &mut Context, sum: &mut i64) -> Result<()> {
|
fn step(&self, ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
|
||||||
*sum += try!(ctx.get::<i64>(0));
|
*sum += ctx.get::<i64>(0)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,7 +743,7 @@ mod test {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step(&self, _ctx: &mut Context, sum: &mut i64) -> Result<()> {
|
fn step(&self, _ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
|
||||||
*sum += 1;
|
*sum += 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
118
src/hooks.rs
118
src/hooks.rs
@ -2,89 +2,29 @@
|
|||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
use std::os::raw::{c_char, c_int, c_void};
|
use std::os::raw::{c_char, c_int, c_void};
|
||||||
|
use std::panic::catch_unwind;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
|
|
||||||
use {Connection, InnerConnection};
|
use crate::{Connection, InnerConnection};
|
||||||
|
|
||||||
/// Authorizer Action Codes
|
/// Action Codes
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[repr(i32)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
UNKNOWN = -1,
|
UNKNOWN = -1,
|
||||||
SQLITE_CREATE_INDEX = ffi::SQLITE_CREATE_INDEX as isize,
|
SQLITE_DELETE = ffi::SQLITE_DELETE,
|
||||||
SQLITE_CREATE_TABLE = ffi::SQLITE_CREATE_TABLE as isize,
|
SQLITE_INSERT = ffi::SQLITE_INSERT,
|
||||||
SQLITE_CREATE_TEMP_INDEX = ffi::SQLITE_CREATE_TEMP_INDEX as isize,
|
SQLITE_UPDATE = ffi::SQLITE_UPDATE,
|
||||||
SQLITE_CREATE_TEMP_TABLE = ffi::SQLITE_CREATE_TEMP_TABLE as isize,
|
|
||||||
SQLITE_CREATE_TEMP_TRIGGER = ffi::SQLITE_CREATE_TEMP_TRIGGER as isize,
|
|
||||||
SQLITE_CREATE_TEMP_VIEW = ffi::SQLITE_CREATE_TEMP_VIEW as isize,
|
|
||||||
SQLITE_CREATE_TRIGGER = ffi::SQLITE_CREATE_TRIGGER as isize,
|
|
||||||
SQLITE_CREATE_VIEW = ffi::SQLITE_CREATE_VIEW as isize,
|
|
||||||
SQLITE_DELETE = ffi::SQLITE_DELETE as isize,
|
|
||||||
SQLITE_DROP_INDEX = ffi::SQLITE_DROP_INDEX as isize,
|
|
||||||
SQLITE_DROP_TABLE = ffi::SQLITE_DROP_TABLE as isize,
|
|
||||||
SQLITE_DROP_TEMP_INDEX = ffi::SQLITE_DROP_TEMP_INDEX as isize,
|
|
||||||
SQLITE_DROP_TEMP_TABLE = ffi::SQLITE_DROP_TEMP_TABLE as isize,
|
|
||||||
SQLITE_DROP_TEMP_TRIGGER = ffi::SQLITE_DROP_TEMP_TRIGGER as isize,
|
|
||||||
SQLITE_DROP_TEMP_VIEW = ffi::SQLITE_DROP_TEMP_VIEW as isize,
|
|
||||||
SQLITE_DROP_TRIGGER = ffi::SQLITE_DROP_TRIGGER as isize,
|
|
||||||
SQLITE_DROP_VIEW = ffi::SQLITE_DROP_VIEW as isize,
|
|
||||||
SQLITE_INSERT = ffi::SQLITE_INSERT as isize,
|
|
||||||
SQLITE_PRAGMA = ffi::SQLITE_PRAGMA as isize,
|
|
||||||
SQLITE_READ = ffi::SQLITE_READ as isize,
|
|
||||||
SQLITE_SELECT = ffi::SQLITE_SELECT as isize,
|
|
||||||
SQLITE_TRANSACTION = ffi::SQLITE_TRANSACTION as isize,
|
|
||||||
SQLITE_UPDATE = ffi::SQLITE_UPDATE as isize,
|
|
||||||
SQLITE_ATTACH = ffi::SQLITE_ATTACH as isize,
|
|
||||||
SQLITE_DETACH = ffi::SQLITE_DETACH as isize,
|
|
||||||
SQLITE_ALTER_TABLE = ffi::SQLITE_ALTER_TABLE as isize,
|
|
||||||
SQLITE_REINDEX = ffi::SQLITE_REINDEX as isize,
|
|
||||||
SQLITE_ANALYZE = ffi::SQLITE_ANALYZE as isize,
|
|
||||||
SQLITE_CREATE_VTABLE = ffi::SQLITE_CREATE_VTABLE as isize,
|
|
||||||
SQLITE_DROP_VTABLE = ffi::SQLITE_DROP_VTABLE as isize,
|
|
||||||
SQLITE_FUNCTION = ffi::SQLITE_FUNCTION as isize,
|
|
||||||
SQLITE_SAVEPOINT = ffi::SQLITE_SAVEPOINT as isize,
|
|
||||||
SQLITE_COPY = ffi::SQLITE_COPY as isize,
|
|
||||||
SQLITE_RECURSIVE = 33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i32> for Action {
|
impl From<i32> for Action {
|
||||||
fn from(code: i32) -> Action {
|
fn from(code: i32) -> Action {
|
||||||
match code {
|
match code {
|
||||||
ffi::SQLITE_CREATE_INDEX => Action::SQLITE_CREATE_INDEX,
|
|
||||||
ffi::SQLITE_CREATE_TABLE => Action::SQLITE_CREATE_TABLE,
|
|
||||||
ffi::SQLITE_CREATE_TEMP_INDEX => Action::SQLITE_CREATE_TEMP_INDEX,
|
|
||||||
ffi::SQLITE_CREATE_TEMP_TABLE => Action::SQLITE_CREATE_TEMP_TABLE,
|
|
||||||
ffi::SQLITE_CREATE_TEMP_TRIGGER => Action::SQLITE_CREATE_TEMP_TRIGGER,
|
|
||||||
ffi::SQLITE_CREATE_TEMP_VIEW => Action::SQLITE_CREATE_TEMP_VIEW,
|
|
||||||
ffi::SQLITE_CREATE_TRIGGER => Action::SQLITE_CREATE_TRIGGER,
|
|
||||||
ffi::SQLITE_CREATE_VIEW => Action::SQLITE_CREATE_VIEW,
|
|
||||||
ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
|
ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
|
||||||
ffi::SQLITE_DROP_INDEX => Action::SQLITE_DROP_INDEX,
|
|
||||||
ffi::SQLITE_DROP_TABLE => Action::SQLITE_DROP_TABLE,
|
|
||||||
ffi::SQLITE_DROP_TEMP_INDEX => Action::SQLITE_DROP_TEMP_INDEX,
|
|
||||||
ffi::SQLITE_DROP_TEMP_TABLE => Action::SQLITE_DROP_TEMP_TABLE,
|
|
||||||
ffi::SQLITE_DROP_TEMP_TRIGGER => Action::SQLITE_DROP_TEMP_TRIGGER,
|
|
||||||
ffi::SQLITE_DROP_TEMP_VIEW => Action::SQLITE_DROP_TEMP_VIEW,
|
|
||||||
ffi::SQLITE_DROP_TRIGGER => Action::SQLITE_DROP_TRIGGER,
|
|
||||||
ffi::SQLITE_DROP_VIEW => Action::SQLITE_DROP_VIEW,
|
|
||||||
ffi::SQLITE_INSERT => Action::SQLITE_INSERT,
|
ffi::SQLITE_INSERT => Action::SQLITE_INSERT,
|
||||||
ffi::SQLITE_PRAGMA => Action::SQLITE_PRAGMA,
|
|
||||||
ffi::SQLITE_READ => Action::SQLITE_READ,
|
|
||||||
ffi::SQLITE_SELECT => Action::SQLITE_SELECT,
|
|
||||||
ffi::SQLITE_TRANSACTION => Action::SQLITE_TRANSACTION,
|
|
||||||
ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE,
|
ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE,
|
||||||
ffi::SQLITE_ATTACH => Action::SQLITE_ATTACH,
|
|
||||||
ffi::SQLITE_DETACH => Action::SQLITE_DETACH,
|
|
||||||
ffi::SQLITE_ALTER_TABLE => Action::SQLITE_ALTER_TABLE,
|
|
||||||
ffi::SQLITE_REINDEX => Action::SQLITE_REINDEX,
|
|
||||||
ffi::SQLITE_ANALYZE => Action::SQLITE_ANALYZE,
|
|
||||||
ffi::SQLITE_CREATE_VTABLE => Action::SQLITE_CREATE_VTABLE,
|
|
||||||
ffi::SQLITE_DROP_VTABLE => Action::SQLITE_DROP_VTABLE,
|
|
||||||
ffi::SQLITE_FUNCTION => Action::SQLITE_FUNCTION,
|
|
||||||
ffi::SQLITE_SAVEPOINT => Action::SQLITE_SAVEPOINT,
|
|
||||||
ffi::SQLITE_COPY => Action::SQLITE_COPY,
|
|
||||||
33 => Action::SQLITE_RECURSIVE,
|
|
||||||
_ => Action::UNKNOWN,
|
_ => Action::UNKNOWN,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,8 +86,11 @@ impl InnerConnection {
|
|||||||
where
|
where
|
||||||
F: FnMut() -> bool,
|
F: FnMut() -> bool,
|
||||||
{
|
{
|
||||||
let boxed_hook: *mut F = p_arg as *mut F;
|
let r = catch_unwind(|| {
|
||||||
if (*boxed_hook)() {
|
let boxed_hook: *mut F = p_arg as *mut F;
|
||||||
|
(*boxed_hook)()
|
||||||
|
});
|
||||||
|
if let Ok(true) = r {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
@ -192,8 +135,10 @@ impl InnerConnection {
|
|||||||
where
|
where
|
||||||
F: FnMut(),
|
F: FnMut(),
|
||||||
{
|
{
|
||||||
let boxed_hook: *mut F = p_arg as *mut F;
|
let _ = catch_unwind(|| {
|
||||||
(*boxed_hook)();
|
let boxed_hook: *mut F = p_arg as *mut F;
|
||||||
|
(*boxed_hook)();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let free_rollback_hook = if hook.is_some() {
|
let free_rollback_hook = if hook.is_some() {
|
||||||
@ -239,8 +184,6 @@ impl InnerConnection {
|
|||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
let boxed_hook: *mut F = p_arg as *mut F;
|
|
||||||
|
|
||||||
let action = Action::from(action_code);
|
let action = Action::from(action_code);
|
||||||
let db_name = {
|
let db_name = {
|
||||||
let c_slice = CStr::from_ptr(db_str).to_bytes();
|
let c_slice = CStr::from_ptr(db_str).to_bytes();
|
||||||
@ -251,7 +194,10 @@ impl InnerConnection {
|
|||||||
str::from_utf8_unchecked(c_slice)
|
str::from_utf8_unchecked(c_slice)
|
||||||
};
|
};
|
||||||
|
|
||||||
(*boxed_hook)(action, db_name, tbl_name, row_id);
|
let _ = catch_unwind(|| {
|
||||||
|
let boxed_hook: *mut F = p_arg as *mut F;
|
||||||
|
(*boxed_hook)(action, db_name, tbl_name, row_id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let free_update_hook = if hook.is_some() {
|
let free_update_hook = if hook.is_some() {
|
||||||
@ -289,23 +235,23 @@ fn free_boxed_hook<F>(p: *mut c_void) {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::Action;
|
use super::Action;
|
||||||
|
use crate::Connection;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use Connection;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_commit_hook() {
|
fn test_commit_hook() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref called: AtomicBool = AtomicBool::new(false);
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
db.commit_hook(Some(|| {
|
db.commit_hook(Some(|| {
|
||||||
called.store(true, Ordering::Relaxed);
|
CALLED.store(true, Ordering::Relaxed);
|
||||||
false
|
false
|
||||||
}));
|
}));
|
||||||
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
|
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(called.load(Ordering::Relaxed));
|
assert!(CALLED.load(Ordering::Relaxed));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -326,14 +272,14 @@ mod test {
|
|||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref called: AtomicBool = AtomicBool::new(false);
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
db.rollback_hook(Some(|| {
|
db.rollback_hook(Some(|| {
|
||||||
called.store(true, Ordering::Relaxed);
|
CALLED.store(true, Ordering::Relaxed);
|
||||||
}));
|
}));
|
||||||
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
|
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(called.load(Ordering::Relaxed));
|
assert!(CALLED.load(Ordering::Relaxed));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -341,17 +287,17 @@ mod test {
|
|||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref called: AtomicBool = AtomicBool::new(false);
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
|
db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
|
||||||
assert_eq!(Action::SQLITE_INSERT, action);
|
assert_eq!(Action::SQLITE_INSERT, action);
|
||||||
assert_eq!("main", db);
|
assert_eq!("main", db);
|
||||||
assert_eq!("foo", tbl);
|
assert_eq!("foo", tbl);
|
||||||
assert_eq!(1, row_id);
|
assert_eq!(1, row_id);
|
||||||
called.store(true, Ordering::Relaxed);
|
CALLED.store(true, Ordering::Relaxed);
|
||||||
}));
|
}));
|
||||||
db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
|
db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
|
||||||
db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
|
db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
|
||||||
assert!(called.load(Ordering::Relaxed));
|
assert!(CALLED.load(Ordering::Relaxed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
409
src/inner_connection.rs
Normal file
409
src/inner_connection.rs
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
use std::ffi::CString;
|
||||||
|
use std::mem;
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
#[cfg(feature = "load_extension")]
|
||||||
|
use std::path::Path;
|
||||||
|
use std::ptr;
|
||||||
|
use std::str;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
|
||||||
|
use std::sync::{Arc, Mutex, Once, ONCE_INIT};
|
||||||
|
|
||||||
|
use super::ffi;
|
||||||
|
use super::str_to_cstring;
|
||||||
|
use super::{Connection, InterruptHandle, OpenFlags, Result};
|
||||||
|
use crate::error::{error_from_handle, error_from_sqlite_code, Error};
|
||||||
|
use crate::raw_statement::RawStatement;
|
||||||
|
use crate::statement::Statement;
|
||||||
|
use crate::unlock_notify;
|
||||||
|
use crate::version::version_number;
|
||||||
|
|
||||||
|
pub struct InnerConnection {
|
||||||
|
pub db: *mut ffi::sqlite3,
|
||||||
|
// It's unsafe to call `sqlite3_close` while another thread is performing
|
||||||
|
// a `sqlite3_interrupt`, and vice versa, so we take this mutex during
|
||||||
|
// those functions. This protects a copy of the `db` pointer (which is
|
||||||
|
// cleared on closing), however the main copy, `db`, is unprotected.
|
||||||
|
// Otherwise, a long running query would prevent calling interrupt, as
|
||||||
|
// interrupt would only acquire the lock after the query's completion.
|
||||||
|
interrupt_lock: Arc<Mutex<*mut ffi::sqlite3>>,
|
||||||
|
#[cfg(feature = "hooks")]
|
||||||
|
pub free_commit_hook: Option<fn(*mut ::std::os::raw::c_void)>,
|
||||||
|
#[cfg(feature = "hooks")]
|
||||||
|
pub free_rollback_hook: Option<fn(*mut ::std::os::raw::c_void)>,
|
||||||
|
#[cfg(feature = "hooks")]
|
||||||
|
pub free_update_hook: Option<fn(*mut ::std::os::raw::c_void)>,
|
||||||
|
owned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerConnection {
|
||||||
|
#[cfg(not(feature = "hooks"))]
|
||||||
|
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
||||||
|
InnerConnection {
|
||||||
|
db,
|
||||||
|
interrupt_lock: Arc::new(Mutex::new(db)),
|
||||||
|
owned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hooks")]
|
||||||
|
pub fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
||||||
|
InnerConnection {
|
||||||
|
db,
|
||||||
|
interrupt_lock: Arc::new(Mutex::new(db)),
|
||||||
|
free_commit_hook: None,
|
||||||
|
free_rollback_hook: None,
|
||||||
|
free_update_hook: None,
|
||||||
|
owned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result<InnerConnection> {
|
||||||
|
#[cfg(not(feature = "bundled"))]
|
||||||
|
ensure_valid_sqlite_version();
|
||||||
|
ensure_safe_sqlite_threading_mode()?;
|
||||||
|
|
||||||
|
// Replicate the check for sane open flags from SQLite, because the check in
|
||||||
|
// SQLite itself wasn't added until version 3.7.3.
|
||||||
|
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits, 0x02);
|
||||||
|
debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits, 0x04);
|
||||||
|
debug_assert_eq!(
|
||||||
|
1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits,
|
||||||
|
0x40
|
||||||
|
);
|
||||||
|
if (1 << (flags.bits & 0x7)) & 0x46 == 0 {
|
||||||
|
return Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
|
||||||
|
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
|
||||||
|
if r != ffi::SQLITE_OK {
|
||||||
|
let e = if db.is_null() {
|
||||||
|
error_from_sqlite_code(r, None)
|
||||||
|
} else {
|
||||||
|
let e = error_from_handle(db, r);
|
||||||
|
ffi::sqlite3_close(db);
|
||||||
|
e
|
||||||
|
};
|
||||||
|
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
let r = ffi::sqlite3_busy_timeout(db, 5000);
|
||||||
|
if r != ffi::SQLITE_OK {
|
||||||
|
let e = error_from_handle(db, r);
|
||||||
|
ffi::sqlite3_close(db);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to turn on extended results code; don't fail if we can't.
|
||||||
|
ffi::sqlite3_extended_result_codes(db, 1);
|
||||||
|
|
||||||
|
Ok(InnerConnection::new(db, true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn db(&self) -> *mut ffi::sqlite3 {
|
||||||
|
self.db
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_result(&mut self, code: c_int) -> Result<()> {
|
||||||
|
InnerConnection::decode_result_raw(self.db(), code)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_result_raw(db: *mut ffi::sqlite3, code: c_int) -> Result<()> {
|
||||||
|
if code == ffi::SQLITE_OK {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(error_from_handle(db, code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&mut self) -> Result<()> {
|
||||||
|
if self.db.is_null() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.remove_hooks();
|
||||||
|
let mut shared_handle = self.interrupt_lock.lock().unwrap();
|
||||||
|
assert!(
|
||||||
|
!shared_handle.is_null(),
|
||||||
|
"Bug: Somehow interrupt_lock was cleared before the DB was closed"
|
||||||
|
);
|
||||||
|
if !self.owned {
|
||||||
|
self.db = ptr::null_mut();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let r = ffi::sqlite3_close(self.db);
|
||||||
|
// Need to use _raw because _guard has a reference out, and
|
||||||
|
// decode_result takes &mut self.
|
||||||
|
let r = InnerConnection::decode_result_raw(self.db, r);
|
||||||
|
if r.is_ok() {
|
||||||
|
*shared_handle = ptr::null_mut();
|
||||||
|
self.db = ptr::null_mut();
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_interrupt_handle(&self) -> InterruptHandle {
|
||||||
|
InterruptHandle {
|
||||||
|
db_lock: Arc::clone(&self.interrupt_lock),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute_batch(&mut self, sql: &str) -> Result<()> {
|
||||||
|
let c_sql = str_to_cstring(sql)?;
|
||||||
|
unsafe {
|
||||||
|
let r = ffi::sqlite3_exec(
|
||||||
|
self.db(),
|
||||||
|
c_sql.as_ptr(),
|
||||||
|
None,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
);
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "load_extension")]
|
||||||
|
pub fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
|
||||||
|
let r = unsafe { ffi::sqlite3_enable_load_extension(self.db, onoff) };
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "load_extension")]
|
||||||
|
pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> {
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
let dylib_str = super::path_to_cstring(dylib_path)?;
|
||||||
|
unsafe {
|
||||||
|
let mut errmsg: *mut c_char = mem::uninitialized();
|
||||||
|
let r = if let Some(entry_point) = entry_point {
|
||||||
|
let c_entry = str_to_cstring(entry_point)?;
|
||||||
|
ffi::sqlite3_load_extension(
|
||||||
|
self.db,
|
||||||
|
dylib_str.as_ptr(),
|
||||||
|
c_entry.as_ptr(),
|
||||||
|
&mut errmsg,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg)
|
||||||
|
};
|
||||||
|
if r == ffi::SQLITE_OK {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let message = super::errmsg_to_string(&*errmsg);
|
||||||
|
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
||||||
|
Err(error_from_sqlite_code(r, Some(message)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last_insert_rowid(&self) -> i64 {
|
||||||
|
unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
|
||||||
|
if sql.len() >= ::std::i32::MAX as usize {
|
||||||
|
return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
|
||||||
|
}
|
||||||
|
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
|
||||||
|
let c_sql = str_to_cstring(sql)?;
|
||||||
|
let len_with_nul = (sql.len() + 1) as c_int;
|
||||||
|
let mut c_tail = ptr::null();
|
||||||
|
let r = unsafe {
|
||||||
|
if cfg!(feature = "unlock_notify") {
|
||||||
|
let mut rc;
|
||||||
|
loop {
|
||||||
|
rc = ffi::sqlite3_prepare_v2(
|
||||||
|
self.db(),
|
||||||
|
c_sql.as_ptr(),
|
||||||
|
len_with_nul,
|
||||||
|
&mut c_stmt,
|
||||||
|
&mut c_tail,
|
||||||
|
);
|
||||||
|
if !unlock_notify::is_locked(self.db, rc) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rc = unlock_notify::wait_for_unlock_notify(self.db);
|
||||||
|
if rc != ffi::SQLITE_OK {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rc
|
||||||
|
} else {
|
||||||
|
ffi::sqlite3_prepare_v2(
|
||||||
|
self.db(),
|
||||||
|
c_sql.as_ptr(),
|
||||||
|
len_with_nul,
|
||||||
|
&mut c_stmt,
|
||||||
|
&mut c_tail,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !c_tail.is_null() && unsafe { *c_tail == 0 } {
|
||||||
|
// '\0' when there is no ';' at the end
|
||||||
|
c_tail = ptr::null(); // TODO ignore spaces, comments, ... at the end
|
||||||
|
}
|
||||||
|
self.decode_result(r)
|
||||||
|
.map(|_| Statement::new(conn, RawStatement::new(c_stmt, c_tail)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn changes(&mut self) -> usize {
|
||||||
|
unsafe { ffi::sqlite3_changes(self.db()) as usize }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_autocommit(&self) -> bool {
|
||||||
|
unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bundled")] // 3.8.6
|
||||||
|
pub fn is_busy(&self) -> bool {
|
||||||
|
let db = self.db();
|
||||||
|
unsafe {
|
||||||
|
let mut stmt = ffi::sqlite3_next_stmt(db, ptr::null_mut());
|
||||||
|
while !stmt.is_null() {
|
||||||
|
if ffi::sqlite3_stmt_busy(stmt) != 0 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
stmt = ffi::sqlite3_next_stmt(db, stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "hooks"))]
|
||||||
|
fn remove_hooks(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for InnerConnection {
|
||||||
|
#[allow(unused_must_use)]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
use std::thread::panicking;
|
||||||
|
|
||||||
|
if let Err(e) = self.close() {
|
||||||
|
if panicking() {
|
||||||
|
eprintln!("Error while closing SQLite connection: {:?}", e);
|
||||||
|
} else {
|
||||||
|
panic!("Error while closing SQLite connection: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "bundled"))]
|
||||||
|
static SQLITE_VERSION_CHECK: Once = ONCE_INIT;
|
||||||
|
#[cfg(not(feature = "bundled"))]
|
||||||
|
pub static BYPASS_VERSION_CHECK: AtomicBool = ATOMIC_BOOL_INIT;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "bundled"))]
|
||||||
|
fn ensure_valid_sqlite_version() {
|
||||||
|
use crate::version::version;
|
||||||
|
|
||||||
|
SQLITE_VERSION_CHECK.call_once(|| {
|
||||||
|
let version_number = version_number();
|
||||||
|
|
||||||
|
// Check our hard floor.
|
||||||
|
if version_number < 3_006_008 {
|
||||||
|
panic!("rusqlite requires SQLite 3.6.8 or newer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the major version number for runtime and buildtime match.
|
||||||
|
let buildtime_major = ffi::SQLITE_VERSION_NUMBER / 1_000_000;
|
||||||
|
let runtime_major = version_number / 1_000_000;
|
||||||
|
if buildtime_major != runtime_major {
|
||||||
|
panic!(
|
||||||
|
"rusqlite was built against SQLite {} but is running with SQLite {}",
|
||||||
|
str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
|
||||||
|
version()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if BYPASS_VERSION_CHECK.load(Ordering::Relaxed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the runtime version number is compatible with the version number
|
||||||
|
// we found at build-time.
|
||||||
|
if version_number < ffi::SQLITE_VERSION_NUMBER {
|
||||||
|
panic!(
|
||||||
|
"\
|
||||||
|
rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fix this, either:
|
||||||
|
* Recompile rusqlite and link against the SQLite version you are using at runtime, or
|
||||||
|
* Call rusqlite::bypass_sqlite_version_check() prior to your first connection attempt. Doing this
|
||||||
|
means you're sure everything will work correctly even though the runtime version is older than
|
||||||
|
the version we found at build time.",
|
||||||
|
str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
|
||||||
|
version()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static SQLITE_INIT: Once = ONCE_INIT;
|
||||||
|
pub static BYPASS_SQLITE_INIT: AtomicBool = ATOMIC_BOOL_INIT;
|
||||||
|
|
||||||
|
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
||||||
|
// Ensure SQLite was compiled in thredsafe mode.
|
||||||
|
if unsafe { ffi::sqlite3_threadsafe() == 0 } {
|
||||||
|
return Err(Error::SqliteSingleThreadedMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we know SQLite is _capable_ of being in Multi-thread of Serialized mode,
|
||||||
|
// but it's possible someone configured it to be in Single-thread mode
|
||||||
|
// before calling into us. That would mean we're exposing an unsafe API via
|
||||||
|
// a safe one (in Rust terminology), which is no good. We have two options
|
||||||
|
// to protect against this, depending on the version of SQLite we're linked
|
||||||
|
// with:
|
||||||
|
//
|
||||||
|
// 1. If we're on 3.7.0 or later, we can ask SQLite for a mutex and check for
|
||||||
|
// the magic value 8. This isn't documented, but it's what SQLite
|
||||||
|
// returns for its mutex allocation function in Single-thread mode.
|
||||||
|
// 2. If we're prior to SQLite 3.7.0, AFAIK there's no way to check the
|
||||||
|
// threading mode. The check we perform for >= 3.7.0 will segfault.
|
||||||
|
// Instead, we insist on being able to call sqlite3_config and
|
||||||
|
// sqlite3_initialize ourself, ensuring we know the threading
|
||||||
|
// mode. This will fail if someone else has already initialized SQLite
|
||||||
|
// even if they initialized it safely. That's not ideal either, which is
|
||||||
|
// why we expose bypass_sqlite_initialization above.
|
||||||
|
if version_number() >= 3_007_000 {
|
||||||
|
const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8;
|
||||||
|
let is_singlethreaded = unsafe {
|
||||||
|
let mutex_ptr = ffi::sqlite3_mutex_alloc(0);
|
||||||
|
let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC;
|
||||||
|
ffi::sqlite3_mutex_free(mutex_ptr);
|
||||||
|
is_singlethreaded
|
||||||
|
};
|
||||||
|
if is_singlethreaded {
|
||||||
|
Err(Error::SqliteSingleThreadedMode)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SQLITE_INIT.call_once(|| {
|
||||||
|
if BYPASS_SQLITE_INIT.load(Ordering::Relaxed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let msg = "\
|
||||||
|
Could not ensure safe initialization of SQLite.
|
||||||
|
To fix this, either:
|
||||||
|
* Upgrade SQLite to at least version 3.7.0
|
||||||
|
* Ensure that SQLite has been initialized in Multi-thread or Serialized mode and call
|
||||||
|
rusqlite::bypass_sqlite_initialization() prior to your first connection attempt.";
|
||||||
|
|
||||||
|
if ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) != ffi::SQLITE_OK {
|
||||||
|
panic!(msg);
|
||||||
|
}
|
||||||
|
if ffi::sqlite3_initialize() != ffi::SQLITE_OK {
|
||||||
|
panic!(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
790
src/lib.rs
790
src/lib.rs
File diff suppressed because it is too large
Load Diff
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
pub use ffi::Limit;
|
pub use crate::ffi::Limit;
|
||||||
|
|
||||||
use Connection;
|
use crate::Connection;
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Returns the current value of a limit.
|
/// Returns the current value of a limit.
|
||||||
@ -23,8 +23,8 @@ impl Connection {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use ffi::Limit;
|
use crate::ffi::Limit;
|
||||||
use Connection;
|
use crate::Connection;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_limit() {
|
fn test_limit() {
|
||||||
@ -57,13 +57,13 @@ mod test {
|
|||||||
assert_eq!(99, db.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER));
|
assert_eq!(99, db.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER));
|
||||||
|
|
||||||
// SQLITE_LIMIT_TRIGGER_DEPTH was added in SQLite 3.6.18.
|
// SQLITE_LIMIT_TRIGGER_DEPTH was added in SQLite 3.6.18.
|
||||||
if ::version_number() >= 3006018 {
|
if crate::version_number() >= 3_006_018 {
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH, 32);
|
db.set_limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH, 32);
|
||||||
assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH));
|
assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
// SQLITE_LIMIT_WORKER_THREADS was added in SQLite 3.8.7.
|
// SQLITE_LIMIT_WORKER_THREADS was added in SQLite 3.8.7.
|
||||||
if ::version_number() >= 3008007 {
|
if crate::version_number() >= 3_008_007 {
|
||||||
db.set_limit(Limit::SQLITE_LIMIT_WORKER_THREADS, 2);
|
db.set_limit(Limit::SQLITE_LIMIT_WORKER_THREADS, 2);
|
||||||
assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS));
|
assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS));
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
use {Connection, Result};
|
use crate::{Connection, Result};
|
||||||
|
|
||||||
/// Old name for `LoadExtensionGuard`. `SqliteLoadExtensionGuard` is deprecated.
|
|
||||||
#[deprecated(since = "0.6.0", note = "Use LoadExtensionGuard instead")]
|
|
||||||
pub type SqliteLoadExtensionGuard<'conn> = LoadExtensionGuard<'conn>;
|
|
||||||
|
|
||||||
/// RAII guard temporarily enabling SQLite extensions to be loaded.
|
/// RAII guard temporarily enabling SQLite extensions to be loaded.
|
||||||
///
|
///
|
||||||
@ -12,7 +8,7 @@ pub type SqliteLoadExtensionGuard<'conn> = LoadExtensionGuard<'conn>;
|
|||||||
/// # use rusqlite::{Connection, Result, LoadExtensionGuard};
|
/// # use rusqlite::{Connection, Result, LoadExtensionGuard};
|
||||||
/// # use std::path::{Path};
|
/// # use std::path::{Path};
|
||||||
/// fn load_my_extension(conn: &Connection) -> Result<()> {
|
/// fn load_my_extension(conn: &Connection) -> Result<()> {
|
||||||
/// let _guard = try!(LoadExtensionGuard::new(conn));
|
/// let _guard = LoadExtensionGuard::new(conn)?;
|
||||||
///
|
///
|
||||||
/// conn.load_extension(Path::new("my_sqlite_extension"), None)
|
/// conn.load_extension(Path::new("my_sqlite_extension"), None)
|
||||||
/// }
|
/// }
|
||||||
@ -25,7 +21,7 @@ impl<'conn> LoadExtensionGuard<'conn> {
|
|||||||
/// Attempt to enable loading extensions. Loading extensions will be
|
/// Attempt to enable loading extensions. Loading extensions will be
|
||||||
/// disabled when this guard goes out of scope. Cannot be meaningfully
|
/// disabled when this guard goes out of scope. Cannot be meaningfully
|
||||||
/// nested.
|
/// nested.
|
||||||
pub fn new(conn: &Connection) -> Result<LoadExtensionGuard> {
|
pub fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
|
||||||
conn.load_extension_enable()
|
conn.load_extension_enable()
|
||||||
.map(|_| LoadExtensionGuard { conn })
|
.map(|_| LoadExtensionGuard { conn })
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use super::ffi;
|
use super::ffi;
|
||||||
use super::unlock_notify;
|
use super::unlock_notify;
|
||||||
|
use super::StatementStatus;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::os::raw::{c_char, c_int};
|
use std::os::raw::{c_char, c_int};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
@ -101,6 +102,11 @@ impl RawStatement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 {
|
||||||
|
assert!(!self.0.is_null());
|
||||||
|
unsafe { ffi::sqlite3_stmt_status(self.0, status as i32, reset as i32) }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn has_tail(&self) -> bool {
|
pub fn has_tail(&self) -> bool {
|
||||||
!self.1.is_null()
|
!self.1.is_null()
|
||||||
}
|
}
|
||||||
|
20
src/row.rs
20
src/row.rs
@ -2,7 +2,7 @@ use std::marker::PhantomData;
|
|||||||
use std::{convert, result};
|
use std::{convert, result};
|
||||||
|
|
||||||
use super::{Error, Result, Statement};
|
use super::{Error, Result, Statement};
|
||||||
use types::{FromSql, FromSqlError, ValueRef};
|
use crate::types::{FromSql, FromSqlError, ValueRef};
|
||||||
|
|
||||||
/// An handle for the resulting rows of a query.
|
/// An handle for the resulting rows of a query.
|
||||||
pub struct Rows<'stmt> {
|
pub struct Rows<'stmt> {
|
||||||
@ -27,7 +27,7 @@ impl<'stmt> Rows<'stmt> {
|
|||||||
/// This is a "streaming iterator". For a more natural interface,
|
/// This is a "streaming iterator". For a more natural interface,
|
||||||
/// consider using `query_map` or `query_and_then` instead, which
|
/// consider using `query_map` or `query_and_then` instead, which
|
||||||
/// return types that implement `Iterator`.
|
/// return types that implement `Iterator`.
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(should_implement_trait))] // cannot implement Iterator
|
#[allow(clippy::should_implement_trait)] // cannot implement Iterator
|
||||||
pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> {
|
pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> {
|
||||||
self.stmt.and_then(|stmt| match stmt.step() {
|
self.stmt.and_then(|stmt| match stmt.step() {
|
||||||
Ok(true) => Some(Ok(Row {
|
Ok(true) => Some(Ok(Row {
|
||||||
@ -73,7 +73,7 @@ pub struct MappedRows<'stmt, F> {
|
|||||||
|
|
||||||
impl<'stmt, T, F> MappedRows<'stmt, F>
|
impl<'stmt, T, F> MappedRows<'stmt, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&Row) -> T,
|
F: FnMut(&Row<'_, '_>) -> T,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
|
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
|
||||||
MappedRows { rows, map: f }
|
MappedRows { rows, map: f }
|
||||||
@ -82,7 +82,7 @@ where
|
|||||||
|
|
||||||
impl<'conn, T, F> Iterator for MappedRows<'conn, F>
|
impl<'conn, T, F> Iterator for MappedRows<'conn, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&Row) -> T,
|
F: FnMut(&Row<'_, '_>) -> T,
|
||||||
{
|
{
|
||||||
type Item = Result<T>;
|
type Item = Result<T>;
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ pub struct AndThenRows<'stmt, F> {
|
|||||||
|
|
||||||
impl<'stmt, T, E, F> AndThenRows<'stmt, F>
|
impl<'stmt, T, E, F> AndThenRows<'stmt, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&Row) -> result::Result<T, E>,
|
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
|
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
|
||||||
AndThenRows { rows, map: f }
|
AndThenRows { rows, map: f }
|
||||||
@ -113,7 +113,7 @@ where
|
|||||||
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
|
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
|
||||||
where
|
where
|
||||||
E: convert::From<Error>,
|
E: convert::From<Error>,
|
||||||
F: FnMut(&Row) -> result::Result<T, E>,
|
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
|
||||||
{
|
{
|
||||||
type Item = result::Result<T, E>;
|
type Item = result::Result<T, E>;
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
|||||||
/// enabled), and the underlying SQLite column is a blob whose size is not
|
/// enabled), and the underlying SQLite column is a blob whose size is not
|
||||||
/// 16 bytes, `Error::InvalidColumnType` will also be returned.
|
/// 16 bytes, `Error::InvalidColumnType` will also be returned.
|
||||||
pub fn get_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
pub fn get_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
||||||
let idx = try!(idx.idx(self.stmt));
|
let idx = idx.idx(self.stmt)?;
|
||||||
let value = self.stmt.value_ref(idx);
|
let value = self.stmt.value_ref(idx);
|
||||||
FromSql::column_result(value).map_err(|err| match err {
|
FromSql::column_result(value).map_err(|err| match err {
|
||||||
FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
|
FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()),
|
||||||
@ -231,12 +231,12 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
|||||||
pub trait RowIndex {
|
pub trait RowIndex {
|
||||||
/// Returns the index of the appropriate column, or `None` if no such
|
/// Returns the index of the appropriate column, or `None` if no such
|
||||||
/// column exists.
|
/// column exists.
|
||||||
fn idx(&self, stmt: &Statement) -> Result<usize>;
|
fn idx(&self, stmt: &Statement<'_>) -> Result<usize>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RowIndex for usize {
|
impl RowIndex for usize {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn idx(&self, stmt: &Statement) -> Result<usize> {
|
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
||||||
if *self >= stmt.column_count() {
|
if *self >= stmt.column_count() {
|
||||||
Err(Error::InvalidColumnIndex(*self))
|
Err(Error::InvalidColumnIndex(*self))
|
||||||
} else {
|
} else {
|
||||||
@ -247,7 +247,7 @@ impl RowIndex for usize {
|
|||||||
|
|
||||||
impl<'a> RowIndex for &'a str {
|
impl<'a> RowIndex for &'a str {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn idx(&self, stmt: &Statement) -> Result<usize> {
|
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
||||||
stmt.column_index(*self)
|
stmt.column_index(*self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
894
src/session.rs
Normal file
894
src/session.rs
Normal file
@ -0,0 +1,894 @@
|
|||||||
|
//! [Session Extension](https://sqlite.org/sessionintro.html)
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::mem;
|
||||||
|
use std::os::raw::{c_char, c_int, c_uchar, c_void};
|
||||||
|
use std::panic::{catch_unwind, RefUnwindSafe};
|
||||||
|
use std::ptr;
|
||||||
|
use std::slice::{from_raw_parts, from_raw_parts_mut};
|
||||||
|
|
||||||
|
use fallible_streaming_iterator::FallibleStreamingIterator;
|
||||||
|
|
||||||
|
use crate::error::error_from_sqlite_code;
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::hooks::Action;
|
||||||
|
use crate::types::ValueRef;
|
||||||
|
use crate::{errmsg_to_string, str_to_cstring, Connection, DatabaseName, Result};
|
||||||
|
|
||||||
|
// https://sqlite.org/session.html
|
||||||
|
|
||||||
|
/// An instance of this object is a session that can be used to record changes
|
||||||
|
/// to a database.
|
||||||
|
pub struct Session<'conn> {
|
||||||
|
phantom: PhantomData<&'conn ()>,
|
||||||
|
s: *mut ffi::sqlite3_session,
|
||||||
|
filter: Option<Box<dyn Fn(&str) -> bool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'conn> Session<'conn> {
|
||||||
|
/// Create a new session object
|
||||||
|
pub fn new(db: &'conn Connection) -> Result<Session<'conn>> {
|
||||||
|
Session::new_with_name(db, DatabaseName::Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new session object
|
||||||
|
pub fn new_with_name(db: &'conn Connection, name: DatabaseName<'_>) -> Result<Session<'conn>> {
|
||||||
|
let name = name.to_cstring()?;
|
||||||
|
|
||||||
|
let db = db.db.borrow_mut().db;
|
||||||
|
|
||||||
|
let mut s: *mut ffi::sqlite3_session = unsafe { mem::uninitialized() };
|
||||||
|
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) });
|
||||||
|
|
||||||
|
Ok(Session {
|
||||||
|
phantom: PhantomData,
|
||||||
|
s,
|
||||||
|
filter: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a table filter
|
||||||
|
pub fn table_filter<F>(&mut self, filter: Option<F>)
|
||||||
|
where
|
||||||
|
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
|
||||||
|
{
|
||||||
|
unsafe extern "C" fn call_boxed_closure<F>(
|
||||||
|
p_arg: *mut c_void,
|
||||||
|
tbl_str: *const c_char,
|
||||||
|
) -> c_int
|
||||||
|
where
|
||||||
|
F: Fn(&str) -> bool + RefUnwindSafe,
|
||||||
|
{
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
let boxed_filter: *mut F = p_arg as *mut F;
|
||||||
|
let tbl_name = {
|
||||||
|
let c_slice = CStr::from_ptr(tbl_str).to_bytes();
|
||||||
|
str::from_utf8_unchecked(c_slice)
|
||||||
|
};
|
||||||
|
if let Ok(true) = catch_unwind(|| (*boxed_filter)(tbl_name)) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match filter {
|
||||||
|
Some(filter) => {
|
||||||
|
let boxed_filter = Box::new(filter);
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3session_table_filter(
|
||||||
|
self.s,
|
||||||
|
Some(call_boxed_closure::<F>),
|
||||||
|
&*boxed_filter as *const F as *mut _,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.filter = Some(boxed_filter);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unsafe { ffi::sqlite3session_table_filter(self.s, None, ptr::null_mut()) }
|
||||||
|
self.filter = None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attach a table. `None` means all tables.
|
||||||
|
pub fn attach(&mut self, table: Option<&str>) -> Result<()> {
|
||||||
|
let table = if let Some(table) = table {
|
||||||
|
str_to_cstring(table)?.as_ptr()
|
||||||
|
} else {
|
||||||
|
ptr::null()
|
||||||
|
};
|
||||||
|
unsafe { check!(ffi::sqlite3session_attach(self.s, table)) };
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a Changeset
|
||||||
|
pub fn changeset(&mut self) -> Result<Changeset> {
|
||||||
|
let mut n = 0;
|
||||||
|
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
||||||
|
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) });
|
||||||
|
Ok(Changeset { cs, n })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the set of changes represented by this session to `output`.
|
||||||
|
pub fn changeset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
|
||||||
|
let output_ref = &output;
|
||||||
|
check!(unsafe {
|
||||||
|
ffi::sqlite3session_changeset_strm(
|
||||||
|
self.s,
|
||||||
|
Some(x_output),
|
||||||
|
output_ref as *const &mut dyn Write as *mut c_void,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a Patchset
|
||||||
|
pub fn patchset(&mut self) -> Result<Changeset> {
|
||||||
|
let mut n = 0;
|
||||||
|
let mut ps: *mut c_void = unsafe { mem::uninitialized() };
|
||||||
|
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) });
|
||||||
|
// TODO Validate: same struct
|
||||||
|
Ok(Changeset { cs: ps, n })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the set of patches represented by this session to `output`.
|
||||||
|
pub fn patchset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
|
||||||
|
let output_ref = &output;
|
||||||
|
check!(unsafe {
|
||||||
|
ffi::sqlite3session_patchset_strm(
|
||||||
|
self.s,
|
||||||
|
Some(x_output),
|
||||||
|
output_ref as *const &mut dyn Write as *mut c_void,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the difference between tables.
|
||||||
|
pub fn diff(&mut self, from: DatabaseName<'_>, table: &str) -> Result<()> {
|
||||||
|
let from = from.to_cstring()?;
|
||||||
|
let table = str_to_cstring(table)?.as_ptr();
|
||||||
|
unsafe {
|
||||||
|
let mut errmsg: *mut c_char = mem::uninitialized();
|
||||||
|
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, &mut errmsg);
|
||||||
|
if r != ffi::SQLITE_OK {
|
||||||
|
let message = errmsg_to_string(&*errmsg);
|
||||||
|
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
||||||
|
return Err(error_from_sqlite_code(r, Some(message)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test if a changeset has recorded any changes
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
unsafe { ffi::sqlite3session_isempty(self.s) != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query the current state of the session
|
||||||
|
pub fn is_enabled(&self) -> bool {
|
||||||
|
unsafe { ffi::sqlite3session_enable(self.s, -1) != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable or disable the recording of changes
|
||||||
|
pub fn set_enabled(&mut self, enabled: bool) {
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3session_enable(self.s, if enabled { 1 } else { 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query the current state of the indirect flag
|
||||||
|
pub fn is_indirect(&self) -> bool {
|
||||||
|
unsafe { ffi::sqlite3session_indirect(self.s, -1) != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set or clear the indirect change flag
|
||||||
|
pub fn set_indirect(&mut self, indirect: bool) {
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3session_indirect(self.s, if indirect { 1 } else { 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'conn> Drop for Session<'conn> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.filter.is_some() {
|
||||||
|
self.table_filter(None::<fn(&str) -> bool>);
|
||||||
|
}
|
||||||
|
unsafe { ffi::sqlite3session_delete(self.s) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invert a changeset
|
||||||
|
pub fn invert_strm(input: &mut dyn Read, output: &mut dyn Write) -> Result<()> {
|
||||||
|
let input_ref = &input;
|
||||||
|
let output_ref = &output;
|
||||||
|
check!(unsafe {
|
||||||
|
ffi::sqlite3changeset_invert_strm(
|
||||||
|
Some(x_input),
|
||||||
|
input_ref as *const &mut dyn Read as *mut c_void,
|
||||||
|
Some(x_output),
|
||||||
|
output_ref as *const &mut dyn Write as *mut c_void,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combine two changesets
|
||||||
|
pub fn concat_strm(
|
||||||
|
input_a: &mut dyn Read,
|
||||||
|
input_b: &mut dyn Read,
|
||||||
|
output: &mut dyn Write,
|
||||||
|
) -> Result<()> {
|
||||||
|
let input_a_ref = &input_a;
|
||||||
|
let input_b_ref = &input_b;
|
||||||
|
let output_ref = &output;
|
||||||
|
check!(unsafe {
|
||||||
|
ffi::sqlite3changeset_concat_strm(
|
||||||
|
Some(x_input),
|
||||||
|
input_a_ref as *const &mut dyn Read as *mut c_void,
|
||||||
|
Some(x_input),
|
||||||
|
input_b_ref as *const &mut dyn Read as *mut c_void,
|
||||||
|
Some(x_output),
|
||||||
|
output_ref as *const &mut dyn Write as *mut c_void,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changeset or Patchset
|
||||||
|
pub struct Changeset {
|
||||||
|
cs: *mut c_void,
|
||||||
|
n: c_int,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Changeset {
|
||||||
|
/// Invert a changeset
|
||||||
|
pub fn invert(&self) -> Result<Changeset> {
|
||||||
|
let mut n = 0;
|
||||||
|
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
||||||
|
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs) });
|
||||||
|
Ok(Changeset { cs, n })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an iterator to traverse a changeset
|
||||||
|
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
|
||||||
|
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
|
||||||
|
check!(unsafe { ffi::sqlite3changeset_start(&mut it, self.n, self.cs) });
|
||||||
|
Ok(ChangesetIter {
|
||||||
|
phantom: PhantomData,
|
||||||
|
it,
|
||||||
|
item: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Concatenate two changeset objects
|
||||||
|
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
|
||||||
|
let mut n = 0;
|
||||||
|
let mut cs: *mut c_void = unsafe { mem::uninitialized() };
|
||||||
|
check!(unsafe { ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs) });
|
||||||
|
Ok(Changeset { cs, n })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Changeset {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_free(self.cs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cursor for iterating over the elements of a changeset or patchset.
|
||||||
|
pub struct ChangesetIter<'changeset> {
|
||||||
|
phantom: PhantomData<&'changeset ()>,
|
||||||
|
it: *mut ffi::sqlite3_changeset_iter,
|
||||||
|
item: Option<ChangesetItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'changeset> ChangesetIter<'changeset> {
|
||||||
|
/// Create an iterator on `input`
|
||||||
|
pub fn start_strm<'input>(input: &'input mut dyn Read) -> Result<ChangesetIter<'input>> {
|
||||||
|
let input_ref = &input;
|
||||||
|
let mut it: *mut ffi::sqlite3_changeset_iter = unsafe { mem::uninitialized() };
|
||||||
|
check!(unsafe {
|
||||||
|
ffi::sqlite3changeset_start_strm(
|
||||||
|
&mut it,
|
||||||
|
Some(x_input),
|
||||||
|
input_ref as *const &mut dyn Read as *mut c_void,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Ok(ChangesetIter {
|
||||||
|
phantom: PhantomData,
|
||||||
|
it,
|
||||||
|
item: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'changeset> FallibleStreamingIterator for ChangesetIter<'changeset> {
|
||||||
|
type Error = crate::error::Error;
|
||||||
|
type Item = ChangesetItem;
|
||||||
|
|
||||||
|
fn advance(&mut self) -> Result<()> {
|
||||||
|
let rc = unsafe { ffi::sqlite3changeset_next(self.it) };
|
||||||
|
match rc {
|
||||||
|
ffi::SQLITE_ROW => {
|
||||||
|
self.item = Some(ChangesetItem { it: self.it });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ffi::SQLITE_DONE => {
|
||||||
|
self.item = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
code => Err(error_from_sqlite_code(code, None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self) -> Option<&ChangesetItem> {
|
||||||
|
self.item.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Operation<'item> {
|
||||||
|
table_name: &'item str,
|
||||||
|
number_of_columns: i32,
|
||||||
|
code: Action,
|
||||||
|
indirect: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'item> Operation<'item> {
|
||||||
|
pub fn table_name(&self) -> &str {
|
||||||
|
self.table_name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn number_of_columns(&self) -> i32 {
|
||||||
|
self.number_of_columns
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn code(&self) -> Action {
|
||||||
|
self.code
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indirect(&self) -> bool {
|
||||||
|
self.indirect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'changeset> Drop for ChangesetIter<'changeset> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3changeset_finalize(self.it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An item passed to a conflict-handler by `Connection::apply`,
|
||||||
|
/// or an item generated by `ChangesetIter::next`.
|
||||||
|
// TODO enum ? Delete, Insert, Update, ...
|
||||||
|
pub struct ChangesetItem {
|
||||||
|
it: *mut ffi::sqlite3_changeset_iter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChangesetItem {
|
||||||
|
/// Obtain conflicting row values
|
||||||
|
///
|
||||||
|
/// May only be called with an `SQLITE_CHANGESET_DATA` or
|
||||||
|
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
|
||||||
|
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||||
|
unsafe {
|
||||||
|
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
||||||
|
check!(ffi::sqlite3changeset_conflict(
|
||||||
|
self.it,
|
||||||
|
col as i32,
|
||||||
|
&mut p_value
|
||||||
|
));
|
||||||
|
Ok(ValueRef::from_value(p_value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the number of foreign key constraint violations
|
||||||
|
///
|
||||||
|
/// May only be called with an `SQLITE_CHANGESET_FOREIGN_KEY` conflict
|
||||||
|
/// handler callback.
|
||||||
|
pub fn fk_conflicts(&self) -> Result<i32> {
|
||||||
|
unsafe {
|
||||||
|
let mut p_out = 0;
|
||||||
|
check!(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out));
|
||||||
|
Ok(p_out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain new.* Values
|
||||||
|
///
|
||||||
|
/// May only be called if the type of change is either `SQLITE_UPDATE` or
|
||||||
|
/// `SQLITE_INSERT`.
|
||||||
|
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||||
|
unsafe {
|
||||||
|
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
||||||
|
check!(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value));
|
||||||
|
Ok(ValueRef::from_value(p_value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain old.* Values
|
||||||
|
///
|
||||||
|
/// May only be called if the type of change is either `SQLITE_DELETE` or
|
||||||
|
/// `SQLITE_UPDATE`.
|
||||||
|
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
|
||||||
|
unsafe {
|
||||||
|
let mut p_value: *mut ffi::sqlite3_value = mem::uninitialized();
|
||||||
|
check!(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value));
|
||||||
|
Ok(ValueRef::from_value(p_value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain the current operation
|
||||||
|
pub fn op(&self) -> Result<Operation<'_>> {
|
||||||
|
let mut number_of_columns = 0;
|
||||||
|
let mut code = 0;
|
||||||
|
let mut indirect = 0;
|
||||||
|
let tab = unsafe {
|
||||||
|
let mut pz_tab: *const c_char = mem::uninitialized();
|
||||||
|
check!(ffi::sqlite3changeset_op(
|
||||||
|
self.it,
|
||||||
|
&mut pz_tab,
|
||||||
|
&mut number_of_columns,
|
||||||
|
&mut code,
|
||||||
|
&mut indirect
|
||||||
|
));
|
||||||
|
CStr::from_ptr(pz_tab)
|
||||||
|
};
|
||||||
|
let table_name = tab.to_str()?;
|
||||||
|
Ok(Operation {
|
||||||
|
table_name,
|
||||||
|
number_of_columns,
|
||||||
|
code: Action::from(code),
|
||||||
|
indirect: indirect != 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain the primary key definition of a table
|
||||||
|
pub fn pk(&self) -> Result<&[u8]> {
|
||||||
|
let mut number_of_columns = 0;
|
||||||
|
unsafe {
|
||||||
|
let mut pks: *mut c_uchar = mem::uninitialized();
|
||||||
|
check!(ffi::sqlite3changeset_pk(
|
||||||
|
self.it,
|
||||||
|
&mut pks,
|
||||||
|
&mut number_of_columns
|
||||||
|
));
|
||||||
|
Ok(from_raw_parts(pks, number_of_columns as usize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to combine two or more changesets or
|
||||||
|
/// patchsets
|
||||||
|
pub struct Changegroup {
|
||||||
|
cg: *mut ffi::sqlite3_changegroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Changegroup {
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
let mut cg: *mut ffi::sqlite3_changegroup = unsafe { mem::uninitialized() };
|
||||||
|
check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) });
|
||||||
|
Ok(Changegroup { cg })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a changeset
|
||||||
|
pub fn add(&mut self, cs: &Changeset) -> Result<()> {
|
||||||
|
check!(unsafe { ffi::sqlite3changegroup_add(self.cg, cs.n, cs.cs) });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a changeset read from `input` to this change group.
|
||||||
|
pub fn add_stream(&mut self, input: &mut dyn Read) -> Result<()> {
|
||||||
|
let input_ref = &input;
|
||||||
|
check!(unsafe {
|
||||||
|
ffi::sqlite3changegroup_add_strm(
|
||||||
|
self.cg,
|
||||||
|
Some(x_input),
|
||||||
|
input_ref as *const &mut dyn Read as *mut c_void,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain a composite Changeset
|
||||||
|
pub fn output(&mut self) -> Result<Changeset> {
|
||||||
|
let mut n = 0;
|
||||||
|
let mut output: *mut c_void = unsafe { mem::uninitialized() };
|
||||||
|
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) });
|
||||||
|
Ok(Changeset { cs: output, n })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the combined set of changes to `output`.
|
||||||
|
pub fn output_strm(&mut self, output: &mut dyn Write) -> Result<()> {
|
||||||
|
let output_ref = &output;
|
||||||
|
check!(unsafe {
|
||||||
|
ffi::sqlite3changegroup_output_strm(
|
||||||
|
self.cg,
|
||||||
|
Some(x_output),
|
||||||
|
output_ref as *const &mut dyn Write as *mut c_void,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Changegroup {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3changegroup_delete(self.cg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
/// Apply a changeset to a database
|
||||||
|
pub fn apply<F, C>(&self, cs: &Changeset, filter: Option<F>, conflict: C) -> Result<()>
|
||||||
|
where
|
||||||
|
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
|
||||||
|
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
|
||||||
|
{
|
||||||
|
let db = self.db.borrow_mut().db;
|
||||||
|
|
||||||
|
let filtered = filter.is_some();
|
||||||
|
let tuple = &mut (filter, conflict);
|
||||||
|
check!(unsafe {
|
||||||
|
if filtered {
|
||||||
|
ffi::sqlite3changeset_apply(
|
||||||
|
db,
|
||||||
|
cs.n,
|
||||||
|
cs.cs,
|
||||||
|
Some(call_filter::<F, C>),
|
||||||
|
Some(call_conflict::<F, C>),
|
||||||
|
tuple as *mut (Option<F>, C) as *mut c_void,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ffi::sqlite3changeset_apply(
|
||||||
|
db,
|
||||||
|
cs.n,
|
||||||
|
cs.cs,
|
||||||
|
None,
|
||||||
|
Some(call_conflict::<F, C>),
|
||||||
|
tuple as *mut (Option<F>, C) as *mut c_void,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply a changeset to a database
|
||||||
|
pub fn apply_strm<F, C>(
|
||||||
|
&self,
|
||||||
|
input: &mut dyn Read,
|
||||||
|
filter: Option<F>,
|
||||||
|
conflict: C,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
|
||||||
|
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
|
||||||
|
{
|
||||||
|
let input_ref = &input;
|
||||||
|
let db = self.db.borrow_mut().db;
|
||||||
|
|
||||||
|
let filtered = filter.is_some();
|
||||||
|
let tuple = &mut (filter, conflict);
|
||||||
|
check!(unsafe {
|
||||||
|
if filtered {
|
||||||
|
ffi::sqlite3changeset_apply_strm(
|
||||||
|
db,
|
||||||
|
Some(x_input),
|
||||||
|
input_ref as *const &mut dyn Read as *mut c_void,
|
||||||
|
Some(call_filter::<F, C>),
|
||||||
|
Some(call_conflict::<F, C>),
|
||||||
|
tuple as *mut (Option<F>, C) as *mut c_void,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ffi::sqlite3changeset_apply_strm(
|
||||||
|
db,
|
||||||
|
Some(x_input),
|
||||||
|
input_ref as *const &mut dyn Read as *mut c_void,
|
||||||
|
None,
|
||||||
|
Some(call_conflict::<F, C>),
|
||||||
|
tuple as *mut (Option<F>, C) as *mut c_void,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants passed to the conflict handler
|
||||||
|
#[repr(i32)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ConflictType {
|
||||||
|
UNKNOWN = -1,
|
||||||
|
SQLITE_CHANGESET_DATA = ffi::SQLITE_CHANGESET_DATA,
|
||||||
|
SQLITE_CHANGESET_NOTFOUND = ffi::SQLITE_CHANGESET_NOTFOUND,
|
||||||
|
SQLITE_CHANGESET_CONFLICT = ffi::SQLITE_CHANGESET_CONFLICT,
|
||||||
|
SQLITE_CHANGESET_CONSTRAINT = ffi::SQLITE_CHANGESET_CONSTRAINT,
|
||||||
|
SQLITE_CHANGESET_FOREIGN_KEY = ffi::SQLITE_CHANGESET_FOREIGN_KEY,
|
||||||
|
}
|
||||||
|
impl From<i32> for ConflictType {
|
||||||
|
fn from(code: i32) -> ConflictType {
|
||||||
|
match code {
|
||||||
|
ffi::SQLITE_CHANGESET_DATA => ConflictType::SQLITE_CHANGESET_DATA,
|
||||||
|
ffi::SQLITE_CHANGESET_NOTFOUND => ConflictType::SQLITE_CHANGESET_NOTFOUND,
|
||||||
|
ffi::SQLITE_CHANGESET_CONFLICT => ConflictType::SQLITE_CHANGESET_CONFLICT,
|
||||||
|
ffi::SQLITE_CHANGESET_CONSTRAINT => ConflictType::SQLITE_CHANGESET_CONSTRAINT,
|
||||||
|
ffi::SQLITE_CHANGESET_FOREIGN_KEY => ConflictType::SQLITE_CHANGESET_FOREIGN_KEY,
|
||||||
|
_ => ConflictType::UNKNOWN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants returned by the conflict handler
|
||||||
|
#[repr(i32)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ConflictAction {
|
||||||
|
SQLITE_CHANGESET_OMIT = ffi::SQLITE_CHANGESET_OMIT,
|
||||||
|
SQLITE_CHANGESET_REPLACE = ffi::SQLITE_CHANGESET_REPLACE,
|
||||||
|
SQLITE_CHANGESET_ABORT = ffi::SQLITE_CHANGESET_ABORT,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn call_filter<F, C>(p_ctx: *mut c_void, tbl_str: *const c_char) -> c_int
|
||||||
|
where
|
||||||
|
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
|
||||||
|
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
|
||||||
|
{
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
|
||||||
|
let tbl_name = {
|
||||||
|
let c_slice = CStr::from_ptr(tbl_str).to_bytes();
|
||||||
|
str::from_utf8_unchecked(c_slice)
|
||||||
|
};
|
||||||
|
match *tuple {
|
||||||
|
(Some(ref filter), _) => {
|
||||||
|
if let Ok(true) = catch_unwind(|| filter(tbl_name)) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn call_conflict<F, C>(
|
||||||
|
p_ctx: *mut c_void,
|
||||||
|
e_conflict: c_int,
|
||||||
|
p: *mut ffi::sqlite3_changeset_iter,
|
||||||
|
) -> c_int
|
||||||
|
where
|
||||||
|
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
|
||||||
|
C: Fn(ConflictType, ChangesetItem) -> ConflictAction + Send + RefUnwindSafe + 'static,
|
||||||
|
{
|
||||||
|
let tuple: *mut (Option<F>, C) = p_ctx as *mut (Option<F>, C);
|
||||||
|
let conflict_type = ConflictType::from(e_conflict);
|
||||||
|
let item = ChangesetItem { it: p };
|
||||||
|
if let Ok(action) = catch_unwind(|| (*tuple).1(conflict_type, item)) {
|
||||||
|
action as c_int
|
||||||
|
} else {
|
||||||
|
ffi::SQLITE_CHANGESET_ABORT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn x_input(p_in: *mut c_void, data: *mut c_void, len: *mut c_int) -> c_int {
|
||||||
|
if p_in.is_null() {
|
||||||
|
return ffi::SQLITE_MISUSE;
|
||||||
|
}
|
||||||
|
let bytes: &mut [u8] = from_raw_parts_mut(data as *mut u8, len as usize);
|
||||||
|
let input = p_in as *mut &mut dyn Read;
|
||||||
|
match (*input).read(bytes) {
|
||||||
|
Ok(n) => {
|
||||||
|
*len = n as i32; // TODO Validate: n = 0 may not mean the reader will always no longer be able to
|
||||||
|
// produce bytes.
|
||||||
|
ffi::SQLITE_OK
|
||||||
|
}
|
||||||
|
Err(_) => ffi::SQLITE_IOERR_READ, // TODO check if err is a (ru)sqlite Error => propagate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn x_output(p_out: *mut c_void, data: *const c_void, len: c_int) -> c_int {
|
||||||
|
if p_out.is_null() {
|
||||||
|
return ffi::SQLITE_MISUSE;
|
||||||
|
}
|
||||||
|
// The sessions module never invokes an xOutput callback with the third
|
||||||
|
// parameter set to a value less than or equal to zero.
|
||||||
|
let bytes: &[u8] = from_raw_parts(data as *const u8, len as usize);
|
||||||
|
let output = p_out as *mut &mut dyn Write;
|
||||||
|
match (*output).write_all(bytes) {
|
||||||
|
Ok(_) => ffi::SQLITE_OK,
|
||||||
|
Err(_) => ffi::SQLITE_IOERR_WRITE, // TODO check if err is a (ru)sqlite Error => propagate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use fallible_streaming_iterator::FallibleStreamingIterator;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session};
|
||||||
|
use crate::hooks::Action;
|
||||||
|
use crate::Connection;
|
||||||
|
|
||||||
|
fn one_changeset() -> Changeset {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut session = Session::new(&db).unwrap();
|
||||||
|
assert!(session.is_empty());
|
||||||
|
|
||||||
|
session.attach(None).unwrap();
|
||||||
|
db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
session.changeset().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn one_changeset_strm() -> Vec<u8> {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut session = Session::new(&db).unwrap();
|
||||||
|
assert!(session.is_empty());
|
||||||
|
|
||||||
|
session.attach(None).unwrap();
|
||||||
|
db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
session.changeset_strm(&mut output).unwrap();
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_changeset() {
|
||||||
|
let changeset = one_changeset();
|
||||||
|
let mut iter = changeset.iter().unwrap();
|
||||||
|
let item = iter.next().unwrap();
|
||||||
|
assert!(item.is_some());
|
||||||
|
|
||||||
|
let item = item.unwrap();
|
||||||
|
let op = item.op().unwrap();
|
||||||
|
assert_eq!("foo", op.table_name());
|
||||||
|
assert_eq!(1, op.number_of_columns());
|
||||||
|
assert_eq!(Action::SQLITE_INSERT, op.code());
|
||||||
|
assert_eq!(false, op.indirect());
|
||||||
|
|
||||||
|
let pk = item.pk().unwrap();
|
||||||
|
assert_eq!(&[1], pk);
|
||||||
|
|
||||||
|
let new_value = item.new_value(0).unwrap();
|
||||||
|
assert_eq!(Ok("bar"), new_value.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_changeset_strm() {
|
||||||
|
let output = one_changeset_strm();
|
||||||
|
assert!(!output.is_empty());
|
||||||
|
assert_eq!(14, output.len());
|
||||||
|
|
||||||
|
let mut input = output.as_slice();
|
||||||
|
let mut iter = ChangesetIter::start_strm(&mut input).unwrap();
|
||||||
|
let item = iter.next().unwrap();
|
||||||
|
assert!(item.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_changeset_apply() {
|
||||||
|
let changeset = one_changeset();
|
||||||
|
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
}
|
||||||
|
db.apply(
|
||||||
|
&changeset,
|
||||||
|
None::<fn(&str) -> bool>,
|
||||||
|
|_conflict_type, _item| {
|
||||||
|
CALLED.store(true, Ordering::Relaxed);
|
||||||
|
ConflictAction::SQLITE_CHANGESET_OMIT
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(!CALLED.load(Ordering::Relaxed));
|
||||||
|
let check = db
|
||||||
|
.query_row("SELECT 1 FROM foo WHERE t = ?", &["bar"], |row| {
|
||||||
|
row.get::<_, i32>(0)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(1, check);
|
||||||
|
|
||||||
|
// conflict expected when same changeset applied again on the same db
|
||||||
|
db.apply(
|
||||||
|
&changeset,
|
||||||
|
None::<fn(&str) -> bool>,
|
||||||
|
|conflict_type, item| {
|
||||||
|
CALLED.store(true, Ordering::Relaxed);
|
||||||
|
assert_eq!(ConflictType::SQLITE_CHANGESET_CONFLICT, conflict_type);
|
||||||
|
let conflict = item.conflict(0).unwrap();
|
||||||
|
assert_eq!(Ok("bar"), conflict.as_str());
|
||||||
|
ConflictAction::SQLITE_CHANGESET_OMIT
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(CALLED.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_changeset_apply_strm() {
|
||||||
|
let output = one_changeset_strm();
|
||||||
|
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.apply_strm(
|
||||||
|
&mut output.as_slice(),
|
||||||
|
None::<fn(&str) -> bool>,
|
||||||
|
|_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let check = db
|
||||||
|
.query_row("SELECT 1 FROM foo WHERE t = ?", &["bar"], |row| {
|
||||||
|
row.get::<_, i32>(0)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(1, check);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_session_empty() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut session = Session::new(&db).unwrap();
|
||||||
|
assert!(session.is_empty());
|
||||||
|
|
||||||
|
session.attach(None).unwrap();
|
||||||
|
db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(!session.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_session_set_enabled() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
let mut session = Session::new(&db).unwrap();
|
||||||
|
assert!(session.is_enabled());
|
||||||
|
session.set_enabled(false);
|
||||||
|
assert!(!session.is_enabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_session_set_indirect() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
let mut session = Session::new(&db).unwrap();
|
||||||
|
assert!(!session.is_indirect());
|
||||||
|
session.set_indirect(true);
|
||||||
|
assert!(session.is_indirect());
|
||||||
|
}
|
||||||
|
}
|
222
src/statement.rs
222
src/statement.rs
@ -11,9 +11,9 @@ use super::str_to_cstring;
|
|||||||
use super::{
|
use super::{
|
||||||
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
|
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
|
||||||
};
|
};
|
||||||
use types::{ToSql, ToSqlOutput};
|
use crate::types::{ToSql, ToSqlOutput};
|
||||||
#[cfg(feature = "array")]
|
#[cfg(feature = "array")]
|
||||||
use vtab::array::{free_array, ARRAY_TYPE};
|
use crate::vtab::array::{free_array, ARRAY_TYPE};
|
||||||
|
|
||||||
/// A prepared statement.
|
/// A prepared statement.
|
||||||
pub struct Statement<'conn> {
|
pub struct Statement<'conn> {
|
||||||
@ -70,10 +70,10 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use rusqlite::{Connection, Result};
|
/// # use rusqlite::{Connection, Result};
|
||||||
/// fn update_rows(conn: &Connection) -> Result<()> {
|
/// fn update_rows(conn: &Connection) -> Result<()> {
|
||||||
/// let mut stmt = try!(conn.prepare("UPDATE foo SET bar = 'baz' WHERE qux = ?"));
|
/// let mut stmt = conn.prepare("UPDATE foo SET bar = 'baz' WHERE qux = ?")?;
|
||||||
///
|
///
|
||||||
/// try!(stmt.execute(&[1i32]));
|
/// stmt.execute(&[1i32])?;
|
||||||
/// try!(stmt.execute(&[2i32]));
|
/// stmt.execute(&[2i32])?;
|
||||||
///
|
///
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
@ -89,7 +89,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
{
|
{
|
||||||
try!(self.bind_parameters(params));
|
self.bind_parameters(params)?;
|
||||||
self.execute_with_bound_parameters()
|
self.execute_with_bound_parameters()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,18 +107,29 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use rusqlite::{Connection, Result};
|
/// # use rusqlite::{Connection, Result};
|
||||||
/// fn insert(conn: &Connection) -> Result<usize> {
|
/// fn insert(conn: &Connection) -> Result<usize> {
|
||||||
/// let mut stmt = try!(conn.prepare("INSERT INTO test (name) VALUES (:name)"));
|
/// let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?;
|
||||||
/// stmt.execute_named(&[(":name", &"one")])
|
/// stmt.execute_named(&[(":name", &"one")])
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// Note, the `named_params` macro is provided for syntactic convenience,
|
||||||
|
/// and so the above example could also be written as:
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use rusqlite::{Connection, Result, named_params};
|
||||||
|
/// fn insert(conn: &Connection) -> Result<usize> {
|
||||||
|
/// let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?;
|
||||||
|
/// stmt.execute_named(named_params!{":name": "one"})
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails, the executed statement
|
/// Will return `Err` if binding parameters fails, the executed statement
|
||||||
/// returns rows (in which case `query` should be used instead), or the
|
/// returns rows (in which case `query` should be used instead), or the
|
||||||
/// underling SQLite call fails.
|
/// underling SQLite call fails.
|
||||||
pub fn execute_named(&mut self, params: &[(&str, &ToSql)]) -> Result<usize> {
|
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
||||||
try!(self.bind_parameters_named(params));
|
self.bind_parameters_named(params)?;
|
||||||
self.execute_with_bound_parameters()
|
self.execute_with_bound_parameters()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +151,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
{
|
{
|
||||||
let changes = try!(self.execute(params));
|
let changes = self.execute(params)?;
|
||||||
match changes {
|
match changes {
|
||||||
1 => Ok(self.conn.last_insert_rowid()),
|
1 => Ok(self.conn.last_insert_rowid()),
|
||||||
_ => Err(Error::StatementChangedRows(changes)),
|
_ => Err(Error::StatementChangedRows(changes)),
|
||||||
@ -159,12 +170,12 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use rusqlite::{Connection, Result, NO_PARAMS};
|
/// # use rusqlite::{Connection, Result, NO_PARAMS};
|
||||||
/// fn get_names(conn: &Connection) -> Result<Vec<String>> {
|
/// fn get_names(conn: &Connection) -> Result<Vec<String>> {
|
||||||
/// let mut stmt = try!(conn.prepare("SELECT name FROM people"));
|
/// let mut stmt = conn.prepare("SELECT name FROM people")?;
|
||||||
/// let mut rows = try!(stmt.query(NO_PARAMS));
|
/// let mut rows = stmt.query(NO_PARAMS)?;
|
||||||
///
|
///
|
||||||
/// let mut names = Vec::new();
|
/// let mut names = Vec::new();
|
||||||
/// while let Some(result_row) = rows.next() {
|
/// while let Some(result_row) = rows.next() {
|
||||||
/// let row = try!(result_row);
|
/// let row = result_row?;
|
||||||
/// names.push(row.get(0));
|
/// names.push(row.get(0));
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -175,13 +186,13 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// ## Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
pub fn query<'a, P>(&'a mut self, params: P) -> Result<Rows<'a>>
|
pub fn query<P>(&mut self, params: P) -> Result<Rows<'_>>
|
||||||
where
|
where
|
||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
{
|
{
|
||||||
try!(self.check_readonly());
|
self.check_readonly()?;
|
||||||
try!(self.bind_parameters(params));
|
self.bind_parameters(params)?;
|
||||||
Ok(Rows::new(self))
|
Ok(Rows::new(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,8 +207,23 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use rusqlite::{Connection, Result};
|
/// # use rusqlite::{Connection, Result};
|
||||||
/// fn query(conn: &Connection) -> Result<()> {
|
/// fn query(conn: &Connection) -> Result<()> {
|
||||||
/// let mut stmt = try!(conn.prepare("SELECT * FROM test where name = :name"));
|
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
|
||||||
/// let mut rows = try!(stmt.query_named(&[(":name", &"one")]));
|
/// let mut rows = stmt.query_named(&[(":name", &"one")])?;
|
||||||
|
/// while let Some(row) = rows.next() {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note, the `named_params!` macro is provided for syntactic convenience,
|
||||||
|
/// and so the above example could also be written as:
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use rusqlite::{Connection, Result, named_params};
|
||||||
|
/// fn query(conn: &Connection) -> Result<()> {
|
||||||
|
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
|
||||||
|
/// let mut rows = stmt.query_named(named_params!{ ":name": "one" })?;
|
||||||
/// while let Some(row) = rows.next() {
|
/// while let Some(row) = rows.next() {
|
||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
@ -208,9 +234,9 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
pub fn query_named<'a>(&'a mut self, params: &[(&str, &ToSql)]) -> Result<Rows<'a>> {
|
pub fn query_named<'a>(&'a mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'a>> {
|
||||||
try!(self.check_readonly());
|
self.check_readonly()?;
|
||||||
try!(self.bind_parameters_named(params));
|
self.bind_parameters_named(params)?;
|
||||||
Ok(Rows::new(self))
|
Ok(Rows::new(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,12 +248,12 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use rusqlite::{Connection, Result, NO_PARAMS};
|
/// # use rusqlite::{Connection, Result, NO_PARAMS};
|
||||||
/// fn get_names(conn: &Connection) -> Result<Vec<String>> {
|
/// fn get_names(conn: &Connection) -> Result<Vec<String>> {
|
||||||
/// let mut stmt = try!(conn.prepare("SELECT name FROM people"));
|
/// let mut stmt = conn.prepare("SELECT name FROM people")?;
|
||||||
/// let rows = try!(stmt.query_map(NO_PARAMS, |row| row.get(0)));
|
/// let rows = stmt.query_map(NO_PARAMS, |row| row.get(0))?;
|
||||||
///
|
///
|
||||||
/// let mut names = Vec::new();
|
/// let mut names = Vec::new();
|
||||||
/// for name_result in rows {
|
/// for name_result in rows {
|
||||||
/// names.push(try!(name_result));
|
/// names.push(name_result?);
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// Ok(names)
|
/// Ok(names)
|
||||||
@ -237,11 +263,11 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// ## Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
pub fn query_map<'a, T, P, F>(&'a mut self, params: P, f: F) -> Result<MappedRows<'a, F>>
|
pub fn query_map<T, P, F>(&mut self, params: P, f: F) -> Result<MappedRows<'_, F>>
|
||||||
where
|
where
|
||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
F: FnMut(&Row) -> T,
|
F: FnMut(&Row<'_, '_>) -> T,
|
||||||
{
|
{
|
||||||
let rows = self.query(params)?;
|
let rows = self.query(params)?;
|
||||||
Ok(MappedRows::new(rows, f))
|
Ok(MappedRows::new(rows, f))
|
||||||
@ -259,12 +285,12 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use rusqlite::{Connection, Result};
|
/// # use rusqlite::{Connection, Result};
|
||||||
/// fn get_names(conn: &Connection) -> Result<Vec<String>> {
|
/// fn get_names(conn: &Connection) -> Result<Vec<String>> {
|
||||||
/// let mut stmt = try!(conn.prepare("SELECT name FROM people WHERE id = :id"));
|
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
|
||||||
/// let rows = try!(stmt.query_map_named(&[(":id", &"one")], |row| row.get(0)));
|
/// let rows = stmt.query_map_named(&[(":id", &"one")], |row| row.get(0))?;
|
||||||
///
|
///
|
||||||
/// let mut names = Vec::new();
|
/// let mut names = Vec::new();
|
||||||
/// for name_result in rows {
|
/// for name_result in rows {
|
||||||
/// names.push(try!(name_result));
|
/// names.push(name_result?);
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// Ok(names)
|
/// Ok(names)
|
||||||
@ -276,11 +302,11 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
pub fn query_map_named<'a, T, F>(
|
pub fn query_map_named<'a, T, F>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
params: &[(&str, &ToSql)],
|
params: &[(&str, &dyn ToSql)],
|
||||||
f: F,
|
f: F,
|
||||||
) -> Result<MappedRows<'a, F>>
|
) -> Result<MappedRows<'a, F>>
|
||||||
where
|
where
|
||||||
F: FnMut(&Row) -> T,
|
F: FnMut(&Row<'_, '_>) -> T,
|
||||||
{
|
{
|
||||||
let rows = self.query_named(params)?;
|
let rows = self.query_named(params)?;
|
||||||
Ok(MappedRows::new(rows, f))
|
Ok(MappedRows::new(rows, f))
|
||||||
@ -293,16 +319,12 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
pub fn query_and_then<'a, T, E, P, F>(
|
pub fn query_and_then<T, E, P, F>(&mut self, params: P, f: F) -> Result<AndThenRows<'_, F>>
|
||||||
&'a mut self,
|
|
||||||
params: P,
|
|
||||||
f: F,
|
|
||||||
) -> Result<AndThenRows<'a, F>>
|
|
||||||
where
|
where
|
||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
E: convert::From<Error>,
|
E: convert::From<Error>,
|
||||||
F: FnMut(&Row) -> result::Result<T, E>,
|
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
|
||||||
{
|
{
|
||||||
let rows = self.query(params)?;
|
let rows = self.query(params)?;
|
||||||
Ok(AndThenRows::new(rows, f))
|
Ok(AndThenRows::new(rows, f))
|
||||||
@ -330,13 +352,13 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn get_names(conn: &Connection) -> Result<Vec<Person>> {
|
/// fn get_names(conn: &Connection) -> Result<Vec<Person>> {
|
||||||
/// let mut stmt = try!(conn.prepare("SELECT name FROM people WHERE id = :id"));
|
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
|
||||||
/// let rows =
|
/// let rows =
|
||||||
/// try!(stmt.query_and_then_named(&[(":id", &"one")], |row| name_to_person(row.get(0))));
|
/// stmt.query_and_then_named(&[(":id", &"one")], |row| name_to_person(row.get(0)))?;
|
||||||
///
|
///
|
||||||
/// let mut persons = Vec::new();
|
/// let mut persons = Vec::new();
|
||||||
/// for person_result in rows {
|
/// for person_result in rows {
|
||||||
/// persons.push(try!(person_result));
|
/// persons.push(person_result?);
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// Ok(persons)
|
/// Ok(persons)
|
||||||
@ -348,12 +370,12 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
pub fn query_and_then_named<'a, T, E, F>(
|
pub fn query_and_then_named<'a, T, E, F>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
params: &[(&str, &ToSql)],
|
params: &[(&str, &dyn ToSql)],
|
||||||
f: F,
|
f: F,
|
||||||
) -> Result<AndThenRows<'a, F>>
|
) -> Result<AndThenRows<'a, F>>
|
||||||
where
|
where
|
||||||
E: convert::From<Error>,
|
E: convert::From<Error>,
|
||||||
F: FnMut(&Row) -> result::Result<T, E>,
|
F: FnMut(&Row<'_, '_>) -> result::Result<T, E>,
|
||||||
{
|
{
|
||||||
let rows = self.query_named(params)?;
|
let rows = self.query_named(params)?;
|
||||||
Ok(AndThenRows::new(rows, f))
|
Ok(AndThenRows::new(rows, f))
|
||||||
@ -366,13 +388,8 @@ impl<'conn> Statement<'conn> {
|
|||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
{
|
{
|
||||||
let mut rows = try!(self.query(params));
|
let mut rows = self.query(params)?;
|
||||||
let exists = {
|
let exists = rows.next().is_some();
|
||||||
match rows.next() {
|
|
||||||
Some(_) => true,
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(exists)
|
Ok(exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,6 +399,10 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// If the query returns more than one row, all rows except the first are
|
/// If the query returns more than one row, all rows except the first are
|
||||||
/// ignored.
|
/// ignored.
|
||||||
///
|
///
|
||||||
|
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
|
||||||
|
/// query truly is optional, you can call `.optional()` on the result of
|
||||||
|
/// this to get a `Result<Option<T>>`.
|
||||||
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if the underlying SQLite call fails.
|
/// Will return `Err` if the underlying SQLite call fails.
|
||||||
@ -389,9 +410,9 @@ impl<'conn> Statement<'conn> {
|
|||||||
where
|
where
|
||||||
P: IntoIterator,
|
P: IntoIterator,
|
||||||
P::Item: ToSql,
|
P::Item: ToSql,
|
||||||
F: FnOnce(&Row) -> T,
|
F: FnOnce(&Row<'_, '_>) -> T,
|
||||||
{
|
{
|
||||||
let mut rows = try!(self.query(params));
|
let mut rows = self.query(params)?;
|
||||||
|
|
||||||
rows.get_expected_row().map(|r| f(&r))
|
rows.get_expected_row().map(|r| f(&r))
|
||||||
}
|
}
|
||||||
@ -415,7 +436,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
/// Will return Err if `name` is invalid. Will return Ok(None) if the name
|
/// Will return Err if `name` is invalid. Will return Ok(None) if the name
|
||||||
/// is valid but not a bound parameter of this statement.
|
/// is valid but not a bound parameter of this statement.
|
||||||
pub fn parameter_index(&self, name: &str) -> Result<Option<usize>> {
|
pub fn parameter_index(&self, name: &str) -> Result<Option<usize>> {
|
||||||
let c_name = try!(str_to_cstring(name));
|
let c_name = str_to_cstring(name)?;
|
||||||
Ok(self.stmt.bind_parameter_index(&c_name))
|
Ok(self.stmt.bind_parameter_index(&c_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,7 +452,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
if index > expected {
|
if index > expected {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
try!(self.bind_parameter(&p, index));
|
self.bind_parameter(&p, index)?;
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
index, expected,
|
index, expected,
|
||||||
@ -442,10 +463,10 @@ impl<'conn> Statement<'conn> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind_parameters_named(&mut self, params: &[(&str, &ToSql)]) -> Result<()> {
|
fn bind_parameters_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<()> {
|
||||||
for &(name, value) in params {
|
for &(name, value) in params {
|
||||||
if let Some(i) = try!(self.parameter_index(name)) {
|
if let Some(i) = self.parameter_index(name)? {
|
||||||
try!(self.bind_parameter(value, i));
|
self.bind_parameter(value, i)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::InvalidParameterName(name.into()));
|
return Err(Error::InvalidParameterName(name.into()));
|
||||||
}
|
}
|
||||||
@ -453,8 +474,8 @@ impl<'conn> Statement<'conn> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind_parameter(&self, param: &ToSql, col: usize) -> Result<()> {
|
fn bind_parameter(&self, param: &dyn ToSql, col: usize) -> Result<()> {
|
||||||
let value = try!(param.to_sql());
|
let value = param.to_sql()?;
|
||||||
|
|
||||||
let ptr = unsafe { self.stmt.ptr() };
|
let ptr = unsafe { self.stmt.ptr() };
|
||||||
let value = match value {
|
let value = match value {
|
||||||
@ -489,7 +510,7 @@ impl<'conn> Statement<'conn> {
|
|||||||
if length > ::std::i32::MAX as usize {
|
if length > ::std::i32::MAX as usize {
|
||||||
ffi::SQLITE_TOOBIG
|
ffi::SQLITE_TOOBIG
|
||||||
} else {
|
} else {
|
||||||
let c_str = try!(str_to_cstring(s));
|
let c_str = str_to_cstring(s)?;
|
||||||
let destructor = if length > 0 {
|
let destructor = if length > 0 {
|
||||||
ffi::SQLITE_TRANSIENT()
|
ffi::SQLITE_TRANSIENT()
|
||||||
} else {
|
} else {
|
||||||
@ -571,6 +592,17 @@ impl<'conn> Statement<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the value for one of the status counters for this statement.
|
||||||
|
pub fn get_status(&self, status: StatementStatus) -> i32 {
|
||||||
|
self.stmt.get_status(status, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the value of one of the status counters for this statement,
|
||||||
|
/// returning the value it had before resetting.
|
||||||
|
pub fn reset_status(&self, status: StatementStatus) -> i32 {
|
||||||
|
self.stmt.get_status(status, true)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn check_no_tail(&self) -> Result<()> {
|
pub(crate) fn check_no_tail(&self) -> Result<()> {
|
||||||
if self.stmt.has_tail() {
|
if self.stmt.has_tail() {
|
||||||
Err(Error::MultipleStatement)
|
Err(Error::MultipleStatement)
|
||||||
@ -589,7 +621,7 @@ impl<'conn> Into<RawStatement> for Statement<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> fmt::Debug for Statement<'conn> {
|
impl<'conn> fmt::Debug for Statement<'conn> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let sql = str::from_utf8(self.stmt.sql().to_bytes());
|
let sql = str::from_utf8(self.stmt.sql().to_bytes());
|
||||||
f.debug_struct("Statement")
|
f.debug_struct("Statement")
|
||||||
.field("conn", self.conn)
|
.field("conn", self.conn)
|
||||||
@ -607,11 +639,11 @@ impl<'conn> Drop for Statement<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Statement<'conn> {
|
impl<'conn> Statement<'conn> {
|
||||||
pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement {
|
pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> {
|
||||||
Statement { conn, stmt }
|
Statement { conn, stmt }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn value_ref(&self, col: usize) -> ValueRef {
|
pub(crate) fn value_ref(&self, col: usize) -> ValueRef<'_> {
|
||||||
let raw = unsafe { self.stmt.ptr() };
|
let raw = unsafe { self.stmt.ptr() };
|
||||||
|
|
||||||
match self.stmt.column_type(col) {
|
match self.stmt.column_type(col) {
|
||||||
@ -679,9 +711,36 @@ impl<'conn> Statement<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepared statement status counters.
|
||||||
|
///
|
||||||
|
/// See https://www.sqlite.org/c3ref/c_stmtstatus_counter.html
|
||||||
|
/// for explanations of each.
|
||||||
|
///
|
||||||
|
/// Note that depending on your version of SQLite, all of these
|
||||||
|
/// may not be available.
|
||||||
|
#[repr(i32)]
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum StatementStatus {
|
||||||
|
/// Equivalent to SQLITE_STMTSTATUS_FULLSCAN_STEP
|
||||||
|
FullscanStep = 1,
|
||||||
|
/// Equivalent to SQLITE_STMTSTATUS_SORT
|
||||||
|
Sort = 2,
|
||||||
|
/// Equivalent to SQLITE_STMTSTATUS_AUTOINDEX
|
||||||
|
AutoIndex = 3,
|
||||||
|
/// Equivalent to SQLITE_STMTSTATUS_VM_STEP
|
||||||
|
VmStep = 4,
|
||||||
|
/// Equivalent to SQLITE_STMTSTATUS_REPREPARE
|
||||||
|
RePrepare = 5,
|
||||||
|
/// Equivalent to SQLITE_STMTSTATUS_RUN
|
||||||
|
Run = 6,
|
||||||
|
/// Equivalent to SQLITE_STMTSTATUS_MEMUSED
|
||||||
|
MemUsed = 99,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use {Connection, Error, Result, NO_PARAMS};
|
use crate::types::ToSql;
|
||||||
|
use crate::{Connection, Error, Result, NO_PARAMS};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_execute_named() {
|
fn test_execute_named() {
|
||||||
@ -803,6 +862,7 @@ mod test {
|
|||||||
assert_eq!(1, doubled_id);
|
assert_eq!(1, doubled_id);
|
||||||
|
|
||||||
// second row should be Err
|
// second row should be Err
|
||||||
|
#[allow(clippy::match_wild_err_arm)]
|
||||||
match rows.next().unwrap() {
|
match rows.next().unwrap() {
|
||||||
Ok(_) => panic!("invalid Ok"),
|
Ok(_) => panic!("invalid Ok"),
|
||||||
Err(Error::SqliteSingleThreadedMode) => (),
|
Err(Error::SqliteSingleThreadedMode) => (),
|
||||||
@ -963,4 +1023,40 @@ mod test {
|
|||||||
stmt.bind_parameter(&1, 1).unwrap();
|
stmt.bind_parameter(&1, 1).unwrap();
|
||||||
assert_eq!(Some("SELECT 1"), stmt.expanded_sql());
|
assert_eq!(Some("SELECT 1"), stmt.expanded_sql());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bind_parameters() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
// dynamic slice:
|
||||||
|
db.query_row(
|
||||||
|
"SELECT ?1, ?2, ?3",
|
||||||
|
&[&1u8 as &dyn ToSql, &"one", &Some("one")],
|
||||||
|
|row| row.get::<_, u8>(0),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// existing collection:
|
||||||
|
let data = vec![1, 2, 3];
|
||||||
|
db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, u8>(0))
|
||||||
|
.unwrap();
|
||||||
|
db.query_row("SELECT ?1, ?2, ?3", data.as_slice(), |row| {
|
||||||
|
row.get::<_, u8>(0)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
db.query_row("SELECT ?1, ?2, ?3", data, |row| row.get::<_, u8>(0))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
let data: BTreeSet<String> = ["one", "two", "three"]
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect();
|
||||||
|
db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, String>(0))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let data = [0; 3];
|
||||||
|
db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, u8>(0))
|
||||||
|
.unwrap();
|
||||||
|
db.query_row("SELECT ?1, ?2, ?3", data.iter(), |row| row.get::<_, u8>(0))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
13
src/trace.rs
13
src/trace.rs
@ -3,12 +3,13 @@
|
|||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::os::raw::{c_char, c_int, c_void};
|
use std::os::raw::{c_char, c_int, c_void};
|
||||||
|
use std::panic::catch_unwind;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
use error::error_from_sqlite_code;
|
use crate::error::error_from_sqlite_code;
|
||||||
use {Connection, Result};
|
use crate::{Connection, Result};
|
||||||
|
|
||||||
/// Set up the process-wide SQLite error logging callback.
|
/// Set up the process-wide SQLite error logging callback.
|
||||||
/// This function is marked unsafe for two reasons:
|
/// This function is marked unsafe for two reasons:
|
||||||
@ -27,7 +28,7 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
|
|||||||
let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
|
let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
|
||||||
|
|
||||||
let s = String::from_utf8_lossy(c_slice);
|
let s = String::from_utf8_lossy(c_slice);
|
||||||
callback(err, &s);
|
let _ = catch_unwind(|| callback(err, &s));
|
||||||
}
|
}
|
||||||
|
|
||||||
let rc = match callback {
|
let rc = match callback {
|
||||||
@ -72,7 +73,7 @@ impl Connection {
|
|||||||
let trace_fn: fn(&str) = mem::transmute(p_arg);
|
let trace_fn: fn(&str) = mem::transmute(p_arg);
|
||||||
let c_slice = CStr::from_ptr(z_sql).to_bytes();
|
let c_slice = CStr::from_ptr(z_sql).to_bytes();
|
||||||
let s = String::from_utf8_lossy(c_slice);
|
let s = String::from_utf8_lossy(c_slice);
|
||||||
trace_fn(&s);
|
let _ = catch_unwind(|| trace_fn(&s));
|
||||||
}
|
}
|
||||||
|
|
||||||
let c = self.db.borrow_mut();
|
let c = self.db.borrow_mut();
|
||||||
@ -106,7 +107,7 @@ impl Connection {
|
|||||||
nanoseconds / NANOS_PER_SEC,
|
nanoseconds / NANOS_PER_SEC,
|
||||||
(nanoseconds % NANOS_PER_SEC) as u32,
|
(nanoseconds % NANOS_PER_SEC) as u32,
|
||||||
);
|
);
|
||||||
profile_fn(&s, duration);
|
let _ = catch_unwind(|| profile_fn(&s, duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
let c = self.db.borrow_mut();
|
let c = self.db.borrow_mut();
|
||||||
@ -124,7 +125,7 @@ mod test {
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use Connection;
|
use crate::Connection;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_trace() {
|
fn test_trace() {
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
|
use crate::{Connection, Result};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use {Connection, Result};
|
|
||||||
|
|
||||||
/// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is
|
|
||||||
/// deprecated.
|
|
||||||
#[deprecated(since = "0.6.0", note = "Use TransactionBehavior instead")]
|
|
||||||
pub type SqliteTransactionBehavior = TransactionBehavior;
|
|
||||||
|
|
||||||
/// Options for transaction behavior. See [BEGIN
|
/// Options for transaction behavior. See [BEGIN
|
||||||
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
|
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
|
||||||
@ -16,7 +11,7 @@ pub enum TransactionBehavior {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Options for how a Transaction or Savepoint should behave when it is dropped.
|
/// Options for how a Transaction or Savepoint should behave when it is dropped.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum DropBehavior {
|
pub enum DropBehavior {
|
||||||
/// Roll back the changes. This is the default.
|
/// Roll back the changes. This is the default.
|
||||||
Rollback,
|
Rollback,
|
||||||
@ -32,10 +27,6 @@ pub enum DropBehavior {
|
|||||||
Panic,
|
Panic,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Old name for `Transaction`. `SqliteTransaction` is deprecated.
|
|
||||||
#[deprecated(since = "0.6.0", note = "Use Transaction instead")]
|
|
||||||
pub type SqliteTransaction<'conn> = Transaction<'conn>;
|
|
||||||
|
|
||||||
/// Represents a transaction on a database connection.
|
/// Represents a transaction on a database connection.
|
||||||
///
|
///
|
||||||
/// ## Note
|
/// ## Note
|
||||||
@ -51,14 +42,15 @@ pub type SqliteTransaction<'conn> = Transaction<'conn>;
|
|||||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
||||||
/// let tx = try!(conn.transaction());
|
/// let tx = conn.transaction()?;
|
||||||
///
|
///
|
||||||
/// try!(do_queries_part_1(&tx)); // tx causes rollback if this fails
|
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
|
||||||
/// try!(do_queries_part_2(&tx)); // tx causes rollback if this fails
|
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
|
||||||
///
|
///
|
||||||
/// tx.commit()
|
/// tx.commit()
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Transaction<'conn> {
|
pub struct Transaction<'conn> {
|
||||||
conn: &'conn Connection,
|
conn: &'conn Connection,
|
||||||
drop_behavior: DropBehavior,
|
drop_behavior: DropBehavior,
|
||||||
@ -79,10 +71,10 @@ pub struct Transaction<'conn> {
|
|||||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
||||||
/// let sp = try!(conn.savepoint());
|
/// let sp = conn.savepoint()?;
|
||||||
///
|
///
|
||||||
/// try!(do_queries_part_1(&sp)); // sp causes rollback if this fails
|
/// do_queries_part_1(&sp)?; // sp causes rollback if this fails
|
||||||
/// try!(do_queries_part_2(&sp)); // sp causes rollback if this fails
|
/// do_queries_part_2(&sp)?; // sp causes rollback if this fails
|
||||||
///
|
///
|
||||||
/// sp.commit()
|
/// sp.commit()
|
||||||
/// }
|
/// }
|
||||||
@ -101,7 +93,7 @@ impl<'conn> Transaction<'conn> {
|
|||||||
/// Even though we don't mutate the connection, we take a `&mut Connection`
|
/// Even though we don't mutate the connection, we take a `&mut Connection`
|
||||||
/// so as to prevent nested or concurrent transactions on the same
|
/// so as to prevent nested or concurrent transactions on the same
|
||||||
/// connection.
|
/// connection.
|
||||||
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction> {
|
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> {
|
||||||
let query = match behavior {
|
let query = match behavior {
|
||||||
TransactionBehavior::Deferred => "BEGIN DEFERRED",
|
TransactionBehavior::Deferred => "BEGIN DEFERRED",
|
||||||
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
|
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
|
||||||
@ -127,12 +119,12 @@ impl<'conn> Transaction<'conn> {
|
|||||||
/// # use rusqlite::{Connection, Result};
|
/// # use rusqlite::{Connection, Result};
|
||||||
/// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true }
|
/// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true }
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
||||||
/// let mut tx = try!(conn.transaction());
|
/// let mut tx = conn.transaction()?;
|
||||||
///
|
///
|
||||||
/// {
|
/// {
|
||||||
/// let sp = try!(tx.savepoint());
|
/// let sp = tx.savepoint()?;
|
||||||
/// if perform_queries_part_1_succeeds(&sp) {
|
/// if perform_queries_part_1_succeeds(&sp) {
|
||||||
/// try!(sp.commit());
|
/// sp.commit()?;
|
||||||
/// }
|
/// }
|
||||||
/// // otherwise, sp will rollback
|
/// // otherwise, sp will rollback
|
||||||
/// }
|
/// }
|
||||||
@ -140,12 +132,12 @@ impl<'conn> Transaction<'conn> {
|
|||||||
/// tx.commit()
|
/// tx.commit()
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn savepoint(&mut self) -> Result<Savepoint> {
|
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
||||||
Savepoint::with_depth(self.conn, 1)
|
Savepoint::with_depth(self.conn, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new savepoint with a custom savepoint name. See `savepoint()`.
|
/// Create a new savepoint with a custom savepoint name. See `savepoint()`.
|
||||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint> {
|
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
||||||
Savepoint::with_depth_and_name(self.conn, 1, name)
|
Savepoint::with_depth_and_name(self.conn, 1, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +215,7 @@ impl<'conn> Savepoint<'conn> {
|
|||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
depth: u32,
|
depth: u32,
|
||||||
name: T,
|
name: T,
|
||||||
) -> Result<Savepoint> {
|
) -> Result<Savepoint<'_>> {
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
conn.execute_batch(&format!("SAVEPOINT {}", name))
|
conn.execute_batch(&format!("SAVEPOINT {}", name))
|
||||||
.map(|_| Savepoint {
|
.map(|_| Savepoint {
|
||||||
@ -235,28 +227,28 @@ impl<'conn> Savepoint<'conn> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint> {
|
fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> {
|
||||||
let name = format!("_rusqlite_sp_{}", depth);
|
let name = format!("_rusqlite_sp_{}", depth);
|
||||||
Savepoint::with_depth_and_name(conn, depth, name)
|
Savepoint::with_depth_and_name(conn, depth, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Begin a new savepoint. Can be nested.
|
/// Begin a new savepoint. Can be nested.
|
||||||
pub fn new(conn: &mut Connection) -> Result<Savepoint> {
|
pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
|
||||||
Savepoint::with_depth(conn, 0)
|
Savepoint::with_depth(conn, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Begin a new savepoint with a user-provided savepoint name.
|
/// Begin a new savepoint with a user-provided savepoint name.
|
||||||
pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint> {
|
pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
|
||||||
Savepoint::with_depth_and_name(conn, 0, name)
|
Savepoint::with_depth_and_name(conn, 0, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Begin a nested savepoint.
|
/// Begin a nested savepoint.
|
||||||
pub fn savepoint(&mut self) -> Result<Savepoint> {
|
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
||||||
Savepoint::with_depth(self.conn, self.depth + 1)
|
Savepoint::with_depth(self.conn, self.depth + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Begin a nested savepoint with a user-provided savepoint name.
|
/// Begin a nested savepoint with a user-provided savepoint name.
|
||||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint> {
|
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
||||||
Savepoint::with_depth_and_name(self.conn, self.depth + 1, name)
|
Savepoint::with_depth_and_name(self.conn, self.depth + 1, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,10 +337,10 @@ impl Connection {
|
|||||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
||||||
/// let tx = try!(conn.transaction());
|
/// let tx = conn.transaction()?;
|
||||||
///
|
///
|
||||||
/// try!(do_queries_part_1(&tx)); // tx causes rollback if this fails
|
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
|
||||||
/// try!(do_queries_part_2(&tx)); // tx causes rollback if this fails
|
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
|
||||||
///
|
///
|
||||||
/// tx.commit()
|
/// tx.commit()
|
||||||
/// }
|
/// }
|
||||||
@ -357,7 +349,7 @@ impl Connection {
|
|||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if the underlying SQLite call fails.
|
/// Will return `Err` if the underlying SQLite call fails.
|
||||||
pub fn transaction(&mut self) -> Result<Transaction> {
|
pub fn transaction(&mut self) -> Result<Transaction<'_>> {
|
||||||
Transaction::new(self, TransactionBehavior::Deferred)
|
Transaction::new(self, TransactionBehavior::Deferred)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,7 +363,7 @@ impl Connection {
|
|||||||
pub fn transaction_with_behavior(
|
pub fn transaction_with_behavior(
|
||||||
&mut self,
|
&mut self,
|
||||||
behavior: TransactionBehavior,
|
behavior: TransactionBehavior,
|
||||||
) -> Result<Transaction> {
|
) -> Result<Transaction<'_>> {
|
||||||
Transaction::new(self, behavior)
|
Transaction::new(self, behavior)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,10 +380,10 @@ impl Connection {
|
|||||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||||
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
||||||
/// let sp = try!(conn.savepoint());
|
/// let sp = conn.savepoint()?;
|
||||||
///
|
///
|
||||||
/// try!(do_queries_part_1(&sp)); // sp causes rollback if this fails
|
/// do_queries_part_1(&sp)?; // sp causes rollback if this fails
|
||||||
/// try!(do_queries_part_2(&sp)); // sp causes rollback if this fails
|
/// do_queries_part_2(&sp)?; // sp causes rollback if this fails
|
||||||
///
|
///
|
||||||
/// sp.commit()
|
/// sp.commit()
|
||||||
/// }
|
/// }
|
||||||
@ -400,7 +392,7 @@ impl Connection {
|
|||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if the underlying SQLite call fails.
|
/// Will return `Err` if the underlying SQLite call fails.
|
||||||
pub fn savepoint(&mut self) -> Result<Savepoint> {
|
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
||||||
Savepoint::new(self)
|
Savepoint::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,7 +403,7 @@ impl Connection {
|
|||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if the underlying SQLite call fails.
|
/// Will return `Err` if the underlying SQLite call fails.
|
||||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint> {
|
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
||||||
Savepoint::with_name(self, name)
|
Savepoint::with_name(self, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,7 +411,7 @@ impl Connection {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::DropBehavior;
|
use super::DropBehavior;
|
||||||
use {Connection, NO_PARAMS};
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
@ -564,6 +556,16 @@ mod test {
|
|||||||
assert_current_sum(8, &db);
|
assert_current_sum(8, &db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rc() {
|
||||||
|
use std::rc::Rc;
|
||||||
|
let mut conn = Connection::open_in_memory().unwrap();
|
||||||
|
let rc_txn = Rc::new(conn.transaction().unwrap());
|
||||||
|
|
||||||
|
// This will compile only if Transaction is Debug
|
||||||
|
Rc::try_unwrap(rc_txn).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
fn insert(x: i32, conn: &Connection) {
|
fn insert(x: i32, conn: &Connection) {
|
||||||
conn.execute("INSERT INTO foo VALUES(?)", &[x]).unwrap();
|
conn.execute("INSERT INTO foo VALUES(?)", &[x]).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
|
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
|
||||||
extern crate chrono;
|
use chrono;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
||||||
|
|
||||||
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
use Result;
|
use crate::Result;
|
||||||
|
|
||||||
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
|
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
|
||||||
impl ToSql for NaiveDate {
|
impl ToSql for NaiveDate {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
let date_str = self.format("%Y-%m-%d").to_string();
|
let date_str = self.format("%Y-%m-%d").to_string();
|
||||||
Ok(ToSqlOutput::from(date_str))
|
Ok(ToSqlOutput::from(date_str))
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@ impl ToSql for NaiveDate {
|
|||||||
|
|
||||||
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
|
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
|
||||||
impl FromSql for NaiveDate {
|
impl FromSql for NaiveDate {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
value
|
value
|
||||||
.as_str()
|
.as_str()
|
||||||
.and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
|
.and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
|
||||||
@ -30,7 +30,7 @@ impl FromSql for NaiveDate {
|
|||||||
|
|
||||||
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
|
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
|
||||||
impl ToSql for NaiveTime {
|
impl ToSql for NaiveTime {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
let date_str = self.format("%H:%M:%S%.f").to_string();
|
let date_str = self.format("%H:%M:%S%.f").to_string();
|
||||||
Ok(ToSqlOutput::from(date_str))
|
Ok(ToSqlOutput::from(date_str))
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ impl ToSql for NaiveTime {
|
|||||||
|
|
||||||
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
|
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
|
||||||
impl FromSql for NaiveTime {
|
impl FromSql for NaiveTime {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
value.as_str().and_then(|s| {
|
value.as_str().and_then(|s| {
|
||||||
let fmt = match s.len() {
|
let fmt = match s.len() {
|
||||||
5 => "%H:%M",
|
5 => "%H:%M",
|
||||||
@ -56,7 +56,7 @@ impl FromSql for NaiveTime {
|
|||||||
/// ISO 8601 combined date and time without timezone =>
|
/// ISO 8601 combined date and time without timezone =>
|
||||||
/// "YYYY-MM-DD HH:MM:SS.SSS"
|
/// "YYYY-MM-DD HH:MM:SS.SSS"
|
||||||
impl ToSql for NaiveDateTime {
|
impl ToSql for NaiveDateTime {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
|
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
|
||||||
Ok(ToSqlOutput::from(date_str))
|
Ok(ToSqlOutput::from(date_str))
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ impl ToSql for NaiveDateTime {
|
|||||||
/// and time without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS"
|
/// and time without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS"
|
||||||
/// also supported)
|
/// also supported)
|
||||||
impl FromSql for NaiveDateTime {
|
impl FromSql for NaiveDateTime {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
value.as_str().and_then(|s| {
|
value.as_str().and_then(|s| {
|
||||||
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
|
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
|
||||||
"%Y-%m-%dT%H:%M:%S%.f"
|
"%Y-%m-%dT%H:%M:%S%.f"
|
||||||
@ -85,17 +85,17 @@ impl FromSql for NaiveDateTime {
|
|||||||
/// Date and time with time zone => UTC RFC3339 timestamp
|
/// Date and time with time zone => UTC RFC3339 timestamp
|
||||||
/// ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
|
/// ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
|
||||||
impl<Tz: TimeZone> ToSql for DateTime<Tz> {
|
impl<Tz: TimeZone> ToSql for DateTime<Tz> {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::from(self.with_timezone(&Utc).to_rfc3339()))
|
Ok(ToSqlOutput::from(self.with_timezone(&Utc).to_rfc3339()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`.
|
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`.
|
||||||
impl FromSql for DateTime<Utc> {
|
impl FromSql for DateTime<Utc> {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
{
|
{
|
||||||
// Try to parse value as rfc3339 first.
|
// Try to parse value as rfc3339 first.
|
||||||
let s = try!(value.as_str());
|
let s = value.as_str()?;
|
||||||
|
|
||||||
// If timestamp looks space-separated, make a copy and replace it with 'T'.
|
// If timestamp looks space-separated, make a copy and replace it with 'T'.
|
||||||
let s = if s.len() >= 11 && s.as_bytes()[10] == b' ' {
|
let s = if s.len() >= 11 && s.as_bytes()[10] == b' ' {
|
||||||
@ -121,8 +121,8 @@ impl FromSql for DateTime<Utc> {
|
|||||||
|
|
||||||
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`.
|
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`.
|
||||||
impl FromSql for DateTime<Local> {
|
impl FromSql for DateTime<Local> {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
let utc_dt = try!(DateTime::<Utc>::column_result(value));
|
let utc_dt = DateTime::<Utc>::column_result(value)?;
|
||||||
Ok(utc_dt.with_timezone(&Local))
|
Ok(utc_dt.with_timezone(&Local))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,7 +132,7 @@ mod test {
|
|||||||
use super::chrono::{
|
use super::chrono::{
|
||||||
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
|
DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
|
||||||
};
|
};
|
||||||
use {Connection, NO_PARAMS};
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
@ -263,4 +263,21 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(local, v);
|
assert_eq!(local, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sqlite_functions() {
|
||||||
|
let db = checked_memory_handle();
|
||||||
|
let result: Result<NaiveTime> =
|
||||||
|
db.query_row("SELECT CURRENT_TIME", NO_PARAMS, |r| r.get(0));
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let result: Result<NaiveDate> =
|
||||||
|
db.query_row("SELECT CURRENT_DATE", NO_PARAMS, |r| r.get(0));
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let result: Result<NaiveDateTime> =
|
||||||
|
db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let result: Result<DateTime<Utc>> =
|
||||||
|
db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,23 @@ pub enum FromSqlError {
|
|||||||
InvalidI128Size(usize),
|
InvalidI128Size(usize),
|
||||||
|
|
||||||
/// An error case available for implementors of the `FromSql` trait.
|
/// An error case available for implementors of the `FromSql` trait.
|
||||||
Other(Box<Error + Send + Sync>),
|
Other(Box<dyn Error + Send + Sync>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for FromSqlError {
|
||||||
|
fn eq(&self, other: &FromSqlError) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(FromSqlError::InvalidType, FromSqlError::InvalidType) => true,
|
||||||
|
(FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
(FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
|
||||||
|
(_, _) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FromSqlError {
|
impl fmt::Display for FromSqlError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
FromSqlError::InvalidType => write!(f, "Invalid type"),
|
FromSqlError::InvalidType => write!(f, "Invalid type"),
|
||||||
FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i),
|
FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i),
|
||||||
@ -47,8 +59,9 @@ impl Error for FromSqlError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "clippy", allow(match_same_arms))]
|
#[allow(clippy::match_same_arms)]
|
||||||
fn cause(&self) -> Option<&Error> {
|
#[allow(deprecated)]
|
||||||
|
fn cause(&self) -> Option<&dyn Error> {
|
||||||
match *self {
|
match *self {
|
||||||
FromSqlError::Other(ref err) => err.cause(),
|
FromSqlError::Other(ref err) => err.cause(),
|
||||||
FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None,
|
FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None,
|
||||||
@ -73,11 +86,11 @@ pub type FromSqlResult<T> = Result<T, FromSqlError>;
|
|||||||
/// fetching values as i64 and then doing the interpretation themselves or by
|
/// fetching values as i64 and then doing the interpretation themselves or by
|
||||||
/// defining a newtype and implementing `FromSql`/`ToSql` for it.
|
/// defining a newtype and implementing `FromSql`/`ToSql` for it.
|
||||||
pub trait FromSql: Sized {
|
pub trait FromSql: Sized {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self>;
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromSql for isize {
|
impl FromSql for isize {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
i64::column_result(value).and_then(|i| {
|
i64::column_result(value).and_then(|i| {
|
||||||
if i < isize::min_value() as i64 || i > isize::max_value() as i64 {
|
if i < isize::min_value() as i64 || i > isize::max_value() as i64 {
|
||||||
Err(FromSqlError::OutOfRange(i))
|
Err(FromSqlError::OutOfRange(i))
|
||||||
@ -91,7 +104,7 @@ impl FromSql for isize {
|
|||||||
macro_rules! from_sql_integral(
|
macro_rules! from_sql_integral(
|
||||||
($t:ident) => (
|
($t:ident) => (
|
||||||
impl FromSql for $t {
|
impl FromSql for $t {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
i64::column_result(value).and_then(|i| {
|
i64::column_result(value).and_then(|i| {
|
||||||
if i < i64::from($t::min_value()) || i > i64::from($t::max_value()) {
|
if i < i64::from($t::min_value()) || i > i64::from($t::max_value()) {
|
||||||
Err(FromSqlError::OutOfRange(i))
|
Err(FromSqlError::OutOfRange(i))
|
||||||
@ -112,13 +125,13 @@ from_sql_integral!(u16);
|
|||||||
from_sql_integral!(u32);
|
from_sql_integral!(u32);
|
||||||
|
|
||||||
impl FromSql for i64 {
|
impl FromSql for i64 {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
value.as_i64()
|
value.as_i64()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromSql for f64 {
|
impl FromSql for f64 {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
ValueRef::Integer(i) => Ok(i as f64),
|
ValueRef::Integer(i) => Ok(i as f64),
|
||||||
ValueRef::Real(f) => Ok(f),
|
ValueRef::Real(f) => Ok(f),
|
||||||
@ -128,7 +141,7 @@ impl FromSql for f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FromSql for bool {
|
impl FromSql for bool {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
i64::column_result(value).map(|i| match i {
|
i64::column_result(value).map(|i| match i {
|
||||||
0 => false,
|
0 => false,
|
||||||
_ => true,
|
_ => true,
|
||||||
@ -137,20 +150,20 @@ impl FromSql for bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FromSql for String {
|
impl FromSql for String {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
value.as_str().map(|s| s.to_string())
|
value.as_str().map(|s| s.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromSql for Vec<u8> {
|
impl FromSql for Vec<u8> {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
value.as_blob().map(|b| b.to_vec())
|
value.as_blob().map(|b| b.to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
impl FromSql for i128 {
|
impl FromSql for i128 {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
|
||||||
value.as_blob().and_then(|bytes| {
|
value.as_blob().and_then(|bytes| {
|
||||||
@ -164,7 +177,7 @@ impl FromSql for i128 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: FromSql> FromSql for Option<T> {
|
impl<T: FromSql> FromSql for Option<T> {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
ValueRef::Null => Ok(None),
|
ValueRef::Null => Ok(None),
|
||||||
_ => FromSql::column_result(value).map(Some),
|
_ => FromSql::column_result(value).map(Some),
|
||||||
@ -173,7 +186,7 @@ impl<T: FromSql> FromSql for Option<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FromSql for Value {
|
impl FromSql for Value {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
Ok(value.into())
|
Ok(value.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,7 +194,7 @@ impl FromSql for Value {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::FromSql;
|
use super::FromSql;
|
||||||
use {Connection, Error};
|
use crate::{Connection, Error};
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
Connection::open_in_memory().unwrap()
|
Connection::open_in_memory().unwrap()
|
||||||
@ -219,11 +232,11 @@ mod test {
|
|||||||
check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
|
check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
|
||||||
check_ranges::<i32>(
|
check_ranges::<i32>(
|
||||||
&db,
|
&db,
|
||||||
&[-2147483649, 2147483648],
|
&[-2_147_483_649, 2_147_483_648],
|
||||||
&[-2147483648, -1, 0, 1, 2147483647],
|
&[-2_147_483_648, -1, 0, 1, 2_147_483_647],
|
||||||
);
|
);
|
||||||
check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
|
check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
|
||||||
check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
|
check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
|
||||||
check_ranges::<u32>(&db, &[-2, -1, 4294967296], &[0, 1, 4294967295]);
|
check_ranges::<u32>(&db, &[-2, -1, 4_294_967_296], &[0, 1, 4_294_967_295]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,6 @@
|
|||||||
//! store timespecs as `f64`s:
|
//! store timespecs as `f64`s:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! extern crate rusqlite;
|
|
||||||
//! extern crate time;
|
|
||||||
//!
|
|
||||||
//! use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
//! use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
//! use rusqlite::Result;
|
//! use rusqlite::Result;
|
||||||
//!
|
//!
|
||||||
@ -77,7 +74,6 @@ mod value_ref;
|
|||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # extern crate rusqlite;
|
|
||||||
/// # use rusqlite::{Connection, Result};
|
/// # use rusqlite::{Connection, Result};
|
||||||
/// # use rusqlite::types::{Null};
|
/// # use rusqlite::types::{Null};
|
||||||
/// fn main() {}
|
/// fn main() {}
|
||||||
@ -98,7 +94,7 @@ pub enum Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Type {
|
impl fmt::Display for Type {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
Type::Null => write!(f, "Null"),
|
Type::Null => write!(f, "Null"),
|
||||||
Type::Integer => write!(f, "Integer"),
|
Type::Integer => write!(f, "Integer"),
|
||||||
@ -111,12 +107,12 @@ impl fmt::Display for Type {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
extern crate time;
|
use time;
|
||||||
|
|
||||||
use super::Value;
|
use super::Value;
|
||||||
|
use crate::{Connection, Error, NO_PARAMS};
|
||||||
use std::f64::EPSILON;
|
use std::f64::EPSILON;
|
||||||
use std::os::raw::{c_double, c_int};
|
use std::os::raw::{c_double, c_int};
|
||||||
use {Connection, Error, NO_PARAMS};
|
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
@ -227,6 +223,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[allow(clippy::cyclomatic_complexity)]
|
||||||
fn test_mismatched_types() {
|
fn test_mismatched_types() {
|
||||||
fn is_invalid_column_type(err: Error) -> bool {
|
fn is_invalid_column_type(err: Error) -> bool {
|
||||||
match err {
|
match err {
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
//! `ToSql` and `FromSql` implementation for JSON `Value`.
|
//! `ToSql` and `FromSql` implementation for JSON `Value`.
|
||||||
extern crate serde_json;
|
use serde_json;
|
||||||
|
|
||||||
use self::serde_json::Value;
|
use self::serde_json::Value;
|
||||||
|
|
||||||
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
use Result;
|
use crate::Result;
|
||||||
|
|
||||||
/// Serialize JSON `Value` to text.
|
/// Serialize JSON `Value` to text.
|
||||||
impl ToSql for Value {
|
impl ToSql for Value {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
|
Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialize text/blob to JSON `Value`.
|
/// Deserialize text/blob to JSON `Value`.
|
||||||
impl FromSql for Value {
|
impl FromSql for Value {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
ValueRef::Text(s) => serde_json::from_str(s),
|
ValueRef::Text(s) => serde_json::from_str(s),
|
||||||
ValueRef::Blob(b) => serde_json::from_slice(b),
|
ValueRef::Blob(b) => serde_json::from_slice(b),
|
||||||
@ -28,8 +28,8 @@ impl FromSql for Value {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::serde_json;
|
use super::serde_json;
|
||||||
use types::ToSql;
|
use crate::types::ToSql;
|
||||||
use {Connection, NO_PARAMS};
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
@ -46,7 +46,7 @@ mod test {
|
|||||||
let data: serde_json::Value = serde_json::from_str(json).unwrap();
|
let data: serde_json::Value = serde_json::from_str(json).unwrap();
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO foo (t, b) VALUES (?, ?)",
|
"INSERT INTO foo (t, b) VALUES (?, ?)",
|
||||||
&[&data as &ToSql, &json.as_bytes()],
|
&[&data as &dyn ToSql, &json.as_bytes()],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
extern crate time;
|
use time;
|
||||||
|
|
||||||
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
use Result;
|
use crate::Result;
|
||||||
|
|
||||||
|
const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S";
|
||||||
const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ";
|
const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ";
|
||||||
const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
|
const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
|
||||||
|
|
||||||
impl ToSql for time::Timespec {
|
impl ToSql for time::Timespec {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
let time_string = time::at_utc(*self)
|
let time_string = time::at_utc(*self)
|
||||||
.strftime(SQLITE_DATETIME_FMT)
|
.strftime(SQLITE_DATETIME_FMT)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -17,14 +18,17 @@ impl ToSql for time::Timespec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FromSql for time::Timespec {
|
impl FromSql for time::Timespec {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
value
|
value
|
||||||
.as_str()
|
.as_str()
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| {
|
match s.len() {
|
||||||
time::strptime(s, SQLITE_DATETIME_FMT_LEGACY)
|
19 => time::strptime(s, CURRENT_TIMESTAMP_FMT),
|
||||||
.or_else(|_| Err(FromSqlError::Other(Box::new(err))))
|
_ => time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| {
|
||||||
})
|
time::strptime(s, SQLITE_DATETIME_FMT_LEGACY).or_else(|_| Err(err))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
.or_else(|err| Err(FromSqlError::Other(Box::new(err))))
|
||||||
})
|
})
|
||||||
.map(|tm| tm.to_timespec())
|
.map(|tm| tm.to_timespec())
|
||||||
}
|
}
|
||||||
@ -33,7 +37,7 @@ impl FromSql for time::Timespec {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::time;
|
use super::time;
|
||||||
use {Connection, NO_PARAMS};
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
@ -50,10 +54,10 @@ mod test {
|
|||||||
|
|
||||||
ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM
|
ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM
|
||||||
ts_vec.push(time::Timespec::new(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
|
ts_vec.push(time::Timespec::new(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
|
||||||
ts_vec.push(time::Timespec::new(1500391124, 1_000_000)); //July 18, 2017
|
ts_vec.push(time::Timespec::new(1_500_391_124, 1_000_000)); //July 18, 2017
|
||||||
ts_vec.push(time::Timespec::new(2000000000, 2_000_000)); //May 18, 2033
|
ts_vec.push(time::Timespec::new(2_000_000_000, 2_000_000)); //May 18, 2033
|
||||||
ts_vec.push(time::Timespec::new(3000000000, 999_999_999)); //January 24, 2065
|
ts_vec.push(time::Timespec::new(3_000_000_000, 999_999_999)); //January 24, 2065
|
||||||
ts_vec.push(time::Timespec::new(10000000000, 0)); //November 20, 2286
|
ts_vec.push(time::Timespec::new(10_000_000_000, 0)); //November 20, 2286
|
||||||
|
|
||||||
for ts in ts_vec {
|
for ts in ts_vec {
|
||||||
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
|
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
|
||||||
@ -67,4 +71,12 @@ mod test {
|
|||||||
assert_eq!(from, ts);
|
assert_eq!(from, ts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sqlite_functions() {
|
||||||
|
let db = checked_memory_handle();
|
||||||
|
let result: Result<time::Timespec> =
|
||||||
|
db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use super::{Null, Value, ValueRef};
|
use super::{Null, Value, ValueRef};
|
||||||
use std::borrow::Cow;
|
|
||||||
#[cfg(feature = "array")]
|
#[cfg(feature = "array")]
|
||||||
use vtab::array::Array;
|
use crate::vtab::array::Array;
|
||||||
use Result;
|
use crate::Result;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// `ToSqlOutput` represents the possible output types for implementors of the
|
/// `ToSqlOutput` represents the possible output types for implementors of the
|
||||||
/// `ToSql` trait.
|
/// `ToSql` trait.
|
||||||
@ -66,7 +66,7 @@ from_value!(Vec<u8>);
|
|||||||
from_value!(i128);
|
from_value!(i128);
|
||||||
|
|
||||||
impl<'a> ToSql for ToSqlOutput<'a> {
|
impl<'a> ToSql for ToSqlOutput<'a> {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(match *self {
|
Ok(match *self {
|
||||||
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
|
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
|
||||||
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
|
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
|
||||||
@ -81,7 +81,7 @@ impl<'a> ToSql for ToSqlOutput<'a> {
|
|||||||
|
|
||||||
/// A trait for types that can be converted into SQLite values.
|
/// A trait for types that can be converted into SQLite values.
|
||||||
pub trait ToSql {
|
pub trait ToSql {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput>;
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should be able to use a generic impl like this:
|
// We should be able to use a generic impl like this:
|
||||||
@ -99,7 +99,7 @@ pub trait ToSql {
|
|||||||
macro_rules! to_sql_self(
|
macro_rules! to_sql_self(
|
||||||
($t:ty) => (
|
($t:ty) => (
|
||||||
impl ToSql for $t {
|
impl ToSql for $t {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::from(*self))
|
Ok(ToSqlOutput::from(*self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,43 +125,43 @@ impl<'a, T: ?Sized> ToSql for &'a T
|
|||||||
where
|
where
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
{
|
{
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
(*self).to_sql()
|
(*self).to_sql()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToSql for String {
|
impl ToSql for String {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::from(self.as_str()))
|
Ok(ToSqlOutput::from(self.as_str()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToSql for str {
|
impl ToSql for str {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::from(self))
|
Ok(ToSqlOutput::from(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToSql for Vec<u8> {
|
impl ToSql for Vec<u8> {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::from(self.as_slice()))
|
Ok(ToSqlOutput::from(self.as_slice()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToSql for [u8] {
|
impl ToSql for [u8] {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::from(self))
|
Ok(ToSqlOutput::from(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToSql for Value {
|
impl ToSql for Value {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::from(self))
|
Ok(ToSqlOutput::from(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ToSql> ToSql for Option<T> {
|
impl<T: ToSql> ToSql for Option<T> {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
match *self {
|
match *self {
|
||||||
None => Ok(ToSqlOutput::from(Null)),
|
None => Ok(ToSqlOutput::from(Null)),
|
||||||
Some(ref t) => t.to_sql(),
|
Some(ref t) => t.to_sql(),
|
||||||
@ -170,7 +170,7 @@ impl<T: ToSql> ToSql for Option<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ToSql for Cow<'a, str> {
|
impl<'a> ToSql for Cow<'a, str> {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::from(self.as_ref()))
|
Ok(ToSqlOutput::from(self.as_ref()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,8 +207,8 @@ mod test {
|
|||||||
#[cfg(feature = "i128_blob")]
|
#[cfg(feature = "i128_blob")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_i128() {
|
fn test_i128() {
|
||||||
|
use crate::{Connection, NO_PARAMS};
|
||||||
use std::i128;
|
use std::i128;
|
||||||
use {Connection, NO_PARAMS};
|
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")
|
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::{Type, Value};
|
use super::{Type, Value};
|
||||||
use types::{FromSqlError, FromSqlResult};
|
use crate::types::{FromSqlError, FromSqlResult};
|
||||||
|
|
||||||
/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the
|
/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the
|
||||||
/// memory backing this value is owned by SQLite.
|
/// memory backing this value is owned by SQLite.
|
||||||
@ -70,7 +70,7 @@ impl<'a> ValueRef<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<ValueRef<'a>> for Value {
|
impl<'a> From<ValueRef<'a>> for Value {
|
||||||
fn from(borrowed: ValueRef) -> Value {
|
fn from(borrowed: ValueRef<'_>) -> Value {
|
||||||
match borrowed {
|
match borrowed {
|
||||||
ValueRef::Null => Value::Null,
|
ValueRef::Null => Value::Null,
|
||||||
ValueRef::Integer(i) => Value::Integer(i),
|
ValueRef::Integer(i) => Value::Integer(i),
|
||||||
@ -82,13 +82,13 @@ impl<'a> From<ValueRef<'a>> for Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a str> for ValueRef<'a> {
|
impl<'a> From<&'a str> for ValueRef<'a> {
|
||||||
fn from(s: &str) -> ValueRef {
|
fn from(s: &str) -> ValueRef<'_> {
|
||||||
ValueRef::Text(s)
|
ValueRef::Text(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a [u8]> for ValueRef<'a> {
|
impl<'a> From<&'a [u8]> for ValueRef<'a> {
|
||||||
fn from(s: &[u8]) -> ValueRef {
|
fn from(s: &[u8]) -> ValueRef<'_> {
|
||||||
ValueRef::Blob(s)
|
ValueRef::Blob(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,3 +104,56 @@ impl<'a> From<&'a Value> for ValueRef<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "functions", feature = "session", feature = "vtab"))]
|
||||||
|
impl<'a> ValueRef<'a> {
|
||||||
|
pub(crate) unsafe fn from_value(value: *mut crate::ffi::sqlite3_value) -> ValueRef<'a> {
|
||||||
|
use crate::ffi;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::slice::from_raw_parts;
|
||||||
|
|
||||||
|
match ffi::sqlite3_value_type(value) {
|
||||||
|
ffi::SQLITE_NULL => ValueRef::Null,
|
||||||
|
ffi::SQLITE_INTEGER => ValueRef::Integer(ffi::sqlite3_value_int64(value)),
|
||||||
|
ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
|
||||||
|
ffi::SQLITE_TEXT => {
|
||||||
|
let text = ffi::sqlite3_value_text(value);
|
||||||
|
assert!(
|
||||||
|
!text.is_null(),
|
||||||
|
"unexpected SQLITE_TEXT value type with NULL data"
|
||||||
|
);
|
||||||
|
let s = CStr::from_ptr(text as *const c_char);
|
||||||
|
|
||||||
|
// sqlite3_value_text returns UTF8 data, so our unwrap here should be fine.
|
||||||
|
let s = s
|
||||||
|
.to_str()
|
||||||
|
.expect("sqlite3_value_text returned invalid UTF-8");
|
||||||
|
ValueRef::Text(s)
|
||||||
|
}
|
||||||
|
ffi::SQLITE_BLOB => {
|
||||||
|
let (blob, len) = (
|
||||||
|
ffi::sqlite3_value_blob(value),
|
||||||
|
ffi::sqlite3_value_bytes(value),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
len >= 0,
|
||||||
|
"unexpected negative return from sqlite3_value_bytes"
|
||||||
|
);
|
||||||
|
if len > 0 {
|
||||||
|
assert!(
|
||||||
|
!blob.is_null(),
|
||||||
|
"unexpected SQLITE_BLOB value type with NULL data"
|
||||||
|
);
|
||||||
|
ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
|
||||||
|
} else {
|
||||||
|
// The return value from sqlite3_value_blob() for a zero-length BLOB
|
||||||
|
// is a NULL pointer.
|
||||||
|
ValueRef::Blob(&[])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!("sqlite3_value_type returned invalid value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,9 +4,11 @@ use std::os::raw::c_int;
|
|||||||
#[cfg(feature = "unlock_notify")]
|
#[cfg(feature = "unlock_notify")]
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
#[cfg(feature = "unlock_notify")]
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
use std::panic::catch_unwind;
|
||||||
|
#[cfg(feature = "unlock_notify")]
|
||||||
use std::sync::{Condvar, Mutex};
|
use std::sync::{Condvar, Mutex};
|
||||||
|
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
|
|
||||||
#[cfg(feature = "unlock_notify")]
|
#[cfg(feature = "unlock_notify")]
|
||||||
struct UnlockNotification {
|
struct UnlockNotification {
|
||||||
@ -42,8 +44,10 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) {
|
|||||||
use std::slice::from_raw_parts;
|
use std::slice::from_raw_parts;
|
||||||
let args = from_raw_parts(ap_arg, n_arg as usize);
|
let args = from_raw_parts(ap_arg, n_arg as usize);
|
||||||
for arg in args {
|
for arg in args {
|
||||||
let un: &mut UnlockNotification = &mut *(*arg as *mut UnlockNotification);
|
let _ = catch_unwind(|| {
|
||||||
un.fired();
|
let un: &mut UnlockNotification = &mut *(*arg as *mut UnlockNotification);
|
||||||
|
un.fired()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,10 +103,10 @@ pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int {
|
|||||||
#[cfg(feature = "unlock_notify")]
|
#[cfg(feature = "unlock_notify")]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior, NO_PARAMS};
|
||||||
use std::sync::mpsc::sync_channel;
|
use std::sync::mpsc::sync_channel;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time;
|
use std::time;
|
||||||
use {Connection, OpenFlags, Result, Transaction, TransactionBehavior, NO_PARAMS};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unlock_notify() {
|
fn test_unlock_notify() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use ffi;
|
use crate::ffi;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
|
||||||
/// Returns the SQLite version as an integer; e.g., `3016002` for version
|
/// Returns the SQLite version as an integer; e.g., `3016002` for version
|
||||||
|
@ -5,13 +5,13 @@ use std::default::Default;
|
|||||||
use std::os::raw::{c_char, c_int, c_void};
|
use std::os::raw::{c_char, c_int, c_void};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
use types::{ToSql, ToSqlOutput, Value};
|
use crate::types::{ToSql, ToSqlOutput, Value};
|
||||||
use vtab::{
|
use crate::vtab::{
|
||||||
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
|
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
|
||||||
VTabCursor, Values,
|
VTabCursor, Values,
|
||||||
};
|
};
|
||||||
use {Connection, Result};
|
use crate::{Connection, Result};
|
||||||
|
|
||||||
// http://sqlite.org/bindptr.html
|
// http://sqlite.org/bindptr.html
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
|
|||||||
pub type Array = Rc<Vec<Value>>;
|
pub type Array = Rc<Vec<Value>>;
|
||||||
|
|
||||||
impl ToSql for Array {
|
impl ToSql for Array {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok(ToSqlOutput::Array(self.clone()))
|
Ok(ToSqlOutput::Array(self.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,9 +129,9 @@ impl ArrayTabCursor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl VTabCursor for ArrayTabCursor {
|
impl VTabCursor for ArrayTabCursor {
|
||||||
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values) -> Result<()> {
|
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> {
|
||||||
if idx_num > 0 {
|
if idx_num > 0 {
|
||||||
self.ptr = try!(args.get_array(0));
|
self.ptr = args.get_array(0)?;
|
||||||
} else {
|
} else {
|
||||||
self.ptr = None;
|
self.ptr = None;
|
||||||
}
|
}
|
||||||
@ -169,10 +169,10 @@ impl VTabCursor for ArrayTabCursor {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::types::Value;
|
||||||
|
use crate::vtab::array;
|
||||||
|
use crate::Connection;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use types::Value;
|
|
||||||
use vtab::array;
|
|
||||||
use Connection;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_array_module() {
|
fn test_array_module() {
|
||||||
@ -180,7 +180,7 @@ mod test {
|
|||||||
array::load_module(&db).unwrap();
|
array::load_module(&db).unwrap();
|
||||||
|
|
||||||
let v = vec![1i64, 2, 3, 4];
|
let v = vec![1i64, 2, 3, 4];
|
||||||
let values = v.into_iter().map(|i| Value::from(i)).collect();
|
let values = v.into_iter().map(Value::from).collect();
|
||||||
let ptr = Rc::new(values);
|
let ptr = Rc::new(values);
|
||||||
{
|
{
|
||||||
let mut stmt = db.prepare("SELECT value from rarray(?);").unwrap();
|
let mut stmt = db.prepare("SELECT value from rarray(?);").unwrap();
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
//! CSV Virtual Table.
|
//! CSV Virtual Table.
|
||||||
//!
|
//!
|
||||||
//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C extension.
|
//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C extension.
|
||||||
extern crate csv;
|
use csv;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
use types::Null;
|
use crate::types::Null;
|
||||||
use vtab::{
|
use crate::vtab::{
|
||||||
dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
|
dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
|
||||||
Module, VTab, VTabConnection, VTabCursor, Values,
|
Module, VTab, VTabConnection, VTabCursor, Values,
|
||||||
};
|
};
|
||||||
use {Connection, Error, Result};
|
use crate::{Connection, Error, Result};
|
||||||
|
|
||||||
/// Register the "csv" module.
|
/// Register the "csv" module.
|
||||||
/// ```sql
|
/// ```sql
|
||||||
@ -60,7 +60,7 @@ impl CSVTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
|
fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
|
||||||
let arg = try!(str::from_utf8(c_slice)).trim();
|
let arg = str::from_utf8(c_slice)?.trim();
|
||||||
let mut split = arg.split('=');
|
let mut split = arg.split('=');
|
||||||
if let Some(key) = split.next() {
|
if let Some(key) = split.next() {
|
||||||
if let Some(value) = split.next() {
|
if let Some(value) = split.next() {
|
||||||
@ -107,7 +107,7 @@ impl VTab for CSVTab {
|
|||||||
|
|
||||||
let args = &args[3..];
|
let args = &args[3..];
|
||||||
for c_slice in args {
|
for c_slice in args {
|
||||||
let (param, value) = try!(CSVTab::parameter(c_slice));
|
let (param, value) = CSVTab::parameter(c_slice)?;
|
||||||
match param {
|
match param {
|
||||||
"filename" => {
|
"filename" => {
|
||||||
if !Path::new(value).exists() {
|
if !Path::new(value).exists() {
|
||||||
@ -189,10 +189,10 @@ impl VTab for CSVTab {
|
|||||||
|
|
||||||
let mut cols: Vec<String> = Vec::new();
|
let mut cols: Vec<String> = Vec::new();
|
||||||
if vtab.has_headers || (n_col.is_none() && schema.is_none()) {
|
if vtab.has_headers || (n_col.is_none() && schema.is_none()) {
|
||||||
let mut reader = try!(vtab.reader());
|
let mut reader = vtab.reader()?;
|
||||||
if vtab.has_headers {
|
if vtab.has_headers {
|
||||||
{
|
{
|
||||||
let headers = try!(reader.headers());
|
let headers = reader.headers()?;
|
||||||
// headers ignored if cols is not empty
|
// headers ignored if cols is not empty
|
||||||
if n_col.is_none() && schema.is_none() {
|
if n_col.is_none() && schema.is_none() {
|
||||||
cols = headers
|
cols = headers
|
||||||
@ -204,7 +204,7 @@ impl VTab for CSVTab {
|
|||||||
vtab.offset_first_row = reader.position().clone();
|
vtab.offset_first_row = reader.position().clone();
|
||||||
} else {
|
} else {
|
||||||
let mut record = csv::ByteRecord::new();
|
let mut record = csv::ByteRecord::new();
|
||||||
if try!(reader.read_byte_record(&mut record)) {
|
if reader.read_byte_record(&mut record)? {
|
||||||
for (i, _) in record.iter().enumerate() {
|
for (i, _) in record.iter().enumerate() {
|
||||||
cols.push(format!("c{}", i));
|
cols.push(format!("c{}", i));
|
||||||
}
|
}
|
||||||
@ -245,7 +245,7 @@ impl VTab for CSVTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn open(&self) -> Result<CSVTabCursor> {
|
fn open(&self) -> Result<CSVTabCursor> {
|
||||||
Ok(CSVTabCursor::new(try!(self.reader())))
|
Ok(CSVTabCursor::new(self.reader()?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,10 +285,15 @@ impl CSVTabCursor {
|
|||||||
impl VTabCursor for CSVTabCursor {
|
impl VTabCursor for CSVTabCursor {
|
||||||
// Only a full table scan is supported. So `filter` simply rewinds to
|
// Only a full table scan is supported. So `filter` simply rewinds to
|
||||||
// the beginning.
|
// the beginning.
|
||||||
fn filter(&mut self, _idx_num: c_int, _idx_str: Option<&str>, _args: &Values) -> Result<()> {
|
fn filter(
|
||||||
|
&mut self,
|
||||||
|
_idx_num: c_int,
|
||||||
|
_idx_str: Option<&str>,
|
||||||
|
_args: &Values<'_>,
|
||||||
|
) -> Result<()> {
|
||||||
{
|
{
|
||||||
let offset_first_row = self.vtab().offset_first_row.clone();
|
let offset_first_row = self.vtab().offset_first_row.clone();
|
||||||
try!(self.reader.seek(offset_first_row));
|
self.reader.seek(offset_first_row)?;
|
||||||
}
|
}
|
||||||
self.row_number = 0;
|
self.row_number = 0;
|
||||||
self.next()
|
self.next()
|
||||||
@ -301,7 +306,7 @@ impl VTabCursor for CSVTabCursor {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.eof = !try!(self.reader.read_record(&mut self.cols));
|
self.eof = !self.reader.read_record(&mut self.cols)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.row_number += 1;
|
self.row_number += 1;
|
||||||
@ -340,8 +345,8 @@ impl From<csv::Error> for Error {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use vtab::csvtab;
|
use crate::vtab::csvtab;
|
||||||
use {Connection, Result, NO_PARAMS};
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_csv_module() {
|
fn test_csv_module() {
|
||||||
@ -361,7 +366,7 @@ mod test {
|
|||||||
.query_map(NO_PARAMS, |row| row.get::<_, i32>(0))
|
.query_map(NO_PARAMS, |row| row.get::<_, i32>(0))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.collect();
|
.collect();
|
||||||
let sum = ids.unwrap().iter().fold(0, |acc, &id| acc + id);
|
let sum = ids.unwrap().iter().sum::<i32>();
|
||||||
assert_eq!(sum, 15);
|
assert_eq!(sum, 15);
|
||||||
}
|
}
|
||||||
db.execute_batch("DROP TABLE vtab").unwrap();
|
db.execute_batch("DROP TABLE vtab").unwrap();
|
||||||
|
@ -17,12 +17,12 @@ use std::os::raw::{c_char, c_int, c_void};
|
|||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
use context::set_result;
|
use crate::context::set_result;
|
||||||
use error::error_from_sqlite_code;
|
use crate::error::error_from_sqlite_code;
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
pub use ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
|
pub use crate::ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
|
||||||
use types::{FromSql, FromSqlError, ToSql, ValueRef};
|
use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
|
||||||
use {str_to_cstring, Connection, Error, InnerConnection, Result};
|
use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
|
||||||
|
|
||||||
// let conn: Connection = ...;
|
// let conn: Connection = ...;
|
||||||
// let mod: Module = ...; // VTab builder
|
// let mod: Module = ...; // VTab builder
|
||||||
@ -99,6 +99,8 @@ pub fn read_only_module<T: CreateVTab>(version: c_int) -> Module<T> {
|
|||||||
xSavepoint: None,
|
xSavepoint: None,
|
||||||
xRelease: None,
|
xRelease: None,
|
||||||
xRollbackTo: None,
|
xRollbackTo: None,
|
||||||
|
#[cfg(any(feature = "bundled", feature = "vtab_v3"))]
|
||||||
|
xShadowName: None,
|
||||||
};
|
};
|
||||||
Module {
|
Module {
|
||||||
base: ffi_module,
|
base: ffi_module,
|
||||||
@ -137,6 +139,8 @@ pub fn eponymous_only_module<T: VTab>(version: c_int) -> Module<T> {
|
|||||||
xSavepoint: None,
|
xSavepoint: None,
|
||||||
xRelease: None,
|
xRelease: None,
|
||||||
xRollbackTo: None,
|
xRollbackTo: None,
|
||||||
|
#[cfg(any(feature = "bundled", feature = "vtab_v3"))]
|
||||||
|
xShadowName: None,
|
||||||
};
|
};
|
||||||
Module {
|
Module {
|
||||||
base: ffi_module,
|
base: ffi_module,
|
||||||
@ -250,7 +254,7 @@ pub struct IndexInfo(*mut ffi::sqlite3_index_info);
|
|||||||
|
|
||||||
impl IndexInfo {
|
impl IndexInfo {
|
||||||
/// Record WHERE clause constraints.
|
/// Record WHERE clause constraints.
|
||||||
pub fn constraints(&self) -> IndexConstraintIter {
|
pub fn constraints(&self) -> IndexConstraintIter<'_> {
|
||||||
let constraints =
|
let constraints =
|
||||||
unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
|
unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
|
||||||
IndexConstraintIter {
|
IndexConstraintIter {
|
||||||
@ -259,7 +263,7 @@ impl IndexInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Information about the ORDER BY clause.
|
/// Information about the ORDER BY clause.
|
||||||
pub fn order_bys(&self) -> OrderByIter {
|
pub fn order_bys(&self) -> OrderByIter<'_> {
|
||||||
let order_bys =
|
let order_bys =
|
||||||
unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) };
|
unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) };
|
||||||
OrderByIter {
|
OrderByIter {
|
||||||
@ -272,7 +276,7 @@ impl IndexInfo {
|
|||||||
unsafe { (*self.0).nOrderBy as usize }
|
unsafe { (*self.0).nOrderBy as usize }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage {
|
pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage<'_> {
|
||||||
let constraint_usages = unsafe {
|
let constraint_usages = unsafe {
|
||||||
slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
|
slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
|
||||||
};
|
};
|
||||||
@ -412,7 +416,7 @@ impl<'a> OrderBy<'a> {
|
|||||||
pub trait VTabCursor: Sized {
|
pub trait VTabCursor: Sized {
|
||||||
/// Begin a search of a virtual table.
|
/// Begin a search of a virtual table.
|
||||||
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xfilter_method))
|
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xfilter_method))
|
||||||
fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Values) -> Result<()>;
|
fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Values<'_>) -> Result<()>;
|
||||||
/// Advance cursor to the next row of a result set initiated by `filter`.
|
/// Advance cursor to the next row of a result set initiated by `filter`.
|
||||||
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method))
|
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method))
|
||||||
fn next(&mut self) -> Result<()>;
|
fn next(&mut self) -> Result<()>;
|
||||||
@ -467,6 +471,8 @@ impl<'a> Values<'a> {
|
|||||||
Error::FromSqlConversionFailure(idx, value.data_type(), err)
|
Error::FromSqlConversionFailure(idx, value.data_type(), err)
|
||||||
}
|
}
|
||||||
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,7 +480,7 @@ impl<'a> Values<'a> {
|
|||||||
// So it seems not possible to enhance `ValueRef::from_value`.
|
// So it seems not possible to enhance `ValueRef::from_value`.
|
||||||
#[cfg(feature = "array")]
|
#[cfg(feature = "array")]
|
||||||
pub(crate) fn get_array(&self, idx: usize) -> Result<Option<array::Array>> {
|
pub(crate) fn get_array(&self, idx: usize) -> Result<Option<array::Array>> {
|
||||||
use types::Value;
|
use crate::types::Value;
|
||||||
let arg = self.args[idx];
|
let arg = self.args[idx];
|
||||||
let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) };
|
let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) };
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
@ -489,7 +495,7 @@ impl<'a> Values<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> ValueIter {
|
pub fn iter(&self) -> ValueIter<'_> {
|
||||||
ValueIter {
|
ValueIter {
|
||||||
iter: self.args.iter(),
|
iter: self.args.iter(),
|
||||||
}
|
}
|
||||||
@ -544,7 +550,7 @@ impl InnerConnection {
|
|||||||
module: &Module<T>,
|
module: &Module<T>,
|
||||||
aux: Option<T::Aux>,
|
aux: Option<T::Aux>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let c_name = try!(str_to_cstring(module_name));
|
let c_name = str_to_cstring(module_name)?;
|
||||||
let r = match aux {
|
let r = match aux {
|
||||||
Some(aux) => {
|
Some(aux) => {
|
||||||
let boxed_aux: *mut T::Aux = Box::into_raw(Box::new(aux));
|
let boxed_aux: *mut T::Aux = Box::into_raw(Box::new(aux));
|
||||||
@ -573,7 +579,7 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Escape double-quote (`"`) character occurences by doubling them (`""`).
|
/// Escape double-quote (`"`) character occurences by doubling them (`""`).
|
||||||
pub fn escape_double_quote(identifier: &str) -> Cow<str> {
|
pub fn escape_double_quote(identifier: &str) -> Cow<'_, str> {
|
||||||
if identifier.contains('"') {
|
if identifier.contains('"') {
|
||||||
// escape quote by doubling them
|
// escape quote by doubling them
|
||||||
Owned(identifier.replace("\"", "\"\""))
|
Owned(identifier.replace("\"", "\"\""))
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
use types::Type;
|
use crate::types::Type;
|
||||||
use vtab::{
|
use crate::vtab::{
|
||||||
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
|
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
|
||||||
VTabCursor, Values,
|
VTabCursor, Values,
|
||||||
};
|
};
|
||||||
use {Connection, Result};
|
use crate::{Connection, Result};
|
||||||
|
|
||||||
/// Register the "generate_series" module.
|
/// Register the "generate_series" module.
|
||||||
pub fn load_module(conn: &Connection) -> Result<()> {
|
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||||
@ -184,23 +184,23 @@ impl SeriesTabCursor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl VTabCursor for SeriesTabCursor {
|
impl VTabCursor for SeriesTabCursor {
|
||||||
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values) -> Result<()> {
|
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> {
|
||||||
let idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
|
let idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
if idx_num.contains(QueryPlanFlags::START) {
|
if idx_num.contains(QueryPlanFlags::START) {
|
||||||
self.min_value = try!(args.get(i));
|
self.min_value = args.get(i)?;
|
||||||
i += 1;
|
i += 1;
|
||||||
} else {
|
} else {
|
||||||
self.min_value = 0;
|
self.min_value = 0;
|
||||||
}
|
}
|
||||||
if idx_num.contains(QueryPlanFlags::STOP) {
|
if idx_num.contains(QueryPlanFlags::STOP) {
|
||||||
self.max_value = try!(args.get(i));
|
self.max_value = args.get(i)?;
|
||||||
i += 1;
|
i += 1;
|
||||||
} else {
|
} else {
|
||||||
self.max_value = 0xffff_ffff;
|
self.max_value = 0xffff_ffff;
|
||||||
}
|
}
|
||||||
if idx_num.contains(QueryPlanFlags::STEP) {
|
if idx_num.contains(QueryPlanFlags::STEP) {
|
||||||
self.step = try!(args.get(i));
|
self.step = args.get(i)?;
|
||||||
if self.step < 1 {
|
if self.step < 1 {
|
||||||
self.step = 1;
|
self.step = 1;
|
||||||
}
|
}
|
||||||
@ -263,14 +263,14 @@ impl VTabCursor for SeriesTabCursor {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use ffi;
|
use crate::ffi;
|
||||||
use vtab::series;
|
use crate::vtab::series;
|
||||||
use {Connection, NO_PARAMS};
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_series_module() {
|
fn test_series_module() {
|
||||||
let version = unsafe { ffi::sqlite3_libversion_number() };
|
let version = unsafe { ffi::sqlite3_libversion_number() };
|
||||||
if version < 3008012 {
|
if version < 3_008_012 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
extern crate rusqlite;
|
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
//! Ensure we reject connections when SQLite is in single-threaded mode, as it
|
//! Ensure we reject connections when SQLite is in single-threaded mode, as it
|
||||||
//! would violate safety if multiple Rust threads tried to use connections.
|
//! would violate safety if multiple Rust threads tried to use connections.
|
||||||
|
|
||||||
extern crate libsqlite3_sys as ffi;
|
use rusqlite::ffi;
|
||||||
extern crate rusqlite;
|
|
||||||
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
//! Ensure Virtual tables can be declared outside `rusqlite` crate.
|
//! Ensure Virtual tables can be declared outside `rusqlite` crate.
|
||||||
|
|
||||||
#[cfg(feature = "vtab")]
|
|
||||||
extern crate rusqlite;
|
|
||||||
|
|
||||||
#[cfg(feature = "vtab")]
|
#[cfg(feature = "vtab")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dummy_module() {
|
fn test_dummy_module() {
|
||||||
@ -61,7 +58,7 @@ fn test_dummy_module() {
|
|||||||
&mut self,
|
&mut self,
|
||||||
_idx_num: c_int,
|
_idx_num: c_int,
|
||||||
_idx_str: Option<&str>,
|
_idx_str: Option<&str>,
|
||||||
_args: &Values,
|
_args: &Values<'_>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.row_id = 1;
|
self.row_id = 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -91,14 +88,14 @@ fn test_dummy_module() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let version = version_number();
|
let version = version_number();
|
||||||
if version < 3008012 {
|
if version < 3_008_012 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut s = db.prepare("SELECT * FROM dummy()").unwrap();
|
let mut s = db.prepare("SELECT * FROM dummy()").unwrap();
|
||||||
|
|
||||||
let dummy = s
|
let dummy = s
|
||||||
.query_row(&[] as &[&ToSql], |row| row.get::<_, i32>(0))
|
.query_row(&[] as &[&dyn ToSql], |row| row.get::<_, i32>(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(1, dummy);
|
assert_eq!(1, dummy);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user