Merge remote-tracking branch 'jgallagher/master' into cant-open

This commit is contained in:
gwenn 2019-10-19 10:21:00 +02:00
commit 15f356e5a7
60 changed files with 54654 additions and 25567 deletions

View File

@ -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"

View File

@ -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)

View File

@ -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"

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -1,10 +1,10 @@
/* automatically generated by rust-bindgen */ /* automatically generated by rust-bindgen */
pub const __GNUC_VA_LIST: i32 = 1; pub const __GNUC_VA_LIST: i32 = 1;
pub const SQLITE_VERSION: &'static [u8; 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 {

View File

@ -1,25 +1,61 @@
fn main() { use std::env;
build::main();
}
#[cfg(feature = "bundled")]
mod build {
extern crate cc;
use std::{env, fs};
use std::path::Path; use std::path::Path;
pub fn main() { fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("bindgen.rs");
if cfg!(feature = "sqlcipher") { if cfg!(feature = "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(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
mod build_bundled {
use cc;
use std::env;
use std::path::Path;
pub fn main(out_dir: &str, out_path: &Path) {
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"); {
use super::{bindings, HeaderLocation};
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) fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
.expect("Could not copy bindings to output directory"); .expect("Could not copy bindings to output directory");
}
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,23 +70,62 @@ 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 {
#[cfg(all(feature = "vcpkg", target_env = "msvc"))] "SQLITE3"
extern crate vcpkg; }
}
use std::env;
pub enum HeaderLocation { pub enum HeaderLocation {
FromEnvironment, FromEnvironment,
@ -63,8 +138,10 @@ mod build {
match header { match header {
HeaderLocation::FromEnvironment => { HeaderLocation::FromEnvironment => {
let prefix = env_prefix(); let prefix = env_prefix();
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)) let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!(
.expect(&format!("{}_INCLUDE_DIR must be set if {}_LIB_DIR is set", prefix, prefix)); "{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
prefix, prefix
));
header.push_str("/sqlite3.h"); header.push_str("/sqlite3.h");
header header
} }
@ -74,19 +151,69 @@ mod build {
} }
} }
pub fn main() { mod build_linked {
use pkg_config;
#[cfg(all(feature = "vcpkg", target_env = "msvc"))]
extern crate vcpkg;
use super::{bindings, env_prefix, HeaderLocation};
use std::env;
use std::path::Path;
pub fn main(_out_dir: &str, out_path: &Path) {
let header = find_sqlite(); let header = find_sqlite();
bindings::write_to_out_dir(header); if cfg!(any(
feature = "bundled",
all(windows, feature = "bundled-windows")
)) && !cfg!(feature = "buildtime_bindgen")
{
// We can only get here if `bundled` and `sqlcipher` were both
// specified (and `builtime_bindgen` was not). In order to keep
// `rusqlite` relatively clean we hide the fact that `bundled` can
// be ignored in some cases, and just use the bundled bindings, even
// though the library we found might not match their version.
// Ideally we'd perform a version check here, but doing so is rather
// tricky, since we might not have access to executables (and
// moreover, we might be cross compiling).
std::fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
.expect("Could not copy bindings to output directory");
} else {
bindings::write_to_out_dir(header, out_path);
}
} }
fn find_link_mode() -> &'static str {
// If the user specifies SQLITE_STATIC (or SQLCIPHER_STATIC), do static
// 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
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); 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,37 +269,26 @@ 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_11")]
"bindgen-bindings/bindgen_3.6.11.rs",
#[cfg(feature = "min_sqlite_version_3_6_23")] #[cfg(feature = "min_sqlite_version_3_6_23")]
"bindgen-bindings/bindgen_3.6.23.rs", "bindgen-bindings/bindgen_3.6.23.rs",
#[cfg(feature = "min_sqlite_version_3_7_7")]
#[cfg(feature = "min_sqlite_version_3_7_3")] "bindgen-bindings/bindgen_3.7.7.rs",
"bindgen-bindings/bindgen_3.7.3.rs",
#[cfg(feature = "min_sqlite_version_3_7_4")]
"bindgen-bindings/bindgen_3.7.4.rs",
#[cfg(feature = "min_sqlite_version_3_7_16")] #[cfg(feature = "min_sqlite_version_3_7_16")]
"bindgen-bindings/bindgen_3.7.16.rs", "bindgen-bindings/bindgen_3.7.16.rs",
]; ];
pub fn write_to_out_dir(_header: HeaderLocation) { pub fn write_to_out_dir(_header: HeaderLocation, out_path: &Path) {
let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("bindgen.rs");
let in_path = PREBUILT_BINDGEN_PATHS[PREBUILT_BINDGEN_PATHS.len() - 1]; 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"); fs::copy(in_path, out_path).expect("Could not copy bindings to output directory");
} }
@ -185,14 +296,13 @@ mod build {
#[cfg(feature = "buildtime_bindgen")] #[cfg(feature = "buildtime_bindgen")]
mod bindings { mod bindings {
extern crate bindgen; use bindgen;
use self::bindgen::callbacks::{ParseCallbacks, IntKind};
use super::HeaderLocation; use super::HeaderLocation;
use bindgen::callbacks::{IntKind, ParseCallbacks};
use std::env;
use std::io::Write;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path; use std::path::Path;
#[derive(Debug)] #[derive(Debug)]
@ -208,37 +318,49 @@ mod build {
} }
} }
pub fn write_to_out_dir(header: HeaderLocation) { pub fn write_to_out_dir(header: HeaderLocation, out_path: &Path) {
let header: String = header.into(); let header: String = header.into();
let out_dir = env::var("OUT_DIR").unwrap();
let mut output = Vec::new(); let mut output = Vec::new();
bindgen::builder() let mut bindings = bindgen::builder()
.header(header.clone()) .header(header.clone())
.parse_callbacks(Box::new(SqliteTypeChooser)) .parse_callbacks(Box::new(SqliteTypeChooser))
.rustfmt_bindings(true);
if cfg!(feature = "unlock_notify") {
bindings = bindings.clang_arg("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
}
if cfg!(feature = "preupdate_hook") {
bindings = bindings.clang_arg("-DSQLITE_ENABLE_PREUPDATE_HOOK");
}
if cfg!(feature = "session") {
bindings = bindings.clang_arg("-DSQLITE_ENABLE_SESSION");
}
bindings
.generate() .generate()
.expect(&format!("could not run bindgen on header {}", header)) .expect(&format!("could not run bindgen on header {}", header))
.write(Box::new(&mut output)) .write(Box::new(&mut output))
.expect("could not write output of bindgen"); .expect("could not write output of bindgen");
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!"); let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
// rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it can. This flag // rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it
// was added in SQLite 3.8.3, but oring it in in prior versions of SQLite is harmless. We // can. This flag was added in SQLite 3.8.3, but oring it in in prior
// don't want to not build just because this flag is missing (e.g., if we're linking against // versions of SQLite is harmless. We don't want to not build just
// SQLite 3.7.x), so append the flag manually if it isn't present in bindgen's output. // 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.
if !output.contains("pub const SQLITE_DETERMINISTIC") { if !output.contains("pub const SQLITE_DETERMINISTIC") {
output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n"); output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n");
} }
let path = Path::new(&out_dir).join("bindgen.rs");
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true) .write(true)
.truncate(true) .truncate(true)
.create(true) .create(true)
.open(path.clone()) .open(out_path.clone())
.expect(&format!("Could not write to {:?}", path)); .expect(&format!("Could not write to {:?}", out_path));
file.write_all(output.as_bytes()).expect(&format!("Could not write to {:?}", path)); file.write_all(output.as_bytes())
} .expect(&format!("Could not write to {:?}", out_path));
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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)]
@ -91,15 +91,20 @@ impl Error {
}; };
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,9 +115,10 @@ impl error::Error for Error {
} }
// Result codes. // Result codes.
// Note: These are not public because our bindgen bindings export whichever constants are present // Note: These are not public because our bindgen bindings export whichever
// in the current version of SQLite. We repeat them here so we don't have to worry about which // constants are present in the current version of SQLite. We repeat them here
// version of SQLite added which constants, and we only use them to implement code_to_str below. // so we don't have to worry about which version of SQLite added which
// constants, and we only use them to implement code_to_str below.
const SQLITE_NOTICE: c_int = 27; const SQLITE_NOTICE: c_int = 27;
const SQLITE_WARNING: c_int = 28; const SQLITE_WARNING: c_int = 28;

