mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-25 02:21:37 +08:00
Merge remote-tracking branch 'jgallagher/master' into cant-open
This commit is contained in:
commit
15f356e5a7
23
.travis.yml
23
.travis.yml
@ -1,5 +1,4 @@
|
|||||||
sudo: false
|
sudo: false
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
language: rust
|
language: rust
|
||||||
|
|
||||||
@ -9,6 +8,7 @@ rust:
|
|||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
|
|
||||||
@ -26,18 +26,23 @@ script:
|
|||||||
- cargo build
|
- cargo build
|
||||||
- cargo build --features bundled
|
- cargo build --features bundled
|
||||||
- cargo build --features sqlcipher
|
- cargo build --features sqlcipher
|
||||||
|
- cargo build --features "bundled sqlcipher"
|
||||||
- cargo test
|
- cargo test
|
||||||
- cargo test --features backup
|
- cargo test --features "backup blob extra_check"
|
||||||
- cargo test --features blob
|
- cargo test --features "collation functions"
|
||||||
- cargo test --features functions
|
- cargo test --features "hooks limits"
|
||||||
- cargo test --features limits
|
|
||||||
- cargo test --features load_extension
|
- cargo test --features load_extension
|
||||||
- cargo test --features trace
|
- cargo test --features trace
|
||||||
- cargo test --features chrono
|
- cargo test --features chrono
|
||||||
- cargo test --features serde_json
|
- cargo test --features serde_json
|
||||||
|
- cargo test --features url
|
||||||
- cargo test --features bundled
|
- cargo test --features bundled
|
||||||
- cargo test --features sqlcipher
|
- cargo test --features sqlcipher
|
||||||
- cargo test --features "backup blob chrono functions limits load_extension serde_json trace"
|
- cargo test --features i128_blob
|
||||||
- cargo test --features "backup blob chrono functions limits load_extension serde_json trace buildtime_bindgen"
|
- cargo test --features uuid
|
||||||
- cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled"
|
- cargo test --features "bundled unlock_notify window"
|
||||||
- cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled buildtime_bindgen"
|
- cargo test --features "array bundled csvtab series vtab"
|
||||||
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab"
|
||||||
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab buildtime_bindgen"
|
||||||
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled"
|
||||||
|
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled buildtime_bindgen"
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
rusqlite contributors
|
|
||||||
=====================
|
|
||||||
|
|
||||||
* [John Gallagher](https://github.com/jgallagher)
|
|
||||||
* [Marcus Klaas de Vries](https://github.com/marcusklaas)
|
|
||||||
* [gwenn](https://github.com/gwenn)
|
|
||||||
* [Jimmy Lu](https://github.com/Yuhta)
|
|
||||||
* [Huon Wilson](https://github.com/huonw)
|
|
||||||
* [Patrick Fernie](https://github.com/pfernie)
|
|
||||||
* [Steve Klabnik](https://github.com/steveklabnik)
|
|
||||||
* [krdln](https://github.com/krdln)
|
|
||||||
* [Ben Striegel](https://github.com/bstrie)
|
|
||||||
* [Andrew Straw](https://github.com/astraw)
|
|
||||||
* [Ronald Kinard](https://github.com/Furyhunter)
|
|
||||||
* [maciejkula](https://github.com/maciejkula)
|
|
||||||
* [Xidorn Quan](https://github.com/upsuper)
|
|
||||||
* [Chip Collier](https://github.com/photex)
|
|
||||||
* [Omar Ferrer](https://github.com/chamakits)
|
|
||||||
* [Lee Jenkins](https://github.com/reddraggone9)
|
|
||||||
* [miedzinski](https://github.com/miedzinski)
|
|
||||||
* [aidanhs](https://github.com/aidanhs)
|
|
||||||
* [Steven Fackler](https://github.com/sfackler)
|
|
||||||
* [Davide Aversa](https://github.com/THeK3nger)
|
|
||||||
* [mcgoo](https://github.com/mcgoo)
|
|
||||||
* [Kelvin Ly](https://github.com/cactorium)
|
|
||||||
* [king6cong](https://github.com/king6cong)
|
|
||||||
* [Andy Russell](https://github.com/euclio)
|
|
||||||
* [Tristan Lostroh](https://github.com/tl8roy)
|
|
||||||
* [Ossi Herrala](https://github.com/oherrala)
|
|
||||||
* [Vojtech Cima](https://github.com/vojtechcima)
|
|
||||||
* [Louis Garczynski](https://github.com/lgarczyn)
|
|
||||||
* [twistedfall](https://github.com/twistedfall)
|
|
||||||
* [Nick Fitzgerald](https://github.com/fitzgen)
|
|
||||||
* [Lorenzo Villani](https://github.com/lvillani)
|
|
57
Cargo.toml
57
Cargo.toml
@ -1,7 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.13.0"
|
version = "0.20.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/"
|
||||||
@ -13,20 +14,47 @@ categories = ["database"]
|
|||||||
[badges]
|
[badges]
|
||||||
travis-ci = { repository = "jgallagher/rusqlite" }
|
travis-ci = { repository = "jgallagher/rusqlite" }
|
||||||
appveyor = { repository = "jgallagher/rusqlite" }
|
appveyor = { repository = "jgallagher/rusqlite" }
|
||||||
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["libsqlite3-sys"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
load_extension = []
|
load_extension = []
|
||||||
backup = ["libsqlite3-sys/min_sqlite_version_3_6_11"]
|
# hot-backup interface: 3.6.11 (2009-02-18)
|
||||||
blob = ["libsqlite3-sys/min_sqlite_version_3_7_4"]
|
backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
|
||||||
functions = ["libsqlite3-sys/min_sqlite_version_3_7_3"]
|
# sqlite3_blob_reopen: 3.7.4
|
||||||
|
blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
||||||
|
collation = []
|
||||||
|
# sqlite3_create_function_v2: 3.7.3 (2010-10-08)
|
||||||
|
functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
|
||||||
|
# sqlite3_log: 3.6.23 (2010-03-09)
|
||||||
trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
|
trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
|
||||||
bundled = ["libsqlite3-sys/bundled"]
|
bundled = ["libsqlite3-sys/bundled"]
|
||||||
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
|
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
|
||||||
limits = []
|
limits = []
|
||||||
|
hooks = []
|
||||||
|
i128_blob = ["byteorder"]
|
||||||
sqlcipher = ["libsqlite3-sys/sqlcipher"]
|
sqlcipher = ["libsqlite3-sys/sqlcipher"]
|
||||||
|
unlock_notify = ["libsqlite3-sys/unlock_notify"]
|
||||||
|
# xSavepoint, xRelease and xRollbackTo: 3.7.7 (2011-06-23)
|
||||||
|
vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"]
|
||||||
|
# xShadowName: 3.26.0
|
||||||
|
vtab_v3 = ["vtab"]
|
||||||
|
csvtab = ["csv", "vtab"]
|
||||||
|
# pointer passing interfaces: 3.20.0
|
||||||
|
array = ["vtab"]
|
||||||
|
# session extension: 3.13.0
|
||||||
|
#session = ["libsqlite3-sys/session", "hooks"]
|
||||||
|
# window functions: 3.25.0
|
||||||
|
window = ["functions"]
|
||||||
|
# 3.9.0
|
||||||
|
series = ["vtab"]
|
||||||
|
# check for invalid query.
|
||||||
|
extra_check = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
time = "0.1.0"
|
time = "0.1.0"
|
||||||
@ -34,15 +62,25 @@ bitflags = "1.0"
|
|||||||
lru-cache = "0.1"
|
lru-cache = "0.1"
|
||||||
chrono = { version = "0.4", optional = true }
|
chrono = { version = "0.4", optional = true }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0", optional = true }
|
||||||
|
csv = { version = "1.0", optional = true }
|
||||||
|
url = { version = "2.0", optional = true }
|
||||||
|
lazy_static = { version = "1.0", optional = true }
|
||||||
|
byteorder = { version = "1.2", features = ["i128"], optional = true }
|
||||||
|
fallible-iterator = "0.2"
|
||||||
|
fallible-streaming-iterator = "0.1"
|
||||||
|
memchr = "2.2.0"
|
||||||
|
uuid = { version = "0.8", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
lazy_static = "0.2"
|
lazy_static = "1.0"
|
||||||
regex = "0.2"
|
regex = "1.0"
|
||||||
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
|
unicase = "2.4.0"
|
||||||
|
|
||||||
[dependencies.libsqlite3-sys]
|
[dependencies.libsqlite3-sys]
|
||||||
path = "libsqlite3-sys"
|
path = "libsqlite3-sys"
|
||||||
version = "0.9"
|
version = "0.16"
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "config_log"
|
name = "config_log"
|
||||||
@ -51,8 +89,11 @@ harness = false
|
|||||||
[[test]]
|
[[test]]
|
||||||
name = "deny_single_threaded_sqlite_config"
|
name = "deny_single_threaded_sqlite_config"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "vtab"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace" ]
|
features = [ "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "trace", "url", "vtab", "window" ]
|
||||||
all-features = false
|
all-features = false
|
||||||
no-default-features = true
|
no-default-features = true
|
||||||
default-target = "x86_64-unknown-linux-gnu"
|
default-target = "x86_64-unknown-linux-gnu"
|
||||||
|
28
Changelog.md
28
Changelog.md
@ -1,3 +1,31 @@
|
|||||||
|
For version 0.15.0 and above, see [Releases](https://github.com/jgallagher/rusqlite/releases) page.
|
||||||
|
|
||||||
|
# Version 0.14.0 (2018-08-17)
|
||||||
|
|
||||||
|
* BREAKING CHANGE: `ToSql` implementation for `time::Timespec` uses RFC 3339 (%Y-%m-%dT%H:%M:%S.%fZ).
|
||||||
|
Previous format was %Y-%m-%d %H:%M:%S:%f %Z.
|
||||||
|
* BREAKING CHANGE: Remove potentially conflicting impl of ToSqlOutput (#313).
|
||||||
|
* BREAKING CHANGE: Replace column index/count type (i32) with usize.
|
||||||
|
* BREAKING CHANGE: Replace parameter index/count type (i32) with usize.
|
||||||
|
* BREAKING CHANGE: Replace row changes/count type (i32) with usize.
|
||||||
|
* BREAKING CHANGE: Scalar functions must be `Send`able and `'static`.
|
||||||
|
* Bugfix: Commit failure unhandled, database left in unusable state (#366).
|
||||||
|
* Bugfix: `free_boxed_hook` does not work for `fn`.
|
||||||
|
* Update the bundled SQLite version to 3.24.0 (#326).
|
||||||
|
* Add DropBehavior::Panic to enforce intentional commit or rollback.
|
||||||
|
* Implement `sqlite3_update_hook` (#260, #328), `sqlite3_commit_hook` and `sqlite3_rollback_hook`.
|
||||||
|
* Add support to unlock notification behind `unlock_notify` feature (#294, #331).
|
||||||
|
* Make `Statement::column_index` case insensitive (#330).
|
||||||
|
* Add comment to justify `&mut Connection` in `Transaction`.
|
||||||
|
* Fix `tyvar_behind_raw_pointer` warnings.
|
||||||
|
* Fix handful of clippy warnings.
|
||||||
|
* Fix `Connection::open` documentation (#332)
|
||||||
|
* Add binding to `sqlite3_get_autocommit` and `sqlite3_stmt_busy`.
|
||||||
|
* Add binding to `sqlite3_busy_timeout` and `sqlite3_busy_handler`.
|
||||||
|
* Add binding to `sqlite3_expanded_sql`.
|
||||||
|
* Use `rerun-if-env-changed` in libsqlite3-sys (#329).
|
||||||
|
* Return an `InvalidQuery` error when SQL is not read only.
|
||||||
|
|
||||||
# Version 0.13.0 (2017-11-13)
|
# Version 0.13.0 (2017-11-13)
|
||||||
|
|
||||||
* Added ToSqlConversionFailure case to Error enum.
|
* Added ToSqlConversionFailure case to Error enum.
|
||||||
|
101
README.md
101
README.md
@ -1,59 +1,65 @@
|
|||||||
# Rusqlite
|
# Rusqlite
|
||||||
|
|
||||||
[![Travis Build Status](https://api.travis-ci.org/jgallagher/rusqlite.svg?branch=master)](https://travis-ci.org/jgallagher/rusqlite)
|
[![Travis Build Status](https://api.travis-ci.org/jgallagher/rusqlite.svg?branch=master)](https://travis-ci.org/jgallagher/rusqlite)
|
||||||
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/jgallagher/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/jgallagher/rusqlite) [![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite)
|
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/jgallagher/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/jgallagher/rusqlite)
|
||||||
|
[![dependency status](https://deps.rs/repo/github/jgallagher/rusqlite/status.svg)](https://deps.rs/repo/github/jgallagher/rusqlite)
|
||||||
|
[![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite)
|
||||||
|
[![Docs](https://docs.rs/rusqlite/badge.svg)](https://docs.rs/rusqlite)
|
||||||
|
|
||||||
Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
|
Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
|
||||||
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). View the full
|
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
|
||||||
[API documentation](http://docs.rs/rusqlite/).
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern crate rusqlite;
|
use rusqlite::types::ToSql;
|
||||||
extern crate time;
|
use rusqlite::{Connection, Result, NO_PARAMS};
|
||||||
|
|
||||||
use time::Timespec;
|
use time::Timespec;
|
||||||
use rusqlite::Connection;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Person {
|
struct Person {
|
||||||
id: i32,
|
id: i32,
|
||||||
name: String,
|
name: String,
|
||||||
time_created: Timespec,
|
time_created: Timespec,
|
||||||
data: Option<Vec<u8>>
|
data: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<()> {
|
||||||
let conn = Connection::open_in_memory().unwrap();
|
let conn = Connection::open_in_memory()?;
|
||||||
|
|
||||||
conn.execute("CREATE TABLE person (
|
conn.execute(
|
||||||
|
"CREATE TABLE person (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
time_created TEXT NOT NULL,
|
time_created TEXT NOT NULL,
|
||||||
data BLOB
|
data BLOB
|
||||||
)", &[]).unwrap();
|
)",
|
||||||
|
NO_PARAMS,
|
||||||
|
)?;
|
||||||
let me = Person {
|
let me = Person {
|
||||||
id: 0,
|
id: 0,
|
||||||
name: "Steven".to_string(),
|
name: "Steven".to_string(),
|
||||||
time_created: time::get_time(),
|
time_created: time::get_time(),
|
||||||
data: None
|
data: None,
|
||||||
};
|
};
|
||||||
conn.execute("INSERT INTO person (name, time_created, data)
|
conn.execute(
|
||||||
|
"INSERT INTO person (name, time_created, data)
|
||||||
VALUES (?1, ?2, ?3)",
|
VALUES (?1, ?2, ?3)",
|
||||||
&[&me.name, &me.time_created, &me.data]).unwrap();
|
&[&me.name as &ToSql, &me.time_created, &me.data],
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person").unwrap();
|
let mut stmt = conn
|
||||||
let person_iter = stmt.query_map(&[], |row| {
|
.prepare("SELECT id, name, time_created, data FROM person")?;
|
||||||
Person {
|
let person_iter = stmt
|
||||||
id: row.get(0),
|
.query_map(NO_PARAMS, |row| Ok(Person {
|
||||||
name: row.get(1),
|
id: row.get(0)?,
|
||||||
time_created: row.get(2),
|
name: row.get(1)?,
|
||||||
data: row.get(3)
|
time_created: row.get(2)?,
|
||||||
}
|
data: row.get(3)?,
|
||||||
}).unwrap();
|
}))?;
|
||||||
|
|
||||||
for person in person_iter {
|
for person in person_iter {
|
||||||
println!("Found person {:?}", person.unwrap());
|
println!("Found person {:?}", person.unwrap());
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -66,31 +72,42 @@ newer SQLite version; see details below.
|
|||||||
### Optional Features
|
### Optional Features
|
||||||
|
|
||||||
Rusqlite provides several features that are behind [Cargo
|
Rusqlite provides several features that are behind [Cargo
|
||||||
features](http://doc.crates.io/manifest.html#the-features-section). They are:
|
features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are:
|
||||||
|
|
||||||
* [`load_extension`](http://jgallagher.github.io/rusqlite/rusqlite/struct.LoadExtensionGuard.html)
|
* [`load_extension`](https://docs.rs/rusqlite/~0/rusqlite/struct.LoadExtensionGuard.html)
|
||||||
allows loading dynamic library-based SQLite extensions.
|
allows loading dynamic library-based SQLite extensions.
|
||||||
* [`backup`](http://jgallagher.github.io/rusqlite/rusqlite/backup/index.html)
|
* [`backup`](https://docs.rs/rusqlite/~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`](http://jgallagher.github.io/rusqlite/rusqlite/functions/index.html)
|
* [`functions`](https://docs.rs/rusqlite/~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`](http://jgallagher.github.io/rusqlite/rusqlite/trace/index.html)
|
* [`trace`](https://docs.rs/rusqlite/~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`](http://jgallagher.github.io/rusqlite/rusqlite/blob/index.html)
|
* [`blob`](https://docs.rs/rusqlite/~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`](http://jgallagher.github.io/rusqlite/rusqlite/struct.Connection.html#method.limit)
|
* [`limits`](https://docs.rs/rusqlite/~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`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.FromSql.html)
|
* `chrono` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
||||||
and [`ToSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.ToSql.html) for various
|
and [`ToSql`](https://docs.rs/rusqlite/~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`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.FromSql.html)
|
* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
||||||
and [`ToSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.ToSql.html) for the
|
and [`ToSql`](https://docs.rs/rusqlite/~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).
|
||||||
|
* `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
|
||||||
|
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the
|
||||||
|
`Url` type from the [`url` crate](https://crates.io/crates/url).
|
||||||
* `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`.
|
||||||
|
* `hooks` for [Commit, Rollback](http://sqlite.org/c3ref/commit_hook.html) and [Data Change](http://sqlite.org/c3ref/update_hook.html) notification callbacks.
|
||||||
|
* `unlock_notify` for [Unlock](https://sqlite.org/unlock_notify.html) notification.
|
||||||
|
* `vtab` for [virtual table](https://sqlite.org/vtab.html) support (allows you to write virtual table implemntations in Rust). Currently, only read-only virtual tables are supported.
|
||||||
|
* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
|
||||||
|
* [`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.
|
||||||
|
* `uuid` allows storing and retrieving `Uuid` values from the [`uuid`](https://docs.rs/uuid/) crate using blobs.
|
||||||
|
* [`session`](https://sqlite.org/sessionintro.html), Session module extension.
|
||||||
|
|
||||||
## Notes on building rusqlite and libsqlite3-sys
|
## Notes on building rusqlite and libsqlite3-sys
|
||||||
|
|
||||||
@ -103,11 +120,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.17.0 (as of `rusqlite` 0.10.1 / `libsqlite3-sys`
|
is currently SQLite 3.29.0 (as of `rusqlite` 0.20.0 / `libsqlite3-sys`
|
||||||
0.7.1). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
|
0.16.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.11.0"
|
version = "0.20.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
|
||||||
@ -115,7 +132,9 @@ You can adjust this behavior in a number of ways:
|
|||||||
* Installing the sqlite3 development packages will usually be all that is required, but
|
* Installing the sqlite3 development packages will usually be all that is required, but
|
||||||
the build helpers for [pkg-config](https://github.com/alexcrichton/pkg-config-rs)
|
the build helpers for [pkg-config](https://github.com/alexcrichton/pkg-config-rs)
|
||||||
and [vcpkg](https://github.com/mcgoo/vcpkg-rs) have some additional configuration
|
and [vcpkg](https://github.com/mcgoo/vcpkg-rs) have some additional configuration
|
||||||
options. The default when using vcpkg is to dynamically link. `vcpkg install sqlite3:x64-windows` will install the required library.
|
options. The default when using vcpkg is to dynamically link,
|
||||||
|
which must be enabled by setting `VCPKGRS_DYNAMIC=1` environment variable before build.
|
||||||
|
`vcpkg install sqlite3:x64-windows` will install the required library.
|
||||||
|
|
||||||
### Binding generation
|
### Binding generation
|
||||||
|
|
||||||
@ -139,10 +158,8 @@ minimum SQLite version that supports your chosen features. If you are using
|
|||||||
pregenerated bindings are chosen:
|
pregenerated bindings are chosen:
|
||||||
|
|
||||||
* `min_sqlite_version_3_6_8` - SQLite 3.6.8 bindings (this is the default)
|
* `min_sqlite_version_3_6_8` - SQLite 3.6.8 bindings (this is the default)
|
||||||
* `min_sqlite_version_3_6_11` - SQLite 3.6.11 bindings
|
|
||||||
* `min_sqlite_version_3_6_23` - SQLite 3.6.23 bindings
|
* `min_sqlite_version_3_6_23` - SQLite 3.6.23 bindings
|
||||||
* `min_sqlite_version_3_7_3` - SQLite 3.7.3 bindings
|
* `min_sqlite_version_3_7_7` - SQLite 3.7.7 bindings
|
||||||
* `min_sqlite_version_3_7_4` - SQLite 3.7.4 bindings
|
|
||||||
|
|
||||||
If you use the `bundled` feature, you will get pregenerated bindings for the
|
If you use the `bundled` feature, you will get pregenerated bindings for the
|
||||||
bundled version of SQLite. If you need other specific pregenerated binding
|
bundled version of SQLite. If you need other specific pregenerated binding
|
||||||
|
49
appveyor.yml
49
appveyor.yml
@ -1,43 +1,42 @@
|
|||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- TARGET: 1.21.0-x86_64-pc-windows-gnu
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
MSYS2_BITS: 64
|
MSYS2_BITS: 64
|
||||||
- TARGET: 1.21.0-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: nightly-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:
|
||||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe"
|
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||||
- rust-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
|
- rustup-init.exe -y --default-host %TARGET%
|
||||||
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
|
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||||
- if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin
|
- if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin
|
||||||
- rustc -V
|
- rustc -V
|
||||||
- cargo -V
|
- cargo -V
|
||||||
- ps: Start-FileDownload 'https://sqlite.org/2017/sqlite-dll-win64-x64-3170000.zip' # download SQLite dll (useful only when the `bundled` feature is not set)
|
# download SQLite dll (useful only when the `bundled` feature is not set)
|
||||||
- if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-dll-win64-x64-3170000.zip -y > nul
|
- appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-dll-win64-x64-3250200.zip -FileName sqlite-dll-win64-x64.zip
|
||||||
- ps: Start-FileDownload 'https://sqlite.org/2017/sqlite-amalgamation-3170000.zip' # download SQLite headers (useful only when the `bundled` feature is not set)
|
- if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-dll-win64-x64.zip -y > nul
|
||||||
- if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-amalgamation-3170000.zip -y > nul
|
# download SQLite headers (useful only when the `bundled` feature is not set)
|
||||||
- if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_LIB_DIR=%APPVEYOR_BUILD_FOLDER% # specify where the SQLite dll has been downloaded (useful only when the `bundled` feature is not set)
|
- appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-amalgamation-3250200.zip -FileName sqlite-amalgamation.zip
|
||||||
- if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_INCLUDE_DIR=%APPVEYOR_BUILD_FOLDER% # specify where the SQLite headers have been downloaded (useful only when the `bundled` feature is not set)
|
- if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-amalgamation.zip -y > nul
|
||||||
# install vcpkg and the sqlite3 package
|
# specify where the SQLite dll has been downloaded (useful only when the `bundled` feature is not set)
|
||||||
- if defined VCPKG_DEFAULT_TRIPLET git clone https://github.com/Microsoft/vcpkg c:\projects\vcpkg
|
- if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_LIB_DIR=%APPVEYOR_BUILD_FOLDER%
|
||||||
- if defined VCPKG_DEFAULT_TRIPLET c:\projects\vcpkg\bootstrap-vcpkg.bat
|
# specify where the SQLite headers have been downloaded (useful only when the `bundled` feature is not set)
|
||||||
- if defined VCPKG_DEFAULT_TRIPLET set VCPKG_ROOT=c:\projects\vcpkg
|
- if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_INCLUDE_DIR=%APPVEYOR_BUILD_FOLDER%
|
||||||
- if defined VCPKG_DEFAULT_TRIPLET %VCPKG_ROOT%\vcpkg.exe install sqlite3
|
# install sqlite3 package
|
||||||
- if defined VCPKG_DEFAULT_TRIPLET appveyor DownloadFile http://releases.llvm.org/4.0.0/LLVM-4.0.0-win64.exe
|
- if defined VCPKG_DEFAULT_TRIPLET vcpkg install sqlite3
|
||||||
- if defined VCPKG_DEFAULT_TRIPLET LLVM-4.0.0-win64.exe /S
|
|
||||||
|
|
||||||
build: false
|
build: false
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cargo test --lib --verbose
|
- cargo test --lib --verbose
|
||||||
- cargo test --lib --verbose --features bundled
|
- cargo test --lib --verbose --features bundled
|
||||||
- cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace"
|
- cargo test --lib --features "backup blob chrono collation functions hooks limits load_extension serde_json trace"
|
||||||
- cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace buildtime_bindgen"
|
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
|
||||||
- cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace bundled"
|
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
|
||||||
- cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace bundled buildtime_bindgen"
|
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- C:\Users\appveyor\.cargo
|
- C:\Users\appveyor\.cargo
|
||||||
|
@ -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.9.1"
|
version = "0.16.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"
|
||||||
@ -13,17 +14,22 @@ categories = ["external-ffi-bindings"]
|
|||||||
[features]
|
[features]
|
||||||
default = ["min_sqlite_version_3_6_8"]
|
default = ["min_sqlite_version_3_6_8"]
|
||||||
bundled = ["cc"]
|
bundled = ["cc"]
|
||||||
|
bundled-windows = ["cc"]
|
||||||
buildtime_bindgen = ["bindgen", "pkg-config", "vcpkg"]
|
buildtime_bindgen = ["bindgen", "pkg-config", "vcpkg"]
|
||||||
sqlcipher = []
|
sqlcipher = []
|
||||||
min_sqlite_version_3_6_8 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_6_8 = ["pkg-config", "vcpkg"]
|
||||||
min_sqlite_version_3_6_11 = ["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_3 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_7_7 = ["pkg-config", "vcpkg"]
|
||||||
min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"]
|
|
||||||
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
|
||||||
|
# sqlite3_unlock_notify >= 3.6.12
|
||||||
|
unlock_notify = []
|
||||||
|
# 3.13.0
|
||||||
|
preupdate_hook = []
|
||||||
|
# 3.13.0
|
||||||
|
session = ["preupdate_hook"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = { version = "0.31", optional = true }
|
bindgen = { version = "0.51", optional = true, default-features = false }
|
||||||
pkg-config = { version = "0.3", optional = true }
|
pkg-config = { version = "0.3", optional = true }
|
||||||
cc = { version = "1.0", optional = true }
|
cc = { version = "1.0", optional = true }
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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; 6usize] = b"3.7.4\x00";
|
pub const SQLITE_VERSION: &'static [u8; 6usize] = b"3.7.7\x00";
|
||||||
pub const SQLITE_VERSION_NUMBER: i32 = 3007004;
|
pub const SQLITE_VERSION_NUMBER: i32 = 3007007;
|
||||||
pub const SQLITE_SOURCE_ID: &'static [u8; 61usize] =
|
pub const SQLITE_SOURCE_ID: &'static [u8; 61usize] =
|
||||||
b"2010-12-07 20:14:09 a586a4deeb25330037a49df295b36aaf624d0f45\x00";
|
b"2011-06-23 19:49:22 4374b7e83ea0a3fbc3691f9c0c936272862f32f2\x00";
|
||||||
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;
|
||||||
@ -54,15 +54,21 @@ pub const SQLITE_IOERR_DIR_CLOSE: i32 = 4362;
|
|||||||
pub const SQLITE_IOERR_SHMOPEN: i32 = 4618;
|
pub const SQLITE_IOERR_SHMOPEN: i32 = 4618;
|
||||||
pub const SQLITE_IOERR_SHMSIZE: i32 = 4874;
|
pub const SQLITE_IOERR_SHMSIZE: i32 = 4874;
|
||||||
pub const SQLITE_IOERR_SHMLOCK: i32 = 5130;
|
pub const SQLITE_IOERR_SHMLOCK: i32 = 5130;
|
||||||
|
pub const SQLITE_IOERR_SHMMAP: i32 = 5386;
|
||||||
|
pub const SQLITE_IOERR_SEEK: i32 = 5642;
|
||||||
pub const SQLITE_LOCKED_SHAREDCACHE: i32 = 262;
|
pub const SQLITE_LOCKED_SHAREDCACHE: i32 = 262;
|
||||||
pub const SQLITE_BUSY_RECOVERY: i32 = 261;
|
pub const SQLITE_BUSY_RECOVERY: i32 = 261;
|
||||||
pub const SQLITE_CANTOPEN_NOTEMPDIR: i32 = 270;
|
pub const SQLITE_CANTOPEN_NOTEMPDIR: i32 = 270;
|
||||||
|
pub const SQLITE_CORRUPT_VTAB: i32 = 267;
|
||||||
|
pub const SQLITE_READONLY_RECOVERY: i32 = 264;
|
||||||
|
pub const SQLITE_READONLY_CANTLOCK: i32 = 520;
|
||||||
pub const SQLITE_OPEN_READONLY: i32 = 1;
|
pub const SQLITE_OPEN_READONLY: i32 = 1;
|
||||||
pub const SQLITE_OPEN_READWRITE: i32 = 2;
|
pub const SQLITE_OPEN_READWRITE: i32 = 2;
|
||||||
pub const SQLITE_OPEN_CREATE: i32 = 4;
|
pub const SQLITE_OPEN_CREATE: i32 = 4;
|
||||||
pub const SQLITE_OPEN_DELETEONCLOSE: i32 = 8;
|
pub const SQLITE_OPEN_DELETEONCLOSE: i32 = 8;
|
||||||
pub const SQLITE_OPEN_EXCLUSIVE: i32 = 16;
|
pub const SQLITE_OPEN_EXCLUSIVE: i32 = 16;
|
||||||
pub const SQLITE_OPEN_AUTOPROXY: i32 = 32;
|
pub const SQLITE_OPEN_AUTOPROXY: i32 = 32;
|
||||||
|
pub const SQLITE_OPEN_URI: i32 = 64;
|
||||||
pub const SQLITE_OPEN_MAIN_DB: i32 = 256;
|
pub const SQLITE_OPEN_MAIN_DB: i32 = 256;
|
||||||
pub const SQLITE_OPEN_TEMP_DB: i32 = 512;
|
pub const SQLITE_OPEN_TEMP_DB: i32 = 512;
|
||||||
pub const SQLITE_OPEN_TRANSIENT_DB: i32 = 1024;
|
pub const SQLITE_OPEN_TRANSIENT_DB: i32 = 1024;
|
||||||
@ -102,6 +108,7 @@ pub const SQLITE_LAST_ERRNO: i32 = 4;
|
|||||||
pub const SQLITE_FCNTL_SIZE_HINT: i32 = 5;
|
pub const SQLITE_FCNTL_SIZE_HINT: i32 = 5;
|
||||||
pub const SQLITE_FCNTL_CHUNK_SIZE: i32 = 6;
|
pub const SQLITE_FCNTL_CHUNK_SIZE: i32 = 6;
|
||||||
pub const SQLITE_FCNTL_FILE_POINTER: i32 = 7;
|
pub const SQLITE_FCNTL_FILE_POINTER: i32 = 7;
|
||||||
|
pub const SQLITE_FCNTL_SYNC_OMITTED: i32 = 8;
|
||||||
pub const SQLITE_ACCESS_EXISTS: i32 = 0;
|
pub const SQLITE_ACCESS_EXISTS: i32 = 0;
|
||||||
pub const SQLITE_ACCESS_READWRITE: i32 = 1;
|
pub const SQLITE_ACCESS_READWRITE: i32 = 1;
|
||||||
pub const SQLITE_ACCESS_READ: i32 = 2;
|
pub const SQLITE_ACCESS_READ: i32 = 2;
|
||||||
@ -125,7 +132,10 @@ pub const SQLITE_CONFIG_LOOKASIDE: i32 = 13;
|
|||||||
pub const SQLITE_CONFIG_PCACHE: i32 = 14;
|
pub const SQLITE_CONFIG_PCACHE: i32 = 14;
|
||||||
pub const SQLITE_CONFIG_GETPCACHE: i32 = 15;
|
pub const SQLITE_CONFIG_GETPCACHE: i32 = 15;
|
||||||
pub const SQLITE_CONFIG_LOG: i32 = 16;
|
pub const SQLITE_CONFIG_LOG: i32 = 16;
|
||||||
|
pub const SQLITE_CONFIG_URI: i32 = 17;
|
||||||
pub const SQLITE_DBCONFIG_LOOKASIDE: i32 = 1001;
|
pub const SQLITE_DBCONFIG_LOOKASIDE: i32 = 1001;
|
||||||
|
pub const SQLITE_DBCONFIG_ENABLE_FKEY: i32 = 1002;
|
||||||
|
pub const SQLITE_DBCONFIG_ENABLE_TRIGGER: i32 = 1003;
|
||||||
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;
|
||||||
@ -199,6 +209,7 @@ pub const SQLITE_MUTEX_STATIC_OPEN: i32 = 4;
|
|||||||
pub const SQLITE_MUTEX_STATIC_PRNG: i32 = 5;
|
pub const SQLITE_MUTEX_STATIC_PRNG: i32 = 5;
|
||||||
pub const SQLITE_MUTEX_STATIC_LRU: i32 = 6;
|
pub const SQLITE_MUTEX_STATIC_LRU: i32 = 6;
|
||||||
pub const SQLITE_MUTEX_STATIC_LRU2: i32 = 7;
|
pub const SQLITE_MUTEX_STATIC_LRU2: i32 = 7;
|
||||||
|
pub const SQLITE_MUTEX_STATIC_PMEM: i32 = 7;
|
||||||
pub const SQLITE_TESTCTRL_FIRST: i32 = 5;
|
pub const SQLITE_TESTCTRL_FIRST: i32 = 5;
|
||||||
pub const SQLITE_TESTCTRL_PRNG_SAVE: i32 = 5;
|
pub const SQLITE_TESTCTRL_PRNG_SAVE: i32 = 5;
|
||||||
pub const SQLITE_TESTCTRL_PRNG_RESTORE: i32 = 6;
|
pub const SQLITE_TESTCTRL_PRNG_RESTORE: i32 = 6;
|
||||||
@ -214,7 +225,8 @@ 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_PGHDRSZ: i32 = 17;
|
pub const SQLITE_TESTCTRL_PGHDRSZ: i32 = 17;
|
||||||
pub const SQLITE_TESTCTRL_SCRATCHMALLOC: i32 = 18;
|
pub const SQLITE_TESTCTRL_SCRATCHMALLOC: i32 = 18;
|
||||||
pub const SQLITE_TESTCTRL_LAST: i32 = 18;
|
pub const SQLITE_TESTCTRL_LOCALTIME_FAULT: i32 = 19;
|
||||||
|
pub const SQLITE_TESTCTRL_LAST: i32 = 19;
|
||||||
pub const SQLITE_STATUS_MEMORY_USED: i32 = 0;
|
pub const SQLITE_STATUS_MEMORY_USED: i32 = 0;
|
||||||
pub const SQLITE_STATUS_PAGECACHE_USED: i32 = 1;
|
pub const SQLITE_STATUS_PAGECACHE_USED: i32 = 1;
|
||||||
pub const SQLITE_STATUS_PAGECACHE_OVERFLOW: i32 = 2;
|
pub const SQLITE_STATUS_PAGECACHE_OVERFLOW: i32 = 2;
|
||||||
@ -229,10 +241,20 @@ pub const SQLITE_DBSTATUS_LOOKASIDE_USED: i32 = 0;
|
|||||||
pub const SQLITE_DBSTATUS_CACHE_USED: i32 = 1;
|
pub const SQLITE_DBSTATUS_CACHE_USED: i32 = 1;
|
||||||
pub const SQLITE_DBSTATUS_SCHEMA_USED: i32 = 2;
|
pub const SQLITE_DBSTATUS_SCHEMA_USED: i32 = 2;
|
||||||
pub const SQLITE_DBSTATUS_STMT_USED: i32 = 3;
|
pub const SQLITE_DBSTATUS_STMT_USED: i32 = 3;
|
||||||
pub const SQLITE_DBSTATUS_MAX: i32 = 3;
|
pub const SQLITE_DBSTATUS_LOOKASIDE_HIT: i32 = 4;
|
||||||
|
pub const SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE: i32 = 5;
|
||||||
|
pub const SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL: i32 = 6;
|
||||||
|
pub const SQLITE_DBSTATUS_MAX: i32 = 6;
|
||||||
pub const SQLITE_STMTSTATUS_FULLSCAN_STEP: i32 = 1;
|
pub const SQLITE_STMTSTATUS_FULLSCAN_STEP: i32 = 1;
|
||||||
pub const SQLITE_STMTSTATUS_SORT: i32 = 2;
|
pub const SQLITE_STMTSTATUS_SORT: i32 = 2;
|
||||||
pub const SQLITE_STMTSTATUS_AUTOINDEX: i32 = 3;
|
pub const SQLITE_STMTSTATUS_AUTOINDEX: i32 = 3;
|
||||||
|
pub const SQLITE_CHECKPOINT_PASSIVE: i32 = 0;
|
||||||
|
pub const SQLITE_CHECKPOINT_FULL: i32 = 1;
|
||||||
|
pub const SQLITE_CHECKPOINT_RESTART: i32 = 2;
|
||||||
|
pub const SQLITE_VTAB_CONSTRAINT_SUPPORT: i32 = 1;
|
||||||
|
pub const SQLITE_ROLLBACK: i32 = 1;
|
||||||
|
pub const SQLITE_FAIL: i32 = 3;
|
||||||
|
pub const SQLITE_REPLACE: i32 = 5;
|
||||||
pub type va_list = __builtin_va_list;
|
pub type va_list = __builtin_va_list;
|
||||||
pub type __gnuc_va_list = __builtin_va_list;
|
pub type __gnuc_va_list = __builtin_va_list;
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -519,15 +541,35 @@ pub struct sqlite3_vfs {
|
|||||||
*mut sqlite3_int64)
|
*mut sqlite3_int64)
|
||||||
->
|
->
|
||||||
::std::os::raw::c_int>,
|
::std::os::raw::c_int>,
|
||||||
|
pub xSetSystemCall: ::std::option::Option<unsafe extern "C" fn(arg1:
|
||||||
|
*mut sqlite3_vfs,
|
||||||
|
zName:
|
||||||
|
*const ::std::os::raw::c_char,
|
||||||
|
arg2:
|
||||||
|
sqlite3_syscall_ptr)
|
||||||
|
-> ::std::os::raw::c_int>,
|
||||||
|
pub xGetSystemCall: ::std::option::Option<unsafe extern "C" fn(arg1:
|
||||||
|
*mut sqlite3_vfs,
|
||||||
|
zName:
|
||||||
|
*const ::std::os::raw::c_char)
|
||||||
|
->
|
||||||
|
::std::option::Option<unsafe extern "C" fn()>>,
|
||||||
|
pub xNextSystemCall: ::std::option::Option<unsafe extern "C" fn(arg1:
|
||||||
|
*mut sqlite3_vfs,
|
||||||
|
zName:
|
||||||
|
*const ::std::os::raw::c_char)
|
||||||
|
->
|
||||||
|
*const ::std::os::raw::c_char>,
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn bindgen_test_layout_sqlite3_vfs() {
|
fn bindgen_test_layout_sqlite3_vfs() {
|
||||||
assert_eq!(::std::mem::size_of::<sqlite3_vfs>() , 144usize);
|
assert_eq!(::std::mem::size_of::<sqlite3_vfs>() , 168usize);
|
||||||
assert_eq!(::std::mem::align_of::<sqlite3_vfs>() , 8usize);
|
assert_eq!(::std::mem::align_of::<sqlite3_vfs>() , 8usize);
|
||||||
}
|
}
|
||||||
impl Clone for sqlite3_vfs {
|
impl Clone for sqlite3_vfs {
|
||||||
fn clone(&self) -> Self { *self }
|
fn clone(&self) -> Self { *self }
|
||||||
}
|
}
|
||||||
|
pub type sqlite3_syscall_ptr = ::std::option::Option<unsafe extern "C" fn()>;
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_initialize() -> ::std::os::raw::c_int;
|
pub fn sqlite3_initialize() -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
@ -651,6 +693,13 @@ extern "C" {
|
|||||||
arg3: *const ::std::os::raw::c_char, ...)
|
arg3: *const ::std::os::raw::c_char, ...)
|
||||||
-> *mut ::std::os::raw::c_char;
|
-> *mut ::std::os::raw::c_char;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_vsnprintf(arg1: ::std::os::raw::c_int,
|
||||||
|
arg2: *mut ::std::os::raw::c_char,
|
||||||
|
arg3: *const ::std::os::raw::c_char,
|
||||||
|
arg4: *mut __va_list_tag)
|
||||||
|
-> *mut ::std::os::raw::c_char;
|
||||||
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_malloc(arg1: ::std::os::raw::c_int)
|
pub fn sqlite3_malloc(arg1: ::std::os::raw::c_int)
|
||||||
-> *mut ::std::os::raw::c_void;
|
-> *mut ::std::os::raw::c_void;
|
||||||
@ -741,6 +790,11 @@ extern "C" {
|
|||||||
zVfs: *const ::std::os::raw::c_char)
|
zVfs: *const ::std::os::raw::c_char)
|
||||||
-> ::std::os::raw::c_int;
|
-> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_uri_parameter(zFilename: *const ::std::os::raw::c_char,
|
||||||
|
zParam: *const ::std::os::raw::c_char)
|
||||||
|
-> *const ::std::os::raw::c_char;
|
||||||
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn sqlite3_errcode(db: *mut sqlite3) -> ::std::os::raw::c_int;
|
pub fn sqlite3_errcode(db: *mut sqlite3) -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
@ -1691,10 +1745,25 @@ pub struct sqlite3_module {
|
|||||||
zNew:
|
zNew:
|
||||||
*const ::std::os::raw::c_char)
|
*const ::std::os::raw::c_char)
|
||||||
-> ::std::os::raw::c_int>,
|
-> ::std::os::raw::c_int>,
|
||||||
|
pub xSavepoint: ::std::option::Option<unsafe extern "C" fn(pVTab:
|
||||||
|
*mut sqlite3_vtab,
|
||||||
|
arg1:
|
||||||
|
::std::os::raw::c_int)
|
||||||
|
-> ::std::os::raw::c_int>,
|
||||||
|
pub xRelease: ::std::option::Option<unsafe extern "C" fn(pVTab:
|
||||||
|
*mut sqlite3_vtab,
|
||||||
|
arg1:
|
||||||
|
::std::os::raw::c_int)
|
||||||
|
-> ::std::os::raw::c_int>,
|
||||||
|
pub xRollbackTo: ::std::option::Option<unsafe extern "C" fn(pVTab:
|
||||||
|
*mut sqlite3_vtab,
|
||||||
|
arg1:
|
||||||
|
::std::os::raw::c_int)
|
||||||
|
-> ::std::os::raw::c_int>,
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn bindgen_test_layout_sqlite3_module() {
|
fn bindgen_test_layout_sqlite3_module() {
|
||||||
assert_eq!(::std::mem::size_of::<sqlite3_module>() , 160usize);
|
assert_eq!(::std::mem::size_of::<sqlite3_module>() , 184usize);
|
||||||
assert_eq!(::std::mem::align_of::<sqlite3_module>() , 8usize);
|
assert_eq!(::std::mem::align_of::<sqlite3_module>() , 8usize);
|
||||||
}
|
}
|
||||||
impl Clone for sqlite3_module {
|
impl Clone for sqlite3_module {
|
||||||
@ -2006,6 +2075,23 @@ extern "C" {
|
|||||||
zDb: *const ::std::os::raw::c_char)
|
zDb: *const ::std::os::raw::c_char)
|
||||||
-> ::std::os::raw::c_int;
|
-> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_wal_checkpoint_v2(db: *mut sqlite3,
|
||||||
|
zDb: *const ::std::os::raw::c_char,
|
||||||
|
eMode: ::std::os::raw::c_int,
|
||||||
|
pnLog: *mut ::std::os::raw::c_int,
|
||||||
|
pnCkpt: *mut ::std::os::raw::c_int)
|
||||||
|
-> ::std::os::raw::c_int;
|
||||||
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_vtab_config(arg1: *mut sqlite3,
|
||||||
|
op: ::std::os::raw::c_int, ...)
|
||||||
|
-> ::std::os::raw::c_int;
|
||||||
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn sqlite3_vtab_on_conflict(arg1: *mut sqlite3)
|
||||||
|
-> ::std::os::raw::c_int;
|
||||||
|
}
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy)]
|
#[derive(Debug, Copy)]
|
||||||
pub struct sqlite3_rtree_geometry {
|
pub struct sqlite3_rtree_geometry {
|
@ -1,25 +1,61 @@
|
|||||||
|
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");
|
||||||
|
if cfg!(feature = "sqlcipher") {
|
||||||
|
if cfg!(any(
|
||||||
|
feature = "bundled",
|
||||||
|
all(windows, feature = "bundled-windows")
|
||||||
|
)) {
|
||||||
|
println!(
|
||||||
|
"cargo:warning={}",
|
||||||
|
"Builds with bundled SQLCipher are not supported. Searching for SQLCipher to link against. \
|
||||||
|
This can lead to issues if your version of SQLCipher is not up to date!");
|
||||||
|
}
|
||||||
|
build_linked::main(&out_dir, &out_path)
|
||||||
|
} else {
|
||||||
|
// This can't be `cfg!` without always requiring our `mod build_bundled` (and
|
||||||
|
// thus `cc`)
|
||||||
|
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
|
||||||
|
{
|
||||||
|
build_bundled::main(&out_dir, &out_path)
|
||||||
|
}
|
||||||
|
#[cfg(not(any(feature = "bundled", all(windows, feature = "bundled-windows"))))]
|
||||||
|
{
|
||||||
|
build_linked::main(&out_dir, &out_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bundled")]
|
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
|
||||||
mod build {
|
mod build_bundled {
|
||||||
extern crate cc;
|
use cc;
|
||||||
use std::{env, fs};
|
use std::env;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main(out_dir: &str, out_path: &Path) {
|
||||||
if cfg!(feature = "sqlcipher") {
|
if cfg!(feature = "sqlcipher") {
|
||||||
|
// This is just a sanity check, the top level `main` should ensure this.
|
||||||
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");
|
||||||
|
}
|
||||||
|
|
||||||
cc::Build::new()
|
let mut cfg = cc::Build::new();
|
||||||
.file("sqlite3/sqlite3.c")
|
cfg.file("sqlite3/sqlite3.c")
|
||||||
.flag("-DSQLITE_CORE")
|
.flag("-DSQLITE_CORE")
|
||||||
.flag("-DSQLITE_DEFAULT_FOREIGN_KEYS=1")
|
.flag("-DSQLITE_DEFAULT_FOREIGN_KEYS=1")
|
||||||
.flag("-DSQLITE_ENABLE_API_ARMOR")
|
.flag("-DSQLITE_ENABLE_API_ARMOR")
|
||||||
@ -34,59 +70,150 @@ mod build {
|
|||||||
.flag("-DSQLITE_ENABLE_RTREE")
|
.flag("-DSQLITE_ENABLE_RTREE")
|
||||||
.flag("-DSQLITE_ENABLE_STAT2")
|
.flag("-DSQLITE_ENABLE_STAT2")
|
||||||
.flag("-DSQLITE_ENABLE_STAT4")
|
.flag("-DSQLITE_ENABLE_STAT4")
|
||||||
.flag("-DSQLITE_HAVE_ISNAN")
|
|
||||||
.flag("-DSQLITE_SOUNDEX")
|
.flag("-DSQLITE_SOUNDEX")
|
||||||
.flag("-DSQLITE_THREADSAFE=1")
|
.flag("-DSQLITE_THREADSAFE=1")
|
||||||
.flag("-DSQLITE_USE_URI")
|
.flag("-DSQLITE_USE_URI")
|
||||||
.flag("-DHAVE_USLEEP=1")
|
.flag("-DHAVE_USLEEP=1");
|
||||||
.compile("libsqlite3.a");
|
// Older versions of visual studio don't support c99 (including isnan), which
|
||||||
|
// causes a build failure when the linker fails to find the `isnan`
|
||||||
|
// function. `sqlite` provides its own implmentation, using the fact
|
||||||
|
// that x != x when x is NaN.
|
||||||
|
//
|
||||||
|
// There may be other platforms that don't support `isnan`, they should be
|
||||||
|
// tested for here.
|
||||||
|
if cfg!(target_env = "msvc") {
|
||||||
|
use cc::windows_registry::{find_vs_version, VsVers};
|
||||||
|
let vs_has_nan = match find_vs_version() {
|
||||||
|
Ok(ver) => ver != VsVers::Vs12,
|
||||||
|
Err(_msg) => false,
|
||||||
|
};
|
||||||
|
if vs_has_nan {
|
||||||
|
cfg.flag("-DSQLITE_HAVE_ISNAN");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.flag("-DSQLITE_HAVE_ISNAN");
|
||||||
|
}
|
||||||
|
if cfg!(feature = "unlock_notify") {
|
||||||
|
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
|
||||||
|
}
|
||||||
|
if cfg!(feature = "preupdate_hook") {
|
||||||
|
cfg.flag("-DSQLITE_ENABLE_PREUPDATE_HOOK");
|
||||||
|
}
|
||||||
|
if cfg!(feature = "session") {
|
||||||
|
cfg.flag("-DSQLITE_ENABLE_SESSION");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(limit) = env::var("SQLITE_MAX_VARIABLE_NUMBER") {
|
||||||
|
cfg.flag(&format!("-DSQLITE_MAX_VARIABLE_NUMBER={}", limit));
|
||||||
|
}
|
||||||
|
println!("cargo:rerun-if-env-changed=SQLITE_MAX_VARIABLE_NUMBER");
|
||||||
|
|
||||||
|
if let Ok(limit) = env::var("SQLITE_MAX_EXPR_DEPTH") {
|
||||||
|
cfg.flag(&format!("-DSQLITE_MAX_EXPR_DEPTH={}", limit));
|
||||||
|
}
|
||||||
|
println!("cargo:rerun-if-env-changed=SQLITE_MAX_EXPR_DEPTH");
|
||||||
|
|
||||||
|
cfg.compile("libsqlite3.a");
|
||||||
|
|
||||||
|
println!("cargo:lib_dir={}", out_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "bundled"))]
|
fn env_prefix() -> &'static str {
|
||||||
mod build {
|
if cfg!(feature = "sqlcipher") {
|
||||||
extern crate pkg_config;
|
"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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod build_linked {
|
||||||
|
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,
|
if cfg!(any(
|
||||||
FromPath(String),
|
feature = "bundled",
|
||||||
}
|
all(windows, feature = "bundled-windows")
|
||||||
|
)) && !cfg!(feature = "buildtime_bindgen")
|
||||||
impl From<HeaderLocation> for String {
|
{
|
||||||
fn from(header: HeaderLocation) -> String {
|
// We can only get here if `bundled` and `sqlcipher` were both
|
||||||
match header {
|
// specified (and `builtime_bindgen` was not). In order to keep
|
||||||
HeaderLocation::FromEnvironment => {
|
// `rusqlite` relatively clean we hide the fact that `bundled` can
|
||||||
let prefix = env_prefix();
|
// be ignored in some cases, and just use the bundled bindings, even
|
||||||
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix))
|
// though the library we found might not match their version.
|
||||||
.expect(&format!("{}_INCLUDE_DIR must be set if {}_LIB_DIR is set", prefix, prefix));
|
// Ideally we'd perform a version check here, but doing so is rather
|
||||||
header.push_str("/sqlite3.h");
|
// tricky, since we might not have access to executables (and
|
||||||
header
|
// moreover, we might be cross compiling).
|
||||||
}
|
std::fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
|
||||||
HeaderLocation::Wrapper => "wrapper.h".into(),
|
.expect("Could not copy bindings to output directory");
|
||||||
HeaderLocation::FromPath(path) => path,
|
} else {
|
||||||
}
|
bindings::write_to_out_dir(header, out_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
fn find_link_mode() -> &'static str {
|
||||||
let header = find_sqlite();
|
// If the user specifies SQLITE_STATIC (or SQLCIPHER_STATIC), do static
|
||||||
bindings::write_to_out_dir(header);
|
// linking, unless it's explicitly set to 0.
|
||||||
|
match &env::var(format!("{}_STATIC", env_prefix())) {
|
||||||
|
Ok(v) if v != "0" => "static",
|
||||||
|
_ => "dylib",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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={}_LIB_DIR", env_prefix());
|
||||||
|
println!("cargo:rerun-if-env-changed={}_STATIC", env_prefix());
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
println!("cargo:rerun-if-env-changed=PATH");
|
||||||
|
}
|
||||||
|
if cfg!(all(feature = "vcpkg", target_env = "msvc")) {
|
||||||
|
println!("cargo:rerun-if-env-changed=VCPKGRS_DYNAMIC");
|
||||||
|
}
|
||||||
// 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);
|
// Try to use pkg-config to determine link commands
|
||||||
println!("cargo:rustc-link-search={}", dir);
|
let pkgconfig_path = Path::new(&dir).join("pkgconfig");
|
||||||
|
env::set_var("PKG_CONFIG_PATH", pkgconfig_path);
|
||||||
|
if let Err(_) = pkg_config::Config::new().probe(link_lib) {
|
||||||
|
// Otherwise just emit the bare minimum link commands.
|
||||||
|
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
|
||||||
|
println!("cargo:rustc-link-search={}", dir);
|
||||||
|
}
|
||||||
return HeaderLocation::FromEnvironment;
|
return HeaderLocation::FromEnvironment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +222,10 @@ mod build {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See if pkg-config can do everything for us.
|
// See if pkg-config can do everything for us.
|
||||||
match pkg_config::Config::new().print_system_libs(false).probe(link_lib) {
|
match pkg_config::Config::new()
|
||||||
|
.print_system_libs(false)
|
||||||
|
.probe(link_lib)
|
||||||
|
{
|
||||||
Ok(mut lib) => {
|
Ok(mut lib) => {
|
||||||
if let Some(mut header) = lib.include_paths.pop() {
|
if let Some(mut header) = lib.include_paths.pop() {
|
||||||
header.push("sqlite3.h");
|
header.push("sqlite3.h");
|
||||||
@ -107,9 +237,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,14 +262,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"
|
||||||
@ -147,98 +269,98 @@ mod build {
|
|||||||
"sqlite3"
|
"sqlite3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "buildtime_bindgen"))]
|
#[cfg(not(feature = "buildtime_bindgen"))]
|
||||||
mod bindings {
|
mod bindings {
|
||||||
use super::HeaderLocation;
|
use super::HeaderLocation;
|
||||||
|
|
||||||
use std::{env, fs};
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
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")]
|
||||||
|
"bindgen-bindings/bindgen_3.6.23.rs",
|
||||||
|
#[cfg(feature = "min_sqlite_version_3_7_7")]
|
||||||
|
"bindgen-bindings/bindgen_3.7.7.rs",
|
||||||
|
#[cfg(feature = "min_sqlite_version_3_7_16")]
|
||||||
|
"bindgen-bindings/bindgen_3.7.16.rs",
|
||||||
|
];
|
||||||
|
|
||||||
#[cfg(feature = "min_sqlite_version_3_6_11")]
|
pub fn write_to_out_dir(_header: HeaderLocation, out_path: &Path) {
|
||||||
"bindgen-bindings/bindgen_3.6.11.rs",
|
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 = "min_sqlite_version_3_6_23")]
|
}
|
||||||
"bindgen-bindings/bindgen_3.6.23.rs",
|
}
|
||||||
|
|
||||||
#[cfg(feature = "min_sqlite_version_3_7_3")]
|
#[cfg(feature = "buildtime_bindgen")]
|
||||||
"bindgen-bindings/bindgen_3.7.3.rs",
|
mod bindings {
|
||||||
|
use bindgen;
|
||||||
#[cfg(feature = "min_sqlite_version_3_7_4")]
|
|
||||||
"bindgen-bindings/bindgen_3.7.4.rs",
|
use super::HeaderLocation;
|
||||||
|
use bindgen::callbacks::{IntKind, ParseCallbacks};
|
||||||
#[cfg(feature = "min_sqlite_version_3_7_16")]
|
|
||||||
"bindgen-bindings/bindgen_3.7.16.rs",
|
use std::fs::OpenOptions;
|
||||||
];
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
pub fn write_to_out_dir(_header: HeaderLocation) {
|
|
||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
#[derive(Debug)]
|
||||||
let out_path = Path::new(&out_dir).join("bindgen.rs");
|
struct SqliteTypeChooser;
|
||||||
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");
|
impl ParseCallbacks for SqliteTypeChooser {
|
||||||
}
|
fn int_macro(&self, _name: &str, value: i64) -> Option<IntKind> {
|
||||||
}
|
if value >= i32::min_value() as i64 && value <= i32::max_value() as i64 {
|
||||||
|
Some(IntKind::I32)
|
||||||
#[cfg(feature = "buildtime_bindgen")]
|
} else {
|
||||||
mod bindings {
|
None
|
||||||
extern crate bindgen;
|
}
|
||||||
|
}
|
||||||
use self::bindgen::callbacks::{ParseCallbacks, IntKind};
|
}
|
||||||
use super::HeaderLocation;
|
|
||||||
|
pub fn write_to_out_dir(header: HeaderLocation, out_path: &Path) {
|
||||||
use std::env;
|
let header: String = header.into();
|
||||||
use std::io::Write;
|
let mut output = Vec::new();
|
||||||
use std::fs::OpenOptions;
|
let mut bindings = bindgen::builder()
|
||||||
use std::path::Path;
|
.header(header.clone())
|
||||||
|
.parse_callbacks(Box::new(SqliteTypeChooser))
|
||||||
#[derive(Debug)]
|
.rustfmt_bindings(true);
|
||||||
struct SqliteTypeChooser;
|
|
||||||
|
if cfg!(feature = "unlock_notify") {
|
||||||
impl ParseCallbacks for SqliteTypeChooser {
|
bindings = bindings.clang_arg("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
|
||||||
fn int_macro(&self, _name: &str, value: i64) -> Option<IntKind> {
|
}
|
||||||
if value >= i32::min_value() as i64 && value <= i32::max_value() as i64 {
|
if cfg!(feature = "preupdate_hook") {
|
||||||
Some(IntKind::I32)
|
bindings = bindings.clang_arg("-DSQLITE_ENABLE_PREUPDATE_HOOK");
|
||||||
} else {
|
}
|
||||||
None
|
if cfg!(feature = "session") {
|
||||||
}
|
bindings = bindings.clang_arg("-DSQLITE_ENABLE_SESSION");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
bindings
|
||||||
pub fn write_to_out_dir(header: HeaderLocation) {
|
.generate()
|
||||||
let header: String = header.into();
|
.expect(&format!("could not run bindgen on header {}", header))
|
||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
.write(Box::new(&mut output))
|
||||||
let mut output = Vec::new();
|
.expect("could not write output of bindgen");
|
||||||
bindgen::builder()
|
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
|
||||||
.header(header.clone())
|
|
||||||
.parse_callbacks(Box::new(SqliteTypeChooser))
|
// rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it
|
||||||
.generate()
|
// can. This flag was added in SQLite 3.8.3, but oring it in in prior
|
||||||
.expect(&format!("could not run bindgen on header {}", header))
|
// versions of SQLite is harmless. We don't want to not build just
|
||||||
.write(Box::new(&mut output))
|
// because this flag is missing (e.g., if we're linking against
|
||||||
.expect("could not write output of bindgen");
|
// SQLite 3.7.x), so append the flag manually if it isn't present in bindgen's
|
||||||
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
|
// output.
|
||||||
|
if !output.contains("pub const SQLITE_DETERMINISTIC") {
|
||||||
// rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it can. This flag
|
output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n");
|
||||||
// was added in SQLite 3.8.3, but oring it in in prior versions of SQLite is harmless. We
|
}
|
||||||
// don't want to not build just because this flag is missing (e.g., if we're linking against
|
|
||||||
// SQLite 3.7.x), so append the flag manually if it isn't present in bindgen's output.
|
let mut file = OpenOptions::new()
|
||||||
if !output.contains("pub const SQLITE_DETERMINISTIC") {
|
.write(true)
|
||||||
output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n");
|
.truncate(true)
|
||||||
}
|
.create(true)
|
||||||
|
.open(out_path.clone())
|
||||||
let path = Path::new(&out_dir).join("bindgen.rs");
|
.expect(&format!("Could not write to {:?}", out_path));
|
||||||
|
|
||||||
let mut file = OpenOptions::new()
|
file.write_all(output.as_bytes())
|
||||||
.write(true)
|
.expect(&format!("Could not write to {:?}", out_path));
|
||||||
.truncate(true)
|
|
||||||
.create(true)
|
|
||||||
.open(path.clone())
|
|
||||||
.expect(&format!("Could not write to {:?}", path));
|
|
||||||
|
|
||||||
file.write_all(output.as_bytes()).expect(&format!("Could not write to {:?}", path));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6012
libsqlite3-sys/sqlite3/bindgen_bundled_version.rs
vendored
6012
libsqlite3-sys/sqlite3/bindgen_bundled_version.rs
vendored
File diff suppressed because it is too large
Load Diff
55847
libsqlite3-sys/sqlite3/sqlite3.c
vendored
55847
libsqlite3-sys/sqlite3/sqlite3.c
vendored
File diff suppressed because it is too large
Load Diff
2050
libsqlite3-sys/sqlite3/sqlite3.h
vendored
2050
libsqlite3-sys/sqlite3/sqlite3.h
vendored
File diff suppressed because it is too large
Load Diff
84
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
84
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
@ -134,7 +134,7 @@ struct sqlite3_api_routines {
|
|||||||
int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
|
int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
|
||||||
const char*,const char*),void*);
|
const char*,const char*),void*);
|
||||||
void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
|
void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
|
||||||
char * (*snprintf)(int,char*,const char*,...);
|
char * (*xsnprintf)(int,char*,const char*,...);
|
||||||
int (*step)(sqlite3_stmt*);
|
int (*step)(sqlite3_stmt*);
|
||||||
int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
|
int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
|
||||||
char const**,char const**,int*,int*,int*);
|
char const**,char const**,int*,int*,int*);
|
||||||
@ -246,7 +246,7 @@ struct sqlite3_api_routines {
|
|||||||
int (*uri_boolean)(const char*,const char*,int);
|
int (*uri_boolean)(const char*,const char*,int);
|
||||||
sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
|
sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
|
||||||
const char *(*uri_parameter)(const char*,const char*);
|
const char *(*uri_parameter)(const char*,const char*);
|
||||||
char *(*vsnprintf)(int,char*,const char*,va_list);
|
char *(*xvsnprintf)(int,char*,const char*,va_list);
|
||||||
int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
|
int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
|
||||||
/* Version 3.8.7 and later */
|
/* Version 3.8.7 and later */
|
||||||
int (*auto_extension)(void(*)(void));
|
int (*auto_extension)(void(*)(void));
|
||||||
@ -282,6 +282,46 @@ struct sqlite3_api_routines {
|
|||||||
/* Version 3.14.0 and later */
|
/* Version 3.14.0 and later */
|
||||||
int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
|
int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
|
||||||
char *(*expanded_sql)(sqlite3_stmt*);
|
char *(*expanded_sql)(sqlite3_stmt*);
|
||||||
|
/* Version 3.18.0 and later */
|
||||||
|
void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64);
|
||||||
|
/* Version 3.20.0 and later */
|
||||||
|
int (*prepare_v3)(sqlite3*,const char*,int,unsigned int,
|
||||||
|
sqlite3_stmt**,const char**);
|
||||||
|
int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int,
|
||||||
|
sqlite3_stmt**,const void**);
|
||||||
|
int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
|
||||||
|
void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
|
||||||
|
void *(*value_pointer)(sqlite3_value*,const char*);
|
||||||
|
int (*vtab_nochange)(sqlite3_context*);
|
||||||
|
int (*value_nochange)(sqlite3_value*);
|
||||||
|
const char *(*vtab_collation)(sqlite3_index_info*,int);
|
||||||
|
/* Version 3.24.0 and later */
|
||||||
|
int (*keyword_count)(void);
|
||||||
|
int (*keyword_name)(int,const char**,int*);
|
||||||
|
int (*keyword_check)(const char*,int);
|
||||||
|
sqlite3_str *(*str_new)(sqlite3*);
|
||||||
|
char *(*str_finish)(sqlite3_str*);
|
||||||
|
void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
|
||||||
|
void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
|
||||||
|
void (*str_append)(sqlite3_str*, const char *zIn, int N);
|
||||||
|
void (*str_appendall)(sqlite3_str*, const char *zIn);
|
||||||
|
void (*str_appendchar)(sqlite3_str*, int N, char C);
|
||||||
|
void (*str_reset)(sqlite3_str*);
|
||||||
|
int (*str_errcode)(sqlite3_str*);
|
||||||
|
int (*str_length)(sqlite3_str*);
|
||||||
|
char *(*str_value)(sqlite3_str*);
|
||||||
|
/* Version 3.25.0 and later */
|
||||||
|
int (*create_window_function)(sqlite3*,const char*,int,int,void*,
|
||||||
|
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
||||||
|
void (*xFinal)(sqlite3_context*),
|
||||||
|
void (*xValue)(sqlite3_context*),
|
||||||
|
void (*xInv)(sqlite3_context*,int,sqlite3_value**),
|
||||||
|
void(*xDestroy)(void*));
|
||||||
|
/* Version 3.26.0 and later */
|
||||||
|
const char *(*normalized_sql)(sqlite3_stmt*);
|
||||||
|
/* Version 3.28.0 and later */
|
||||||
|
int (*stmt_isexplain)(sqlite3_stmt*);
|
||||||
|
int (*value_frombind)(sqlite3_value*);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -408,7 +448,7 @@ typedef int (*sqlite3_loadext_entry)(
|
|||||||
#define sqlite3_rollback_hook sqlite3_api->rollback_hook
|
#define sqlite3_rollback_hook sqlite3_api->rollback_hook
|
||||||
#define sqlite3_set_authorizer sqlite3_api->set_authorizer
|
#define sqlite3_set_authorizer sqlite3_api->set_authorizer
|
||||||
#define sqlite3_set_auxdata sqlite3_api->set_auxdata
|
#define sqlite3_set_auxdata sqlite3_api->set_auxdata
|
||||||
#define sqlite3_snprintf sqlite3_api->snprintf
|
#define sqlite3_snprintf sqlite3_api->xsnprintf
|
||||||
#define sqlite3_step sqlite3_api->step
|
#define sqlite3_step sqlite3_api->step
|
||||||
#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
|
#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
|
||||||
#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
|
#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
|
||||||
@ -432,7 +472,7 @@ typedef int (*sqlite3_loadext_entry)(
|
|||||||
#define sqlite3_value_text16le sqlite3_api->value_text16le
|
#define sqlite3_value_text16le sqlite3_api->value_text16le
|
||||||
#define sqlite3_value_type sqlite3_api->value_type
|
#define sqlite3_value_type sqlite3_api->value_type
|
||||||
#define sqlite3_vmprintf sqlite3_api->vmprintf
|
#define sqlite3_vmprintf sqlite3_api->vmprintf
|
||||||
#define sqlite3_vsnprintf sqlite3_api->vsnprintf
|
#define sqlite3_vsnprintf sqlite3_api->xvsnprintf
|
||||||
#define sqlite3_overload_function sqlite3_api->overload_function
|
#define sqlite3_overload_function sqlite3_api->overload_function
|
||||||
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
|
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
|
||||||
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
|
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
|
||||||
@ -508,7 +548,7 @@ typedef int (*sqlite3_loadext_entry)(
|
|||||||
#define sqlite3_uri_boolean sqlite3_api->uri_boolean
|
#define sqlite3_uri_boolean sqlite3_api->uri_boolean
|
||||||
#define sqlite3_uri_int64 sqlite3_api->uri_int64
|
#define sqlite3_uri_int64 sqlite3_api->uri_int64
|
||||||
#define sqlite3_uri_parameter sqlite3_api->uri_parameter
|
#define sqlite3_uri_parameter sqlite3_api->uri_parameter
|
||||||
#define sqlite3_uri_vsnprintf sqlite3_api->vsnprintf
|
#define sqlite3_uri_vsnprintf sqlite3_api->xvsnprintf
|
||||||
#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2
|
#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2
|
||||||
/* Version 3.8.7 and later */
|
/* Version 3.8.7 and later */
|
||||||
#define sqlite3_auto_extension sqlite3_api->auto_extension
|
#define sqlite3_auto_extension sqlite3_api->auto_extension
|
||||||
@ -540,6 +580,40 @@ typedef int (*sqlite3_loadext_entry)(
|
|||||||
/* Version 3.14.0 and later */
|
/* Version 3.14.0 and later */
|
||||||
#define sqlite3_trace_v2 sqlite3_api->trace_v2
|
#define sqlite3_trace_v2 sqlite3_api->trace_v2
|
||||||
#define sqlite3_expanded_sql sqlite3_api->expanded_sql
|
#define sqlite3_expanded_sql sqlite3_api->expanded_sql
|
||||||
|
/* Version 3.18.0 and later */
|
||||||
|
#define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid
|
||||||
|
/* Version 3.20.0 and later */
|
||||||
|
#define sqlite3_prepare_v3 sqlite3_api->prepare_v3
|
||||||
|
#define sqlite3_prepare16_v3 sqlite3_api->prepare16_v3
|
||||||
|
#define sqlite3_bind_pointer sqlite3_api->bind_pointer
|
||||||
|
#define sqlite3_result_pointer sqlite3_api->result_pointer
|
||||||
|
#define sqlite3_value_pointer sqlite3_api->value_pointer
|
||||||
|
/* Version 3.22.0 and later */
|
||||||
|
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
|
||||||
|
#define sqlite3_value_nochange sqlite3_api->value_nochange
|
||||||
|
#define sqlite3_vtab_collation sqlite3_api->vtab_collation
|
||||||
|
/* Version 3.24.0 and later */
|
||||||
|
#define sqlite3_keyword_count sqlite3_api->keyword_count
|
||||||
|
#define sqlite3_keyword_name sqlite3_api->keyword_name
|
||||||
|
#define sqlite3_keyword_check sqlite3_api->keyword_check
|
||||||
|
#define sqlite3_str_new sqlite3_api->str_new
|
||||||
|
#define sqlite3_str_finish sqlite3_api->str_finish
|
||||||
|
#define sqlite3_str_appendf sqlite3_api->str_appendf
|
||||||
|
#define sqlite3_str_vappendf sqlite3_api->str_vappendf
|
||||||
|
#define sqlite3_str_append sqlite3_api->str_append
|
||||||
|
#define sqlite3_str_appendall sqlite3_api->str_appendall
|
||||||
|
#define sqlite3_str_appendchar sqlite3_api->str_appendchar
|
||||||
|
#define sqlite3_str_reset sqlite3_api->str_reset
|
||||||
|
#define sqlite3_str_errcode sqlite3_api->str_errcode
|
||||||
|
#define sqlite3_str_length sqlite3_api->str_length
|
||||||
|
#define sqlite3_str_value sqlite3_api->str_value
|
||||||
|
/* Version 3.25.0 and later */
|
||||||
|
#define sqlite3_create_window_function sqlite3_api->create_window_function
|
||||||
|
/* Version 3.26.0 and later */
|
||||||
|
#define sqlite3_normalized_sql sqlite3_api->normalized_sql
|
||||||
|
/* Version 3.28.0 and later */
|
||||||
|
#define sqlite3_stmt_isexplain sqlite3_api->isexplain
|
||||||
|
#define sqlite3_value_frombind sqlite3_api->frombind
|
||||||
#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)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::os::raw::c_int;
|
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
/// Error Codes
|
/// Error Codes
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
@ -64,42 +64,47 @@ pub struct Error {
|
|||||||
impl Error {
|
impl Error {
|
||||||
pub fn new(result_code: c_int) -> Error {
|
pub fn new(result_code: c_int) -> Error {
|
||||||
let code = match result_code & 0xff {
|
let code = match result_code & 0xff {
|
||||||
super::SQLITE_INTERNAL => ErrorCode::InternalMalfunction,
|
super::SQLITE_INTERNAL => ErrorCode::InternalMalfunction,
|
||||||
super::SQLITE_PERM => ErrorCode::PermissionDenied,
|
super::SQLITE_PERM => ErrorCode::PermissionDenied,
|
||||||
super::SQLITE_ABORT => ErrorCode::OperationAborted,
|
super::SQLITE_ABORT => ErrorCode::OperationAborted,
|
||||||
super::SQLITE_BUSY => ErrorCode::DatabaseBusy,
|
super::SQLITE_BUSY => ErrorCode::DatabaseBusy,
|
||||||
super::SQLITE_LOCKED => ErrorCode::DatabaseLocked,
|
super::SQLITE_LOCKED => ErrorCode::DatabaseLocked,
|
||||||
super::SQLITE_NOMEM => ErrorCode::OutOfMemory,
|
super::SQLITE_NOMEM => ErrorCode::OutOfMemory,
|
||||||
super::SQLITE_READONLY => ErrorCode::ReadOnly,
|
super::SQLITE_READONLY => ErrorCode::ReadOnly,
|
||||||
super::SQLITE_INTERRUPT => ErrorCode::OperationInterrupted,
|
super::SQLITE_INTERRUPT => ErrorCode::OperationInterrupted,
|
||||||
super::SQLITE_IOERR => ErrorCode::SystemIOFailure,
|
super::SQLITE_IOERR => ErrorCode::SystemIOFailure,
|
||||||
super::SQLITE_CORRUPT => ErrorCode::DatabaseCorrupt,
|
super::SQLITE_CORRUPT => ErrorCode::DatabaseCorrupt,
|
||||||
super::SQLITE_NOTFOUND => ErrorCode::NotFound,
|
super::SQLITE_NOTFOUND => ErrorCode::NotFound,
|
||||||
super::SQLITE_FULL => ErrorCode::DiskFull,
|
super::SQLITE_FULL => ErrorCode::DiskFull,
|
||||||
super::SQLITE_CANTOPEN => ErrorCode::CannotOpen,
|
super::SQLITE_CANTOPEN => ErrorCode::CannotOpen,
|
||||||
super::SQLITE_PROTOCOL => ErrorCode::FileLockingProtocolFailed,
|
super::SQLITE_PROTOCOL => ErrorCode::FileLockingProtocolFailed,
|
||||||
super::SQLITE_SCHEMA => ErrorCode::SchemaChanged,
|
super::SQLITE_SCHEMA => ErrorCode::SchemaChanged,
|
||||||
super::SQLITE_TOOBIG => ErrorCode::TooBig,
|
super::SQLITE_TOOBIG => ErrorCode::TooBig,
|
||||||
super::SQLITE_CONSTRAINT=> ErrorCode::ConstraintViolation,
|
super::SQLITE_CONSTRAINT => ErrorCode::ConstraintViolation,
|
||||||
super::SQLITE_MISMATCH => ErrorCode::TypeMismatch,
|
super::SQLITE_MISMATCH => ErrorCode::TypeMismatch,
|
||||||
super::SQLITE_MISUSE => ErrorCode::APIMisuse,
|
super::SQLITE_MISUSE => ErrorCode::APIMisuse,
|
||||||
super::SQLITE_NOLFS => ErrorCode::NoLargeFileSupport,
|
super::SQLITE_NOLFS => ErrorCode::NoLargeFileSupport,
|
||||||
super::SQLITE_AUTH => ErrorCode::AuthorizationForStatementDenied,
|
super::SQLITE_AUTH => ErrorCode::AuthorizationForStatementDenied,
|
||||||
super::SQLITE_RANGE => ErrorCode::ParameterOutOfRange,
|
super::SQLITE_RANGE => ErrorCode::ParameterOutOfRange,
|
||||||
super::SQLITE_NOTADB => ErrorCode::NotADatabase,
|
super::SQLITE_NOTADB => ErrorCode::NotADatabase,
|
||||||
_ => ErrorCode::Unknown,
|
_ => ErrorCode::Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
Error {
|
Error {
|
||||||
code: code,
|
code,
|
||||||
extended_code: result_code,
|
extended_code: result_code,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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!(f, "Error code {}: {}", self.extended_code, code_to_str(self.extended_code))
|
write!(
|
||||||
|
f,
|
||||||
|
"Error code {}: {}",
|
||||||
|
self.extended_code,
|
||||||
|
code_to_str(self.extended_code)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,52 +115,53 @@ 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;
|
||||||
|
|
||||||
// Extended result codes.
|
// Extended result codes.
|
||||||
|
|
||||||
const SQLITE_IOERR_SHMOPEN : c_int = (super::SQLITE_IOERR | (18<<8));
|
const SQLITE_IOERR_SHMOPEN: c_int = (super::SQLITE_IOERR | (18 << 8));
|
||||||
const SQLITE_IOERR_SHMSIZE : c_int = (super::SQLITE_IOERR | (19<<8));
|
const SQLITE_IOERR_SHMSIZE: c_int = (super::SQLITE_IOERR | (19 << 8));
|
||||||
const SQLITE_IOERR_SHMLOCK : c_int = (super::SQLITE_IOERR | (20<<8));
|
const SQLITE_IOERR_SHMLOCK: c_int = (super::SQLITE_IOERR | (20 << 8));
|
||||||
const SQLITE_IOERR_SHMMAP : c_int = (super::SQLITE_IOERR | (21<<8));
|
const SQLITE_IOERR_SHMMAP: c_int = (super::SQLITE_IOERR | (21 << 8));
|
||||||
const SQLITE_IOERR_SEEK : c_int = (super::SQLITE_IOERR | (22<<8));
|
const SQLITE_IOERR_SEEK: c_int = (super::SQLITE_IOERR | (22 << 8));
|
||||||
const SQLITE_IOERR_DELETE_NOENT : c_int = (super::SQLITE_IOERR | (23<<8));
|
const SQLITE_IOERR_DELETE_NOENT: c_int = (super::SQLITE_IOERR | (23 << 8));
|
||||||
const SQLITE_IOERR_MMAP : c_int = (super::SQLITE_IOERR | (24<<8));
|
const SQLITE_IOERR_MMAP: c_int = (super::SQLITE_IOERR | (24 << 8));
|
||||||
const SQLITE_IOERR_GETTEMPPATH : c_int = (super::SQLITE_IOERR | (25<<8));
|
const SQLITE_IOERR_GETTEMPPATH: c_int = (super::SQLITE_IOERR | (25 << 8));
|
||||||
const SQLITE_IOERR_CONVPATH : c_int = (super::SQLITE_IOERR | (26<<8));
|
const SQLITE_IOERR_CONVPATH: c_int = (super::SQLITE_IOERR | (26 << 8));
|
||||||
const SQLITE_IOERR_VNODE : c_int = (super::SQLITE_IOERR | (27<<8));
|
const SQLITE_IOERR_VNODE: c_int = (super::SQLITE_IOERR | (27 << 8));
|
||||||
const SQLITE_LOCKED_SHAREDCACHE : c_int = (super::SQLITE_LOCKED | (1<<8));
|
const SQLITE_LOCKED_SHAREDCACHE: c_int = (super::SQLITE_LOCKED | (1 << 8));
|
||||||
const SQLITE_BUSY_RECOVERY : c_int = (super::SQLITE_BUSY | (1<<8));
|
const SQLITE_BUSY_RECOVERY: c_int = (super::SQLITE_BUSY | (1 << 8));
|
||||||
const SQLITE_BUSY_SNAPSHOT : c_int = (super::SQLITE_BUSY | (2<<8));
|
const SQLITE_BUSY_SNAPSHOT: c_int = (super::SQLITE_BUSY | (2 << 8));
|
||||||
const SQLITE_CANTOPEN_NOTEMPDIR : c_int = (super::SQLITE_CANTOPEN | (1<<8));
|
const SQLITE_CANTOPEN_NOTEMPDIR: c_int = (super::SQLITE_CANTOPEN | (1 << 8));
|
||||||
const SQLITE_CANTOPEN_ISDIR : c_int = (super::SQLITE_CANTOPEN | (2<<8));
|
const SQLITE_CANTOPEN_ISDIR: c_int = (super::SQLITE_CANTOPEN | (2 << 8));
|
||||||
const SQLITE_CANTOPEN_FULLPATH : c_int = (super::SQLITE_CANTOPEN | (3<<8));
|
const SQLITE_CANTOPEN_FULLPATH: c_int = (super::SQLITE_CANTOPEN | (3 << 8));
|
||||||
const SQLITE_CANTOPEN_CONVPATH : c_int = (super::SQLITE_CANTOPEN | (4<<8));
|
const SQLITE_CANTOPEN_CONVPATH: c_int = (super::SQLITE_CANTOPEN | (4 << 8));
|
||||||
const SQLITE_CORRUPT_VTAB : c_int = (super::SQLITE_CORRUPT | (1<<8));
|
const SQLITE_CORRUPT_VTAB: c_int = (super::SQLITE_CORRUPT | (1 << 8));
|
||||||
const SQLITE_READONLY_RECOVERY : c_int = (super::SQLITE_READONLY | (1<<8));
|
const SQLITE_READONLY_RECOVERY: c_int = (super::SQLITE_READONLY | (1 << 8));
|
||||||
const SQLITE_READONLY_CANTLOCK : c_int = (super::SQLITE_READONLY | (2<<8));
|
const SQLITE_READONLY_CANTLOCK: c_int = (super::SQLITE_READONLY | (2 << 8));
|
||||||
const SQLITE_READONLY_ROLLBACK : c_int = (super::SQLITE_READONLY | (3<<8));
|
const SQLITE_READONLY_ROLLBACK: c_int = (super::SQLITE_READONLY | (3 << 8));
|
||||||
const SQLITE_READONLY_DBMOVED : c_int = (super::SQLITE_READONLY | (4<<8));
|
const SQLITE_READONLY_DBMOVED: c_int = (super::SQLITE_READONLY | (4 << 8));
|
||||||
const SQLITE_ABORT_ROLLBACK : c_int = (super::SQLITE_ABORT | (2<<8));
|
const SQLITE_ABORT_ROLLBACK: c_int = (super::SQLITE_ABORT | (2 << 8));
|
||||||
const SQLITE_CONSTRAINT_CHECK : c_int = (super::SQLITE_CONSTRAINT | (1<<8));
|
const SQLITE_CONSTRAINT_CHECK: c_int = (super::SQLITE_CONSTRAINT | (1 << 8));
|
||||||
const SQLITE_CONSTRAINT_COMMITHOOK : c_int = (super::SQLITE_CONSTRAINT | (2<<8));
|
const SQLITE_CONSTRAINT_COMMITHOOK: c_int = (super::SQLITE_CONSTRAINT | (2 << 8));
|
||||||
const SQLITE_CONSTRAINT_FOREIGNKEY : c_int = (super::SQLITE_CONSTRAINT | (3<<8));
|
const SQLITE_CONSTRAINT_FOREIGNKEY: c_int = (super::SQLITE_CONSTRAINT | (3 << 8));
|
||||||
const SQLITE_CONSTRAINT_FUNCTION : c_int = (super::SQLITE_CONSTRAINT | (4<<8));
|
const SQLITE_CONSTRAINT_FUNCTION: c_int = (super::SQLITE_CONSTRAINT | (4 << 8));
|
||||||
const SQLITE_CONSTRAINT_NOTNULL : c_int = (super::SQLITE_CONSTRAINT | (5<<8));
|
const SQLITE_CONSTRAINT_NOTNULL: c_int = (super::SQLITE_CONSTRAINT | (5 << 8));
|
||||||
const SQLITE_CONSTRAINT_PRIMARYKEY : c_int = (super::SQLITE_CONSTRAINT | (6<<8));
|
const SQLITE_CONSTRAINT_PRIMARYKEY: c_int = (super::SQLITE_CONSTRAINT | (6 << 8));
|
||||||
const SQLITE_CONSTRAINT_TRIGGER : c_int = (super::SQLITE_CONSTRAINT | (7<<8));
|
const SQLITE_CONSTRAINT_TRIGGER: c_int = (super::SQLITE_CONSTRAINT | (7 << 8));
|
||||||
const SQLITE_CONSTRAINT_UNIQUE : c_int = (super::SQLITE_CONSTRAINT | (8<<8));
|
const SQLITE_CONSTRAINT_UNIQUE: c_int = (super::SQLITE_CONSTRAINT | (8 << 8));
|
||||||
const SQLITE_CONSTRAINT_VTAB : c_int = (super::SQLITE_CONSTRAINT | (9<<8));
|
const SQLITE_CONSTRAINT_VTAB: c_int = (super::SQLITE_CONSTRAINT | (9 << 8));
|
||||||
const SQLITE_CONSTRAINT_ROWID : c_int = (super::SQLITE_CONSTRAINT |(10<<8));
|
const SQLITE_CONSTRAINT_ROWID: c_int = (super::SQLITE_CONSTRAINT | (10 << 8));
|
||||||
const SQLITE_NOTICE_RECOVER_WAL : c_int = (SQLITE_NOTICE | (1<<8));
|
const SQLITE_NOTICE_RECOVER_WAL: c_int = (SQLITE_NOTICE | (1 << 8));
|
||||||
const SQLITE_NOTICE_RECOVER_ROLLBACK : c_int = (SQLITE_NOTICE | (2<<8));
|
const SQLITE_NOTICE_RECOVER_ROLLBACK: c_int = (SQLITE_NOTICE | (2 << 8));
|
||||||
const SQLITE_WARNING_AUTOINDEX : c_int = (SQLITE_WARNING | (1<<8));
|
const SQLITE_WARNING_AUTOINDEX: c_int = (SQLITE_WARNING | (1 << 8));
|
||||||
const SQLITE_AUTH_USER : c_int = (super::SQLITE_AUTH | (1<<8));
|
const SQLITE_AUTH_USER: c_int = (super::SQLITE_AUTH | (1 << 8));
|
||||||
|
|
||||||
pub fn code_to_str(code: c_int) -> &'static str {
|
pub fn code_to_str(code: c_int) -> &'static str {
|
||||||
match code {
|
match code {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
pub use self::error::*;
|
pub use self::error::*;
|
||||||
|
|
||||||
|
use std::default::Default;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
@ -15,33 +16,52 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
|
include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
|
||||||
|
|
||||||
|
pub type sqlite3_index_constraint = sqlite3_index_info_sqlite3_index_constraint;
|
||||||
|
pub type sqlite3_index_constraint_usage = sqlite3_index_info_sqlite3_index_constraint_usage;
|
||||||
|
|
||||||
|
impl Default for sqlite3_vtab {
|
||||||
|
fn default() -> Self {
|
||||||
|
unsafe { mem::zeroed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for sqlite3_vtab_cursor {
|
||||||
|
fn default() -> Self {
|
||||||
|
unsafe { mem::zeroed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
26
libsqlite3-sys/upgrade.sh
Executable file
26
libsqlite3-sys/upgrade.sh
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
SCRIPT_DIR=$(cd "$(dirname "$_")" && pwd)
|
||||||
|
echo $SCRIPT_DIR
|
||||||
|
cd $SCRIPT_DIR
|
||||||
|
export SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
|
||||||
|
|
||||||
|
# Download and extract amalgamation
|
||||||
|
SQLITE=sqlite-amalgamation-3290000
|
||||||
|
curl -O http://sqlite.org/2019/$SQLITE.zip
|
||||||
|
unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c
|
||||||
|
unzip -p $SQLITE.zip $SQLITE/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h
|
||||||
|
unzip -p $SQLITE.zip $SQLITE/sqlite3ext.h > $SQLITE3_LIB_DIR/sqlite3ext.h
|
||||||
|
rm -f $SQLITE.zip
|
||||||
|
|
||||||
|
# Regenerate bindgen file
|
||||||
|
rm -f $SQLITE3_LIB_DIR/bindgen_bundled_version.rs
|
||||||
|
export SQLITE3_INCLUDE_DIR=$SQLITE3_LIB_DIR
|
||||||
|
cargo update
|
||||||
|
# Just to make sure there is only one bindgen.rs file in target dir
|
||||||
|
find $SCRIPT_DIR/../target -type f -name bindgen.rs -exec rm {} \;
|
||||||
|
cargo build --features "buildtime_bindgen" --no-default-features
|
||||||
|
find $SCRIPT_DIR/../target -type f -name bindgen.rs -exec cp {} $SQLITE3_LIB_DIR/bindgen_bundled_version.rs \;
|
||||||
|
# Sanity check
|
||||||
|
cd $SCRIPT_DIR/..
|
||||||
|
cargo update
|
||||||
|
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'
|
203
src/backup.rs
203
src/backup.rs
@ -18,10 +18,13 @@
|
|||||||
//! # use std::path::Path;
|
//! # use std::path::Path;
|
||||||
//! # use std::time;
|
//! # use std::time;
|
||||||
//!
|
//!
|
||||||
//! fn backup_db<P: AsRef<Path>>(src: &Connection, dst: P, progress: fn(backup::Progress))
|
//! fn backup_db<P: AsRef<Path>>(
|
||||||
//! -> Result<()> {
|
//! src: &Connection,
|
||||||
//! let mut dst = try!(Connection::open(dst));
|
//! dst: P,
|
||||||
//! let backup = try!(backup::Backup::new(src, &mut dst));
|
//! progress: fn(backup::Progress),
|
||||||
|
//! ) -> Result<()> {
|
||||||
|
//! let mut dst = Connection::open(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))
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
@ -34,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 {DatabaseName, Connection, Result};
|
use crate::error::{error_from_handle, error_from_sqlite_code};
|
||||||
use error::{error_from_sqlite_code, error_from_handle};
|
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.
|
||||||
@ -52,18 +55,19 @@ impl Connection {
|
|||||||
///
|
///
|
||||||
/// Will return `Err` if the destination path cannot be opened
|
/// Will return `Err` if the destination path cannot be opened
|
||||||
/// or if the backup fails.
|
/// or if the backup fails.
|
||||||
pub fn backup<P: AsRef<Path>>(&self,
|
pub fn backup<P: AsRef<Path>>(
|
||||||
name: DatabaseName,
|
&self,
|
||||||
dst_path: P,
|
name: DatabaseName<'_>,
|
||||||
progress: Option<fn(Progress)>)
|
dst_path: P,
|
||||||
-> Result<()> {
|
progress: Option<fn(Progress)>,
|
||||||
use self::StepResult::{More, Done, Busy, Locked};
|
) -> Result<()> {
|
||||||
let mut dst = try!(Connection::open(dst_path));
|
use self::StepResult::{Busy, Done, Locked, More};
|
||||||
let backup = try!(Backup::new_with_names(self, name, &mut dst, DatabaseName::Main));
|
let mut dst = Connection::open(dst_path)?;
|
||||||
|
let backup = Backup::new_with_names(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());
|
||||||
}
|
}
|
||||||
@ -89,20 +93,21 @@ impl Connection {
|
|||||||
///
|
///
|
||||||
/// Will return `Err` if the destination path cannot be opened
|
/// Will return `Err` if the destination path cannot be opened
|
||||||
/// or if the restore fails.
|
/// or if the restore fails.
|
||||||
pub fn restore<P: AsRef<Path>>(&mut self,
|
pub fn restore<P: AsRef<Path>, F: Fn(Progress)>(
|
||||||
name: DatabaseName,
|
&mut self,
|
||||||
src_path: P,
|
name: DatabaseName<'_>,
|
||||||
progress: Option<fn(Progress)>)
|
src_path: P,
|
||||||
-> Result<()> {
|
progress: Option<F>,
|
||||||
use self::StepResult::{More, Done, Busy, Locked};
|
) -> Result<()> {
|
||||||
let src = try!(Connection::open(src_path));
|
use self::StepResult::{Busy, Done, Locked, More};
|
||||||
let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name));
|
let src = Connection::open(src_path)?;
|
||||||
|
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(f) = progress {
|
if let Some(ref f) = progress {
|
||||||
f(restore.progress());
|
f(restore.progress());
|
||||||
}
|
}
|
||||||
if r == Busy {
|
if r == Busy {
|
||||||
@ -124,12 +129,13 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Possible successful results of calling `Backup::step`.
|
/// Possible successful results of calling `Backup::step`.
|
||||||
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum StepResult {
|
pub enum StepResult {
|
||||||
/// The backup is complete.
|
/// The backup is complete.
|
||||||
Done,
|
Done,
|
||||||
|
|
||||||
/// The step was successful but there are still more pages that need to be backed up.
|
/// The step was successful but there are still more pages that need to be
|
||||||
|
/// backed up.
|
||||||
More,
|
More,
|
||||||
|
|
||||||
/// The step failed because appropriate locks could not be aquired. This is
|
/// The step failed because appropriate locks could not be aquired. This is
|
||||||
@ -146,7 +152,7 @@ pub enum StepResult {
|
|||||||
/// backup is as of the last call to `step` - if the source database is
|
/// backup is as of the last call to `step` - if the source database is
|
||||||
/// modified after a call to `step`, the progress value will become outdated
|
/// modified after a call to `step`, the progress value will become outdated
|
||||||
/// and potentially incorrect.
|
/// and potentially incorrect.
|
||||||
#[derive(Copy,Clone,Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct Progress {
|
pub struct Progress {
|
||||||
/// Number of pages in the source database that still need to be backed up.
|
/// Number of pages in the source database that still need to be backed up.
|
||||||
pub remaining: c_int,
|
pub remaining: c_int,
|
||||||
@ -161,7 +167,7 @@ pub struct Backup<'a, 'b> {
|
|||||||
b: *mut ffi::sqlite3_backup,
|
b: *mut ffi::sqlite3_backup,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Backup<'a, 'b> {
|
impl Backup<'_, '_> {
|
||||||
/// Attempt to create a new handle that will allow backups from `from` to
|
/// Attempt to create a new handle that will allow backups from `from` to
|
||||||
/// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any
|
/// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any
|
||||||
/// API calls on the destination of a backup while the backup is taking
|
/// API calls on the destination of a backup while the backup is taking
|
||||||
@ -171,7 +177,7 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
///
|
///
|
||||||
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
|
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
|
||||||
/// `NULL`.
|
/// `NULL`.
|
||||||
pub fn new(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
|
pub fn new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
|
||||||
Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
|
Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,21 +190,24 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
///
|
///
|
||||||
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
|
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
|
||||||
/// `NULL`.
|
/// `NULL`.
|
||||||
pub fn new_with_names(from: &'a Connection,
|
pub fn new_with_names<'a, 'b>(
|
||||||
from_name: DatabaseName,
|
from: &'a Connection,
|
||||||
to: &'b mut Connection,
|
from_name: DatabaseName<'_>,
|
||||||
to_name: DatabaseName)
|
to: &'b mut Connection,
|
||||||
-> Result<Backup<'a, 'b>> {
|
to_name: DatabaseName<'_>,
|
||||||
let to_name = try!(to_name.to_cstring());
|
) -> Result<Backup<'a, 'b>> {
|
||||||
let from_name = try!(from_name.to_cstring());
|
let to_name = to_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;
|
||||||
|
|
||||||
let b = unsafe {
|
let b = unsafe {
|
||||||
let b = ffi::sqlite3_backup_init(to_db,
|
let b = ffi::sqlite3_backup_init(
|
||||||
to_name.as_ptr(),
|
to_db,
|
||||||
from.db.borrow_mut().db,
|
to_name.as_ptr(),
|
||||||
from_name.as_ptr());
|
from.db.borrow_mut().db,
|
||||||
|
from_name.as_ptr(),
|
||||||
|
);
|
||||||
if b.is_null() {
|
if b.is_null() {
|
||||||
return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
|
return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
|
||||||
}
|
}
|
||||||
@ -206,10 +215,10 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(Backup {
|
Ok(Backup {
|
||||||
phantom_from: PhantomData,
|
phantom_from: PhantomData,
|
||||||
phantom_to: PhantomData,
|
phantom_to: PhantomData,
|
||||||
b: b,
|
b,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the progress of the backup as of the last call to `step`.
|
/// Gets the progress of the backup as of the last call to `step`.
|
||||||
@ -235,7 +244,7 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
/// `LOCKED` are transient errors and are therefore returned as possible
|
/// `LOCKED` are transient errors and are therefore returned as possible
|
||||||
/// `Ok` values.
|
/// `Ok` values.
|
||||||
pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
|
pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
|
||||||
use self::StepResult::{Done, More, Busy, Locked};
|
use self::StepResult::{Busy, Done, Locked, More};
|
||||||
|
|
||||||
let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
|
let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
|
||||||
match rc {
|
match rc {
|
||||||
@ -262,17 +271,18 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if any of the calls to `step` return `Err`.
|
/// Will return `Err` if any of the calls to `step` return `Err`.
|
||||||
pub fn run_to_completion(&self,
|
pub fn run_to_completion(
|
||||||
pages_per_step: c_int,
|
&self,
|
||||||
pause_between_pages: Duration,
|
pages_per_step: c_int,
|
||||||
progress: Option<fn(Progress)>)
|
pause_between_pages: Duration,
|
||||||
-> Result<()> {
|
progress: Option<fn(Progress)>,
|
||||||
use self::StepResult::{Done, More, Busy, Locked};
|
) -> Result<()> {
|
||||||
|
use self::StepResult::{Busy, Done, Locked, More};
|
||||||
|
|
||||||
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())
|
||||||
}
|
}
|
||||||
@ -284,7 +294,7 @@ impl<'a, 'b> Backup<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Drop for Backup<'a, 'b> {
|
impl Drop for Backup<'_, '_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe { ffi::sqlite3_backup_finish(self.b) };
|
unsafe { ffi::sqlite3_backup_finish(self.b) };
|
||||||
}
|
}
|
||||||
@ -292,12 +302,11 @@ impl<'a, 'b> Drop for Backup<'a, 'b> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use {Connection, DatabaseName};
|
|
||||||
use std::time::Duration;
|
|
||||||
use super::Backup;
|
use super::Backup;
|
||||||
|
use crate::{Connection, DatabaseName, NO_PARAMS};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
|
||||||
fn test_backup() {
|
fn test_backup() {
|
||||||
let src = Connection::open_in_memory().unwrap();
|
let src = Connection::open_in_memory().unwrap();
|
||||||
let sql = "BEGIN;
|
let sql = "BEGIN;
|
||||||
@ -313,22 +322,27 @@ mod test {
|
|||||||
backup.step(-1).unwrap();
|
backup.step(-1).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap();
|
let the_answer: i64 = dst
|
||||||
|
.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(42, the_answer);
|
assert_eq!(42, the_answer);
|
||||||
|
|
||||||
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
|
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
let backup = Backup::new(&src, &mut dst).unwrap();
|
let backup = Backup::new(&src, &mut dst).unwrap();
|
||||||
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
|
backup
|
||||||
|
.run_to_completion(5, Duration::from_millis(250), None)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap();
|
let the_answer: i64 = dst
|
||||||
|
.query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(42 + 43, the_answer);
|
assert_eq!(42 + 43, the_answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
|
||||||
fn test_backup_temp() {
|
fn test_backup_temp() {
|
||||||
let src = Connection::open_in_memory().unwrap();
|
let src = Connection::open_in_memory().unwrap();
|
||||||
let sql = "BEGIN;
|
let sql = "BEGIN;
|
||||||
@ -340,34 +354,35 @@ mod test {
|
|||||||
let mut dst = Connection::open_in_memory().unwrap();
|
let mut dst = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
let backup = Backup::new_with_names(&src,
|
let backup =
|
||||||
DatabaseName::Temp,
|
Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
|
||||||
&mut dst,
|
.unwrap();
|
||||||
DatabaseName::Main)
|
|
||||||
.unwrap();
|
|
||||||
backup.step(-1).unwrap();
|
backup.step(-1).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap();
|
let the_answer: i64 = dst
|
||||||
|
.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(42, the_answer);
|
assert_eq!(42, the_answer);
|
||||||
|
|
||||||
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
|
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
let backup = Backup::new_with_names(&src,
|
let backup =
|
||||||
DatabaseName::Temp,
|
Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
|
||||||
&mut dst,
|
.unwrap();
|
||||||
DatabaseName::Main)
|
backup
|
||||||
.unwrap();
|
.run_to_completion(5, Duration::from_millis(250), None)
|
||||||
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap();
|
let the_answer: i64 = dst
|
||||||
|
.query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(42 + 43, the_answer);
|
assert_eq!(42 + 43, the_answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
|
||||||
fn test_backup_attached() {
|
fn test_backup_attached() {
|
||||||
let src = Connection::open_in_memory().unwrap();
|
let src = Connection::open_in_memory().unwrap();
|
||||||
let sql = "ATTACH DATABASE ':memory:' AS my_attached;
|
let sql = "ATTACH DATABASE ':memory:' AS my_attached;
|
||||||
@ -380,29 +395,39 @@ mod test {
|
|||||||
let mut dst = Connection::open_in_memory().unwrap();
|
let mut dst = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
let backup = Backup::new_with_names(&src,
|
let backup = Backup::new_with_names(
|
||||||
DatabaseName::Attached("my_attached"),
|
&src,
|
||||||
&mut dst,
|
DatabaseName::Attached("my_attached"),
|
||||||
DatabaseName::Main)
|
&mut dst,
|
||||||
.unwrap();
|
DatabaseName::Main,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
backup.step(-1).unwrap();
|
backup.step(-1).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap();
|
let the_answer: i64 = dst
|
||||||
|
.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(42, the_answer);
|
assert_eq!(42, the_answer);
|
||||||
|
|
||||||
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
|
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
let backup = Backup::new_with_names(&src,
|
let backup = Backup::new_with_names(
|
||||||
DatabaseName::Attached("my_attached"),
|
&src,
|
||||||
&mut dst,
|
DatabaseName::Attached("my_attached"),
|
||||||
DatabaseName::Main)
|
&mut dst,
|
||||||
.unwrap();
|
DatabaseName::Main,
|
||||||
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
backup
|
||||||
|
.run_to_completion(5, Duration::from_millis(250), None)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap();
|
let the_answer: i64 = dst
|
||||||
|
.query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(42 + 43, the_answer);
|
assert_eq!(42 + 43, the_answer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
220
src/blob.rs
220
src/blob.rs
@ -1,61 +1,64 @@
|
|||||||
//! Incremental BLOB I/O.
|
//! Incremental BLOB I/O.
|
||||||
//!
|
//!
|
||||||
//! Note that SQLite does not provide API-level access to change the size of a BLOB; that must
|
//! Note that SQLite does not provide API-level access to change the size of a
|
||||||
//! be performed through SQL statements.
|
//! BLOB; that must be performed through SQL statements.
|
||||||
//!
|
//!
|
||||||
//! `Blob` conforms to `std::io::Read`, `std::io::Write`, and `std::io::Seek`, so it plays
|
//! `Blob` conforms to `std::io::Read`, `std::io::Write`, and `std::io::Seek`,
|
||||||
//! nicely with other types that build on these (such as `std::io::BufReader` and
|
//! so it plays nicely with other types that build on these (such as
|
||||||
//! `std::io::BufWriter`). However, you must be careful with the size of the blob. For example,
|
//! `std::io::BufReader` and `std::io::BufWriter`). However, you must be
|
||||||
//! when using a `BufWriter`, the `BufWriter` will accept more data than the `Blob` will allow,
|
//! careful with the size of the blob. For example, when using a `BufWriter`,
|
||||||
//! so make sure to call `flush` and check for errors. (See the unit tests in this module for
|
//! the `BufWriter` will accept more data than the `Blob`
|
||||||
//! an example.)
|
//! will allow, so make sure to call `flush` and check for errors. (See the
|
||||||
|
//! unit tests in this module for an example.)
|
||||||
//!
|
//!
|
||||||
//! ## Example
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! extern crate libsqlite3_sys;
|
|
||||||
//! extern crate rusqlite;
|
|
||||||
//!
|
|
||||||
//! use rusqlite::{Connection, DatabaseName};
|
|
||||||
//! use rusqlite::blob::ZeroBlob;
|
//! use rusqlite::blob::ZeroBlob;
|
||||||
//! use std::io::{Read, Write, Seek, SeekFrom};
|
//! use rusqlite::{Connection, DatabaseName, NO_PARAMS};
|
||||||
|
//! use std::error::Error;
|
||||||
|
//! use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() -> Result<(), Box<Error>> {
|
||||||
//! let db = Connection::open_in_memory().unwrap();
|
//! let db = Connection::open_in_memory()?;
|
||||||
//! db.execute_batch("CREATE TABLE test (content BLOB);").unwrap();
|
//! db.execute_batch("CREATE TABLE test (content BLOB);")?;
|
||||||
//! db.execute("INSERT INTO test (content) VALUES (ZEROBLOB(10))", &[]).unwrap();
|
//! db.execute(
|
||||||
|
//! "INSERT INTO test (content) VALUES (ZEROBLOB(10))",
|
||||||
|
//! NO_PARAMS,
|
||||||
|
//! )?;
|
||||||
//!
|
//!
|
||||||
//! let rowid = db.last_insert_rowid();
|
//! let rowid = db.last_insert_rowid();
|
||||||
//! let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false).unwrap();
|
//! let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
|
||||||
//!
|
//!
|
||||||
//! // Make sure to test that the number of bytes written matches what you expect;
|
//! // Make sure to test that the number of bytes written matches what you expect;
|
||||||
//! // if you try to write too much, the data will be truncated to the size of the BLOB.
|
//! // if you try to write too much, the data will be truncated to the size of the
|
||||||
//! let bytes_written = blob.write(b"01234567").unwrap();
|
//! // BLOB.
|
||||||
|
//! let bytes_written = blob.write(b"01234567")?;
|
||||||
//! assert_eq!(bytes_written, 8);
|
//! assert_eq!(bytes_written, 8);
|
||||||
//!
|
//!
|
||||||
//! // Same guidance - make sure you check the number of bytes read!
|
//! // Same guidance - make sure you check the number of bytes read!
|
||||||
//! blob.seek(SeekFrom::Start(0)).unwrap();
|
//! blob.seek(SeekFrom::Start(0))?;
|
||||||
//! let mut buf = [0u8; 20];
|
//! let mut buf = [0u8; 20];
|
||||||
//! let bytes_read = blob.read(&mut buf[..]).unwrap();
|
//! let bytes_read = blob.read(&mut buf[..])?;
|
||||||
//! assert_eq!(bytes_read, 10); // note we read 10 bytes because the blob has size 10
|
//! assert_eq!(bytes_read, 10); // note we read 10 bytes because the blob has size 10
|
||||||
//!
|
//!
|
||||||
//! db.execute("INSERT INTO test (content) VALUES (?)", &[&ZeroBlob(64)]).unwrap();
|
//! db.execute("INSERT INTO test (content) VALUES (?)", &[ZeroBlob(64)])?;
|
||||||
//!
|
//!
|
||||||
//! // given a new row ID, we can reopen the blob on that row
|
//! // given a new row ID, we can reopen the blob on that row
|
||||||
//! let rowid = db.last_insert_rowid();
|
//! let rowid = db.last_insert_rowid();
|
||||||
//! blob.reopen(rowid).unwrap();
|
//! blob.reopen(rowid)?;
|
||||||
//!
|
//!
|
||||||
//! assert_eq!(blob.size(), 64);
|
//! assert_eq!(blob.size(), 64);
|
||||||
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use std::io;
|
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::mem;
|
use std::io;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
use super::types::{ToSql, ToSqlOutput};
|
use super::types::{ToSql, ToSqlOutput};
|
||||||
use {Result, Connection, DatabaseName};
|
use crate::{Connection, DatabaseName, Result};
|
||||||
|
|
||||||
/// Handle to an open BLOB.
|
/// Handle to an open BLOB.
|
||||||
pub struct Blob<'conn> {
|
pub struct Blob<'conn> {
|
||||||
@ -65,45 +68,47 @@ pub struct Blob<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Open a handle to the BLOB located in `row`, `column`, `table` in database `db`.
|
/// Open a handle to the BLOB located in `row_id`, `column`, `table` in
|
||||||
|
/// database `db`.
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if `db`/`table`/`column` cannot be converted to a C-compatible string
|
/// Will return `Err` if `db`/`table`/`column` cannot be converted to a
|
||||||
/// or if the underlying SQLite BLOB open call fails.
|
/// C-compatible string or if the underlying SQLite BLOB open call
|
||||||
pub fn blob_open<'a>(&'a self,
|
/// fails.
|
||||||
db: DatabaseName,
|
pub fn blob_open<'a>(
|
||||||
table: &str,
|
&'a self,
|
||||||
column: &str,
|
db: DatabaseName<'_>,
|
||||||
row: i64,
|
table: &str,
|
||||||
read_only: bool)
|
column: &str,
|
||||||
-> Result<Blob<'a>> {
|
row_id: i64,
|
||||||
|
read_only: bool,
|
||||||
|
) -> 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(c.db(),
|
ffi::sqlite3_blob_open(
|
||||||
db.as_ptr(),
|
c.db(),
|
||||||
table.as_ptr(),
|
db.as_ptr(),
|
||||||
column.as_ptr(),
|
table.as_ptr(),
|
||||||
row,
|
column.as_ptr(),
|
||||||
if read_only { 0 } else { 1 },
|
row_id,
|
||||||
&mut blob)
|
if read_only { 0 } else { 1 },
|
||||||
|
&mut blob,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
c.decode_result(rc)
|
c.decode_result(rc).map(|_| Blob {
|
||||||
.map(|_| {
|
conn: self,
|
||||||
Blob {
|
blob,
|
||||||
conn: self,
|
pos: 0,
|
||||||
blob: blob,
|
})
|
||||||
pos: 0,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Blob<'conn> {
|
impl Blob<'_> {
|
||||||
/// Move a BLOB handle to a new row.
|
/// Move a BLOB handle to a new row.
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
@ -125,8 +130,9 @@ impl<'conn> Blob<'conn> {
|
|||||||
|
|
||||||
/// Close a BLOB handle.
|
/// Close a BLOB handle.
|
||||||
///
|
///
|
||||||
/// Calling `close` explicitly is not required (the BLOB will be closed when the
|
/// Calling `close` explicitly is not required (the BLOB will be closed
|
||||||
/// `Blob` is dropped), but it is available so you can get any errors that occur.
|
/// when the `Blob` is dropped), but it is available so you can get any
|
||||||
|
/// errors that occur.
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
@ -142,9 +148,9 @@ impl<'conn> Blob<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> io::Read for Blob<'conn> {
|
impl io::Read for Blob<'_> {
|
||||||
/// Read data from a BLOB incrementally. Will return Ok(0) if the end of the blob
|
/// Read data from a BLOB incrementally. Will return Ok(0) if the end of
|
||||||
/// has been reached.
|
/// the blob has been reached.
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
@ -155,25 +161,25 @@ impl<'conn> io::Read for Blob<'conn> {
|
|||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
let rc =
|
let rc = unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
|
||||||
unsafe { ffi::sqlite3_blob_read(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) };
|
|
||||||
self.conn
|
self.conn
|
||||||
.decode_result(rc)
|
.decode_result(rc)
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
self.pos += n;
|
self.pos += n;
|
||||||
n as usize
|
n as usize
|
||||||
})
|
})
|
||||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> io::Write for Blob<'conn> {
|
impl io::Write for Blob<'_> {
|
||||||
/// Write data into a BLOB incrementally. Will return `Ok(0)` if the end of the blob
|
/// Write data into a BLOB incrementally. Will return `Ok(0)` if the end of
|
||||||
/// has been reached; consider using `Write::write_all(buf)` if you want to get an
|
/// the blob has been reached; consider using `Write::write_all(buf)`
|
||||||
/// error if the entirety of the buffer cannot be written.
|
/// if you want to get an error if the entirety of the buffer cannot be
|
||||||
|
/// written.
|
||||||
///
|
///
|
||||||
/// This function may only modify the contents of the BLOB; it is not possible to increase
|
/// This function may only modify the contents of the BLOB; it is not
|
||||||
/// the size of a BLOB using this API.
|
/// possible to increase the size of a BLOB using this API.
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
@ -184,15 +190,13 @@ impl<'conn> io::Write for Blob<'conn> {
|
|||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
let rc = unsafe {
|
let rc = unsafe { ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
|
||||||
ffi::sqlite3_blob_write(self.blob, mem::transmute(buf.as_ptr()), n, self.pos)
|
|
||||||
};
|
|
||||||
self.conn
|
self.conn
|
||||||
.decode_result(rc)
|
.decode_result(rc)
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
self.pos += n;
|
self.pos += n;
|
||||||
n as usize
|
n as usize
|
||||||
})
|
})
|
||||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,21 +205,25 @@ impl<'conn> io::Write for Blob<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> io::Seek for Blob<'conn> {
|
impl io::Seek for Blob<'_> {
|
||||||
/// Seek to an offset, in bytes, in BLOB.
|
/// Seek to an offset, in bytes, in BLOB.
|
||||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||||
let pos = match pos {
|
let pos = match pos {
|
||||||
io::SeekFrom::Start(offset) => offset as i64,
|
io::SeekFrom::Start(offset) => offset as i64,
|
||||||
io::SeekFrom::Current(offset) => self.pos as i64 + offset,
|
io::SeekFrom::Current(offset) => i64::from(self.pos) + offset,
|
||||||
io::SeekFrom::End(offset) => self.size() as i64 + offset,
|
io::SeekFrom::End(offset) => i64::from(self.size()) + offset,
|
||||||
};
|
};
|
||||||
|
|
||||||
if pos < 0 {
|
if pos < 0 {
|
||||||
Err(io::Error::new(io::ErrorKind::InvalidInput,
|
Err(io::Error::new(
|
||||||
"invalid seek to negative position"))
|
io::ErrorKind::InvalidInput,
|
||||||
} else if pos > self.size() as i64 {
|
"invalid seek to negative position",
|
||||||
Err(io::Error::new(io::ErrorKind::InvalidInput,
|
))
|
||||||
"invalid seek to position past end of blob"))
|
} else if pos > i64::from(self.size()) {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"invalid seek to position past end of blob",
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
self.pos = pos as i32;
|
self.pos = pos as i32;
|
||||||
Ok(pos as u64)
|
Ok(pos as u64)
|
||||||
@ -224,7 +232,7 @@ impl<'conn> io::Seek for Blob<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
impl<'conn> Drop for Blob<'conn> {
|
impl Drop for Blob<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.close_();
|
self.close_();
|
||||||
}
|
}
|
||||||
@ -232,15 +240,15 @@ impl<'conn> Drop for Blob<'conn> {
|
|||||||
|
|
||||||
/// BLOB of length N that is filled with zeroes.
|
/// BLOB of length N that is filled with zeroes.
|
||||||
///
|
///
|
||||||
/// Zeroblobs are intended to serve as placeholders for BLOBs whose content is later written using
|
/// Zeroblobs are intended to serve as placeholders for BLOBs whose content is
|
||||||
/// incremental BLOB I/O routines.
|
/// later written using incremental BLOB I/O routines.
|
||||||
///
|
///
|
||||||
/// A negative value for the zeroblob results in a zero-length BLOB.
|
/// A negative value for the zeroblob results in a zero-length BLOB.
|
||||||
#[derive(Copy,Clone)]
|
#[derive(Copy, Clone)]
|
||||||
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))
|
||||||
}
|
}
|
||||||
@ -248,17 +256,16 @@ impl ToSql for ZeroBlob {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::io::{BufReader, BufRead, BufWriter, Read, Write, Seek, SeekFrom};
|
use crate::{Connection, DatabaseName, Result};
|
||||||
use {Connection, DatabaseName, Result};
|
use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
|
||||||
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))
|
||||||
}
|
}
|
||||||
@ -267,7 +274,8 @@ mod test {
|
|||||||
fn test_blob() {
|
fn test_blob() {
|
||||||
let (db, rowid) = db_with_test_blob().unwrap();
|
let (db, rowid) = db_with_test_blob().unwrap();
|
||||||
|
|
||||||
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
let mut blob = db
|
||||||
|
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(4, blob.write(b"Clob").unwrap());
|
assert_eq!(4, blob.write(b"Clob").unwrap());
|
||||||
assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10
|
assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10
|
||||||
@ -276,7 +284,8 @@ mod test {
|
|||||||
blob.reopen(rowid).unwrap();
|
blob.reopen(rowid).unwrap();
|
||||||
blob.close().unwrap();
|
blob.close().unwrap();
|
||||||
|
|
||||||
blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, true)
|
blob = db
|
||||||
|
.blob_open(DatabaseName::Main, "test", "content", rowid, true)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut bytes = [0u8; 5];
|
let mut bytes = [0u8; 5];
|
||||||
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
|
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
|
||||||
@ -317,7 +326,8 @@ mod test {
|
|||||||
fn test_blob_in_bufreader() {
|
fn test_blob_in_bufreader() {
|
||||||
let (db, rowid) = db_with_test_blob().unwrap();
|
let (db, rowid) = db_with_test_blob().unwrap();
|
||||||
|
|
||||||
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
let mut blob = db
|
||||||
|
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(8, blob.write(b"one\ntwo\n").unwrap());
|
assert_eq!(8, blob.write(b"one\ntwo\n").unwrap());
|
||||||
|
|
||||||
@ -342,7 +352,8 @@ mod test {
|
|||||||
let (db, rowid) = db_with_test_blob().unwrap();
|
let (db, rowid) = db_with_test_blob().unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
let blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
let blob = db
|
||||||
|
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut writer = BufWriter::new(blob);
|
let mut writer = BufWriter::new(blob);
|
||||||
|
|
||||||
@ -354,7 +365,8 @@ mod test {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// ... but it should've written the first 10 bytes
|
// ... but it should've written the first 10 bytes
|
||||||
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
let mut blob = db
|
||||||
|
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut bytes = [0u8; 10];
|
let mut bytes = [0u8; 10];
|
||||||
assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
|
assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
|
||||||
@ -362,7 +374,8 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
let blob = db
|
||||||
|
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut writer = BufWriter::new(blob);
|
let mut writer = BufWriter::new(blob);
|
||||||
|
|
||||||
@ -373,7 +386,8 @@ mod test {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// ... but it should've written the first 10 bytes
|
// ... but it should've written the first 10 bytes
|
||||||
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
let mut blob = db
|
||||||
|
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut bytes = [0u8; 10];
|
let mut bytes = [0u8; 10];
|
||||||
assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
|
assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
|
||||||
|
175
src/busy.rs
Normal file
175
src/busy.rs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
///! Busy handler (when the database is locked)
|
||||||
|
use std::mem;
|
||||||
|
use std::os::raw::{c_int, c_void};
|
||||||
|
use std::panic::catch_unwind;
|
||||||
|
use std::ptr;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::{Connection, InnerConnection, Result};
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
/// Set a busy handler that sleeps for a specified amount of time when a
|
||||||
|
/// table is locked. The handler will sleep multiple times until at
|
||||||
|
/// least "ms" milliseconds of sleeping have accumulated.
|
||||||
|
///
|
||||||
|
/// Calling this routine with an argument equal to zero turns off all busy
|
||||||
|
/// handlers.
|
||||||
|
///
|
||||||
|
/// There can only be a single busy handler for a particular database
|
||||||
|
/// connection at any given moment. If another busy handler was defined
|
||||||
|
/// (using `busy_handler`) prior to calling this routine, that other
|
||||||
|
/// busy handler is cleared.
|
||||||
|
pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
|
||||||
|
let ms = timeout
|
||||||
|
.as_secs()
|
||||||
|
.checked_mul(1000)
|
||||||
|
.and_then(|t| t.checked_add(timeout.subsec_millis().into()))
|
||||||
|
.expect("too big");
|
||||||
|
self.db.borrow_mut().busy_timeout(ms as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a callback to handle `SQLITE_BUSY` errors.
|
||||||
|
///
|
||||||
|
/// If the busy callback is `None`, then `SQLITE_BUSY is returned
|
||||||
|
/// immediately upon encountering the lock.` The argument to the busy
|
||||||
|
/// handler callback is the number of times that the
|
||||||
|
/// busy handler has been invoked previously for the
|
||||||
|
/// same locking event. If the busy callback returns `false`, then no
|
||||||
|
/// additional attempts are made to access the
|
||||||
|
/// database and `SQLITE_BUSY` is returned to the
|
||||||
|
/// application. If the callback returns `true`, then another attempt
|
||||||
|
/// is made to access the database and the cycle repeats.
|
||||||
|
///
|
||||||
|
/// There can only be a single busy handler defined for each database
|
||||||
|
/// connection. Setting a new busy handler clears any previously set
|
||||||
|
/// handler. Note that calling `busy_timeout()` or evaluating `PRAGMA
|
||||||
|
/// busy_timeout=N` will change the busy handler and thus
|
||||||
|
/// clear any previously set busy handler.
|
||||||
|
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 {
|
||||||
|
let handler_fn: fn(i32) -> bool = mem::transmute(p_arg);
|
||||||
|
if let Ok(true) = catch_unwind(|| handler_fn(count)) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut c = self.db.borrow_mut();
|
||||||
|
let r = match callback {
|
||||||
|
Some(f) => unsafe {
|
||||||
|
ffi::sqlite3_busy_handler(c.db(), Some(busy_handler_callback), mem::transmute(f))
|
||||||
|
},
|
||||||
|
None => unsafe { ffi::sqlite3_busy_handler(c.db(), None, ptr::null_mut()) },
|
||||||
|
};
|
||||||
|
c.decode_result(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerConnection {
|
||||||
|
fn busy_timeout(&mut self, timeout: c_int) -> Result<()> {
|
||||||
|
let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) };
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::mpsc::sync_channel;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tempdir::TempDir;
|
||||||
|
|
||||||
|
use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_busy() {
|
||||||
|
let temp_dir = TempDir::new("test_default_busy").unwrap();
|
||||||
|
let path = temp_dir.path().join("test.db3");
|
||||||
|
|
||||||
|
let mut db1 = Connection::open(&path).unwrap();
|
||||||
|
let tx1 = db1
|
||||||
|
.transaction_with_behavior(TransactionBehavior::Exclusive)
|
||||||
|
.unwrap();
|
||||||
|
let db2 = Connection::open(&path).unwrap();
|
||||||
|
let r: Result<()> = db2.query_row("PRAGMA schema_version", NO_PARAMS, |_| unreachable!());
|
||||||
|
match r.unwrap_err() {
|
||||||
|
Error::SqliteFailure(err, _) => {
|
||||||
|
assert_eq!(err.code, ErrorCode::DatabaseBusy);
|
||||||
|
}
|
||||||
|
err => panic!("Unexpected error {}", err),
|
||||||
|
}
|
||||||
|
tx1.rollback().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore] // FIXME: unstable
|
||||||
|
fn test_busy_timeout() {
|
||||||
|
let temp_dir = TempDir::new("test_busy_timeout").unwrap();
|
||||||
|
let path = temp_dir.path().join("test.db3");
|
||||||
|
|
||||||
|
let db2 = Connection::open(&path).unwrap();
|
||||||
|
db2.busy_timeout(Duration::from_secs(1)).unwrap();
|
||||||
|
|
||||||
|
let (rx, tx) = sync_channel(0);
|
||||||
|
let child = thread::spawn(move || {
|
||||||
|
let mut db1 = Connection::open(&path).unwrap();
|
||||||
|
let tx1 = db1
|
||||||
|
.transaction_with_behavior(TransactionBehavior::Exclusive)
|
||||||
|
.unwrap();
|
||||||
|
rx.send(1).unwrap();
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
tx1.rollback().unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(tx.recv().unwrap(), 1);
|
||||||
|
let _ = db2
|
||||||
|
.query_row("PRAGMA schema_version", NO_PARAMS, |row| {
|
||||||
|
row.get::<_, i32>(0)
|
||||||
|
})
|
||||||
|
.expect("unexpected error");
|
||||||
|
|
||||||
|
child.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore] // FIXME: unstable
|
||||||
|
fn test_busy_handler() {
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
}
|
||||||
|
fn busy_handler(_: i32) -> bool {
|
||||||
|
CALLED.store(true, Ordering::Relaxed);
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
let temp_dir = TempDir::new("test_busy_handler").unwrap();
|
||||||
|
let path = temp_dir.path().join("test.db3");
|
||||||
|
|
||||||
|
let db2 = Connection::open(&path).unwrap();
|
||||||
|
db2.busy_handler(Some(busy_handler)).unwrap();
|
||||||
|
|
||||||
|
let (rx, tx) = sync_channel(0);
|
||||||
|
let child = thread::spawn(move || {
|
||||||
|
let mut db1 = Connection::open(&path).unwrap();
|
||||||
|
let tx1 = db1
|
||||||
|
.transaction_with_behavior(TransactionBehavior::Exclusive)
|
||||||
|
.unwrap();
|
||||||
|
rx.send(1).unwrap();
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
tx1.rollback().unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(tx.recv().unwrap(), 1);
|
||||||
|
let _ = db2
|
||||||
|
.query_row("PRAGMA schema_version", NO_PARAMS, |row| {
|
||||||
|
row.get::<_, i32>(0)
|
||||||
|
})
|
||||||
|
.expect("unexpected error");
|
||||||
|
assert_eq!(CALLED.load(Ordering::Relaxed), true);
|
||||||
|
|
||||||
|
child.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
174
src/cache.rs
174
src/cache.rs
@ -1,30 +1,29 @@
|
|||||||
//! 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 std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use lru_cache::LruCache;
|
|
||||||
use {Result, Connection, Statement};
|
|
||||||
use raw_statement::RawStatement;
|
|
||||||
use statement::StatementCrateImpl;
|
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Prepare a SQL statement for execution, returning a previously prepared (but
|
/// Prepare a SQL statement for execution, returning a previously prepared
|
||||||
/// not currently in-use) statement if one is available. The returned statement
|
/// (but not currently in-use) statement if one is available. The
|
||||||
/// will be cached for reuse by future calls to `prepare_cached` once it is
|
/// returned statement will be cached for reuse by future calls to
|
||||||
/// dropped.
|
/// `prepare_cached` once it is dropped.
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # 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(())
|
||||||
/// }
|
/// }
|
||||||
@ -32,20 +31,22 @@ impl Connection {
|
|||||||
///
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
|
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||||
/// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum number of cached prepared statements this connection will hold.
|
/// Set the maximum number of cached prepared statements this connection
|
||||||
/// By default, a connection will hold a relatively small number of cached statements.
|
/// will hold. By default, a connection will hold a relatively small
|
||||||
/// If you need more, or know that you will not use cached statements, you can set
|
/// number of cached statements. If you need more, or know that you
|
||||||
/// the capacity manually using this method.
|
/// will not use cached statements, you
|
||||||
|
/// can set the capacity manually using this method.
|
||||||
pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) {
|
pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) {
|
||||||
self.cache.set_capacity(capacity)
|
self.cache.set_capacity(capacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove/finalize all prepared statements currently in the cache.
|
||||||
pub fn flush_prepared_statement_cache(&self) {
|
pub fn flush_prepared_statement_cache(&self) {
|
||||||
self.cache.flush()
|
self.cache.flush()
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ impl<'conn> DerefMut for CachedStatement<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Drop for CachedStatement<'conn> {
|
impl Drop for CachedStatement<'_> {
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(stmt) = self.stmt.take() {
|
if let Some(stmt) = self.stmt.take() {
|
||||||
@ -87,16 +88,16 @@ impl<'conn> Drop for CachedStatement<'conn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> CachedStatement<'conn> {
|
impl CachedStatement<'_> {
|
||||||
fn new(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
|
fn new<'conn>(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
|
||||||
CachedStatement {
|
CachedStatement {
|
||||||
stmt: Some(stmt),
|
stmt: Some(stmt),
|
||||||
cache: cache,
|
cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Discard the statement, preventing it from being returned to its `Connection`'s collection
|
/// Discard the statement, preventing it from being returned to its
|
||||||
/// of cached statements.
|
/// `Connection`'s collection of cached statements.
|
||||||
pub fn discard(mut self) {
|
pub fn discard(mut self) {
|
||||||
self.stmt = None;
|
self.stmt = None;
|
||||||
}
|
}
|
||||||
@ -117,14 +118,15 @@ impl StatementCache {
|
|||||||
//
|
//
|
||||||
// # Failure
|
// # Failure
|
||||||
//
|
//
|
||||||
// Will return `Err` if no cached statement can be found and the underlying SQLite prepare
|
// Will return `Err` if no cached statement can be found and the underlying
|
||||||
// call fails.
|
// SQLite prepare call fails.
|
||||||
fn get<'conn>(&'conn self,
|
fn get<'conn>(
|
||||||
conn: &'conn Connection,
|
&'conn self,
|
||||||
sql: &str)
|
conn: &'conn Connection,
|
||||||
-> Result<CachedStatement<'conn>> {
|
sql: &str,
|
||||||
|
) -> Result<CachedStatement<'conn>> {
|
||||||
let mut cache = self.0.borrow_mut();
|
let mut cache = self.0.borrow_mut();
|
||||||
let stmt = match cache.remove(sql) {
|
let stmt = match cache.remove(sql.trim()) {
|
||||||
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
|
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
|
||||||
None => conn.prepare(sql),
|
None => conn.prepare(sql),
|
||||||
};
|
};
|
||||||
@ -135,7 +137,9 @@ impl StatementCache {
|
|||||||
fn cache_stmt(&self, stmt: RawStatement) {
|
fn cache_stmt(&self, stmt: RawStatement) {
|
||||||
let mut cache = self.0.borrow_mut();
|
let mut cache = self.0.borrow_mut();
|
||||||
stmt.clear_bindings();
|
stmt.clear_bindings();
|
||||||
let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).to_string();
|
let sql = String::from_utf8_lossy(stmt.sql().to_bytes())
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
cache.insert(sql, stmt);
|
cache.insert(sql, stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +151,9 @@ impl StatementCache {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use Connection;
|
|
||||||
use super::StatementCache;
|
use super::StatementCache;
|
||||||
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
use fallible_iterator::FallibleIterator;
|
||||||
|
|
||||||
impl StatementCache {
|
impl StatementCache {
|
||||||
fn clear(&self) {
|
fn clear(&self) {
|
||||||
@ -176,14 +181,20 @@ mod test {
|
|||||||
{
|
{
|
||||||
let mut stmt = db.prepare_cached(sql).unwrap();
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
assert_eq!(0, cache.len());
|
assert_eq!(0, cache.len());
|
||||||
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap());
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(1, cache.len());
|
assert_eq!(1, cache.len());
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut stmt = db.prepare_cached(sql).unwrap();
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
assert_eq!(0, cache.len());
|
assert_eq!(0, cache.len());
|
||||||
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap());
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(1, cache.len());
|
assert_eq!(1, cache.len());
|
||||||
|
|
||||||
@ -201,7 +212,10 @@ mod test {
|
|||||||
{
|
{
|
||||||
let mut stmt = db.prepare_cached(sql).unwrap();
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
assert_eq!(0, cache.len());
|
assert_eq!(0, cache.len());
|
||||||
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap());
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(1, cache.len());
|
assert_eq!(1, cache.len());
|
||||||
|
|
||||||
@ -211,7 +225,10 @@ mod test {
|
|||||||
{
|
{
|
||||||
let mut stmt = db.prepare_cached(sql).unwrap();
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
assert_eq!(0, cache.len());
|
assert_eq!(0, cache.len());
|
||||||
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap());
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(0, cache.len());
|
assert_eq!(0, cache.len());
|
||||||
|
|
||||||
@ -219,7 +236,10 @@ mod test {
|
|||||||
{
|
{
|
||||||
let mut stmt = db.prepare_cached(sql).unwrap();
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
assert_eq!(0, cache.len());
|
assert_eq!(0, cache.len());
|
||||||
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap());
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(1, cache.len());
|
assert_eq!(1, cache.len());
|
||||||
}
|
}
|
||||||
@ -233,7 +253,10 @@ mod test {
|
|||||||
{
|
{
|
||||||
let mut stmt = db.prepare_cached(sql).unwrap();
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
assert_eq!(0, cache.len());
|
assert_eq!(0, cache.len());
|
||||||
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap());
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
|
||||||
|
);
|
||||||
stmt.discard();
|
stmt.discard();
|
||||||
}
|
}
|
||||||
assert_eq!(0, cache.len());
|
assert_eq!(0, cache.len());
|
||||||
@ -242,47 +265,78 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_ddl() {
|
fn test_ddl() {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
db.execute_batch(r#"
|
db.execute_batch(
|
||||||
|
r#"
|
||||||
CREATE TABLE foo (x INT);
|
CREATE TABLE foo (x INT);
|
||||||
INSERT INTO foo VALUES (1);
|
INSERT INTO foo VALUES (1);
|
||||||
"#)
|
"#,
|
||||||
.unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let sql = "SELECT * FROM foo";
|
let sql = "SELECT * FROM foo";
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut stmt = db.prepare_cached(sql).unwrap();
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
assert_eq!(1i32,
|
assert_eq!(
|
||||||
stmt.query_map::<i32, _>(&[], |r| r.get(0))
|
Ok(Some(1i32)),
|
||||||
.unwrap()
|
stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).next()
|
||||||
.next()
|
);
|
||||||
.unwrap()
|
|
||||||
.unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
db.execute_batch(r#"
|
db.execute_batch(
|
||||||
|
r#"
|
||||||
ALTER TABLE foo ADD COLUMN y INT;
|
ALTER TABLE foo ADD COLUMN y INT;
|
||||||
UPDATE foo SET y = 2;
|
UPDATE foo SET y = 2;
|
||||||
"#)
|
"#,
|
||||||
.unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut stmt = db.prepare_cached(sql).unwrap();
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
assert_eq!((1i32, 2i32),
|
assert_eq!(
|
||||||
stmt.query_map(&[], |r| (r.get(0), r.get(1)))
|
Ok(Some((1i32, 2i32))),
|
||||||
.unwrap()
|
stmt.query(NO_PARAMS)
|
||||||
.next()
|
.unwrap()
|
||||||
.unwrap()
|
.map(|r| Ok((r.get(0)?, r.get(1)?)))
|
||||||
.unwrap());
|
.next()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_connection_close() {
|
fn test_connection_close() {
|
||||||
let conn = Connection::open_in_memory().unwrap();
|
let conn = Connection::open_in_memory().unwrap();
|
||||||
conn.prepare_cached("SELECT * FROM sqlite_master;")
|
conn.prepare_cached("SELECT * FROM sqlite_master;").unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
conn.close().expect("connection not closed");
|
conn.close().expect("connection not closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cache_key() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let cache = &db.cache;
|
||||||
|
assert_eq!(0, cache.len());
|
||||||
|
|
||||||
|
//let sql = " PRAGMA schema_version; -- comment";
|
||||||
|
let sql = "PRAGMA schema_version; ";
|
||||||
|
{
|
||||||
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
|
assert_eq!(0, cache.len());
|
||||||
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(1, cache.len());
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut stmt = db.prepare_cached(sql).unwrap();
|
||||||
|
assert_eq!(0, cache.len());
|
||||||
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(1, cache.len());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
209
src/collation.rs
Normal file
209
src/collation.rs
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
//! Add, remove, or modify a collation
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::os::raw::{c_char, c_int, c_void};
|
||||||
|
use std::panic::{catch_unwind, UnwindSafe};
|
||||||
|
use std::ptr;
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::{str_to_cstring, Connection, InnerConnection, Result};
|
||||||
|
|
||||||
|
// FIXME copy/paste from function.rs
|
||||||
|
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
|
||||||
|
drop(Box::from_raw(p as *mut T));
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
/// Add or modify a collation.
|
||||||
|
pub fn create_collation<C>(&self, collation_name: &str, x_compare: C) -> Result<()>
|
||||||
|
where
|
||||||
|
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
|
||||||
|
{
|
||||||
|
self.db
|
||||||
|
.borrow_mut()
|
||||||
|
.create_collation(collation_name, x_compare)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collation needed callback
|
||||||
|
pub fn collation_needed(
|
||||||
|
&self,
|
||||||
|
x_coll_needed: fn(&Connection, &str) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.db.borrow_mut().collation_needed(x_coll_needed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove collation.
|
||||||
|
pub fn remove_collation(&self, collation_name: &str) -> Result<()> {
|
||||||
|
self.db.borrow_mut().remove_collation(collation_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerConnection {
|
||||||
|
fn create_collation<C>(&mut self, collation_name: &str, x_compare: C) -> Result<()>
|
||||||
|
where
|
||||||
|
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
|
||||||
|
{
|
||||||
|
unsafe extern "C" fn call_boxed_closure<C>(
|
||||||
|
arg1: *mut c_void,
|
||||||
|
arg2: c_int,
|
||||||
|
arg3: *const c_void,
|
||||||
|
arg4: c_int,
|
||||||
|
arg5: *const c_void,
|
||||||
|
) -> c_int
|
||||||
|
where
|
||||||
|
C: Fn(&str, &str) -> Ordering,
|
||||||
|
{
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
let r = catch_unwind(|| {
|
||||||
|
let boxed_f: *mut C = arg1 as *mut C;
|
||||||
|
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
|
||||||
|
let s1 = {
|
||||||
|
let c_slice = slice::from_raw_parts(arg3 as *const u8, arg2 as usize);
|
||||||
|
str::from_utf8_unchecked(c_slice)
|
||||||
|
};
|
||||||
|
let s2 = {
|
||||||
|
let c_slice = slice::from_raw_parts(arg5 as *const u8, arg4 as usize);
|
||||||
|
str::from_utf8_unchecked(c_slice)
|
||||||
|
};
|
||||||
|
(*boxed_f)(s1, s2)
|
||||||
|
});
|
||||||
|
let t = match r {
|
||||||
|
Err(_) => {
|
||||||
|
return -1; // FIXME How ?
|
||||||
|
}
|
||||||
|
Ok(r) => r,
|
||||||
|
};
|
||||||
|
|
||||||
|
match t {
|
||||||
|
Ordering::Less => -1,
|
||||||
|
Ordering::Equal => 0,
|
||||||
|
Ordering::Greater => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let boxed_f: *mut C = Box::into_raw(Box::new(x_compare));
|
||||||
|
let c_name = str_to_cstring(collation_name)?;
|
||||||
|
let flags = ffi::SQLITE_UTF8;
|
||||||
|
let r = unsafe {
|
||||||
|
ffi::sqlite3_create_collation_v2(
|
||||||
|
self.db(),
|
||||||
|
c_name.as_ptr(),
|
||||||
|
flags,
|
||||||
|
boxed_f as *mut c_void,
|
||||||
|
Some(call_boxed_closure::<C>),
|
||||||
|
Some(free_boxed_value::<C>),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collation_needed(
|
||||||
|
&mut self,
|
||||||
|
x_coll_needed: fn(&Connection, &str) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
use std::mem;
|
||||||
|
unsafe extern "C" fn collation_needed_callback(
|
||||||
|
arg1: *mut c_void,
|
||||||
|
arg2: *mut ffi::sqlite3,
|
||||||
|
e_text_rep: c_int,
|
||||||
|
arg3: *const c_char,
|
||||||
|
) {
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
if e_text_rep != ffi::SQLITE_UTF8 {
|
||||||
|
// TODO: validate
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let callback: fn(&Connection, &str) -> Result<()> = mem::transmute(arg1);
|
||||||
|
if catch_unwind(|| {
|
||||||
|
let conn = Connection::from_handle(arg2).unwrap();
|
||||||
|
let collation_name = {
|
||||||
|
let c_slice = CStr::from_ptr(arg3).to_bytes();
|
||||||
|
str::from_utf8_unchecked(c_slice)
|
||||||
|
};
|
||||||
|
callback(&conn, collation_name)
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return; // FIXME How ?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = unsafe {
|
||||||
|
ffi::sqlite3_collation_needed(
|
||||||
|
self.db(),
|
||||||
|
mem::transmute(x_coll_needed),
|
||||||
|
Some(collation_needed_callback),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_collation(&mut self, collation_name: &str) -> Result<()> {
|
||||||
|
let c_name = str_to_cstring(collation_name)?;
|
||||||
|
let r = unsafe {
|
||||||
|
ffi::sqlite3_create_collation_v2(
|
||||||
|
self.db(),
|
||||||
|
c_name.as_ptr(),
|
||||||
|
ffi::SQLITE_UTF8,
|
||||||
|
ptr::null_mut(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.decode_result(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
use fallible_streaming_iterator::FallibleStreamingIterator;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use unicase::UniCase;
|
||||||
|
|
||||||
|
fn unicase_compare(s1: &str, s2: &str) -> Ordering {
|
||||||
|
UniCase::new(s1).cmp(&UniCase::new(s2))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unicase() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
db.create_collation("unicase", unicase_compare).unwrap();
|
||||||
|
|
||||||
|
collate(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collate(db: Connection) {
|
||||||
|
db.execute_batch(
|
||||||
|
"CREATE TABLE foo (bar);
|
||||||
|
INSERT INTO foo (bar) VALUES ('Maße');
|
||||||
|
INSERT INTO foo (bar) VALUES ('MASSE');",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut stmt = db
|
||||||
|
.prepare("SELECT DISTINCT bar COLLATE unicase FROM foo ORDER BY 1")
|
||||||
|
.unwrap();
|
||||||
|
let rows = stmt.query(NO_PARAMS).unwrap();
|
||||||
|
assert_eq!(rows.count().unwrap(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collation_needed(db: &Connection, collation_name: &str) -> Result<()> {
|
||||||
|
if "unicase" == collation_name {
|
||||||
|
db.create_collation(collation_name, unicase_compare)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collation_needed() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.collation_needed(collation_needed).unwrap();
|
||||||
|
collate(db);
|
||||||
|
}
|
||||||
|
}
|
215
src/column.rs
Normal file
215
src/column.rs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
use std::str;
|
||||||
|
|
||||||
|
use crate::{Error, Result, Row, Rows, Statement};
|
||||||
|
|
||||||
|
/// Information about a column of a SQLite query.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Column<'stmt> {
|
||||||
|
name: &'stmt str,
|
||||||
|
decl_type: Option<&'stmt str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Column<'_> {
|
||||||
|
/// Returns the name of the column.
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type of the column (`None` for expression).
|
||||||
|
pub fn decl_type(&self) -> Option<&str> {
|
||||||
|
self.decl_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Statement<'_> {
|
||||||
|
/// Get all the column names in the result set of the prepared statement.
|
||||||
|
pub fn column_names(&self) -> Vec<&str> {
|
||||||
|
let n = self.column_count();
|
||||||
|
let mut cols = Vec::with_capacity(n as usize);
|
||||||
|
for i in 0..n {
|
||||||
|
let s = self.column_name_unwrap(i);
|
||||||
|
cols.push(s);
|
||||||
|
}
|
||||||
|
cols
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of columns in the result set returned by the prepared
|
||||||
|
/// statement.
|
||||||
|
pub fn column_count(&self) -> usize {
|
||||||
|
self.stmt.column_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn column_name_unwrap(&self, col: usize) -> &str {
|
||||||
|
// Just panic if the bounds are wrong for now, we never call this
|
||||||
|
// without checking first.
|
||||||
|
self.column_name(col).expect("Column out of bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the name assigned to a particular column in the result set
|
||||||
|
/// returned by the prepared statement.
|
||||||
|
///
|
||||||
|
/// ## Failure
|
||||||
|
///
|
||||||
|
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
|
||||||
|
/// column range for this row.
|
||||||
|
///
|
||||||
|
/// Panics when column name is not valid UTF-8.
|
||||||
|
pub fn column_name(&self, col: usize) -> Result<&str> {
|
||||||
|
self.stmt
|
||||||
|
.column_name(col)
|
||||||
|
.ok_or(Error::InvalidColumnIndex(col))
|
||||||
|
.map(|slice| {
|
||||||
|
str::from_utf8(slice.to_bytes()).expect("Invalid UTF-8 sequence in column name")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the column index in the result set for a given column name.
|
||||||
|
///
|
||||||
|
/// If there is no AS clause then the name of the column is unspecified and
|
||||||
|
/// may change from one release of SQLite to the next.
|
||||||
|
///
|
||||||
|
/// # Failure
|
||||||
|
///
|
||||||
|
/// Will return an `Error::InvalidColumnName` when there is no column with
|
||||||
|
/// the specified `name`.
|
||||||
|
pub fn column_index(&self, name: &str) -> Result<usize> {
|
||||||
|
let bytes = name.as_bytes();
|
||||||
|
let n = self.column_count();
|
||||||
|
for i in 0..n {
|
||||||
|
// Note: `column_name` is only fallible if `i` is out of bounds,
|
||||||
|
// which we've already checked.
|
||||||
|
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
|
||||||
|
return Ok(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::InvalidColumnName(String::from(name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slice describing the columns of the result of the query.
|
||||||
|
pub fn columns(&self) -> Vec<Column> {
|
||||||
|
let n = self.column_count();
|
||||||
|
let mut cols = Vec::with_capacity(n as usize);
|
||||||
|
for i in 0..n {
|
||||||
|
let name = self.column_name_unwrap(i);
|
||||||
|
let slice = self.stmt.column_decltype(i);
|
||||||
|
let decl_type = slice.map(|s| {
|
||||||
|
str::from_utf8(s.to_bytes()).expect("Invalid UTF-8 sequence in column declaration")
|
||||||
|
});
|
||||||
|
cols.push(Column { name, decl_type });
|
||||||
|
}
|
||||||
|
cols
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'stmt> Rows<'stmt> {
|
||||||
|
/// Get all the column names.
|
||||||
|
pub fn column_names(&self) -> Option<Vec<&str>> {
|
||||||
|
self.stmt.map(Statement::column_names)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of columns.
|
||||||
|
pub fn column_count(&self) -> Option<usize> {
|
||||||
|
self.stmt.map(Statement::column_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the name of the column.
|
||||||
|
pub fn column_name(&self, col: usize) -> Option<Result<&str>> {
|
||||||
|
self.stmt.map(|stmt| stmt.column_name(col))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the index of the column.
|
||||||
|
pub fn column_index(&self, name: &str) -> Option<Result<usize>> {
|
||||||
|
self.stmt.map(|stmt| stmt.column_index(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slice describing the columns of the Rows.
|
||||||
|
pub fn columns(&self) -> Option<Vec<Column>> {
|
||||||
|
self.stmt.map(Statement::columns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'stmt> Row<'stmt> {
|
||||||
|
/// Get all the column names of the Row.
|
||||||
|
pub fn column_names(&self) -> Vec<&str> {
|
||||||
|
self.stmt.column_names()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of columns in the current row.
|
||||||
|
pub fn column_count(&self) -> usize {
|
||||||
|
self.stmt.column_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the name of the column.
|
||||||
|
pub fn column_name(&self, col: usize) -> Result<&str> {
|
||||||
|
self.stmt.column_name(col)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the index of the column.
|
||||||
|
pub fn column_index(&self, name: &str) -> Result<usize> {
|
||||||
|
self.stmt.column_index(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slice describing the columns of the Row.
|
||||||
|
pub fn columns(&self) -> Vec<Column> {
|
||||||
|
self.stmt.columns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Column;
|
||||||
|
use crate::Connection;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_columns() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let query = db.prepare("SELECT * FROM sqlite_master").unwrap();
|
||||||
|
let columns = query.columns();
|
||||||
|
let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
|
||||||
|
assert_eq!(
|
||||||
|
column_names.as_slice(),
|
||||||
|
&["type", "name", "tbl_name", "rootpage", "sql"]
|
||||||
|
);
|
||||||
|
let column_types: Vec<Option<&str>> = columns.iter().map(Column::decl_type).collect();
|
||||||
|
assert_eq!(
|
||||||
|
&column_types[..3],
|
||||||
|
&[Some("text"), Some("text"), Some("text"),]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_column_name_in_error() {
|
||||||
|
use crate::{types::Type, Error};
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch(
|
||||||
|
"BEGIN;
|
||||||
|
CREATE TABLE foo(x INTEGER, y TEXT);
|
||||||
|
INSERT INTO foo VALUES(4, NULL);
|
||||||
|
END;",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut stmt = db.prepare("SELECT x as renamed, y FROM foo").unwrap();
|
||||||
|
let mut rows = stmt.query(crate::NO_PARAMS).unwrap();
|
||||||
|
let row = rows.next().unwrap().unwrap();
|
||||||
|
match row.get::<_, String>(0).unwrap_err() {
|
||||||
|
Error::InvalidColumnType(idx, name, ty) => {
|
||||||
|
assert_eq!(idx, 0);
|
||||||
|
assert_eq!(name, "renamed");
|
||||||
|
assert_eq!(ty, Type::Integer);
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
panic!("Unexpected error type: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match row.get::<_, String>("y").unwrap_err() {
|
||||||
|
Error::InvalidColumnType(idx, name, ty) => {
|
||||||
|
assert_eq!(idx, 1);
|
||||||
|
assert_eq!(name, "y");
|
||||||
|
assert_eq!(ty, Type::Null);
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
panic!("Unexpected error type: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
src/config.rs
Normal file
114
src/config.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
//! Configure database connections
|
||||||
|
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::{Connection, Result};
|
||||||
|
|
||||||
|
/// Database Connection Configuration Options
|
||||||
|
#[repr(i32)]
|
||||||
|
#[allow(non_snake_case, non_camel_case_types)]
|
||||||
|
pub enum DbConfig {
|
||||||
|
//SQLITE_DBCONFIG_MAINDBNAME = 1000, /* const char* */
|
||||||
|
//SQLITE_DBCONFIG_LOOKASIDE = 1001, /* void* int int */
|
||||||
|
SQLITE_DBCONFIG_ENABLE_FKEY = 1002,
|
||||||
|
SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003,
|
||||||
|
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // 3.12.0
|
||||||
|
//SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005,
|
||||||
|
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006,
|
||||||
|
SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0
|
||||||
|
SQLITE_DBCONFIG_TRIGGER_EQP = 1008,
|
||||||
|
//SQLITE_DBCONFIG_RESET_DATABASE = 1009,
|
||||||
|
SQLITE_DBCONFIG_DEFENSIVE = 1010,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
/// Returns the current value of a `config`.
|
||||||
|
///
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_FKEY: return `false` or `true` to indicate
|
||||||
|
/// whether FK enforcement is off or on
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_TRIGGER: return `false` or `true` to indicate
|
||||||
|
/// whether triggers are disabled or enabled
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: return `false` or `true` to
|
||||||
|
/// indicate whether fts3_tokenizer are disabled or enabled
|
||||||
|
/// - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: return `false` to indicate
|
||||||
|
/// checkpoints-on-close are not disabled or `true` if they are
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_QPSG: return `false` or `true` to indicate
|
||||||
|
/// whether the QPSG is disabled or enabled
|
||||||
|
/// - SQLITE_DBCONFIG_TRIGGER_EQP: return `false` to indicate
|
||||||
|
/// output-for-trigger are not disabled or `true` if it is
|
||||||
|
pub fn db_config(&self, config: DbConfig) -> Result<bool> {
|
||||||
|
let c = self.db.borrow();
|
||||||
|
unsafe {
|
||||||
|
let mut val = 0;
|
||||||
|
check!(ffi::sqlite3_db_config(
|
||||||
|
c.db(),
|
||||||
|
config as c_int,
|
||||||
|
-1,
|
||||||
|
&mut val
|
||||||
|
));
|
||||||
|
Ok(val != 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make configuration changes to a database connection
|
||||||
|
///
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_FKEY: `false` to disable FK enforcement, `true`
|
||||||
|
/// to enable FK enforcement
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_TRIGGER: `false` to disable triggers, `true` to
|
||||||
|
/// enable triggers
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: `false` to disable
|
||||||
|
/// fts3_tokenizer(), `true` to enable fts3_tokenizer()
|
||||||
|
/// - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: `false` (the default) to enable
|
||||||
|
/// checkpoints-on-close, `true` to disable them
|
||||||
|
/// - SQLITE_DBCONFIG_ENABLE_QPSG: `false` to disable the QPSG, `true` to
|
||||||
|
/// enable QPSG
|
||||||
|
/// - SQLITE_DBCONFIG_TRIGGER_EQP: `false` to disable output for trigger
|
||||||
|
/// programs, `true` to enable it
|
||||||
|
pub fn set_db_config(&self, config: DbConfig, new_val: bool) -> Result<bool> {
|
||||||
|
let c = self.db.borrow_mut();
|
||||||
|
unsafe {
|
||||||
|
let mut val = 0;
|
||||||
|
check!(ffi::sqlite3_db_config(
|
||||||
|
c.db(),
|
||||||
|
config as c_int,
|
||||||
|
if new_val { 1 } else { 0 },
|
||||||
|
&mut val
|
||||||
|
));
|
||||||
|
Ok(val != 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::DbConfig;
|
||||||
|
use crate::Connection;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_db_config() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY, opposite),
|
||||||
|
Ok(opposite)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY),
|
||||||
|
Ok(opposite)
|
||||||
|
);
|
||||||
|
|
||||||
|
let opposite = !db
|
||||||
|
.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER, opposite),
|
||||||
|
Ok(opposite)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER),
|
||||||
|
Ok(opposite)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
68
src/context.rs
Normal file
68
src/context.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
//! Code related to `sqlite3_context` common to `functions` and `vtab` modules.
|
||||||
|
|
||||||
|
use std::os::raw::{c_int, c_void};
|
||||||
|
#[cfg(feature = "array")]
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::ffi::sqlite3_context;
|
||||||
|
|
||||||
|
use crate::str_for_sqlite;
|
||||||
|
use crate::types::{ToSqlOutput, ValueRef};
|
||||||
|
#[cfg(feature = "array")]
|
||||||
|
use crate::vtab::array::{free_array, ARRAY_TYPE};
|
||||||
|
|
||||||
|
pub(crate) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<'_>) {
|
||||||
|
let value = match *result {
|
||||||
|
ToSqlOutput::Borrowed(v) => v,
|
||||||
|
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
|
||||||
|
|
||||||
|
#[cfg(feature = "blob")]
|
||||||
|
ToSqlOutput::ZeroBlob(len) => {
|
||||||
|
return ffi::sqlite3_result_zeroblob(ctx, len);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "array")]
|
||||||
|
ToSqlOutput::Array(ref a) => {
|
||||||
|
return ffi::sqlite3_result_pointer(
|
||||||
|
ctx,
|
||||||
|
Rc::into_raw(a.clone()) as *mut c_void,
|
||||||
|
ARRAY_TYPE,
|
||||||
|
Some(free_array),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match value {
|
||||||
|
ValueRef::Null => ffi::sqlite3_result_null(ctx),
|
||||||
|
ValueRef::Integer(i) => ffi::sqlite3_result_int64(ctx, i),
|
||||||
|
ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r),
|
||||||
|
ValueRef::Text(s) => {
|
||||||
|
let length = s.len();
|
||||||
|
if length > c_int::max_value() as usize {
|
||||||
|
ffi::sqlite3_result_error_toobig(ctx);
|
||||||
|
} else {
|
||||||
|
let (c_str, len, destructor) = match str_for_sqlite(s) {
|
||||||
|
Ok(c_str) => c_str,
|
||||||
|
// TODO sqlite3_result_error
|
||||||
|
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
|
||||||
|
};
|
||||||
|
ffi::sqlite3_result_text(ctx, c_str, len, destructor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValueRef::Blob(b) => {
|
||||||
|
let length = b.len();
|
||||||
|
if length > c_int::max_value() as usize {
|
||||||
|
ffi::sqlite3_result_error_toobig(ctx);
|
||||||
|
} else if length == 0 {
|
||||||
|
ffi::sqlite3_result_zeroblob(ctx, 0)
|
||||||
|
} else {
|
||||||
|
ffi::sqlite3_result_blob(
|
||||||
|
ctx,
|
||||||
|
b.as_ptr() as *const c_void,
|
||||||
|
length as c_int,
|
||||||
|
ffi::SQLITE_TRANSIENT(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
282
src/error.rs
282
src/error.rs
@ -1,42 +1,42 @@
|
|||||||
|
use crate::types::FromSqlError;
|
||||||
|
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::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::os::raw::c_int;
|
|
||||||
use {ffi, errmsg_to_string};
|
|
||||||
use types::Type;
|
|
||||||
|
|
||||||
/// 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>),
|
||||||
|
|
||||||
/// Error reported when attempting to open a connection when SQLite was configured to
|
/// Error reported when attempting to open a connection when SQLite was
|
||||||
/// allow single-threaded use only.
|
/// configured to allow single-threaded use only.
|
||||||
SqliteSingleThreadedMode,
|
SqliteSingleThreadedMode,
|
||||||
|
|
||||||
/// Error when the value of a particular column is requested, but it cannot be converted to
|
/// Error when the value of a particular column is requested, but it cannot
|
||||||
/// 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 requested type (e.g.,
|
/// Error when SQLite gives us an integral value outside the range of the
|
||||||
/// trying to get the value 1000 into a `u8`). The associated `c_int` is the column index, and
|
/// requested type (e.g., trying to get the value 1000 into a `u8`).
|
||||||
/// the associated `i64` is the value returned by SQLite.
|
/// The associated `usize` is the column index,
|
||||||
IntegralValueOutOfRange(c_int, i64),
|
/// and the associated `i64` is the value returned by SQLite.
|
||||||
|
IntegralValueOutOfRange(usize, i64),
|
||||||
|
|
||||||
/// Error converting a string to UTF-8.
|
/// Error converting a string to UTF-8.
|
||||||
Utf8Error(str::Utf8Error),
|
Utf8Error(str::Utf8Error),
|
||||||
|
|
||||||
/// Error converting a string to a C-compatible string because it contained an embedded nul.
|
/// Error converting a string to a C-compatible string because it contained
|
||||||
|
/// an embedded nul.
|
||||||
NulError(::std::ffi::NulError),
|
NulError(::std::ffi::NulError),
|
||||||
|
|
||||||
/// Error when using SQL named parameters and passing a parameter name not present in the SQL.
|
/// Error when using SQL named parameters and passing a parameter name not
|
||||||
|
/// present in the SQL.
|
||||||
InvalidParameterName(String),
|
InvalidParameterName(String),
|
||||||
|
|
||||||
/// Error converting a file path to a string.
|
/// Error converting a file path to a string.
|
||||||
@ -45,38 +45,106 @@ pub enum Error {
|
|||||||
/// Error returned when an `execute` call returns rows.
|
/// Error returned when an `execute` call returns rows.
|
||||||
ExecuteReturnedResults,
|
ExecuteReturnedResults,
|
||||||
|
|
||||||
/// Error when a query that was expected to return at least one row (e.g., for `query_row`)
|
/// Error when a query that was expected to return at least one row (e.g.,
|
||||||
/// did not return any.
|
/// for `query_row`) did not return any.
|
||||||
QueryReturnedNoRows,
|
QueryReturnedNoRows,
|
||||||
|
|
||||||
/// Error when the value of a particular column is requested, but the index is out of range
|
/// Error when the value of a particular column is requested, but the index
|
||||||
/// for the statement.
|
/// is out of range for the statement.
|
||||||
InvalidColumnIndex(c_int),
|
InvalidColumnIndex(usize),
|
||||||
|
|
||||||
/// Error when the value of a named column is requested, but no column matches the name
|
/// Error when the value of a named column is requested, but no column
|
||||||
/// for the statement.
|
/// matches the name for the statement.
|
||||||
InvalidColumnName(String),
|
InvalidColumnName(String),
|
||||||
|
|
||||||
/// Error when the value of a particular column is requested, but the type of the result in
|
/// Error when the value of a particular column is requested, but the type
|
||||||
/// that column cannot be converted to the requested Rust type.
|
/// of the result in that column cannot be converted to the requested
|
||||||
InvalidColumnType(c_int, Type),
|
/// Rust type.
|
||||||
|
InvalidColumnType(usize, String, Type),
|
||||||
|
|
||||||
/// Error when a query that was expected to insert one row did not insert any or insert many.
|
/// Error when a query that was expected to insert one row did not insert
|
||||||
StatementChangedRows(c_int),
|
/// any or insert many.
|
||||||
|
StatementChangedRows(usize),
|
||||||
|
|
||||||
/// Error returned by `functions::Context::get` when the function argument cannot be converted
|
/// Error returned by `functions::Context::get` when the function argument
|
||||||
/// to the requested type.
|
/// cannot be converted to the requested type.
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
InvalidFunctionParameterType(usize, Type),
|
InvalidFunctionParameterType(usize, Type),
|
||||||
|
/// Error returned by `vtab::Values::get` when the filter argument cannot
|
||||||
|
/// be converted to the requested type.
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
InvalidFilterParameterType(usize, Type),
|
||||||
|
|
||||||
/// An error case available for implementors of custom user functions (e.g.,
|
/// An error case available for implementors of custom user functions (e.g.,
|
||||||
/// `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.
|
||||||
|
InvalidQuery,
|
||||||
|
|
||||||
|
/// An error case available for implementors of custom modules (e.g.,
|
||||||
|
/// `create_module`).
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
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.
|
||||||
|
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, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
|
||||||
|
i1 == i2 && t1 == t2 && n1 == n2
|
||||||
|
}
|
||||||
|
(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 {
|
||||||
@ -91,24 +159,58 @@ impl From<::std::ffi::NulError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UNKNOWN_COLUMN: usize = std::usize::MAX;
|
||||||
|
|
||||||
|
/// The conversion isn't precise, but it's convenient to have it
|
||||||
|
/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
|
||||||
|
impl From<FromSqlError> for Error {
|
||||||
|
fn from(err: FromSqlError) -> Error {
|
||||||
|
// The error type requires index and type fields, but they aren't known in this
|
||||||
|
// context.
|
||||||
|
match err {
|
||||||
|
FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
FromSqlError::InvalidI128Size(_) => {
|
||||||
|
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(_) => {
|
||||||
|
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
|
||||||
|
}
|
||||||
|
FromSqlError::Other(source) => {
|
||||||
|
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source)
|
||||||
|
}
|
||||||
|
_ => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
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),
|
||||||
Error::SqliteSingleThreadedMode => {
|
Error::SqliteSingleThreadedMode => write!(
|
||||||
write!(f,
|
f,
|
||||||
"SQLite was compiled or configured for single-threaded use only")
|
"SQLite was compiled or configured for single-threaded use only"
|
||||||
}
|
),
|
||||||
Error::FromSqlConversionFailure(i, ref t, ref err) => {
|
Error::FromSqlConversionFailure(i, ref t, ref err) => {
|
||||||
write!(f,
|
if i != UNKNOWN_COLUMN {
|
||||||
"Conversion error from type {} at index: {}, {}",
|
write!(
|
||||||
t,
|
f,
|
||||||
i,
|
"Conversion error from type {} at index: {}, {}",
|
||||||
err)
|
t, i, err
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
err.fmt(f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Error::IntegralValueOutOfRange(col, val) => {
|
Error::IntegralValueOutOfRange(col, val) => {
|
||||||
write!(f, "Integer {} out of range at index {}", val, col)
|
if col != UNKNOWN_COLUMN {
|
||||||
|
write!(f, "Integer {} out of range at index {}", val, col)
|
||||||
|
} else {
|
||||||
|
write!(f, "Integer {} out of range", val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Error::Utf8Error(ref err) => err.fmt(f),
|
Error::Utf8Error(ref err) => err.fmt(f),
|
||||||
Error::NulError(ref err) => err.fmt(f),
|
Error::NulError(ref err) => err.fmt(f),
|
||||||
@ -120,18 +222,32 @@ impl fmt::Display for Error {
|
|||||||
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
||||||
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
|
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
|
||||||
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
|
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
|
||||||
Error::InvalidColumnType(i, ref t) => {
|
Error::InvalidColumnType(i, ref name, ref t) => write!(
|
||||||
write!(f, "Invalid column type {} at index: {}", t, i)
|
f,
|
||||||
}
|
"Invalid column type {} at index: {}, name: {}",
|
||||||
|
t, i, name
|
||||||
|
),
|
||||||
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
|
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::InvalidFunctionParameterType(i, ref t) => {
|
Error::InvalidFunctionParameterType(i, ref t) => {
|
||||||
write!(f, "Invalid function parameter type {} at index {}", t, i)
|
write!(f, "Invalid function parameter type {} at index {}", t, i)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
Error::InvalidFilterParameterType(i, ref t) => {
|
||||||
|
write!(f, "Invalid filter parameter type {} at index {}", t, i)
|
||||||
|
}
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::UserFunctionError(ref err) => err.fmt(f),
|
Error::UserFunctionError(ref err) => err.fmt(f),
|
||||||
Error::ToSqlConversionFailure(ref err) => err.fmt(f),
|
Error::ToSqlConversionFailure(ref err) => err.fmt(f),
|
||||||
|
Error::InvalidQuery => write!(f, "Query is not read-only"),
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,53 +257,80 @@ impl error::Error for Error {
|
|||||||
match *self {
|
match *self {
|
||||||
Error::SqliteFailure(ref err, None) => err.description(),
|
Error::SqliteFailure(ref err, None) => err.description(),
|
||||||
Error::SqliteFailure(_, Some(ref s)) => s,
|
Error::SqliteFailure(_, Some(ref s)) => s,
|
||||||
Error::SqliteSingleThreadedMode => "SQLite was compiled or configured for single-threaded use only",
|
Error::SqliteSingleThreadedMode => {
|
||||||
|
"SQLite was compiled or configured for single-threaded use only"
|
||||||
|
}
|
||||||
Error::FromSqlConversionFailure(_, _, ref err) => err.description(),
|
Error::FromSqlConversionFailure(_, _, ref err) => err.description(),
|
||||||
Error::IntegralValueOutOfRange(_, _) => "integral value out of range of requested type",
|
Error::IntegralValueOutOfRange(_, _) => "integral value out of range of requested type",
|
||||||
Error::Utf8Error(ref err) => err.description(),
|
Error::Utf8Error(ref err) => err.description(),
|
||||||
Error::InvalidParameterName(_) => "invalid parameter name",
|
Error::InvalidParameterName(_) => "invalid parameter name",
|
||||||
Error::NulError(ref err) => err.description(),
|
Error::NulError(ref err) => err.description(),
|
||||||
Error::InvalidPath(_) => "invalid path",
|
Error::InvalidPath(_) => "invalid path",
|
||||||
Error::ExecuteReturnedResults => "execute returned results - did you mean to call query?",
|
Error::ExecuteReturnedResults => {
|
||||||
|
"execute returned results - did you mean to call query?"
|
||||||
|
}
|
||||||
Error::QueryReturnedNoRows => "query returned no rows",
|
Error::QueryReturnedNoRows => "query returned no rows",
|
||||||
Error::InvalidColumnIndex(_) => "invalid column index",
|
Error::InvalidColumnIndex(_) => "invalid column index",
|
||||||
Error::InvalidColumnName(_) => "invalid column name",
|
Error::InvalidColumnName(_) => "invalid column name",
|
||||||
Error::InvalidColumnType(_, _) => "invalid column type",
|
Error::InvalidColumnType(_, _, _) => "invalid column type",
|
||||||
Error::StatementChangedRows(_) => "query inserted zero or more than one row",
|
Error::StatementChangedRows(_) => "query inserted zero or more than one row",
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
|
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
Error::InvalidFilterParameterType(_, _) => "invalid filter parameter type",
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::UserFunctionError(ref err) => err.description(),
|
Error::UserFunctionError(ref err) => err.description(),
|
||||||
Error::ToSqlConversionFailure(ref err) => err.description(),
|
Error::ToSqlConversionFailure(ref err) => err.description(),
|
||||||
|
Error::InvalidQuery => "query is not read-only",
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
||||||
Error::NulError(ref err) => Some(err),
|
Error::NulError(ref err) => Some(err),
|
||||||
|
|
||||||
Error::IntegralValueOutOfRange(_, _) |
|
Error::IntegralValueOutOfRange(_, _)
|
||||||
Error::SqliteSingleThreadedMode |
|
| Error::SqliteSingleThreadedMode
|
||||||
Error::InvalidParameterName(_) |
|
| Error::InvalidParameterName(_)
|
||||||
Error::ExecuteReturnedResults |
|
| Error::ExecuteReturnedResults
|
||||||
Error::QueryReturnedNoRows |
|
| Error::QueryReturnedNoRows
|
||||||
Error::InvalidColumnIndex(_) |
|
| Error::InvalidColumnIndex(_)
|
||||||
Error::InvalidColumnName(_) |
|
| Error::InvalidColumnName(_)
|
||||||
Error::InvalidColumnType(_, _) |
|
| Error::InvalidColumnType(_, _, _)
|
||||||
Error::InvalidPath(_) |
|
| Error::InvalidPath(_)
|
||||||
Error::StatementChangedRows(_) => None,
|
| Error::StatementChangedRows(_)
|
||||||
|
| Error::InvalidQuery
|
||||||
|
| Error::MultipleStatement => None,
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::InvalidFunctionParameterType(_, _) => None,
|
Error::InvalidFunctionParameterType(_, _) => None,
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
Error::InvalidFilterParameterType(_, _) => None,
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::UserFunctionError(ref err) => Some(&**err),
|
Error::UserFunctionError(ref err) => Some(&**err),
|
||||||
|
|
||||||
Error::FromSqlConversionFailure(_, _, ref err) |
|
Error::FromSqlConversionFailure(_, _, ref err)
|
||||||
Error::ToSqlConversionFailure(ref err) => Some(&**err),
|
| Error::ToSqlConversionFailure(ref err) => Some(&**err),
|
||||||
|
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
Error::ModuleError(_) => None,
|
||||||
|
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
Error::UnwindingPanic => None,
|
||||||
|
|
||||||
|
#[cfg(feature = "functions")]
|
||||||
|
Error::GetAuxWrongType => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,3 +349,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 {
|
||||||
|
return Err(crate::error::error_from_sqlite_code(rc, None).into());
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
1078
src/functions.rs
1078
src/functions.rs
File diff suppressed because it is too large
Load Diff
304
src/hooks.rs
Normal file
304
src/hooks.rs
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
//! Commit, Data Change and Rollback Notification Callbacks
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
|
use std::os::raw::{c_char, c_int, c_void};
|
||||||
|
use std::panic::catch_unwind;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
|
||||||
|
use crate::{Connection, InnerConnection};
|
||||||
|
|
||||||
|
/// Action Codes
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum Action {
|
||||||
|
UNKNOWN = -1,
|
||||||
|
SQLITE_DELETE = ffi::SQLITE_DELETE,
|
||||||
|
SQLITE_INSERT = ffi::SQLITE_INSERT,
|
||||||
|
SQLITE_UPDATE = ffi::SQLITE_UPDATE,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i32> for Action {
|
||||||
|
fn from(code: i32) -> Action {
|
||||||
|
match code {
|
||||||
|
ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
|
||||||
|
ffi::SQLITE_INSERT => Action::SQLITE_INSERT,
|
||||||
|
ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE,
|
||||||
|
_ => Action::UNKNOWN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
/// Register a callback function to be invoked whenever a transaction is
|
||||||
|
/// committed.
|
||||||
|
///
|
||||||
|
/// The callback returns `true` to rollback.
|
||||||
|
pub fn commit_hook<F>(&self, hook: Option<F>)
|
||||||
|
where
|
||||||
|
F: FnMut() -> bool + Send + 'static,
|
||||||
|
{
|
||||||
|
self.db.borrow_mut().commit_hook(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a callback function to be invoked whenever a transaction is
|
||||||
|
/// committed.
|
||||||
|
///
|
||||||
|
/// The callback returns `true` to rollback.
|
||||||
|
pub fn rollback_hook<F>(&self, hook: Option<F>)
|
||||||
|
where
|
||||||
|
F: FnMut() + Send + 'static,
|
||||||
|
{
|
||||||
|
self.db.borrow_mut().rollback_hook(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a callback function to be invoked whenever a row is updated,
|
||||||
|
/// inserted or deleted in a rowid table.
|
||||||
|
///
|
||||||
|
/// The callback parameters are:
|
||||||
|
///
|
||||||
|
/// - the type of database update (SQLITE_INSERT, SQLITE_UPDATE or
|
||||||
|
/// SQLITE_DELETE),
|
||||||
|
/// - the name of the database ("main", "temp", ...),
|
||||||
|
/// - the name of the table that is updated,
|
||||||
|
/// - the ROWID of the row that is updated.
|
||||||
|
pub fn update_hook<F>(&self, hook: Option<F>)
|
||||||
|
where
|
||||||
|
F: FnMut(Action, &str, &str, i64) + Send + 'static,
|
||||||
|
{
|
||||||
|
self.db.borrow_mut().update_hook(hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerConnection {
|
||||||
|
pub fn remove_hooks(&mut self) {
|
||||||
|
self.update_hook(None::<fn(Action, &str, &str, i64)>);
|
||||||
|
self.commit_hook(None::<fn() -> bool>);
|
||||||
|
self.rollback_hook(None::<fn()>);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit_hook<F>(&mut self, hook: Option<F>)
|
||||||
|
where
|
||||||
|
F: FnMut() -> bool + Send + 'static,
|
||||||
|
{
|
||||||
|
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
|
||||||
|
where
|
||||||
|
F: FnMut() -> bool,
|
||||||
|
{
|
||||||
|
let r = catch_unwind(|| {
|
||||||
|
let boxed_hook: *mut F = p_arg as *mut F;
|
||||||
|
(*boxed_hook)()
|
||||||
|
});
|
||||||
|
if let Ok(true) = r {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlike `sqlite3_create_function_v2`, we cannot specify a `xDestroy` with
|
||||||
|
// `sqlite3_commit_hook`. so we keep the `xDestroy` function in
|
||||||
|
// `InnerConnection.free_boxed_hook`.
|
||||||
|
let free_commit_hook = if hook.is_some() {
|
||||||
|
Some(free_boxed_hook::<F> as fn(*mut c_void))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let previous_hook = match hook {
|
||||||
|
Some(hook) => {
|
||||||
|
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_commit_hook(
|
||||||
|
self.db(),
|
||||||
|
Some(call_boxed_closure::<F>),
|
||||||
|
boxed_hook as *mut _,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) },
|
||||||
|
};
|
||||||
|
if !previous_hook.is_null() {
|
||||||
|
if let Some(free_boxed_hook) = self.free_commit_hook {
|
||||||
|
free_boxed_hook(previous_hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.free_commit_hook = free_commit_hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rollback_hook<F>(&mut self, hook: Option<F>)
|
||||||
|
where
|
||||||
|
F: FnMut() + Send + 'static,
|
||||||
|
{
|
||||||
|
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
|
||||||
|
where
|
||||||
|
F: FnMut(),
|
||||||
|
{
|
||||||
|
let _ = catch_unwind(|| {
|
||||||
|
let boxed_hook: *mut F = p_arg as *mut F;
|
||||||
|
(*boxed_hook)();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let free_rollback_hook = if hook.is_some() {
|
||||||
|
Some(free_boxed_hook::<F> as fn(*mut c_void))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let previous_hook = match hook {
|
||||||
|
Some(hook) => {
|
||||||
|
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_rollback_hook(
|
||||||
|
self.db(),
|
||||||
|
Some(call_boxed_closure::<F>),
|
||||||
|
boxed_hook as *mut _,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) },
|
||||||
|
};
|
||||||
|
if !previous_hook.is_null() {
|
||||||
|
if let Some(free_boxed_hook) = self.free_rollback_hook {
|
||||||
|
free_boxed_hook(previous_hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.free_rollback_hook = free_rollback_hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_hook<F>(&mut self, hook: Option<F>)
|
||||||
|
where
|
||||||
|
F: FnMut(Action, &str, &str, i64) + Send + 'static,
|
||||||
|
{
|
||||||
|
unsafe extern "C" fn call_boxed_closure<F>(
|
||||||
|
p_arg: *mut c_void,
|
||||||
|
action_code: c_int,
|
||||||
|
db_str: *const c_char,
|
||||||
|
tbl_str: *const c_char,
|
||||||
|
row_id: i64,
|
||||||
|
) where
|
||||||
|
F: FnMut(Action, &str, &str, i64),
|
||||||
|
{
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
let action = Action::from(action_code);
|
||||||
|
let db_name = {
|
||||||
|
let c_slice = CStr::from_ptr(db_str).to_bytes();
|
||||||
|
str::from_utf8_unchecked(c_slice)
|
||||||
|
};
|
||||||
|
let tbl_name = {
|
||||||
|
let c_slice = CStr::from_ptr(tbl_str).to_bytes();
|
||||||
|
str::from_utf8_unchecked(c_slice)
|
||||||
|
};
|
||||||
|
|
||||||
|
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() {
|
||||||
|
Some(free_boxed_hook::<F> as fn(*mut c_void))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let previous_hook = match hook {
|
||||||
|
Some(hook) => {
|
||||||
|
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_update_hook(
|
||||||
|
self.db(),
|
||||||
|
Some(call_boxed_closure::<F>),
|
||||||
|
boxed_hook as *mut _,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) },
|
||||||
|
};
|
||||||
|
if !previous_hook.is_null() {
|
||||||
|
if let Some(free_boxed_hook) = self.free_update_hook {
|
||||||
|
free_boxed_hook(previous_hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.free_update_hook = free_update_hook;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_boxed_hook<F>(p: *mut c_void) {
|
||||||
|
drop(unsafe { Box::from_raw(p as *mut F) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Action;
|
||||||
|
use crate::Connection;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_commit_hook() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
}
|
||||||
|
db.commit_hook(Some(|| {
|
||||||
|
CALLED.store(true, Ordering::Relaxed);
|
||||||
|
false
|
||||||
|
}));
|
||||||
|
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
|
||||||
|
.unwrap();
|
||||||
|
assert!(CALLED.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fn_commit_hook() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
fn hook() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
db.commit_hook(Some(hook));
|
||||||
|
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
|
||||||
|
.unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rollback_hook() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
}
|
||||||
|
db.rollback_hook(Some(|| {
|
||||||
|
CALLED.store(true, Ordering::Relaxed);
|
||||||
|
}));
|
||||||
|
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
|
||||||
|
.unwrap();
|
||||||
|
assert!(CALLED.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_hook() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
}
|
||||||
|
db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
|
||||||
|
assert_eq!(Action::SQLITE_INSERT, action);
|
||||||
|
assert_eq!("main", db);
|
||||||
|
assert_eq!("foo", tbl);
|
||||||
|
assert_eq!(1, row_id);
|
||||||
|
CALLED.store(true, Ordering::Relaxed);
|
||||||
|
}));
|
||||||
|
db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
|
||||||
|
db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
|
||||||
|
assert!(CALLED.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
}
|
417
src/inner_connection.rs
Normal file
417
src/inner_connection.rs
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
use std::ffi::CString;
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
use std::os::raw::{c_char, c_int};
|
||||||
|
#[cfg(feature = "load_extension")]
|
||||||
|
use std::path::Path;
|
||||||
|
use std::ptr;
|
||||||
|
use std::str;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex, Once};
|
||||||
|
|
||||||
|
use super::ffi;
|
||||||
|
use super::{str_for_sqlite, 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 = MaybeUninit::uninit();
|
||||||
|
let r =
|
||||||
|
ffi::sqlite3_open_v2(c_path.as_ptr(), db.as_mut_ptr(), flags.bits(), ptr::null());
|
||||||
|
let db: *mut ffi::sqlite3 = db.assume_init();
|
||||||
|
if r != ffi::SQLITE_OK {
|
||||||
|
let e = if db.is_null() {
|
||||||
|
error_from_sqlite_code(r, Some(c_path.to_string_lossy().to_string()))
|
||||||
|
} else {
|
||||||
|
let mut e = error_from_handle(db, r);
|
||||||
|
if let Error::SqliteFailure(
|
||||||
|
ffi::Error{code: ffi::ErrorCode::CannotOpen, extended_code: _}, Some(msg)) = e {
|
||||||
|
e = Error::SqliteFailure(
|
||||||
|
ffi::Error::new(r), Some(format!("{}: {}", msg, c_path.to_string_lossy())));
|
||||||
|
}
|
||||||
|
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<()> {
|
||||||
|
let dylib_str = super::path_to_cstring(dylib_path)?;
|
||||||
|
unsafe {
|
||||||
|
let mut errmsg = MaybeUninit::uninit();
|
||||||
|
let r = if let Some(entry_point) = entry_point {
|
||||||
|
let c_entry = str_to_cstring(entry_point)?;
|
||||||
|
ffi::sqlite3_load_extension(
|
||||||
|
self.db,
|
||||||
|
dylib_str.as_ptr(),
|
||||||
|
c_entry.as_ptr(),
|
||||||
|
errmsg.as_mut_ptr(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ffi::sqlite3_load_extension(
|
||||||
|
self.db,
|
||||||
|
dylib_str.as_ptr(),
|
||||||
|
ptr::null(),
|
||||||
|
errmsg.as_mut_ptr(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if r == ffi::SQLITE_OK {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let errmsg: *mut c_char = errmsg.assume_init();
|
||||||
|
let message = super::errmsg_to_string(&*errmsg);
|
||||||
|
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
||||||
|
Err(error_from_sqlite_code(r, Some(message)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>> {
|
||||||
|
let mut c_stmt = MaybeUninit::uninit();
|
||||||
|
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
|
||||||
|
let mut c_tail = MaybeUninit::uninit();
|
||||||
|
let r = unsafe {
|
||||||
|
if cfg!(feature = "unlock_notify") {
|
||||||
|
let mut rc;
|
||||||
|
loop {
|
||||||
|
rc = ffi::sqlite3_prepare_v2(
|
||||||
|
self.db(),
|
||||||
|
c_sql,
|
||||||
|
len,
|
||||||
|
c_stmt.as_mut_ptr(),
|
||||||
|
c_tail.as_mut_ptr(),
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
len,
|
||||||
|
c_stmt.as_mut_ptr(),
|
||||||
|
c_tail.as_mut_ptr(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.decode_result(r)?;
|
||||||
|
|
||||||
|
let c_stmt: *mut ffi::sqlite3_stmt = unsafe { c_stmt.assume_init() };
|
||||||
|
let c_tail: *const c_char = unsafe { c_tail.assume_init() };
|
||||||
|
// TODO ignore spaces, comments, ... at the end
|
||||||
|
let tail = !c_tail.is_null() && unsafe { c_tail != c_sql.offset(len as isize) };
|
||||||
|
Ok(Statement::new(conn, RawStatement::new(c_stmt, tail)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn changes(&mut self) -> usize {
|
||||||
|
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::new();
|
||||||
|
#[cfg(not(feature = "bundled"))]
|
||||||
|
pub static BYPASS_VERSION_CHECK: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
#[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::new();
|
||||||
|
pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
1458
src/lib.rs
1458
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 {Result, Connection};
|
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)
|
||||||
/// }
|
/// }
|
||||||
@ -21,17 +17,18 @@ pub struct LoadExtensionGuard<'conn> {
|
|||||||
conn: &'conn Connection,
|
conn: &'conn Connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> LoadExtensionGuard<'conn> {
|
impl LoadExtensionGuard<'_> {
|
||||||
/// Attempt to enable loading extensions. Loading extensions will be disabled when this
|
/// Attempt to enable loading extensions. Loading extensions will be
|
||||||
/// guard goes out of scope. Cannot be meaningfully nested.
|
/// disabled when this guard goes out of scope. Cannot be meaningfully
|
||||||
pub fn new(conn: &Connection) -> Result<LoadExtensionGuard> {
|
/// nested.
|
||||||
|
pub fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
|
||||||
conn.load_extension_enable()
|
conn.load_extension_enable()
|
||||||
.map(|_| LoadExtensionGuard { conn: conn })
|
.map(|_| LoadExtensionGuard { conn })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
impl<'conn> Drop for LoadExtensionGuard<'conn> {
|
impl Drop for LoadExtensionGuard<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.conn.load_extension_disable();
|
self.conn.load_extension_disable();
|
||||||
}
|
}
|
||||||
|
433
src/pragma.rs
Normal file
433
src/pragma.rs
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
//! Pragma helpers
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::types::{ToSql, ToSqlOutput, ValueRef};
|
||||||
|
use crate::{Connection, DatabaseName, Result, Row, NO_PARAMS};
|
||||||
|
|
||||||
|
pub struct Sql {
|
||||||
|
buf: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sql {
|
||||||
|
pub fn new() -> Sql {
|
||||||
|
Sql { buf: String::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_pragma(
|
||||||
|
&mut self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.push_keyword("PRAGMA")?;
|
||||||
|
self.push_space();
|
||||||
|
if let Some(schema_name) = schema_name {
|
||||||
|
self.push_schema_name(schema_name);
|
||||||
|
self.push_dot();
|
||||||
|
}
|
||||||
|
self.push_keyword(pragma_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_keyword(&mut self, keyword: &str) -> Result<()> {
|
||||||
|
if !keyword.is_empty() && is_identifier(keyword) {
|
||||||
|
self.buf.push_str(keyword);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("Invalid keyword \"{}\"", keyword)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_schema_name(&mut self, schema_name: DatabaseName<'_>) {
|
||||||
|
match schema_name {
|
||||||
|
DatabaseName::Main => self.buf.push_str("main"),
|
||||||
|
DatabaseName::Temp => self.buf.push_str("temp"),
|
||||||
|
DatabaseName::Attached(s) => self.push_identifier(s),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_identifier(&mut self, s: &str) {
|
||||||
|
if is_identifier(s) {
|
||||||
|
self.buf.push_str(s);
|
||||||
|
} else {
|
||||||
|
self.wrap_and_escape(s, '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_value(&mut self, value: &dyn ToSql) -> Result<()> {
|
||||||
|
let value = value.to_sql()?;
|
||||||
|
let value = match value {
|
||||||
|
ToSqlOutput::Borrowed(v) => v,
|
||||||
|
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
|
||||||
|
#[cfg(feature = "blob")]
|
||||||
|
ToSqlOutput::ZeroBlob(_) => {
|
||||||
|
return Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("Unsupported value \"{:?}\"", value)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#[cfg(feature = "array")]
|
||||||
|
ToSqlOutput::Array(_) => {
|
||||||
|
return Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("Unsupported value \"{:?}\"", value)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match value {
|
||||||
|
ValueRef::Integer(i) => {
|
||||||
|
self.push_int(i);
|
||||||
|
}
|
||||||
|
ValueRef::Real(r) => {
|
||||||
|
self.push_real(r);
|
||||||
|
}
|
||||||
|
ValueRef::Text(s) => {
|
||||||
|
let s = std::str::from_utf8(s)?;
|
||||||
|
self.push_string_literal(s);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("Unsupported value \"{:?}\"", value)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_string_literal(&mut self, s: &str) {
|
||||||
|
self.wrap_and_escape(s, '\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_int(&mut self, i: i64) {
|
||||||
|
self.buf.push_str(&i.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_real(&mut self, f: f64) {
|
||||||
|
self.buf.push_str(&f.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_space(&mut self) {
|
||||||
|
self.buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_dot(&mut self) {
|
||||||
|
self.buf.push('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_equal_sign(&mut self) {
|
||||||
|
self.buf.push('=');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_brace(&mut self) {
|
||||||
|
self.buf.push('(');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_brace(&mut self) {
|
||||||
|
self.buf.push(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_and_escape(&mut self, s: &str, quote: char) {
|
||||||
|
self.buf.push(quote);
|
||||||
|
let chars = s.chars();
|
||||||
|
for ch in chars {
|
||||||
|
// escape `quote` by doubling it
|
||||||
|
if ch == quote {
|
||||||
|
self.buf.push(ch);
|
||||||
|
}
|
||||||
|
self.buf.push(ch)
|
||||||
|
}
|
||||||
|
self.buf.push(quote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Sql {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
/// Query the current value of `pragma_name`.
|
||||||
|
///
|
||||||
|
/// Some pragmas will return multiple rows/values which cannot be retrieved
|
||||||
|
/// with this method.
|
||||||
|
///
|
||||||
|
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
|
||||||
|
/// `SELECT user_version FROM pragma_user_version;`
|
||||||
|
pub fn pragma_query_value<T, F>(
|
||||||
|
&self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
f: F,
|
||||||
|
) -> Result<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
|
{
|
||||||
|
let mut query = Sql::new();
|
||||||
|
query.push_pragma(schema_name, pragma_name)?;
|
||||||
|
self.query_row(&query, NO_PARAMS, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query the current rows/values of `pragma_name`.
|
||||||
|
///
|
||||||
|
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
|
||||||
|
/// `SELECT * FROM pragma_collation_list;`
|
||||||
|
pub fn pragma_query<F>(
|
||||||
|
&self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
mut f: F,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnMut(&Row<'_>) -> Result<()>,
|
||||||
|
{
|
||||||
|
let mut query = Sql::new();
|
||||||
|
query.push_pragma(schema_name, pragma_name)?;
|
||||||
|
let mut stmt = self.prepare(&query)?;
|
||||||
|
let mut rows = stmt.query(NO_PARAMS)?;
|
||||||
|
while let Some(result_row) = rows.next()? {
|
||||||
|
let row = result_row;
|
||||||
|
f(&row)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query the current value(s) of `pragma_name` associated to
|
||||||
|
/// `pragma_value`.
|
||||||
|
///
|
||||||
|
/// This method can be used with query-only pragmas which need an argument
|
||||||
|
/// (e.g. `table_info('one_tbl')`) or pragmas which returns value(s)
|
||||||
|
/// (e.g. `integrity_check`).
|
||||||
|
///
|
||||||
|
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
|
||||||
|
/// `SELECT * FROM pragma_table_info(?);`
|
||||||
|
pub fn pragma<F>(
|
||||||
|
&self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
pragma_value: &dyn ToSql,
|
||||||
|
mut f: F,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnMut(&Row<'_>) -> Result<()>,
|
||||||
|
{
|
||||||
|
let mut sql = Sql::new();
|
||||||
|
sql.push_pragma(schema_name, pragma_name)?;
|
||||||
|
// The argument may be either in parentheses
|
||||||
|
// or it may be separated from the pragma name by an equal sign.
|
||||||
|
// The two syntaxes yield identical results.
|
||||||
|
sql.open_brace();
|
||||||
|
sql.push_value(pragma_value)?;
|
||||||
|
sql.close_brace();
|
||||||
|
let mut stmt = self.prepare(&sql)?;
|
||||||
|
let mut rows = stmt.query(NO_PARAMS)?;
|
||||||
|
while let Some(result_row) = rows.next()? {
|
||||||
|
let row = result_row;
|
||||||
|
f(&row)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a new value to `pragma_name`.
|
||||||
|
///
|
||||||
|
/// Some pragmas will return the updated value which cannot be retrieved
|
||||||
|
/// with this method.
|
||||||
|
pub fn pragma_update(
|
||||||
|
&self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
pragma_value: &dyn ToSql,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut sql = Sql::new();
|
||||||
|
sql.push_pragma(schema_name, pragma_name)?;
|
||||||
|
// The argument may be either in parentheses
|
||||||
|
// or it may be separated from the pragma name by an equal sign.
|
||||||
|
// The two syntaxes yield identical results.
|
||||||
|
sql.push_equal_sign();
|
||||||
|
sql.push_value(pragma_value)?;
|
||||||
|
self.execute_batch(&sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a new value to `pragma_name` and return the updated value.
|
||||||
|
///
|
||||||
|
/// Only few pragmas automatically return the updated value.
|
||||||
|
pub fn pragma_update_and_check<F, T>(
|
||||||
|
&self,
|
||||||
|
schema_name: Option<DatabaseName<'_>>,
|
||||||
|
pragma_name: &str,
|
||||||
|
pragma_value: &dyn ToSql,
|
||||||
|
f: F,
|
||||||
|
) -> Result<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(&Row<'_>) -> Result<T>,
|
||||||
|
{
|
||||||
|
let mut sql = Sql::new();
|
||||||
|
sql.push_pragma(schema_name, pragma_name)?;
|
||||||
|
// The argument may be either in parentheses
|
||||||
|
// or it may be separated from the pragma name by an equal sign.
|
||||||
|
// The two syntaxes yield identical results.
|
||||||
|
sql.push_equal_sign();
|
||||||
|
sql.push_value(pragma_value)?;
|
||||||
|
self.query_row(&sql, NO_PARAMS, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_identifier(s: &str) -> bool {
|
||||||
|
let chars = s.char_indices();
|
||||||
|
for (i, ch) in chars {
|
||||||
|
if i == 0 {
|
||||||
|
if !is_identifier_start(ch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if !is_identifier_continue(ch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_identifier_start(c: char) -> bool {
|
||||||
|
(c >= 'A' && c <= 'Z') || c == '_' || (c >= 'a' && c <= 'z') || c > '\x7F'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_identifier_continue(c: char) -> bool {
|
||||||
|
c == '$'
|
||||||
|
|| (c >= '0' && c <= '9')
|
||||||
|
|| (c >= 'A' && c <= 'Z')
|
||||||
|
|| c == '_'
|
||||||
|
|| (c >= 'a' && c <= 'z')
|
||||||
|
|| c > '\x7F'
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Sql;
|
||||||
|
use crate::pragma;
|
||||||
|
use crate::{Connection, DatabaseName};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma_query_value() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let user_version: i32 = db
|
||||||
|
.pragma_query_value(None, "user_version", |row| row.get(0))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(0, user_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "bundled")]
|
||||||
|
fn pragma_func_query_value() {
|
||||||
|
use crate::NO_PARAMS;
|
||||||
|
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let user_version: i32 = db
|
||||||
|
.query_row(
|
||||||
|
"SELECT user_version FROM pragma_user_version",
|
||||||
|
NO_PARAMS,
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(0, user_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma_query_no_schema() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let mut user_version = -1;
|
||||||
|
db.pragma_query(None, "user_version", |row| {
|
||||||
|
user_version = row.get(0)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(0, user_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma_query_with_schema() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let mut user_version = -1;
|
||||||
|
db.pragma_query(Some(DatabaseName::Main), "user_version", |row| {
|
||||||
|
user_version = row.get(0)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(0, user_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let mut columns = Vec::new();
|
||||||
|
db.pragma(None, "table_info", &"sqlite_master", |row| {
|
||||||
|
let column: String = row.get(1)?;
|
||||||
|
columns.push(column);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(5, columns.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "bundled")]
|
||||||
|
fn pragma_func() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap();
|
||||||
|
let mut columns = Vec::new();
|
||||||
|
let mut rows = table_info.query(&["sqlite_master"]).unwrap();
|
||||||
|
|
||||||
|
while let Some(row) = rows.next().unwrap() {
|
||||||
|
let row = row;
|
||||||
|
let column: String = row.get(1).unwrap();
|
||||||
|
columns.push(column);
|
||||||
|
}
|
||||||
|
assert_eq!(5, columns.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma_update() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.pragma_update(None, "user_version", &1).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pragma_update_and_check() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
let journal_mode: String = db
|
||||||
|
.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row.get(0))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!("off", &journal_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_identifier() {
|
||||||
|
assert!(pragma::is_identifier("full"));
|
||||||
|
assert!(pragma::is_identifier("r2d2"));
|
||||||
|
assert!(!pragma::is_identifier("sp ce"));
|
||||||
|
assert!(!pragma::is_identifier("semi;colon"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn double_quote() {
|
||||||
|
let mut sql = Sql::new();
|
||||||
|
sql.push_schema_name(DatabaseName::Attached(r#"schema";--"#));
|
||||||
|
assert_eq!(r#""schema"";--""#, sql.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrap_and_escape() {
|
||||||
|
let mut sql = Sql::new();
|
||||||
|
sql.push_string_literal("value'; --");
|
||||||
|
assert_eq!("'value''; --'", sql.as_str());
|
||||||
|
}
|
||||||
|
}
|
@ -1,50 +1,93 @@
|
|||||||
use std::ffi::CStr;
|
|
||||||
use std::ptr;
|
|
||||||
use std::os::raw::c_int;
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
|
use super::unlock_notify;
|
||||||
|
use super::StatementStatus;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
// Private newtype for raw sqlite3_stmts that finalize themselves when dropped.
|
// Private newtype for raw sqlite3_stmts that finalize themselves when dropped.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RawStatement(*mut ffi::sqlite3_stmt);
|
pub struct RawStatement(*mut ffi::sqlite3_stmt, bool);
|
||||||
|
|
||||||
impl RawStatement {
|
impl RawStatement {
|
||||||
pub fn new(stmt: *mut ffi::sqlite3_stmt) -> RawStatement {
|
pub fn new(stmt: *mut ffi::sqlite3_stmt, tail: bool) -> RawStatement {
|
||||||
RawStatement(stmt)
|
RawStatement(stmt, tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt {
|
pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn column_count(&self) -> c_int {
|
pub fn column_count(&self) -> usize {
|
||||||
unsafe { ffi::sqlite3_column_count(self.0) }
|
unsafe { ffi::sqlite3_column_count(self.0) as usize }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn column_type(&self, idx: c_int) -> c_int {
|
pub fn column_type(&self, idx: usize) -> c_int {
|
||||||
unsafe { ffi::sqlite3_column_type(self.0, idx) }
|
unsafe { ffi::sqlite3_column_type(self.0, idx as c_int) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn column_name(&self, idx: c_int) -> &CStr {
|
pub fn column_decltype(&self, idx: usize) -> Option<&CStr> {
|
||||||
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx)) }
|
unsafe {
|
||||||
|
let decltype = ffi::sqlite3_column_decltype(self.0, idx as c_int);
|
||||||
|
if decltype.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(CStr::from_ptr(decltype))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn column_name(&self, idx: usize) -> Option<&CStr> {
|
||||||
|
let idx = idx as c_int;
|
||||||
|
if idx < 0 || idx >= self.column_count() as c_int {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let ptr = ffi::sqlite3_column_name(self.0, idx);
|
||||||
|
// If ptr is null here, it's an OOM, so there's probably nothing
|
||||||
|
// meaningful we can do. Just assert instead of returning None.
|
||||||
|
assert!(
|
||||||
|
!ptr.is_null(),
|
||||||
|
"Null pointer from sqlite3_column_name: Out of memory?"
|
||||||
|
);
|
||||||
|
Some(CStr::from_ptr(ptr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&self) -> c_int {
|
pub fn step(&self) -> c_int {
|
||||||
unsafe { ffi::sqlite3_step(self.0) }
|
if cfg!(feature = "unlock_notify") {
|
||||||
|
let db = unsafe { ffi::sqlite3_db_handle(self.0) };
|
||||||
|
let mut rc;
|
||||||
|
loop {
|
||||||
|
rc = unsafe { ffi::sqlite3_step(self.0) };
|
||||||
|
if !unlock_notify::is_locked(db, rc) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rc = unlock_notify::wait_for_unlock_notify(db);
|
||||||
|
if rc != ffi::SQLITE_OK {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.reset();
|
||||||
|
}
|
||||||
|
rc
|
||||||
|
} else {
|
||||||
|
unsafe { ffi::sqlite3_step(self.0) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&self) -> c_int {
|
pub fn reset(&self) -> c_int {
|
||||||
unsafe { ffi::sqlite3_reset(self.0) }
|
unsafe { ffi::sqlite3_reset(self.0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind_parameter_count(&self) -> c_int {
|
pub fn bind_parameter_count(&self) -> usize {
|
||||||
unsafe { ffi::sqlite3_bind_parameter_count(self.0) }
|
unsafe { ffi::sqlite3_bind_parameter_count(self.0) as usize }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind_parameter_index(&self, name: &CStr) -> Option<c_int> {
|
pub fn bind_parameter_index(&self, name: &CStr) -> Option<usize> {
|
||||||
let r = unsafe { ffi::sqlite3_bind_parameter_index(self.0, name.as_ptr()) };
|
let r = unsafe { ffi::sqlite3_bind_parameter_index(self.0, name.as_ptr()) };
|
||||||
match r {
|
match r {
|
||||||
0 => None,
|
0 => None,
|
||||||
i => Some(i),
|
i => Some(i as usize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +108,31 @@ impl RawStatement {
|
|||||||
self.0 = ptr::null_mut();
|
self.0 = ptr::null_mut();
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bundled")]
|
||||||
|
pub fn readonly(&self) -> bool {
|
||||||
|
unsafe { ffi::sqlite3_stmt_readonly(self.0) != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `CStr` must be freed
|
||||||
|
#[cfg(feature = "bundled")]
|
||||||
|
pub unsafe fn expanded_sql(&self) -> Option<&CStr> {
|
||||||
|
let ptr = ffi::sqlite3_expanded_sql(self.0);
|
||||||
|
if ptr.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(CStr::from_ptr(ptr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for RawStatement {
|
impl Drop for RawStatement {
|
||||||
|
304
src/row.rs
304
src/row.rs
@ -1,13 +1,14 @@
|
|||||||
|
use fallible_iterator::FallibleIterator;
|
||||||
|
use fallible_streaming_iterator::FallibleStreamingIterator;
|
||||||
use std::{convert, result};
|
use std::{convert, result};
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use super::{Statement, Error, Result};
|
use super::{Error, Result, Statement};
|
||||||
use types::{FromSql, FromSqlError};
|
use crate::types::{FromSql, FromSqlError, ValueRef};
|
||||||
use statement::StatementCrateImpl;
|
|
||||||
|
|
||||||
/// 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> {
|
||||||
stmt: Option<&'stmt Statement<'stmt>>,
|
pub(crate) stmt: Option<&'stmt Statement<'stmt>>,
|
||||||
|
row: Option<Row<'stmt>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stmt> Rows<'stmt> {
|
impl<'stmt> Rows<'stmt> {
|
||||||
@ -17,85 +18,91 @@ impl<'stmt> Rows<'stmt> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to get the next row from the query. Returns `Some(Ok(Row))` if there
|
/// Attempt to get the next row from the query. Returns `Ok(Some(Row))` if
|
||||||
/// is another row, `Some(Err(...))` if there was an error getting the next
|
/// there is another row, `Err(...)` if there was an error
|
||||||
/// row, and `None` if all rows have been retrieved.
|
/// getting the next row, and `Ok(None)` if all rows have been retrieved.
|
||||||
///
|
///
|
||||||
/// ## Note
|
/// ## Note
|
||||||
///
|
///
|
||||||
/// This interface is not compatible with Rust's `Iterator` trait, because the
|
/// This interface is not compatible with Rust's `Iterator` trait, because
|
||||||
/// lifetime of the returned row is tied to the lifetime of `self`. This is a
|
/// the lifetime of the returned row is tied to the lifetime of `self`.
|
||||||
/// "streaming iterator". For a more natural interface, consider using `query_map`
|
/// This is a fallible "streaming iterator". For a more natural interface,
|
||||||
/// or `query_and_then` instead, which return types that implement `Iterator`.
|
/// consider using `query_map` or `query_and_then` instead, which
|
||||||
pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> {
|
/// return types that implement `Iterator`.
|
||||||
self.stmt
|
#[allow(clippy::should_implement_trait)] // cannot implement Iterator
|
||||||
.and_then(|stmt| match stmt.step() {
|
pub fn next(&mut self) -> Result<Option<&Row<'stmt>>> {
|
||||||
Ok(true) => {
|
self.advance()?;
|
||||||
Some(Ok(Row {
|
Ok((*self).get())
|
||||||
stmt: stmt,
|
}
|
||||||
phantom: PhantomData,
|
|
||||||
}))
|
pub fn map<F, B>(self, f: F) -> Map<'stmt, F>
|
||||||
}
|
where
|
||||||
Ok(false) => {
|
F: FnMut(&Row<'_>) -> Result<B>,
|
||||||
self.reset();
|
{
|
||||||
None
|
Map { rows: self, f }
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
self.reset();
|
|
||||||
Some(Err(err))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this
|
impl<'stmt> Rows<'stmt> {
|
||||||
// once pub(crate) is stable.
|
pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
|
||||||
pub trait RowsCrateImpl<'stmt> {
|
Rows {
|
||||||
fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt>;
|
stmt: Some(stmt),
|
||||||
fn get_expected_row<'a>(&'a mut self) -> Result<Row<'a, 'stmt>>;
|
row: None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stmt> RowsCrateImpl<'stmt> for Rows<'stmt> {
|
|
||||||
fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
|
|
||||||
Rows { stmt: Some(stmt) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_expected_row<'a>(&'a mut self) -> Result<Row<'a, 'stmt>> {
|
pub(crate) fn get_expected_row(&mut self) -> Result<&Row<'stmt>> {
|
||||||
match self.next() {
|
match self.next()? {
|
||||||
Some(row) => row,
|
Some(row) => Ok(row),
|
||||||
None => Err(Error::QueryReturnedNoRows),
|
None => Err(Error::QueryReturnedNoRows),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stmt> Drop for Rows<'stmt> {
|
impl Drop for Rows<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.reset();
|
self.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Map<'stmt, F> {
|
||||||
|
rows: Rows<'stmt>,
|
||||||
|
f: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, B> FallibleIterator for Map<'_, F>
|
||||||
|
where
|
||||||
|
F: FnMut(&Row<'_>) -> Result<B>,
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
type Item = B;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Result<Option<B>> {
|
||||||
|
match self.rows.next()? {
|
||||||
|
Some(v) => Ok(Some((self.f)(v)?)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An iterator over the mapped resulting rows of a query.
|
/// An iterator over the mapped resulting rows of a query.
|
||||||
pub struct MappedRows<'stmt, F> {
|
pub struct MappedRows<'stmt, F> {
|
||||||
rows: Rows<'stmt>,
|
rows: Rows<'stmt>,
|
||||||
map: F,
|
map: F,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this
|
impl<'stmt, T, F> MappedRows<'stmt, F>
|
||||||
// once pub(crate) is stable.
|
where
|
||||||
pub trait MappedRowsCrateImpl<'stmt, T, F> {
|
F: FnMut(&Row<'_>) -> Result<T>,
|
||||||
fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'stmt, T, F> MappedRowsCrateImpl<'stmt, T, F> for MappedRows<'stmt, F>
|
|
||||||
where F: FnMut(&Row) -> T
|
|
||||||
{
|
{
|
||||||
fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
|
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
|
||||||
MappedRows { rows: rows, map: f }
|
MappedRows { rows, map: f }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn, T, F> Iterator for MappedRows<'conn, F>
|
impl<T, F> Iterator for MappedRows<'_, F>
|
||||||
where F: FnMut(&Row) -> T
|
where
|
||||||
|
F: FnMut(&Row<'_>) -> Result<T>,
|
||||||
{
|
{
|
||||||
type Item = Result<T>;
|
type Item = Result<T>;
|
||||||
|
|
||||||
@ -103,7 +110,8 @@ impl<'conn, T, F> Iterator for MappedRows<'conn, F>
|
|||||||
let map = &mut self.map;
|
let map = &mut self.map;
|
||||||
self.rows
|
self.rows
|
||||||
.next()
|
.next()
|
||||||
.map(|row_result| row_result.map(|row| (map)(&row)))
|
.transpose()
|
||||||
|
.map(|row_result| row_result.and_then(|row| (map)(&row)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,23 +122,19 @@ pub struct AndThenRows<'stmt, F> {
|
|||||||
map: F,
|
map: F,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this
|
impl<'stmt, T, E, F> AndThenRows<'stmt, F>
|
||||||
// once pub(crate) is stable.
|
where
|
||||||
pub trait AndThenRowsCrateImpl<'stmt, T, E, F> {
|
F: FnMut(&Row<'_>) -> result::Result<T, E>,
|
||||||
fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'stmt, T, E, F> AndThenRowsCrateImpl<'stmt, T, E, F> for AndThenRows<'stmt, F>
|
|
||||||
where F: FnMut(&Row) -> result::Result<T, E>
|
|
||||||
{
|
{
|
||||||
fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
|
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
|
||||||
AndThenRows { rows: rows, map: f }
|
AndThenRows { rows, map: f }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
|
impl<T, E, F> Iterator for AndThenRows<'_, F>
|
||||||
where E: convert::From<Error>,
|
where
|
||||||
F: FnMut(&Row) -> result::Result<T, E>
|
E: convert::From<Error>,
|
||||||
|
F: FnMut(&Row<'_>) -> result::Result<T, E>,
|
||||||
{
|
{
|
||||||
type Item = result::Result<T, E>;
|
type Item = result::Result<T, E>;
|
||||||
|
|
||||||
@ -138,28 +142,65 @@ impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
|
|||||||
let map = &mut self.map;
|
let map = &mut self.map;
|
||||||
self.rows
|
self.rows
|
||||||
.next()
|
.next()
|
||||||
|
.transpose()
|
||||||
.map(|row_result| row_result.map_err(E::from).and_then(|row| (map)(&row)))
|
.map(|row_result| row_result.map_err(E::from).and_then(|row| (map)(&row)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single result row of a query.
|
impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
|
||||||
pub struct Row<'a, 'stmt> {
|
type Error = Error;
|
||||||
stmt: &'stmt Statement<'stmt>,
|
type Item = Row<'stmt>;
|
||||||
phantom: PhantomData<&'a ()>,
|
|
||||||
|
fn advance(&mut self) -> Result<()> {
|
||||||
|
match self.stmt {
|
||||||
|
Some(ref stmt) => match stmt.step() {
|
||||||
|
Ok(true) => {
|
||||||
|
self.row = Some(Row { stmt });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(false) => {
|
||||||
|
self.reset();
|
||||||
|
self.row = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.reset();
|
||||||
|
self.row = None;
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
self.row = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self) -> Option<&Row<'stmt>> {
|
||||||
|
self.row.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'stmt> Row<'a, 'stmt> {
|
/// A single result row of a query.
|
||||||
|
pub struct Row<'stmt> {
|
||||||
|
pub(crate) stmt: &'stmt Statement<'stmt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'stmt> Row<'stmt> {
|
||||||
/// Get the value of a particular column of the result row.
|
/// Get the value of a particular column of the result row.
|
||||||
///
|
///
|
||||||
/// ## Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Panics if calling `row.get_checked(idx)` would return an error, including:
|
/// Panics if calling `row.get(idx)` would return an error,
|
||||||
|
/// including:
|
||||||
///
|
///
|
||||||
/// * If the underlying SQLite column type is not a valid type as a source for `T`
|
/// * If the underlying SQLite column type is not a valid type as a source
|
||||||
/// * If the underlying SQLite integral value is outside the range representable by `T`
|
/// for `T`
|
||||||
/// * If `idx` is outside the range of columns in the returned query
|
/// * If the underlying SQLite integral value is outside the range
|
||||||
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
|
/// representable by `T`
|
||||||
self.get_checked(idx).unwrap()
|
/// * If `idx` is outside the range of columns in the returned query
|
||||||
|
pub fn get_unwrap<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
|
||||||
|
self.get(idx).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of a particular column of the result row.
|
/// Get the value of a particular column of the result row.
|
||||||
@ -169,31 +210,84 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
|||||||
/// Returns an `Error::InvalidColumnType` if the underlying SQLite column
|
/// Returns an `Error::InvalidColumnType` if the underlying SQLite column
|
||||||
/// type is not a valid type as a source for `T`.
|
/// type is not a valid type as a source for `T`.
|
||||||
///
|
///
|
||||||
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid column range
|
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
|
||||||
/// for this row.
|
/// column range for this row.
|
||||||
///
|
///
|
||||||
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column name
|
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
|
||||||
/// for this row.
|
/// name for this row.
|
||||||
pub fn get_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
///
|
||||||
let idx = try!(idx.idx(self.stmt));
|
/// If the result type is i128 (which requires the `i128_blob` feature to be
|
||||||
|
/// enabled), and the underlying SQLite column is a blob whose size is not
|
||||||
|
/// 16 bytes, `Error::InvalidColumnType` will also be returned.
|
||||||
|
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
||||||
|
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 => {
|
FromSqlError::InvalidType => Error::InvalidColumnType(
|
||||||
Error::InvalidColumnType(idx,
|
idx,
|
||||||
value.data_type())
|
self.stmt.column_name_unwrap(idx).into(),
|
||||||
}
|
value.data_type(),
|
||||||
FromSqlError::OutOfRange(i) => {
|
),
|
||||||
Error::IntegralValueOutOfRange(idx, i)
|
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
|
||||||
}
|
FromSqlError::Other(err) => {
|
||||||
FromSqlError::Other(err) => {
|
|
||||||
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
|
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
|
||||||
}
|
}
|
||||||
})
|
#[cfg(feature = "i128_blob")]
|
||||||
|
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(
|
||||||
|
idx,
|
||||||
|
self.stmt.column_name_unwrap(idx).into(),
|
||||||
|
value.data_type(),
|
||||||
|
),
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(
|
||||||
|
idx,
|
||||||
|
self.stmt.column_name_unwrap(idx).into(),
|
||||||
|
value.data_type(),
|
||||||
|
),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the number of columns in the current row.
|
/// Get the value of a particular column of the result row as a `ValueRef`,
|
||||||
pub fn column_count(&self) -> i32 {
|
/// allowing data to be read out of a row without copying.
|
||||||
self.stmt.column_count()
|
///
|
||||||
|
/// This `ValueRef` is valid only as long as this Row, which is enforced by
|
||||||
|
/// it's lifetime. This means that while this method is completely safe,
|
||||||
|
/// it can be somewhat difficult to use, and most callers will be better
|
||||||
|
/// served by `get` or `get`.
|
||||||
|
///
|
||||||
|
/// ## Failure
|
||||||
|
///
|
||||||
|
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
|
||||||
|
/// column range for this row.
|
||||||
|
///
|
||||||
|
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
|
||||||
|
/// name for this row.
|
||||||
|
pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
|
||||||
|
let idx = idx.idx(self.stmt)?;
|
||||||
|
// Narrowing from `ValueRef<'stmt>` (which `self.stmt.value_ref(idx)`
|
||||||
|
// returns) to `ValueRef<'a>` is needed because it's only valid until
|
||||||
|
// the next call to sqlite3_step.
|
||||||
|
let val_ref = self.stmt.value_ref(idx);
|
||||||
|
Ok(val_ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of a particular column of the result row as a `ValueRef`,
|
||||||
|
/// allowing data to be read out of a row without copying.
|
||||||
|
///
|
||||||
|
/// This `ValueRef` is valid only as long as this Row, which is enforced by
|
||||||
|
/// it's lifetime. This means that while this method is completely safe,
|
||||||
|
/// it can be difficult to use, and most callers will be better served by
|
||||||
|
/// `get` or `get`.
|
||||||
|
///
|
||||||
|
/// ## Failure
|
||||||
|
///
|
||||||
|
/// Panics if calling `row.get_raw_checked(idx)` would return an error,
|
||||||
|
/// including:
|
||||||
|
///
|
||||||
|
/// * If `idx` is outside the range of columns in the returned query.
|
||||||
|
/// * If `idx` is not a valid column name for this row.
|
||||||
|
pub fn get_raw<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
|
||||||
|
self.get_raw_checked(idx).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,13 +295,13 @@ 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<i32>;
|
fn idx(&self, stmt: &Statement<'_>) -> Result<usize>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RowIndex for i32 {
|
impl RowIndex for usize {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn idx(&self, stmt: &Statement) -> Result<i32> {
|
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
||||||
if *self < 0 || *self >= stmt.column_count() {
|
if *self >= stmt.column_count() {
|
||||||
Err(Error::InvalidColumnIndex(*self))
|
Err(Error::InvalidColumnIndex(*self))
|
||||||
} else {
|
} else {
|
||||||
Ok(*self)
|
Ok(*self)
|
||||||
@ -215,9 +309,9 @@ impl RowIndex for i32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RowIndex for &'a str {
|
impl RowIndex for &'_ str {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn idx(&self, stmt: &Statement) -> Result<i32> {
|
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
|
||||||
stmt.column_index(*self)
|
stmt.column_index(*self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
920
src/session.rs
Normal file
920
src/session.rs
Normal file
@ -0,0 +1,920 @@
|
|||||||
|
//! [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::MaybeUninit;
|
||||||
|
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 Session<'_> {
|
||||||
|
/// Create a new session object
|
||||||
|
pub fn new<'conn>(db: &'conn Connection) -> Result<Session<'conn>> {
|
||||||
|
Session::new_with_name(db, DatabaseName::Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new session object
|
||||||
|
pub fn new_with_name<'conn>(
|
||||||
|
db: &'conn Connection,
|
||||||
|
name: DatabaseName<'_>,
|
||||||
|
) -> Result<Session<'conn>> {
|
||||||
|
let name = name.to_cstring()?;
|
||||||
|
|
||||||
|
let db = db.db.borrow_mut().db;
|
||||||
|
|
||||||
|
let mut s = MaybeUninit::uninit();
|
||||||
|
check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), s.as_mut_ptr()) });
|
||||||
|
let s: *mut ffi::sqlite3_session = unsafe { s.assume_init() };
|
||||||
|
|
||||||
|
Ok(Session {
|
||||||
|
phantom: PhantomData,
|
||||||
|
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::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 = MaybeUninit::uninit();
|
||||||
|
check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, cs.as_mut_ptr()) });
|
||||||
|
let cs: *mut c_void = unsafe { cs.assume_init() };
|
||||||
|
Ok(Changeset { cs, n })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 = MaybeUninit::uninit();
|
||||||
|
check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, ps.as_mut_ptr()) });
|
||||||
|
let ps: *mut c_void = unsafe { ps.assume_init() };
|
||||||
|
// TODO Validate: same struct
|
||||||
|
Ok(Changeset { cs: ps, n })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 = MaybeUninit::uninit();
|
||||||
|
let r = ffi::sqlite3session_diff(self.s, from.as_ptr(), table, errmsg.as_mut_ptr());
|
||||||
|
if r != ffi::SQLITE_OK {
|
||||||
|
let errmsg: *mut c_char = errmsg.assume_init();
|
||||||
|
let message = errmsg_to_string(&*errmsg);
|
||||||
|
ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
|
||||||
|
return Err(error_from_sqlite_code(r, Some(message)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 Drop for Session<'_> {
|
||||||
|
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 = MaybeUninit::uninit();
|
||||||
|
check!(unsafe { ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, cs.as_mut_ptr()) });
|
||||||
|
let cs: *mut c_void = unsafe { cs.assume_init() };
|
||||||
|
Ok(Changeset { cs, n })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an iterator to traverse a changeset
|
||||||
|
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
|
||||||
|
let mut it = MaybeUninit::uninit();
|
||||||
|
check!(unsafe { ffi::sqlite3changeset_start(it.as_mut_ptr(), self.n, self.cs) });
|
||||||
|
let it: *mut ffi::sqlite3_changeset_iter = unsafe { it.assume_init() };
|
||||||
|
Ok(ChangesetIter {
|
||||||
|
phantom: PhantomData,
|
||||||
|
it,
|
||||||
|
item: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Concatenate two changeset objects
|
||||||
|
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
|
||||||
|
let mut n = 0;
|
||||||
|
let mut cs = MaybeUninit::uninit();
|
||||||
|
check!(unsafe {
|
||||||
|
ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, cs.as_mut_ptr())
|
||||||
|
});
|
||||||
|
let cs: *mut c_void = unsafe { cs.assume_init() };
|
||||||
|
Ok(Changeset { cs, n })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ChangesetIter<'_> {
|
||||||
|
/// Create an iterator on `input`
|
||||||
|
pub fn start_strm<'input>(input: &'input mut dyn Read) -> Result<ChangesetIter<'input>> {
|
||||||
|
let input_ref = &input;
|
||||||
|
let mut it = MaybeUninit::uninit();
|
||||||
|
check!(unsafe {
|
||||||
|
ffi::sqlite3changeset_start_strm(
|
||||||
|
it.as_mut_ptr(),
|
||||||
|
Some(x_input),
|
||||||
|
input_ref as *const &mut dyn Read as *mut c_void,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let it: *mut ffi::sqlite3_changeset_iter = unsafe { it.assume_init() };
|
||||||
|
Ok(ChangesetIter {
|
||||||
|
phantom: PhantomData,
|
||||||
|
it,
|
||||||
|
item: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FallibleStreamingIterator for ChangesetIter<'_> {
|
||||||
|
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 Operation<'_> {
|
||||||
|
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 Drop for ChangesetIter<'_> {
|
||||||
|
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 = MaybeUninit::uninit();
|
||||||
|
check!(ffi::sqlite3changeset_conflict(
|
||||||
|
self.it,
|
||||||
|
col as i32,
|
||||||
|
p_value.as_mut_ptr()
|
||||||
|
));
|
||||||
|
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
|
||||||
|
Ok(ValueRef::from_value(p_value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 = MaybeUninit::uninit();
|
||||||
|
check!(ffi::sqlite3changeset_new(
|
||||||
|
self.it,
|
||||||
|
col as i32,
|
||||||
|
p_value.as_mut_ptr()
|
||||||
|
));
|
||||||
|
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
|
||||||
|
Ok(ValueRef::from_value(p_value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 = MaybeUninit::uninit();
|
||||||
|
check!(ffi::sqlite3changeset_old(
|
||||||
|
self.it,
|
||||||
|
col as i32,
|
||||||
|
p_value.as_mut_ptr()
|
||||||
|
));
|
||||||
|
let p_value: *mut ffi::sqlite3_value = p_value.assume_init();
|
||||||
|
Ok(ValueRef::from_value(p_value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 = MaybeUninit::uninit();
|
||||||
|
check!(ffi::sqlite3changeset_op(
|
||||||
|
self.it,
|
||||||
|
pz_tab.as_mut_ptr(),
|
||||||
|
&mut number_of_columns,
|
||||||
|
&mut code,
|
||||||
|
&mut indirect
|
||||||
|
));
|
||||||
|
let pz_tab: *const c_char = pz_tab.assume_init();
|
||||||
|
CStr::from_ptr(pz_tab)
|
||||||
|
};
|
||||||
|
let table_name = tab.to_str()?;
|
||||||
|
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 = MaybeUninit::uninit();
|
||||||
|
check!(ffi::sqlite3changeset_pk(
|
||||||
|
self.it,
|
||||||
|
pks.as_mut_ptr(),
|
||||||
|
&mut number_of_columns
|
||||||
|
));
|
||||||
|
let pks: *mut c_uchar = pks.assume_init();
|
||||||
|
Ok(from_raw_parts(pks, number_of_columns as usize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 = MaybeUninit::uninit();
|
||||||
|
check!(unsafe { ffi::sqlite3changegroup_new(cg.as_mut_ptr()) });
|
||||||
|
let cg: *mut ffi::sqlite3_changegroup = unsafe { cg.assume_init() };
|
||||||
|
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 = MaybeUninit::uninit();
|
||||||
|
check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, output.as_mut_ptr()) });
|
||||||
|
let output: *mut c_void = unsafe { output.assume_init() };
|
||||||
|
Ok(Changeset { cs: output, n })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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::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::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());
|
||||||
|
}
|
||||||
|
}
|
881
src/statement.rs
881
src/statement.rs
File diff suppressed because it is too large
Load Diff
64
src/trace.rs
64
src/trace.rs
@ -1,14 +1,15 @@
|
|||||||
//! Tracing and profiling functions. Error and warning log.
|
//! Tracing and profiling functions. Error and warning log.
|
||||||
|
|
||||||
use std::os::raw::{c_char, c_int, c_void};
|
|
||||||
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::panic::catch_unwind;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
use {Result, Connection};
|
use crate::error::error_from_sqlite_code;
|
||||||
use error::error_from_sqlite_code;
|
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,15 +28,17 @@ 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 {
|
||||||
Some(f) => {
|
Some(f) => {
|
||||||
let p_arg: *mut c_void = mem::transmute(f);
|
let p_arg: *mut c_void = mem::transmute(f);
|
||||||
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG,
|
ffi::sqlite3_config(
|
||||||
log_callback as extern "C" fn(_, _, _),
|
ffi::SQLITE_CONFIG_LOG,
|
||||||
p_arg)
|
log_callback as extern "C" fn(_, _, _),
|
||||||
|
p_arg,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let nullptr: *mut c_void = ptr::null_mut();
|
let nullptr: *mut c_void = ptr::null_mut();
|
||||||
@ -59,18 +62,18 @@ pub fn log(err_code: c_int, msg: &str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Register or clear a callback function that can be used for tracing the execution of SQL
|
/// Register or clear a callback function that can be used for tracing the
|
||||||
/// statements.
|
/// execution of SQL statements.
|
||||||
///
|
///
|
||||||
/// Prepared statement placeholders are replaced/logged with their assigned values.
|
/// Prepared statement placeholders are replaced/logged with their assigned
|
||||||
/// There can only be a single tracer defined for each database connection.
|
/// values. There can only be a single tracer defined for each database
|
||||||
/// Setting a new tracer clears the old one.
|
/// connection. Setting a new tracer clears the old one.
|
||||||
pub fn trace(&mut self, trace_fn: Option<fn(&str)>) {
|
pub fn trace(&mut self, trace_fn: Option<fn(&str)>) {
|
||||||
unsafe extern "C" fn trace_callback(p_arg: *mut c_void, z_sql: *const c_char) {
|
unsafe extern "C" fn trace_callback(p_arg: *mut c_void, z_sql: *const c_char) {
|
||||||
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();
|
||||||
@ -84,23 +87,27 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register or clear a callback function that can be used for profiling the execution of SQL
|
/// Register or clear a callback function that can be used for profiling
|
||||||
/// statements.
|
/// the execution of SQL statements.
|
||||||
///
|
///
|
||||||
/// There can only be a single profiler defined for each database connection.
|
/// There can only be a single profiler defined for each database
|
||||||
/// Setting a new profiler clears the old one.
|
/// connection. Setting a new profiler clears the old one.
|
||||||
pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
|
pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
|
||||||
unsafe extern "C" fn profile_callback(p_arg: *mut c_void,
|
unsafe extern "C" fn profile_callback(
|
||||||
z_sql: *const c_char,
|
p_arg: *mut c_void,
|
||||||
nanoseconds: u64) {
|
z_sql: *const c_char,
|
||||||
|
nanoseconds: u64,
|
||||||
|
) {
|
||||||
let profile_fn: fn(&str, Duration) = mem::transmute(p_arg);
|
let profile_fn: fn(&str, Duration) = 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);
|
||||||
const NANOS_PER_SEC: u64 = 1_000_000_000;
|
const NANOS_PER_SEC: u64 = 1_000_000_000;
|
||||||
|
|
||||||
let duration = Duration::new(nanoseconds / NANOS_PER_SEC,
|
let duration = Duration::new(
|
||||||
(nanoseconds % NANOS_PER_SEC) as u32);
|
nanoseconds / NANOS_PER_SEC,
|
||||||
profile_fn(&s, duration);
|
(nanoseconds % NANOS_PER_SEC) as u32,
|
||||||
|
);
|
||||||
|
let _ = catch_unwind(|| profile_fn(&s, duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
let c = self.db.borrow_mut();
|
let c = self.db.borrow_mut();
|
||||||
@ -115,10 +122,11 @@ impl Connection {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use Connection;
|
use crate::Connection;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_trace() {
|
fn test_trace() {
|
||||||
@ -133,13 +141,13 @@ mod test {
|
|||||||
let mut db = Connection::open_in_memory().unwrap();
|
let mut db = Connection::open_in_memory().unwrap();
|
||||||
db.trace(Some(tracer));
|
db.trace(Some(tracer));
|
||||||
{
|
{
|
||||||
let _ = db.query_row("SELECT ?", &[&1i32], |_| {});
|
let _ = db.query_row("SELECT ?", &[1i32], |_| Ok(()));
|
||||||
let _ = db.query_row("SELECT ?", &[&"hello"], |_| {});
|
let _ = db.query_row("SELECT ?", &["hello"], |_| Ok(()));
|
||||||
}
|
}
|
||||||
db.trace(None);
|
db.trace(None);
|
||||||
{
|
{
|
||||||
let _ = db.query_row("SELECT ?", &[&2i32], |_| {});
|
let _ = db.query_row("SELECT ?", &[2i32], |_| Ok(()));
|
||||||
let _ = db.query_row("SELECT ?", &[&"goodbye"], |_| {});
|
let _ = db.query_row("SELECT ?", &["goodbye"], |_| Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let traced_stmts = TRACED_STMTS.lock().unwrap();
|
let traced_stmts = TRACED_STMTS.lock().unwrap();
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
|
use crate::{Connection, Result};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use {Result, Connection};
|
|
||||||
|
|
||||||
/// 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.
|
||||||
#[derive(Copy,Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum TransactionBehavior {
|
pub enum TransactionBehavior {
|
||||||
Deferred,
|
Deferred,
|
||||||
Immediate,
|
Immediate,
|
||||||
@ -15,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,
|
||||||
@ -23,22 +19,21 @@ pub enum DropBehavior {
|
|||||||
/// Commit the changes.
|
/// Commit the changes.
|
||||||
Commit,
|
Commit,
|
||||||
|
|
||||||
/// Do not commit or roll back changes - this will leave the transaction or savepoint
|
/// Do not commit or roll back changes - this will leave the transaction or
|
||||||
/// open, so should be used with care.
|
/// savepoint open, so should be used with care.
|
||||||
Ignore,
|
Ignore,
|
||||||
}
|
|
||||||
|
|
||||||
/// Old name for `Transaction`. `SqliteTransaction` is deprecated.
|
/// Panic. Used to enforce intentional behavior during development.
|
||||||
#[deprecated(since = "0.6.0", note = "Use Transaction instead")]
|
Panic,
|
||||||
pub type SqliteTransaction<'conn> = Transaction<'conn>;
|
}
|
||||||
|
|
||||||
/// Represents a transaction on a database connection.
|
/// Represents a transaction on a database connection.
|
||||||
///
|
///
|
||||||
/// ## Note
|
/// ## Note
|
||||||
///
|
///
|
||||||
/// Transactions will roll back by default. Use `commit` method to explicitly commit the
|
/// Transactions will roll back by default. Use `commit` method to explicitly
|
||||||
/// transaction, or use `set_drop_behavior` to change what happens when the transaction
|
/// commit the transaction, or use `set_drop_behavior` to change what happens
|
||||||
/// is dropped.
|
/// when the transaction is dropped.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
@ -47,27 +42,27 @@ 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,
|
||||||
committed: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a savepoint on a database connection.
|
/// Represents a savepoint on a database connection.
|
||||||
///
|
///
|
||||||
/// ## Note
|
/// ## Note
|
||||||
///
|
///
|
||||||
/// Savepoints will roll back by default. Use `commit` method to explicitly commit the
|
/// Savepoints will roll back by default. Use `commit` method to explicitly
|
||||||
/// savepoint, or use `set_drop_behavior` to change what happens when the savepoint
|
/// commit the savepoint, or use `set_drop_behavior` to change what happens
|
||||||
/// is dropped.
|
/// when the savepoint is dropped.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
@ -76,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()
|
||||||
/// }
|
/// }
|
||||||
@ -92,22 +87,22 @@ pub struct Savepoint<'conn> {
|
|||||||
committed: bool,
|
committed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Transaction<'conn> {
|
impl Transaction<'_> {
|
||||||
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested transactions.
|
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested
|
||||||
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction> {
|
/// transactions.
|
||||||
|
/// Even though we don't mutate the connection, we take a `&mut Connection`
|
||||||
|
/// so as to prevent nested or concurrent transactions on the same
|
||||||
|
/// connection.
|
||||||
|
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",
|
||||||
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
|
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
|
||||||
};
|
};
|
||||||
conn.execute_batch(query)
|
conn.execute_batch(query).map(move |_| Transaction {
|
||||||
.map(move |_| {
|
conn,
|
||||||
Transaction {
|
drop_behavior: DropBehavior::Rollback,
|
||||||
conn: conn,
|
})
|
||||||
drop_behavior: DropBehavior::Rollback,
|
|
||||||
committed: false,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested
|
/// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested
|
||||||
@ -115,7 +110,8 @@ impl<'conn> Transaction<'conn> {
|
|||||||
///
|
///
|
||||||
/// ## Note
|
/// ## Note
|
||||||
///
|
///
|
||||||
/// Just like outer level transactions, savepoint transactions rollback by default.
|
/// Just like outer level transactions, savepoint transactions rollback by
|
||||||
|
/// default.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
@ -123,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
|
||||||
/// }
|
/// }
|
||||||
@ -136,21 +132,23 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current setting for what happens to the transaction when it is dropped.
|
/// Get the current setting for what happens to the transaction when it is
|
||||||
|
/// dropped.
|
||||||
pub fn drop_behavior(&self) -> DropBehavior {
|
pub fn drop_behavior(&self) -> DropBehavior {
|
||||||
self.drop_behavior
|
self.drop_behavior
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure the transaction to perform the specified action when it is dropped.
|
/// Configure the transaction to perform the specified action when it is
|
||||||
|
/// dropped.
|
||||||
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
|
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
|
||||||
self.drop_behavior = drop_behavior
|
self.drop_behavior = drop_behavior
|
||||||
}
|
}
|
||||||
@ -161,8 +159,8 @@ impl<'conn> Transaction<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn commit_(&mut self) -> Result<()> {
|
fn commit_(&mut self) -> Result<()> {
|
||||||
self.committed = true;
|
self.conn.execute_batch("COMMIT")?;
|
||||||
self.conn.execute_batch("COMMIT")
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A convenience method which consumes and rolls back a transaction.
|
/// A convenience method which consumes and rolls back a transaction.
|
||||||
@ -171,32 +169,33 @@ impl<'conn> Transaction<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn rollback_(&mut self) -> Result<()> {
|
fn rollback_(&mut self) -> Result<()> {
|
||||||
self.committed = true;
|
self.conn.execute_batch("ROLLBACK")?;
|
||||||
self.conn.execute_batch("ROLLBACK")
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes the transaction, committing or rolling back according to the current setting
|
/// Consumes the transaction, committing or rolling back according to the
|
||||||
/// (see `drop_behavior`).
|
/// current setting (see `drop_behavior`).
|
||||||
///
|
///
|
||||||
/// Functionally equivalent to the `Drop` implementation, but allows callers to see any
|
/// Functionally equivalent to the `Drop` implementation, but allows
|
||||||
/// errors that occur.
|
/// callers to see any errors that occur.
|
||||||
pub fn finish(mut self) -> Result<()> {
|
pub fn finish(mut self) -> Result<()> {
|
||||||
self.finish_()
|
self.finish_()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_(&mut self) -> Result<()> {
|
fn finish_(&mut self) -> Result<()> {
|
||||||
if self.committed {
|
if self.conn.is_autocommit() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
match self.drop_behavior() {
|
match self.drop_behavior() {
|
||||||
DropBehavior::Commit => self.commit_(),
|
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()),
|
||||||
DropBehavior::Rollback => self.rollback_(),
|
DropBehavior::Rollback => self.rollback_(),
|
||||||
DropBehavior::Ignore => Ok(()),
|
DropBehavior::Ignore => Ok(()),
|
||||||
|
DropBehavior::Panic => panic!("Transaction dropped unexpectedly."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Deref for Transaction<'conn> {
|
impl Deref for Transaction<'_> {
|
||||||
type Target = Connection;
|
type Target = Connection;
|
||||||
|
|
||||||
fn deref(&self) -> &Connection {
|
fn deref(&self) -> &Connection {
|
||||||
@ -205,61 +204,62 @@ impl<'conn> Deref for Transaction<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
impl<'conn> Drop for Transaction<'conn> {
|
impl Drop for Transaction<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.finish_();
|
self.finish_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Savepoint<'conn> {
|
impl Savepoint<'_> {
|
||||||
fn with_depth_and_name<T: Into<String>>(conn: &Connection,
|
fn with_depth_and_name<T: Into<String>>(
|
||||||
depth: u32,
|
conn: &Connection,
|
||||||
name: T)
|
depth: u32,
|
||||||
-> Result<Savepoint> {
|
name: T,
|
||||||
|
) -> Result<Savepoint<'_>> {
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
conn.execute_batch(&format!("SAVEPOINT {}", name))
|
conn.execute_batch(&format!("SAVEPOINT {}", name))
|
||||||
.map(|_| {
|
.map(|_| Savepoint {
|
||||||
Savepoint {
|
conn,
|
||||||
conn: conn,
|
name,
|
||||||
name: name,
|
depth,
|
||||||
depth: depth,
|
drop_behavior: DropBehavior::Rollback,
|
||||||
drop_behavior: DropBehavior::Rollback,
|
committed: false,
|
||||||
committed: false,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current setting for what happens to the savepoint when it is dropped.
|
/// Get the current setting for what happens to the savepoint when it is
|
||||||
|
/// dropped.
|
||||||
pub fn drop_behavior(&self) -> DropBehavior {
|
pub fn drop_behavior(&self) -> DropBehavior {
|
||||||
self.drop_behavior
|
self.drop_behavior
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure the savepoint to perform the specified action when it is dropped.
|
/// Configure the savepoint to perform the specified action when it is
|
||||||
|
/// dropped.
|
||||||
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
|
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
|
||||||
self.drop_behavior = drop_behavior
|
self.drop_behavior = drop_behavior
|
||||||
}
|
}
|
||||||
@ -270,27 +270,27 @@ impl<'conn> Savepoint<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn commit_(&mut self) -> Result<()> {
|
fn commit_(&mut self) -> Result<()> {
|
||||||
|
self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
|
||||||
self.committed = true;
|
self.committed = true;
|
||||||
self.conn
|
Ok(())
|
||||||
.execute_batch(&format!("RELEASE {}", self.name))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A convenience method which rolls back a savepoint.
|
/// A convenience method which rolls back a savepoint.
|
||||||
///
|
///
|
||||||
/// ## Note
|
/// ## Note
|
||||||
///
|
///
|
||||||
/// Unlike `Transaction`s, savepoints remain active after they have been rolled back,
|
/// Unlike `Transaction`s, savepoints remain active after they have been
|
||||||
/// and can be rolled back again or committed.
|
/// rolled back, and can be rolled back again or committed.
|
||||||
pub fn rollback(&mut self) -> Result<()> {
|
pub fn rollback(&mut self) -> Result<()> {
|
||||||
self.conn
|
self.conn
|
||||||
.execute_batch(&format!("ROLLBACK TO {}", self.name))
|
.execute_batch(&format!("ROLLBACK TO {}", self.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes the savepoint, committing or rolling back according to the current setting
|
/// Consumes the savepoint, committing or rolling back according to the
|
||||||
/// (see `drop_behavior`).
|
/// current setting (see `drop_behavior`).
|
||||||
///
|
///
|
||||||
/// Functionally equivalent to the `Drop` implementation, but allows callers to see any
|
/// Functionally equivalent to the `Drop` implementation, but allows
|
||||||
/// errors that occur.
|
/// callers to see any errors that occur.
|
||||||
pub fn finish(mut self) -> Result<()> {
|
pub fn finish(mut self) -> Result<()> {
|
||||||
self.finish_()
|
self.finish_()
|
||||||
}
|
}
|
||||||
@ -300,14 +300,15 @@ impl<'conn> Savepoint<'conn> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
match self.drop_behavior() {
|
match self.drop_behavior() {
|
||||||
DropBehavior::Commit => self.commit_(),
|
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()),
|
||||||
DropBehavior::Rollback => self.rollback(),
|
DropBehavior::Rollback => self.rollback(),
|
||||||
DropBehavior::Ignore => Ok(()),
|
DropBehavior::Ignore => Ok(()),
|
||||||
|
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'conn> Deref for Savepoint<'conn> {
|
impl Deref for Savepoint<'_> {
|
||||||
type Target = Connection;
|
type Target = Connection;
|
||||||
|
|
||||||
fn deref(&self) -> &Connection {
|
fn deref(&self) -> &Connection {
|
||||||
@ -316,7 +317,7 @@ impl<'conn> Deref for Savepoint<'conn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
impl<'conn> Drop for Savepoint<'conn> {
|
impl Drop for Savepoint<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.finish_();
|
self.finish_();
|
||||||
}
|
}
|
||||||
@ -325,8 +326,9 @@ impl<'conn> Drop for Savepoint<'conn> {
|
|||||||
impl Connection {
|
impl Connection {
|
||||||
/// Begin a new transaction with the default behavior (DEFERRED).
|
/// Begin a new transaction with the default behavior (DEFERRED).
|
||||||
///
|
///
|
||||||
/// The transaction defaults to rolling back when it is dropped. If you want the transaction to
|
/// The transaction defaults to rolling back when it is dropped. If you
|
||||||
/// commit, you must call `commit` or `set_drop_behavior(DropBehavior::Commit)`.
|
/// want the transaction to commit, you must call `commit` or
|
||||||
|
/// `set_drop_behavior(DropBehavior::Commit)`.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
@ -335,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()
|
||||||
/// }
|
/// }
|
||||||
@ -347,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,16 +360,18 @@ 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_with_behavior(&mut self,
|
pub fn transaction_with_behavior(
|
||||||
behavior: TransactionBehavior)
|
&mut self,
|
||||||
-> Result<Transaction> {
|
behavior: TransactionBehavior,
|
||||||
|
) -> Result<Transaction<'_>> {
|
||||||
Transaction::new(self, behavior)
|
Transaction::new(self, behavior)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Begin a new savepoint with the default behavior (DEFERRED).
|
/// Begin a new savepoint with the default behavior (DEFERRED).
|
||||||
///
|
///
|
||||||
/// The savepoint defaults to rolling back when it is dropped. If you want the savepoint to
|
/// The savepoint defaults to rolling back when it is dropped. If you want
|
||||||
/// commit, you must call `commit` or `set_drop_behavior(DropBehavior::Commit)`.
|
/// the savepoint to commit, you must call `commit` or
|
||||||
|
/// `set_drop_behavior(DropBehavior::Commit)`.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
@ -376,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()
|
||||||
/// }
|
/// }
|
||||||
@ -388,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,15 +403,15 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use Connection;
|
|
||||||
use super::DropBehavior;
|
use super::DropBehavior;
|
||||||
|
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();
|
||||||
@ -430,9 +434,11 @@ mod test {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
let tx = db.transaction().unwrap();
|
let tx = db.transaction().unwrap();
|
||||||
assert_eq!(2i32,
|
assert_eq!(
|
||||||
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
|
2i32,
|
||||||
.unwrap());
|
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,9 +463,11 @@ mod test {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
let tx = db.transaction().unwrap();
|
let tx = db.transaction().unwrap();
|
||||||
assert_eq!(6i32,
|
assert_eq!(
|
||||||
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
|
6i32,
|
||||||
.unwrap());
|
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,12 +556,23 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_current_sum(x: i32, conn: &Connection) {
|
fn assert_current_sum(x: i32, conn: &Connection) {
|
||||||
let i = conn.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
|
let i = conn
|
||||||
|
.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(x, i);
|
assert_eq!(x, i);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
//! 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 std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, Utc, Local};
|
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
||||||
|
|
||||||
use Result;
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
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,19 +17,19 @@ 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") {
|
||||||
Ok(dt) => Ok(dt),
|
Ok(dt) => Ok(dt),
|
||||||
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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,65 +37,64 @@ 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
|
value.as_str().and_then(|s| {
|
||||||
.as_str()
|
let fmt = match s.len() {
|
||||||
.and_then(|s| {
|
5 => "%H:%M",
|
||||||
let fmt = match s.len() {
|
8 => "%H:%M:%S",
|
||||||
5 => "%H:%M",
|
_ => "%H:%M:%S%.f",
|
||||||
8 => "%H:%M:%S",
|
};
|
||||||
_ => "%H:%M:%S%.f",
|
match NaiveTime::parse_from_str(s, fmt) {
|
||||||
};
|
Ok(dt) => Ok(dt),
|
||||||
match NaiveTime::parse_from_str(s, fmt) {
|
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
||||||
Ok(dt) => Ok(dt),
|
}
|
||||||
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
})
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS"
|
/// ISO 8601 combined date and time without timezone =>
|
||||||
|
/// "YYYY-MM-DDTHH:MM:SS.SSS"
|
||||||
impl ToSql for NaiveDateTime {
|
impl ToSql for NaiveDateTime {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
|
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
|
||||||
Ok(ToSqlOutput::from(date_str))
|
Ok(ToSqlOutput::from(date_str))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// "YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS" => ISO 8601 combined date and time
|
/// "YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS" => ISO 8601 combined date
|
||||||
/// without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported)
|
/// and time without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS"
|
||||||
|
/// 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
|
value.as_str().and_then(|s| {
|
||||||
.as_str()
|
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
|
||||||
.and_then(|s| {
|
"%Y-%m-%dT%H:%M:%S%.f"
|
||||||
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
|
} else {
|
||||||
"%Y-%m-%dT%H:%M:%S%.f"
|
"%Y-%m-%d %H:%M:%S%.f"
|
||||||
} else {
|
};
|
||||||
"%Y-%m-%d %H:%M:%S%.f"
|
|
||||||
};
|
|
||||||
|
|
||||||
match NaiveDateTime::parse_from_str(s, fmt) {
|
match NaiveDateTime::parse_from_str(s, fmt) {
|
||||||
Ok(dt) => Ok(dt),
|
Ok(dt) => Ok(dt),
|
||||||
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Date and time with time zone => UTC RFC3339 timestamp ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
|
/// Date and time with time zone => UTC RFC3339 timestamp
|
||||||
|
/// ("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' ' {
|
||||||
@ -122,17 +120,16 @@ 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use Connection;
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
|
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
||||||
Duration};
|
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
@ -148,10 +145,12 @@ mod test {
|
|||||||
db.execute("INSERT INTO foo (t) VALUES (?)", &[&date])
|
db.execute("INSERT INTO foo (t) VALUES (?)", &[&date])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let s: String = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!("2016-02-23", s);
|
assert_eq!("2016-02-23", s);
|
||||||
let t: NaiveDate = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let t: NaiveDate = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(date, t);
|
assert_eq!(date, t);
|
||||||
}
|
}
|
||||||
@ -163,10 +162,12 @@ mod test {
|
|||||||
db.execute("INSERT INTO foo (t) VALUES (?)", &[&time])
|
db.execute("INSERT INTO foo (t) VALUES (?)", &[&time])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let s: String = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!("23:56:04", s);
|
assert_eq!("23:56:04", s);
|
||||||
let v: NaiveTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let v: NaiveTime = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(time, v);
|
assert_eq!(time, v);
|
||||||
}
|
}
|
||||||
@ -181,16 +182,19 @@ mod test {
|
|||||||
db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt])
|
db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let s: String = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!("2016-02-23T23:56:04", s);
|
assert_eq!("2016-02-23T23:56:04", s);
|
||||||
let v: NaiveDateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let v: NaiveDateTime = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(dt, v);
|
assert_eq!(dt, v);
|
||||||
|
|
||||||
db.execute("UPDATE foo set b = datetime(t)", &[])
|
db.execute("UPDATE foo set b = datetime(t)", NO_PARAMS)
|
||||||
.unwrap(); // "YYYY-MM-DD HH:MM:SS"
|
.unwrap(); // "YYYY-MM-DD HH:MM:SS"
|
||||||
let hms: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0))
|
let hms: NaiveDateTime = db
|
||||||
|
.query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(dt, hms);
|
assert_eq!(dt, hms);
|
||||||
}
|
}
|
||||||
@ -206,25 +210,31 @@ mod test {
|
|||||||
db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc])
|
db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let s: String = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!("2016-02-23T23:56:04.789+00:00", s);
|
assert_eq!("2016-02-23T23:56:04.789+00:00", s);
|
||||||
|
|
||||||
let v1: DateTime<Utc> = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let v1: DateTime<Utc> = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(utc, v1);
|
assert_eq!(utc, v1);
|
||||||
|
|
||||||
let v2: DateTime<Utc> = db.query_row("SELECT '2016-02-23 23:56:04.789'", &[], |r| r.get(0))
|
let v2: DateTime<Utc> = db
|
||||||
|
.query_row("SELECT '2016-02-23 23:56:04.789'", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(utc, v2);
|
assert_eq!(utc, v2);
|
||||||
|
|
||||||
let v3: DateTime<Utc> = db.query_row("SELECT '2016-02-23 23:56:04'", &[], |r| r.get(0))
|
let v3: DateTime<Utc> = db
|
||||||
|
.query_row("SELECT '2016-02-23 23:56:04'", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(utc - Duration::milliseconds(789), v3);
|
assert_eq!(utc - Duration::milliseconds(789), v3);
|
||||||
|
|
||||||
let v4: DateTime<Utc> =
|
let v4: DateTime<Utc> = db
|
||||||
db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0))
|
.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", NO_PARAMS, |r| {
|
||||||
.unwrap();
|
r.get(0)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
assert_eq!(utc, v4);
|
assert_eq!(utc, v4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,12 +250,31 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Stored string should be in UTC
|
// Stored string should be in UTC
|
||||||
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let s: String = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(s.ends_with("+00:00"));
|
assert!(s.ends_with("+00:00"));
|
||||||
|
|
||||||
let v: DateTime<Local> = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let v: DateTime<Local> = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,59 @@
|
|||||||
use super::{ValueRef, Value};
|
use super::{Value, ValueRef};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// Enum listing possible errors from `FromSql` trait.
|
/// Enum listing possible errors from `FromSql` trait.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FromSqlError {
|
pub enum FromSqlError {
|
||||||
/// Error when an SQLite value is requested, but the type of the result cannot be converted to
|
/// Error when an SQLite value is requested, but the type of the result
|
||||||
/// the requested Rust type.
|
/// cannot be converted to the requested Rust type.
|
||||||
InvalidType,
|
InvalidType,
|
||||||
|
|
||||||
/// Error when the i64 value returned by SQLite cannot be stored into the requested type.
|
/// Error when the i64 value returned by SQLite cannot be stored into the
|
||||||
|
/// requested type.
|
||||||
OutOfRange(i64),
|
OutOfRange(i64),
|
||||||
|
|
||||||
|
/// Error returned when reading an `i128` from a blob with a size
|
||||||
|
/// other than 16. Only available when the `i128_blob` feature is enabled.
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
InvalidI128Size(usize),
|
||||||
|
|
||||||
|
/// Error returned when reading a `uuid` from a blob with a size
|
||||||
|
/// other than 16. Only available when the `uuid` feature is enabled.
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
InvalidUuidSize(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,
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
(FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(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),
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
FromSqlError::InvalidI128Size(s) => {
|
||||||
|
write!(f, "Cannot read 128bit value out of {} byte blob", s)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(s) => {
|
||||||
|
write!(f, "Cannot read UUID value out of {} byte blob", s)
|
||||||
|
}
|
||||||
FromSqlError::Other(ref err) => err.fmt(f),
|
FromSqlError::Other(ref err) => err.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,16 +64,20 @@ impl Error for FromSqlError {
|
|||||||
match *self {
|
match *self {
|
||||||
FromSqlError::InvalidType => "invalid type",
|
FromSqlError::InvalidType => "invalid type",
|
||||||
FromSqlError::OutOfRange(_) => "value out of range",
|
FromSqlError::OutOfRange(_) => "value out of range",
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
FromSqlError::InvalidI128Size(_) => "unexpected blob size for 128bit value",
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
FromSqlError::InvalidUuidSize(_) => "unexpected blob size for UUID value",
|
||||||
FromSqlError::Other(ref err) => err.description(),
|
FromSqlError::Other(ref err) => err.description(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 |
|
_ => None,
|
||||||
FromSqlError::OutOfRange(_) => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,19 +87,21 @@ pub type FromSqlResult<T> = Result<T, FromSqlError>;
|
|||||||
|
|
||||||
/// A trait for types that can be created from a SQLite value.
|
/// A trait for types that can be created from a SQLite value.
|
||||||
///
|
///
|
||||||
/// Note that `FromSql` and `ToSql` are defined for most integral types, but not `u64` or `usize`.
|
/// Note that `FromSql` and `ToSql` are defined for most integral types, but
|
||||||
/// This is intentional; SQLite returns integers as signed 64-bit values, which cannot fully
|
/// not `u64` or `usize`. This is intentional; SQLite returns integers as
|
||||||
/// represent the range of these types. Rusqlite would have to decide how to handle negative
|
/// signed 64-bit values, which cannot fully represent the range of these
|
||||||
/// values: return an error or reinterpret as a very large postive numbers, neither of which is
|
/// types. Rusqlite would have to
|
||||||
/// guaranteed to be correct for everyone. Callers can work around this by fetching values as i64
|
/// decide how to handle negative values: return an error or reinterpret as a
|
||||||
/// and then doing the interpretation themselves or by defining a newtype and implementing
|
/// very large postive numbers, neither of which
|
||||||
/// `FromSql`/`ToSql` for it.
|
/// is guaranteed to be correct for everyone. Callers can work around this by
|
||||||
|
/// fetching values as i64 and then doing the interpretation themselves or by
|
||||||
|
/// 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))
|
||||||
@ -76,7 +115,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))
|
||||||
@ -97,13 +136,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),
|
||||||
@ -113,28 +152,56 @@ 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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(ToString::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")]
|
||||||
|
impl FromSql for i128 {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
|
||||||
|
value.as_blob().and_then(|bytes| {
|
||||||
|
if bytes.len() == 16 {
|
||||||
|
Ok(BigEndian::read_i128(bytes) ^ (1i128 << 127))
|
||||||
|
} else {
|
||||||
|
Err(FromSqlError::InvalidI128Size(bytes.len()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
impl FromSql for uuid::Uuid {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
value
|
||||||
|
.as_blob()
|
||||||
|
.and_then(|bytes| {
|
||||||
|
uuid::Builder::from_slice(bytes)
|
||||||
|
.map_err(|_| FromSqlError::InvalidUuidSize(bytes.len()))
|
||||||
|
})
|
||||||
|
.map(|mut builder| builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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),
|
||||||
@ -143,15 +210,15 @@ 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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use {Connection, Error};
|
|
||||||
use super::FromSql;
|
use super::FromSql;
|
||||||
|
use crate::{Connection, Error};
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
Connection::open_in_memory().unwrap()
|
Connection::open_in_memory().unwrap()
|
||||||
@ -162,11 +229,12 @@ mod test {
|
|||||||
let db = checked_memory_handle();
|
let db = checked_memory_handle();
|
||||||
|
|
||||||
fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64])
|
fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64])
|
||||||
where T: Into<i64> + FromSql + ::std::fmt::Debug
|
where
|
||||||
|
T: Into<i64> + FromSql + ::std::fmt::Debug,
|
||||||
{
|
{
|
||||||
for n in out_of_range {
|
for n in out_of_range {
|
||||||
let err = db.query_row("SELECT ?", &[n], |r| r.get_checked::<_, T>(0))
|
let err = db
|
||||||
.unwrap()
|
.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
match err {
|
match err {
|
||||||
Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
|
Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
|
||||||
@ -174,20 +242,24 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for n in in_range {
|
for n in in_range {
|
||||||
assert_eq!(*n,
|
assert_eq!(
|
||||||
db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
|
*n,
|
||||||
.unwrap()
|
db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
|
||||||
.into());
|
.unwrap()
|
||||||
|
.into()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
|
check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
|
||||||
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>(&db,
|
check_ranges::<i32>(
|
||||||
&[-2147483649, 2147483648],
|
&db,
|
||||||
&[-2147483648, -1, 0, 1, 2147483647]);
|
&[-2_147_483_649, 2_147_483_648],
|
||||||
|
&[-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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
279
src/types/mod.rs
279
src/types/mod.rs
@ -1,36 +1,36 @@
|
|||||||
//! Traits dealing with SQLite data types.
|
//! Traits dealing with SQLite data types.
|
||||||
//!
|
//!
|
||||||
//! SQLite uses a [dynamic type system](https://www.sqlite.org/datatype3.html). Implementations of
|
//! SQLite uses a [dynamic type system](https://www.sqlite.org/datatype3.html). Implementations of
|
||||||
//! the `ToSql` and `FromSql` traits are provided for the basic types that SQLite provides methods
|
//! the `ToSql` and `FromSql` traits are provided for the basic types that
|
||||||
//! for:
|
//! SQLite provides methods for:
|
||||||
//!
|
//!
|
||||||
//! * Integers (`i32` and `i64`; SQLite uses `i64` internally, so getting an `i32` will truncate
|
//! * Integers (`i32` and `i64`; SQLite uses `i64` internally, so getting an
|
||||||
//! if the value is too large or too small).
|
//! `i32` will truncate if the value is too large or too small).
|
||||||
//! * Reals (`f64`)
|
//! * Reals (`f64`)
|
||||||
//! * Strings (`String` and `&str`)
|
//! * Strings (`String` and `&str`)
|
||||||
//! * Blobs (`Vec<u8>` and `&[u8]`)
|
//! * Blobs (`Vec<u8>` and `&[u8]`)
|
||||||
//!
|
//!
|
||||||
//! Additionally, because it is such a common data type, implementations are provided for
|
//! Additionally, because it is such a common data type, implementations are
|
||||||
//! `time::Timespec` that use a string for storage (using the same format string,
|
//! provided for `time::Timespec` that use the RFC 3339 date/time format,
|
||||||
//! `"%Y-%m-%d %H:%M:%S"`, as SQLite's builtin
|
//! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values
|
||||||
//! [datetime](https://www.sqlite.org/lang_datefunc.html) function. Note that this storage
|
//! can be parsed by SQLite's builtin
|
||||||
//! truncates timespecs to the nearest second. If you want different storage for timespecs, you can
|
//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you
|
||||||
//! use a newtype. For example, to store timespecs as `f64`s:
|
//! want different storage for timespecs, you can use a newtype. For example, to
|
||||||
|
//! store timespecs as `f64`s:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! extern crate rusqlite;
|
//! use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
//! extern crate time;
|
//! use rusqlite::Result;
|
||||||
//!
|
|
||||||
//! use rusqlite::types::{FromSql, FromSqlResult, ValueRef, ToSql, ToSqlOutput};
|
|
||||||
//! use rusqlite::{Result};
|
|
||||||
//!
|
//!
|
||||||
//! pub struct TimespecSql(pub time::Timespec);
|
//! pub struct TimespecSql(pub time::Timespec);
|
||||||
//!
|
//!
|
||||||
//! impl FromSql for TimespecSql {
|
//! impl FromSql for TimespecSql {
|
||||||
//! fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
//! fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||||
//! f64::column_result(value).map(|as_f64| {
|
//! f64::column_result(value).map(|as_f64| {
|
||||||
//! TimespecSql(time::Timespec{ sec: as_f64.trunc() as i64,
|
//! TimespecSql(time::Timespec {
|
||||||
//! nsec: (as_f64.fract() * 1.0e9) as i32 })
|
//! sec: as_f64.trunc() as i64,
|
||||||
|
//! nsec: (as_f64.fract() * 1.0e9) as i32,
|
||||||
|
//! })
|
||||||
//! })
|
//! })
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
@ -42,14 +42,11 @@
|
|||||||
//! Ok(as_f64.into())
|
//! Ok(as_f64.into())
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
|
||||||
//! # // Prevent this doc test from being wrapped in a `fn main()` so that it will compile.
|
|
||||||
//! # fn main() {}
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T` implements `ToSql` or
|
//! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T`
|
||||||
//! `FromSql` for the cases where you want to know if a value was NULL (which gets translated to
|
//! implements `ToSql` or `FromSql` for the cases where you want to know if a
|
||||||
//! `None`).
|
//! value was NULL (which gets translated to `None`).
|
||||||
|
|
||||||
pub use self::from_sql::{FromSql, FromSqlError, FromSqlResult};
|
pub use self::from_sql::{FromSql, FromSqlError, FromSqlResult};
|
||||||
pub use self::to_sql::{ToSql, ToSqlOutput};
|
pub use self::to_sql::{ToSql, ToSqlOutput};
|
||||||
@ -58,35 +55,34 @@ pub use self::value_ref::ValueRef;
|
|||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
mod value;
|
|
||||||
mod value_ref;
|
|
||||||
mod from_sql;
|
|
||||||
mod to_sql;
|
|
||||||
mod time;
|
|
||||||
#[cfg(feature = "chrono")]
|
#[cfg(feature = "chrono")]
|
||||||
mod chrono;
|
mod chrono;
|
||||||
|
mod from_sql;
|
||||||
#[cfg(feature = "serde_json")]
|
#[cfg(feature = "serde_json")]
|
||||||
mod serde_json;
|
mod serde_json;
|
||||||
|
mod time;
|
||||||
|
mod to_sql;
|
||||||
|
#[cfg(feature = "url")]
|
||||||
|
mod url;
|
||||||
|
mod value;
|
||||||
|
mod value_ref;
|
||||||
|
|
||||||
/// Empty struct that can be used to fill in a query parameter as `NULL`.
|
/// Empty struct that can be used to fill in a query parameter as `NULL`.
|
||||||
///
|
///
|
||||||
/// ## 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};
|
||||||
/// # use std::os::raw::{c_int};
|
///
|
||||||
/// fn main() {
|
/// fn insert_null(conn: &Connection) -> Result<usize> {
|
||||||
/// }
|
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[Null])
|
||||||
/// fn insert_null(conn: &Connection) -> Result<c_int> {
|
|
||||||
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[&Null])
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Copy,Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Null;
|
pub struct Null;
|
||||||
|
|
||||||
#[derive(Clone,Debug,PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
Null,
|
Null,
|
||||||
Integer,
|
Integer,
|
||||||
@ -96,7 +92,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"),
|
||||||
@ -109,13 +105,12 @@ impl fmt::Display for Type {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
extern crate time;
|
use time;
|
||||||
|
|
||||||
use Connection;
|
|
||||||
use Error;
|
|
||||||
use std::os::raw::{c_int, c_double};
|
|
||||||
use std::f64::EPSILON;
|
|
||||||
use super::Value;
|
use super::Value;
|
||||||
|
use crate::{Connection, Error, NO_PARAMS};
|
||||||
|
use std::f64::EPSILON;
|
||||||
|
use std::os::raw::{c_double, c_int};
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
@ -132,7 +127,8 @@ mod test {
|
|||||||
db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])
|
db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let v: Vec<u8> = db.query_row("SELECT b FROM foo", &[], |r| r.get(0))
|
let v: Vec<u8> = db
|
||||||
|
.query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(v, v1234);
|
assert_eq!(v, v1234);
|
||||||
}
|
}
|
||||||
@ -145,7 +141,8 @@ mod test {
|
|||||||
db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])
|
db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let v: Vec<u8> = db.query_row("SELECT b FROM foo", &[], |r| r.get(0))
|
let v: Vec<u8> = db
|
||||||
|
.query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(v, empty);
|
assert_eq!(v, empty);
|
||||||
}
|
}
|
||||||
@ -155,10 +152,10 @@ mod test {
|
|||||||
let db = checked_memory_handle();
|
let db = checked_memory_handle();
|
||||||
|
|
||||||
let s = "hello, world!";
|
let s = "hello, world!";
|
||||||
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s])
|
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let from: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let from: String = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(from, s);
|
assert_eq!(from, s);
|
||||||
}
|
}
|
||||||
@ -168,10 +165,11 @@ mod test {
|
|||||||
let db = checked_memory_handle();
|
let db = checked_memory_handle();
|
||||||
|
|
||||||
let s = "hello, world!";
|
let s = "hello, world!";
|
||||||
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_owned()])
|
db.execute("INSERT INTO foo(t) VALUES (?)", &[s.to_owned()])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let from: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let from: String = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(from, s);
|
assert_eq!(from, s);
|
||||||
}
|
}
|
||||||
@ -180,12 +178,14 @@ mod test {
|
|||||||
fn test_value() {
|
fn test_value() {
|
||||||
let db = checked_memory_handle();
|
let db = checked_memory_handle();
|
||||||
|
|
||||||
db.execute("INSERT INTO foo(i) VALUES (?)", &[&Value::Integer(10)])
|
db.execute("INSERT INTO foo(i) VALUES (?)", &[Value::Integer(10)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(10i64,
|
assert_eq!(
|
||||||
db.query_row::<i64, _>("SELECT i FROM foo", &[], |r| r.get(0))
|
10i64,
|
||||||
.unwrap());
|
db.query_row::<i64, _, _>("SELECT i FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -195,104 +195,144 @@ mod test {
|
|||||||
let s = Some("hello, world!");
|
let s = Some("hello, world!");
|
||||||
let b = Some(vec![1u8, 2, 3, 4]);
|
let b = Some(vec![1u8, 2, 3, 4]);
|
||||||
|
|
||||||
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s])
|
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
|
||||||
.unwrap();
|
db.execute("INSERT INTO foo(b) VALUES (?)", &[&b]).unwrap();
|
||||||
db.execute("INSERT INTO foo(b) VALUES (?)", &[&b])
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")
|
let mut stmt = db
|
||||||
|
.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut rows = stmt.query(&[]).unwrap();
|
let mut rows = stmt.query(NO_PARAMS).unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
let row1 = rows.next().unwrap().unwrap();
|
let row1 = rows.next().unwrap().unwrap();
|
||||||
let s1: Option<String> = row1.get(0);
|
let s1: Option<String> = row1.get_unwrap(0);
|
||||||
let b1: Option<Vec<u8>> = row1.get(1);
|
let b1: Option<Vec<u8>> = row1.get_unwrap(1);
|
||||||
assert_eq!(s.unwrap(), s1.unwrap());
|
assert_eq!(s.unwrap(), s1.unwrap());
|
||||||
assert!(b1.is_none());
|
assert!(b1.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let row2 = rows.next().unwrap().unwrap();
|
let row2 = rows.next().unwrap().unwrap();
|
||||||
let s2: Option<String> = row2.get(0);
|
let s2: Option<String> = row2.get_unwrap(0);
|
||||||
let b2: Option<Vec<u8>> = row2.get(1);
|
let b2: Option<Vec<u8>> = row2.get_unwrap(1);
|
||||||
assert!(s2.is_none());
|
assert!(s2.is_none());
|
||||||
assert_eq!(b, b2);
|
assert_eq!(b, b2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[allow(clippy::cognitive_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 {
|
||||||
Error::InvalidColumnType(_, _) => true,
|
Error::InvalidColumnType(_, _, _) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = checked_memory_handle();
|
let db = checked_memory_handle();
|
||||||
|
|
||||||
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
|
db.execute(
|
||||||
&[])
|
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
|
||||||
.unwrap();
|
NO_PARAMS,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
|
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
|
||||||
let mut rows = stmt.query(&[]).unwrap();
|
let mut rows = stmt.query(NO_PARAMS).unwrap();
|
||||||
|
|
||||||
let row = rows.next().unwrap().unwrap();
|
let row = rows.next().unwrap().unwrap();
|
||||||
|
|
||||||
// check the correct types come back as expected
|
// check the correct types come back as expected
|
||||||
assert_eq!(vec![1, 2], row.get_checked::<i32, Vec<u8>>(0).unwrap());
|
assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0).unwrap());
|
||||||
assert_eq!("text", row.get_checked::<i32, String>(1).unwrap());
|
assert_eq!("text", row.get::<_, String>(1).unwrap());
|
||||||
assert_eq!(1, row.get_checked::<i32, c_int>(2).unwrap());
|
assert_eq!(1, row.get::<_, c_int>(2).unwrap());
|
||||||
assert!((1.5 - row.get_checked::<i32, c_double>(3).unwrap()).abs() < EPSILON);
|
assert!((1.5 - row.get::<_, c_double>(3).unwrap()).abs() < EPSILON);
|
||||||
assert!(row.get_checked::<i32, Option<c_int>>(4)
|
assert!(row.get::<_, Option<c_int>>(4).unwrap().is_none());
|
||||||
.unwrap()
|
assert!(row.get::<_, Option<c_double>>(4).unwrap().is_none());
|
||||||
.is_none());
|
assert!(row.get::<_, Option<String>>(4).unwrap().is_none());
|
||||||
assert!(row.get_checked::<i32, Option<c_double>>(4)
|
|
||||||
.unwrap()
|
|
||||||
.is_none());
|
|
||||||
assert!(row.get_checked::<i32, Option<String>>(4)
|
|
||||||
.unwrap()
|
|
||||||
.is_none());
|
|
||||||
|
|
||||||
// check some invalid types
|
// check some invalid types
|
||||||
|
|
||||||
// 0 is actually a blob (Vec<u8>)
|
// 0 is actually a blob (Vec<u8>)
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(0).err().unwrap()));
|
assert!(is_invalid_column_type(
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(0).err().unwrap()));
|
row.get::<_, c_int>(0).err().unwrap()
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(0).err().unwrap()));
|
));
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, c_double>(0).err().unwrap()));
|
assert!(is_invalid_column_type(
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, String>(0).err().unwrap()));
|
row.get::<_, c_int>(0).err().unwrap()
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, time::Timespec>(0).err().unwrap()));
|
));
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, Option<c_int>>(0).err().unwrap()));
|
assert!(is_invalid_column_type(row.get::<_, i64>(0).err().unwrap()));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, c_double>(0).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, String>(0).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, time::Timespec>(0).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, Option<c_int>>(0).err().unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
// 1 is actually a text (String)
|
// 1 is actually a text (String)
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(1).err().unwrap()));
|
assert!(is_invalid_column_type(
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(1).err().unwrap()));
|
row.get::<_, c_int>(1).err().unwrap()
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, c_double>(1).err().unwrap()));
|
));
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(1).err().unwrap()));
|
assert!(is_invalid_column_type(row.get::<_, i64>(1).err().unwrap()));
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, Option<c_int>>(1).err().unwrap()));
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, c_double>(1).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, Vec<u8>>(1).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, Option<c_int>>(1).err().unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
// 2 is actually an integer
|
// 2 is actually an integer
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, String>(2).err().unwrap()));
|
assert!(is_invalid_column_type(
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(2).err().unwrap()));
|
row.get::<_, String>(2).err().unwrap()
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, Option<String>>(2).err().unwrap()));
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, Vec<u8>>(2).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, Option<String>>(2).err().unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
// 3 is actually a float (c_double)
|
// 3 is actually a float (c_double)
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(3).err().unwrap()));
|
assert!(is_invalid_column_type(
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(3).err().unwrap()));
|
row.get::<_, c_int>(3).err().unwrap()
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, String>(3).err().unwrap()));
|
));
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(3).err().unwrap()));
|
assert!(is_invalid_column_type(row.get::<_, i64>(3).err().unwrap()));
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, Option<c_int>>(3).err().unwrap()));
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, String>(3).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, Vec<u8>>(3).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, Option<c_int>>(3).err().unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
// 4 is actually NULL
|
// 4 is actually NULL
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(4).err().unwrap()));
|
assert!(is_invalid_column_type(
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(4).err().unwrap()));
|
row.get::<_, c_int>(4).err().unwrap()
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, c_double>(4).err().unwrap()));
|
));
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, String>(4).err().unwrap()));
|
assert!(is_invalid_column_type(row.get::<_, i64>(4).err().unwrap()));
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(4).err().unwrap()));
|
assert!(is_invalid_column_type(
|
||||||
assert!(is_invalid_column_type(row.get_checked::<i32, time::Timespec>(4).err().unwrap()));
|
row.get::<_, c_double>(4).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, String>(4).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, Vec<u8>>(4).err().unwrap()
|
||||||
|
));
|
||||||
|
assert!(is_invalid_column_type(
|
||||||
|
row.get::<_, time::Timespec>(4).err().unwrap()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -300,23 +340,26 @@ mod test {
|
|||||||
use super::Value;
|
use super::Value;
|
||||||
let db = checked_memory_handle();
|
let db = checked_memory_handle();
|
||||||
|
|
||||||
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
|
db.execute(
|
||||||
&[])
|
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
|
||||||
.unwrap();
|
NO_PARAMS,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
|
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
|
||||||
let mut rows = stmt.query(&[]).unwrap();
|
let mut rows = stmt.query(NO_PARAMS).unwrap();
|
||||||
|
|
||||||
let row = rows.next().unwrap().unwrap();
|
let row = rows.next().unwrap().unwrap();
|
||||||
assert_eq!(Value::Blob(vec![1, 2]),
|
assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0).unwrap());
|
||||||
row.get_checked::<i32, Value>(0).unwrap());
|
assert_eq!(
|
||||||
assert_eq!(Value::Text(String::from("text")),
|
Value::Text(String::from("text")),
|
||||||
row.get_checked::<i32, Value>(1).unwrap());
|
row.get::<_, Value>(1).unwrap()
|
||||||
assert_eq!(Value::Integer(1), row.get_checked::<i32, Value>(2).unwrap());
|
);
|
||||||
match row.get_checked::<i32, Value>(3).unwrap() {
|
assert_eq!(Value::Integer(1), row.get::<_, Value>(2).unwrap());
|
||||||
|
match row.get::<_, Value>(3).unwrap() {
|
||||||
Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
|
Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
|
||||||
x => panic!("Invalid Value {:?}", x),
|
x => panic!("Invalid Value {:?}", x),
|
||||||
}
|
}
|
||||||
assert_eq!(Value::Null, row.get_checked::<i32, Value>(4).unwrap());
|
assert_eq!(Value::Null, row.get::<_, Value>(4).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
//! `ToSql` and `FromSql` implementation for JSON `Value`.
|
//! `ToSql` and `FromSql` implementation for JSON `Value`.
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
use self::serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use Result;
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
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_slice(s),
|
||||||
ValueRef::Blob(b) => serde_json::from_slice(b),
|
ValueRef::Blob(b) => serde_json::from_slice(b),
|
||||||
_ => return Err(FromSqlError::InvalidType),
|
_ => return Err(FromSqlError::InvalidType),
|
||||||
}
|
}
|
||||||
.map_err(|err| FromSqlError::Other(Box::new(err)))
|
.map_err(|err| FromSqlError::Other(Box::new(err)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use Connection;
|
use crate::types::ToSql;
|
||||||
use super::serde_json;
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
@ -43,14 +43,18 @@ mod test {
|
|||||||
|
|
||||||
let json = r#"{"foo": 13, "bar": "baz"}"#;
|
let json = r#"{"foo": 13, "bar": "baz"}"#;
|
||||||
let data: serde_json::Value = serde_json::from_str(json).unwrap();
|
let data: serde_json::Value = serde_json::from_str(json).unwrap();
|
||||||
db.execute("INSERT INTO foo (t, b) VALUES (?, ?)",
|
db.execute(
|
||||||
&[&data, &json.as_bytes()])
|
"INSERT INTO foo (t, b) VALUES (?, ?)",
|
||||||
.unwrap();
|
&[&data as &dyn ToSql, &json.as_bytes()],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let t: serde_json::Value = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
let t: serde_json::Value = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(data, t);
|
assert_eq!(data, t);
|
||||||
let b: serde_json::Value = db.query_row("SELECT b FROM foo", &[], |r| r.get(0))
|
let b: serde_json::Value = db
|
||||||
|
.query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(data, b);
|
assert_eq!(data, b);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
extern crate time;
|
use time;
|
||||||
|
|
||||||
use Result;
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
use crate::Result;
|
||||||
|
|
||||||
const SQLITE_DATETIME_FMT: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
|
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_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()
|
||||||
@ -16,20 +18,26 @@ 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| match time::strptime(s, SQLITE_DATETIME_FMT) {
|
.and_then(|s| {
|
||||||
Ok(tm) => Ok(tm.to_timespec()),
|
match s.len() {
|
||||||
Err(err) => Err(FromSqlError::Other(Box::new(err))),
|
19 => time::strptime(s, CURRENT_TIMESTAMP_FMT),
|
||||||
})
|
_ => 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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use Connection;
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
use super::time;
|
use time;
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
let db = Connection::open_in_memory().unwrap();
|
||||||
@ -44,25 +52,31 @@ mod test {
|
|||||||
|
|
||||||
let mut ts_vec = vec![];
|
let mut ts_vec = vec![];
|
||||||
|
|
||||||
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])
|
let from: time::Timespec = db
|
||||||
|
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let from: time::Timespec = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
|
db.execute("DELETE FROM foo", NO_PARAMS).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
db.execute("DELETE FROM foo", &[]).unwrap();
|
|
||||||
|
|
||||||
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,12 @@
|
|||||||
use super::{Null, Value, ValueRef};
|
use super::{Null, Value, ValueRef};
|
||||||
use Result;
|
#[cfg(feature = "array")]
|
||||||
|
use crate::vtab::array::Array;
|
||||||
|
use crate::Result;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// `ToSqlOutput` represents the possible output types for implementors of the `ToSql` trait.
|
/// `ToSqlOutput` represents the possible output types for implementors of the
|
||||||
#[derive(Clone,Debug,PartialEq)]
|
/// `ToSql` trait.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum ToSqlOutput<'a> {
|
pub enum ToSqlOutput<'a> {
|
||||||
/// A borrowed SQLite-representable value.
|
/// A borrowed SQLite-representable value.
|
||||||
Borrowed(ValueRef<'a>),
|
Borrowed(ValueRef<'a>),
|
||||||
@ -13,37 +17,81 @@ pub enum ToSqlOutput<'a> {
|
|||||||
/// A BLOB of the given length that is filled with zeroes.
|
/// A BLOB of the given length that is filled with zeroes.
|
||||||
#[cfg(feature = "blob")]
|
#[cfg(feature = "blob")]
|
||||||
ZeroBlob(i32),
|
ZeroBlob(i32),
|
||||||
|
|
||||||
|
#[cfg(feature = "array")]
|
||||||
|
Array(Array),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generically allow any type that can be converted into a ValueRef
|
||||||
|
// to be converted into a ToSqlOutput as well.
|
||||||
impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a>
|
impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a>
|
||||||
where &'a T: Into<ValueRef<'a>>
|
where
|
||||||
|
&'a T: Into<ValueRef<'a>>,
|
||||||
{
|
{
|
||||||
fn from(t: &'a T) -> Self {
|
fn from(t: &'a T) -> Self {
|
||||||
ToSqlOutput::Borrowed(t.into())
|
ToSqlOutput::Borrowed(t.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: Into<Value>> From<T> for ToSqlOutput<'a> {
|
// We cannot also generically allow any type that can be converted
|
||||||
fn from(t: T) -> Self {
|
// into a Value to be converted into a ToSqlOutput because of
|
||||||
ToSqlOutput::Owned(t.into())
|
// coherence rules (https://github.com/rust-lang/rust/pull/46192),
|
||||||
}
|
// so we'll manually implement it for all the types we know can
|
||||||
}
|
// be converted into Values.
|
||||||
|
macro_rules! from_value(
|
||||||
|
($t:ty) => (
|
||||||
|
impl From<$t> for ToSqlOutput<'_> {
|
||||||
|
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
from_value!(String);
|
||||||
|
from_value!(Null);
|
||||||
|
from_value!(bool);
|
||||||
|
from_value!(i8);
|
||||||
|
from_value!(i16);
|
||||||
|
from_value!(i32);
|
||||||
|
from_value!(i64);
|
||||||
|
from_value!(isize);
|
||||||
|
from_value!(u8);
|
||||||
|
from_value!(u16);
|
||||||
|
from_value!(u32);
|
||||||
|
from_value!(f64);
|
||||||
|
from_value!(Vec<u8>);
|
||||||
|
|
||||||
impl<'a> ToSql for ToSqlOutput<'a> {
|
// It would be nice if we could avoid the heap allocation (of the `Vec`) that
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
// `i128` needs in `Into<Value>`, but it's probably fine for the moment, and not
|
||||||
|
// worth adding another case to Value.
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
from_value!(i128);
|
||||||
|
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
from_value!(uuid::Uuid);
|
||||||
|
|
||||||
|
impl ToSql for 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)),
|
||||||
|
|
||||||
#[cfg(feature = "blob")]
|
#[cfg(feature = "blob")]
|
||||||
ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i),
|
ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i),
|
||||||
})
|
#[cfg(feature = "array")]
|
||||||
|
ToSqlOutput::Array(ref a) => ToSqlOutput::Array(a.clone()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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<'_>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSql for Box<dyn ToSql> {
|
||||||
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
|
let derefed: &dyn ToSql = &**self;
|
||||||
|
derefed.to_sql()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should be able to use a generic impl like this:
|
// We should be able to use a generic impl like this:
|
||||||
@ -61,7 +109,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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,46 +128,53 @@ to_sql_self!(u16);
|
|||||||
to_sql_self!(u32);
|
to_sql_self!(u32);
|
||||||
to_sql_self!(f64);
|
to_sql_self!(f64);
|
||||||
|
|
||||||
impl<'a, T: ?Sized> ToSql for &'a T
|
#[cfg(feature = "i128_blob")]
|
||||||
where &'a T: Into<ToSqlOutput<'a>>
|
to_sql_self!(i128);
|
||||||
|
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
to_sql_self!(uuid::Uuid);
|
||||||
|
|
||||||
|
impl<T: ?Sized> ToSql for &'_ T
|
||||||
|
where
|
||||||
|
T: ToSql,
|
||||||
{
|
{
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
Ok((*self).into())
|
(*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(),
|
||||||
@ -127,6 +182,12 @@ impl<T: ToSql> ToSql for Option<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToSql for Cow<'_, str> {
|
||||||
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
|
Ok(ToSqlOutput::from(self.as_ref()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::ToSql;
|
use super::ToSql;
|
||||||
@ -143,4 +204,93 @@ mod test {
|
|||||||
is_to_sql::<u16>();
|
is_to_sql::<u16>();
|
||||||
is_to_sql::<u32>();
|
is_to_sql::<u32>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cow_str() {
|
||||||
|
use std::borrow::Cow;
|
||||||
|
let s = "str";
|
||||||
|
let cow = Cow::Borrowed(s);
|
||||||
|
let r = cow.to_sql();
|
||||||
|
assert!(r.is_ok());
|
||||||
|
let cow = Cow::Owned::<str>(String::from(s));
|
||||||
|
let r = cow.to_sql();
|
||||||
|
assert!(r.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
#[test]
|
||||||
|
fn test_i128() {
|
||||||
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
use std::i128;
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")
|
||||||
|
.unwrap();
|
||||||
|
db.execute(
|
||||||
|
"
|
||||||
|
INSERT INTO foo(i128, desc) VALUES
|
||||||
|
(?, 'zero'),
|
||||||
|
(?, 'neg one'), (?, 'neg two'),
|
||||||
|
(?, 'pos one'), (?, 'pos two'),
|
||||||
|
(?, 'min'), (?, 'max')",
|
||||||
|
&[0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut stmt = db
|
||||||
|
.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res = stmt
|
||||||
|
.query_map(NO_PARAMS, |row| {
|
||||||
|
Ok((row.get::<_, i128>(0)?, row.get::<_, String>(1)?))
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
&[
|
||||||
|
(i128::MIN, "min".to_owned()),
|
||||||
|
(-2, "neg two".to_owned()),
|
||||||
|
(-1, "neg one".to_owned()),
|
||||||
|
(0, "zero".to_owned()),
|
||||||
|
(1, "pos one".to_owned()),
|
||||||
|
(2, "pos two".to_owned()),
|
||||||
|
(i128::MAX, "max".to_owned()),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
#[test]
|
||||||
|
fn test_uuid() {
|
||||||
|
use crate::{params, Connection};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
|
||||||
|
db.execute(
|
||||||
|
"INSERT INTO foo (id, label) VALUES (?, ?)",
|
||||||
|
params![id, "target"],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut stmt = db
|
||||||
|
.prepare("SELECT id, label FROM foo WHERE id = ?")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut rows = stmt.query(params![id]).unwrap();
|
||||||
|
let row = rows.next().unwrap().unwrap();
|
||||||
|
|
||||||
|
let found_id: Uuid = row.get_unwrap(0);
|
||||||
|
let found_label: String = row.get_unwrap(1);
|
||||||
|
|
||||||
|
assert_eq!(found_id, id);
|
||||||
|
assert_eq!(found_label, "target");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
81
src/types/url.rs
Normal file
81
src/types/url.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
//! `ToSql` and `FromSql` implementation for [`url::Url`].
|
||||||
|
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
|
use crate::Result;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// Serialize `Url` to text.
|
||||||
|
impl ToSql for Url {
|
||||||
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
|
Ok(ToSqlOutput::from(self.as_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize text to `Url`.
|
||||||
|
impl FromSql for Url {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
match value {
|
||||||
|
ValueRef::Text(s) => {
|
||||||
|
let s = std::str::from_utf8(s).map_err(|e| FromSqlError::Other(Box::new(e)))?;
|
||||||
|
Url::parse(s).map_err(|e| FromSqlError::Other(Box::new(e)))
|
||||||
|
}
|
||||||
|
_ => Err(FromSqlError::InvalidType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{params, Connection, Error, Result};
|
||||||
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
|
fn checked_memory_handle() -> Connection {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
db.execute_batch("CREATE TABLE urls (i INTEGER, v TEXT)")
|
||||||
|
.unwrap();
|
||||||
|
db
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_url(db: &Connection, id: i64) -> Result<Url> {
|
||||||
|
db.query_row("SELECT v FROM urls WHERE i = ?", params![id], |r| r.get(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sql_url() {
|
||||||
|
let db = &checked_memory_handle();
|
||||||
|
|
||||||
|
let url0 = Url::parse("http://www.example1.com").unwrap();
|
||||||
|
let url1 = Url::parse("http://www.example1.com/👌").unwrap();
|
||||||
|
let url2 = "http://www.example2.com/👌";
|
||||||
|
|
||||||
|
db.execute(
|
||||||
|
"INSERT INTO urls (i, v) VALUES (0, ?), (1, ?), (2, ?), (3, ?)",
|
||||||
|
// also insert a non-hex encoded url (which might be present if it was
|
||||||
|
// inserted separately)
|
||||||
|
params![url0, url1, url2, "illegal"],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(get_url(db, 0).unwrap(), url0);
|
||||||
|
|
||||||
|
assert_eq!(get_url(db, 1).unwrap(), url1);
|
||||||
|
|
||||||
|
// Should successfully read it, even though it wasn't inserted as an
|
||||||
|
// escaped url.
|
||||||
|
let out_url2: Url = get_url(db, 2).unwrap();
|
||||||
|
assert_eq!(out_url2, Url::parse(url2).unwrap());
|
||||||
|
|
||||||
|
// Make sure the conversion error comes through correctly.
|
||||||
|
let err = get_url(db, 3).unwrap_err();
|
||||||
|
match err {
|
||||||
|
Error::FromSqlConversionFailure(_, _, e) => {
|
||||||
|
assert_eq!(
|
||||||
|
*e.downcast::<ParseError>().unwrap(),
|
||||||
|
ParseError::RelativeUrlWithoutBase,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
panic!("Expected conversion failure, got {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ use super::{Null, Type};
|
|||||||
/// dictated by SQLite (not by the caller).
|
/// dictated by SQLite (not by the caller).
|
||||||
///
|
///
|
||||||
/// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value.
|
/// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value.
|
||||||
#[derive(Clone,Debug,PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
/// The value is a `NULL` value.
|
/// The value is a `NULL` value.
|
||||||
Null,
|
Null,
|
||||||
@ -31,7 +31,28 @@ impl From<bool> for Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl From<isize> for Value {
|
impl From<isize> for Value {
|
||||||
fn from(i: isize) -> Value { Value::Integer(i as i64) }
|
fn from(i: isize) -> Value {
|
||||||
|
Value::Integer(i as i64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
impl From<i128> for Value {
|
||||||
|
fn from(i: i128) -> Value {
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
let mut buf = vec![0u8; 16];
|
||||||
|
// We store these biased (e.g. with the most significant bit flipped)
|
||||||
|
// so that comparisons with negative numbers work properly.
|
||||||
|
BigEndian::write_i128(&mut buf, i ^ (1i128 << 127));
|
||||||
|
Value::Blob(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
impl From<uuid::Uuid> for Value {
|
||||||
|
fn from(id: uuid::Uuid) -> Value {
|
||||||
|
Value::Blob(id.as_bytes().to_vec())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! from_i64(
|
macro_rules! from_i64(
|
||||||
@ -75,6 +96,18 @@ impl From<Vec<u8>> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> From<Option<T>> for Value
|
||||||
|
where
|
||||||
|
T: Into<Value>,
|
||||||
|
{
|
||||||
|
fn from(v: Option<T>) -> Value {
|
||||||
|
match v {
|
||||||
|
Some(x) => x.into(),
|
||||||
|
None => Value::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
pub fn data_type(&self) -> Type {
|
pub fn data_type(&self) -> Type {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use types::{FromSqlError, FromSqlResult};
|
use super::{Type, Value};
|
||||||
use super::{Value, Type};
|
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.
|
||||||
///
|
///
|
||||||
/// See [`Value`](enum.Value.html) for an owning dynamic type value.
|
/// See [`Value`](enum.Value.html) for an owning dynamic type value.
|
||||||
#[derive(Copy,Clone,Debug,PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum ValueRef<'a> {
|
pub enum ValueRef<'a> {
|
||||||
/// The value is a `NULL` value.
|
/// The value is a `NULL` value.
|
||||||
Null,
|
Null,
|
||||||
@ -14,12 +14,12 @@ pub enum ValueRef<'a> {
|
|||||||
/// The value is a floating point number.
|
/// The value is a floating point number.
|
||||||
Real(f64),
|
Real(f64),
|
||||||
/// The value is a text string.
|
/// The value is a text string.
|
||||||
Text(&'a str),
|
Text(&'a [u8]),
|
||||||
/// The value is a blob of data
|
/// The value is a blob of data
|
||||||
Blob(&'a [u8]),
|
Blob(&'a [u8]),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ValueRef<'a> {
|
impl ValueRef<'_> {
|
||||||
pub fn data_type(&self) -> Type {
|
pub fn data_type(&self) -> Type {
|
||||||
match *self {
|
match *self {
|
||||||
ValueRef::Null => Type::Null,
|
ValueRef::Null => Type::Null,
|
||||||
@ -32,8 +32,8 @@ impl<'a> ValueRef<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ValueRef<'a> {
|
impl<'a> ValueRef<'a> {
|
||||||
/// If `self` is case `Integer`, returns the integral value. Otherwise, returns
|
/// If `self` is case `Integer`, returns the integral value. Otherwise,
|
||||||
/// `Err(Error::InvalidColumnType)`.
|
/// returns `Err(Error::InvalidColumnType)`.
|
||||||
pub fn as_i64(&self) -> FromSqlResult<i64> {
|
pub fn as_i64(&self) -> FromSqlResult<i64> {
|
||||||
match *self {
|
match *self {
|
||||||
ValueRef::Integer(i) => Ok(i),
|
ValueRef::Integer(i) => Ok(i),
|
||||||
@ -41,8 +41,8 @@ impl<'a> ValueRef<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `self` is case `Real`, returns the floating point value. Otherwise, returns
|
/// If `self` is case `Real`, returns the floating point value. Otherwise,
|
||||||
/// `Err(Error::InvalidColumnType)`.
|
/// returns `Err(Error::InvalidColumnType)`.
|
||||||
pub fn as_f64(&self) -> FromSqlResult<f64> {
|
pub fn as_f64(&self) -> FromSqlResult<f64> {
|
||||||
match *self {
|
match *self {
|
||||||
ValueRef::Real(f) => Ok(f),
|
ValueRef::Real(f) => Ok(f),
|
||||||
@ -52,16 +52,18 @@ impl<'a> ValueRef<'a> {
|
|||||||
|
|
||||||
/// If `self` is case `Text`, returns the string value. Otherwise, returns
|
/// If `self` is case `Text`, returns the string value. Otherwise, returns
|
||||||
/// `Err(Error::InvalidColumnType)`.
|
/// `Err(Error::InvalidColumnType)`.
|
||||||
pub fn as_str(&self) -> FromSqlResult<&str> {
|
pub fn as_str(&self) -> FromSqlResult<&'a str> {
|
||||||
match *self {
|
match *self {
|
||||||
ValueRef::Text(t) => Ok(t),
|
ValueRef::Text(t) => {
|
||||||
|
std::str::from_utf8(t).map_err(|e| FromSqlError::Other(Box::new(e)))
|
||||||
|
}
|
||||||
_ => Err(FromSqlError::InvalidType),
|
_ => Err(FromSqlError::InvalidType),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `self` is case `Blob`, returns the byte slice. Otherwise, returns
|
/// If `self` is case `Blob`, returns the byte slice. Otherwise, returns
|
||||||
/// `Err(Error::InvalidColumnType)`.
|
/// `Err(Error::InvalidColumnType)`.
|
||||||
pub fn as_blob(&self) -> FromSqlResult<&[u8]> {
|
pub fn as_blob(&self) -> FromSqlResult<&'a [u8]> {
|
||||||
match *self {
|
match *self {
|
||||||
ValueRef::Blob(b) => Ok(b),
|
ValueRef::Blob(b) => Ok(b),
|
||||||
_ => Err(FromSqlError::InvalidType),
|
_ => Err(FromSqlError::InvalidType),
|
||||||
@ -69,26 +71,29 @@ impl<'a> ValueRef<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<ValueRef<'a>> for Value {
|
impl From<ValueRef<'_>> 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),
|
||||||
ValueRef::Real(r) => Value::Real(r),
|
ValueRef::Real(r) => Value::Real(r),
|
||||||
ValueRef::Text(s) => Value::Text(s.to_string()),
|
ValueRef::Text(s) => {
|
||||||
|
let s = std::str::from_utf8(s).expect("invalid UTF-8");
|
||||||
|
Value::Text(s.to_string())
|
||||||
|
}
|
||||||
ValueRef::Blob(b) => Value::Blob(b.to_vec()),
|
ValueRef::Blob(b) => Value::Blob(b.to_vec()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a str> for ValueRef<'a> {
|
impl<'a> From<&'a str> for ValueRef<'a> {
|
||||||
fn from(s: &str) -> ValueRef {
|
fn from(s: &str) -> ValueRef<'_> {
|
||||||
ValueRef::Text(s)
|
ValueRef::Text(s.as_bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,8 +104,70 @@ impl<'a> From<&'a Value> for ValueRef<'a> {
|
|||||||
Value::Null => ValueRef::Null,
|
Value::Null => ValueRef::Null,
|
||||||
Value::Integer(i) => ValueRef::Integer(i),
|
Value::Integer(i) => ValueRef::Integer(i),
|
||||||
Value::Real(r) => ValueRef::Real(r),
|
Value::Real(r) => ValueRef::Real(r),
|
||||||
Value::Text(ref s) => ValueRef::Text(s),
|
Value::Text(ref s) => ValueRef::Text(s.as_bytes()),
|
||||||
Value::Blob(ref b) => ValueRef::Blob(b),
|
Value::Blob(ref b) => ValueRef::Blob(b),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T> From<Option<T>> for ValueRef<'a>
|
||||||
|
where
|
||||||
|
T: Into<ValueRef<'a>>,
|
||||||
|
{
|
||||||
|
fn from(s: Option<T>) -> ValueRef<'a> {
|
||||||
|
match s {
|
||||||
|
Some(x) => x.into(),
|
||||||
|
None => ValueRef::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "functions", feature = "session", feature = "vtab"))]
|
||||||
|
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);
|
||||||
|
|
||||||
|
let s = s.to_bytes();
|
||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
132
src/unlock_notify.rs
Normal file
132
src/unlock_notify.rs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
//! [Unlock Notification](http://sqlite.org/unlock_notify.html)
|
||||||
|
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
use std::os::raw::c_void;
|
||||||
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
use std::panic::catch_unwind;
|
||||||
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
use std::sync::{Condvar, Mutex};
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
|
||||||
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
struct UnlockNotification {
|
||||||
|
cond: Condvar, // Condition variable to wait on
|
||||||
|
mutex: Mutex<bool>, // Mutex to protect structure
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
impl UnlockNotification {
|
||||||
|
fn new() -> UnlockNotification {
|
||||||
|
UnlockNotification {
|
||||||
|
cond: Condvar::new(),
|
||||||
|
mutex: Mutex::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fired(&mut self) {
|
||||||
|
*self.mutex.lock().unwrap() = true;
|
||||||
|
self.cond.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait(&mut self) {
|
||||||
|
let mut fired = self.mutex.lock().unwrap();
|
||||||
|
while !*fired {
|
||||||
|
fired = self.cond.wait(fired).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is an unlock-notify callback
|
||||||
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) {
|
||||||
|
use std::slice::from_raw_parts;
|
||||||
|
let args = from_raw_parts(ap_arg, n_arg as usize);
|
||||||
|
for arg in args {
|
||||||
|
let _ = catch_unwind(|| {
|
||||||
|
let un: &mut UnlockNotification = &mut *(*arg as *mut UnlockNotification);
|
||||||
|
un.fired()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
pub fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool {
|
||||||
|
rc == ffi::SQLITE_LOCKED_SHAREDCACHE
|
||||||
|
|| (rc & 0xFF) == ffi::SQLITE_LOCKED
|
||||||
|
&& unsafe { ffi::sqlite3_extended_errcode(db) } == ffi::SQLITE_LOCKED_SHAREDCACHE
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()`
|
||||||
|
/// or `sqlite3_step()`) has just returned `SQLITE_LOCKED`. The argument is the
|
||||||
|
/// associated database connection.
|
||||||
|
///
|
||||||
|
/// This function calls `sqlite3_unlock_notify()` to register for an
|
||||||
|
/// unlock-notify callback, then blocks until that callback is delivered
|
||||||
|
/// and returns `SQLITE_OK`. The caller should then retry the failed operation.
|
||||||
|
///
|
||||||
|
/// Or, if `sqlite3_unlock_notify()` indicates that to block would deadlock
|
||||||
|
/// the system, then this function returns `SQLITE_LOCKED` immediately. In
|
||||||
|
/// this case the caller should not retry the operation and should roll
|
||||||
|
/// back the current transaction (if any).
|
||||||
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int {
|
||||||
|
let mut un = UnlockNotification::new();
|
||||||
|
/* Register for an unlock-notify callback. */
|
||||||
|
let rc = unsafe {
|
||||||
|
ffi::sqlite3_unlock_notify(
|
||||||
|
db,
|
||||||
|
Some(unlock_notify_cb),
|
||||||
|
&mut un as *mut UnlockNotification as *mut c_void,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
debug_assert!(
|
||||||
|
rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_LOCKED_SHAREDCACHE || rc == ffi::SQLITE_OK
|
||||||
|
);
|
||||||
|
if rc == ffi::SQLITE_OK {
|
||||||
|
un.wait();
|
||||||
|
}
|
||||||
|
rc
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unlock_notify"))]
|
||||||
|
pub fn is_locked(_db: *mut ffi::sqlite3, _rc: c_int) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unlock_notify"))]
|
||||||
|
pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "unlock_notify")]
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior, NO_PARAMS};
|
||||||
|
use std::sync::mpsc::sync_channel;
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unlock_notify() {
|
||||||
|
let url = "file::memory:?cache=shared";
|
||||||
|
let flags = OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_URI;
|
||||||
|
let db1 = Connection::open_with_flags(url, flags).unwrap();
|
||||||
|
db1.execute_batch("CREATE TABLE foo (x)").unwrap();
|
||||||
|
let (rx, tx) = sync_channel(0);
|
||||||
|
let child = thread::spawn(move || {
|
||||||
|
let mut db2 = Connection::open_with_flags(url, flags).unwrap();
|
||||||
|
let tx2 = Transaction::new(&mut db2, TransactionBehavior::Immediate).unwrap();
|
||||||
|
tx2.execute_batch("INSERT INTO foo VALUES (42)").unwrap();
|
||||||
|
rx.send(1).unwrap();
|
||||||
|
let ten_millis = time::Duration::from_millis(10);
|
||||||
|
thread::sleep(ten_millis);
|
||||||
|
tx2.commit().unwrap();
|
||||||
|
});
|
||||||
|
assert_eq!(tx.recv().unwrap(), 1);
|
||||||
|
let the_answer: Result<i64> = db1.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0));
|
||||||
|
assert_eq!(42i64, the_answer.unwrap());
|
||||||
|
child.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
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 3.16.2.
|
/// Returns the SQLite version as an integer; e.g., `3016002` for version
|
||||||
|
/// 3.16.2.
|
||||||
///
|
///
|
||||||
/// See [`sqlite3_libversion_number()`](https://www.sqlite.org/c3ref/libversion.html).
|
/// See [`sqlite3_libversion_number()`](https://www.sqlite.org/c3ref/libversion.html).
|
||||||
pub fn version_number() -> i32 {
|
pub fn version_number() -> i32 {
|
||||||
|
199
src/vtab/array.rs
Normal file
199
src/vtab/array.rs
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
//! Array Virtual Table.
|
||||||
|
//!
|
||||||
|
//! Port of [carray](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/carray.c) C extension.
|
||||||
|
use std::default::Default;
|
||||||
|
use std::os::raw::{c_char, c_int, c_void};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::types::{ToSql, ToSqlOutput, Value};
|
||||||
|
use crate::vtab::{
|
||||||
|
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
|
||||||
|
VTabCursor, Values,
|
||||||
|
};
|
||||||
|
use crate::{Connection, Result};
|
||||||
|
|
||||||
|
// http://sqlite.org/bindptr.html
|
||||||
|
|
||||||
|
pub(crate) const ARRAY_TYPE: *const c_char = b"rarray\0" as *const u8 as *const c_char;
|
||||||
|
|
||||||
|
pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
|
||||||
|
let _: Array = Rc::from_raw(p as *const Vec<Value>);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Array = Rc<Vec<Value>>;
|
||||||
|
|
||||||
|
impl ToSql for Array {
|
||||||
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
|
Ok(ToSqlOutput::Array(self.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register the "rarray" module.
|
||||||
|
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||||
|
let aux: Option<()> = None;
|
||||||
|
conn.create_module("rarray", &ARRAY_MODULE, aux)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column numbers
|
||||||
|
// const CARRAY_COLUMN_VALUE : c_int = 0;
|
||||||
|
const CARRAY_COLUMN_POINTER: c_int = 1;
|
||||||
|
|
||||||
|
/// An instance of the Array virtual table
|
||||||
|
#[repr(C)]
|
||||||
|
struct ArrayTab {
|
||||||
|
/// Base class. Must be first
|
||||||
|
base: ffi::sqlite3_vtab,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VTab for ArrayTab {
|
||||||
|
type Aux = ();
|
||||||
|
type Cursor = ArrayTabCursor;
|
||||||
|
|
||||||
|
fn connect(
|
||||||
|
_: &mut VTabConnection,
|
||||||
|
_aux: Option<&()>,
|
||||||
|
_args: &[&[u8]],
|
||||||
|
) -> Result<(String, ArrayTab)> {
|
||||||
|
let vtab = ArrayTab {
|
||||||
|
base: ffi::sqlite3_vtab::default(),
|
||||||
|
};
|
||||||
|
Ok(("CREATE TABLE x(value,pointer hidden)".to_owned(), vtab))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||||
|
// Index of the pointer= constraint
|
||||||
|
let mut ptr_idx = None;
|
||||||
|
for (i, constraint) in info.constraints().enumerate() {
|
||||||
|
if !constraint.is_usable() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let CARRAY_COLUMN_POINTER = constraint.column() {
|
||||||
|
ptr_idx = Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ptr_idx) = ptr_idx {
|
||||||
|
{
|
||||||
|
let mut constraint_usage = info.constraint_usage(ptr_idx);
|
||||||
|
constraint_usage.set_argv_index(1);
|
||||||
|
constraint_usage.set_omit(true);
|
||||||
|
}
|
||||||
|
info.set_estimated_cost(1f64);
|
||||||
|
info.set_estimated_rows(100);
|
||||||
|
info.set_idx_num(1);
|
||||||
|
} else {
|
||||||
|
info.set_estimated_cost(2_147_483_647f64);
|
||||||
|
info.set_estimated_rows(2_147_483_647);
|
||||||
|
info.set_idx_num(0);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open(&self) -> Result<ArrayTabCursor> {
|
||||||
|
Ok(ArrayTabCursor::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A cursor for the Array virtual table
|
||||||
|
#[repr(C)]
|
||||||
|
struct ArrayTabCursor {
|
||||||
|
/// Base class. Must be first
|
||||||
|
base: ffi::sqlite3_vtab_cursor,
|
||||||
|
/// The rowid
|
||||||
|
row_id: i64,
|
||||||
|
/// Pointer to the array of values ("pointer")
|
||||||
|
ptr: Option<Array>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrayTabCursor {
|
||||||
|
fn new() -> ArrayTabCursor {
|
||||||
|
ArrayTabCursor {
|
||||||
|
base: ffi::sqlite3_vtab_cursor::default(),
|
||||||
|
row_id: 0,
|
||||||
|
ptr: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> i64 {
|
||||||
|
match self.ptr {
|
||||||
|
Some(ref a) => a.len() as i64,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl VTabCursor for ArrayTabCursor {
|
||||||
|
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> {
|
||||||
|
if idx_num > 0 {
|
||||||
|
self.ptr = args.get_array(0)?;
|
||||||
|
} else {
|
||||||
|
self.ptr = None;
|
||||||
|
}
|
||||||
|
self.row_id = 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Result<()> {
|
||||||
|
self.row_id += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof(&self) -> bool {
|
||||||
|
self.row_id > self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
|
||||||
|
match i {
|
||||||
|
CARRAY_COLUMN_POINTER => Ok(()),
|
||||||
|
_ => {
|
||||||
|
if let Some(ref array) = self.ptr {
|
||||||
|
let value = &array[(self.row_id - 1) as usize];
|
||||||
|
ctx.set_result(&value)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rowid(&self) -> Result<i64> {
|
||||||
|
Ok(self.row_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::types::Value;
|
||||||
|
use crate::vtab::array;
|
||||||
|
use crate::Connection;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_module() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
array::load_module(&db).unwrap();
|
||||||
|
|
||||||
|
let v = vec![1i64, 2, 3, 4];
|
||||||
|
let values = v.into_iter().map(Value::from).collect();
|
||||||
|
let ptr = Rc::new(values);
|
||||||
|
{
|
||||||
|
let mut stmt = db.prepare("SELECT value from rarray(?);").unwrap();
|
||||||
|
|
||||||
|
let rows = stmt.query_map(&[&ptr], |row| row.get::<_, i64>(0)).unwrap();
|
||||||
|
assert_eq!(2, Rc::strong_count(&ptr));
|
||||||
|
let mut count = 0;
|
||||||
|
for (i, value) in rows.enumerate() {
|
||||||
|
assert_eq!(i as i64, value.unwrap() - 1);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
assert_eq!(4, count);
|
||||||
|
}
|
||||||
|
assert_eq!(1, Rc::strong_count(&ptr));
|
||||||
|
}
|
||||||
|
}
|
398
src/vtab/csvtab.rs
Normal file
398
src/vtab/csvtab.rs
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
//! CSV Virtual Table.
|
||||||
|
//!
|
||||||
|
//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C extension.
|
||||||
|
use csv;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::result;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::types::Null;
|
||||||
|
use crate::vtab::{
|
||||||
|
dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
|
||||||
|
Module, VTab, VTabConnection, VTabCursor, Values,
|
||||||
|
};
|
||||||
|
use crate::{Connection, Error, Result};
|
||||||
|
|
||||||
|
/// Register the "csv" module.
|
||||||
|
/// ```sql
|
||||||
|
/// CREATE VIRTUAL TABLE vtab USING csv(
|
||||||
|
/// filename=FILENAME -- Name of file containing CSV content
|
||||||
|
/// [, schema=SCHEMA] -- Alternative CSV schema. 'CREATE TABLE x(col1 TEXT NOT NULL, col2 INT, ...);'
|
||||||
|
/// [, header=YES|NO] -- First row of CSV defines the names of columns if "yes". Default "no".
|
||||||
|
/// [, columns=N] -- Assume the CSV file contains N columns.
|
||||||
|
/// [, delimiter=C] -- CSV delimiter. Default ','.
|
||||||
|
/// [, quote=C] -- CSV quote. Default '"'. 0 means no quote.
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||||
|
let aux: Option<()> = None;
|
||||||
|
conn.create_module("csv", &CSV_MODULE, aux)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An instance of the CSV virtual table
|
||||||
|
#[repr(C)]
|
||||||
|
struct CSVTab {
|
||||||
|
/// Base class. Must be first
|
||||||
|
base: ffi::sqlite3_vtab,
|
||||||
|
/// Name of the CSV file
|
||||||
|
filename: String,
|
||||||
|
has_headers: bool,
|
||||||
|
delimiter: u8,
|
||||||
|
quote: u8,
|
||||||
|
/// Offset to start of data
|
||||||
|
offset_first_row: csv::Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CSVTab {
|
||||||
|
fn reader(&self) -> result::Result<csv::Reader<File>, csv::Error> {
|
||||||
|
csv::ReaderBuilder::new()
|
||||||
|
.has_headers(self.has_headers)
|
||||||
|
.delimiter(self.delimiter)
|
||||||
|
.quote(self.quote)
|
||||||
|
.from_path(&self.filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
|
||||||
|
let arg = str::from_utf8(c_slice)?.trim();
|
||||||
|
let mut split = arg.split('=');
|
||||||
|
if let Some(key) = split.next() {
|
||||||
|
if let Some(value) = split.next() {
|
||||||
|
let param = key.trim();
|
||||||
|
let value = dequote(value);
|
||||||
|
return Ok((param, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::ModuleError(format!("illegal argument: '{}'", arg)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_byte(arg: &str) -> Option<u8> {
|
||||||
|
if arg.len() == 1 {
|
||||||
|
arg.bytes().next()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VTab for CSVTab {
|
||||||
|
type Aux = ();
|
||||||
|
type Cursor = CSVTabCursor;
|
||||||
|
|
||||||
|
fn connect(
|
||||||
|
_: &mut VTabConnection,
|
||||||
|
_aux: Option<&()>,
|
||||||
|
args: &[&[u8]],
|
||||||
|
) -> Result<(String, CSVTab)> {
|
||||||
|
if args.len() < 4 {
|
||||||
|
return Err(Error::ModuleError("no CSV file specified".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut vtab = CSVTab {
|
||||||
|
base: ffi::sqlite3_vtab::default(),
|
||||||
|
filename: "".to_owned(),
|
||||||
|
has_headers: false,
|
||||||
|
delimiter: b',',
|
||||||
|
quote: b'"',
|
||||||
|
offset_first_row: csv::Position::new(),
|
||||||
|
};
|
||||||
|
let mut schema = None;
|
||||||
|
let mut n_col = None;
|
||||||
|
|
||||||
|
let args = &args[3..];
|
||||||
|
for c_slice in args {
|
||||||
|
let (param, value) = CSVTab::parameter(c_slice)?;
|
||||||
|
match param {
|
||||||
|
"filename" => {
|
||||||
|
if !Path::new(value).exists() {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"file '{}' does not exist",
|
||||||
|
value
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
vtab.filename = value.to_owned();
|
||||||
|
}
|
||||||
|
"schema" => {
|
||||||
|
schema = Some(value.to_owned());
|
||||||
|
}
|
||||||
|
"columns" => {
|
||||||
|
if let Ok(n) = value.parse::<u16>() {
|
||||||
|
if n_col.is_some() {
|
||||||
|
return Err(Error::ModuleError(
|
||||||
|
"more than one 'columns' parameter".to_owned(),
|
||||||
|
));
|
||||||
|
} else if n == 0 {
|
||||||
|
return Err(Error::ModuleError(
|
||||||
|
"must have at least one column".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
n_col = Some(n);
|
||||||
|
} else {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"unrecognized argument to 'columns': {}",
|
||||||
|
value
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"header" => {
|
||||||
|
if let Some(b) = parse_boolean(value) {
|
||||||
|
vtab.has_headers = b;
|
||||||
|
} else {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"unrecognized argument to 'header': {}",
|
||||||
|
value
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"delimiter" => {
|
||||||
|
if let Some(b) = CSVTab::parse_byte(value) {
|
||||||
|
vtab.delimiter = b;
|
||||||
|
} else {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"unrecognized argument to 'delimiter': {}",
|
||||||
|
value
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"quote" => {
|
||||||
|
if let Some(b) = CSVTab::parse_byte(value) {
|
||||||
|
if b == b'0' {
|
||||||
|
vtab.quote = 0;
|
||||||
|
} else {
|
||||||
|
vtab.quote = b;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"unrecognized argument to 'quote': {}",
|
||||||
|
value
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"unrecognized parameter '{}'",
|
||||||
|
param
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vtab.filename.is_empty() {
|
||||||
|
return Err(Error::ModuleError("no CSV file specified".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cols: Vec<String> = Vec::new();
|
||||||
|
if vtab.has_headers || (n_col.is_none() && schema.is_none()) {
|
||||||
|
let mut reader = vtab.reader()?;
|
||||||
|
if vtab.has_headers {
|
||||||
|
{
|
||||||
|
let headers = reader.headers()?;
|
||||||
|
// headers ignored if cols is not empty
|
||||||
|
if n_col.is_none() && schema.is_none() {
|
||||||
|
cols = headers
|
||||||
|
.into_iter()
|
||||||
|
.map(|header| escape_double_quote(&header).into_owned())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vtab.offset_first_row = reader.position().clone();
|
||||||
|
} else {
|
||||||
|
let mut record = csv::ByteRecord::new();
|
||||||
|
if reader.read_byte_record(&mut record)? {
|
||||||
|
for (i, _) in record.iter().enumerate() {
|
||||||
|
cols.push(format!("c{}", i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(n_col) = n_col {
|
||||||
|
for i in 0..n_col {
|
||||||
|
cols.push(format!("c{}", i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cols.is_empty() && schema.is_none() {
|
||||||
|
return Err(Error::ModuleError("no column specified".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if schema.is_none() {
|
||||||
|
let mut sql = String::from("CREATE TABLE x(");
|
||||||
|
for (i, col) in cols.iter().enumerate() {
|
||||||
|
sql.push('"');
|
||||||
|
sql.push_str(col);
|
||||||
|
sql.push_str("\" TEXT");
|
||||||
|
if i == cols.len() - 1 {
|
||||||
|
sql.push_str(");");
|
||||||
|
} else {
|
||||||
|
sql.push_str(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schema = Some(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((schema.unwrap(), vtab))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only a forward full table scan is supported.
|
||||||
|
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||||
|
info.set_estimated_cost(1_000_000.);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open(&self) -> Result<CSVTabCursor> {
|
||||||
|
Ok(CSVTabCursor::new(self.reader()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateVTab for CSVTab {}
|
||||||
|
|
||||||
|
/// A cursor for the CSV virtual table
|
||||||
|
#[repr(C)]
|
||||||
|
struct CSVTabCursor {
|
||||||
|
/// Base class. Must be first
|
||||||
|
base: ffi::sqlite3_vtab_cursor,
|
||||||
|
/// The CSV reader object
|
||||||
|
reader: csv::Reader<File>,
|
||||||
|
/// Current cursor position used as rowid
|
||||||
|
row_number: usize,
|
||||||
|
/// Values of the current row
|
||||||
|
cols: csv::StringRecord,
|
||||||
|
eof: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CSVTabCursor {
|
||||||
|
fn new(reader: csv::Reader<File>) -> CSVTabCursor {
|
||||||
|
CSVTabCursor {
|
||||||
|
base: ffi::sqlite3_vtab_cursor::default(),
|
||||||
|
reader,
|
||||||
|
row_number: 0,
|
||||||
|
cols: csv::StringRecord::new(),
|
||||||
|
eof: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accessor to the associated virtual table.
|
||||||
|
fn vtab(&self) -> &CSVTab {
|
||||||
|
unsafe { &*(self.base.pVtab as *const CSVTab) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VTabCursor for CSVTabCursor {
|
||||||
|
// Only a full table scan is supported. So `filter` simply rewinds to
|
||||||
|
// the beginning.
|
||||||
|
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();
|
||||||
|
self.reader.seek(offset_first_row)?;
|
||||||
|
}
|
||||||
|
self.row_number = 0;
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Result<()> {
|
||||||
|
{
|
||||||
|
self.eof = self.reader.is_done();
|
||||||
|
if self.eof {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.eof = !self.reader.read_record(&mut self.cols)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.row_number += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof(&self) -> bool {
|
||||||
|
self.eof
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column(&self, ctx: &mut Context, col: c_int) -> Result<()> {
|
||||||
|
if col < 0 || col as usize >= self.cols.len() {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"column index out of bounds: {}",
|
||||||
|
col
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if self.cols.is_empty() {
|
||||||
|
return ctx.set_result(&Null);
|
||||||
|
}
|
||||||
|
// TODO Affinity
|
||||||
|
ctx.set_result(&self.cols[col as usize].to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rowid(&self) -> Result<i64> {
|
||||||
|
Ok(self.row_number as i64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<csv::Error> for Error {
|
||||||
|
fn from(err: csv::Error) -> Error {
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
Error::ModuleError(String::from(err.description()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::vtab::csvtab;
|
||||||
|
use crate::{Connection, Result, NO_PARAMS};
|
||||||
|
use fallible_iterator::FallibleIterator;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_csv_module() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
csvtab::load_module(&db).unwrap();
|
||||||
|
db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut s = db.prepare("SELECT rowid, * FROM vtab").unwrap();
|
||||||
|
{
|
||||||
|
let headers = s.column_names();
|
||||||
|
assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ids: Result<Vec<i32>> = s
|
||||||
|
.query(NO_PARAMS)
|
||||||
|
.unwrap()
|
||||||
|
.map(|row| row.get::<_, i32>(0))
|
||||||
|
.collect();
|
||||||
|
let sum = ids.unwrap().iter().sum::<i32>();
|
||||||
|
assert_eq!(sum, 15);
|
||||||
|
}
|
||||||
|
db.execute_batch("DROP TABLE vtab").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_csv_cursor() {
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
csvtab::load_module(&db).unwrap();
|
||||||
|
db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut s = db
|
||||||
|
.prepare(
|
||||||
|
"SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
|
||||||
|
v1.rowid < v2.rowid",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut rows = s.query(NO_PARAMS).unwrap();
|
||||||
|
let row = rows.next().unwrap().unwrap();
|
||||||
|
assert_eq!(row.get_unwrap::<_, i32>(0), 2);
|
||||||
|
}
|
||||||
|
db.execute_batch("DROP TABLE vtab").unwrap();
|
||||||
|
}
|
||||||
|
}
|
1017
src/vtab/mod.rs
Normal file
1017
src/vtab/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
290
src/vtab/series.rs
Normal file
290
src/vtab/series.rs
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
//! generate series virtual table.
|
||||||
|
//!
|
||||||
|
//! Port of C [generate series "function"](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/series.c).
|
||||||
|
use std::default::Default;
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::types::Type;
|
||||||
|
use crate::vtab::{
|
||||||
|
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
|
||||||
|
VTabCursor, Values,
|
||||||
|
};
|
||||||
|
use crate::{Connection, Result};
|
||||||
|
|
||||||
|
/// Register the "generate_series" module.
|
||||||
|
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||||
|
let aux: Option<()> = None;
|
||||||
|
conn.create_module("generate_series", &SERIES_MODULE, aux)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref SERIES_MODULE: Module<SeriesTab> = eponymous_only_module::<SeriesTab>(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column numbers
|
||||||
|
// const SERIES_COLUMN_VALUE : c_int = 0;
|
||||||
|
const SERIES_COLUMN_START: c_int = 1;
|
||||||
|
const SERIES_COLUMN_STOP: c_int = 2;
|
||||||
|
const SERIES_COLUMN_STEP: c_int = 3;
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
#[repr(C)]
|
||||||
|
struct QueryPlanFlags: ::std::os::raw::c_int {
|
||||||
|
// start = $value -- constraint exists
|
||||||
|
const START = 1;
|
||||||
|
// stop = $value -- constraint exists
|
||||||
|
const STOP = 2;
|
||||||
|
// step = $value -- constraint exists
|
||||||
|
const STEP = 4;
|
||||||
|
// output in descending order
|
||||||
|
const DESC = 8;
|
||||||
|
// Both start and stop
|
||||||
|
const BOTH = QueryPlanFlags::START.bits | QueryPlanFlags::STOP.bits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An instance of the Series virtual table
|
||||||
|
#[repr(C)]
|
||||||
|
struct SeriesTab {
|
||||||
|
/// Base class. Must be first
|
||||||
|
base: ffi::sqlite3_vtab,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VTab for SeriesTab {
|
||||||
|
type Aux = ();
|
||||||
|
type Cursor = SeriesTabCursor;
|
||||||
|
|
||||||
|
fn connect(
|
||||||
|
_: &mut VTabConnection,
|
||||||
|
_aux: Option<&()>,
|
||||||
|
_args: &[&[u8]],
|
||||||
|
) -> Result<(String, SeriesTab)> {
|
||||||
|
let vtab = SeriesTab {
|
||||||
|
base: ffi::sqlite3_vtab::default(),
|
||||||
|
};
|
||||||
|
Ok((
|
||||||
|
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(),
|
||||||
|
vtab,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||||
|
// The query plan bitmask
|
||||||
|
let mut idx_num: QueryPlanFlags = QueryPlanFlags::empty();
|
||||||
|
// Index of the start= constraint
|
||||||
|
let mut start_idx = None;
|
||||||
|
// Index of the stop= constraint
|
||||||
|
let mut stop_idx = None;
|
||||||
|
// Index of the step= constraint
|
||||||
|
let mut step_idx = None;
|
||||||
|
for (i, constraint) in info.constraints().enumerate() {
|
||||||
|
if !constraint.is_usable() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match constraint.column() {
|
||||||
|
SERIES_COLUMN_START => {
|
||||||
|
start_idx = Some(i);
|
||||||
|
idx_num |= QueryPlanFlags::START;
|
||||||
|
}
|
||||||
|
SERIES_COLUMN_STOP => {
|
||||||
|
stop_idx = Some(i);
|
||||||
|
idx_num |= QueryPlanFlags::STOP;
|
||||||
|
}
|
||||||
|
SERIES_COLUMN_STEP => {
|
||||||
|
step_idx = Some(i);
|
||||||
|
idx_num |= QueryPlanFlags::STEP;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut num_of_arg = 0;
|
||||||
|
if let Some(start_idx) = start_idx {
|
||||||
|
num_of_arg += 1;
|
||||||
|
let mut constraint_usage = info.constraint_usage(start_idx);
|
||||||
|
constraint_usage.set_argv_index(num_of_arg);
|
||||||
|
constraint_usage.set_omit(true);
|
||||||
|
}
|
||||||
|
if let Some(stop_idx) = stop_idx {
|
||||||
|
num_of_arg += 1;
|
||||||
|
let mut constraint_usage = info.constraint_usage(stop_idx);
|
||||||
|
constraint_usage.set_argv_index(num_of_arg);
|
||||||
|
constraint_usage.set_omit(true);
|
||||||
|
}
|
||||||
|
if let Some(step_idx) = step_idx {
|
||||||
|
num_of_arg += 1;
|
||||||
|
let mut constraint_usage = info.constraint_usage(step_idx);
|
||||||
|
constraint_usage.set_argv_index(num_of_arg);
|
||||||
|
constraint_usage.set_omit(true);
|
||||||
|
}
|
||||||
|
if idx_num.contains(QueryPlanFlags::BOTH) {
|
||||||
|
// Both start= and stop= boundaries are available.
|
||||||
|
info.set_estimated_cost(f64::from(
|
||||||
|
2 - if idx_num.contains(QueryPlanFlags::STEP) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
));
|
||||||
|
info.set_estimated_rows(1000);
|
||||||
|
let order_by_consumed = {
|
||||||
|
let mut order_bys = info.order_bys();
|
||||||
|
if let Some(order_by) = order_bys.next() {
|
||||||
|
if order_by.is_order_by_desc() {
|
||||||
|
idx_num |= QueryPlanFlags::DESC;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if order_by_consumed {
|
||||||
|
info.set_order_by_consumed(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info.set_estimated_cost(2_147_483_647f64);
|
||||||
|
info.set_estimated_rows(2_147_483_647);
|
||||||
|
}
|
||||||
|
info.set_idx_num(idx_num.bits());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open(&self) -> Result<SeriesTabCursor> {
|
||||||
|
Ok(SeriesTabCursor::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A cursor for the Series virtual table
|
||||||
|
#[derive(Default)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct SeriesTabCursor {
|
||||||
|
/// Base class. Must be first
|
||||||
|
base: ffi::sqlite3_vtab_cursor,
|
||||||
|
/// True to count down rather than up
|
||||||
|
is_desc: bool,
|
||||||
|
/// The rowid
|
||||||
|
row_id: i64,
|
||||||
|
/// Current value ("value")
|
||||||
|
value: i64,
|
||||||
|
/// Mimimum value ("start")
|
||||||
|
min_value: i64,
|
||||||
|
/// Maximum value ("stop")
|
||||||
|
max_value: i64,
|
||||||
|
/// Increment ("step")
|
||||||
|
step: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeriesTabCursor {
|
||||||
|
fn new() -> SeriesTabCursor {
|
||||||
|
SeriesTabCursor::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl VTabCursor for SeriesTabCursor {
|
||||||
|
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 mut i = 0;
|
||||||
|
if idx_num.contains(QueryPlanFlags::START) {
|
||||||
|
self.min_value = args.get(i)?;
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
self.min_value = 0;
|
||||||
|
}
|
||||||
|
if idx_num.contains(QueryPlanFlags::STOP) {
|
||||||
|
self.max_value = args.get(i)?;
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
self.max_value = 0xffff_ffff;
|
||||||
|
}
|
||||||
|
if idx_num.contains(QueryPlanFlags::STEP) {
|
||||||
|
self.step = args.get(i)?;
|
||||||
|
if self.step < 1 {
|
||||||
|
self.step = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.step = 1;
|
||||||
|
};
|
||||||
|
for arg in args.iter() {
|
||||||
|
if arg.data_type() == Type::Null {
|
||||||
|
// If any of the constraints have a NULL value, then return no rows.
|
||||||
|
self.min_value = 1;
|
||||||
|
self.max_value = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.is_desc = idx_num.contains(QueryPlanFlags::DESC);
|
||||||
|
if self.is_desc {
|
||||||
|
self.value = self.max_value;
|
||||||
|
if self.step > 0 {
|
||||||
|
self.value -= (self.max_value - self.min_value) % self.step;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.value = self.min_value;
|
||||||
|
}
|
||||||
|
self.row_id = 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Result<()> {
|
||||||
|
if self.is_desc {
|
||||||
|
self.value -= self.step;
|
||||||
|
} else {
|
||||||
|
self.value += self.step;
|
||||||
|
}
|
||||||
|
self.row_id += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof(&self) -> bool {
|
||||||
|
if self.is_desc {
|
||||||
|
self.value < self.min_value
|
||||||
|
} else {
|
||||||
|
self.value > self.max_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
|
||||||
|
let x = match i {
|
||||||
|
SERIES_COLUMN_START => self.min_value,
|
||||||
|
SERIES_COLUMN_STOP => self.max_value,
|
||||||
|
SERIES_COLUMN_STEP => self.step,
|
||||||
|
_ => self.value,
|
||||||
|
};
|
||||||
|
ctx.set_result(&x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rowid(&self) -> Result<i64> {
|
||||||
|
Ok(self.row_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::ffi;
|
||||||
|
use crate::vtab::series;
|
||||||
|
use crate::{Connection, NO_PARAMS};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_series_module() {
|
||||||
|
let version = unsafe { ffi::sqlite3_libversion_number() };
|
||||||
|
if version < 3_008_012 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
series::load_module(&db).unwrap();
|
||||||
|
|
||||||
|
let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)").unwrap();
|
||||||
|
|
||||||
|
let series = s.query_map(NO_PARAMS, |row| row.get::<_, i32>(0)).unwrap();
|
||||||
|
|
||||||
|
let mut expected = 0;
|
||||||
|
for value in series {
|
||||||
|
assert_eq!(expected, value.unwrap());
|
||||||
|
expected += 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
test.csv
Normal file
6
test.csv
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
"colA","colB","colC"
|
||||||
|
1,2,3
|
||||||
|
a,b,c
|
||||||
|
a,"b",c
|
||||||
|
"a","b","c .. z"
|
||||||
|
"a","b","c,d"
|
|
@ -1,13 +1,10 @@
|
|||||||
//! This file contains unit tests for `rusqlite::trace::config_log`. This function affects
|
//! This file contains unit tests for `rusqlite::trace::config_log`. This
|
||||||
//! SQLite process-wide and so is not safe to run as a normal #[test] in the library.
|
//! function affects SQLite process-wide and so is not safe to run as a normal
|
||||||
|
//! #[test] in the library.
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
extern crate rusqlite;
|
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
fn main() {
|
fn main() {
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
@ -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 rusqlite;
|
use rusqlite::ffi;
|
||||||
extern crate libsqlite3_sys as ffi;
|
|
||||||
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
101
tests/vtab.rs
Normal file
101
tests/vtab.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
//! Ensure Virtual tables can be declared outside `rusqlite` crate.
|
||||||
|
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
#[test]
|
||||||
|
fn test_dummy_module() {
|
||||||
|
use rusqlite::types::ToSql;
|
||||||
|
use rusqlite::vtab::{
|
||||||
|
eponymous_only_module, sqlite3_vtab, sqlite3_vtab_cursor, Context, IndexInfo, VTab,
|
||||||
|
VTabConnection, VTabCursor, Values,
|
||||||
|
};
|
||||||
|
use rusqlite::{version_number, Connection, Result};
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
|
let module = eponymous_only_module::<DummyTab>(1);
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct DummyTab {
|
||||||
|
/// Base class. Must be first
|
||||||
|
base: sqlite3_vtab,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VTab for DummyTab {
|
||||||
|
type Aux = ();
|
||||||
|
type Cursor = DummyTabCursor;
|
||||||
|
|
||||||
|
fn connect(
|
||||||
|
_: &mut VTabConnection,
|
||||||
|
_aux: Option<&()>,
|
||||||
|
_args: &[&[u8]],
|
||||||
|
) -> Result<(String, DummyTab)> {
|
||||||
|
let vtab = DummyTab {
|
||||||
|
base: sqlite3_vtab::default(),
|
||||||
|
};
|
||||||
|
Ok(("CREATE TABLE x(value)".to_owned(), vtab))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||||
|
info.set_estimated_cost(1.);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open(&self) -> Result<DummyTabCursor> {
|
||||||
|
Ok(DummyTabCursor::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct DummyTabCursor {
|
||||||
|
/// Base class. Must be first
|
||||||
|
base: sqlite3_vtab_cursor,
|
||||||
|
/// The rowid
|
||||||
|
row_id: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VTabCursor for DummyTabCursor {
|
||||||
|
fn filter(
|
||||||
|
&mut self,
|
||||||
|
_idx_num: c_int,
|
||||||
|
_idx_str: Option<&str>,
|
||||||
|
_args: &Values<'_>,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.row_id = 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Result<()> {
|
||||||
|
self.row_id += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof(&self) -> bool {
|
||||||
|
self.row_id > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column(&self, ctx: &mut Context, _: c_int) -> Result<()> {
|
||||||
|
ctx.set_result(&self.row_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rowid(&self) -> Result<i64> {
|
||||||
|
Ok(self.row_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let db = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
db.create_module::<DummyTab>("dummy", &module, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let version = version_number();
|
||||||
|
if version < 3_008_012 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut s = db.prepare("SELECT * FROM dummy()").unwrap();
|
||||||
|
|
||||||
|
let dummy = s
|
||||||
|
.query_row(&[] as &[&dyn ToSql], |row| row.get::<_, i32>(0))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(1, dummy);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user