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
dist: trusty
language: rust
@ -9,6 +8,7 @@ rust:
- nightly
matrix:
fast_finish: true
allow_failures:
- rust: nightly
@ -26,18 +26,23 @@ script:
- cargo build
- cargo build --features bundled
- cargo build --features sqlcipher
- cargo build --features "bundled sqlcipher"
- cargo test
- cargo test --features backup
- cargo test --features blob
- cargo test --features functions
- cargo test --features limits
- cargo test --features "backup blob extra_check"
- cargo test --features "collation functions"
- cargo test --features "hooks limits"
- cargo test --features load_extension
- cargo test --features trace
- cargo test --features chrono
- cargo test --features serde_json
- cargo test --features url
- cargo test --features bundled
- cargo test --features sqlcipher
- cargo test --features "backup blob chrono functions limits load_extension serde_json trace"
- cargo test --features "backup blob chrono functions limits load_extension serde_json trace buildtime_bindgen"
- cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled"
- cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled buildtime_bindgen"
- cargo test --features i128_blob
- cargo test --features uuid
- cargo test --features "bundled unlock_notify window"
- cargo test --features "array bundled csvtab series vtab"
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab"
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab buildtime_bindgen"
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled"
- cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension 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]
name = "rusqlite"
version = "0.13.0"
version = "0.20.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
edition = "2018"
description = "Ergonomic wrapper for SQLite"
repository = "https://github.com/jgallagher/rusqlite"
documentation = "http://docs.rs/rusqlite/"
@ -13,20 +14,47 @@ categories = ["database"]
[badges]
travis-ci = { repository = "jgallagher/rusqlite" }
appveyor = { repository = "jgallagher/rusqlite" }
maintenance = { status = "actively-developed" }
[lib]
name = "rusqlite"
[workspace]
members = ["libsqlite3-sys"]
[features]
load_extension = []
backup = ["libsqlite3-sys/min_sqlite_version_3_6_11"]
blob = ["libsqlite3-sys/min_sqlite_version_3_7_4"]
functions = ["libsqlite3-sys/min_sqlite_version_3_7_3"]
# hot-backup interface: 3.6.11 (2009-02-18)
backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
# sqlite3_blob_reopen: 3.7.4
blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
collation = []
# sqlite3_create_function_v2: 3.7.3 (2010-10-08)
functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
# sqlite3_log: 3.6.23 (2010-03-09)
trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
bundled = ["libsqlite3-sys/bundled"]
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
limits = []
hooks = []
i128_blob = ["byteorder"]
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]
time = "0.1.0"
@ -34,15 +62,25 @@ bitflags = "1.0"
lru-cache = "0.1"
chrono = { version = "0.4", optional = true }
serde_json = { version = "1.0", optional = true }
csv = { version = "1.0", optional = true }
url = { version = "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]
tempdir = "0.3"
lazy_static = "0.2"
regex = "0.2"
lazy_static = "1.0"
regex = "1.0"
uuid = { version = "0.8", features = ["v4"] }
unicase = "2.4.0"
[dependencies.libsqlite3-sys]
path = "libsqlite3-sys"
version = "0.9"
version = "0.16"
[[test]]
name = "config_log"
@ -51,8 +89,11 @@ harness = false
[[test]]
name = "deny_single_threaded_sqlite_config"
[[test]]
name = "vtab"
[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
no-default-features = true
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)
* Added ToSqlConversionFailure case to Error enum.

103
README.md
View File

@ -1,59 +1,65 @@
# 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
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). View the full
[API documentation](http://docs.rs/rusqlite/).
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
```rust
extern crate rusqlite;
extern crate time;
use rusqlite::types::ToSql;
use rusqlite::{Connection, Result, NO_PARAMS};
use time::Timespec;
use rusqlite::Connection;
#[derive(Debug)]
struct Person {
id: i32,
name: String,
time_created: Timespec,
data: Option<Vec<u8>>
data: Option<Vec<u8>>,
}
fn main() {
let conn = Connection::open_in_memory().unwrap();
fn main() -> Result<()> {
let conn = Connection::open_in_memory()?;
conn.execute("CREATE TABLE person (
conn.execute(
"CREATE TABLE person (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
time_created TEXT NOT NULL,
data BLOB
)", &[]).unwrap();
)",
NO_PARAMS,
)?;
let me = Person {
id: 0,
name: "Steven".to_string(),
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)",
&[&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 person_iter = stmt.query_map(&[], |row| {
Person {
id: row.get(0),
name: row.get(1),
time_created: row.get(2),
data: row.get(3)
}
}).unwrap();
let mut stmt = conn
.prepare("SELECT id, name, time_created, data FROM person")?;
let person_iter = stmt
.query_map(NO_PARAMS, |row| Ok(Person {
id: row.get(0)?,
name: row.get(1)?,
time_created: row.get(2)?,
data: row.get(3)?,
}))?;
for person in person_iter {
println!("Found person {:?}", person.unwrap());
}
Ok(())
}
```
@ -66,31 +72,42 @@ newer SQLite version; see details below.
### Optional Features
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.
* [`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.
* [`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.
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
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
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.
* `chrono` implements [`FromSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.FromSql.html)
and [`ToSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.ToSql.html) for various
* `chrono` 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 various
types from the [`chrono` crate](https://crates.io/crates/chrono).
* `serde_json` implements [`FromSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.FromSql.html)
and [`ToSql`](http://jgallagher.github.io/rusqlite/rusqlite/types/trait.ToSql.html) for the
* `serde_json` 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
`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.
* `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
@ -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
[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
is currently SQLite 3.17.0 (as of `rusqlite` 0.10.1 / `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:
is currently SQLite 3.29.0 (as of `rusqlite` 0.20.0 / `libsqlite3-sys`
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]
version = "0.11.0"
version = "0.20.0"
features = ["bundled"]
```
* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite
@ -115,8 +132,10 @@ You can adjust this behavior in a number of ways:
* 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)
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
We use [bindgen](https://crates.io/crates/bindgen) to generate the Rust
@ -139,10 +158,8 @@ minimum SQLite version that supports your chosen features. If you are using
pregenerated bindings are chosen:
* `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_7_3` - SQLite 3.7.3 bindings
* `min_sqlite_version_3_7_4` - SQLite 3.7.4 bindings
* `min_sqlite_version_3_7_7` - SQLite 3.7.7 bindings
If you use the `bundled` feature, you will get pregenerated bindings for the
bundled version of SQLite. If you need other specific pregenerated binding

View File

@ -1,43 +1,42 @@
environment:
matrix:
- TARGET: 1.21.0-x86_64-pc-windows-gnu
- TARGET: x86_64-pc-windows-gnu
MSYS2_BITS: 64
- TARGET: 1.21.0-x86_64-pc-windows-msvc
- TARGET: x86_64-pc-windows-msvc
VCPKG_DEFAULT_TRIPLET: x64-windows
VCPKGRS_DYNAMIC: 1
- TARGET: nightly-x86_64-pc-windows-msvc
VCPKG_DEFAULT_TRIPLET: x64-windows-static
RUSTFLAGS: -Ctarget-feature=+crt-static
# - TARGET: x86_64-pc-windows-msvc
# VCPKG_DEFAULT_TRIPLET: x64-windows-static
# RUSTFLAGS: -Ctarget-feature=+crt-static
install:
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe"
- rust-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init.exe -y --default-host %TARGET%
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin
- rustc -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)
- if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-dll-win64-x64-3170000.zip -y > nul
- 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-amalgamation-3170000.zip -y > nul
- 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)
- 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)
# install vcpkg and the sqlite3 package
- if defined VCPKG_DEFAULT_TRIPLET git clone https://github.com/Microsoft/vcpkg c:\projects\vcpkg
- if defined VCPKG_DEFAULT_TRIPLET c:\projects\vcpkg\bootstrap-vcpkg.bat
- if defined VCPKG_DEFAULT_TRIPLET set VCPKG_ROOT=c:\projects\vcpkg
- if defined VCPKG_DEFAULT_TRIPLET %VCPKG_ROOT%\vcpkg.exe install sqlite3
- 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 LLVM-4.0.0-win64.exe /S
# download SQLite dll (useful only when the `bundled` feature is not set)
- appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-dll-win64-x64-3250200.zip -FileName sqlite-dll-win64-x64.zip
- if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-dll-win64-x64.zip -y > nul
# download SQLite headers (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 7z e sqlite-amalgamation.zip -y > nul
# specify where the SQLite dll has been downloaded (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 headers have been downloaded (useful only when the `bundled` feature is not set)
- if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_INCLUDE_DIR=%APPVEYOR_BUILD_FOLDER%
# install sqlite3 package
- if defined VCPKG_DEFAULT_TRIPLET vcpkg install sqlite3
build: false
test_script:
- cargo test --lib --verbose
- 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 functions 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 functions limits load_extension serde_json trace bundled buildtime_bindgen"
- cargo test --lib --features "backup blob chrono collation functions hooks limits load_extension serde_json trace"
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
cache:
- C:\Users\appveyor\.cargo

View File

@ -1,8 +1,6 @@
#![feature(test)]
extern crate test;
extern crate rusqlite;
use rusqlite::Connection;
use test::Bencher;

View File

@ -1,7 +1,8 @@
[package]
name = "libsqlite3-sys"
version = "0.9.1"
version = "0.16.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
edition = "2018"
repository = "https://github.com/jgallagher/rusqlite"
description = "Native bindings to the libsqlite3 library"
license = "MIT"
@ -13,17 +14,22 @@ categories = ["external-ffi-bindings"]
[features]
default = ["min_sqlite_version_3_6_8"]
bundled = ["cc"]
bundled-windows = ["cc"]
buildtime_bindgen = ["bindgen", "pkg-config", "vcpkg"]
sqlcipher = []
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_7_3 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_7 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
# sqlite3_unlock_notify >= 3.6.12
unlock_notify = []
# 3.13.0
preupdate_hook = []
# 3.13.0
session = ["preupdate_hook"]
[build-dependencies]
bindgen = { version = "0.31", optional = true }
bindgen = { version = "0.51", optional = true, default-features = false }
pkg-config = { version = "0.3", 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 */
pub const __GNUC_VA_LIST: i32 = 1;
pub const SQLITE_VERSION: &'static [u8; 6usize] = b"3.7.4\x00";
pub const SQLITE_VERSION_NUMBER: i32 = 3007004;
pub const SQLITE_VERSION: &'static [u8; 6usize] = b"3.7.7\x00";
pub const SQLITE_VERSION_NUMBER: i32 = 3007007;
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_ERROR: i32 = 1;
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_SHMSIZE: i32 = 4874;
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_BUSY_RECOVERY: i32 = 261;
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_READWRITE: i32 = 2;
pub const SQLITE_OPEN_CREATE: i32 = 4;
pub const SQLITE_OPEN_DELETEONCLOSE: i32 = 8;
pub const SQLITE_OPEN_EXCLUSIVE: i32 = 16;
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_TEMP_DB: i32 = 512;
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_CHUNK_SIZE: i32 = 6;
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_READWRITE: i32 = 1;
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_GETPCACHE: i32 = 15;
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_ENABLE_FKEY: i32 = 1002;
pub const SQLITE_DBCONFIG_ENABLE_TRIGGER: i32 = 1003;
pub const SQLITE_DENY: i32 = 1;
pub const SQLITE_IGNORE: i32 = 2;
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_LRU: i32 = 6;
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_PRNG_SAVE: i32 = 5;
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_PGHDRSZ: i32 = 17;
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_PAGECACHE_USED: i32 = 1;
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_SCHEMA_USED: i32 = 2;
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_SORT: i32 = 2;
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 __gnuc_va_list = __builtin_va_list;
extern "C" {
@ -519,15 +541,35 @@ pub struct sqlite3_vfs {
*mut sqlite3_int64)
->
::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]
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);
}
impl Clone for sqlite3_vfs {
fn clone(&self) -> Self { *self }
}
pub type sqlite3_syscall_ptr = ::std::option::Option<unsafe extern "C" fn()>;
extern "C" {
pub fn sqlite3_initialize() -> ::std::os::raw::c_int;
}
@ -651,6 +693,13 @@ extern "C" {
arg3: *const ::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" {
pub fn sqlite3_malloc(arg1: ::std::os::raw::c_int)
-> *mut ::std::os::raw::c_void;
@ -741,6 +790,11 @@ extern "C" {
zVfs: *const ::std::os::raw::c_char)
-> ::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" {
pub fn sqlite3_errcode(db: *mut sqlite3) -> ::std::os::raw::c_int;
}
@ -1691,10 +1745,25 @@ pub struct sqlite3_module {
zNew:
*const ::std::os::raw::c_char)
-> ::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]
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);
}
impl Clone for sqlite3_module {
@ -2006,6 +2075,23 @@ extern "C" {
zDb: *const ::std::os::raw::c_char)
-> ::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)]
#[derive(Debug, Copy)]
pub struct sqlite3_rtree_geometry {

View File

@ -1,25 +1,61 @@
use std::env;
use std::path::Path;
fn main() {
build::main();
let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("bindgen.rs");
if cfg!(feature = "sqlcipher") {
if cfg!(any(
feature = "bundled",
all(windows, feature = "bundled-windows")
)) {
println!(
"cargo:warning={}",
"Builds with bundled SQLCipher are not supported. Searching for SQLCipher to link against. \
This can lead to issues if your version of SQLCipher is not up to date!");
}
build_linked::main(&out_dir, &out_path)
} else {
// This can't be `cfg!` without always requiring our `mod build_bundled` (and
// thus `cc`)
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
{
build_bundled::main(&out_dir, &out_path)
}
#[cfg(not(any(feature = "bundled", all(windows, feature = "bundled-windows"))))]
{
build_linked::main(&out_dir, &out_path)
}
}
}
#[cfg(feature = "bundled")]
mod build {
extern crate cc;
use std::{env, fs};
#[cfg(any(feature = "bundled", all(windows, feature = "bundled-windows")))]
mod build_bundled {
use cc;
use std::env;
use std::path::Path;
pub fn main() {
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");
}
let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("bindgen.rs");
fs::copy("sqlite3/bindgen_bundled_version.rs", out_path)
.expect("Could not copy bindings to output directory");
#[cfg(feature = "buildtime_bindgen")]
{
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)
.expect("Could not copy bindings to output directory");
}
cc::Build::new()
.file("sqlite3/sqlite3.c")
let mut cfg = cc::Build::new();
cfg.file("sqlite3/sqlite3.c")
.flag("-DSQLITE_CORE")
.flag("-DSQLITE_DEFAULT_FOREIGN_KEYS=1")
.flag("-DSQLITE_ENABLE_API_ARMOR")
@ -34,59 +70,150 @@ mod build {
.flag("-DSQLITE_ENABLE_RTREE")
.flag("-DSQLITE_ENABLE_STAT2")
.flag("-DSQLITE_ENABLE_STAT4")
.flag("-DSQLITE_HAVE_ISNAN")
.flag("-DSQLITE_SOUNDEX")
.flag("-DSQLITE_THREADSAFE=1")
.flag("-DSQLITE_USE_URI")
.flag("-DHAVE_USLEEP=1")
.compile("libsqlite3.a");
.flag("-DHAVE_USLEEP=1");
// Older versions of visual studio don't support c99 (including isnan), which
// causes a build failure when the linker fails to find the `isnan`
// function. `sqlite` provides its own implmentation, using the fact
// that x != x when x is NaN.
//
// There may be other platforms that don't support `isnan`, they should be
// tested for here.
if cfg!(target_env = "msvc") {
use cc::windows_registry::{find_vs_version, VsVers};
let vs_has_nan = match find_vs_version() {
Ok(ver) => ver != VsVers::Vs12,
Err(_msg) => false,
};
if vs_has_nan {
cfg.flag("-DSQLITE_HAVE_ISNAN");
}
} else {
cfg.flag("-DSQLITE_HAVE_ISNAN");
}
if cfg!(feature = "unlock_notify") {
cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY");
}
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"))]
mod build {
extern crate pkg_config;
fn env_prefix() -> &'static str {
if cfg!(feature = "sqlcipher") {
"SQLCIPHER"
} else {
"SQLITE3"
}
}
pub enum HeaderLocation {
FromEnvironment,
Wrapper,
FromPath(String),
}
impl From<HeaderLocation> for String {
fn from(header: HeaderLocation) -> String {
match header {
HeaderLocation::FromEnvironment => {
let prefix = env_prefix();
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix)).expect(&format!(
"{}_INCLUDE_DIR must be set if {}_LIB_DIR is set",
prefix, prefix
));
header.push_str("/sqlite3.h");
header
}
HeaderLocation::Wrapper => "wrapper.h".into(),
HeaderLocation::FromPath(path) => path,
}
}
}
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 enum HeaderLocation {
FromEnvironment,
Wrapper,
FromPath(String),
}
impl From<HeaderLocation> for String {
fn from(header: HeaderLocation) -> String {
match header {
HeaderLocation::FromEnvironment => {
let prefix = env_prefix();
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix))
.expect(&format!("{}_INCLUDE_DIR must be set if {}_LIB_DIR is set", prefix, prefix));
header.push_str("/sqlite3.h");
header
}
HeaderLocation::Wrapper => "wrapper.h".into(),
HeaderLocation::FromPath(path) => path,
}
pub fn main(_out_dir: &str, out_path: &Path) {
let header = find_sqlite();
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);
}
}
pub fn main() {
let header = find_sqlite();
bindings::write_to_out_dir(header);
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.
fn find_sqlite() -> HeaderLocation {
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.
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
println!("cargo:rustc-link-lib={}", link_lib);
println!("cargo:rustc-link-search={}", dir);
// 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);
}
return HeaderLocation::FromEnvironment;
}
@ -95,7 +222,10 @@ mod build {
}
// 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) => {
if let Some(mut header) = lib.include_paths.pop() {
header.push("sqlite3.h");
@ -107,9 +237,9 @@ mod build {
Err(_) => {
// 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
// output /usr/lib explicitly, but that can introduce other linking problems; see
// https://github.com/jgallagher/rusqlite/issues/207.
println!("cargo:rustc-link-lib={}", link_lib);
// output /usr/lib explicitly, but that can introduce other linking problems;
// see https://github.com/jgallagher/rusqlite/issues/207.
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
HeaderLocation::Wrapper
}
}
@ -132,14 +262,6 @@ mod build {
None
}
fn env_prefix() -> &'static str {
if cfg!(feature = "sqlcipher") {
"SQLCIPHER"
} else {
"SQLITE3"
}
}
fn link_lib() -> &'static str {
if cfg!(feature = "sqlcipher") {
"sqlcipher"
@ -147,98 +269,98 @@ mod build {
"sqlite3"
}
}
}
#[cfg(not(feature = "buildtime_bindgen"))]
mod bindings {
use super::HeaderLocation;
#[cfg(not(feature = "buildtime_bindgen"))]
mod bindings {
use super::HeaderLocation;
use std::{env, fs};
use std::path::Path;
use std::fs;
use std::path::Path;
#[cfg_attr(rustfmt, rustfmt_skip)]
static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[
"bindgen-bindings/bindgen_3.6.8.rs",
static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[
"bindgen-bindings/bindgen_3.6.8.rs",
#[cfg(feature = "min_sqlite_version_3_6_23")]
"bindgen-bindings/bindgen_3.6.23.rs",
#[cfg(feature = "min_sqlite_version_3_7_7")]
"bindgen-bindings/bindgen_3.7.7.rs",
#[cfg(feature = "min_sqlite_version_3_7_16")]
"bindgen-bindings/bindgen_3.7.16.rs",
];
#[cfg(feature = "min_sqlite_version_3_6_11")]
"bindgen-bindings/bindgen_3.6.11.rs",
#[cfg(feature = "min_sqlite_version_3_6_23")]
"bindgen-bindings/bindgen_3.6.23.rs",
#[cfg(feature = "min_sqlite_version_3_7_3")]
"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")]
"bindgen-bindings/bindgen_3.7.16.rs",
];
pub fn write_to_out_dir(_header: HeaderLocation) {
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];
fs::copy(in_path, out_path).expect("Could not copy bindings to output directory");
}
}
#[cfg(feature = "buildtime_bindgen")]
mod bindings {
extern crate bindgen;
use self::bindgen::callbacks::{ParseCallbacks, IntKind};
use super::HeaderLocation;
use std::env;
use std::io::Write;
use std::fs::OpenOptions;
use std::path::Path;
#[derive(Debug)]
struct SqliteTypeChooser;
impl ParseCallbacks for SqliteTypeChooser {
fn int_macro(&self, _name: &str, value: i64) -> Option<IntKind> {
if value >= i32::min_value() as i64 && value <= i32::max_value() as i64 {
Some(IntKind::I32)
} else {
None
}
}
}
pub fn write_to_out_dir(header: HeaderLocation) {
let header: String = header.into();
let out_dir = env::var("OUT_DIR").unwrap();
let mut output = Vec::new();
bindgen::builder()
.header(header.clone())
.parse_callbacks(Box::new(SqliteTypeChooser))
.generate()
.expect(&format!("could not run bindgen on header {}", header))
.write(Box::new(&mut output))
.expect("could not write output of bindgen");
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
// rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it can. This flag
// was added in SQLite 3.8.3, but oring it in in prior versions of SQLite is harmless. We
// don't want to not build just because this flag is missing (e.g., if we're linking against
// SQLite 3.7.x), so append the flag manually if it isn't present in bindgen's output.
if !output.contains("pub const SQLITE_DETERMINISTIC") {
output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n");
}
let path = Path::new(&out_dir).join("bindgen.rs");
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path.clone())
.expect(&format!("Could not write to {:?}", path));
file.write_all(output.as_bytes()).expect(&format!("Could not write to {:?}", path));
}
pub fn write_to_out_dir(_header: HeaderLocation, out_path: &Path) {
let in_path = PREBUILT_BINDGEN_PATHS[PREBUILT_BINDGEN_PATHS.len() - 1];
fs::copy(in_path, out_path).expect("Could not copy bindings to output directory");
}
}
#[cfg(feature = "buildtime_bindgen")]
mod bindings {
use bindgen;
use super::HeaderLocation;
use bindgen::callbacks::{IntKind, ParseCallbacks};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
#[derive(Debug)]
struct SqliteTypeChooser;
impl ParseCallbacks for SqliteTypeChooser {
fn int_macro(&self, _name: &str, value: i64) -> Option<IntKind> {
if value >= i32::min_value() as i64 && value <= i32::max_value() as i64 {
Some(IntKind::I32)
} else {
None
}
}
}
pub fn write_to_out_dir(header: HeaderLocation, out_path: &Path) {
let header: String = header.into();
let mut output = Vec::new();
let mut bindings = bindgen::builder()
.header(header.clone())
.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()
.expect(&format!("could not run bindgen on header {}", header))
.write(Box::new(&mut output))
.expect("could not write output of bindgen");
let mut output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
// rusqlite's functions feature ors in the SQLITE_DETERMINISTIC flag when it
// can. This flag was added in SQLite 3.8.3, but oring it in in prior
// versions of SQLite is harmless. We don't want to not build just
// because this flag is missing (e.g., if we're linking against
// SQLite 3.7.x), so append the flag manually if it isn't present in bindgen's
// output.
if !output.contains("pub const SQLITE_DETERMINISTIC") {
output.push_str("\npub const SQLITE_DETERMINISTIC: i32 = 2048;\n");
}
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(out_path.clone())
.expect(&format!("Could not write to {:?}", out_path));
file.write_all(output.as_bytes())
.expect(&format!("Could not write to {:?}", out_path));
}
}

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*,
const char*,const char*),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 (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
char const**,char const**,int*,int*,int*);
@ -246,7 +246,7 @@ struct sqlite3_api_routines {
int (*uri_boolean)(const char*,const char*,int);
sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
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*);
/* Version 3.8.7 and later */
int (*auto_extension)(void(*)(void));
@ -282,6 +282,46 @@ struct sqlite3_api_routines {
/* Version 3.14.0 and later */
int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
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_set_authorizer sqlite3_api->set_authorizer
#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_table_column_metadata sqlite3_api->table_column_metadata
#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_type sqlite3_api->value_type
#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_prepare_v2 sqlite3_api->prepare_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_int64 sqlite3_api->uri_int64
#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
/* Version 3.8.7 and later */
#define sqlite3_auto_extension sqlite3_api->auto_extension
@ -540,6 +580,40 @@ typedef int (*sqlite3_loadext_entry)(
/* Version 3.14.0 and later */
#define sqlite3_trace_v2 sqlite3_api->trace_v2
#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) */
#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::fmt;
use std::os::raw::c_int;
/// Error Codes
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -64,42 +64,47 @@ pub struct Error {
impl Error {
pub fn new(result_code: c_int) -> Error {
let code = match result_code & 0xff {
super::SQLITE_INTERNAL => ErrorCode::InternalMalfunction,
super::SQLITE_PERM => ErrorCode::PermissionDenied,
super::SQLITE_ABORT => ErrorCode::OperationAborted,
super::SQLITE_BUSY => ErrorCode::DatabaseBusy,
super::SQLITE_LOCKED => ErrorCode::DatabaseLocked,
super::SQLITE_NOMEM => ErrorCode::OutOfMemory,
super::SQLITE_READONLY => ErrorCode::ReadOnly,
super::SQLITE_INTERNAL => ErrorCode::InternalMalfunction,
super::SQLITE_PERM => ErrorCode::PermissionDenied,
super::SQLITE_ABORT => ErrorCode::OperationAborted,
super::SQLITE_BUSY => ErrorCode::DatabaseBusy,
super::SQLITE_LOCKED => ErrorCode::DatabaseLocked,
super::SQLITE_NOMEM => ErrorCode::OutOfMemory,
super::SQLITE_READONLY => ErrorCode::ReadOnly,
super::SQLITE_INTERRUPT => ErrorCode::OperationInterrupted,
super::SQLITE_IOERR => ErrorCode::SystemIOFailure,
super::SQLITE_CORRUPT => ErrorCode::DatabaseCorrupt,
super::SQLITE_NOTFOUND => ErrorCode::NotFound,
super::SQLITE_FULL => ErrorCode::DiskFull,
super::SQLITE_CANTOPEN => ErrorCode::CannotOpen,
super::SQLITE_PROTOCOL => ErrorCode::FileLockingProtocolFailed,
super::SQLITE_SCHEMA => ErrorCode::SchemaChanged,
super::SQLITE_TOOBIG => ErrorCode::TooBig,
super::SQLITE_CONSTRAINT=> ErrorCode::ConstraintViolation,
super::SQLITE_MISMATCH => ErrorCode::TypeMismatch,
super::SQLITE_MISUSE => ErrorCode::APIMisuse,
super::SQLITE_NOLFS => ErrorCode::NoLargeFileSupport,
super::SQLITE_AUTH => ErrorCode::AuthorizationForStatementDenied,
super::SQLITE_RANGE => ErrorCode::ParameterOutOfRange,
super::SQLITE_NOTADB => ErrorCode::NotADatabase,
_ => ErrorCode::Unknown,
super::SQLITE_IOERR => ErrorCode::SystemIOFailure,
super::SQLITE_CORRUPT => ErrorCode::DatabaseCorrupt,
super::SQLITE_NOTFOUND => ErrorCode::NotFound,
super::SQLITE_FULL => ErrorCode::DiskFull,
super::SQLITE_CANTOPEN => ErrorCode::CannotOpen,
super::SQLITE_PROTOCOL => ErrorCode::FileLockingProtocolFailed,
super::SQLITE_SCHEMA => ErrorCode::SchemaChanged,
super::SQLITE_TOOBIG => ErrorCode::TooBig,
super::SQLITE_CONSTRAINT => ErrorCode::ConstraintViolation,
super::SQLITE_MISMATCH => ErrorCode::TypeMismatch,
super::SQLITE_MISUSE => ErrorCode::APIMisuse,
super::SQLITE_NOLFS => ErrorCode::NoLargeFileSupport,
super::SQLITE_AUTH => ErrorCode::AuthorizationForStatementDenied,
super::SQLITE_RANGE => ErrorCode::ParameterOutOfRange,
super::SQLITE_NOTADB => ErrorCode::NotADatabase,
_ => ErrorCode::Unknown,
};
Error {
code: code,
code,
extended_code: result_code,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Error code {}: {}", self.extended_code, code_to_str(self.extended_code))
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Error code {}: {}",
self.extended_code,
code_to_str(self.extended_code)
)
}
}
@ -110,52 +115,53 @@ impl error::Error for Error {
}
// Result codes.
// Note: These are not public because our bindgen bindings export whichever constants are present
// in the current version of SQLite. We repeat them here 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.
// Note: These are not public because our bindgen bindings export whichever
// constants are present in the current version of SQLite. We repeat them here
// 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_WARNING : c_int = 28;
const SQLITE_NOTICE: c_int = 27;
const SQLITE_WARNING: c_int = 28;
// Extended result codes.
const SQLITE_IOERR_SHMOPEN : c_int = (super::SQLITE_IOERR | (18<<8));
const SQLITE_IOERR_SHMSIZE : c_int = (super::SQLITE_IOERR | (19<<8));
const SQLITE_IOERR_SHMLOCK : c_int = (super::SQLITE_IOERR | (20<<8));
const SQLITE_IOERR_SHMMAP : c_int = (super::SQLITE_IOERR | (21<<8));
const SQLITE_IOERR_SEEK : c_int = (super::SQLITE_IOERR | (22<<8));
const SQLITE_IOERR_DELETE_NOENT : c_int = (super::SQLITE_IOERR | (23<<8));
const SQLITE_IOERR_MMAP : c_int = (super::SQLITE_IOERR | (24<<8));
const SQLITE_IOERR_GETTEMPPATH : c_int = (super::SQLITE_IOERR | (25<<8));
const SQLITE_IOERR_CONVPATH : c_int = (super::SQLITE_IOERR | (26<<8));
const SQLITE_IOERR_VNODE : c_int = (super::SQLITE_IOERR | (27<<8));
const SQLITE_LOCKED_SHAREDCACHE : c_int = (super::SQLITE_LOCKED | (1<<8));
const SQLITE_BUSY_RECOVERY : c_int = (super::SQLITE_BUSY | (1<<8));
const SQLITE_BUSY_SNAPSHOT : c_int = (super::SQLITE_BUSY | (2<<8));
const SQLITE_CANTOPEN_NOTEMPDIR : c_int = (super::SQLITE_CANTOPEN | (1<<8));
const SQLITE_CANTOPEN_ISDIR : c_int = (super::SQLITE_CANTOPEN | (2<<8));
const SQLITE_CANTOPEN_FULLPATH : c_int = (super::SQLITE_CANTOPEN | (3<<8));
const SQLITE_CANTOPEN_CONVPATH : c_int = (super::SQLITE_CANTOPEN | (4<<8));
const SQLITE_CORRUPT_VTAB : c_int = (super::SQLITE_CORRUPT | (1<<8));
const SQLITE_READONLY_RECOVERY : c_int = (super::SQLITE_READONLY | (1<<8));
const SQLITE_READONLY_CANTLOCK : c_int = (super::SQLITE_READONLY | (2<<8));
const SQLITE_READONLY_ROLLBACK : c_int = (super::SQLITE_READONLY | (3<<8));
const SQLITE_READONLY_DBMOVED : c_int = (super::SQLITE_READONLY | (4<<8));
const SQLITE_ABORT_ROLLBACK : c_int = (super::SQLITE_ABORT | (2<<8));
const SQLITE_CONSTRAINT_CHECK : c_int = (super::SQLITE_CONSTRAINT | (1<<8));
const SQLITE_CONSTRAINT_COMMITHOOK : c_int = (super::SQLITE_CONSTRAINT | (2<<8));
const SQLITE_CONSTRAINT_FOREIGNKEY : c_int = (super::SQLITE_CONSTRAINT | (3<<8));
const SQLITE_CONSTRAINT_FUNCTION : c_int = (super::SQLITE_CONSTRAINT | (4<<8));
const SQLITE_CONSTRAINT_NOTNULL : c_int = (super::SQLITE_CONSTRAINT | (5<<8));
const SQLITE_CONSTRAINT_PRIMARYKEY : c_int = (super::SQLITE_CONSTRAINT | (6<<8));
const SQLITE_CONSTRAINT_TRIGGER : c_int = (super::SQLITE_CONSTRAINT | (7<<8));
const SQLITE_CONSTRAINT_UNIQUE : c_int = (super::SQLITE_CONSTRAINT | (8<<8));
const SQLITE_CONSTRAINT_VTAB : c_int = (super::SQLITE_CONSTRAINT | (9<<8));
const SQLITE_CONSTRAINT_ROWID : c_int = (super::SQLITE_CONSTRAINT |(10<<8));
const SQLITE_NOTICE_RECOVER_WAL : c_int = (SQLITE_NOTICE | (1<<8));
const SQLITE_NOTICE_RECOVER_ROLLBACK : c_int = (SQLITE_NOTICE | (2<<8));
const SQLITE_WARNING_AUTOINDEX : c_int = (SQLITE_WARNING | (1<<8));
const SQLITE_AUTH_USER : c_int = (super::SQLITE_AUTH | (1<<8));
const SQLITE_IOERR_SHMOPEN: c_int = (super::SQLITE_IOERR | (18 << 8));
const SQLITE_IOERR_SHMSIZE: c_int = (super::SQLITE_IOERR | (19 << 8));
const SQLITE_IOERR_SHMLOCK: c_int = (super::SQLITE_IOERR | (20 << 8));
const SQLITE_IOERR_SHMMAP: c_int = (super::SQLITE_IOERR | (21 << 8));
const SQLITE_IOERR_SEEK: c_int = (super::SQLITE_IOERR | (22 << 8));
const SQLITE_IOERR_DELETE_NOENT: c_int = (super::SQLITE_IOERR | (23 << 8));
const SQLITE_IOERR_MMAP: c_int = (super::SQLITE_IOERR | (24 << 8));
const SQLITE_IOERR_GETTEMPPATH: c_int = (super::SQLITE_IOERR | (25 << 8));
const SQLITE_IOERR_CONVPATH: c_int = (super::SQLITE_IOERR | (26 << 8));
const SQLITE_IOERR_VNODE: c_int = (super::SQLITE_IOERR | (27 << 8));
const SQLITE_LOCKED_SHAREDCACHE: c_int = (super::SQLITE_LOCKED | (1 << 8));
const SQLITE_BUSY_RECOVERY: c_int = (super::SQLITE_BUSY | (1 << 8));
const SQLITE_BUSY_SNAPSHOT: c_int = (super::SQLITE_BUSY | (2 << 8));
const SQLITE_CANTOPEN_NOTEMPDIR: c_int = (super::SQLITE_CANTOPEN | (1 << 8));
const SQLITE_CANTOPEN_ISDIR: c_int = (super::SQLITE_CANTOPEN | (2 << 8));
const SQLITE_CANTOPEN_FULLPATH: c_int = (super::SQLITE_CANTOPEN | (3 << 8));
const SQLITE_CANTOPEN_CONVPATH: c_int = (super::SQLITE_CANTOPEN | (4 << 8));
const SQLITE_CORRUPT_VTAB: c_int = (super::SQLITE_CORRUPT | (1 << 8));
const SQLITE_READONLY_RECOVERY: c_int = (super::SQLITE_READONLY | (1 << 8));
const SQLITE_READONLY_CANTLOCK: c_int = (super::SQLITE_READONLY | (2 << 8));
const SQLITE_READONLY_ROLLBACK: c_int = (super::SQLITE_READONLY | (3 << 8));
const SQLITE_READONLY_DBMOVED: c_int = (super::SQLITE_READONLY | (4 << 8));
const SQLITE_ABORT_ROLLBACK: c_int = (super::SQLITE_ABORT | (2 << 8));
const SQLITE_CONSTRAINT_CHECK: c_int = (super::SQLITE_CONSTRAINT | (1 << 8));
const SQLITE_CONSTRAINT_COMMITHOOK: c_int = (super::SQLITE_CONSTRAINT | (2 << 8));
const SQLITE_CONSTRAINT_FOREIGNKEY: c_int = (super::SQLITE_CONSTRAINT | (3 << 8));
const SQLITE_CONSTRAINT_FUNCTION: c_int = (super::SQLITE_CONSTRAINT | (4 << 8));
const SQLITE_CONSTRAINT_NOTNULL: c_int = (super::SQLITE_CONSTRAINT | (5 << 8));
const SQLITE_CONSTRAINT_PRIMARYKEY: c_int = (super::SQLITE_CONSTRAINT | (6 << 8));
const SQLITE_CONSTRAINT_TRIGGER: c_int = (super::SQLITE_CONSTRAINT | (7 << 8));
const SQLITE_CONSTRAINT_UNIQUE: c_int = (super::SQLITE_CONSTRAINT | (8 << 8));
const SQLITE_CONSTRAINT_VTAB: c_int = (super::SQLITE_CONSTRAINT | (9 << 8));
const SQLITE_CONSTRAINT_ROWID: c_int = (super::SQLITE_CONSTRAINT | (10 << 8));
const SQLITE_NOTICE_RECOVER_WAL: c_int = (SQLITE_NOTICE | (1 << 8));
const SQLITE_NOTICE_RECOVER_ROLLBACK: c_int = (SQLITE_NOTICE | (2 << 8));
const SQLITE_WARNING_AUTOINDEX: c_int = (SQLITE_WARNING | (1 << 8));
const SQLITE_AUTH_USER: c_int = (super::SQLITE_AUTH | (1 << 8));
pub fn code_to_str(code: c_int) -> &'static str {
match code {

View File

@ -2,6 +2,7 @@
pub use self::error::*;
use std::default::Default;
use std::mem;
mod error;
@ -15,33 +16,52 @@ pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type {
}
/// Run-Time Limit Categories
#[repr(C)]
#[repr(i32)]
pub enum Limit {
/// 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.
SQLITE_LIMIT_SQL_LENGTH = SQLITE_LIMIT_SQL_LENGTH as isize,
/// The maximum number of columns in a table definition or in the result set of a SELECT
/// or the maximum number of columns in an index or in an ORDER BY or GROUP BY clause.
SQLITE_LIMIT_COLUMN = SQLITE_LIMIT_COLUMN 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 or the maximum number of columns in an index or in an
/// ORDER BY or GROUP BY clause.
SQLITE_LIMIT_COLUMN = SQLITE_LIMIT_COLUMN,
/// 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.
SQLITE_LIMIT_COMPOUND_SELECT = SQLITE_LIMIT_COMPOUND_SELECT as isize,
/// The maximum number of instructions in a virtual machine program used to implement an SQL statement.
SQLITE_LIMIT_VDBE_OP = SQLITE_LIMIT_VDBE_OP 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.
SQLITE_LIMIT_VDBE_OP = SQLITE_LIMIT_VDBE_OP,
/// 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.
SQLITE_LIMIT_ATTACHED = SQLITE_LIMIT_ATTACHED as isize,
/// The maximum length of the pattern argument to the LIKE or GLOB operators.
SQLITE_LIMIT_LIKE_PATTERN_LENGTH = SQLITE_LIMIT_LIKE_PATTERN_LENGTH as isize,
SQLITE_LIMIT_ATTACHED = SQLITE_LIMIT_ATTACHED,
/// The maximum length of the pattern argument to the LIKE or GLOB
/// operators.
SQLITE_LIMIT_LIKE_PATTERN_LENGTH = SQLITE_LIMIT_LIKE_PATTERN_LENGTH,
/// 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.
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,
}
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::time;
//!
//! fn backup_db<P: AsRef<Path>>(src: &Connection, dst: P, progress: fn(backup::Progress))
//! -> Result<()> {
//! let mut dst = try!(Connection::open(dst));
//! let backup = try!(backup::Backup::new(src, &mut dst));
//! fn backup_db<P: AsRef<Path>>(
//! src: &Connection,
//! dst: P,
//! 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))
//! }
//! ```
@ -34,10 +37,10 @@ use std::os::raw::c_int;
use std::thread;
use std::time::Duration;
use ffi;
use crate::ffi;
use {DatabaseName, Connection, Result};
use error::{error_from_sqlite_code, error_from_handle};
use crate::error::{error_from_handle, error_from_sqlite_code};
use crate::{Connection, DatabaseName, Result};
impl Connection {
/// 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
/// or if the backup fails.
pub fn backup<P: AsRef<Path>>(&self,
name: DatabaseName,
dst_path: P,
progress: Option<fn(Progress)>)
-> Result<()> {
use self::StepResult::{More, Done, Busy, Locked};
let mut dst = try!(Connection::open(dst_path));
let backup = try!(Backup::new_with_names(self, name, &mut dst, DatabaseName::Main));
pub fn backup<P: AsRef<Path>>(
&self,
name: DatabaseName<'_>,
dst_path: P,
progress: Option<fn(Progress)>,
) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More};
let mut dst = Connection::open(dst_path)?;
let backup = Backup::new_with_names(self, name, &mut dst, DatabaseName::Main)?;
let mut r = More;
while r == More {
r = try!(backup.step(100));
r = backup.step(100)?;
if let Some(f) = progress {
f(backup.progress());
}
@ -89,20 +93,21 @@ impl Connection {
///
/// Will return `Err` if the destination path cannot be opened
/// or if the restore fails.
pub fn restore<P: AsRef<Path>>(&mut self,
name: DatabaseName,
src_path: P,
progress: Option<fn(Progress)>)
-> Result<()> {
use self::StepResult::{More, Done, Busy, Locked};
let src = try!(Connection::open(src_path));
let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name));
pub fn restore<P: AsRef<Path>, F: Fn(Progress)>(
&mut self,
name: DatabaseName<'_>,
src_path: P,
progress: Option<F>,
) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More};
let src = Connection::open(src_path)?;
let restore = Backup::new_with_names(&src, DatabaseName::Main, self, name)?;
let mut r = More;
let mut busy_count = 0i32;
'restore_loop: while r == More || r == Busy {
r = try!(restore.step(100));
if let Some(f) = progress {
r = restore.step(100)?;
if let Some(ref f) = progress {
f(restore.progress());
}
if r == Busy {
@ -124,12 +129,13 @@ impl Connection {
}
/// Possible successful results of calling `Backup::step`.
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum StepResult {
/// The backup is complete.
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,
/// The step failed because appropriate locks could not be aquired. This is
@ -146,7 +152,7 @@ pub enum StepResult {
/// backup is as of the last call to `step` - if the source database is
/// modified after a call to `step`, the progress value will become outdated
/// and potentially incorrect.
#[derive(Copy,Clone,Debug)]
#[derive(Copy, Clone, Debug)]
pub struct Progress {
/// Number of pages in the source database that still need to be backed up.
pub remaining: c_int,
@ -161,7 +167,7 @@ pub struct Backup<'a, 'b> {
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
/// `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
@ -171,7 +177,7 @@ impl<'a, 'b> Backup<'a, 'b> {
///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `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)
}
@ -184,21 +190,24 @@ impl<'a, 'b> Backup<'a, 'b> {
///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `NULL`.
pub fn new_with_names(from: &'a Connection,
from_name: DatabaseName,
to: &'b mut Connection,
to_name: DatabaseName)
-> Result<Backup<'a, 'b>> {
let to_name = try!(to_name.to_cstring());
let from_name = try!(from_name.to_cstring());
pub fn new_with_names<'a, 'b>(
from: &'a Connection,
from_name: DatabaseName<'_>,
to: &'b mut Connection,
to_name: DatabaseName<'_>,
) -> Result<Backup<'a, 'b>> {
let to_name = to_name.to_cstring()?;
let from_name = from_name.to_cstring()?;
let to_db = to.db.borrow_mut().db;
let b = unsafe {
let b = ffi::sqlite3_backup_init(to_db,
to_name.as_ptr(),
from.db.borrow_mut().db,
from_name.as_ptr());
let b = ffi::sqlite3_backup_init(
to_db,
to_name.as_ptr(),
from.db.borrow_mut().db,
from_name.as_ptr(),
);
if b.is_null() {
return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
}
@ -206,10 +215,10 @@ impl<'a, 'b> Backup<'a, 'b> {
};
Ok(Backup {
phantom_from: PhantomData,
phantom_to: PhantomData,
b: b,
})
phantom_from: PhantomData,
phantom_to: PhantomData,
b,
})
}
/// Gets the progress of the backup as of the last call to `step`.
@ -235,7 +244,7 @@ impl<'a, 'b> Backup<'a, 'b> {
/// `LOCKED` are transient errors and are therefore returned as possible
/// `Ok` values.
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) };
match rc {
@ -262,17 +271,18 @@ impl<'a, 'b> Backup<'a, 'b> {
/// # Failure
///
/// Will return `Err` if any of the calls to `step` return `Err`.
pub fn run_to_completion(&self,
pages_per_step: c_int,
pause_between_pages: Duration,
progress: Option<fn(Progress)>)
-> Result<()> {
use self::StepResult::{Done, More, Busy, Locked};
pub fn run_to_completion(
&self,
pages_per_step: c_int,
pause_between_pages: Duration,
progress: Option<fn(Progress)>,
) -> Result<()> {
use self::StepResult::{Busy, Done, Locked, More};
assert!(pages_per_step > 0, "pages_per_step must be positive");
loop {
let r = try!(self.step(pages_per_step));
let r = self.step(pages_per_step)?;
if let Some(progress) = 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) {
unsafe { ffi::sqlite3_backup_finish(self.b) };
}
@ -292,12 +302,11 @@ impl<'a, 'b> Drop for Backup<'a, 'b> {
#[cfg(test)]
mod test {
use {Connection, DatabaseName};
use std::time::Duration;
use super::Backup;
use crate::{Connection, DatabaseName, NO_PARAMS};
use std::time::Duration;
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup() {
let src = Connection::open_in_memory().unwrap();
let sql = "BEGIN;
@ -313,22 +322,27 @@ mod test {
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);
src.execute_batch("INSERT INTO foo VALUES(43)").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);
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup_temp() {
let src = Connection::open_in_memory().unwrap();
let sql = "BEGIN;
@ -340,34 +354,35 @@ mod test {
let mut dst = Connection::open_in_memory().unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Temp,
&mut dst,
DatabaseName::Main)
.unwrap();
let backup =
Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
.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);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Temp,
&mut dst,
DatabaseName::Main)
.unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
let backup =
Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
.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);
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_backup_attached() {
let src = Connection::open_in_memory().unwrap();
let sql = "ATTACH DATABASE ':memory:' AS my_attached;
@ -380,29 +395,39 @@ mod test {
let mut dst = Connection::open_in_memory().unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main)
.unwrap();
let backup = Backup::new_with_names(
&src,
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main,
)
.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);
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
{
let backup = Backup::new_with_names(&src,
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main)
.unwrap();
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
let backup = Backup::new_with_names(
&src,
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main,
)
.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);
}
}

View File

@ -1,61 +1,64 @@
//! Incremental BLOB I/O.
//!
//! Note that SQLite does not provide API-level access to change the size of a BLOB; that must
//! be performed through SQL statements.
//! Note that SQLite does not provide API-level access to change the size of a
//! BLOB; that must be performed through SQL statements.
//!
//! `Blob` conforms to `std::io::Read`, `std::io::Write`, and `std::io::Seek`, so it plays
//! nicely with other types that build on these (such as `std::io::BufReader` and
//! `std::io::BufWriter`). However, you must be careful with the size of the blob. For example,
//! when using a `BufWriter`, the `BufWriter` will accept more data than the `Blob` will allow,
//! so make sure to call `flush` and check for errors. (See the unit tests in this module for
//! an example.)
//! `Blob` conforms to `std::io::Read`, `std::io::Write`, and `std::io::Seek`,
//! so it plays nicely with other types that build on these (such as
//! `std::io::BufReader` and `std::io::BufWriter`). However, you must be
//! careful with the size of the blob. For example, when using a `BufWriter`,
//! the `BufWriter` will accept more data than the `Blob`
//! will allow, so make sure to call `flush` and check for errors. (See the
//! unit tests in this module for an example.)
//!
//! ## Example
//!
//! ```rust
//! extern crate libsqlite3_sys;
//! extern crate rusqlite;
//!
//! use rusqlite::{Connection, DatabaseName};
//! 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() {
//! let db = Connection::open_in_memory().unwrap();
//! db.execute_batch("CREATE TABLE test (content BLOB);").unwrap();
//! db.execute("INSERT INTO test (content) VALUES (ZEROBLOB(10))", &[]).unwrap();
//! fn main() -> Result<(), Box<Error>> {
//! let db = Connection::open_in_memory()?;
//! db.execute_batch("CREATE TABLE test (content BLOB);")?;
//! db.execute(
//! "INSERT INTO test (content) VALUES (ZEROBLOB(10))",
//! NO_PARAMS,
//! )?;
//!
//! 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;
//! // if you try to write too much, the data will be truncated to the size of the BLOB.
//! let bytes_written = blob.write(b"01234567").unwrap();
//! // if you try to write too much, the data will be truncated to the size of the
//! // BLOB.
//! let bytes_written = blob.write(b"01234567")?;
//! assert_eq!(bytes_written, 8);
//!
//! // 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 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
//!
//! 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
//! let rowid = db.last_insert_rowid();
//! blob.reopen(rowid).unwrap();
//! blob.reopen(rowid)?;
//!
//! assert_eq!(blob.size(), 64);
//! Ok(())
//! }
//! ```
use std::io;
use std::cmp::min;
use std::mem;
use std::io;
use std::ptr;
use super::ffi;
use super::types::{ToSql, ToSqlOutput};
use {Result, Connection, DatabaseName};
use crate::{Connection, DatabaseName, Result};
/// Handle to an open BLOB.
pub struct Blob<'conn> {
@ -65,45 +68,47 @@ pub struct Blob<'conn> {
}
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
///
/// Will return `Err` if `db`/`table`/`column` cannot be converted to a C-compatible string
/// or if the underlying SQLite BLOB open call fails.
pub fn blob_open<'a>(&'a self,
db: DatabaseName,
table: &str,
column: &str,
row: i64,
read_only: bool)
-> Result<Blob<'a>> {
/// Will return `Err` if `db`/`table`/`column` cannot be converted to a
/// C-compatible string or if the underlying SQLite BLOB open call
/// fails.
pub fn blob_open<'a>(
&'a self,
db: DatabaseName<'_>,
table: &str,
column: &str,
row_id: i64,
read_only: bool,
) -> Result<Blob<'a>> {
let mut c = self.db.borrow_mut();
let mut blob = ptr::null_mut();
let db = try!(db.to_cstring());
let table = try!(super::str_to_cstring(table));
let column = try!(super::str_to_cstring(column));
let db = db.to_cstring()?;
let table = super::str_to_cstring(table)?;
let column = super::str_to_cstring(column)?;
let rc = unsafe {
ffi::sqlite3_blob_open(c.db(),
db.as_ptr(),
table.as_ptr(),
column.as_ptr(),
row,
if read_only { 0 } else { 1 },
&mut blob)
ffi::sqlite3_blob_open(
c.db(),
db.as_ptr(),
table.as_ptr(),
column.as_ptr(),
row_id,
if read_only { 0 } else { 1 },
&mut blob,
)
};
c.decode_result(rc)
.map(|_| {
Blob {
conn: self,
blob: blob,
pos: 0,
}
})
c.decode_result(rc).map(|_| Blob {
conn: self,
blob,
pos: 0,
})
}
}
impl<'conn> Blob<'conn> {
impl Blob<'_> {
/// Move a BLOB handle to a new row.
///
/// # Failure
@ -125,8 +130,9 @@ impl<'conn> Blob<'conn> {
/// Close a BLOB handle.
///
/// Calling `close` explicitly is not required (the BLOB will be closed when the
/// `Blob` is dropped), but it is available so you can get any errors that occur.
/// Calling `close` explicitly is not required (the BLOB will be closed
/// when the `Blob` is dropped), but it is available so you can get any
/// errors that occur.
///
/// # Failure
///
@ -142,9 +148,9 @@ impl<'conn> Blob<'conn> {
}
}
impl<'conn> io::Read for Blob<'conn> {
/// Read data from a BLOB incrementally. Will return Ok(0) if the end of the blob
/// has been reached.
impl io::Read for Blob<'_> {
/// Read data from a BLOB incrementally. Will return Ok(0) if the end of
/// the blob has been reached.
///
/// # Failure
///
@ -155,25 +161,25 @@ impl<'conn> io::Read for Blob<'conn> {
if n <= 0 {
return Ok(0);
}
let rc =
unsafe { ffi::sqlite3_blob_read(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) };
let rc = unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
self.conn
.decode_result(rc)
.map(|_| {
self.pos += n;
n as usize
})
self.pos += n;
n as usize
})
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
}
}
impl<'conn> io::Write for Blob<'conn> {
/// Write data into a BLOB incrementally. Will return `Ok(0)` if the end of the blob
/// has been reached; consider using `Write::write_all(buf)` if you want to get an
/// error if the entirety of the buffer cannot be written.
impl io::Write for Blob<'_> {
/// Write data into a BLOB incrementally. Will return `Ok(0)` if the end of
/// the blob has been reached; consider using `Write::write_all(buf)`
/// 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
/// the size of a BLOB using this API.
/// This function may only modify the contents of the BLOB; it is not
/// possible to increase the size of a BLOB using this API.
///
/// # Failure
///
@ -184,15 +190,13 @@ impl<'conn> io::Write for Blob<'conn> {
if n <= 0 {
return Ok(0);
}
let rc = unsafe {
ffi::sqlite3_blob_write(self.blob, mem::transmute(buf.as_ptr()), n, self.pos)
};
let rc = unsafe { ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
self.conn
.decode_result(rc)
.map(|_| {
self.pos += n;
n as usize
})
self.pos += n;
n as usize
})
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
}
@ -201,21 +205,25 @@ impl<'conn> io::Write for Blob<'conn> {
}
}
impl<'conn> io::Seek for Blob<'conn> {
impl io::Seek for Blob<'_> {
/// Seek to an offset, in bytes, in BLOB.
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let pos = match pos {
io::SeekFrom::Start(offset) => offset as i64,
io::SeekFrom::Current(offset) => self.pos as i64 + offset,
io::SeekFrom::End(offset) => self.size() as i64 + offset,
io::SeekFrom::Current(offset) => i64::from(self.pos) + offset,
io::SeekFrom::End(offset) => i64::from(self.size()) + offset,
};
if pos < 0 {
Err(io::Error::new(io::ErrorKind::InvalidInput,
"invalid seek to negative position"))
} else if pos > self.size() as i64 {
Err(io::Error::new(io::ErrorKind::InvalidInput,
"invalid seek to position past end of blob"))
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid seek to negative position",
))
} else if pos > i64::from(self.size()) {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid seek to position past end of blob",
))
} else {
self.pos = pos as i32;
Ok(pos as u64)
@ -224,7 +232,7 @@ impl<'conn> io::Seek for Blob<'conn> {
}
#[allow(unused_must_use)]
impl<'conn> Drop for Blob<'conn> {
impl Drop for Blob<'_> {
fn drop(&mut self) {
self.close_();
}
@ -232,15 +240,15 @@ impl<'conn> Drop for Blob<'conn> {
/// BLOB of length N that is filled with zeroes.
///
/// Zeroblobs are intended to serve as placeholders for BLOBs whose content is later written using
/// incremental BLOB I/O routines.
/// Zeroblobs are intended to serve as placeholders for BLOBs whose content is
/// later written using incremental BLOB I/O routines.
///
/// A negative value for the zeroblob results in a zero-length BLOB.
#[derive(Copy,Clone)]
#[derive(Copy, Clone)]
pub struct ZeroBlob(pub i32);
impl ToSql for ZeroBlob {
fn to_sql(&self) -> Result<ToSqlOutput> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let ZeroBlob(length) = *self;
Ok(ToSqlOutput::ZeroBlob(length))
}
@ -248,17 +256,16 @@ impl ToSql for ZeroBlob {
#[cfg(test)]
mod test {
use std::io::{BufReader, BufRead, BufWriter, Read, Write, Seek, SeekFrom};
use {Connection, DatabaseName, Result};
use crate::{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)> {
let db = try!(Connection::open_in_memory());
let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE test (content BLOB);
INSERT INTO test VALUES (ZEROBLOB(10));
END;";
try!(db.execute_batch(sql));
db.execute_batch(sql)?;
let rowid = db.last_insert_rowid();
Ok((db, rowid))
}
@ -267,7 +274,8 @@ mod test {
fn test_blob() {
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();
assert_eq!(4, blob.write(b"Clob").unwrap());
assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10
@ -276,7 +284,8 @@ mod test {
blob.reopen(rowid).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();
let mut bytes = [0u8; 5];
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
@ -317,7 +326,8 @@ mod test {
fn test_blob_in_bufreader() {
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();
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 blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)
let blob = db
.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap();
let mut writer = BufWriter::new(blob);
@ -354,7 +365,8 @@ mod test {
{
// ... 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();
let mut bytes = [0u8; 10];
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();
let mut writer = BufWriter::new(blob);
@ -373,7 +386,8 @@ mod test {
{
// ... 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();
let mut bytes = [0u8; 10];
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.
use crate::raw_statement::RawStatement;
use crate::{Connection, Result, Statement};
use lru_cache::LruCache;
use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
use lru_cache::LruCache;
use {Result, Connection, Statement};
use raw_statement::RawStatement;
use statement::StatementCrateImpl;
impl Connection {
/// Prepare a SQL statement for execution, returning a previously prepared (but
/// not currently in-use) statement if one is available. The returned statement
/// will be cached for reuse by future calls to `prepare_cached` once it is
/// dropped.
/// Prepare a SQL statement for execution, returning a previously prepared
/// (but not currently in-use) statement if one is available. The
/// returned statement will be cached for reuse by future calls to
/// `prepare_cached` once it is dropped.
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn insert_new_people(conn: &Connection) -> Result<()> {
/// {
/// let mut stmt = try!(conn.prepare_cached("INSERT INTO People (name) VALUES (?)"));
/// try!(stmt.execute(&[&"Joe Smith"]));
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
/// stmt.execute(&["Joe Smith"])?;
/// }
/// {
/// // This will return the same underlying SQLite statement handle without
/// // having to prepare it again.
/// let mut stmt = try!(conn.prepare_cached("INSERT INTO People (name) VALUES (?)"));
/// try!(stmt.execute(&[&"Bob Jones"]));
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
/// stmt.execute(&["Bob Jones"])?;
/// }
/// Ok(())
/// }
@ -32,20 +31,22 @@ impl Connection {
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn prepare_cached<'a>(&'a self, sql: &str) -> Result<CachedStatement<'a>> {
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>> {
self.cache.get(self, sql)
}
/// Set the maximum number of cached prepared statements this connection will hold.
/// By default, a connection will hold a relatively small number of cached statements.
/// If you need more, or know that you will not use cached statements, you can set
/// the capacity manually using this method.
/// Set the maximum number of cached prepared statements this connection
/// will hold. By default, a connection will hold a relatively small
/// number of cached statements. If you need more, or know that you
/// will not use cached statements, you
/// can set the capacity manually using this method.
pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) {
self.cache.set_capacity(capacity)
}
/// Remove/finalize all prepared statements currently in the cache.
pub fn flush_prepared_statement_cache(&self) {
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)]
fn drop(&mut self) {
if let Some(stmt) = self.stmt.take() {
@ -87,16 +88,16 @@ impl<'conn> Drop for CachedStatement<'conn> {
}
}
impl<'conn> CachedStatement<'conn> {
fn new(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
impl CachedStatement<'_> {
fn new<'conn>(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
CachedStatement {
stmt: Some(stmt),
cache: cache,
cache,
}
}
/// Discard the statement, preventing it from being returned to its `Connection`'s collection
/// of cached statements.
/// Discard the statement, preventing it from being returned to its
/// `Connection`'s collection of cached statements.
pub fn discard(mut self) {
self.stmt = None;
}
@ -117,14 +118,15 @@ impl StatementCache {
//
// # Failure
//
// Will return `Err` if no cached statement can be found and the underlying SQLite prepare
// call fails.
fn get<'conn>(&'conn self,
conn: &'conn Connection,
sql: &str)
-> Result<CachedStatement<'conn>> {
// Will return `Err` if no cached statement can be found and the underlying
// SQLite prepare call fails.
fn get<'conn>(
&'conn self,
conn: &'conn Connection,
sql: &str,
) -> Result<CachedStatement<'conn>> {
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)),
None => conn.prepare(sql),
};
@ -135,7 +137,9 @@ impl StatementCache {
fn cache_stmt(&self, stmt: RawStatement) {
let mut cache = self.0.borrow_mut();
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);
}
@ -147,8 +151,9 @@ impl StatementCache {
#[cfg(test)]
mod test {
use Connection;
use super::StatementCache;
use crate::{Connection, NO_PARAMS};
use fallible_iterator::FallibleIterator;
impl StatementCache {
fn clear(&self) {
@ -176,14 +181,20 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
@ -201,7 +212,10 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
@ -211,7 +225,10 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
@ -219,7 +236,10 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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());
}
@ -233,7 +253,10 @@ mod test {
{
let mut stmt = db.prepare_cached(sql).unwrap();
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();
}
assert_eq!(0, cache.len());
@ -242,47 +265,78 @@ mod test {
#[test]
fn test_ddl() {
let db = Connection::open_in_memory().unwrap();
db.execute_batch(r#"
db.execute_batch(
r#"
CREATE TABLE foo (x INT);
INSERT INTO foo VALUES (1);
"#)
.unwrap();
"#,
)
.unwrap();
let sql = "SELECT * FROM foo";
{
let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(1i32,
stmt.query_map::<i32, _>(&[], |r| r.get(0))
.unwrap()
.next()
.unwrap()
.unwrap());
assert_eq!(
Ok(Some(1i32)),
stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).next()
);
}
db.execute_batch(r#"
db.execute_batch(
r#"
ALTER TABLE foo ADD COLUMN y INT;
UPDATE foo SET y = 2;
"#)
.unwrap();
"#,
)
.unwrap();
{
let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!((1i32, 2i32),
stmt.query_map(&[], |r| (r.get(0), r.get(1)))
.unwrap()
.next()
.unwrap()
.unwrap());
assert_eq!(
Ok(Some((1i32, 2i32))),
stmt.query(NO_PARAMS)
.unwrap()
.map(|r| Ok((r.get(0)?, r.get(1)?)))
.next()
);
}
}
#[test]
fn test_connection_close() {
let conn = Connection::open_in_memory().unwrap();
conn.prepare_cached("SELECT * FROM sqlite_master;")
.unwrap();
conn.prepare_cached("SELECT * FROM sqlite_master;").unwrap();
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::fmt;
use std::os::raw::c_int;
use std::path::PathBuf;
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.
#[derive(Debug)]
#[allow(enum_variant_names)]
#[allow(clippy::enum_variant_names)]
pub enum Error {
/// An error from an underlying SQLite call.
SqliteFailure(ffi::Error, Option<String>),
/// Error reported when attempting to open a connection when SQLite was configured to
/// allow single-threaded use only.
/// Error reported when attempting to open a connection when SQLite was
/// configured to allow single-threaded use only.
SqliteSingleThreadedMode,
/// Error when the value of a particular column is requested, but it cannot be converted to
/// the requested Rust type.
FromSqlConversionFailure(usize, Type, Box<error::Error + Send + Sync>),
/// Error when the value of a particular column is requested, but it cannot
/// be converted to the requested Rust type.
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.,
/// trying to get the value 1000 into a `u8`). The associated `c_int` is the column index, and
/// the associated `i64` is the value returned by SQLite.
IntegralValueOutOfRange(c_int, i64),
/// Error when SQLite gives us an integral value outside the range of the
/// requested type (e.g., trying to get the value 1000 into a `u8`).
/// The associated `usize` is the column index,
/// and the associated `i64` is the value returned by SQLite.
IntegralValueOutOfRange(usize, i64),
/// Error converting a string to UTF-8.
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),
/// 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),
/// Error converting a file path to a string.
@ -45,38 +45,106 @@ pub enum Error {
/// Error returned when an `execute` call returns rows.
ExecuteReturnedResults,
/// Error when a query that was expected to return at least one row (e.g., for `query_row`)
/// did not return any.
/// Error when a query that was expected to return at least one row (e.g.,
/// for `query_row`) did not return any.
QueryReturnedNoRows,
/// Error when the value of a particular column is requested, but the index is out of range
/// for the statement.
InvalidColumnIndex(c_int),
/// Error when the value of a particular column is requested, but the index
/// is out of range for the statement.
InvalidColumnIndex(usize),
/// Error when the value of a named column is requested, but no column matches the name
/// for the statement.
/// Error when the value of a named column is requested, but no column
/// matches the name for the statement.
InvalidColumnName(String),
/// Error when the value of a particular column is requested, but the type of the result in
/// that column cannot be converted to the requested Rust type.
InvalidColumnType(c_int, Type),
/// Error when the value of a particular column is requested, but the type
/// of the result in that column cannot be converted to the requested
/// Rust type.
InvalidColumnType(usize, String, Type),
/// Error when a query that was expected to insert one row did not insert any or insert many.
StatementChangedRows(c_int),
/// Error when a query that was expected to insert one row did not insert
/// any or insert many.
StatementChangedRows(usize),
/// Error returned by `functions::Context::get` when the function argument cannot be converted
/// to the requested type.
/// Error returned by `functions::Context::get` when the function argument
/// cannot be converted to the requested type.
#[cfg(feature = "functions")]
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.,
/// `create_scalar_function`).
#[cfg(feature = "functions")]
#[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.
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 {
@ -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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::SqliteFailure(ref err, None) => err.fmt(f),
Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
Error::SqliteSingleThreadedMode => {
write!(f,
"SQLite was compiled or configured for single-threaded use only")
}
Error::SqliteSingleThreadedMode => write!(
f,
"SQLite was compiled or configured for single-threaded use only"
),
Error::FromSqlConversionFailure(i, ref t, ref err) => {
write!(f,
"Conversion error from type {} at index: {}, {}",
t,
i,
err)
if i != UNKNOWN_COLUMN {
write!(
f,
"Conversion error from type {} at index: {}, {}",
t, i, err
)
} else {
err.fmt(f)
}
}
Error::IntegralValueOutOfRange(col, val) => {
write!(f, "Integer {} out of range at index {}", val, col)
if col != UNKNOWN_COLUMN {
write!(f, "Integer {} out of range at index {}", val, col)
} else {
write!(f, "Integer {} out of range", val)
}
}
Error::Utf8Error(ref err) => err.fmt(f),
Error::NulError(ref err) => err.fmt(f),
@ -120,18 +222,32 @@ impl fmt::Display for Error {
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
Error::InvalidColumnType(i, ref t) => {
write!(f, "Invalid column type {} at index: {}", t, i)
}
Error::InvalidColumnType(i, ref name, ref t) => write!(
f,
"Invalid column type {} at index: {}, name: {}",
t, i, name
),
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
#[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(i, ref t) => {
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")]
Error::UserFunctionError(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 {
Error::SqliteFailure(ref err, None) => err.description(),
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::IntegralValueOutOfRange(_, _) => "integral value out of range of requested type",
Error::Utf8Error(ref err) => err.description(),
Error::InvalidParameterName(_) => "invalid parameter name",
Error::NulError(ref err) => err.description(),
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::InvalidColumnIndex(_) => "invalid column index",
Error::InvalidColumnName(_) => "invalid column name",
Error::InvalidColumnType(_, _) => "invalid column type",
Error::InvalidColumnType(_, _, _) => "invalid column type",
Error::StatementChangedRows(_) => "query inserted zero or more than one row",
#[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
#[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(_, _) => "invalid filter parameter type",
#[cfg(feature = "functions")]
Error::UserFunctionError(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 {
Error::SqliteFailure(ref err, _) => Some(err),
Error::Utf8Error(ref err) => Some(err),
Error::NulError(ref err) => Some(err),
Error::IntegralValueOutOfRange(_, _) |
Error::SqliteSingleThreadedMode |
Error::InvalidParameterName(_) |
Error::ExecuteReturnedResults |
Error::QueryReturnedNoRows |
Error::InvalidColumnIndex(_) |
Error::InvalidColumnName(_) |
Error::InvalidColumnType(_, _) |
Error::InvalidPath(_) |
Error::StatementChangedRows(_) => None,
Error::IntegralValueOutOfRange(_, _)
| Error::SqliteSingleThreadedMode
| Error::InvalidParameterName(_)
| Error::ExecuteReturnedResults
| Error::QueryReturnedNoRows
| Error::InvalidColumnIndex(_)
| Error::InvalidColumnName(_)
| Error::InvalidColumnType(_, _, _)
| Error::InvalidPath(_)
| Error::StatementChangedRows(_)
| Error::InvalidQuery
| Error::MultipleStatement => None,
#[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => None,
#[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(_, _) => None,
#[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => Some(&**err),
Error::FromSqlConversionFailure(_, _, ref err) |
Error::ToSqlConversionFailure(ref err) => Some(&**err),
Error::FromSqlConversionFailure(_, _, ref 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)
}
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(())
}
}

1458
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 ffi;
pub use ffi::Limit;
use crate::ffi;
pub use crate::ffi::Limit;
use Connection;
use crate::Connection;
impl Connection {
/// Returns the current value of a limit.
@ -23,8 +23,8 @@ impl Connection {
#[cfg(test)]
mod test {
use ffi::Limit;
use Connection;
use crate::ffi::Limit;
use crate::Connection;
#[test]
fn test_limit() {
@ -57,13 +57,13 @@ mod test {
assert_eq!(99, db.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER));
// 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);
assert_eq!(32, db.limit(Limit::SQLITE_LIMIT_TRIGGER_DEPTH));
}
// 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);
assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS));
}

View File

@ -1,8 +1,4 @@
use {Result, Connection};
/// Old name for `LoadExtensionGuard`. `SqliteLoadExtensionGuard` is deprecated.
#[deprecated(since = "0.6.0", note = "Use LoadExtensionGuard instead")]
pub type SqliteLoadExtensionGuard<'conn> = LoadExtensionGuard<'conn>;
use crate::{Connection, Result};
/// 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 std::path::{Path};
/// 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)
/// }
@ -21,17 +17,18 @@ pub struct LoadExtensionGuard<'conn> {
conn: &'conn Connection,
}
impl<'conn> LoadExtensionGuard<'conn> {
/// Attempt to enable loading extensions. Loading extensions will be disabled when this
/// guard goes out of scope. Cannot be meaningfully nested.
pub fn new(conn: &Connection) -> Result<LoadExtensionGuard> {
impl LoadExtensionGuard<'_> {
/// Attempt to enable loading extensions. Loading extensions will be
/// disabled when this guard goes out of scope. Cannot be meaningfully
/// nested.
pub fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
conn.load_extension_enable()
.map(|_| LoadExtensionGuard { conn: conn })
.map(|_| LoadExtensionGuard { conn })
}
}
#[allow(unused_must_use)]
impl<'conn> Drop for LoadExtensionGuard<'conn> {
impl Drop for LoadExtensionGuard<'_> {
fn drop(&mut self) {
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::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.
#[derive(Debug)]
pub struct RawStatement(*mut ffi::sqlite3_stmt);
pub struct RawStatement(*mut ffi::sqlite3_stmt, bool);
impl RawStatement {
pub fn new(stmt: *mut ffi::sqlite3_stmt) -> RawStatement {
RawStatement(stmt)
pub fn new(stmt: *mut ffi::sqlite3_stmt, tail: bool) -> RawStatement {
RawStatement(stmt, tail)
}
pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt {
self.0
}
pub fn column_count(&self) -> c_int {
unsafe { ffi::sqlite3_column_count(self.0) }
pub fn column_count(&self) -> usize {
unsafe { ffi::sqlite3_column_count(self.0) as usize }
}
pub fn column_type(&self, idx: c_int) -> c_int {
unsafe { ffi::sqlite3_column_type(self.0, idx) }
pub fn column_type(&self, idx: usize) -> c_int {
unsafe { ffi::sqlite3_column_type(self.0, idx as c_int) }
}
pub fn column_name(&self, idx: c_int) -> &CStr {
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx)) }
pub fn column_decltype(&self, idx: usize) -> Option<&CStr> {
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 {
unsafe { ffi::sqlite3_step(self.0) }
if cfg!(feature = "unlock_notify") {
let db = unsafe { ffi::sqlite3_db_handle(self.0) };
let mut rc;
loop {
rc = unsafe { ffi::sqlite3_step(self.0) };
if !unlock_notify::is_locked(db, rc) {
break;
}
rc = unlock_notify::wait_for_unlock_notify(db);
if rc != ffi::SQLITE_OK {
break;
}
self.reset();
}
rc
} else {
unsafe { ffi::sqlite3_step(self.0) }
}
}
pub fn reset(&self) -> c_int {
unsafe { ffi::sqlite3_reset(self.0) }
}
pub fn bind_parameter_count(&self) -> c_int {
unsafe { ffi::sqlite3_bind_parameter_count(self.0) }
pub fn bind_parameter_count(&self) -> usize {
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()) };
match r {
0 => None,
i => Some(i),
i => Some(i as usize),
}
}
@ -65,6 +108,31 @@ impl RawStatement {
self.0 = ptr::null_mut();
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 {

View File

@ -1,13 +1,14 @@
use fallible_iterator::FallibleIterator;
use fallible_streaming_iterator::FallibleStreamingIterator;
use std::{convert, result};
use std::marker::PhantomData;
use super::{Statement, Error, Result};
use types::{FromSql, FromSqlError};
use statement::StatementCrateImpl;
use super::{Error, Result, Statement};
use crate::types::{FromSql, FromSqlError, ValueRef};
/// An handle for the resulting rows of a query.
pub struct Rows<'stmt> {
stmt: Option<&'stmt Statement<'stmt>>,
pub(crate) stmt: Option<&'stmt Statement<'stmt>>,
row: Option<Row<'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
/// is another row, `Some(Err(...))` if there was an error getting the next
/// row, and `None` if all rows have been retrieved.
/// Attempt to get the next row from the query. Returns `Ok(Some(Row))` if
/// there is another row, `Err(...)` if there was an error
/// getting the next row, and `Ok(None)` if all rows have been retrieved.
///
/// ## Note
///
/// This interface is not compatible with Rust's `Iterator` trait, because the
/// lifetime of the returned row is tied to the lifetime of `self`. This is a
/// "streaming iterator". For a more natural interface, consider using `query_map`
/// or `query_and_then` instead, which return types that implement `Iterator`.
pub fn next<'a>(&'a mut self) -> Option<Result<Row<'a, 'stmt>>> {
self.stmt
.and_then(|stmt| match stmt.step() {
Ok(true) => {
Some(Ok(Row {
stmt: stmt,
phantom: PhantomData,
}))
}
Ok(false) => {
self.reset();
None
}
Err(err) => {
self.reset();
Some(Err(err))
}
})
/// This interface is not compatible with Rust's `Iterator` trait, because
/// the lifetime of the returned row is tied to the lifetime of `self`.
/// This is a fallible "streaming iterator". For a more natural interface,
/// consider using `query_map` or `query_and_then` instead, which
/// return types that implement `Iterator`.
#[allow(clippy::should_implement_trait)] // cannot implement Iterator
pub fn next(&mut self) -> Result<Option<&Row<'stmt>>> {
self.advance()?;
Ok((*self).get())
}
pub fn map<F, B>(self, f: F) -> Map<'stmt, F>
where
F: FnMut(&Row<'_>) -> Result<B>,
{
Map { rows: self, f }
}
}
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this
// once pub(crate) is stable.
pub trait RowsCrateImpl<'stmt> {
fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt>;
fn get_expected_row<'a>(&'a mut self) -> Result<Row<'a, 'stmt>>;
}
impl<'stmt> RowsCrateImpl<'stmt> for Rows<'stmt> {
fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
Rows { stmt: Some(stmt) }
impl<'stmt> Rows<'stmt> {
pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
Rows {
stmt: Some(stmt),
row: None,
}
}
fn get_expected_row<'a>(&'a mut self) -> Result<Row<'a, 'stmt>> {
match self.next() {
Some(row) => row,
pub(crate) fn get_expected_row(&mut self) -> Result<&Row<'stmt>> {
match self.next()? {
Some(row) => Ok(row),
None => Err(Error::QueryReturnedNoRows),
}
}
}
impl<'stmt> Drop for Rows<'stmt> {
impl Drop for Rows<'_> {
fn drop(&mut self) {
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.
pub struct MappedRows<'stmt, F> {
rows: Rows<'stmt>,
map: F,
}
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this
// once pub(crate) is stable.
pub trait MappedRowsCrateImpl<'stmt, T, F> {
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
impl<'stmt, T, F> MappedRows<'stmt, F>
where
F: FnMut(&Row<'_>) -> Result<T>,
{
fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
MappedRows { rows: rows, map: f }
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
MappedRows { rows, map: f }
}
}
impl<'conn, T, F> Iterator for MappedRows<'conn, F>
where F: FnMut(&Row) -> T
impl<T, F> Iterator for MappedRows<'_, F>
where
F: FnMut(&Row<'_>) -> Result<T>,
{
type Item = Result<T>;
@ -103,7 +110,8 @@ impl<'conn, T, F> Iterator for MappedRows<'conn, F>
let map = &mut self.map;
self.rows
.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,
}
// TODO: This trait lets us have "pub(crate)" visibility on some methods. Remove this
// once pub(crate) is stable.
pub trait AndThenRowsCrateImpl<'stmt, T, E, F> {
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>
impl<'stmt, T, E, F> AndThenRows<'stmt, F>
where
F: FnMut(&Row<'_>) -> result::Result<T, E>,
{
fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
AndThenRows { rows: rows, map: f }
pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
AndThenRows { rows, map: f }
}
}
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
where E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>
impl<T, E, F> Iterator for AndThenRows<'_, F>
where
E: convert::From<Error>,
F: FnMut(&Row<'_>) -> 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;
self.rows
.next()
.transpose()
.map(|row_result| row_result.map_err(E::from).and_then(|row| (map)(&row)))
}
}
/// A single result row of a query.
pub struct Row<'a, 'stmt> {
stmt: &'stmt Statement<'stmt>,
phantom: PhantomData<&'a ()>,
impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
type Error = Error;
type Item = Row<'stmt>;
fn advance(&mut self) -> Result<()> {
match self.stmt {
Some(ref stmt) => match stmt.step() {
Ok(true) => {
self.row = Some(Row { stmt });
Ok(())
}
Ok(false) => {
self.reset();
self.row = None;
Ok(())
}
Err(e) => {
self.reset();
self.row = None;
Err(e)
}
},
None => {
self.row = None;
Ok(())
}
}
}
fn get(&self) -> Option<&Row<'stmt>> {
self.row.as_ref()
}
}
impl<'a, 'stmt> Row<'a, 'stmt> {
/// A single result row of a query.
pub struct Row<'stmt> {
pub(crate) stmt: &'stmt Statement<'stmt>,
}
impl<'stmt> Row<'stmt> {
/// Get the value of a particular column of the result row.
///
/// ## 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 integral value is outside the range representable by `T`
/// * If `idx` is outside the range of columns in the returned query
pub fn get<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
self.get_checked(idx).unwrap()
/// * If the underlying SQLite column type is not a valid type as a source
/// 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
pub fn get_unwrap<I: RowIndex, T: FromSql>(&self, idx: I) -> T {
self.get(idx).unwrap()
}
/// Get the value of a particular column of the result row.
@ -169,31 +210,84 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
/// Returns an `Error::InvalidColumnType` if the underlying SQLite column
/// type is not a valid type as a source for `T`.
///
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid column range
/// for this row.
/// 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_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
let idx = try!(idx.idx(self.stmt));
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
/// name for this row.
///
/// 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);
FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => {
Error::InvalidColumnType(idx,
value.data_type())
}
FromSqlError::OutOfRange(i) => {
Error::IntegralValueOutOfRange(idx, i)
}
FromSqlError::Other(err) => {
FromSqlError::InvalidType => Error::InvalidColumnType(
idx,
self.stmt.column_name_unwrap(idx).into(),
value.data_type(),
),
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
}
})
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(
idx,
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.
pub fn column_count(&self) -> i32 {
self.stmt.column_count()
/// 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 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 {
/// Returns the index of the appropriate column, or `None` if no such
/// 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]
fn idx(&self, stmt: &Statement) -> Result<i32> {
if *self < 0 || *self >= stmt.column_count() {
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
if *self >= stmt.column_count() {
Err(Error::InvalidColumnIndex(*self))
} else {
Ok(*self)
@ -215,9 +309,9 @@ impl RowIndex for i32 {
}
}
impl<'a> RowIndex for &'a str {
impl RowIndex for &'_ str {
#[inline]
fn idx(&self, stmt: &Statement) -> Result<i32> {
fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
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.
use std::os::raw::{c_char, c_int, c_void};
use std::ffi::{CStr, CString};
use std::mem;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::catch_unwind;
use std::ptr;
use std::time::Duration;
use super::ffi;
use {Result, Connection};
use error::error_from_sqlite_code;
use crate::error::error_from_sqlite_code;
use crate::{Connection, Result};
/// Set up the process-wide SQLite error logging callback.
/// 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 s = String::from_utf8_lossy(c_slice);
callback(err, &s);
let _ = catch_unwind(|| callback(err, &s));
}
let rc = match callback {
Some(f) => {
let p_arg: *mut c_void = mem::transmute(f);
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG,
log_callback as extern "C" fn(_, _, _),
p_arg)
ffi::sqlite3_config(
ffi::SQLITE_CONFIG_LOG,
log_callback as extern "C" fn(_, _, _),
p_arg,
)
}
None => {
let nullptr: *mut c_void = ptr::null_mut();
@ -59,18 +62,18 @@ pub fn log(err_code: c_int, msg: &str) {
}
impl Connection {
/// Register or clear a callback function that can be used for tracing the execution of SQL
/// statements.
/// Register or clear a callback function that can be used for tracing the
/// execution of SQL statements.
///
/// Prepared statement placeholders are replaced/logged with their assigned values.
/// There can only be a single tracer defined for each database connection.
/// Setting a new tracer clears the old one.
/// Prepared statement placeholders are replaced/logged with their assigned
/// values. There can only be a single tracer defined for each database
/// connection. Setting a new tracer clears the old one.
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) {
let trace_fn: fn(&str) = mem::transmute(p_arg);
let c_slice = CStr::from_ptr(z_sql).to_bytes();
let s = String::from_utf8_lossy(c_slice);
trace_fn(&s);
let _ = catch_unwind(|| trace_fn(&s));
}
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
/// statements.
/// Register or clear a callback function that can be used for profiling
/// the execution of SQL statements.
///
/// There can only be a single profiler defined for each database connection.
/// Setting a new profiler clears the old one.
/// There can only be a single profiler defined for each database
/// connection. Setting a new profiler clears the old one.
pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
unsafe extern "C" fn profile_callback(p_arg: *mut c_void,
z_sql: *const c_char,
nanoseconds: u64) {
unsafe extern "C" fn profile_callback(
p_arg: *mut c_void,
z_sql: *const c_char,
nanoseconds: u64,
) {
let profile_fn: fn(&str, Duration) = mem::transmute(p_arg);
let c_slice = CStr::from_ptr(z_sql).to_bytes();
let s = String::from_utf8_lossy(c_slice);
const NANOS_PER_SEC: u64 = 1_000_000_000;
let duration = Duration::new(nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32);
profile_fn(&s, duration);
let duration = Duration::new(
nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32,
);
let _ = catch_unwind(|| profile_fn(&s, duration));
}
let c = self.db.borrow_mut();
@ -115,10 +122,11 @@ impl Connection {
#[cfg(test)]
mod test {
use lazy_static::lazy_static;
use std::sync::Mutex;
use std::time::Duration;
use Connection;
use crate::Connection;
#[test]
fn test_trace() {
@ -133,13 +141,13 @@ mod test {
let mut db = Connection::open_in_memory().unwrap();
db.trace(Some(tracer));
{
let _ = db.query_row("SELECT ?", &[&1i32], |_| {});
let _ = db.query_row("SELECT ?", &[&"hello"], |_| {});
let _ = db.query_row("SELECT ?", &[1i32], |_| Ok(()));
let _ = db.query_row("SELECT ?", &["hello"], |_| Ok(()));
}
db.trace(None);
{
let _ = db.query_row("SELECT ?", &[&2i32], |_| {});
let _ = db.query_row("SELECT ?", &[&"goodbye"], |_| {});
let _ = db.query_row("SELECT ?", &[2i32], |_| Ok(()));
let _ = db.query_row("SELECT ?", &["goodbye"], |_| Ok(()));
}
let traced_stmts = TRACED_STMTS.lock().unwrap();

View File

@ -1,13 +1,9 @@
use crate::{Connection, Result};
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
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
#[derive(Copy,Clone)]
#[derive(Copy, Clone)]
pub enum TransactionBehavior {
Deferred,
Immediate,
@ -15,7 +11,7 @@ pub enum TransactionBehavior {
}
/// 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 {
/// Roll back the changes. This is the default.
Rollback,
@ -23,22 +19,21 @@ pub enum DropBehavior {
/// Commit the changes.
Commit,
/// Do not commit or roll back changes - this will leave the transaction or savepoint
/// open, so should be used with care.
/// Do not commit or roll back changes - this will leave the transaction or
/// savepoint open, so should be used with care.
Ignore,
}
/// Old name for `Transaction`. `SqliteTransaction` is deprecated.
#[deprecated(since = "0.6.0", note = "Use Transaction instead")]
pub type SqliteTransaction<'conn> = Transaction<'conn>;
/// Panic. Used to enforce intentional behavior during development.
Panic,
}
/// Represents a transaction on a database connection.
///
/// ## Note
///
/// Transactions will roll back by default. Use `commit` method to explicitly commit the
/// transaction, or use `set_drop_behavior` to change what happens when the transaction
/// is dropped.
/// Transactions will roll back by default. Use `commit` method to explicitly
/// commit the transaction, or use `set_drop_behavior` to change what happens
/// when the transaction is dropped.
///
/// ## Example
///
@ -47,27 +42,27 @@ pub type SqliteTransaction<'conn> = Transaction<'conn>;
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
/// 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
/// try!(do_queries_part_2(&tx)); // tx causes rollback if this fails
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
///
/// tx.commit()
/// }
/// ```
#[derive(Debug)]
pub struct Transaction<'conn> {
conn: &'conn Connection,
drop_behavior: DropBehavior,
committed: bool,
}
/// Represents a savepoint on a database connection.
///
/// ## Note
///
/// Savepoints will roll back by default. Use `commit` method to explicitly commit the
/// savepoint, or use `set_drop_behavior` to change what happens when the savepoint
/// is dropped.
/// Savepoints will roll back by default. Use `commit` method to explicitly
/// commit the savepoint, or use `set_drop_behavior` to change what happens
/// when the savepoint is dropped.
///
/// ## Example
///
@ -76,10 +71,10 @@ pub struct Transaction<'conn> {
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
/// 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
/// try!(do_queries_part_2(&sp)); // sp causes rollback if this fails
/// do_queries_part_1(&sp)?; // sp causes rollback if this fails
/// do_queries_part_2(&sp)?; // sp causes rollback if this fails
///
/// sp.commit()
/// }
@ -92,22 +87,22 @@ pub struct Savepoint<'conn> {
committed: bool,
}
impl<'conn> Transaction<'conn> {
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested transactions.
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction> {
impl Transaction<'_> {
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested
/// 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 {
TransactionBehavior::Deferred => "BEGIN DEFERRED",
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
};
conn.execute_batch(query)
.map(move |_| {
Transaction {
conn: conn,
drop_behavior: DropBehavior::Rollback,
committed: false,
}
})
conn.execute_batch(query).map(move |_| Transaction {
conn,
drop_behavior: DropBehavior::Rollback,
})
}
/// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested
@ -115,7 +110,8 @@ impl<'conn> Transaction<'conn> {
///
/// ## Note
///
/// Just like outer level transactions, savepoint transactions rollback by default.
/// Just like outer level transactions, savepoint transactions rollback by
/// default.
///
/// ## Example
///
@ -123,12 +119,12 @@ impl<'conn> Transaction<'conn> {
/// # use rusqlite::{Connection, Result};
/// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true }
/// 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) {
/// try!(sp.commit());
/// sp.commit()?;
/// }
/// // otherwise, sp will rollback
/// }
@ -136,21 +132,23 @@ impl<'conn> Transaction<'conn> {
/// tx.commit()
/// }
/// ```
pub fn savepoint(&mut self) -> Result<Savepoint> {
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
Savepoint::with_depth(self.conn, 1)
}
/// 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)
}
/// 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 {
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) {
self.drop_behavior = drop_behavior
}
@ -161,8 +159,8 @@ impl<'conn> Transaction<'conn> {
}
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.
@ -171,32 +169,33 @@ impl<'conn> Transaction<'conn> {
}
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
/// (see `drop_behavior`).
/// Consumes the transaction, committing or rolling back according to the
/// current setting (see `drop_behavior`).
///
/// Functionally equivalent to the `Drop` implementation, but allows callers to see any
/// errors that occur.
/// Functionally equivalent to the `Drop` implementation, but allows
/// callers to see any errors that occur.
pub fn finish(mut self) -> Result<()> {
self.finish_()
}
fn finish_(&mut self) -> Result<()> {
if self.committed {
if self.conn.is_autocommit() {
return Ok(());
}
match self.drop_behavior() {
DropBehavior::Commit => self.commit_(),
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()),
DropBehavior::Rollback => self.rollback_(),
DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Transaction dropped unexpectedly."),
}
}
}
impl<'conn> Deref for Transaction<'conn> {
impl Deref for Transaction<'_> {
type Target = Connection;
fn deref(&self) -> &Connection {
@ -205,61 +204,62 @@ impl<'conn> Deref for Transaction<'conn> {
}
#[allow(unused_must_use)]
impl<'conn> Drop for Transaction<'conn> {
impl Drop for Transaction<'_> {
fn drop(&mut self) {
self.finish_();
}
}
impl<'conn> Savepoint<'conn> {
fn with_depth_and_name<T: Into<String>>(conn: &Connection,
depth: u32,
name: T)
-> Result<Savepoint> {
impl Savepoint<'_> {
fn with_depth_and_name<T: Into<String>>(
conn: &Connection,
depth: u32,
name: T,
) -> Result<Savepoint<'_>> {
let name = name.into();
conn.execute_batch(&format!("SAVEPOINT {}", name))
.map(|_| {
Savepoint {
conn: conn,
name: name,
depth: depth,
drop_behavior: DropBehavior::Rollback,
committed: false,
}
.map(|_| Savepoint {
conn,
name,
depth,
drop_behavior: DropBehavior::Rollback,
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);
Savepoint::with_depth_and_name(conn, depth, name)
}
/// 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)
}
/// 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)
}
/// 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)
}
/// 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)
}
/// 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 {
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) {
self.drop_behavior = drop_behavior
}
@ -270,27 +270,27 @@ impl<'conn> Savepoint<'conn> {
}
fn commit_(&mut self) -> Result<()> {
self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
self.committed = true;
self.conn
.execute_batch(&format!("RELEASE {}", self.name))
Ok(())
}
/// A convenience method which rolls back a savepoint.
///
/// ## Note
///
/// Unlike `Transaction`s, savepoints remain active after they have been rolled back,
/// and can be rolled back again or committed.
/// Unlike `Transaction`s, savepoints remain active after they have been
/// rolled back, and can be rolled back again or committed.
pub fn rollback(&mut self) -> Result<()> {
self.conn
.execute_batch(&format!("ROLLBACK TO {}", self.name))
}
/// Consumes the savepoint, committing or rolling back according to the current setting
/// (see `drop_behavior`).
/// Consumes the savepoint, committing or rolling back according to the
/// current setting (see `drop_behavior`).
///
/// Functionally equivalent to the `Drop` implementation, but allows callers to see any
/// errors that occur.
/// Functionally equivalent to the `Drop` implementation, but allows
/// callers to see any errors that occur.
pub fn finish(mut self) -> Result<()> {
self.finish_()
}
@ -300,14 +300,15 @@ impl<'conn> Savepoint<'conn> {
return Ok(());
}
match self.drop_behavior() {
DropBehavior::Commit => self.commit_(),
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()),
DropBehavior::Rollback => self.rollback(),
DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
}
}
}
impl<'conn> Deref for Savepoint<'conn> {
impl Deref for Savepoint<'_> {
type Target = Connection;
fn deref(&self) -> &Connection {
@ -316,7 +317,7 @@ impl<'conn> Deref for Savepoint<'conn> {
}
#[allow(unused_must_use)]
impl<'conn> Drop for Savepoint<'conn> {
impl Drop for Savepoint<'_> {
fn drop(&mut self) {
self.finish_();
}
@ -325,8 +326,9 @@ impl<'conn> Drop for Savepoint<'conn> {
impl Connection {
/// 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
/// commit, you must call `commit` or `set_drop_behavior(DropBehavior::Commit)`.
/// The transaction defaults to rolling back when it is dropped. If you
/// want the transaction to commit, you must call `commit` or
/// `set_drop_behavior(DropBehavior::Commit)`.
///
/// ## Example
///
@ -335,10 +337,10 @@ impl Connection {
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
/// 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
/// try!(do_queries_part_2(&tx)); // tx causes rollback if this fails
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
///
/// tx.commit()
/// }
@ -347,7 +349,7 @@ impl Connection {
/// # Failure
///
/// 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)
}
@ -358,16 +360,18 @@ impl Connection {
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn transaction_with_behavior(&mut self,
behavior: TransactionBehavior)
-> Result<Transaction> {
pub fn transaction_with_behavior(
&mut self,
behavior: TransactionBehavior,
) -> Result<Transaction<'_>> {
Transaction::new(self, behavior)
}
/// 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
/// commit, you must call `commit` or `set_drop_behavior(DropBehavior::Commit)`.
/// The savepoint defaults to rolling back when it is dropped. If you want
/// the savepoint to commit, you must call `commit` or
/// `set_drop_behavior(DropBehavior::Commit)`.
///
/// ## Example
///
@ -376,10 +380,10 @@ impl Connection {
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
/// 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
/// try!(do_queries_part_2(&sp)); // sp causes rollback if this fails
/// do_queries_part_1(&sp)?; // sp causes rollback if this fails
/// do_queries_part_2(&sp)?; // sp causes rollback if this fails
///
/// sp.commit()
/// }
@ -388,7 +392,7 @@ impl Connection {
/// # Failure
///
/// 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)
}
@ -399,15 +403,15 @@ impl Connection {
/// # Failure
///
/// 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)
}
}
#[cfg(test)]
mod test {
use Connection;
use super::DropBehavior;
use crate::{Connection, NO_PARAMS};
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -430,9 +434,11 @@ mod test {
}
{
let tx = db.transaction().unwrap();
assert_eq!(2i32,
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap());
assert_eq!(
2i32,
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();
assert_eq!(6i32,
tx.query_row::<i32, _>("SELECT SUM(x) FROM foo", &[], |r| r.get(0))
.unwrap());
assert_eq!(
6i32,
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);
}
#[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) {
conn.execute("INSERT INTO foo VALUES(?)", &[&x]).unwrap();
conn.execute("INSERT INTO foo VALUES(?)", &[x]).unwrap();
}
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();
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.
extern crate chrono;
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 types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
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();
Ok(ToSqlOutput::from(date_str))
}
@ -18,19 +17,19 @@ impl ToSql for NaiveDate {
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
impl FromSql for NaiveDate {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value
.as_str()
.and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
})
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
})
}
}
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
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();
Ok(ToSqlOutput::from(date_str))
}
@ -38,65 +37,64 @@ impl ToSql for NaiveTime {
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
impl FromSql for NaiveTime {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value
.as_str()
.and_then(|s| {
let fmt = match s.len() {
5 => "%H:%M",
8 => "%H:%M:%S",
_ => "%H:%M:%S%.f",
};
match NaiveTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
}
})
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
let fmt = match s.len() {
5 => "%H:%M",
8 => "%H:%M:%S",
_ => "%H:%M:%S%.f",
};
match NaiveTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
}
})
}
}
/// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS"
/// ISO 8601 combined date and time without timezone =>
/// "YYYY-MM-DDTHH:MM:SS.SSS"
impl ToSql for NaiveDateTime {
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();
Ok(ToSqlOutput::from(date_str))
}
}
/// "YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS" => ISO 8601 combined date and time
/// without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported)
/// "YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS" => ISO 8601 combined date
/// and time without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS"
/// also supported)
impl FromSql for NaiveDateTime {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value
.as_str()
.and_then(|s| {
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%Y-%m-%dT%H:%M:%S%.f"
} else {
"%Y-%m-%d %H:%M:%S%.f"
};
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%Y-%m-%dT%H:%M:%S%.f"
} else {
"%Y-%m-%d %H:%M:%S%.f"
};
match NaiveDateTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
}
})
match NaiveDateTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
}
})
}
}
/// Date and time with time zone => UTC RFC3339 timestamp ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
/// Date and time with time zone => UTC RFC3339 timestamp
/// ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
impl<Tz: TimeZone> ToSql for DateTime<Tz> {
fn to_sql(&self) -> Result<ToSqlOutput> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.with_timezone(&Utc).to_rfc3339()))
}
}
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `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.
let s = try!(value.as_str());
let s = value.as_str()?;
// If timestamp looks space-separated, make a copy and replace it with 'T'.
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>`.
impl FromSql for DateTime<Local> {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
let utc_dt = try!(DateTime::<Utc>::column_result(value));
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let utc_dt = DateTime::<Utc>::column_result(value)?;
Ok(utc_dt.with_timezone(&Local))
}
}
#[cfg(test)]
mod test {
use Connection;
use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
Duration};
use crate::{Connection, Result, NO_PARAMS};
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -148,10 +145,12 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&date])
.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();
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();
assert_eq!(date, t);
}
@ -163,10 +162,12 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&time])
.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();
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();
assert_eq!(time, v);
}
@ -181,16 +182,19 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt])
.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();
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();
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"
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();
assert_eq!(dt, hms);
}
@ -206,25 +210,31 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc])
.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();
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();
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();
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();
assert_eq!(utc - Duration::milliseconds(789), v3);
let v4: DateTime<Utc> =
db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0))
.unwrap();
let v4: DateTime<Utc> = db
.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", NO_PARAMS, |r| {
r.get(0)
})
.unwrap();
assert_eq!(utc, v4);
}
@ -240,12 +250,31 @@ mod test {
.unwrap();
// 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();
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();
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::fmt;
/// Enum listing possible errors from `FromSql` trait.
#[derive(Debug)]
pub enum FromSqlError {
/// Error when an SQLite value is requested, but the type of the result cannot be converted to
/// the requested Rust type.
/// Error when an SQLite value is requested, but the type of the result
/// cannot be converted to the requested Rust type.
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),
/// 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.
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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
FromSqlError::InvalidType => write!(f, "Invalid type"),
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),
}
}
@ -31,16 +64,20 @@ impl Error for FromSqlError {
match *self {
FromSqlError::InvalidType => "invalid type",
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(),
}
}
#[cfg_attr(feature="clippy", allow(match_same_arms))]
fn cause(&self) -> Option<&Error> {
#[allow(clippy::match_same_arms)]
#[allow(deprecated)]
fn cause(&self) -> Option<&dyn Error> {
match *self {
FromSqlError::Other(ref err) => err.cause(),
FromSqlError::InvalidType |
FromSqlError::OutOfRange(_) => None,
_ => None,
}
}
}
@ -50,19 +87,21 @@ pub type FromSqlResult<T> = Result<T, FromSqlError>;
/// 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`.
/// This is intentional; SQLite returns integers as signed 64-bit values, which cannot fully
/// represent the range of these types. Rusqlite would have to decide how to handle negative
/// values: return an error or reinterpret as a very large postive numbers, neither of which 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.
/// Note that `FromSql` and `ToSql` are defined for most integral types, but
/// not `u64` or `usize`. This is intentional; SQLite returns integers as
/// signed 64-bit values, which cannot fully represent the range of these
/// types. Rusqlite would have to
/// decide how to handle negative values: return an error or reinterpret as a
/// very large postive numbers, neither of which
/// 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 {
fn column_result(value: ValueRef) -> FromSqlResult<Self>;
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
}
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| {
if i < isize::min_value() as i64 || i > isize::max_value() as i64 {
Err(FromSqlError::OutOfRange(i))
@ -76,7 +115,7 @@ impl FromSql for isize {
macro_rules! from_sql_integral(
($t:ident) => (
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| {
if i < i64::from($t::min_value()) || i > i64::from($t::max_value()) {
Err(FromSqlError::OutOfRange(i))
@ -97,13 +136,13 @@ from_sql_integral!(u16);
from_sql_integral!(u32);
impl FromSql for i64 {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_i64()
}
}
impl FromSql for f64 {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => Ok(i as f64),
ValueRef::Real(f) => Ok(f),
@ -113,28 +152,56 @@ impl FromSql for f64 {
}
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 {
0 => false,
_ => true,
})
0 => false,
_ => true,
})
}
}
impl FromSql for String {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value.as_str().map(|s| s.to_string())
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(ToString::to_string)
}
}
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())
}
}
#[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> {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Null => Ok(None),
_ => FromSql::column_result(value).map(Some),
@ -143,15 +210,15 @@ impl<T: FromSql> FromSql for Option<T> {
}
impl FromSql for Value {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
Ok(value.into())
}
}
#[cfg(test)]
mod test {
use {Connection, Error};
use super::FromSql;
use crate::{Connection, Error};
fn checked_memory_handle() -> Connection {
Connection::open_in_memory().unwrap()
@ -162,11 +229,12 @@ mod test {
let db = checked_memory_handle();
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 {
let err = db.query_row("SELECT ?", &[n], |r| r.get_checked::<_, T>(0))
.unwrap()
let err = db
.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
.unwrap_err();
match err {
Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
@ -174,20 +242,24 @@ mod test {
}
}
for n in in_range {
assert_eq!(*n,
db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
.unwrap()
.into());
assert_eq!(
*n,
db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
.unwrap()
.into()
);
}
}
check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
check_ranges::<i32>(&db,
&[-2147483649, 2147483648],
&[-2147483648, -1, 0, 1, 2147483647]);
check_ranges::<i32>(
&db,
&[-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::<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.
//!
//! 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
//! for:
//! the `ToSql` and `FromSql` traits are provided for the basic types that
//! SQLite provides methods for:
//!
//! * Integers (`i32` and `i64`; SQLite uses `i64` internally, so getting an `i32` will truncate
//! if the value is too large or too small).
//! * Integers (`i32` and `i64`; SQLite uses `i64` internally, so getting an
//! `i32` will truncate if the value is too large or too small).
//! * Reals (`f64`)
//! * Strings (`String` and `&str`)
//! * Blobs (`Vec<u8>` and `&[u8]`)
//!
//! Additionally, because it is such a common data type, implementations are provided for
//! `time::Timespec` that use a string for storage (using the same format string,
//! `"%Y-%m-%d %H:%M:%S"`, as SQLite's builtin
//! [datetime](https://www.sqlite.org/lang_datefunc.html) function. Note that this storage
//! truncates timespecs to the nearest second. If you want different storage for timespecs, you can
//! use a newtype. For example, to store timespecs as `f64`s:
//! Additionally, because it is such a common data type, implementations are
//! provided for `time::Timespec` that use the RFC 3339 date/time format,
//! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values
//! can be parsed by SQLite's builtin
//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you
//! want different storage for timespecs, you can use a newtype. For example, to
//! store timespecs as `f64`s:
//!
//! ```rust
//! extern crate rusqlite;
//! extern crate time;
//!
//! use rusqlite::types::{FromSql, FromSqlResult, ValueRef, ToSql, ToSqlOutput};
//! use rusqlite::{Result};
//! use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
//! use rusqlite::Result;
//!
//! pub struct TimespecSql(pub time::Timespec);
//!
//! impl FromSql for TimespecSql {
//! fn column_result(value: ValueRef) -> FromSqlResult<Self> {
//! f64::column_result(value).map(|as_f64| {
//! TimespecSql(time::Timespec{ sec: as_f64.trunc() as i64,
//! nsec: (as_f64.fract() * 1.0e9) as i32 })
//! TimespecSql(time::Timespec {
//! sec: as_f64.trunc() as i64,
//! nsec: (as_f64.fract() * 1.0e9) as i32,
//! })
//! })
//! }
//! }
@ -42,14 +42,11 @@
//! 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
//! `FromSql` for the cases where you want to know if a value was NULL (which gets translated to
//! `None`).
//! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T`
//! implements `ToSql` or `FromSql` for the cases where you want to know if a
//! value was NULL (which gets translated to `None`).
pub use self::from_sql::{FromSql, FromSqlError, FromSqlResult};
pub use self::to_sql::{ToSql, ToSqlOutput};
@ -58,35 +55,34 @@ pub use self::value_ref::ValueRef;
use std::fmt;
mod value;
mod value_ref;
mod from_sql;
mod to_sql;
mod time;
#[cfg(feature = "chrono")]
mod chrono;
mod from_sql;
#[cfg(feature = "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`.
///
/// ## Example
///
/// ```rust,no_run
/// # extern crate rusqlite;
/// # use rusqlite::{Connection, Result};
/// # use rusqlite::types::{Null};
/// # use std::os::raw::{c_int};
/// fn main() {
/// }
/// fn insert_null(conn: &Connection) -> Result<c_int> {
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[&Null])
///
/// fn insert_null(conn: &Connection) -> Result<usize> {
/// conn.execute("INSERT INTO people (name) VALUES (?)", &[Null])
/// }
/// ```
#[derive(Copy,Clone)]
#[derive(Copy, Clone)]
pub struct Null;
#[derive(Clone,Debug,PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum Type {
Null,
Integer,
@ -96,7 +92,7 @@ pub enum 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 {
Type::Null => write!(f, "Null"),
Type::Integer => write!(f, "Integer"),
@ -109,13 +105,12 @@ impl fmt::Display for Type {
#[cfg(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 crate::{Connection, Error, NO_PARAMS};
use std::f64::EPSILON;
use std::os::raw::{c_double, c_int};
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -132,7 +127,8 @@ mod test {
db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])
.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();
assert_eq!(v, v1234);
}
@ -145,7 +141,8 @@ mod test {
db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])
.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();
assert_eq!(v, empty);
}
@ -155,10 +152,10 @@ mod test {
let db = checked_memory_handle();
let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s])
.unwrap();
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).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();
assert_eq!(from, s);
}
@ -168,10 +165,11 @@ mod test {
let db = checked_memory_handle();
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();
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();
assert_eq!(from, s);
}
@ -180,12 +178,14 @@ mod test {
fn test_value() {
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();
assert_eq!(10i64,
db.query_row::<i64, _>("SELECT i FROM foo", &[], |r| r.get(0))
.unwrap());
assert_eq!(
10i64,
db.query_row::<i64, _, _>("SELECT i FROM foo", NO_PARAMS, |r| r.get(0))
.unwrap()
);
}
#[test]
@ -195,104 +195,144 @@ mod test {
let s = Some("hello, world!");
let b = Some(vec![1u8, 2, 3, 4]);
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s])
.unwrap();
db.execute("INSERT INTO foo(b) VALUES (?)", &[&b])
.unwrap();
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).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();
let mut rows = stmt.query(&[]).unwrap();
let mut rows = stmt.query(NO_PARAMS).unwrap();
{
let row1 = rows.next().unwrap().unwrap();
let s1: Option<String> = row1.get(0);
let b1: Option<Vec<u8>> = row1.get(1);
let s1: Option<String> = row1.get_unwrap(0);
let b1: Option<Vec<u8>> = row1.get_unwrap(1);
assert_eq!(s.unwrap(), s1.unwrap());
assert!(b1.is_none());
}
{
let row2 = rows.next().unwrap().unwrap();
let s2: Option<String> = row2.get(0);
let b2: Option<Vec<u8>> = row2.get(1);
let s2: Option<String> = row2.get_unwrap(0);
let b2: Option<Vec<u8>> = row2.get_unwrap(1);
assert!(s2.is_none());
assert_eq!(b, b2);
}
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_mismatched_types() {
fn is_invalid_column_type(err: Error) -> bool {
match err {
Error::InvalidColumnType(_, _) => true,
Error::InvalidColumnType(_, _, _) => true,
_ => false,
}
}
let db = checked_memory_handle();
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
&[])
.unwrap();
db.execute(
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
NO_PARAMS,
)
.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();
// check the correct types come back as expected
assert_eq!(vec![1, 2], row.get_checked::<i32, Vec<u8>>(0).unwrap());
assert_eq!("text", row.get_checked::<i32, String>(1).unwrap());
assert_eq!(1, row.get_checked::<i32, c_int>(2).unwrap());
assert!((1.5 - row.get_checked::<i32, c_double>(3).unwrap()).abs() < EPSILON);
assert!(row.get_checked::<i32, Option<c_int>>(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());
assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0).unwrap());
assert_eq!("text", row.get::<_, String>(1).unwrap());
assert_eq!(1, row.get::<_, c_int>(2).unwrap());
assert!((1.5 - row.get::<_, c_double>(3).unwrap()).abs() < EPSILON);
assert!(row.get::<_, Option<c_int>>(4).unwrap().is_none());
assert!(row.get::<_, Option<c_double>>(4).unwrap().is_none());
assert!(row.get::<_, Option<String>>(4).unwrap().is_none());
// check some invalid types
// 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(row.get_checked::<i32, 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(row.get_checked::<i32, String>(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::<_, c_int>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get::<_, 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)
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(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_checked::<i32, Option<c_int>>(1).err().unwrap()));
assert!(is_invalid_column_type(
row.get::<_, c_int>(1).err().unwrap()
));
assert!(is_invalid_column_type(row.get::<_, i64>(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
assert!(is_invalid_column_type(row.get_checked::<i32, String>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Option<String>>(2).err().unwrap()));
assert!(is_invalid_column_type(
row.get::<_, 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)
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(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_checked::<i32, Option<c_int>>(3).err().unwrap()));
assert!(is_invalid_column_type(
row.get::<_, c_int>(3).err().unwrap()
));
assert!(is_invalid_column_type(row.get::<_, i64>(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
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(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_checked::<i32, Vec<u8>>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, time::Timespec>(4).err().unwrap()));
assert!(is_invalid_column_type(
row.get::<_, c_int>(4).err().unwrap()
));
assert!(is_invalid_column_type(row.get::<_, i64>(4).err().unwrap()));
assert!(is_invalid_column_type(
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]
@ -300,23 +340,26 @@ mod test {
use super::Value;
let db = checked_memory_handle();
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
&[])
.unwrap();
db.execute(
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
NO_PARAMS,
)
.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();
assert_eq!(Value::Blob(vec![1, 2]),
row.get_checked::<i32, Value>(0).unwrap());
assert_eq!(Value::Text(String::from("text")),
row.get_checked::<i32, 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::Blob(vec![1, 2]), row.get::<_, Value>(0).unwrap());
assert_eq!(
Value::Text(String::from("text")),
row.get::<_, Value>(1).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),
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,34 +1,34 @@
//! `ToSql` and `FromSql` implementation for JSON `Value`.
extern crate serde_json;
use self::serde_json::Value;
use serde_json::Value;
use Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
/// Serialize JSON `Value` to text.
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()))
}
}
/// Deserialize text/blob to JSON `Value`.
impl FromSql for Value {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(s) => serde_json::from_str(s),
ValueRef::Blob(b) => serde_json::from_slice(b),
_ => return Err(FromSqlError::InvalidType),
}
.map_err(|err| FromSqlError::Other(Box::new(err)))
ValueRef::Text(s) => serde_json::from_slice(s),
ValueRef::Blob(b) => serde_json::from_slice(b),
_ => return Err(FromSqlError::InvalidType),
}
.map_err(|err| FromSqlError::Other(Box::new(err)))
}
}
#[cfg(test)]
mod test {
use Connection;
use super::serde_json;
use crate::types::ToSql;
use crate::{Connection, NO_PARAMS};
use serde_json;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -43,14 +43,18 @@ mod test {
let json = r#"{"foo": 13, "bar": "baz"}"#;
let data: serde_json::Value = serde_json::from_str(json).unwrap();
db.execute("INSERT INTO foo (t, b) VALUES (?, ?)",
&[&data, &json.as_bytes()])
.unwrap();
db.execute(
"INSERT INTO foo (t, b) VALUES (?, ?)",
&[&data as &dyn ToSql, &json.as_bytes()],
)
.unwrap();
let t: serde_json::Value = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
let t: serde_json::Value = db
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
.unwrap();
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();
assert_eq!(data, b);
}

View File

@ -1,12 +1,14 @@
extern crate time;
use time;
use Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::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 {
fn to_sql(&self) -> Result<ToSqlOutput> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let time_string = time::at_utc(*self)
.strftime(SQLITE_DATETIME_FMT)
.unwrap()
@ -16,20 +18,26 @@ impl ToSql for time::Timespec {
}
impl FromSql for time::Timespec {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value
.as_str()
.and_then(|s| match time::strptime(s, SQLITE_DATETIME_FMT) {
Ok(tm) => Ok(tm.to_timespec()),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
})
.and_then(|s| {
match s.len() {
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)]
mod test {
use Connection;
use super::time;
use crate::{Connection, Result, NO_PARAMS};
use time;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -44,25 +52,31 @@ mod test {
let mut ts_vec = vec![];
ts_vec.push(time::Timespec::new(10_000, 0));//January 1, 1970 2:46:40 AM
ts_vec.push(time::Timespec::new(10_000, 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(2000000000, 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(10000000000, 0));//November 20, 2286
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(1_500_391_124, 1_000_000)); //July 18, 2017
ts_vec.push(time::Timespec::new(2_000_000_000, 2_000_000)); //May 18, 2033
ts_vec.push(time::Timespec::new(3_000_000_000, 999_999_999)); //January 24, 2065
ts_vec.push(time::Timespec::new(10_000_000_000, 0)); //November 20, 2286
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();
let from: time::Timespec = db.query_row("SELECT t FROM foo", &[], |r| r.get(0))
.unwrap();
db.execute("DELETE FROM foo", &[]).unwrap();
db.execute("DELETE FROM foo", NO_PARAMS).unwrap();
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,8 +1,12 @@
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.
#[derive(Clone,Debug,PartialEq)]
/// `ToSqlOutput` represents the possible output types for implementors of the
/// `ToSql` trait.
#[derive(Clone, Debug, PartialEq)]
pub enum ToSqlOutput<'a> {
/// A borrowed SQLite-representable value.
Borrowed(ValueRef<'a>),
@ -13,37 +17,81 @@ pub enum ToSqlOutput<'a> {
/// A BLOB of the given length that is filled with zeroes.
#[cfg(feature = "blob")]
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>
where &'a T: Into<ValueRef<'a>>
where
&'a T: Into<ValueRef<'a>>,
{
fn from(t: &'a T) -> Self {
ToSqlOutput::Borrowed(t.into())
}
}
impl<'a, T: Into<Value>> From<T> for ToSqlOutput<'a> {
fn from(t: T) -> Self {
ToSqlOutput::Owned(t.into())
}
}
// We cannot also generically allow any type that can be converted
// into a Value to be converted into a ToSqlOutput because of
// 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> {
fn to_sql(&self) -> Result<ToSqlOutput> {
// It would be nice if we could avoid the heap allocation (of the `Vec`) that
// `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 {
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
#[cfg(feature = "blob")]
#[cfg(feature = "blob")]
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.
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:
@ -61,7 +109,7 @@ pub trait ToSql {
macro_rules! to_sql_self(
($t:ty) => (
impl ToSql for $t {
fn to_sql(&self) -> Result<ToSqlOutput> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(*self))
}
}
@ -80,46 +128,53 @@ to_sql_self!(u16);
to_sql_self!(u32);
to_sql_self!(f64);
impl<'a, T: ?Sized> ToSql for &'a T
where &'a T: Into<ToSqlOutput<'a>>
#[cfg(feature = "i128_blob")]
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> {
Ok((*self).into())
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
(*self).to_sql()
}
}
impl ToSql for String {
fn to_sql(&self) -> Result<ToSqlOutput> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.as_str()))
}
}
impl ToSql for str {
fn to_sql(&self) -> Result<ToSqlOutput> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self))
}
}
impl ToSql for Vec<u8> {
fn to_sql(&self) -> Result<ToSqlOutput> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.as_slice()))
}
}
impl ToSql for [u8] {
fn to_sql(&self) -> Result<ToSqlOutput> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self))
}
}
impl ToSql for Value {
fn to_sql(&self) -> Result<ToSqlOutput> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self))
}
}
impl<T: ToSql> ToSql for Option<T> {
fn to_sql(&self) -> Result<ToSqlOutput> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
match *self {
None => Ok(ToSqlOutput::from(Null)),
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)]
mod test {
use super::ToSql;
@ -143,4 +204,93 @@ mod test {
is_to_sql::<u16>();
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

@ -4,7 +4,7 @@ use super::{Null, Type};
/// dictated by SQLite (not by the caller).
///
/// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value.
#[derive(Clone,Debug,PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
/// The value is a `NULL` value.
Null,
@ -31,7 +31,28 @@ impl From<bool> 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(
@ -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 {
pub fn data_type(&self) -> Type {
match *self {

View File

@ -1,11 +1,11 @@
use types::{FromSqlError, FromSqlResult};
use super::{Value, Type};
use super::{Type, Value};
use crate::types::{FromSqlError, FromSqlResult};
/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the
/// memory backing this value is owned by SQLite.
///
/// See [`Value`](enum.Value.html) for an owning dynamic type value.
#[derive(Copy,Clone,Debug,PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ValueRef<'a> {
/// The value is a `NULL` value.
Null,
@ -14,12 +14,12 @@ pub enum ValueRef<'a> {
/// The value is a floating point number.
Real(f64),
/// The value is a text string.
Text(&'a str),
Text(&'a [u8]),
/// The value is a blob of data
Blob(&'a [u8]),
}
impl<'a> ValueRef<'a> {
impl ValueRef<'_> {
pub fn data_type(&self) -> Type {
match *self {
ValueRef::Null => Type::Null,
@ -32,8 +32,8 @@ impl<'a> ValueRef<'a> {
}
impl<'a> ValueRef<'a> {
/// If `self` is case `Integer`, returns the integral value. Otherwise, returns
/// `Err(Error::InvalidColumnType)`.
/// If `self` is case `Integer`, returns the integral value. Otherwise,
/// returns `Err(Error::InvalidColumnType)`.
pub fn as_i64(&self) -> FromSqlResult<i64> {
match *self {
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
/// `Err(Error::InvalidColumnType)`.
/// If `self` is case `Real`, returns the floating point value. Otherwise,
/// returns `Err(Error::InvalidColumnType)`.
pub fn as_f64(&self) -> FromSqlResult<f64> {
match *self {
ValueRef::Real(f) => Ok(f),
@ -52,16 +52,18 @@ impl<'a> ValueRef<'a> {
/// If `self` is case `Text`, returns the string value. Otherwise, returns
/// `Err(Error::InvalidColumnType)`.
pub fn as_str(&self) -> FromSqlResult<&str> {
pub fn as_str(&self) -> FromSqlResult<&'a str> {
match *self {
ValueRef::Text(t) => Ok(t),
ValueRef::Text(t) => {
std::str::from_utf8(t).map_err(|e| FromSqlError::Other(Box::new(e)))
}
_ => Err(FromSqlError::InvalidType),
}
}
/// If `self` is case `Blob`, returns the byte slice. Otherwise, returns
/// `Err(Error::InvalidColumnType)`.
pub fn as_blob(&self) -> FromSqlResult<&[u8]> {
pub fn as_blob(&self) -> FromSqlResult<&'a [u8]> {
match *self {
ValueRef::Blob(b) => Ok(b),
_ => Err(FromSqlError::InvalidType),
@ -69,26 +71,29 @@ impl<'a> ValueRef<'a> {
}
}
impl<'a> From<ValueRef<'a>> for Value {
fn from(borrowed: ValueRef) -> Value {
impl From<ValueRef<'_>> for Value {
fn from(borrowed: ValueRef<'_>) -> Value {
match borrowed {
ValueRef::Null => Value::Null,
ValueRef::Integer(i) => Value::Integer(i),
ValueRef::Real(r) => Value::Real(r),
ValueRef::Text(s) => Value::Text(s.to_string()),
ValueRef::Text(s) => {
let s = std::str::from_utf8(s).expect("invalid UTF-8");
Value::Text(s.to_string())
}
ValueRef::Blob(b) => Value::Blob(b.to_vec()),
}
}
}
impl<'a> From<&'a str> for ValueRef<'a> {
fn from(s: &str) -> ValueRef {
ValueRef::Text(s)
fn from(s: &str) -> ValueRef<'_> {
ValueRef::Text(s.as_bytes())
}
}
impl<'a> From<&'a [u8]> for ValueRef<'a> {
fn from(s: &[u8]) -> ValueRef {
fn from(s: &[u8]) -> ValueRef<'_> {
ValueRef::Blob(s)
}
}
@ -99,8 +104,70 @@ impl<'a> From<&'a Value> for ValueRef<'a> {
Value::Null => ValueRef::Null,
Value::Integer(i) => ValueRef::Integer(i),
Value::Real(r) => ValueRef::Real(r),
Value::Text(ref s) => ValueRef::Text(s),
Value::Text(ref s) => ValueRef::Text(s.as_bytes()),
Value::Blob(ref b) => ValueRef::Blob(b),
}
}
}
impl<'a, T> From<Option<T>> for ValueRef<'a>
where
T: Into<ValueRef<'a>>,
{
fn from(s: Option<T>) -> ValueRef<'a> {
match s {
Some(x) => x.into(),
None => ValueRef::Null,
}
}
}
#[cfg(any(feature = "functions", feature = "session", feature = "vtab"))]
impl<'a> ValueRef<'a> {
pub(crate) unsafe fn from_value(value: *mut crate::ffi::sqlite3_value) -> ValueRef<'a> {
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;
/// 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).
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
//! 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;
//! This file contains unit tests for `rusqlite::trace::config_log`. This
//! function affects SQLite process-wide and so is not safe to run as a normal
//! #[test] in the library.
#[cfg(feature = "trace")]
fn main() {
use lazy_static::lazy_static;
use std::os::raw::c_int;
use std::sync::Mutex;

View File

@ -1,9 +1,7 @@
//! Ensure we reject connections when SQLite is in single-threaded mode, as it
//! would violate safety if multiple Rust threads tried to use connections.
extern crate rusqlite;
extern crate libsqlite3_sys as ffi;
use rusqlite::ffi;
use rusqlite::Connection;
#[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);
}