View File

@ -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
View 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'

View File

@ -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,
name: DatabaseName<'_>,
dst_path: P, dst_path: P,
progress: Option<fn(Progress)>) progress: Option<fn(Progress)>,
-> Result<()> { ) -> Result<()> {
use self::StepResult::{More, Done, Busy, Locked}; use self::StepResult::{Busy, Done, Locked, More};
let mut dst = try!(Connection::open(dst_path)); let mut dst = Connection::open(dst_path)?;
let backup = try!(Backup::new_with_names(self, name, &mut dst, DatabaseName::Main)); 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,
name: DatabaseName<'_>,
src_path: P, src_path: P,
progress: Option<fn(Progress)>) progress: Option<F>,
-> Result<()> { ) -> Result<()> {
use self::StepResult::{More, Done, Busy, Locked}; use self::StepResult::{Busy, Done, Locked, More};
let src = try!(Connection::open(src_path)); let src = Connection::open(src_path)?;
let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name)); let restore = Backup::new_with_names(&src, DatabaseName::Main, self, name)?;
let mut r = More; let mut r = More;
let mut busy_count = 0i32; let mut busy_count = 0i32;
'restore_loop: while r == More || r == Busy { 'restore_loop: while r == More || r == Busy {
r = try!(restore.step(100)); r = restore.step(100)?;
if let Some(f) = progress { if let Some(ref f) = progress {
f(restore.progress()); f(restore.progress());
} }
if r == Busy { if r == Busy {
@ -129,7 +134,8 @@ 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
@ -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,
from_name: DatabaseName<'_>,
to: &'b mut Connection, to: &'b mut Connection,
to_name: DatabaseName) to_name: DatabaseName<'_>,
-> Result<Backup<'a, 'b>> { ) -> Result<Backup<'a, 'b>> {
let to_name = try!(to_name.to_cstring()); let to_name = to_name.to_cstring()?;
let from_name = try!(from_name.to_cstring()); let from_name = from_name.to_cstring()?;
let to_db = to.db.borrow_mut().db; let to_db = to.db.borrow_mut().db;
let b = unsafe { let b = unsafe {
let b = ffi::sqlite3_backup_init(to_db, let b = ffi::sqlite3_backup_init(
to_db,
to_name.as_ptr(), to_name.as_ptr(),
from.db.borrow_mut().db, from.db.borrow_mut().db,
from_name.as_ptr()); 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)));
} }
@ -208,7 +217,7 @@ 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,
}) })
} }
@ -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(
&self,
pages_per_step: c_int, pages_per_step: c_int,
pause_between_pages: Duration, pause_between_pages: Duration,
progress: Option<fn(Progress)>) progress: Option<fn(Progress)>,
-> Result<()> { ) -> Result<()> {
use self::StepResult::{Done, More, Busy, Locked}; 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,
DatabaseName::Main)
.unwrap(); .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
.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);
} }
#[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(
&src,
DatabaseName::Attached("my_attached"), DatabaseName::Attached("my_attached"),
&mut dst, &mut dst,
DatabaseName::Main) DatabaseName::Main,
)
.unwrap(); .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(
&src,
DatabaseName::Attached("my_attached"), DatabaseName::Attached("my_attached"),
&mut dst, &mut dst,
DatabaseName::Main) DatabaseName::Main,
)
.unwrap();
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);
} }
} }

View File

@ -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>(
&'a self,
db: DatabaseName<'_>,
table: &str, table: &str,
column: &str, column: &str,
row: i64, row_id: i64,
read_only: bool) read_only: bool,
-> Result<Blob<'a>> { ) -> Result<Blob<'a>> {
let mut c = self.db.borrow_mut(); let mut c = self.db.borrow_mut();
let mut blob = ptr::null_mut(); let mut blob = ptr::null_mut();
let db = try!(db.to_cstring()); let db = db.to_cstring()?;
let table = try!(super::str_to_cstring(table)); let table = super::str_to_cstring(table)?;
let column = try!(super::str_to_cstring(column)); let column = super::str_to_cstring(column)?;
let rc = unsafe { let rc = unsafe {
ffi::sqlite3_blob_open(c.db(), ffi::sqlite3_blob_open(
c.db(),
db.as_ptr(), db.as_ptr(),
table.as_ptr(), table.as_ptr(),
column.as_ptr(), column.as_ptr(),
row, row_id,
if read_only { 0 } else { 1 }, if read_only { 0 } else { 1 },
&mut blob) &mut blob,
)
}; };
c.decode_result(rc) c.decode_result(rc).map(|_| Blob {
.map(|_| {
Blob {
conn: self, conn: self,
blob: blob, blob,
pos: 0, 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,8 +161,7 @@ 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(|_| {
@ -167,13 +172,14 @@ impl<'conn> io::Read for Blob<'conn> {
} }
} }
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,9 +190,7 @@ 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(|_| {
@ -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
View 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();
}
}

View File

@ -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 self,
conn: &'conn Connection, conn: &'conn Connection,
sql: &str) sql: &str,
-> Result<CachedStatement<'conn>> { ) -> 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))),
stmt.query(NO_PARAMS)
.unwrap() .unwrap()
.map(|r| Ok((r.get(0)?, r.get(1)?)))
.next() .next()
.unwrap() );
.unwrap());
} }
} }
#[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
View File

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

215
src/column.rs Normal file
View 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
View 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
View 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(),
);
}
}
}
}

View File

@ -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 {
write!(
f,
"Conversion error from type {} at index: {}, {}", "Conversion error from type {} at index: {}, {}",
t, t, i, err
i, )
err) } else {
err.fmt(f)
}
} }
Error::IntegralValueOutOfRange(col, val) => { Error::IntegralValueOutOfRange(col, val) => {
if col != UNKNOWN_COLUMN {
write!(f, "Integer {} out of range at index {}", val, col) write!(f, "Integer {} out of range at index {}", val, col)
} else {
write!(f, "Integer {} out of range", val)
}
} }
Error::Utf8Error(ref err) => err.fmt(f), Error::Utf8Error(ref err) => err.fmt(f),
Error::NulError(ref err) => err.fmt(f), Error::NulError(ref err) => err.fmt(f),
@ -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());
}
}};
}

File diff suppressed because it is too large Load Diff

304
src/hooks.rs Normal file
View 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
View 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(())
}
}

1426
src/lib.rs

File diff suppressed because it is too large Load Diff

View File

@ -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));
} }

View File

@ -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
View 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());
}
}

View File

@ -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 {
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) } 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 {

View File

@ -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,
}))
} }
Ok(false) => {
self.reset(); pub fn map<F, B>(self, f: F) -> Map<'stmt, F>
None where
} F: FnMut(&Row<'_>) -> Result<B>,
Err(err) => { {
self.reset(); Map { rows: self, f }
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> { pub(crate) fn get_expected_row(&mut self) -> Result<&Row<'stmt>> {
fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> { match self.next()? {
Rows { stmt: Some(stmt) } Some(row) => Ok(row),
}
fn get_expected_row<'a>(&'a mut self) -> Result<Row<'a, 'stmt>> {
match self.next() {
Some(row) => 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(())
}
}
} }
impl<'a, 'stmt> Row<'a, 'stmt> { fn get(&self) -> Option<&Row<'stmt>> {
self.row.as_ref()
}
}
/// 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 the underlying SQLite integral value is outside the range
/// representable by `T`
/// * If `idx` is outside the range of columns in the returned query /// * If `idx` is outside the range of columns in the returned query
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> T { pub fn get_unwrap<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
self.get_checked(idx).unwrap() 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
View 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());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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(
ffi::SQLITE_CONFIG_LOG,
log_callback as extern "C" fn(_, _, _), log_callback as extern "C" fn(_, _, _),
p_arg) 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(
p_arg: *mut c_void,
z_sql: *const c_char, z_sql: *const c_char,
nanoseconds: u64) { 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();

View File

@ -1,9 +1,5 @@
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.
@ -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,21 +87,21 @@ 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 {
conn: conn,
drop_behavior: DropBehavior::Rollback, drop_behavior: DropBehavior::Rollback,
committed: false,
}
}) })
} }
@ -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>>(
conn: &Connection,
depth: u32, depth: u32,
name: T) name: T,
-> Result<Savepoint> { ) -> Result<Savepoint<'_>> {
let name = name.into(); let name = name.into();
conn.execute_batch(&format!("SAVEPOINT {}", name)) conn.execute_batch(&format!("SAVEPOINT {}", name))
.map(|_| { .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);
} }

View File

@ -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,7 +17,7 @@ impl ToSql for NaiveDate {
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone. /// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
impl FromSql for NaiveDate { impl FromSql for NaiveDate {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value value
.as_str() .as_str()
.and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") { .and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
@ -30,7 +29,7 @@ impl FromSql for NaiveDate {
/// ISO 8601 time without timezone => "HH:MM:SS.SSS" /// ISO 8601 time without timezone => "HH:MM:SS.SSS"
impl ToSql for NaiveTime { impl ToSql for NaiveTime {
fn to_sql(&self) -> Result<ToSqlOutput> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self.format("%H:%M:%S%.f").to_string(); let date_str = self.format("%H:%M:%S%.f").to_string();
Ok(ToSqlOutput::from(date_str)) Ok(ToSqlOutput::from(date_str))
} }
@ -38,10 +37,8 @@ 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()
.and_then(|s| {
let fmt = match s.len() { let fmt = match s.len() {
5 => "%H:%M", 5 => "%H:%M",
8 => "%H:%M:%S", 8 => "%H:%M:%S",
@ -55,21 +52,21 @@ impl FromSql for NaiveTime {
} }
} }
/// 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()
.and_then(|s| {
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' { let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%Y-%m-%dT%H:%M:%S%.f" "%Y-%m-%dT%H:%M:%S%.f"
} else { } else {
@ -84,19 +81,20 @@ impl FromSql for NaiveDateTime {
} }
} }
/// 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,24 +210,30 @@ 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| {
r.get(0)
})
.unwrap(); .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());
}
} }

View File

@ -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,7 +152,7 @@ impl FromSql for f64 {
} }
impl FromSql for bool { impl FromSql for bool {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
i64::column_result(value).map(|i| match i { i64::column_result(value).map(|i| match i {
0 => false, 0 => false,
_ => true, _ => true,
@ -122,19 +161,47 @@ impl FromSql for bool {
} }
impl FromSql for String { impl FromSql for String {
fn column_result(value: ValueRef) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(|s| s.to_string()) value.as_str().map(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!(
*n,
db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0)) db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
.unwrap() .unwrap()
.into()); .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]);
} }
} }

View File

@ -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,29 +55,28 @@ 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)]
@ -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)",
NO_PARAMS,
)
.unwrap(); .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)",
NO_PARAMS,
)
.unwrap(); .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());
} }
} }

View File

@ -1,23 +1,22 @@
//! `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),
} }
@ -27,8 +26,9 @@ impl FromSql for Value {
#[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 (?, ?)",
&[&data as &dyn ToSql, &json.as_bytes()],
)
.unwrap(); .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);
} }

View File

@ -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();
@ -46,23 +54,29 @@ mod test {
ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM
ts_vec.push(time::Timespec::new(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond) ts_vec.push(time::Timespec::new(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
ts_vec.push(time::Timespec::new(1500391124, 1_000_000));//July 18, 2017 ts_vec.push(time::Timespec::new(1_500_391_124, 1_000_000)); //July 18, 2017
ts_vec.push(time::Timespec::new(2000000000, 2_000_000));//May 18, 2033 ts_vec.push(time::Timespec::new(2_000_000_000, 2_000_000)); //May 18, 2033
ts_vec.push(time::Timespec::new(3000000000, 999_999_999));//January 24, 2065 ts_vec.push(time::Timespec::new(3_000_000_000, 999_999_999)); //January 24, 2065
ts_vec.push(time::Timespec::new(10000000000, 0));//November 20, 2286 ts_vec.push(time::Timespec::new(10_000_000_000, 0)); //November 20, 2286
for ts in ts_vec { for ts in ts_vec {
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]) 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());
} }
} }

View File

@ -1,7 +1,11 @@
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
/// `ToSql` trait.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum ToSqlOutput<'a> { pub enum ToSqlOutput<'a> {
/// A borrowed SQLite-representable value. /// A borrowed SQLite-representable value.
@ -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
View 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);
}
}
}
}

View File

@ -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 {

View File

@ -1,5 +1,5 @@
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.
@ -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
View 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();
}
}

View File

@ -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
View 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
View 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

File diff suppressed because it is too large Load Diff

290
src/vtab/series.rs Normal file
View 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
View 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 colA colB colC
2 1 2 3
3 a b c
4 a b c
5 a b c .. z
6 a b c,d

View File

@ -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;

View File

@ -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
View 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);
}