aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Galenson <jgalenson@google.com>2021-05-26 19:59:01 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-05-26 19:59:01 +0000
commit578b5611237bab30e919e146ba056e09acbc584d (patch)
tree8d03944fce98d059f599af2acc141a828a87a56d
parent0f8d0e124a52acf424cd2be3a55d648f10b57955 (diff)
parent7f25b68873e1030334a1432b067bd93da10463ac (diff)
downloadrusqlite-578b5611237bab30e919e146ba056e09acbc584d.tar.gz
Upgrade rust/crates/rusqlite to 0.25.3 am: 570a929070 am: 68e86d6915 am: 7f25b68873
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/rusqlite/+/1713208 Change-Id: I3bc64efeb9123181f9868fcdcdc10ec2a762c26a
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--.github/workflows/main.yml2
-rw-r--r--.travis.yml48
-rw-r--r--Android.bp18
-rw-r--r--Cargo.toml13
-rw-r--r--Cargo.toml.orig21
-rw-r--r--LICENSE2
-rw-r--r--METADATA10
-rw-r--r--README.md38
-rw-r--r--TEST_MAPPING11
-rw-r--r--benches/exec.rs4
-rw-r--r--src/backup.rs145
-rw-r--r--src/blob/mod.rs102
-rw-r--r--src/blob/pos_io.rs20
-rw-r--r--src/busy.rs50
-rw-r--r--src/cache.rs139
-rw-r--r--src/collation.rs55
-rw-r--r--src/column.rs99
-rw-r--r--src/config.rs16
-rw-r--r--src/context.rs4
-rw-r--r--src/error.rs37
-rw-r--r--src/functions.rs292
-rw-r--r--src/hooks.rs173
-rw-r--r--src/inner_connection.rs36
-rw-r--r--src/lib.rs706
-rw-r--r--src/limits.rs9
-rw-r--r--src/load_extension_guard.rs2
-rw-r--r--src/params.rs358
-rw-r--r--src/pragma.rs107
-rw-r--r--src/raw_statement.rs35
-rw-r--r--src/row.rs177
-rw-r--r--src/session.rs182
-rw-r--r--src/statement.rs905
-rw-r--r--src/trace.rs25
-rw-r--r--src/transaction.rs242
-rw-r--r--src/types/chrono.rs207
-rw-r--r--src/types/from_sql.rs79
-rw-r--r--src/types/mod.rs280
-rw-r--r--src/types/serde_json.rs31
-rw-r--r--src/types/time.rs44
-rw-r--r--src/types/to_sql.rs93
-rw-r--r--src/types/url.rs29
-rw-r--r--src/types/value.rs22
-rw-r--r--src/types/value_ref.rs22
-rw-r--r--src/unlock_notify.rs13
-rw-r--r--src/util/small_cstr.rs1
-rw-r--r--src/util/sqlite_string.rs8
-rw-r--r--src/version.rs2
-rw-r--r--src/vtab/array.rs22
-rw-r--r--src/vtab/csvtab.rs91
-rw-r--r--src/vtab/mod.rs77
-rw-r--r--src/vtab/series.rs122
-rw-r--r--tests/vtab.rs17
53 files changed, 3192 insertions, 2053 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index deaa273..f4d4931 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,5 @@
{
"git": {
- "sha1": "cef6dbbb26211baebedbebe6e114f5bcf9be2431"
+ "sha1": "e7bb33a99ca6f122b5f338efd7889a6509b3dee0"
}
}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 38c28c9..2c2d7fc 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -91,7 +91,7 @@ jobs:
env:
RUSTFLAGS: -Zsanitizer=address
RUSTDOCFLAGS: -Zsanitizer=address
- ASAN_OPTIONS: 'detect_stack_use_after_return=1'
+ ASAN_OPTIONS: 'detect_stack_use_after_return=1:detect_leaks=0'
# Work around https://github.com/rust-lang/rust/issues/59125 by
# disabling backtraces. In an ideal world we'd probably suppress the
# leak sanitization, but we don't care about backtraces here, so long
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e51abea..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-sudo: false
-
-language: rust
-
-rust:
- - stable
- - beta
- - nightly
-
-matrix:
- fast_finish: true
- allow_failures:
- - rust: nightly
-
-addons:
- apt:
- packages: # recommanded versions for rust-bindgen
- - llvm-3.9-dev
- - libclang-3.9-dev
- - libsqlcipher-dev
-
-env: # specify the clang path for rust-bindgen
- - LIBCLANG_PATH=/usr/lib/llvm-3.9/lib
-
-script:
- - cargo build
- - cargo build --features bundled
- - cargo build --features sqlcipher
- - cargo build --features "bundled sqlcipher"
- - cargo test
- - 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 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 session serde_json trace url uuid vtab bundled buildtime_bindgen"
diff --git a/Android.bp b/Android.bp
index db8ee02..a699fe8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,4 +1,5 @@
// This file is generated by cargo2android.py --device --run --dependencies --features=modern_sqlite.
+// Do not modify this file as changes will be overridden on upgrade.
package {
default_applicable_licenses: ["external_rust_crates_rusqlite_license"],
@@ -36,13 +37,18 @@ rust_library {
}
// dependent_library ["feature_list"]
-// ahash-0.4.6
+// ahash-0.7.2 "folded_multiply,runtime-rng,specialize"
// bitflags-1.2.1 "default"
+// cfg-if-1.0.0
// fallible-iterator-0.2.0 "default,std"
// fallible-streaming-iterator-0.1.9
-// hashbrown-0.9.1 "ahash,default,inline-more"
-// hashlink-0.6.0
-// libsqlite3-sys-0.20.1 "bundled_bindings,default,min_sqlite_version_3_6_8,pkg-config,vcpkg"
-// memchr-2.3.4 "default,std,use_std"
+// getrandom-0.2.2 "std"
+// hashbrown-0.11.2 "ahash,default,inline-more"
+// hashlink-0.7.0
+// libc-0.2.94 "default,std"
+// libsqlite3-sys-0.22.2 "bundled_bindings,default,min_sqlite_version_3_6_8,pkg-config,vcpkg"
+// memchr-2.4.0 "default,std"
+// once_cell-1.7.2 "alloc,race,unstable"
// pkg-config-0.3.19
-// smallvec-1.5.1
+// smallvec-1.6.1
+// version_check-0.9.3
diff --git a/Cargo.toml b/Cargo.toml
index 8efebab..abcdbe1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@
[package]
edition = "2018"
name = "rusqlite"
-version = "0.24.2"
+version = "0.25.3"
authors = ["The rusqlite developers"]
description = "Ergonomic wrapper for SQLite"
documentation = "http://docs.rs/rusqlite/"
@@ -75,14 +75,14 @@ version = "0.2"
version = "0.1"
[dependencies.hashlink]
-version = "0.6"
+version = "0.7"
[dependencies.lazy_static]
version = "1.4"
optional = true
[dependencies.libsqlite3-sys]
-version = "0.20.1"
+version = "0.22.2"
[dependencies.memchr]
version = "2.3"
@@ -92,10 +92,10 @@ version = "1.0"
optional = true
[dependencies.smallvec]
-version = "1.0"
+version = "1.6.1"
[dependencies.time]
-version = "0.2"
+version = "0.2.23"
optional = true
[dependencies.url]
@@ -164,6 +164,3 @@ repository = "rusqlite/rusqlite"
[badges.maintenance]
status = "actively-developed"
-
-[badges.travis-ci]
-repository = "rusqlite/rusqlite"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 857bb86..c93656b 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
[package]
name = "rusqlite"
-version = "0.24.2"
+version = "0.25.3"
authors = ["The rusqlite developers"]
edition = "2018"
description = "Ergonomic wrapper for SQLite"
@@ -12,7 +12,6 @@ license = "MIT"
categories = ["database"]
[badges]
-travis-ci = { repository = "rusqlite/rusqlite" }
appveyor = { repository = "rusqlite/rusqlite" }
codecov = { repository = "rusqlite/rusqlite" }
maintenance = { status = "actively-developed" }
@@ -63,10 +62,10 @@ column_decltype = []
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
winsqlite3 = ["libsqlite3-sys/winsqlite3"]
-# Helper feature for enabling both `bundled` and most non-build-related optional
-# features or dependencies. This is useful for running tests / clippy / etc. New
-# features and optional dependencies that don't conflict with anything else
-# should be added here.
+# Helper feature for enabling both `bundled` and most non-build-related
+# optional features or dependencies (except `session`). This is useful for
+# running tests / clippy / etc. New features and optional dependencies that
+# don't conflict with anything else should be added here.
bundled-full = [
"array",
"backup",
@@ -96,9 +95,9 @@ bundled-full = [
]
[dependencies]
-time = { version = "0.2", optional = true }
+time = { version = "0.2.23", optional = true }
bitflags = "1.2"
-hashlink = "0.6"
+hashlink = "0.7"
chrono = { version = "0.4", optional = true }
serde_json = { version = "1.0", optional = true }
csv = { version = "1.1", optional = true }
@@ -109,7 +108,7 @@ fallible-iterator = "0.2"
fallible-streaming-iterator = "0.1"
memchr = "2.3"
uuid = { version = "0.8", optional = true }
-smallvec = "1.0"
+smallvec = "1.6.1"
[dev-dependencies]
doc-comment = "0.3"
@@ -118,13 +117,13 @@ lazy_static = "1.4"
regex = "1.3"
uuid = { version = "0.8", features = ["v4"] }
unicase = "2.6.0"
-# Use `bencher` over criterion becasue it builds much faster and we don't have
+# Use `bencher` over criterion because it builds much faster and we don't have
# many benchmarks
bencher = "0.1"
[dependencies.libsqlite3-sys]
path = "libsqlite3-sys"
-version = "0.20.1"
+version = "0.22.2"
[[test]]
name = "config_log"
diff --git a/LICENSE b/LICENSE
index 0245a83..9e5b9f7 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2020 The rusqlite developers
+Copyright (c) 2014-2021 The rusqlite developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/METADATA b/METADATA
index c2f666c..d84e059 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/rusqlite/rusqlite-0.24.2.crate"
+ value: "https://static.crates.io/crates/rusqlite/rusqlite-0.25.3.crate"
}
- version: "0.24.2"
+ version: "0.25.3"
license_type: NOTICE
last_upgrade_date {
- year: 2020
- month: 12
- day: 5
+ year: 2021
+ month: 5
+ day: 19
}
}
diff --git a/README.md b/README.md
index 09f0c20..e7d831c 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ fn main() -> Result<()> {
name TEXT NOT NULL,
data BLOB
)",
- params![],
+ [],
)?;
let me = Person {
id: 0,
@@ -44,7 +44,7 @@ fn main() -> Result<()> {
)?;
let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
- let person_iter = stmt.query_map(params![], |row| {
+ let person_iter = stmt.query_map([], |row| {
Ok(Person {
id: row.get(0)?,
name: row.get(1)?,
@@ -77,6 +77,7 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
* [`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.
+* `window` for [window function](https://www.sqlite.org/windowfunctions.html) support (`fun(...) OVER ...`). (Implies `functions`.)
* [`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.
@@ -97,16 +98,20 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
* `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the
`Url` type from the [`url` crate](https://crates.io/crates/url).
-* `bundled` uses a bundled version of sqlite3. This is a good option for cases where linking to sqlite3 is complicated, such as Windows.
+* `bundled` uses a bundled version of SQLite. This is a good option for cases where linking to SQLite 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 implementations 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.
+* `series` exposes [`generate_series(...)`](https://www.sqlite.org/series.html) Table-Valued Function. (Implies `vtab`.)
+* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust. (Implies `vtab`.)
+* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function. (Implies `vtab`.)
* `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. Requires `buildtime_bindgen` feature.
+* [`session`](https://sqlite.org/sessionintro.html), Session module extension. Requires `buildtime_bindgen` feature. (Implies `hooks`.)
+* `extra_check` fail when a query passed to execute is readonly or has a column count > 0.
+* `column_decltype` provides `columns()` method for Statements and Rows; omit if linking to a version of SQLite/SQLCipher compiled with `-DSQLITE_OMIT_DECLTYPE`.
+* `collation` exposes [`sqlite3_create_collation_v2`](https://sqlite.org/c3ref/create_collation.html).
## Notes on building rusqlite and libsqlite3-sys
@@ -119,21 +124,24 @@ You can adjust this behavior in a number of ways:
* If you use the `bundled` feature, `libsqlite3-sys` will use the
[cc](https://crates.io/crates/cc) crate to compile SQLite from source and
link against that. This source is embedded in the `libsqlite3-sys` crate and
- is currently SQLite 3.33.0 (as of `rusqlite` 0.24.1 / `libsqlite3-sys`
- 0.20.0). 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.35.4 (as of `rusqlite` 0.25.0 / `libsqlite3-sys`
+ 0.22.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
```toml
[dependencies.rusqlite]
- version = "0.24.2"
+ version = "0.25.1"
features = ["bundled"]
```
-* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite
- library.
+* When using the `bundled` feature, the build script will honor `SQLITE_MAX_VARIABLE_NUMBER` and `SQLITE_MAX_EXPR_DEPTH` variables. It will also honor a `LIBSQLITE3_FLAGS` variable, which can have a format like `"-USQLITE_ALPHA -DSQLITE_BETA SQLITE_GAMMA ..."`. That would disable the `SQLITE_ALPHA` flag, and set the `SQLITE_BETA` and `SQLITE_GAMMA` flags. (The initial `-D` can be omitted, as on the last one.)
+
+* When linking against a SQLite library already on the system (so *not* using the `bundled` feature), you can set the `SQLITE3_LIB_DIR` environment variable to point to a directory containing the library. You can also set the `SQLITE3_INCLUDE_DIR` variable to point to the directory containing `sqlite3.h`.
* 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,
which must be enabled by setting `VCPKGRS_DYNAMIC=1` environment variable before build.
`vcpkg install sqlite3:x64-windows` will install the required library.
+* When linking against a SQLite library already on the system, you can set the `SQLITE3_STATIC` environment variable to 1 to request that the library be statically instead of dynamically linked.
+
### Binding generation
@@ -169,7 +177,7 @@ If you enable the `modern_sqlite` feature, we'll use the bindings we would have
included with the bundled build. You generally should have `buildtime_bindgen`
enabled if you turn this on, as otherwise you'll need to keep the version of
SQLite you link with in sync with what rusqlite would have bundled, (usually the
-most recent release of sqlite). Failing to do this will cause a runtime error.
+most recent release of SQLite). Failing to do this will cause a runtime error.
## Contributing
@@ -177,7 +185,7 @@ Rusqlite has many features, and many of them impact the build configuration in
incompatible ways. This is unfortunate, and makes testing changes hard.
To help here: you generally should ensure that you run tests/lint for
-`--features bundled`, and `--features bundled-full session buildtime_bindgen`.
+`--features bundled`, and `--features "bundled-full session buildtime_bindgen"`.
If running bindgen is problematic for you, `--features bundled-full` enables
bundled and all features which don't require binding generation, and can be used
@@ -187,9 +195,9 @@ instead.
- Run `cargo fmt` to ensure your Rust code is correctly formatted.
- Ensure `cargo clippy --all-targets --workspace --features bundled` passes without warnings.
-- Ensure `cargo test --all-targets --workspace --features bundled-full session buildtime_bindgen` reports no failures.
+- Ensure `cargo clippy --all-targets --workspace --features "bundled-full session buildtime_bindgen"` passes without warnings.
- Ensure `cargo test --all-targets --workspace --features bundled` reports no failures.
-- Ensure `cargo test --all-targets --workspace --features bundled-full session buildtime_bindgen` reports no failures.
+- Ensure `cargo test --all-targets --workspace --features "bundled-full session buildtime_bindgen"` reports no failures.
## Author
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..e707449
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,11 @@
+// Generated by update_crate_tests.py for tests that depend on this crate.
+{
+ "presubmit": [
+ {
+ "name": "keystore2_test"
+ },
+ {
+ "name": "vpnprofilestore_test"
+ }
+ ]
+}
diff --git a/benches/exec.rs b/benches/exec.rs
index 360a98b..b95cb35 100644
--- a/benches/exec.rs
+++ b/benches/exec.rs
@@ -1,10 +1,10 @@
use bencher::{benchmark_group, benchmark_main, Bencher};
-use rusqlite::{Connection, NO_PARAMS};
+use rusqlite::Connection;
fn bench_execute(b: &mut Bencher) {
let db = Connection::open_in_memory().unwrap();
let sql = "PRAGMA user_version=1";
- b.iter(|| db.execute(sql, NO_PARAMS).unwrap());
+ b.iter(|| db.execute(sql, []).unwrap());
}
fn bench_execute_batch(b: &mut Bencher) {
diff --git a/src/backup.rs b/src/backup.rs
index 4654907..72d54e5 100644
--- a/src/backup.rs
+++ b/src/backup.rs
@@ -1,13 +1,14 @@
//! `feature = "backup"` Online SQLite backup API.
//!
-//! To create a `Backup`, you must have two distinct `Connection`s - one
+//! To create a [`Backup`], you must have two distinct [`Connection`]s - one
//! for the source (which can be used while the backup is running) and one for
-//! the destination (which cannot). A `Backup` handle exposes three methods:
-//! `step` will attempt to back up a specified number of pages, `progress` gets
-//! the current progress of the backup as of the last call to `step`, and
-//! `run_to_completion` will attempt to back up the entire source database,
-//! allowing you to specify how many pages are backed up at a time and how long
-//! the thread should sleep between chunks of pages.
+//! the destination (which cannot). A [`Backup`] handle exposes three methods:
+//! [`step`](Backup::step) will attempt to back up a specified number of pages,
+//! [`progress`](Backup::progress) gets the current progress of the backup as of
+//! the last call to [`step`](Backup::step), and
+//! [`run_to_completion`](Backup::run_to_completion) will attempt to back up the
+//! entire source database, allowing you to specify how many pages are backed up
+//! at a time and how long the thread should sleep between chunks of pages.
//!
//! The following example is equivalent to "Example 2: Online Backup of a
//! Running Database" from [SQLite's Online Backup API
@@ -130,7 +131,8 @@ impl Connection {
}
}
-/// `feature = "backup"` Possible successful results of calling `Backup::step`.
+/// `feature = "backup"` Possible successful results of calling
+/// [`Backup::step`].
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum StepResult {
@@ -141,7 +143,7 @@ pub enum StepResult {
/// backed up.
More,
- /// The step failed because appropriate locks could not be aquired. This is
+ /// The step failed because appropriate locks could not be acquired. This is
/// not a fatal error - the step can be retried.
Busy,
@@ -152,9 +154,10 @@ pub enum StepResult {
/// `feature = "backup"` Struct specifying the progress of a backup. The
/// percentage completion can be calculated as `(pagecount - remaining) /
-/// pagecount`. The progress of a 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.
+/// pagecount`. The progress of a backup is as of the last call to
+/// [`step`](Backup::step) - if the source database is modified after a call to
+/// [`step`](Backup::step), the progress value will become outdated and
+/// potentially incorrect.
#[derive(Copy, Clone, Debug)]
pub struct Progress {
/// Number of pages in the source database that still need to be backed up.
@@ -180,6 +183,7 @@ impl Backup<'_, '_> {
///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `NULL`.
+ #[inline]
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)
}
@@ -224,7 +228,9 @@ impl Backup<'_, '_> {
})
}
- /// Gets the progress of the backup as of the last call to `step`.
+ /// Gets the progress of the backup as of the last call to
+ /// [`step`](Backup::step).
+ #[inline]
pub fn progress(&self) -> Progress {
unsafe {
Progress {
@@ -238,7 +244,8 @@ impl Backup<'_, '_> {
/// negative, will attempt to back up all remaining pages. This will hold a
/// lock on the source database for the duration, so it is probably not
/// what you want for databases that are currently active (see
- /// `run_to_completion` for a better alternative).
+ /// [`run_to_completion`](Backup::run_to_completion) for a better
+ /// alternative).
///
/// # Failure
///
@@ -246,6 +253,7 @@ impl Backup<'_, '_> {
/// an error code other than `DONE`, `OK`, `BUSY`, or `LOCKED`. `BUSY` and
/// `LOCKED` are transient errors and are therefore returned as possible
/// `Ok` values.
+ #[inline]
pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
use self::StepResult::{Busy, Done, Locked, More};
@@ -259,12 +267,12 @@ impl Backup<'_, '_> {
}
}
- /// Attempts to run the entire backup. Will call `step(pages_per_step)` as
- /// many times as necessary, sleeping for `pause_between_pages` between
- /// each call to give the source database time to process any pending
- /// queries. This is a direct implementation of "Example 2: Online Backup
- /// of a Running Database" from [SQLite's Online Backup API
- /// documentation](https://www.sqlite.org/backup.html).
+ /// Attempts to run the entire backup. Will call
+ /// [`step(pages_per_step)`](Backup::step) as many times as necessary,
+ /// sleeping for `pause_between_pages` between each call to give the
+ /// source database time to process any pending queries. This is a
+ /// direct implementation of "Example 2: Online Backup of a Running
+ /// Database" from [SQLite's Online Backup API documentation](https://www.sqlite.org/backup.html).
///
/// If `progress` is not `None`, it will be called after each step with the
/// current progress of the backup. Note that is possible the progress may
@@ -273,7 +281,8 @@ impl Backup<'_, '_> {
///
/// # Failure
///
- /// Will return `Err` if any of the calls to `step` return `Err`.
+ /// Will return `Err` if any of the calls to [`step`](Backup::step) return
+ /// `Err`.
pub fn run_to_completion(
&self,
pages_per_step: c_int,
@@ -298,6 +307,7 @@ impl Backup<'_, '_> {
}
impl Drop for Backup<'_, '_> {
+ #[inline]
fn drop(&mut self) {
unsafe { ffi::sqlite3_backup_finish(self.b) };
}
@@ -306,96 +316,84 @@ impl Drop for Backup<'_, '_> {
#[cfg(test)]
mod test {
use super::Backup;
- use crate::{Connection, DatabaseName, NO_PARAMS};
+ use crate::{Connection, DatabaseName, Result};
use std::time::Duration;
#[test]
- fn test_backup() {
- let src = Connection::open_in_memory().unwrap();
+ fn test_backup() -> Result<()> {
+ let src = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42);
END;";
- src.execute_batch(sql).unwrap();
+ src.execute_batch(sql)?;
- let mut dst = Connection::open_in_memory().unwrap();
+ let mut dst = Connection::open_in_memory()?;
{
- let backup = Backup::new(&src, &mut dst).unwrap();
- backup.step(-1).unwrap();
+ let backup = Backup::new(&src, &mut dst)?;
+ backup.step(-1)?;
}
- let the_answer: i64 = dst
- .query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
assert_eq!(42, the_answer);
- src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
+ src.execute_batch("INSERT INTO foo VALUES(43)")?;
{
- let backup = Backup::new(&src, &mut dst).unwrap();
- backup
- .run_to_completion(5, Duration::from_millis(250), None)
- .unwrap();
+ let backup = Backup::new(&src, &mut dst)?;
+ backup.run_to_completion(5, Duration::from_millis(250), None)?;
}
- let the_answer: i64 = dst
- .query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
assert_eq!(42 + 43, the_answer);
+ Ok(())
}
#[test]
- fn test_backup_temp() {
- let src = Connection::open_in_memory().unwrap();
+ fn test_backup_temp() -> Result<()> {
+ let src = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TEMPORARY TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42);
END;";
- src.execute_batch(sql).unwrap();
+ src.execute_batch(sql)?;
- let mut dst = Connection::open_in_memory().unwrap();
+ let mut dst = Connection::open_in_memory()?;
{
let backup =
- Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
- .unwrap();
- backup.step(-1).unwrap();
+ Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)?;
+ backup.step(-1)?;
}
- let the_answer: i64 = dst
- .query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
assert_eq!(42, the_answer);
- src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
+ src.execute_batch("INSERT INTO foo VALUES(43)")?;
{
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();
+ Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)?;
+ backup.run_to_completion(5, Duration::from_millis(250), None)?;
}
- let the_answer: i64 = dst
- .query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
assert_eq!(42 + 43, the_answer);
+ Ok(())
}
#[test]
- fn test_backup_attached() {
- let src = Connection::open_in_memory().unwrap();
+ fn test_backup_attached() -> Result<()> {
+ let src = Connection::open_in_memory()?;
let sql = "ATTACH DATABASE ':memory:' AS my_attached;
BEGIN;
CREATE TABLE my_attached.foo(x INTEGER);
INSERT INTO my_attached.foo VALUES(42);
END;";
- src.execute_batch(sql).unwrap();
+ src.execute_batch(sql)?;
- let mut dst = Connection::open_in_memory().unwrap();
+ let mut dst = Connection::open_in_memory()?;
{
let backup = Backup::new_with_names(
@@ -403,17 +401,14 @@ mod test {
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main,
- )
- .unwrap();
- backup.step(-1).unwrap();
+ )?;
+ backup.step(-1)?;
}
- let the_answer: i64 = dst
- .query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
assert_eq!(42, the_answer);
- src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
+ src.execute_batch("INSERT INTO foo VALUES(43)")?;
{
let backup = Backup::new_with_names(
@@ -421,16 +416,12 @@ mod test {
DatabaseName::Attached("my_attached"),
&mut dst,
DatabaseName::Main,
- )
- .unwrap();
- backup
- .run_to_completion(5, Duration::from_millis(250), None)
- .unwrap();
+ )?;
+ backup.run_to_completion(5, Duration::from_millis(250), None)?;
}
- let the_answer: i64 = dst
- .query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
assert_eq!(42 + 43, the_answer);
+ Ok(())
}
}
diff --git a/src/blob/mod.rs b/src/blob/mod.rs
index 7d7ec3d..202f65d 100644
--- a/src/blob/mod.rs
+++ b/src/blob/mod.rs
@@ -47,18 +47,18 @@
//! functions take a `&mut [MaybeUninit<u8>]` as the destination buffer,
//! where the "normal" functions take a `&mut [u8]`.
//!
-//! Using `MaybeUninit` here can be more efficient in some cases, but is
+//! Using `MaybeUninit` here can be more efficient in some cases, but is
//! often inconvenient, so both are provided.
//!
//! 2. Exact/inexact refers to to whether or not the entire buffer must be
//! filled in order for the call to be considered a success.
//!
//! The "exact" functions require the provided buffer be entirely filled, or
-//! they return an error, wheras the "inexact" functions read as much out of
+//! they return an error, whereas the "inexact" functions read as much out of
//! the blob as is available, and return how much they were able to read.
//!
-//! The inexact functions are preferrable if you do not know the size of the
-//! blob already, and the exact functions are preferrable if you do.
+//! The inexact functions are preferable if you do not know the size of the
+//! blob already, and the exact functions are preferable if you do.
//!
//! ### Comparison to using the `std::io` traits:
//!
@@ -101,7 +101,7 @@
//!
//! ```rust
//! # use rusqlite::blob::ZeroBlob;
-//! # use rusqlite::{Connection, DatabaseName, NO_PARAMS};
+//! # use rusqlite::{Connection, DatabaseName};
//! # use std::error::Error;
//! # use std::io::{Read, Seek, SeekFrom, Write};
//! # fn main() -> Result<(), Box<dyn Error>> {
@@ -111,10 +111,7 @@
//! // Insert a BLOB into the `content` column of `test_table`. Note that the Blob
//! // I/O API provides no way of inserting or resizing BLOBs in the DB -- this
//! // must be done via SQL.
-//! db.execute(
-//! "INSERT INTO test_table (content) VALUES (ZEROBLOB(10))",
-//! NO_PARAMS,
-//! )?;
+//! db.execute("INSERT INTO test_table (content) VALUES (ZEROBLOB(10))", [])?;
//!
//! // Get the row id off the BLOB we just inserted.
//! let rowid = db.last_insert_rowid();
@@ -136,7 +133,10 @@
//!
//! // Insert another BLOB, this time using a parameter passed in from
//! // rust (potentially with a dynamic size).
-//! db.execute("INSERT INTO test_table (content) VALUES (?)", &[ZeroBlob(64)])?;
+//! db.execute(
+//! "INSERT INTO test_table (content) VALUES (?)",
+//! [ZeroBlob(64)],
+//! )?;
//!
//! // given a new row ID, we can reopen the blob on that row
//! let rowid = db.last_insert_rowid();
@@ -151,7 +151,7 @@
//!
//! ```rust
//! # use rusqlite::blob::ZeroBlob;
-//! # use rusqlite::{Connection, DatabaseName, NO_PARAMS};
+//! # use rusqlite::{Connection, DatabaseName};
//! # use std::error::Error;
//! # fn main() -> Result<(), Box<dyn Error>> {
//! let db = Connection::open_in_memory()?;
@@ -159,10 +159,7 @@
//! // Insert a blob into the `content` column of `test_table`. Note that the Blob
//! // I/O API provides no way of inserting or resizing blobs in the DB -- this
//! // must be done via SQL.
-//! db.execute(
-//! "INSERT INTO test_table (content) VALUES (ZEROBLOB(10))",
-//! NO_PARAMS,
-//! )?;
+//! db.execute("INSERT INTO test_table (content) VALUES (ZEROBLOB(10))", [])?;
//! // Get the row id off the blob we just inserted.
//! let rowid = db.last_insert_rowid();
//! // Open the blob we just inserted for IO.
@@ -177,7 +174,10 @@
//!
//! // Insert another blob, this time using a parameter passed in from
//! // rust (potentially with a dynamic size).
-//! db.execute("INSERT INTO test_table (content) VALUES (?)", &[ZeroBlob(64)])?;
+//! db.execute(
+//! "INSERT INTO test_table (content) VALUES (?)",
+//! [ZeroBlob(64)],
+//! )?;
//!
//! // given a new row ID, we can reopen the blob on that row
//! let rowid = db.last_insert_rowid();
@@ -196,8 +196,8 @@ use crate::{Connection, DatabaseName, Result};
mod pos_io;
-/// `feature = "blob"` Handle to an open BLOB. See [`rusqlite::blob`](crate::blob) documentation for
-/// in-depth discussion.
+/// `feature = "blob"` Handle to an open BLOB. See
+/// [`rusqlite::blob`](crate::blob) documentation for in-depth discussion.
pub struct Blob<'conn> {
conn: &'conn Connection,
blob: *mut ffi::sqlite3_blob,
@@ -214,6 +214,7 @@ impl Connection {
/// Will return `Err` if `db`/`table`/`column` cannot be converted to a
/// C-compatible string or if the underlying SQLite BLOB open call
/// fails.
+ #[inline]
pub fn blob_open<'a>(
&'a self,
db: DatabaseName<'_>,
@@ -252,6 +253,7 @@ impl Blob<'_> {
/// # Failure
///
/// Will return `Err` if the underlying SQLite BLOB reopen call fails.
+ #[inline]
pub fn reopen(&mut self, row: i64) -> Result<()> {
let rc = unsafe { ffi::sqlite3_blob_reopen(self.blob, row) };
if rc != ffi::SQLITE_OK {
@@ -262,17 +264,20 @@ impl Blob<'_> {
}
/// Return the size in bytes of the BLOB.
+ #[inline]
pub fn size(&self) -> i32 {
unsafe { ffi::sqlite3_blob_bytes(self.blob) }
}
/// Return the current size in bytes of the BLOB.
+ #[inline]
pub fn len(&self) -> usize {
use std::convert::TryInto;
self.size().try_into().unwrap()
}
/// Return true if the BLOB is empty.
+ #[inline]
pub fn is_empty(&self) -> bool {
self.size() == 0
}
@@ -286,10 +291,12 @@ impl Blob<'_> {
/// # Failure
///
/// Will return `Err` if the underlying SQLite close call fails.
+ #[inline]
pub fn close(mut self) -> Result<()> {
self.close_()
}
+ #[inline]
fn close_(&mut self) -> Result<()> {
let rc = unsafe { ffi::sqlite3_blob_close(self.blob) };
self.blob = ptr::null_mut();
@@ -304,6 +311,7 @@ impl io::Read for Blob<'_> {
/// # Failure
///
/// Will return `Err` if the underlying SQLite read call fails.
+ #[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let max_allowed_len = (self.size() - self.pos) as usize;
let n = min(buf.len(), max_allowed_len) as i32;
@@ -334,6 +342,7 @@ impl io::Write for Blob<'_> {
/// # Failure
///
/// Will return `Err` if the underlying SQLite write call fails.
+ #[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let max_allowed_len = (self.size() - self.pos) as usize;
let n = min(buf.len(), max_allowed_len) as i32;
@@ -350,6 +359,7 @@ impl io::Write for Blob<'_> {
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
}
+ #[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
@@ -357,6 +367,7 @@ impl io::Write for Blob<'_> {
impl io::Seek for Blob<'_> {
/// Seek to an offset, in bytes, in BLOB.
+ #[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let pos = match pos {
io::SeekFrom::Start(offset) => offset as i64,
@@ -383,6 +394,7 @@ impl io::Seek for Blob<'_> {
#[allow(unused_must_use)]
impl Drop for Blob<'_> {
+ #[inline]
fn drop(&mut self) {
self.close_();
}
@@ -398,6 +410,7 @@ impl Drop for Blob<'_> {
pub struct ZeroBlob(pub i32);
impl ToSql for ZeroBlob {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let ZeroBlob(length) = *self;
Ok(ToSqlOutput::ZeroBlob(length))
@@ -421,22 +434,18 @@ mod test {
}
#[test]
- fn test_blob() {
- let (db, rowid) = db_with_test_blob().unwrap();
+ fn test_blob() -> Result<()> {
+ let (db, rowid) = db_with_test_blob()?;
- 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)?;
assert_eq!(4, blob.write(b"Clob").unwrap());
assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10
assert_eq!(0, blob.write(b"5678").unwrap()); // still cannot write past 10
- blob.reopen(rowid).unwrap();
- blob.close().unwrap();
+ blob.reopen(rowid)?;
+ blob.close()?;
- blob = db
- .blob_open(DatabaseName::Main, "test", "content", rowid, true)
- .unwrap();
+ blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, true)?;
let mut bytes = [0u8; 5];
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
assert_eq!(&bytes, b"Clob5");
@@ -457,7 +466,7 @@ mod test {
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
assert_eq!(&bytes, b"56789");
- blob.reopen(rowid).unwrap();
+ blob.reopen(rowid)?;
assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
assert_eq!(&bytes, b"Clob5");
@@ -468,20 +477,19 @@ mod test {
// write_all should detect when we return Ok(0) because there is no space left,
// and return a write error
- blob.reopen(rowid).unwrap();
+ blob.reopen(rowid)?;
assert!(blob.write_all(b"0123456789x").is_err());
+ Ok(())
}
#[test]
- fn test_blob_in_bufreader() {
- let (db, rowid) = db_with_test_blob().unwrap();
+ fn test_blob_in_bufreader() -> Result<()> {
+ let (db, rowid) = db_with_test_blob()?;
- 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)?;
assert_eq!(8, blob.write(b"one\ntwo\n").unwrap());
- blob.reopen(rowid).unwrap();
+ blob.reopen(rowid)?;
let mut reader = BufReader::new(blob);
let mut line = String::new();
@@ -495,16 +503,15 @@ mod test {
line.truncate(0);
assert_eq!(2, reader.read_line(&mut line).unwrap());
assert_eq!("\0\0", line);
+ Ok(())
}
#[test]
- fn test_blob_in_bufwriter() {
- let (db, rowid) = db_with_test_blob().unwrap();
+ fn test_blob_in_bufwriter() -> Result<()> {
+ let (db, rowid) = db_with_test_blob()?;
{
- let blob = db
- .blob_open(DatabaseName::Main, "test", "content", rowid, false)
- .unwrap();
+ let blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
let mut writer = BufWriter::new(blob);
// trying to write too much and then flush should fail
@@ -515,18 +522,14 @@ mod test {
{
// ... but it should've written the first 10 bytes
- 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)?;
let mut bytes = [0u8; 10];
assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
assert_eq!(b"0123456701", &bytes);
}
{
- let blob = db
- .blob_open(DatabaseName::Main, "test", "content", rowid, false)
- .unwrap();
+ let blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
let mut writer = BufWriter::new(blob);
// trying to write_all too much should fail
@@ -536,12 +539,11 @@ mod test {
{
// ... but it should've written the first 10 bytes
- 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)?;
let mut bytes = [0u8; 10];
assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
assert_eq!(b"aaaaaaaaaa", &bytes);
+ Ok(())
}
}
}
diff --git a/src/blob/pos_io.rs b/src/blob/pos_io.rs
index 9f1f994..dd6167d 100644
--- a/src/blob/pos_io.rs
+++ b/src/blob/pos_io.rs
@@ -194,25 +194,18 @@ impl<'conn> Blob<'conn> {
#[cfg(test)]
mod test {
- use crate::{Connection, DatabaseName, NO_PARAMS};
+ use crate::{Connection, DatabaseName, Result};
// to ensure we don't modify seek pos
use std::io::Seek as _;
#[test]
- fn test_pos_io() {
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE test_table(content BLOB);")
- .unwrap();
- db.execute(
- "INSERT INTO test_table(content) VALUES (ZEROBLOB(10))",
- NO_PARAMS,
- )
- .unwrap();
+ fn test_pos_io() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE test_table(content BLOB);")?;
+ db.execute("INSERT INTO test_table(content) VALUES (ZEROBLOB(10))", [])?;
let rowid = db.last_insert_rowid();
- let mut blob = db
- .blob_open(DatabaseName::Main, "test_table", "content", rowid, false)
- .unwrap();
+ let mut blob = db.blob_open(DatabaseName::Main, "test_table", "content", rowid, false)?;
// modify the seek pos to ensure we aren't using it or modifying it.
blob.seek(std::io::SeekFrom::Start(1)).unwrap();
@@ -277,5 +270,6 @@ mod test {
let end_pos = blob.seek(std::io::SeekFrom::Current(0)).unwrap();
assert_eq!(end_pos, 1);
+ Ok(())
}
}
diff --git a/src/busy.rs b/src/busy.rs
index b87504a..447610e 100644
--- a/src/busy.rs
+++ b/src/busy.rs
@@ -19,8 +19,11 @@ impl Connection {
///
/// 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.
+ /// (using [`busy_handler`](Connection::busy_handler)) prior to calling this
+ /// routine, that other busy handler is cleared.
+ ///
+ /// Newly created connections currently have a default busy timeout of
+ /// 5000ms, but this may be subject to change.
pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
let ms: i32 = timeout
.as_secs()
@@ -33,8 +36,8 @@ impl Connection {
/// 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
+ /// 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
@@ -45,9 +48,13 @@ impl Connection {
///
/// 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.
+ /// handler. Note that calling [`busy_timeout()`](Connection::busy_timeout)
+ /// or evaluating `PRAGMA busy_timeout=N` will change the busy handler
+ /// and thus clear any previously set busy handler.
+ ///
+ /// Newly created connections default to a
+ /// [`busy_timeout()`](Connection::busy_timeout) handler with a timeout
+ /// of 5000ms, although this is subject to change.
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);
@@ -69,6 +76,7 @@ impl Connection {
}
impl InnerConnection {
+ #[inline]
fn busy_timeout(&mut self, timeout: c_int) -> Result<()> {
let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) };
self.decode_result(r)
@@ -82,26 +90,24 @@ mod test {
use std::thread;
use std::time::Duration;
- use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS};
+ use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior};
#[test]
- fn test_default_busy() {
+ fn test_default_busy() -> Result<()> {
let temp_dir = tempfile::tempdir().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!());
+ let mut db1 = Connection::open(&path)?;
+ let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?;
+ let db2 = Connection::open(&path)?;
+ let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!());
match r.unwrap_err() {
Error::SqliteFailure(err, _) => {
assert_eq!(err.code, ErrorCode::DatabaseBusy);
}
err => panic!("Unexpected error {}", err),
}
- tx1.rollback().unwrap();
+ tx1.rollback()
}
#[test]
@@ -126,9 +132,7 @@ mod test {
assert_eq!(tx.recv().unwrap(), 1);
let _ = db2
- .query_row("PRAGMA schema_version", NO_PARAMS, |row| {
- row.get::<_, i32>(0)
- })
+ .query_row("PRAGMA schema_version", [], |row| row.get::<_, i32>(0))
.expect("unexpected error");
child.join().unwrap();
@@ -137,9 +141,7 @@ mod test {
#[test]
#[ignore] // FIXME: unstable
fn test_busy_handler() {
- lazy_static::lazy_static! {
- static ref CALLED: AtomicBool = AtomicBool::new(false);
- }
+ static CALLED: AtomicBool = AtomicBool::new(false);
fn busy_handler(_: i32) -> bool {
CALLED.store(true, Ordering::Relaxed);
thread::sleep(Duration::from_millis(100));
@@ -165,9 +167,7 @@ mod test {
assert_eq!(tx.recv().unwrap(), 1);
let _ = db2
- .query_row("PRAGMA schema_version", NO_PARAMS, |row| {
- row.get::<_, i32>(0)
- })
+ .query_row("PRAGMA schema_version", [], |row| row.get::<_, i32>(0))
.expect("unexpected error");
assert_eq!(CALLED.load(Ordering::Relaxed), true);
diff --git a/src/cache.rs b/src/cache.rs
index 7dc9d23..89459ce 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -11,20 +11,20 @@ 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_cached`](Connection::prepare_cached) once it is dropped.
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn insert_new_people(conn: &Connection) -> Result<()> {
/// {
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
- /// stmt.execute(&["Joe Smith"])?;
+ /// stmt.execute(["Joe Smith"])?;
/// }
/// {
/// // This will return the same underlying SQLite statement handle without
/// // having to prepare it again.
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
- /// stmt.execute(&["Bob Jones"])?;
+ /// stmt.execute(["Bob Jones"])?;
/// }
/// Ok(())
/// }
@@ -34,6 +34,7 @@ impl Connection {
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
+ #[inline]
pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>> {
self.cache.get(self, sql)
}
@@ -43,11 +44,13 @@ impl Connection {
/// 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.
+ #[inline]
pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) {
self.cache.set_capacity(capacity)
}
/// Remove/finalize all prepared statements currently in the cache.
+ #[inline]
pub fn flush_prepared_statement_cache(&self) {
self.cache.flush()
}
@@ -60,7 +63,8 @@ pub struct StatementCache(RefCell<LruCache<Arc<str>, RawStatement>>);
/// Cacheable statement.
///
/// Statement will return automatically to the cache by default.
-/// If you want the statement to be discarded, call `discard()` on it.
+/// If you want the statement to be discarded, call
+/// [`discard()`](CachedStatement::discard) on it.
pub struct CachedStatement<'conn> {
stmt: Option<Statement<'conn>>,
cache: &'conn StatementCache,
@@ -69,12 +73,14 @@ pub struct CachedStatement<'conn> {
impl<'conn> Deref for CachedStatement<'conn> {
type Target = Statement<'conn>;
+ #[inline]
fn deref(&self) -> &Statement<'conn> {
self.stmt.as_ref().unwrap()
}
}
impl<'conn> DerefMut for CachedStatement<'conn> {
+ #[inline]
fn deref_mut(&mut self) -> &mut Statement<'conn> {
self.stmt.as_mut().unwrap()
}
@@ -82,6 +88,7 @@ impl<'conn> DerefMut for CachedStatement<'conn> {
impl Drop for CachedStatement<'_> {
#[allow(unused_must_use)]
+ #[inline]
fn drop(&mut self) {
if let Some(stmt) = self.stmt.take() {
self.cache.cache_stmt(unsafe { stmt.into_raw() });
@@ -90,6 +97,7 @@ impl Drop for CachedStatement<'_> {
}
impl CachedStatement<'_> {
+ #[inline]
fn new<'conn>(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
CachedStatement {
stmt: Some(stmt),
@@ -98,7 +106,8 @@ impl CachedStatement<'_> {
}
/// Discard the statement, preventing it from being returned to its
- /// `Connection`'s collection of cached statements.
+ /// [`Connection`]'s collection of cached statements.
+ #[inline]
pub fn discard(mut self) {
self.stmt = None;
}
@@ -106,10 +115,12 @@ impl CachedStatement<'_> {
impl StatementCache {
/// Create a statement cache.
+ #[inline]
pub fn with_capacity(capacity: usize) -> StatementCache {
StatementCache(RefCell::new(LruCache::new(capacity)))
}
+ #[inline]
fn set_capacity(&self, capacity: usize) {
self.0.borrow_mut().set_capacity(capacity)
}
@@ -155,6 +166,7 @@ impl StatementCache {
}
}
+ #[inline]
fn flush(&self) {
let mut cache = self.0.borrow_mut();
cache.clear()
@@ -164,7 +176,7 @@ impl StatementCache {
#[cfg(test)]
mod test {
use super::StatementCache;
- use crate::{Connection, NO_PARAMS};
+ use crate::{Connection, Result};
use fallible_iterator::FallibleIterator;
impl StatementCache {
@@ -182,8 +194,8 @@ mod test {
}
#[test]
- fn test_cache() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_cache() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let cache = &db.cache;
let initial_capacity = cache.capacity();
assert_eq!(0, cache.len());
@@ -191,43 +203,35 @@ mod test {
let sql = "PRAGMA schema_version";
{
- let mut stmt = db.prepare_cached(sql).unwrap();
+ let mut stmt = db.prepare_cached(sql)?;
assert_eq!(0, cache.len());
- assert_eq!(
- 0,
- stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
- );
+ assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
}
assert_eq!(1, cache.len());
{
- let mut stmt = db.prepare_cached(sql).unwrap();
+ let mut stmt = db.prepare_cached(sql)?;
assert_eq!(0, cache.len());
- assert_eq!(
- 0,
- stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
- );
+ assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
}
assert_eq!(1, cache.len());
cache.clear();
assert_eq!(0, cache.len());
assert_eq!(initial_capacity, cache.capacity());
+ Ok(())
}
#[test]
- fn test_set_capacity() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_set_capacity() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let cache = &db.cache;
let sql = "PRAGMA schema_version";
{
- let mut stmt = db.prepare_cached(sql).unwrap();
+ let mut stmt = db.prepare_cached(sql)?;
assert_eq!(0, cache.len());
- assert_eq!(
- 0,
- stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
- );
+ assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
}
assert_eq!(1, cache.len());
@@ -235,64 +239,53 @@ mod test {
assert_eq!(0, cache.len());
{
- let mut stmt = db.prepare_cached(sql).unwrap();
+ let mut stmt = db.prepare_cached(sql)?;
assert_eq!(0, cache.len());
- assert_eq!(
- 0,
- stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
- );
+ assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
}
assert_eq!(0, cache.len());
db.set_prepared_statement_cache_capacity(8);
{
- let mut stmt = db.prepare_cached(sql).unwrap();
+ let mut stmt = db.prepare_cached(sql)?;
assert_eq!(0, cache.len());
- assert_eq!(
- 0,
- stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
- );
+ assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
}
assert_eq!(1, cache.len());
+ Ok(())
}
#[test]
- fn test_discard() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_discard() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let cache = &db.cache;
let sql = "PRAGMA schema_version";
{
- let mut stmt = db.prepare_cached(sql).unwrap();
+ let mut stmt = db.prepare_cached(sql)?;
assert_eq!(0, cache.len());
- assert_eq!(
- 0,
- stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
- );
+ assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
stmt.discard();
}
assert_eq!(0, cache.len());
+ Ok(())
}
#[test]
- fn test_ddl() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_ddl() -> Result<()> {
+ let db = Connection::open_in_memory()?;
db.execute_batch(
r#"
CREATE TABLE foo (x INT);
INSERT INTO foo VALUES (1);
"#,
- )
- .unwrap();
+ )?;
let sql = "SELECT * FROM foo";
{
- let mut stmt = db.prepare_cached(sql).unwrap();
- assert_eq!(
- Ok(Some(1i32)),
- stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).next()
- );
+ let mut stmt = db.prepare_cached(sql)?;
+ assert_eq!(Ok(Some(1i32)), stmt.query([])?.map(|r| r.get(0)).next());
}
db.execute_batch(
@@ -300,61 +293,55 @@ mod test {
ALTER TABLE foo ADD COLUMN y INT;
UPDATE foo SET y = 2;
"#,
- )
- .unwrap();
+ )?;
{
- let mut stmt = db.prepare_cached(sql).unwrap();
+ let mut stmt = db.prepare_cached(sql)?;
assert_eq!(
Ok(Some((1i32, 2i32))),
- stmt.query(NO_PARAMS)
- .unwrap()
- .map(|r| Ok((r.get(0)?, r.get(1)?)))
- .next()
+ stmt.query([])?.map(|r| Ok((r.get(0)?, r.get(1)?))).next()
);
}
+ Ok(())
}
#[test]
- fn test_connection_close() {
- let conn = Connection::open_in_memory().unwrap();
- conn.prepare_cached("SELECT * FROM sqlite_master;").unwrap();
+ fn test_connection_close() -> Result<()> {
+ let conn = Connection::open_in_memory()?;
+ conn.prepare_cached("SELECT * FROM sqlite_master;")?;
conn.close().expect("connection not closed");
+ Ok(())
}
#[test]
- fn test_cache_key() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_cache_key() -> Result<()> {
+ let db = Connection::open_in_memory()?;
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();
+ let mut stmt = db.prepare_cached(sql)?;
assert_eq!(0, cache.len());
- assert_eq!(
- 0,
- stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
- );
+ assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
}
assert_eq!(1, cache.len());
{
- let mut stmt = db.prepare_cached(sql).unwrap();
+ let mut stmt = db.prepare_cached(sql)?;
assert_eq!(0, cache.len());
- assert_eq!(
- 0,
- stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
- );
+ assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
}
assert_eq!(1, cache.len());
+ Ok(())
}
#[test]
- fn test_empty_stmt() {
- let conn = Connection::open_in_memory().unwrap();
- conn.prepare_cached("").unwrap();
+ fn test_empty_stmt() -> Result<()> {
+ let conn = Connection::open_in_memory()?;
+ conn.prepare_cached("")?;
+ Ok(())
}
}
diff --git a/src/collation.rs b/src/collation.rs
index 1168b75..2b93a9a 100644
--- a/src/collation.rs
+++ b/src/collation.rs
@@ -15,9 +15,10 @@ unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
impl Connection {
/// `feature = "collation"` Add or modify a collation.
- pub fn create_collation<C>(&self, collation_name: &str, x_compare: C) -> Result<()>
+ #[inline]
+ pub fn create_collation<'c, C>(&'c self, collation_name: &str, x_compare: C) -> Result<()>
where
- C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
+ C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'c,
{
self.db
.borrow_mut()
@@ -25,6 +26,7 @@ impl Connection {
}
/// `feature = "collation"` Collation needed callback
+ #[inline]
pub fn collation_needed(
&self,
x_coll_needed: fn(&Connection, &str) -> Result<()>,
@@ -33,15 +35,16 @@ impl Connection {
}
/// `feature = "collation"` Remove collation.
+ #[inline]
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<()>
+ fn create_collation<'c, C>(&'c mut self, collation_name: &str, x_compare: C) -> Result<()>
where
- C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
+ C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'c,
{
unsafe extern "C" fn call_boxed_closure<C>(
arg1: *mut c_void,
@@ -93,7 +96,13 @@ impl InnerConnection {
Some(free_boxed_value::<C>),
)
};
- self.decode_result(r)
+ let res = self.decode_result(r);
+ // The xDestroy callback is not called if the sqlite3_create_collation_v2()
+ // function fails.
+ if res.is_err() {
+ drop(unsafe { Box::from_raw(boxed_f) });
+ }
+ res
}
fn collation_needed(
@@ -101,6 +110,7 @@ impl InnerConnection {
x_coll_needed: fn(&Connection, &str) -> Result<()>,
) -> Result<()> {
use std::mem;
+ #[allow(clippy::needless_return)]
unsafe extern "C" fn collation_needed_callback(
arg1: *mut c_void,
arg2: *mut ffi::sqlite3,
@@ -120,7 +130,7 @@ impl InnerConnection {
let conn = Connection::from_handle(arg2).unwrap();
let collation_name = {
let c_slice = CStr::from_ptr(arg3).to_bytes();
- str::from_utf8(c_slice).expect("illegal coallation sequence name")
+ str::from_utf8(c_slice).expect("illegal collation sequence name")
};
callback(&conn, collation_name)
});
@@ -139,6 +149,7 @@ impl InnerConnection {
self.decode_result(r)
}
+ #[inline]
fn remove_collation(&mut self, collation_name: &str) -> Result<()> {
let c_name = str_to_cstring(collation_name)?;
let r = unsafe {
@@ -157,7 +168,7 @@ impl InnerConnection {
#[cfg(test)]
mod test {
- use crate::{Connection, Result, NO_PARAMS};
+ use crate::{Connection, Result};
use fallible_streaming_iterator::FallibleStreamingIterator;
use std::cmp::Ordering;
use unicase::UniCase;
@@ -167,26 +178,24 @@ mod test {
}
#[test]
- fn test_unicase() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_unicase() -> Result<()> {
+ let db = Connection::open_in_memory()?;
- db.create_collation("unicase", unicase_compare).unwrap();
+ db.create_collation("unicase", unicase_compare)?;
- collate(db);
+ collate(db)
}
- fn collate(db: Connection) {
+ fn collate(db: Connection) -> Result<()> {
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);
+ )?;
+ let mut stmt = db.prepare("SELECT DISTINCT bar COLLATE unicase FROM foo ORDER BY 1")?;
+ let rows = stmt.query([])?;
+ assert_eq!(rows.count()?, 1);
+ Ok(())
}
fn collation_needed(db: &Connection, collation_name: &str) -> Result<()> {
@@ -198,9 +207,9 @@ mod test {
}
#[test]
- fn test_collation_needed() {
- let db = Connection::open_in_memory().unwrap();
- db.collation_needed(collation_needed).unwrap();
- collate(db);
+ fn test_collation_needed() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.collation_needed(collation_needed)?;
+ collate(db)
}
}
diff --git a/src/column.rs b/src/column.rs
index 4f6daac..b9122c4 100644
--- a/src/column.rs
+++ b/src/column.rs
@@ -11,11 +11,13 @@ pub struct Column<'stmt> {
impl Column<'_> {
/// Returns the name of the column.
+ #[inline]
pub fn name(&self) -> &str {
self.name
}
/// Returns the type of the column (`None` for expression).
+ #[inline]
pub fn decl_type(&self) -> Option<&str> {
self.decl_type
}
@@ -23,6 +25,10 @@ impl Column<'_> {
impl Statement<'_> {
/// Get all the column names in the result set of the prepared statement.
+ ///
+ /// If associated DB schema can be altered concurrently, you should make
+ /// sure that current statement has already been stepped once before
+ /// calling this method.
pub fn column_names(&self) -> Vec<&str> {
let n = self.column_count();
let mut cols = Vec::with_capacity(n as usize);
@@ -35,10 +41,35 @@ impl Statement<'_> {
/// Return the number of columns in the result set returned by the prepared
/// statement.
+ ///
+ /// If associated DB schema can be altered concurrently, you should make
+ /// sure that current statement has already been stepped once before
+ /// calling this method.
+ #[inline]
pub fn column_count(&self) -> usize {
self.stmt.column_count()
}
+ /// Check that column name reference lifetime is limited:
+ /// https://www.sqlite.org/c3ref/column_name.html
+ /// > The returned string pointer is valid...
+ ///
+ /// `column_name` reference can become invalid if `stmt` is reprepared
+ /// (because of schema change) when `query_row` is called. So we assert
+ /// that a compilation error happens if this reference is kept alive:
+ /// ```compile_fail
+ /// use rusqlite::{Connection, Result};
+ /// fn main() -> Result<()> {
+ /// let db = Connection::open_in_memory()?;
+ /// let mut stmt = db.prepare("SELECT 1 as x")?;
+ /// let column_name = stmt.column_name(0)?;
+ /// let x = stmt.query_row([], |r| r.get::<_, i64>(0))?; // E0502
+ /// assert_eq!(1, x);
+ /// assert_eq!("x", column_name);
+ /// Ok(())
+ /// }
+ /// ```
+ #[inline]
pub(super) fn column_name_unwrap(&self, col: usize) -> &str {
// Just panic if the bounds are wrong for now, we never call this
// without checking first.
@@ -48,12 +79,17 @@ impl Statement<'_> {
/// Returns the name assigned to a particular column in the result set
/// returned by the prepared statement.
///
+ /// If associated DB schema can be altered concurrently, you should make
+ /// sure that current statement has already been stepped once before
+ /// calling this method.
+ ///
/// ## 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.
+ #[inline]
pub fn column_name(&self, col: usize) -> Result<&str> {
self.stmt
.column_name(col)
@@ -68,10 +104,15 @@ impl Statement<'_> {
/// 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.
///
+ /// If associated DB schema can be altered concurrently, you should make
+ /// sure that current statement has already been stepped once before
+ /// calling this method.
+ ///
/// # Failure
///
/// Will return an `Error::InvalidColumnName` when there is no column with
/// the specified `name`.
+ #[inline]
pub fn column_index(&self, name: &str) -> Result<usize> {
let bytes = name.as_bytes();
let n = self.column_count();
@@ -86,6 +127,10 @@ impl Statement<'_> {
}
/// Returns a slice describing the columns of the result of the query.
+ ///
+ /// If associated DB schema can be altered concurrently, you should make
+ /// sure that current statement has already been stepped once before
+ /// calling this method.
#[cfg(feature = "column_decltype")]
pub fn columns(&self) -> Vec<Column> {
let n = self.column_count();
@@ -104,26 +149,31 @@ impl Statement<'_> {
impl<'stmt> Rows<'stmt> {
/// Get all the column names.
+ #[inline]
pub fn column_names(&self) -> Option<Vec<&str>> {
self.stmt.map(Statement::column_names)
}
/// Return the number of columns.
+ #[inline]
pub fn column_count(&self) -> Option<usize> {
self.stmt.map(Statement::column_count)
}
/// Return the name of the column.
+ #[inline]
pub fn column_name(&self, col: usize) -> Option<Result<&str>> {
self.stmt.map(|stmt| stmt.column_name(col))
}
/// Return the index of the column.
+ #[inline]
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.
+ #[inline]
#[cfg(feature = "column_decltype")]
pub fn columns(&self) -> Option<Vec<Column>> {
self.stmt.map(Statement::columns)
@@ -132,26 +182,31 @@ impl<'stmt> Rows<'stmt> {
impl<'stmt> Row<'stmt> {
/// Get all the column names of the Row.
+ #[inline]
pub fn column_names(&self) -> Vec<&str> {
self.stmt.column_names()
}
/// Return the number of columns in the current row.
+ #[inline]
pub fn column_count(&self) -> usize {
self.stmt.column_count()
}
/// Return the name of the column.
+ #[inline]
pub fn column_name(&self, col: usize) -> Result<&str> {
self.stmt.column_name(col)
}
/// Return the index of the column.
+ #[inline]
pub fn column_index(&self, name: &str) -> Result<usize> {
self.stmt.column_index(name)
}
/// Returns a slice describing the columns of the Row.
+ #[inline]
#[cfg(feature = "column_decltype")]
pub fn columns(&self) -> Vec<Column> {
self.stmt.columns()
@@ -160,15 +215,15 @@ impl<'stmt> Row<'stmt> {
#[cfg(test)]
mod test {
- use crate::Connection;
+ use crate::{Connection, Result};
#[test]
#[cfg(feature = "column_decltype")]
- fn test_columns() {
+ fn test_columns() -> Result<()> {
use super::Column;
- let db = Connection::open_in_memory().unwrap();
- let query = db.prepare("SELECT * FROM sqlite_master").unwrap();
+ let db = Connection::open_in_memory()?;
+ let query = db.prepare("SELECT * FROM sqlite_master")?;
let columns = query.columns();
let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
assert_eq!(
@@ -180,22 +235,22 @@ mod test {
&column_types[..3],
&[Some("text"), Some("text"), Some("text"),]
);
+ Ok(())
}
#[test]
- fn test_column_name_in_error() {
+ fn test_column_name_in_error() -> Result<()> {
use crate::{types::Type, Error};
- let db = Connection::open_in_memory().unwrap();
+ let db = Connection::open_in_memory()?;
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();
+ )?;
+ let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?;
+ let mut rows = stmt.query([])?;
+ let row = rows.next()?.unwrap();
match row.get::<_, String>(0).unwrap_err() {
Error::InvalidColumnType(idx, name, ty) => {
assert_eq!(idx, 0);
@@ -216,5 +271,27 @@ mod test {
panic!("Unexpected error type: {:?}", e);
}
}
+ Ok(())
+ }
+
+ /// `column_name` reference should stay valid until `stmt` is reprepared (or
+ /// reset) even if DB schema is altered (SQLite documentation is
+ /// ambiguous here because it says reference "is valid until (...) the next
+ /// call to sqlite3_column_name() or sqlite3_column_name16() on the same
+ /// column.". We assume that reference is valid if only
+ /// `sqlite3_column_name()` is used):
+ #[test]
+ #[cfg(feature = "modern_sqlite")]
+ fn test_column_name_reference() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE y (x);")?;
+ let stmt = db.prepare("SELECT x FROM y;")?;
+ let column_name = stmt.column_name(0)?;
+ assert_eq!("x", column_name);
+ db.execute_batch("ALTER TABLE y RENAME COLUMN x TO z;")?;
+ // column name is not refreshed until statement is re-prepared
+ let same_column_name = stmt.column_name(0)?;
+ assert_eq!(same_column_name, column_name);
+ Ok(())
}
}
diff --git a/src/config.rs b/src/config.rs
index 797069e..ff4762e 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -10,6 +10,7 @@ use crate::{Connection, Result};
#[repr(i32)]
#[allow(non_snake_case, non_camel_case_types)]
#[non_exhaustive]
+#[allow(clippy::upper_case_acronyms)]
pub enum DbConfig {
//SQLITE_DBCONFIG_MAINDBNAME = 1000, /* const char* */
//SQLITE_DBCONFIG_LOOKASIDE = 1001, /* void* int int */
@@ -74,6 +75,7 @@ impl Connection {
/// 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
+ #[inline]
pub fn db_config(&self, config: DbConfig) -> Result<bool> {
let c = self.db.borrow();
unsafe {
@@ -102,6 +104,7 @@ impl Connection {
/// enable QPSG
/// - SQLITE_DBCONFIG_TRIGGER_EQP: `false` to disable output for trigger
/// programs, `true` to enable it
+ #[inline]
pub fn set_db_config(&self, config: DbConfig, new_val: bool) -> Result<bool> {
let c = self.db.borrow_mut();
unsafe {
@@ -120,13 +123,13 @@ impl Connection {
#[cfg(test)]
mod test {
use super::DbConfig;
- use crate::Connection;
+ use crate::{Connection, Result};
#[test]
- fn test_db_config() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_db_config() -> Result<()> {
+ let db = Connection::open_in_memory()?;
- let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY).unwrap();
+ let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY)?;
assert_eq!(
db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY, opposite),
Ok(opposite)
@@ -136,9 +139,7 @@ mod test {
Ok(opposite)
);
- let opposite = !db
- .db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER)
- .unwrap();
+ let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER)?;
assert_eq!(
db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER, opposite),
Ok(opposite)
@@ -147,5 +148,6 @@ mod test {
db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER),
Ok(opposite)
);
+ Ok(())
}
}
diff --git a/src/context.rs b/src/context.rs
index b7e8bc8..ce60ebb 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -12,6 +12,10 @@ use crate::types::{ToSqlOutput, ValueRef};
#[cfg(feature = "array")]
use crate::vtab::array::{free_array, ARRAY_TYPE};
+// This function is inline despite it's size because what's in the ToSqlOutput
+// is often known to the compiler, and thus const prop/DCE can substantially
+// simplify the function.
+#[inline]
pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<'_>) {
let value = match *result {
ToSqlOutput::Borrowed(v) => v,
diff --git a/src/error.rs b/src/error.rs
index 98583cb..a8d3f23 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -43,11 +43,12 @@ pub enum Error {
/// Error converting a file path to a string.
InvalidPath(PathBuf),
- /// Error returned when an `execute` call returns rows.
+ /// Error returned when an [`execute`](crate::Connection::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.
+ /// for [`query_row`](crate::Connection::query_row)) did not return any.
QueryReturnedNoRows,
/// Error when the value of a particular column is requested, but the index
@@ -67,29 +68,31 @@ pub enum Error {
/// 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`](crate::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.
+ /// Error returned by [`vtab::Values::get`](crate::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`).
+ /// [`create_scalar_function`](crate::Connection::create_scalar_function)).
#[cfg(feature = "functions")]
#[allow(dead_code)]
UserFunctionError(Box<dyn error::Error + Send + Sync + 'static>),
- /// Error available for the implementors of the `ToSql` trait.
+ /// Error available for the implementors of the
+ /// [`ToSql`](crate::types::ToSql) trait.
ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
/// 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`).
+ /// [`create_module`](crate::Connection::create_module)).
#[cfg(feature = "vtab")]
#[allow(dead_code)]
ModuleError(String),
@@ -98,8 +101,10 @@ pub enum Error {
#[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`.
+ /// An error returned when
+ /// [`Context::get_aux`](crate::functions::Context::get_aux) attempts to
+ /// retrieve data of a different type than what had been stored using
+ /// [`Context::set_aux`](crate::functions::Context::set_aux).
#[cfg(feature = "functions")]
GetAuxWrongType,
@@ -111,8 +116,9 @@ pub enum Error {
InvalidParameterCount(usize, usize),
/// Returned from various functions in the Blob IO positional API. For
- /// example, [`Blob::raw_read_at_exact`](crate::blob::Blob::raw_read_at_exact)
- /// will return it if the blob has insufficient data.
+ /// example,
+ /// [`Blob::raw_read_at_exact`](crate::blob::Blob::raw_read_at_exact) will
+ /// return it if the blob has insufficient data.
#[cfg(feature = "blob")]
BlobSizeError,
}
@@ -165,12 +171,14 @@ impl PartialEq for Error {
}
impl From<str::Utf8Error> for Error {
+ #[cold]
fn from(err: str::Utf8Error) -> Error {
Error::Utf8Error(err)
}
}
impl From<::std::ffi::NulError> for Error {
+ #[cold]
fn from(err: ::std::ffi::NulError) -> Error {
Error::NulError(err)
}
@@ -181,6 +189,7 @@ 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 {
+ #[cold]
fn from(err: FromSqlError) -> Error {
// The error type requires index and type fields, but they aren't known in this
// context.
@@ -326,10 +335,12 @@ impl error::Error for Error {
// These are public but not re-exported by lib.rs, so only visible within crate.
+#[cold]
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
Error::SqliteFailure(ffi::Error::new(code), message)
}
+#[cold]
pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error {
let message = if db.is_null() {
None
diff --git a/src/functions.rs b/src/functions.rs
index 3531391..ce908d5 100644
--- a/src/functions.rs
+++ b/src/functions.rs
@@ -4,14 +4,14 @@
//!
//! Adding a `regexp` function to a connection in which compiled regular
//! expressions are cached in a `HashMap`. For an alternative implementation
-//! that uses SQLite's [Function Auxilliary Data](https://www.sqlite.org/c3ref/get_auxdata.html) interface
+//! that uses SQLite's [Function Auxiliary Data](https://www.sqlite.org/c3ref/get_auxdata.html) interface
//! to avoid recompiling regular expressions, see the unit tests for this
//! module.
//!
//! ```rust
//! use regex::Regex;
//! use rusqlite::functions::FunctionFlags;
-//! use rusqlite::{Connection, Error, Result, NO_PARAMS};
+//! use rusqlite::{Connection, Error, Result};
//! use std::sync::Arc;
//! type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
//!
@@ -22,10 +22,9 @@
//! FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
//! move |ctx| {
//! assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
-//! let regexp: Arc<Regex> = ctx
-//! .get_or_create_aux(0, |vr| -> Result<_, BoxError> {
-//! Ok(Regex::new(vr.as_str()?)?)
-//! })?;
+//! let regexp: Arc<Regex> = ctx.get_or_create_aux(0, |vr| -> Result<_, BoxError> {
+//! Ok(Regex::new(vr.as_str()?)?)
+//! })?;
//! let is_match = {
//! let text = ctx
//! .get_raw(1)
@@ -44,17 +43,18 @@
//! let db = Connection::open_in_memory()?;
//! add_regexp_function(&db)?;
//!
-//! let is_match: bool = db.query_row(
-//! "SELECT regexp('[aeiou]*', 'aaaaeeeiii')",
-//! NO_PARAMS,
-//! |row| row.get(0),
-//! )?;
+//! let is_match: bool =
+//! db.query_row("SELECT regexp('[aeiou]*', 'aaaaeeeiii')", [], |row| {
+//! row.get(0)
+//! })?;
//!
//! assert!(is_match);
//! Ok(())
//! }
//! ```
use std::any::Any;
+use std::marker::PhantomData;
+use std::ops::Deref;
use std::os::raw::{c_int, c_void};
use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe};
use std::ptr;
@@ -113,11 +113,13 @@ pub struct Context<'a> {
impl Context<'_> {
/// Returns the number of arguments to the function.
+ #[inline]
pub fn len(&self) -> usize {
self.args.len()
}
/// Returns `true` when there is no argument.
+ #[inline]
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
@@ -126,7 +128,8 @@ impl Context<'_> {
///
/// # Failure
///
- /// Will panic if `idx` is greater than or equal to `self.len()`.
+ /// Will panic if `idx` is greater than or equal to
+ /// [`self.len()`](Context::len).
///
/// Will return Err if the underlying SQLite type cannot be converted to a
/// `T`.
@@ -156,17 +159,20 @@ impl Context<'_> {
///
/// # Failure
///
- /// Will panic if `idx` is greater than or equal to `self.len()`.
+ /// Will panic if `idx` is greater than or equal to
+ /// [`self.len()`](Context::len).
+ #[inline]
pub fn get_raw(&self, idx: usize) -> ValueRef<'_> {
let arg = self.args[idx];
unsafe { ValueRef::from_value(arg) }
}
- /// Fetch or insert the the auxilliary data associated with a particular
+ /// Fetch or insert the auxiliary data associated with a particular
/// parameter. This is intended to be an easier-to-use way of fetching it
- /// compared to calling `get_aux` and `set_aux` separately.
+ /// compared to calling [`get_aux`](Context::get_aux) and
+ /// [`set_aux`](Context::set_aux) separately.
///
- /// See https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of
+ /// See `https://www.sqlite.org/c3ref/get_auxdata.html` for a discussion of
/// this feature, or the unit tests of this module for an example.
pub fn get_or_create_aux<T, E, F>(&self, arg: c_int, func: F) -> Result<Arc<T>>
where
@@ -185,8 +191,8 @@ impl Context<'_> {
}
}
- /// Sets the auxilliary data associated with a particular parameter. See
- /// https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of
+ /// Sets the auxiliary data associated with a particular parameter. See
+ /// `https://www.sqlite.org/c3ref/get_auxdata.html` for a discussion of
/// this feature, or the unit tests of this module for an example.
pub fn set_aux<T: Send + Sync + 'static>(&self, arg: c_int, value: T) -> Result<Arc<T>> {
let orig: Arc<T> = Arc::new(value);
@@ -204,10 +210,10 @@ impl Context<'_> {
Ok(orig)
}
- /// Gets the auxilliary data that was associated with a given parameter via
- /// `set_aux`. Returns `Ok(None)` if no data has been associated, and
- /// Ok(Some(v)) if it has. Returns an error if the requested type does not
- /// match.
+ /// Gets the auxiliary data that was associated with a given parameter via
+ /// [`set_aux`](Context::set_aux). Returns `Ok(None)` if no data has been
+ /// associated, and Ok(Some(v)) if it has. Returns an error if the
+ /// requested type does not match.
pub fn get_aux<T: Send + Sync + 'static>(&self, arg: c_int) -> Result<Option<Arc<T>>> {
let p = unsafe { ffi::sqlite3_get_auxdata(self.ctx, arg) as *const AuxInner };
if p.is_null() {
@@ -219,6 +225,37 @@ impl Context<'_> {
.map_err(|_| Error::GetAuxWrongType)
}
}
+
+ /// Get the db connection handle via [sqlite3_context_db_handle](https://www.sqlite.org/c3ref/context_db_handle.html)
+ ///
+ /// # Safety
+ ///
+ /// This function is marked unsafe because there is a potential for other
+ /// references to the connection to be sent across threads, [see this comment](https://github.com/rusqlite/rusqlite/issues/643#issuecomment-640181213).
+ pub unsafe fn get_connection(&self) -> Result<ConnectionRef<'_>> {
+ let handle = ffi::sqlite3_context_db_handle(self.ctx);
+ Ok(ConnectionRef {
+ conn: Connection::from_handle(handle)?,
+ phantom: PhantomData,
+ })
+ }
+}
+
+/// A reference to a connection handle with a lifetime bound to something.
+pub struct ConnectionRef<'ctx> {
+ // comes from Connection::from_handle(sqlite3_context_db_handle(...))
+ // and is non-owning
+ conn: Connection,
+ phantom: PhantomData<&'ctx Context<'ctx>>,
+}
+
+impl Deref for ConnectionRef<'_> {
+ type Target = Connection;
+
+ #[inline]
+ fn deref(&self) -> &Connection {
+ &self.conn
+ }
}
type AuxInner = Arc<dyn Any + Send + Sync + 'static>;
@@ -234,20 +271,25 @@ where
T: ToSql,
{
/// Initializes the aggregation context. Will be called prior to the first
- /// call to `step()` to set up the context for an invocation of the
- /// function. (Note: `init()` will not be called if there are no rows.)
- fn init(&self) -> A;
+ /// call to [`step()`](Aggregate::step) to set up the context for an
+ /// invocation of the function. (Note: `init()` will not be called if
+ /// there are no rows.)
+ fn init(&self, _: &mut Context<'_>) -> Result<A>;
/// "step" function called once for each row in an aggregate group. May be
/// called 0 times if there are no rows.
fn step(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
/// Computes and returns the final result. Will be called exactly once for
- /// each invocation of the function. If `step()` was called at least
- /// once, will be given `Some(A)` (the same `A` as was created by
- /// `init` and given to `step`); if `step()` was not called (because
- /// the function is running against 0 rows), will be given `None`.
- fn finalize(&self, _: Option<A>) -> Result<T>;
+ /// each invocation of the function. If [`step()`](Aggregate::step) was
+ /// called at least once, will be given `Some(A)` (the same `A` as was
+ /// created by [`init`](Aggregate::init) and given to
+ /// [`step`](Aggregate::step)); if [`step()`](Aggregate::step) was not
+ /// called (because the function is running against 0 rows), will be
+ /// given `None`.
+ ///
+ /// The passed context will have no arguments.
+ fn finalize(&self, _: &mut Context<'_>, _: Option<A>) -> Result<T>;
}
/// `feature = "window"` WindowAggregate is the callback interface for
@@ -292,6 +334,7 @@ bitflags::bitflags! {
}
impl Default for FunctionFlags {
+ #[inline]
fn default() -> FunctionFlags {
FunctionFlags::SQLITE_UTF8
}
@@ -307,12 +350,13 @@ impl Connection {
/// given the same input, `deterministic` should be `true`.
///
/// The function will remain available until the connection is closed or
- /// until it is explicitly removed via `remove_function`.
+ /// until it is explicitly removed via
+ /// [`remove_function`](Connection::remove_function).
///
/// # Example
///
/// ```rust
- /// # use rusqlite::{Connection, Result, NO_PARAMS};
+ /// # use rusqlite::{Connection, Result};
/// # use rusqlite::functions::FunctionFlags;
/// fn scalar_function_example(db: Connection) -> Result<()> {
/// db.create_scalar_function(
@@ -325,7 +369,7 @@ impl Connection {
/// },
/// )?;
///
- /// let six_halved: f64 = db.query_row("SELECT halve(6)", NO_PARAMS, |r| r.get(0))?;
+ /// let six_halved: f64 = db.query_row("SELECT halve(6)", [], |r| r.get(0))?;
/// assert_eq!(six_halved, 3f64);
/// Ok(())
/// }
@@ -334,15 +378,16 @@ impl Connection {
/// # Failure
///
/// Will return Err if the function could not be attached to the connection.
- pub fn create_scalar_function<F, T>(
- &self,
+ #[inline]
+ pub fn create_scalar_function<'c, F, T>(
+ &'c self,
fn_name: &str,
n_arg: c_int,
flags: FunctionFlags,
x_func: F,
) -> Result<()>
where
- F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'static,
+ F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'c,
T: ToSql,
{
self.db
@@ -356,6 +401,7 @@ impl Connection {
/// # Failure
///
/// Will return Err if the function could not be attached to the connection.
+ #[inline]
pub fn create_aggregate_function<A, D, T>(
&self,
fn_name: &str,
@@ -376,9 +422,10 @@ impl Connection {
/// `feature = "window"` Attach a user-defined aggregate window function to
/// this database connection.
///
- /// See https://sqlite.org/windowfunctions.html#udfwinfunc for more
+ /// See `https://sqlite.org/windowfunctions.html#udfwinfunc` for more
/// information.
#[cfg(feature = "window")]
+ #[inline]
pub fn create_window_function<A, W, T>(
&self,
fn_name: &str,
@@ -400,26 +447,28 @@ impl Connection {
/// database connection.
///
/// `fn_name` and `n_arg` should match the name and number of arguments
- /// given to `create_scalar_function` or `create_aggregate_function`.
+ /// given to [`create_scalar_function`](Connection::create_scalar_function)
+ /// or [`create_aggregate_function`](Connection::create_aggregate_function).
///
/// # Failure
///
/// Will return Err if the function could not be removed.
+ #[inline]
pub fn remove_function(&self, fn_name: &str, n_arg: c_int) -> Result<()> {
self.db.borrow_mut().remove_function(fn_name, n_arg)
}
}
impl InnerConnection {
- fn create_scalar_function<F, T>(
- &mut self,
+ fn create_scalar_function<'c, F, T>(
+ &'c mut self,
fn_name: &str,
n_arg: c_int,
flags: FunctionFlags,
x_func: F,
) -> Result<()>
where
- F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'static,
+ F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'c,
T: ToSql,
{
unsafe extern "C" fn call_boxed_closure<F, T>(
@@ -585,13 +634,15 @@ unsafe extern "C" fn call_boxed_step<A, D, T>(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
- if (*pac as *mut A).is_null() {
- *pac = Box::into_raw(Box::new((*boxed_aggr).init()));
- }
let mut ctx = Context {
ctx,
args: slice::from_raw_parts(argv, argc as usize),
};
+
+ if (*pac as *mut A).is_null() {
+ *pac = Box::into_raw(Box::new((*boxed_aggr).init(&mut ctx)?));
+ }
+
(*boxed_aggr).step(&mut ctx, &mut **pac)
});
let r = match r {
@@ -676,7 +727,8 @@ where
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
);
- (*boxed_aggr).finalize(a)
+ let mut ctx = Context { ctx, args: &mut [] };
+ (*boxed_aggr).finalize(&mut ctx, a)
});
let t = match r {
Err(_) => {
@@ -746,7 +798,7 @@ mod test {
#[cfg(feature = "window")]
use crate::functions::WindowAggregate;
use crate::functions::{Aggregate, Context, FunctionFlags};
- use crate::{Connection, Error, Result, NO_PARAMS};
+ use crate::{Connection, Error, Result};
fn half(ctx: &Context<'_>) -> Result<c_double> {
assert_eq!(ctx.len(), 1, "called with unexpected number of arguments");
@@ -755,39 +807,39 @@ mod test {
}
#[test]
- fn test_function_half() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_function_half() -> Result<()> {
+ let db = Connection::open_in_memory()?;
db.create_scalar_function(
"half",
1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
half,
- )
- .unwrap();
- let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
+ )?;
+ let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
- assert!((3f64 - result.unwrap()).abs() < EPSILON);
+ assert!((3f64 - result?).abs() < EPSILON);
+ Ok(())
}
#[test]
- fn test_remove_function() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_remove_function() -> Result<()> {
+ let db = Connection::open_in_memory()?;
db.create_scalar_function(
"half",
1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
half,
- )
- .unwrap();
- let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
- assert!((3f64 - result.unwrap()).abs() < EPSILON);
+ )?;
+ let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
+ assert!((3f64 - result?).abs() < EPSILON);
- db.remove_function("half", 1).unwrap();
- let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
+ db.remove_function("half", 1)?;
+ let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
assert!(result.is_err());
+ Ok(())
}
- // This implementation of a regexp scalar function uses SQLite's auxilliary data
+ // This implementation of a regexp scalar function uses SQLite's auxiliary data
// (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular
// expression multiple times within one query.
fn regexp_with_auxilliary(ctx: &Context<'_>) -> Result<bool> {
@@ -811,8 +863,8 @@ mod test {
}
#[test]
- fn test_function_regexp_with_auxilliary() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_function_regexp_with_auxilliary() -> Result<()> {
+ let db = Connection::open_in_memory()?;
db.execute_batch(
"BEGIN;
CREATE TABLE foo (x string);
@@ -820,35 +872,32 @@ mod test {
INSERT INTO foo VALUES ('lXsi');
INSERT INTO foo VALUES ('lisX');
END;",
- )
- .unwrap();
+ )?;
db.create_scalar_function(
"regexp",
2,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
regexp_with_auxilliary,
- )
- .unwrap();
+ )?;
let result: Result<bool> =
- db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| {
- r.get(0)
- });
+ db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", [], |r| r.get(0));
- assert_eq!(true, result.unwrap());
+ assert_eq!(true, result?);
let result: Result<i64> = db.query_row(
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
- NO_PARAMS,
+ [],
|r| r.get(0),
);
- assert_eq!(2, result.unwrap());
+ assert_eq!(2, result?);
+ Ok(())
}
#[test]
- fn test_varargs_function() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_varargs_function() -> Result<()> {
+ let db = Connection::open_in_memory()?;
db.create_scalar_function(
"my_concat",
-1,
@@ -863,50 +912,48 @@ mod test {
Ok(ret)
},
- )
- .unwrap();
+ )?;
for &(expected, query) in &[
("", "SELECT my_concat()"),
("onetwo", "SELECT my_concat('one', 'two')"),
("abc", "SELECT my_concat('a', 'b', 'c')"),
] {
- let result: String = db.query_row(query, NO_PARAMS, |r| r.get(0)).unwrap();
+ let result: String = db.query_row(query, [], |r| r.get(0))?;
assert_eq!(expected, result);
}
+ Ok(())
}
#[test]
- fn test_get_aux_type_checking() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_get_aux_type_checking() -> Result<()> {
+ let db = Connection::open_in_memory()?;
db.create_scalar_function("example", 2, FunctionFlags::default(), |ctx| {
if !ctx.get::<bool>(1)? {
ctx.set_aux::<i64>(0, 100)?;
} else {
assert_eq!(ctx.get_aux::<String>(0), Err(Error::GetAuxWrongType));
- assert_eq!(*ctx.get_aux::<i64>(0).unwrap().unwrap(), 100);
+ assert_eq!(*ctx.get_aux::<i64>(0)?.unwrap(), 100);
}
Ok(true)
- })
- .unwrap();
+ })?;
- let res: bool = db
- .query_row(
- "SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)",
- NO_PARAMS,
- |r| r.get(0),
- )
- .unwrap();
+ let res: bool = db.query_row(
+ "SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)",
+ [],
+ |r| r.get(0),
+ )?;
// Doesn't actually matter, we'll assert in the function if there's a problem.
assert!(res);
+ Ok(())
}
struct Sum;
struct Count;
impl Aggregate<i64, Option<i64>> for Sum {
- fn init(&self) -> i64 {
- 0
+ fn init(&self, _: &mut Context<'_>) -> Result<i64> {
+ Ok(0)
}
fn step(&self, ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
@@ -914,14 +961,14 @@ mod test {
Ok(())
}
- fn finalize(&self, sum: Option<i64>) -> Result<Option<i64>> {
+ fn finalize(&self, _: &mut Context<'_>, sum: Option<i64>) -> Result<Option<i64>> {
Ok(sum)
}
}
impl Aggregate<i64, i64> for Count {
- fn init(&self) -> i64 {
- 0
+ fn init(&self, _: &mut Context<'_>) -> Result<i64> {
+ Ok(0)
}
fn step(&self, _ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
@@ -929,58 +976,56 @@ mod test {
Ok(())
}
- fn finalize(&self, sum: Option<i64>) -> Result<i64> {
+ fn finalize(&self, _: &mut Context<'_>, sum: Option<i64>) -> Result<i64> {
Ok(sum.unwrap_or(0))
}
}
#[test]
- fn test_sum() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_sum() -> Result<()> {
+ let db = Connection::open_in_memory()?;
db.create_aggregate_function(
"my_sum",
1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
Sum,
- )
- .unwrap();
+ )?;
// sum should return NULL when given no columns (contrast with count below)
let no_result = "SELECT my_sum(i) FROM (SELECT 2 AS i WHERE 1 <> 1)";
- let result: Option<i64> = db.query_row(no_result, NO_PARAMS, |r| r.get(0)).unwrap();
+ let result: Option<i64> = db.query_row(no_result, [], |r| r.get(0))?;
assert!(result.is_none());
let single_sum = "SELECT my_sum(i) FROM (SELECT 2 AS i UNION ALL SELECT 2)";
- let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
+ let result: i64 = db.query_row(single_sum, [], |r| r.get(0))?;
assert_eq!(4, result);
let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \
2, 1)";
- let result: (i64, i64) = db
- .query_row(dual_sum, NO_PARAMS, |r| Ok((r.get(0)?, r.get(1)?)))
- .unwrap();
+ let result: (i64, i64) = db.query_row(dual_sum, [], |r| Ok((r.get(0)?, r.get(1)?)))?;
assert_eq!((4, 2), result);
+ Ok(())
}
#[test]
- fn test_count() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_count() -> Result<()> {
+ let db = Connection::open_in_memory()?;
db.create_aggregate_function(
"my_count",
-1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
Count,
- )
- .unwrap();
+ )?;
// count should return 0 when given no columns (contrast with sum above)
let no_result = "SELECT my_count(i) FROM (SELECT 2 AS i WHERE 1 <> 1)";
- let result: i64 = db.query_row(no_result, NO_PARAMS, |r| r.get(0)).unwrap();
+ let result: i64 = db.query_row(no_result, [], |r| r.get(0))?;
assert_eq!(result, 0);
let single_sum = "SELECT my_count(i) FROM (SELECT 2 AS i UNION ALL SELECT 2)";
- let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
+ let result: i64 = db.query_row(single_sum, [], |r| r.get(0))?;
assert_eq!(2, result);
+ Ok(())
}
#[cfg(feature = "window")]
@@ -997,17 +1042,16 @@ mod test {
#[test]
#[cfg(feature = "window")]
- fn test_window() {
+ fn test_window() -> Result<()> {
use fallible_iterator::FallibleIterator;
- let db = Connection::open_in_memory().unwrap();
+ let db = Connection::open_in_memory()?;
db.create_window_function(
"sumint",
1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
Sum,
- )
- .unwrap();
+ )?;
db.execute_batch(
"CREATE TABLE t3(x, y);
INSERT INTO t3 VALUES('a', 4),
@@ -1015,24 +1059,19 @@ mod test {
('c', 3),
('d', 8),
('e', 1);",
- )
- .unwrap();
+ )?;
- let mut stmt = db
- .prepare(
- "SELECT x, sumint(y) OVER (
+ let mut stmt = db.prepare(
+ "SELECT x, sumint(y) OVER (
ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS sum_y
FROM t3 ORDER BY x;",
- )
- .unwrap();
+ )?;
let results: Vec<(String, i64)> = stmt
- .query(NO_PARAMS)
- .unwrap()
+ .query([])?
.map(|row| Ok((row.get("x")?, row.get("sum_y")?)))
- .collect()
- .unwrap();
+ .collect()?;
let expected = vec![
("a".to_owned(), 9),
("b".to_owned(), 12),
@@ -1041,5 +1080,6 @@ mod test {
("e".to_owned(), 9),
];
assert_eq!(expected, results);
+ Ok(())
}
}
diff --git a/src/hooks.rs b/src/hooks.rs
index 53dc041..71e2f4c 100644
--- a/src/hooks.rs
+++ b/src/hooks.rs
@@ -2,7 +2,7 @@
#![allow(non_camel_case_types)]
use std::os::raw::{c_char, c_int, c_void};
-use std::panic::catch_unwind;
+use std::panic::{catch_unwind, RefUnwindSafe};
use std::ptr;
use crate::ffi;
@@ -13,6 +13,7 @@ use crate::{Connection, InnerConnection};
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(i32)]
#[non_exhaustive]
+#[allow(clippy::upper_case_acronyms)]
pub enum Action {
/// Unsupported / unexpected action
UNKNOWN = -1,
@@ -25,6 +26,7 @@ pub enum Action {
}
impl From<i32> for Action {
+ #[inline]
fn from(code: i32) -> Action {
match code {
ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
@@ -40,9 +42,10 @@ impl Connection {
/// a transaction is committed.
///
/// The callback returns `true` to rollback.
- pub fn commit_hook<F>(&self, hook: Option<F>)
+ #[inline]
+ pub fn commit_hook<'c, F>(&'c self, hook: Option<F>)
where
- F: FnMut() -> bool + Send + 'static,
+ F: FnMut() -> bool + Send + 'c,
{
self.db.borrow_mut().commit_hook(hook);
}
@@ -51,9 +54,10 @@ impl Connection {
/// a transaction is committed.
///
/// The callback returns `true` to rollback.
- pub fn rollback_hook<F>(&self, hook: Option<F>)
+ #[inline]
+ pub fn rollback_hook<'c, F>(&'c self, hook: Option<F>)
where
- F: FnMut() + Send + 'static,
+ F: FnMut() + Send + 'c,
{
self.db.borrow_mut().rollback_hook(hook);
}
@@ -68,24 +72,42 @@ impl Connection {
/// - 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>)
+ #[inline]
+ pub fn update_hook<'c, F>(&'c self, hook: Option<F>)
where
- F: FnMut(Action, &str, &str, i64) + Send + 'static,
+ F: FnMut(Action, &str, &str, i64) + Send + 'c,
{
self.db.borrow_mut().update_hook(hook);
}
+
+ /// `feature = "hooks"` Register a query progress callback.
+ ///
+ /// The parameter `num_ops` is the approximate number of virtual machine
+ /// instructions that are evaluated between successive invocations of the
+ /// `handler`. If `num_ops` is less than one then the progress handler
+ /// is disabled.
+ ///
+ /// If the progress callback returns `true`, the operation is interrupted.
+ pub fn progress_handler<F>(&self, num_ops: c_int, handler: Option<F>)
+ where
+ F: FnMut() -> bool + Send + RefUnwindSafe + 'static,
+ {
+ self.db.borrow_mut().progress_handler(num_ops, handler);
+ }
}
impl InnerConnection {
+ #[inline]
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()>);
+ self.progress_handler(0, None::<fn() -> bool>);
}
- fn commit_hook<F>(&mut self, hook: Option<F>)
+ fn commit_hook<'c, F>(&'c mut self, hook: Option<F>)
where
- F: FnMut() -> bool + Send + 'static,
+ F: FnMut() -> bool + Send + 'c,
{
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
where
@@ -132,9 +154,9 @@ impl InnerConnection {
self.free_commit_hook = free_commit_hook;
}
- fn rollback_hook<F>(&mut self, hook: Option<F>)
+ fn rollback_hook<'c, F>(&'c mut self, hook: Option<F>)
where
- F: FnMut() + Send + 'static,
+ F: FnMut() + Send + 'c,
{
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
where
@@ -173,9 +195,9 @@ impl InnerConnection {
self.free_rollback_hook = free_rollback_hook;
}
- fn update_hook<F>(&mut self, hook: Option<F>)
+ fn update_hook<'c, F>(&'c mut self, hook: Option<F>)
where
- F: FnMut(Action, &str, &str, i64) + Send + 'static,
+ F: FnMut(Action, &str, &str, i64) + Send + 'c,
{
unsafe extern "C" fn call_boxed_closure<F>(
p_arg: *mut c_void,
@@ -236,6 +258,45 @@ impl InnerConnection {
}
self.free_update_hook = free_update_hook;
}
+
+ fn progress_handler<F>(&mut self, num_ops: c_int, handler: Option<F>)
+ where
+ F: FnMut() -> bool + Send + RefUnwindSafe + '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_handler: *mut F = p_arg as *mut F;
+ (*boxed_handler)()
+ });
+ if let Ok(true) = r {
+ 1
+ } else {
+ 0
+ }
+ }
+
+ match handler {
+ Some(handler) => {
+ let boxed_handler = Box::new(handler);
+ unsafe {
+ ffi::sqlite3_progress_handler(
+ self.db(),
+ num_ops,
+ Some(call_boxed_closure::<F>),
+ &*boxed_handler as *const F as *mut _,
+ )
+ }
+ self.progress_handler = Some(boxed_handler);
+ }
+ _ => {
+ unsafe { ffi::sqlite3_progress_handler(self.db(), num_ops, None, ptr::null_mut()) }
+ self.progress_handler = None;
+ }
+ };
+ }
}
unsafe fn free_boxed_hook<F>(p: *mut c_void) {
@@ -245,29 +306,26 @@ unsafe fn free_boxed_hook<F>(p: *mut c_void) {
#[cfg(test)]
mod test {
use super::Action;
- use crate::Connection;
- use lazy_static::lazy_static;
+ use crate::{Connection, Result};
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
- fn test_commit_hook() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_commit_hook() -> Result<()> {
+ let db = Connection::open_in_memory()?;
- lazy_static! {
- static ref CALLED: AtomicBool = AtomicBool::new(false);
- }
+ let mut called = false;
db.commit_hook(Some(|| {
- CALLED.store(true, Ordering::Relaxed);
+ called = true;
false
}));
- db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
- .unwrap();
- assert!(CALLED.load(Ordering::Relaxed));
+ db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
+ assert!(called);
+ Ok(())
}
#[test]
- fn test_fn_commit_hook() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_fn_commit_hook() -> Result<()> {
+ let db = Connection::open_in_memory()?;
fn hook() -> bool {
true
@@ -276,39 +334,68 @@ mod test {
db.commit_hook(Some(hook));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
.unwrap_err();
+ Ok(())
}
#[test]
- fn test_rollback_hook() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_rollback_hook() -> Result<()> {
+ let db = Connection::open_in_memory()?;
- lazy_static! {
- static ref CALLED: AtomicBool = AtomicBool::new(false);
- }
+ let mut called = false;
db.rollback_hook(Some(|| {
- CALLED.store(true, Ordering::Relaxed);
+ called = true;
}));
- db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
- .unwrap();
- assert!(CALLED.load(Ordering::Relaxed));
+ db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")?;
+ assert!(called);
+ Ok(())
}
#[test]
- fn test_update_hook() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_update_hook() -> Result<()> {
+ let db = Connection::open_in_memory()?;
- lazy_static! {
- static ref CALLED: AtomicBool = AtomicBool::new(false);
- }
+ let mut called = 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);
+ called = true;
}));
- db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
- db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
+ db.execute_batch("CREATE TABLE foo (t TEXT)")?;
+ db.execute_batch("INSERT INTO foo VALUES ('lisa')")?;
+ assert!(called);
+ Ok(())
+ }
+
+ #[test]
+ fn test_progress_handler() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+
+ static CALLED: AtomicBool = AtomicBool::new(false);
+ db.progress_handler(
+ 1,
+ Some(|| {
+ CALLED.store(true, Ordering::Relaxed);
+ false
+ }),
+ );
+ db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
assert!(CALLED.load(Ordering::Relaxed));
+ Ok(())
+ }
+
+ #[test]
+ fn test_progress_handler_interrupt() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+
+ fn handler() -> bool {
+ true
+ }
+
+ db.progress_handler(1, Some(handler));
+ db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
+ .unwrap_err();
+ Ok(())
}
}
diff --git a/src/inner_connection.rs b/src/inner_connection.rs
index dd786fe..0fbf5c1 100644
--- a/src/inner_connection.rs
+++ b/src/inner_connection.rs
@@ -31,11 +31,14 @@ pub struct InnerConnection {
pub free_rollback_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>,
#[cfg(feature = "hooks")]
pub free_update_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>,
+ #[cfg(feature = "hooks")]
+ pub progress_handler: Option<Box<dyn FnMut() -> bool + Send>>,
owned: bool,
}
impl InnerConnection {
#[allow(clippy::mutex_atomic)]
+ #[inline]
pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
InnerConnection {
db,
@@ -46,6 +49,8 @@ impl InnerConnection {
free_rollback_hook: None,
#[cfg(feature = "hooks")]
free_update_hook: None,
+ #[cfg(feature = "hooks")]
+ progress_handler: None,
owned,
}
}
@@ -121,14 +126,17 @@ impl InnerConnection {
}
}
+ #[inline]
pub fn db(&self) -> *mut ffi::sqlite3 {
self.db
}
+ #[inline]
pub fn decode_result(&mut self, code: c_int) -> Result<()> {
unsafe { InnerConnection::decode_result_raw(self.db(), code) }
}
+ #[inline]
unsafe fn decode_result_raw(db: *mut ffi::sqlite3, code: c_int) -> Result<()> {
if code == ffi::SQLITE_OK {
Ok(())
@@ -165,12 +173,14 @@ impl InnerConnection {
}
}
+ #[inline]
pub fn get_interrupt_handle(&self) -> InterruptHandle {
InterruptHandle {
db_lock: Arc::clone(&self.interrupt_lock),
}
}
+ #[inline]
#[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) };
@@ -203,6 +213,7 @@ impl InnerConnection {
}
}
+ #[inline]
pub fn last_insert_rowid(&self) -> i64 {
unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
}
@@ -250,7 +261,6 @@ impl InnerConnection {
let tail = if c_tail.is_null() {
0
} else {
- // TODO nightly feature ptr_offset_from #41079
let n = (c_tail as isize) - (c_sql as isize);
if n <= 0 || n >= len as isize {
0
@@ -263,10 +273,12 @@ impl InnerConnection {
}))
}
+ #[inline]
pub fn changes(&mut self) -> usize {
unsafe { ffi::sqlite3_changes(self.db()) as usize }
}
+ #[inline]
pub fn is_autocommit(&self) -> bool {
unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
}
@@ -287,11 +299,13 @@ impl InnerConnection {
}
#[cfg(not(feature = "hooks"))]
+ #[inline]
fn remove_hooks(&mut self) {}
}
impl Drop for InnerConnection {
#[allow(unused_must_use)]
+ #[inline]
fn drop(&mut self) {
use std::thread::panicking;
@@ -410,18 +424,14 @@ fn ensure_safe_sqlite_threading_mode() -> Result<()> {
}
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);
+ if ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) != ffi::SQLITE_OK || ffi::sqlite3_initialize() != ffi::SQLITE_OK {
+ panic!(
+ "Could not ensure safe initialization of SQLite.\n\
+ To fix this, either:\n\
+ * Upgrade SQLite to at least version 3.7.0\n\
+ * Ensure that SQLite has been initialized in Multi-thread or Serialized mode and call\n\
+ rusqlite::bypass_sqlite_initialization() prior to your first connection attempt."
+ );
}
}
});
diff --git a/src/lib.rs b/src/lib.rs
index 53f1773..926cad3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,7 +20,7 @@
//! name TEXT NOT NULL,
//! data BLOB
//! )",
-//! params![],
+//! [],
//! )?;
//! let me = Person {
//! id: 0,
@@ -33,7 +33,7 @@
//! )?;
//!
//! let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
-//! let person_iter = stmt.query_map(params![], |row| {
+//! let person_iter = stmt.query_map([], |row| {
//! Ok(Person {
//! id: row.get(0)?,
//! name: row.get(1)?,
@@ -77,6 +77,7 @@ pub use crate::ffi::ErrorCode;
pub use crate::hooks::Action;
#[cfg(feature = "load_extension")]
pub use crate::load_extension_guard::LoadExtensionGuard;
+pub use crate::params::{params_from_iter, Params, ParamsFromIter};
pub use crate::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows};
pub use crate::statement::{Statement, StatementStatus};
pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
@@ -107,6 +108,7 @@ mod inner_connection;
pub mod limits;
#[cfg(feature = "load_extension")]
mod load_extension_guard;
+mod params;
mod pragma;
mod raw_statement;
mod row;
@@ -127,11 +129,16 @@ pub(crate) use util::SmallCString;
// Number of cached prepared statements we'll hold on to.
const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
-/// To be used when your statement has no [parameter](https://sqlite.org/lang_expr.html#varparam).
+/// To be used when your statement has no [parameter][sqlite-varparam].
+///
+/// [sqlite-varparam]: https://sqlite.org/lang_expr.html#varparam
+///
+/// This is deprecated in favor of using an empty array literal.
+#[deprecated = "Use an empty array instead; `stmt.execute(NO_PARAMS)` => `stmt.execute([])`"]
pub const NO_PARAMS: &[&dyn ToSql] = &[];
-/// A macro making it more convenient to pass heterogeneous lists
-/// of parameters as a `&[&dyn ToSql]`.
+/// A macro making it more convenient to pass heterogeneous or long lists of
+/// parameters as a `&[&dyn ToSql]`.
///
/// # Example
///
@@ -154,7 +161,7 @@ pub const NO_PARAMS: &[&dyn ToSql] = &[];
#[macro_export]
macro_rules! params {
() => {
- $crate::NO_PARAMS
+ &[] as &[&dyn $crate::ToSql]
};
($($param:expr),+ $(,)?) => {
&[$(&$param as &dyn $crate::ToSql),+] as &[&dyn $crate::ToSql]
@@ -176,7 +183,7 @@ macro_rules! params {
/// }
///
/// fn add_person(conn: &Connection, person: &Person) -> Result<()> {
-/// conn.execute_named(
+/// conn.execute(
/// "INSERT INTO person (name, age_in_years, data)
/// VALUES (:name, :age, :data)",
/// named_params!{
@@ -191,12 +198,12 @@ macro_rules! params {
#[macro_export]
macro_rules! named_params {
() => {
- &[]
+ &[] as &[(&str, &dyn $crate::ToSql)]
};
// Note: It's a lot more work to support this as part of the same macro as
// `params!`, unfortunately.
($($param_name:literal: $param_val:expr),+ $(,)?) => {
- &[$(($param_name, &$param_val as &dyn $crate::ToSql)),+]
+ &[$(($param_name, &$param_val as &dyn $crate::ToSql)),+] as &[(&str, &dyn $crate::ToSql)]
};
}
@@ -275,7 +282,7 @@ fn path_to_cstring(p: &Path) -> Result<CString> {
}
/// Name for a database within a SQLite connection.
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Debug)]
pub enum DatabaseName<'a> {
/// The main database.
Main,
@@ -287,6 +294,12 @@ pub enum DatabaseName<'a> {
Attached(&'a str),
}
+/// Shorthand for [`DatabaseName::Main`].
+pub const MAIN_DB: DatabaseName<'static> = DatabaseName::Main;
+
+/// Shorthand for [`DatabaseName::Temp`].
+pub const TEMP_DB: DatabaseName<'static> = DatabaseName::Temp;
+
// Currently DatabaseName is only used by the backup and blob mods, so hide
// this (private) impl to avoid dead code warnings.
#[cfg(any(
@@ -296,6 +309,7 @@ pub enum DatabaseName<'a> {
feature = "modern_sqlite"
))]
impl DatabaseName<'_> {
+ #[inline]
fn to_cstring(&self) -> Result<util::SmallCString> {
use self::DatabaseName::{Attached, Main, Temp};
match *self {
@@ -316,6 +330,7 @@ pub struct Connection {
unsafe impl Send for Connection {}
impl Drop for Connection {
+ #[inline]
fn drop(&mut self) {
self.flush_prepared_statement_cache();
}
@@ -343,6 +358,7 @@ impl Connection {
///
/// Will return `Err` if `path` cannot be converted to a C-compatible
/// string or if the underlying SQLite open call fails.
+ #[inline]
pub fn open<P: AsRef<Path>>(path: P) -> Result<Connection> {
let flags = OpenFlags::default();
Connection::open_with_flags(path, flags)
@@ -353,6 +369,7 @@ impl Connection {
/// # Failure
///
/// Will return `Err` if the underlying SQLite open call fails.
+ #[inline]
pub fn open_in_memory() -> Result<Connection> {
let flags = OpenFlags::default();
Connection::open_in_memory_with_flags(flags)
@@ -367,6 +384,7 @@ impl Connection {
///
/// Will return `Err` if `path` cannot be converted to a C-compatible
/// string or if the underlying SQLite open call fails.
+ #[inline]
pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: OpenFlags) -> Result<Connection> {
let c_path = path_to_cstring(path.as_ref())?;
InnerConnection::open_with_flags(&c_path, flags, None).map(|db| Connection {
@@ -386,6 +404,7 @@ impl Connection {
///
/// Will return `Err` if either `path` or `vfs` cannot be converted to a
/// C-compatible string or if the underlying SQLite open call fails.
+ #[inline]
pub fn open_with_flags_and_vfs<P: AsRef<Path>>(
path: P,
flags: OpenFlags,
@@ -408,6 +427,7 @@ impl Connection {
/// # Failure
///
/// Will return `Err` if the underlying SQLite open call fails.
+ #[inline]
pub fn open_in_memory_with_flags(flags: OpenFlags) -> Result<Connection> {
Connection::open_with_flags(":memory:", flags)
}
@@ -422,6 +442,7 @@ impl Connection {
///
/// Will return `Err` if vfs` cannot be converted to a C-compatible
/// string or if the underlying SQLite open call fails.
+ #[inline]
pub fn open_in_memory_with_flags_and_vfs(flags: OpenFlags, vfs: &str) -> Result<Connection> {
Connection::open_with_flags_and_vfs(":memory:", flags, vfs)
}
@@ -434,8 +455,7 @@ impl Connection {
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn create_tables(conn: &Connection) -> Result<()> {
- /// conn.execute_batch(
- /// "BEGIN;
+ /// conn.execute_batch("BEGIN;
/// CREATE TABLE foo(x INTEGER);
/// CREATE TABLE bar(y TEXT);
/// COMMIT;",
@@ -471,25 +491,48 @@ impl Connection {
///
/// ## Example
///
+ /// ### With positional params
+ ///
/// ```rust,no_run
/// # use rusqlite::{Connection};
/// fn update_rows(conn: &Connection) {
- /// match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?", &[1i32]) {
+ /// match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?", [1i32]) {
/// Ok(updated) => println!("{} rows were updated", updated),
/// Err(err) => println!("update failed: {}", err),
/// }
/// }
/// ```
///
+ /// ### With positional params of varying types
+ ///
+ /// ```rust,no_run
+ /// # use rusqlite::{Connection};
+ /// fn update_rows(conn: &Connection) {
+ /// match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?", [1i32]) {
+ /// Ok(updated) => println!("{} rows were updated", updated),
+ /// Err(err) => println!("update failed: {}", err),
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ### With named params
+ ///
+ /// ```rust,no_run
+ /// # use rusqlite::{Connection, Result};
+ /// fn insert(conn: &Connection) -> Result<usize> {
+ /// conn.execute(
+ /// "INSERT INTO test (name) VALUES (:name)",
+ /// &[(":name", "one")],
+ /// )
+ /// }
+ /// ```
+ ///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
- pub fn execute<P>(&self, sql: &str, params: P) -> Result<usize>
- where
- P: IntoIterator,
- P::Item: ToSql,
- {
+ #[inline]
+ pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize> {
self.prepare(sql)
.and_then(|mut stmt| stmt.check_no_tail().and_then(|_| stmt.execute(params)))
}
@@ -500,23 +543,14 @@ impl Connection {
/// On success, returns the number of rows that were changed or inserted or
/// deleted (via `sqlite3_changes`).
///
- /// ## Example
- ///
- /// ```rust,no_run
- /// # use rusqlite::{Connection, Result};
- /// fn insert(conn: &Connection) -> Result<usize> {
- /// conn.execute_named(
- /// "INSERT INTO test (name) VALUES (:name)",
- /// &[(":name", &"one")],
- /// )
- /// }
- /// ```
- ///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
+ #[deprecated = "You can use `execute` with named params now."]
pub fn execute_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
+ // This function itself is deprecated, so it's fine
+ #![allow(deprecated)]
self.prepare(sql).and_then(|mut stmt| {
stmt.check_no_tail()
.and_then(|_| stmt.execute_named(params))
@@ -527,6 +561,7 @@ impl Connection {
///
/// Uses [sqlite3_last_insert_rowid](https://www.sqlite.org/c3ref/last_insert_rowid.html) under
/// the hood.
+ #[inline]
pub fn last_insert_rowid(&self) -> i64 {
self.db.borrow_mut().last_insert_rowid()
}
@@ -537,11 +572,11 @@ impl Connection {
/// ## Example
///
/// ```rust,no_run
- /// # use rusqlite::{Result,Connection, NO_PARAMS};
+ /// # use rusqlite::{Result, Connection};
/// fn preferred_locale(conn: &Connection) -> Result<String> {
/// conn.query_row(
/// "SELECT value FROM preferences WHERE name='locale'",
- /// NO_PARAMS,
+ /// [],
/// |row| row.get(0),
/// )
/// }
@@ -558,10 +593,10 @@ impl Connection {
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
+ #[inline]
pub fn query_row<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T>
where
- P: IntoIterator,
- P::Item: ToSql,
+ P: Params,
F: FnOnce(&Row<'_>) -> Result<T>,
{
let mut stmt = self.prepare(sql)?;
@@ -583,13 +618,12 @@ impl Connection {
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
+ #[deprecated = "You can use `query_row` with named params now."]
pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
where
F: FnOnce(&Row<'_>) -> Result<T>,
{
- let mut stmt = self.prepare(sql)?;
- stmt.check_no_tail()?;
- stmt.query_row_named(params, f)
+ self.query_row(sql, params, f)
}
/// Convenience method to execute a query that is expected to return a
@@ -600,11 +634,11 @@ impl Connection {
/// ## Example
///
/// ```rust,no_run
- /// # use rusqlite::{Result,Connection, NO_PARAMS};
+ /// # use rusqlite::{Result, Connection};
/// fn preferred_locale(conn: &Connection) -> Result<String> {
/// conn.query_row_and_then(
/// "SELECT value FROM preferences WHERE name='locale'",
- /// NO_PARAMS,
+ /// [],
/// |row| row.get(0),
/// )
/// }
@@ -617,10 +651,10 @@ impl Connection {
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
+ #[inline]
pub fn query_row_and_then<T, E, P, F>(&self, sql: &str, params: P, f: F) -> Result<T, E>
where
- P: IntoIterator,
- P::Item: ToSql,
+ P: Params,
F: FnOnce(&Row<'_>) -> Result<T, E>,
E: convert::From<Error>,
{
@@ -639,8 +673,8 @@ impl Connection {
/// # use rusqlite::{Connection, Result};
/// fn insert_new_people(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?)")?;
- /// stmt.execute(&["Joe Smith"])?;
- /// stmt.execute(&["Bob Jones"])?;
+ /// stmt.execute(["Joe Smith"])?;
+ /// stmt.execute(["Bob Jones"])?;
/// Ok(())
/// }
/// ```
@@ -649,6 +683,7 @@ impl Connection {
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
+ #[inline]
pub fn prepare(&self, sql: &str) -> Result<Statement<'_>> {
self.db.borrow_mut().prepare(self, sql)
}
@@ -662,6 +697,7 @@ impl Connection {
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
+ #[inline]
pub fn close(self) -> Result<(), (Connection, Error)> {
self.flush_prepared_statement_cache();
let r = self.db.borrow_mut().close();
@@ -687,6 +723,7 @@ impl Connection {
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")]
+ #[inline]
pub fn load_extension_enable(&self) -> Result<()> {
self.db.borrow_mut().enable_load_extension(1)
}
@@ -699,6 +736,7 @@ impl Connection {
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")]
+ #[inline]
pub fn load_extension_disable(&self) -> Result<()> {
self.db.borrow_mut().enable_load_extension(0)
}
@@ -727,6 +765,7 @@ impl Connection {
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")]
+ #[inline]
pub fn load_extension<P: AsRef<Path>>(
&self,
dylib_path: P,
@@ -750,6 +789,7 @@ impl Connection {
/// This function is unsafe because it gives you raw access
/// to the SQLite connection, and what you do with it could impact the
/// safety of this `Connection`.
+ #[inline]
pub unsafe fn handle(&self) -> *mut ffi::sqlite3 {
self.db.borrow().db()
}
@@ -762,6 +802,7 @@ impl Connection {
/// # Safety
///
/// This function is unsafe because improper use may impact the Connection.
+ #[inline]
pub unsafe fn from_handle(db: *mut ffi::sqlite3) -> Result<Connection> {
let db_path = db_filename(db);
let db = InnerConnection::new(db, false);
@@ -774,10 +815,12 @@ impl Connection {
/// Get access to a handle that can be used to interrupt long running
/// queries from another thread.
+ #[inline]
pub fn get_interrupt_handle(&self) -> InterruptHandle {
self.db.borrow().get_interrupt_handle()
}
+ #[inline]
fn decode_result(&self, code: c_int) -> Result<()> {
self.db.borrow_mut().decode_result(code)
}
@@ -785,17 +828,20 @@ impl Connection {
/// Return the number of rows modified, inserted or deleted by the most
/// recently completed INSERT, UPDATE or DELETE statement on the database
/// connection.
+ #[inline]
fn changes(&self) -> usize {
self.db.borrow_mut().changes()
}
/// Test for auto-commit mode.
/// Autocommit mode is on by default.
+ #[inline]
pub fn is_autocommit(&self) -> bool {
self.db.borrow().is_autocommit()
}
/// Determine if all associated prepared statements have been reset.
+ #[inline]
#[cfg(feature = "modern_sqlite")] // 3.8.6
pub fn is_busy(&self) -> bool {
self.db.borrow().is_busy()
@@ -810,6 +856,67 @@ impl fmt::Debug for Connection {
}
}
+/// Batch iterator
+/// ```rust
+/// use rusqlite::{Batch, Connection, Result};
+///
+/// fn main() -> Result<()> {
+/// let conn = Connection::open_in_memory()?;
+/// let sql = r"
+/// CREATE TABLE tbl1 (col);
+/// CREATE TABLE tbl2 (col);
+/// ";
+/// let mut batch = Batch::new(&conn, sql);
+/// while let Some(mut stmt) = batch.next()? {
+/// stmt.execute([])?;
+/// }
+/// Ok(())
+/// }
+/// ```
+#[derive(Debug)]
+pub struct Batch<'conn, 'sql> {
+ conn: &'conn Connection,
+ sql: &'sql str,
+ tail: usize,
+}
+
+impl<'conn, 'sql> Batch<'conn, 'sql> {
+ /// Constructor
+ pub fn new(conn: &'conn Connection, sql: &'sql str) -> Batch<'conn, 'sql> {
+ Batch { conn, sql, tail: 0 }
+ }
+
+ /// Iterates on each batch statements.
+ ///
+ /// Returns `Ok(None)` when batch is completed.
+ #[allow(clippy::should_implement_trait)] // fallible iterator
+ pub fn next(&mut self) -> Result<Option<Statement<'conn>>> {
+ while self.tail < self.sql.len() {
+ let sql = &self.sql[self.tail..];
+ let next = self.conn.prepare(sql)?;
+ let tail = next.stmt.tail();
+ if tail == 0 {
+ self.tail = self.sql.len();
+ } else {
+ self.tail += tail;
+ }
+ if next.stmt.is_null() {
+ continue;
+ }
+ return Ok(Some(next));
+ }
+ Ok(None)
+ }
+}
+
+impl<'conn> Iterator for Batch<'conn, '_> {
+ type Item = Result<Statement<'conn>>;
+
+ fn next(&mut self) -> Option<Result<Statement<'conn>>> {
+ self.next().transpose()
+ }
+}
+
bitflags::bitflags! {
/// Flags for opening SQLite database connections.
/// See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
@@ -867,7 +974,7 @@ impl Default for OpenFlags {
///
/// This function is unsafe because if you call it and SQLite has actually been
/// configured to run in single-thread mode,
-/// you may enounter memory errors or data corruption or any number of terrible
+/// you may encounter memory errors or data corruption or any number of terrible
/// things that should not be possible when you're using Rust.
pub unsafe fn bypass_sqlite_initialization() {
BYPASS_SQLITE_INIT.store(true, Ordering::Relaxed);
@@ -952,39 +1059,33 @@ mod test {
}
#[test]
- fn test_concurrent_transactions_busy_commit() {
+ fn test_concurrent_transactions_busy_commit() -> Result<()> {
use std::time::Duration;
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("transactions.db3");
- Connection::open(&path)
- .expect("create temp db")
- .execute_batch(
- "
+ Connection::open(&path)?.execute_batch(
+ "
BEGIN; CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42); END;",
- )
- .expect("create temp db");
+ )?;
- let mut db1 =
- Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE).unwrap();
- let mut db2 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY).unwrap();
+ let mut db1 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE)?;
+ let mut db2 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY)?;
- db1.busy_timeout(Duration::from_millis(0)).unwrap();
- db2.busy_timeout(Duration::from_millis(0)).unwrap();
+ db1.busy_timeout(Duration::from_millis(0))?;
+ db2.busy_timeout(Duration::from_millis(0))?;
{
- let tx1 = db1.transaction().unwrap();
- let tx2 = db2.transaction().unwrap();
+ let tx1 = db1.transaction()?;
+ let tx2 = db2.transaction()?;
// SELECT first makes sqlite lock with a shared lock
- tx1.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| Ok(()))
- .unwrap();
- tx2.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| Ok(()))
- .unwrap();
+ tx1.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(()))?;
+ tx2.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(()))?;
- tx1.execute("INSERT INTO foo VALUES(?1)", &[1]).unwrap();
- let _ = tx2.execute("INSERT INTO foo VALUES(?1)", &[2]);
+ tx1.execute("INSERT INTO foo VALUES(?1)", [1])?;
+ let _ = tx2.execute("INSERT INTO foo VALUES(?1)", [2]);
let _ = tx1.commit();
let _ = tx2.commit();
@@ -996,27 +1097,29 @@ mod test {
let _ = db2
.transaction()
.expect("commit should have closed transaction");
+ Ok(())
}
#[test]
- fn test_persistence() {
+ fn test_persistence() -> Result<()> {
let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.path().join("test.db3");
{
- let db = Connection::open(&path).unwrap();
+ let db = Connection::open(&path)?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42);
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
}
let path_string = path.to_str().unwrap();
- let db = Connection::open(&path_string).unwrap();
- let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0));
+ let db = Connection::open(&path_string)?;
+ let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
- assert_eq!(42i64, the_answer.unwrap());
+ assert_eq!(42i64, the_answer?);
+ Ok(())
}
#[test]
@@ -1049,7 +1152,7 @@ mod test {
#[cfg(unix)]
#[test]
- fn test_invalid_unicode_file_names() {
+ fn test_invalid_unicode_file_names() -> Result<()> {
use std::ffi::OsStr;
use std::fs::File;
use std::os::unix::ffi::OsStrExt;
@@ -1058,26 +1161,27 @@ mod test {
let path = temp_dir.path();
if File::create(path.join(OsStr::from_bytes(&[0xFE]))).is_err() {
// Skip test, filesystem doesn't support invalid Unicode
- return;
+ return Ok(());
}
let db_path = path.join(OsStr::from_bytes(&[0xFF]));
{
- let db = Connection::open(&db_path).unwrap();
+ let db = Connection::open(&db_path)?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(42);
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
}
- let db = Connection::open(&db_path).unwrap();
- let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0));
+ let db = Connection::open(&db_path)?;
+ let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
- assert_eq!(42i64, the_answer.unwrap());
+ assert_eq!(42i64, the_answer?);
+ Ok(())
}
#[test]
- fn test_close_retry() {
+ fn test_close_retry() -> Result<()> {
let db = checked_memory_handle();
// force the DB to be busy by preparing a statement; this must be done at the
@@ -1091,7 +1195,7 @@ mod test {
let raw_db = db.db.borrow_mut().db;
let sql = "SELECT 1";
let mut raw_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
- let cstring = str_to_cstring(sql).unwrap();
+ let cstring = str_to_cstring(sql)?;
let rc = unsafe {
ffi::sqlite3_prepare_v2(
raw_db,
@@ -1115,6 +1219,7 @@ mod test {
assert_eq!(ffi::SQLITE_OK, unsafe { ffi::sqlite3_finalize(raw_stmt) });
db.close().unwrap();
+ Ok(())
}
#[test]
@@ -1129,7 +1234,7 @@ mod test {
}
#[test]
- fn test_execute_batch() {
+ fn test_execute_batch() -> Result<()> {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
@@ -1138,42 +1243,34 @@ mod test {
INSERT INTO foo VALUES(3);
INSERT INTO foo VALUES(4);
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
- db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3")
- .unwrap();
+ db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3")?;
assert!(db.execute_batch("INVALID SQL").is_err());
+ Ok(())
}
#[test]
- fn test_execute() {
+ fn test_execute() -> Result<()> {
let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
+ db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
- assert_eq!(
- 1,
- db.execute("INSERT INTO foo(x) VALUES (?)", &[1i32])
- .unwrap()
- );
- assert_eq!(
- 1,
- db.execute("INSERT INTO foo(x) VALUES (?)", &[2i32])
- .unwrap()
- );
+ assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [1i32])?);
+ assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [2i32])?);
assert_eq!(
3i32,
- db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap()
+ db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
+ Ok(())
}
#[test]
#[cfg(feature = "extra_check")]
fn test_execute_select() {
let db = checked_memory_handle();
- let err = db.execute("SELECT 1 WHERE 1 < ?", &[1i32]).unwrap_err();
+ let err = db.execute("SELECT 1 WHERE 1 < ?", [1i32]).unwrap_err();
if err != Error::ExecuteReturnedResults {
panic!("Unexpected error: {}", err);
}
@@ -1186,7 +1283,7 @@ mod test {
let err = db
.execute(
"CREATE TABLE foo(x INTEGER); CREATE TABLE foo(x INTEGER)",
- NO_PARAMS,
+ [],
)
.unwrap_err();
match err {
@@ -1196,77 +1293,78 @@ mod test {
}
#[test]
- fn test_prepare_column_names() {
+ fn test_prepare_column_names() -> Result<()> {
let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
+ db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
- let stmt = db.prepare("SELECT * FROM foo").unwrap();
+ let stmt = db.prepare("SELECT * FROM foo")?;
assert_eq!(stmt.column_count(), 1);
assert_eq!(stmt.column_names(), vec!["x"]);
- let stmt = db.prepare("SELECT x AS a, x AS b FROM foo").unwrap();
+ let stmt = db.prepare("SELECT x AS a, x AS b FROM foo")?;
assert_eq!(stmt.column_count(), 2);
assert_eq!(stmt.column_names(), vec!["a", "b"]);
+ Ok(())
}
#[test]
- fn test_prepare_execute() {
+ fn test_prepare_execute() -> Result<()> {
let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
+ db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
- let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)").unwrap();
- assert_eq!(insert_stmt.execute(&[1i32]).unwrap(), 1);
- assert_eq!(insert_stmt.execute(&[2i32]).unwrap(), 1);
- assert_eq!(insert_stmt.execute(&[3i32]).unwrap(), 1);
+ let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
+ assert_eq!(insert_stmt.execute([1i32])?, 1);
+ assert_eq!(insert_stmt.execute([2i32])?, 1);
+ assert_eq!(insert_stmt.execute([3i32])?, 1);
- assert_eq!(insert_stmt.execute(&["hello".to_string()]).unwrap(), 1);
- assert_eq!(insert_stmt.execute(&["goodbye".to_string()]).unwrap(), 1);
- assert_eq!(insert_stmt.execute(&[types::Null]).unwrap(), 1);
+ assert_eq!(insert_stmt.execute(["hello"])?, 1);
+ assert_eq!(insert_stmt.execute(["goodbye"])?, 1);
+ assert_eq!(insert_stmt.execute([types::Null])?, 1);
- let mut update_stmt = db.prepare("UPDATE foo SET x=? WHERE x<?").unwrap();
- assert_eq!(update_stmt.execute(&[3i32, 3i32]).unwrap(), 2);
- assert_eq!(update_stmt.execute(&[3i32, 3i32]).unwrap(), 0);
- assert_eq!(update_stmt.execute(&[8i32, 8i32]).unwrap(), 3);
+ let mut update_stmt = db.prepare("UPDATE foo SET x=? WHERE x<?")?;
+ assert_eq!(update_stmt.execute([3i32, 3i32])?, 2);
+ assert_eq!(update_stmt.execute([3i32, 3i32])?, 0);
+ assert_eq!(update_stmt.execute([8i32, 8i32])?, 3);
+ Ok(())
}
#[test]
- fn test_prepare_query() {
+ fn test_prepare_query() -> Result<()> {
let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
+ db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
- let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)").unwrap();
- assert_eq!(insert_stmt.execute(&[1i32]).unwrap(), 1);
- assert_eq!(insert_stmt.execute(&[2i32]).unwrap(), 1);
- assert_eq!(insert_stmt.execute(&[3i32]).unwrap(), 1);
+ let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
+ assert_eq!(insert_stmt.execute([1i32])?, 1);
+ assert_eq!(insert_stmt.execute([2i32])?, 1);
+ assert_eq!(insert_stmt.execute([3i32])?, 1);
- let mut query = db
- .prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC")
- .unwrap();
+ let mut query = db.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC")?;
{
- let mut rows = query.query(&[4i32]).unwrap();
+ let mut rows = query.query([4i32])?;
let mut v = Vec::<i32>::new();
- while let Some(row) = rows.next().unwrap() {
- v.push(row.get(0).unwrap());
+ while let Some(row) = rows.next()? {
+ v.push(row.get(0)?);
}
assert_eq!(v, [3i32, 2, 1]);
}
{
- let mut rows = query.query(&[3i32]).unwrap();
+ let mut rows = query.query([3i32])?;
let mut v = Vec::<i32>::new();
- while let Some(row) = rows.next().unwrap() {
- v.push(row.get(0).unwrap());
+ while let Some(row) = rows.next()? {
+ v.push(row.get(0)?);
}
assert_eq!(v, [2i32, 1]);
}
+ Ok(())
}
#[test]
- fn test_query_map() {
+ fn test_query_map() -> Result<()> {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
@@ -1275,20 +1373,17 @@ mod test {
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
- let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
- let results: Result<Vec<String>> = query
- .query(NO_PARAMS)
- .unwrap()
- .map(|row| row.get(1))
- .collect();
+ let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
+ let results: Result<Vec<String>> = query.query([])?.map(|row| row.get(1)).collect();
- assert_eq!(results.unwrap().concat(), "hello, world!");
+ assert_eq!(results?.concat(), "hello, world!");
+ Ok(())
}
#[test]
- fn test_query_row() {
+ fn test_query_row() -> Result<()> {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
@@ -1297,89 +1392,88 @@ mod test {
INSERT INTO foo VALUES(3);
INSERT INTO foo VALUES(4);
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
assert_eq!(
10i64,
- db.query_row::<i64, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap()
+ db.query_row::<i64, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
- let result: Result<i64> =
- db.query_row("SELECT x FROM foo WHERE x > 5", NO_PARAMS, |r| r.get(0));
+ let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", [], |r| r.get(0));
match result.unwrap_err() {
Error::QueryReturnedNoRows => (),
err => panic!("Unexpected error {}", err),
}
- let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", NO_PARAMS, |_| Ok(()));
+ let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()));
assert!(bad_query_result.is_err());
+ Ok(())
}
#[test]
- fn test_optional() {
+ fn test_optional() -> Result<()> {
let db = checked_memory_handle();
- let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 <> 0", NO_PARAMS, |r| r.get(0));
+ let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 <> 0", [], |r| r.get(0));
let result = result.optional();
- match result.unwrap() {
+ match result? {
None => (),
_ => panic!("Unexpected result"),
}
- let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 == 0", NO_PARAMS, |r| r.get(0));
+ let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 == 0", [], |r| r.get(0));
let result = result.optional();
- match result.unwrap() {
+ match result? {
Some(1) => (),
_ => panic!("Unexpected result"),
}
- let bad_query_result: Result<i64> =
- db.query_row("NOT A PROPER QUERY", NO_PARAMS, |r| r.get(0));
+ let bad_query_result: Result<i64> = db.query_row("NOT A PROPER QUERY", [], |r| r.get(0));
let bad_query_result = bad_query_result.optional();
assert!(bad_query_result.is_err());
+ Ok(())
}
#[test]
- fn test_pragma_query_row() {
+ fn test_pragma_query_row() -> Result<()> {
let db = checked_memory_handle();
assert_eq!(
"memory",
- db.query_row::<String, _, _>("PRAGMA journal_mode", NO_PARAMS, |r| r.get(0))
- .unwrap()
+ db.query_row::<String, _, _>("PRAGMA journal_mode", [], |r| r.get(0))?
);
assert_eq!(
"off",
- db.query_row::<String, _, _>("PRAGMA journal_mode=off", NO_PARAMS, |r| r.get(0))
- .unwrap()
+ db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?
);
+ Ok(())
}
#[test]
- fn test_prepare_failures() {
+ fn test_prepare_failures() -> Result<()> {
let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
+ db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let err = db.prepare("SELECT * FROM does_not_exist").unwrap_err();
assert!(format!("{}", err).contains("does_not_exist"));
+ Ok(())
}
#[test]
- fn test_last_insert_rowid() {
+ fn test_last_insert_rowid() -> Result<()> {
let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")
- .unwrap();
- db.execute_batch("INSERT INTO foo DEFAULT VALUES").unwrap();
+ db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
+ db.execute_batch("INSERT INTO foo DEFAULT VALUES")?;
assert_eq!(db.last_insert_rowid(), 1);
- let mut stmt = db.prepare("INSERT INTO foo DEFAULT VALUES").unwrap();
+ let mut stmt = db.prepare("INSERT INTO foo DEFAULT VALUES")?;
for _ in 0i32..9 {
- stmt.execute(NO_PARAMS).unwrap();
+ stmt.execute([])?;
}
assert_eq!(db.last_insert_rowid(), 10);
+ Ok(())
}
#[test]
@@ -1393,32 +1487,34 @@ mod test {
#[test]
#[cfg(feature = "modern_sqlite")]
- fn test_is_busy() {
+ fn test_is_busy() -> Result<()> {
let db = checked_memory_handle();
assert!(!db.is_busy());
- let mut stmt = db.prepare("PRAGMA schema_version").unwrap();
+ let mut stmt = db.prepare("PRAGMA schema_version")?;
assert!(!db.is_busy());
{
- let mut rows = stmt.query(NO_PARAMS).unwrap();
+ let mut rows = stmt.query([])?;
assert!(!db.is_busy());
- let row = rows.next().unwrap();
+ let row = rows.next()?;
assert!(db.is_busy());
assert!(row.is_some());
}
assert!(!db.is_busy());
+ Ok(())
}
#[test]
- fn test_statement_debugging() {
+ fn test_statement_debugging() -> Result<()> {
let db = checked_memory_handle();
let query = "SELECT 12345";
- let stmt = db.prepare(query).unwrap();
+ let stmt = db.prepare(query)?;
assert!(format!("{:?}", stmt).contains(query));
+ Ok(())
}
#[test]
- fn test_notnull_constraint_error() {
+ fn test_notnull_constraint_error() -> Result<()> {
// extended error codes for constraints were added in SQLite 3.7.16; if we're
// running on our bundled version, we know the extended error code exists.
#[cfg(feature = "modern_sqlite")]
@@ -1429,9 +1525,9 @@ mod test {
fn check_extended_code(_extended_code: c_int) {}
let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x NOT NULL)").unwrap();
+ db.execute_batch("CREATE TABLE foo(x NOT NULL)")?;
- let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", NO_PARAMS);
+ let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", []);
assert!(result.is_err());
match result.unwrap_err() {
@@ -1441,6 +1537,7 @@ mod test {
}
err => panic!("Unexpected error {}", err),
}
+ Ok(())
}
#[test]
@@ -1455,7 +1552,7 @@ mod test {
#[test]
#[cfg(feature = "functions")]
- fn test_interrupt() {
+ fn test_interrupt() -> Result<()> {
let db = checked_memory_handle();
let interrupt_handle = db.get_interrupt_handle();
@@ -1468,14 +1565,12 @@ mod test {
interrupt_handle.interrupt();
Ok(0)
},
- )
- .unwrap();
+ )?;
- let mut stmt = db
- .prepare("SELECT interrupt() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)")
- .unwrap();
+ let mut stmt =
+ db.prepare("SELECT interrupt() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)")?;
- let result: Result<Vec<i32>> = stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).collect();
+ let result: Result<Vec<i32>> = stmt.query([])?.map(|r| r.get(0)).collect();
match result.unwrap_err() {
Error::SqliteFailure(err, _) => {
@@ -1485,6 +1580,7 @@ mod test {
panic!("Unexpected error {}", err);
}
}
+ Ok(())
}
#[test]
@@ -1504,36 +1600,48 @@ mod test {
}
#[test]
- fn test_get_raw() {
+ fn test_get_raw() -> Result<()> {
let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(i, x);").unwrap();
+ db.execute_batch("CREATE TABLE foo(i, x);")?;
let vals = ["foobar", "1234", "qwerty"];
- let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?, ?)").unwrap();
+ let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?, ?)")?;
for (i, v) in vals.iter().enumerate() {
let i_to_insert = i as i64;
- assert_eq!(insert_stmt.execute(params![i_to_insert, v]).unwrap(), 1);
+ assert_eq!(insert_stmt.execute(params![i_to_insert, v])?, 1);
}
- let mut query = db.prepare("SELECT i, x FROM foo").unwrap();
- let mut rows = query.query(NO_PARAMS).unwrap();
+ let mut query = db.prepare("SELECT i, x FROM foo")?;
+ let mut rows = query.query([])?;
- while let Some(row) = rows.next().unwrap() {
- let i = row.get_raw(0).as_i64().unwrap();
+ while let Some(row) = rows.next()? {
+ let i = row.get_ref(0)?.as_i64()?;
let expect = vals[i as usize];
- let x = row.get_raw("x").as_str().unwrap();
+ let x = row.get_ref("x")?.as_str()?;
assert_eq!(x, expect);
}
+
+ let mut query = db.prepare("SELECT x FROM foo")?;
+ let rows = query.query_map([], |row| {
+ let x = row.get_ref(0)?.as_str()?; // check From<FromSqlError> for Error
+ Ok(x[..].to_owned())
+ })?;
+
+ for (i, row) in rows.enumerate() {
+ assert_eq!(row?, vals[i]);
+ }
+ Ok(())
}
#[test]
- fn test_from_handle() {
+ fn test_from_handle() -> Result<()> {
let db = checked_memory_handle();
let handle = unsafe { db.handle() };
{
- let db = unsafe { Connection::from_handle(handle) }.unwrap();
- db.execute_batch("PRAGMA VACUUM").unwrap();
+ let db = unsafe { Connection::from_handle(handle) }?;
+ db.execute_batch("PRAGMA VACUUM")?;
}
db.close().unwrap();
+ Ok(())
}
mod query_and_then_tests {
@@ -1577,7 +1685,7 @@ mod test {
type CustomResult<T> = Result<T, CustomError>;
#[test]
- fn test_query_and_then() {
+ fn test_query_and_then() -> Result<()> {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
@@ -1586,19 +1694,18 @@ mod test {
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
- let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
- let results: Result<Vec<String>> = query
- .query_and_then(NO_PARAMS, |row| row.get(1))
- .unwrap()
- .collect();
+ let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
+ let results: Result<Vec<String>> =
+ query.query_and_then([], |row| row.get(1))?.collect();
- assert_eq!(results.unwrap().concat(), "hello, world!");
+ assert_eq!(results?.concat(), "hello, world!");
+ Ok(())
}
#[test]
- fn test_query_and_then_fails() {
+ fn test_query_and_then_fails() -> Result<()> {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
@@ -1607,32 +1714,28 @@ mod test {
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
- let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
- let bad_type: Result<Vec<f64>> = query
- .query_and_then(NO_PARAMS, |row| row.get(1))
- .unwrap()
- .collect();
+ let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
+ let bad_type: Result<Vec<f64>> = query.query_and_then([], |row| row.get(1))?.collect();
match bad_type.unwrap_err() {
Error::InvalidColumnType(..) => (),
err => panic!("Unexpected error {}", err),
}
- let bad_idx: Result<Vec<String>> = query
- .query_and_then(NO_PARAMS, |row| row.get(3))
- .unwrap()
- .collect();
+ let bad_idx: Result<Vec<String>> =
+ query.query_and_then([], |row| row.get(3))?.collect();
match bad_idx.unwrap_err() {
Error::InvalidColumnIndex(_) => (),
err => panic!("Unexpected error {}", err),
}
+ Ok(())
}
#[test]
- fn test_query_and_then_custom_error() {
+ fn test_query_and_then_custom_error() -> CustomResult<()> {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
@@ -1641,19 +1744,19 @@ mod test {
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
- let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
+ let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
let results: CustomResult<Vec<String>> = query
- .query_and_then(NO_PARAMS, |row| row.get(1).map_err(CustomError::Sqlite))
- .unwrap()
+ .query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite))?
.collect();
- assert_eq!(results.unwrap().concat(), "hello, world!");
+ assert_eq!(results?.concat(), "hello, world!");
+ Ok(())
}
#[test]
- fn test_query_and_then_custom_error_fails() {
+ fn test_query_and_then_custom_error_fails() -> Result<()> {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
@@ -1662,12 +1765,11 @@ mod test {
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
- let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
+ let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
let bad_type: CustomResult<Vec<f64>> = query
- .query_and_then(NO_PARAMS, |row| row.get(1).map_err(CustomError::Sqlite))
- .unwrap()
+ .query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite))?
.collect();
match bad_type.unwrap_err() {
@@ -1676,8 +1778,7 @@ mod test {
}
let bad_idx: CustomResult<Vec<String>> = query
- .query_and_then(NO_PARAMS, |row| row.get(3).map_err(CustomError::Sqlite))
- .unwrap()
+ .query_and_then([], |row| row.get(3).map_err(CustomError::Sqlite))?
.collect();
match bad_idx.unwrap_err() {
@@ -1686,55 +1787,53 @@ mod test {
}
let non_sqlite_err: CustomResult<Vec<String>> = query
- .query_and_then(NO_PARAMS, |_| Err(CustomError::SomeError))
- .unwrap()
+ .query_and_then([], |_| Err(CustomError::SomeError))?
.collect();
match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (),
err => panic!("Unexpected error {}", err),
}
+ Ok(())
}
#[test]
- fn test_query_row_and_then_custom_error() {
+ fn test_query_row_and_then_custom_error() -> CustomResult<()> {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
let query = "SELECT x, y FROM foo ORDER BY x DESC";
- let results: CustomResult<String> = db.query_row_and_then(query, NO_PARAMS, |row| {
- row.get(1).map_err(CustomError::Sqlite)
- });
+ let results: CustomResult<String> =
+ db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite));
- assert_eq!(results.unwrap(), "hello");
+ assert_eq!(results?, "hello");
+ Ok(())
}
#[test]
- fn test_query_row_and_then_custom_error_fails() {
+ fn test_query_row_and_then_custom_error_fails() -> Result<()> {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
let query = "SELECT x, y FROM foo ORDER BY x DESC";
- let bad_type: CustomResult<f64> = db.query_row_and_then(query, NO_PARAMS, |row| {
- row.get(1).map_err(CustomError::Sqlite)
- });
+ let bad_type: CustomResult<f64> =
+ db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite));
match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
err => panic!("Unexpected error {}", err),
}
- let bad_idx: CustomResult<String> = db.query_row_and_then(query, NO_PARAMS, |row| {
- row.get(3).map_err(CustomError::Sqlite)
- });
+ let bad_idx: CustomResult<String> =
+ db.query_row_and_then(query, [], |row| row.get(3).map_err(CustomError::Sqlite));
match bad_idx.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
@@ -1742,70 +1841,97 @@ mod test {
}
let non_sqlite_err: CustomResult<String> =
- db.query_row_and_then(query, NO_PARAMS, |_| Err(CustomError::SomeError));
+ db.query_row_and_then(query, [], |_| Err(CustomError::SomeError));
match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (),
err => panic!("Unexpected error {}", err),
}
+ Ok(())
}
+ }
- #[test]
- fn test_dynamic() {
- let db = checked_memory_handle();
- let sql = "BEGIN;
+ #[test]
+ fn test_dynamic() -> Result<()> {
+ let db = checked_memory_handle();
+ let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
END;";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
- db.query_row("SELECT * FROM foo", params![], |r| {
- assert_eq!(2, r.column_count());
- Ok(())
- })
- .unwrap();
- }
- #[test]
- fn test_dyn_box() {
- let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
- let b: Box<dyn ToSql> = Box::new(5);
- db.execute("INSERT INTO foo VALUES(?)", &[b]).unwrap();
- db.query_row("SELECT x FROM foo", params![], |r| {
- assert_eq!(5, r.get_unwrap::<_, i32>(0));
- Ok(())
- })
- .unwrap();
- }
+ db.query_row("SELECT * FROM foo", [], |r| {
+ assert_eq!(2, r.column_count());
+ Ok(())
+ })
+ }
+ #[test]
+ fn test_dyn_box() -> Result<()> {
+ let db = checked_memory_handle();
+ db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
+ let b: Box<dyn ToSql> = Box::new(5);
+ db.execute("INSERT INTO foo VALUES(?)", [b])?;
+ db.query_row("SELECT x FROM foo", [], |r| {
+ assert_eq!(5, r.get_unwrap::<_, i32>(0));
+ Ok(())
+ })
+ }
- #[test]
- fn test_params() {
- let db = checked_memory_handle();
- db.query_row(
- "SELECT
+ #[test]
+ fn test_params() -> Result<()> {
+ let db = checked_memory_handle();
+ db.query_row(
+ "SELECT
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?;",
- params![
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1,
- ],
- |r| {
- assert_eq!(1, r.get_unwrap::<_, i32>(0));
- Ok(())
- },
- )
- .unwrap();
- }
+ params![
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1,
+ ],
+ |r| {
+ assert_eq!(1, r.get_unwrap::<_, i32>(0));
+ Ok(())
+ },
+ )
+ }
- #[test]
- #[cfg(not(feature = "extra_check"))]
- fn test_alter_table() {
- let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE x(t);").unwrap();
- // `execute_batch` should be used but `execute` should also work
- db.execute("ALTER TABLE x RENAME TO y;", params![]).unwrap();
+ #[test]
+ #[cfg(not(feature = "extra_check"))]
+ fn test_alter_table() -> Result<()> {
+ let db = checked_memory_handle();
+ db.execute_batch("CREATE TABLE x(t);")?;
+ // `execute_batch` should be used but `execute` should also work
+ db.execute("ALTER TABLE x RENAME TO y;", [])?;
+ Ok(())
+ }
+
+ #[test]
+ fn test_batch() -> Result<()> {
+ let db = checked_memory_handle();
+ let sql = r"
+ CREATE TABLE tbl1 (col);
+ CREATE TABLE tbl2 (col);
+ ";
+ let batch = Batch::new(&db, sql);
+ for stmt in batch {
+ let mut stmt = stmt?;
+ stmt.execute([])?;
}
+ Ok(())
+ }
+
+ #[test]
+ #[cfg(feature = "bundled")] // SQLite >= 3.35.0
+ fn test_returning() -> Result<()> {
+ let db = checked_memory_handle();
+ db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
+ let row_id =
+ db.query_row::<i64, _, _>("INSERT INTO foo DEFAULT VALUES RETURNING ROWID", [], |r| {
+ r.get(0)
+ })?;
+ assert_eq!(row_id, 1);
+ Ok(())
}
}
diff --git a/src/limits.rs b/src/limits.rs
index 238ce56..cacaa90 100644
--- a/src/limits.rs
+++ b/src/limits.rs
@@ -9,6 +9,7 @@ use crate::Connection;
impl Connection {
/// `feature = "limits"` Returns the current value of a limit.
+ #[inline]
pub fn limit(&self, limit: Limit) -> i32 {
let c = self.db.borrow();
unsafe { ffi::sqlite3_limit(c.db(), limit as c_int, -1) }
@@ -16,6 +17,7 @@ impl Connection {
/// `feature = "limits"` Changes the limit to `new_val`, returning the prior
/// value of the limit.
+ #[inline]
pub fn set_limit(&self, limit: Limit, new_val: i32) -> i32 {
let c = self.db.borrow_mut();
unsafe { ffi::sqlite3_limit(c.db(), limit as c_int, new_val) }
@@ -25,11 +27,11 @@ impl Connection {
#[cfg(test)]
mod test {
use crate::ffi::Limit;
- use crate::Connection;
+ use crate::{Connection, Result};
#[test]
- fn test_limit() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_limit() -> Result<()> {
+ let db = Connection::open_in_memory()?;
db.set_limit(Limit::SQLITE_LIMIT_LENGTH, 1024);
assert_eq!(1024, db.limit(Limit::SQLITE_LIMIT_LENGTH));
@@ -68,5 +70,6 @@ mod test {
db.set_limit(Limit::SQLITE_LIMIT_WORKER_THREADS, 2);
assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS));
}
+ Ok(())
}
}
diff --git a/src/load_extension_guard.rs b/src/load_extension_guard.rs
index f4f67d1..b4c38c2 100644
--- a/src/load_extension_guard.rs
+++ b/src/load_extension_guard.rs
@@ -22,6 +22,7 @@ impl LoadExtensionGuard<'_> {
/// Attempt to enable loading extensions. Loading extensions will be
/// disabled when this guard goes out of scope. Cannot be meaningfully
/// nested.
+ #[inline]
pub fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
conn.load_extension_enable()
.map(|_| LoadExtensionGuard { conn })
@@ -30,6 +31,7 @@ impl LoadExtensionGuard<'_> {
#[allow(unused_must_use)]
impl Drop for LoadExtensionGuard<'_> {
+ #[inline]
fn drop(&mut self) {
self.conn.load_extension_disable();
}
diff --git a/src/params.rs b/src/params.rs
new file mode 100644
index 0000000..88fce97
--- /dev/null
+++ b/src/params.rs
@@ -0,0 +1,358 @@
+use crate::{Result, Statement, ToSql};
+
+mod sealed {
+ /// This trait exists just to ensure that the only impls of `trait Params`
+ /// that are allowed are ones in this crate.
+ pub trait Sealed {}
+}
+use sealed::Sealed;
+
+/// Trait used for [sets of parameter][params] passed into SQL
+/// statements/queries.
+///
+/// [params]: https://www.sqlite.org/c3ref/bind_blob.html
+///
+/// Note: Currently, this trait can only be implemented inside this crate.
+/// Additionally, it's methods (which are `doc(hidden)`) should currently not be
+/// considered part of the stable API, although it's possible they will
+/// stabilize in the future.
+///
+/// # Passing parameters to SQLite
+///
+/// Many functions in this library let you pass parameters to SQLite. Doing this
+/// lets you avoid any risk of SQL injection, and is simpler than escaping
+/// things manually. Aside from deprecated functions and a few helpers, this is
+/// indicated by the function taking a generic argument that implements `Params`
+/// (this trait).
+///
+/// ## Positional parameters
+///
+/// For cases where you want to pass a list of parameters where the number of
+/// parameters is known at compile time, this can be done in one of the
+/// following ways:
+///
+/// - Using the [`rusqlite::params!`](crate::params!) macro, e.g.
+/// `thing.query(rusqlite::params![1, "foo", bar])`. This is mostly useful for
+/// heterogeneous lists of parameters, or lists where the number of parameters
+/// exceeds 32.
+///
+/// - For small heterogeneous lists of parameters, they can either be passed as:
+///
+/// - an array, as in `thing.query([1i32, 2, 3, 4])` or `thing.query(["foo",
+/// "bar", "baz"])`.
+///
+/// - a reference to an array of references, as in `thing.query(&["foo",
+/// "bar", "baz"])` or `thing.query(&[&1i32, &2, &3])`.
+///
+/// (Note: in this case we don't implement this for slices for coherence
+/// reasons, so it really is only for the "reference to array" types —
+/// hence why the number of parameters must be <= 32 or you need to
+/// reach for `rusqlite::params!`)
+///
+/// Unfortunately, in the current design it's not possible to allow this for
+/// references to arrays of non-references (e.g. `&[1i32, 2, 3]`). Code like
+/// this should instead either use `params!`, an array literal, a `&[&dyn
+/// ToSql]` or if none of those work, [`ParamsFromIter`].
+///
+/// - As a slice of `ToSql` trait object references, e.g. `&[&dyn ToSql]`. This
+/// is mostly useful for passing parameter lists around as arguments without
+/// having every function take a generic `P: Params`.
+///
+/// ### Example (positional)
+///
+/// ```rust,no_run
+/// # use rusqlite::{Connection, Result, params};
+/// fn update_rows(conn: &Connection) -> Result<()> {
+/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?, ?)")?;
+///
+/// // Using `rusqlite::params!`:
+/// stmt.execute(params![1i32, "blah"])?;
+///
+/// // array literal — non-references
+/// stmt.execute([2i32, 3i32])?;
+///
+/// // array literal — references
+/// stmt.execute(["foo", "bar"])?;
+///
+/// // Slice literal, references:
+/// stmt.execute(&[&2i32, &3i32])?;
+///
+/// // Note: The types behind the references don't have to be `Sized`
+/// stmt.execute(&["foo", "bar"])?;
+///
+/// // However, this doesn't work (see above):
+/// // stmt.execute(&[1i32, 2i32])?;
+/// Ok(())
+/// }
+/// ```
+///
+/// ## Named parameters
+///
+/// SQLite lets you name parameters using a number of conventions (":foo",
+/// "@foo", "$foo"). You can pass named parameters in to SQLite using rusqlite
+/// in a few ways:
+///
+/// - Using the [`rusqlite::named_params!`](crate::named_params!) macro, as in
+/// `stmt.execute(named_params!{ ":name": "foo", ":age": 99 })`. Similar to
+/// the `params` macro, this is most useful for heterogeneous lists of
+/// parameters, or lists where the number of parameters exceeds 32.
+///
+/// - As a slice of `&[(&str, &dyn ToSql)]`. This is what essentially all of
+/// these boil down to in the end, conceptually at least. In theory you can
+/// pass this as `stmt.
+///
+/// - As array references, similar to the positional params. This looks like
+/// `thing.query(&[(":foo", &1i32), (":bar", &2i32)])` or
+/// `thing.query(&[(":foo", "abc"), (":bar", "def")])`.
+///
+/// Note: Unbound named parameters will be left to the value they previously
+/// were bound with, falling back to `NULL` for parameters which have never been
+/// bound.
+///
+/// ### Example (named)
+///
+/// ```rust,no_run
+/// # use rusqlite::{Connection, Result, named_params};
+/// fn insert(conn: &Connection) -> Result<()> {
+/// let mut stmt = conn.prepare("INSERT INTO test (key, value) VALUES (:key, :value)")?;
+/// // Using `rusqlite::params!`:
+/// stmt.execute(named_params!{ ":key": "one", ":val": 2 })?;
+/// // Alternatively:
+/// stmt.execute(&[(":key", "three"), (":val", "four")])?;
+/// // Or:
+/// stmt.execute(&[(":key", &100), (":val", &200)])?;
+/// Ok(())
+/// }
+/// ```
+///
+/// ## No parameters
+///
+/// You can just use an empty array literal for no params. The
+/// `rusqlite::NO_PARAMS` constant which was so common in previous versions of
+/// this library is no longer needed (and is now deprecated).
+///
+/// ### Example (no parameters)
+///
+/// ```rust,no_run
+/// # use rusqlite::{Connection, Result, params};
+/// fn delete_all_users(conn: &Connection) -> Result<()> {
+/// // Just use an empty array (e.g. `[]`) for no params.
+/// conn.execute("DELETE FROM users", [])?;
+/// Ok(())
+/// }
+/// ```
+///
+/// ## Dynamic parameter list
+///
+/// If you have a number of parameters which is unknown at compile time (for
+/// example, building a dynamic query at runtime), you have two choices:
+///
+/// - Use a `&[&dyn ToSql]`, which is nice if you have one otherwise might be
+/// annoying.
+/// - Use the [`ParamsFromIter`] type. This essentially lets you wrap an
+/// iterator some `T: ToSql` with something that implements `Params`.
+///
+/// A lot of the considerations here are similar either way, so you should see
+/// the [`ParamsFromIter`] documentation for more info / examples.
+pub trait Params: Sealed {
+ // XXX not public api, might not need to expose.
+ //
+ // Binds the parameters to the statement. It is unlikely calling this
+ // explicitly will do what you want. Please use `Statement::query` or
+ // similar directly.
+ //
+ // For now, just hide the function in the docs...
+ #[doc(hidden)]
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()>;
+}
+
+// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
+// unambiguous, this must be the *only* implementation for an empty array. This
+// avoids `NO_PARAMS` being a necessary part of the API.
+impl Sealed for [&dyn ToSql; 0] {}
+impl Params for [&dyn ToSql; 0] {
+ #[inline]
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
+ // Note: Can't just return `Ok(())` — `Statement::bind_parameters`
+ // checks that the right number of params were passed too.
+ // TODO: we should have tests for `Error::InvalidParameterCount`...
+ stmt.bind_parameters(&[] as &[&dyn ToSql])
+ }
+}
+
+impl Sealed for &[&dyn ToSql] {}
+impl Params for &[&dyn ToSql] {
+ #[inline]
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
+ stmt.bind_parameters(self)
+ }
+}
+
+impl Sealed for &[(&str, &dyn ToSql)] {}
+impl Params for &[(&str, &dyn ToSql)] {
+ #[inline]
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
+ stmt.bind_parameters_named(self)
+ }
+}
+
+macro_rules! impl_for_array_ref {
+ ($($N:literal)+) => {$(
+ // These are already generic, and there's a shedload of them, so lets
+ // avoid the compile time hit from making them all inline for now.
+ impl<T: ToSql + ?Sized> Sealed for &[&T; $N] {}
+ impl<T: ToSql + ?Sized> Params for &[&T; $N] {
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
+ stmt.bind_parameters(self)
+ }
+ }
+ impl<T: ToSql + ?Sized> Sealed for &[(&str, &T); $N] {}
+ impl<T: ToSql + ?Sized> Params for &[(&str, &T); $N] {
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
+ stmt.bind_parameters_named(self)
+ }
+ }
+ impl<T: ToSql> Sealed for [T; $N] {}
+ impl<T: ToSql> Params for [T; $N] {
+ #[inline]
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
+ stmt.bind_parameters(&self)
+ }
+ }
+ )+};
+}
+
+// Following libstd/libcore's (old) lead, implement this for arrays up to `[_;
+// 32]`. Note `[_; 0]` is intentionally omitted for coherence reasons, see the
+// note above the impl of `[&dyn ToSql; 0]` for more information.
+impl_for_array_ref!(
+ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
+ 18 19 20 21 22 23 24 25 26 27 29 30 31 32
+);
+
+/// Adapter type which allows any iterator over [`ToSql`] values to implement
+/// [`Params`].
+///
+/// This struct is created by the [`params_from_iter`] function.
+///
+/// This can be useful if you have something like an `&[String]` (of unknown
+/// length), and you want to use them with an API that wants something
+/// implementing `Params`. This way, you can avoid having to allocate storage
+/// for something like a `&[&dyn ToSql]`.
+///
+/// This essentially is only ever actually needed when dynamically generating
+/// SQL — static SQL (by definition) has the number of parameters known
+/// statically. As dynamically generating SQL is itself pretty advanced, this
+/// API is itself for advanced use cases (See "Realistic use case" in the
+/// examples).
+///
+/// # Example
+///
+/// ## Basic usage
+///
+/// ```rust,no_run
+/// use rusqlite::{Connection, Result, params_from_iter};
+/// use std::collections::BTreeSet;
+///
+/// fn query(conn: &Connection, ids: &BTreeSet<String>) -> Result<()> {
+/// assert_eq!(ids.len(), 3, "Unrealistic sample code");
+///
+/// let mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?, ?, ?)")?;
+/// let _rows = stmt.query(params_from_iter(ids.iter()))?;
+///
+/// // use _rows...
+/// Ok(())
+/// }
+/// ```
+///
+/// ## Realistic use case
+///
+/// Here's how you'd use `ParamsFromIter` to call [`Statement::exists`] with a
+/// dynamic number of parameters.
+///
+/// ```rust,no_run
+/// use rusqlite::{Connection, Result};
+///
+/// pub fn any_active_users(conn: &Connection, usernames: &[String]) -> Result<bool> {
+/// if usernames.is_empty() {
+/// return Ok(false);
+/// }
+///
+/// // Note: `repeat_vars` never returns anything attacker-controlled, so
+/// // it's fine to use it in a dynamically-built SQL string.
+/// let vars = repeat_vars(usernames.len());
+///
+/// let sql = format!(
+/// // In practice this would probably be better as an `EXISTS` query.
+/// "SELECT 1 FROM user WHERE is_active AND name IN ({}) LIMIT 1",
+/// vars,
+/// );
+/// let mut stmt = conn.prepare(&sql)?;
+/// stmt.exists(rusqlite::params_from_iter(usernames))
+/// }
+///
+/// // Helper function to return a comma-separated sequence of `?`.
+/// // - `repeat_vars(0) => panic!(...)`
+/// // - `repeat_vars(1) => "?"`
+/// // - `repeat_vars(2) => "?,?"`
+/// // - `repeat_vars(3) => "?,?,?"`
+/// // - ...
+/// fn repeat_vars(count: usize) -> String {
+/// assert_ne!(count, 0);
+/// let mut s = "?,".repeat(count);
+/// // Remove trailing comma
+/// s.pop();
+/// s
+/// }
+/// ```
+///
+/// That is fairly complex, and even so would need even more work to be fully
+/// production-ready:
+///
+/// - production code should ensure `usernames` isn't so large that it will
+/// surpass [`conn.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)`][limits]),
+/// chunking if too large. (Note that the limits api requires rusqlite to have
+/// the "limits" feature).
+///
+/// - `repeat_vars` can be implemented in a way that avoids needing to allocate
+/// a String.
+///
+/// - Etc...
+///
+/// [limits]: crate::Connection::limit
+///
+/// This complexity reflects the fact that `ParamsFromIter` is mainly intended
+/// for advanced use cases — most of the time you should know how many
+/// parameters you have statically (and if you don't, you're either doing
+/// something tricky, or should take a moment to think about the design).
+#[derive(Clone, Debug)]
+pub struct ParamsFromIter<I>(I);
+
+/// Constructor function for a [`ParamsFromIter`]. See its documentation for
+/// more.
+#[inline]
+pub fn params_from_iter<I>(iter: I) -> ParamsFromIter<I>
+where
+ I: IntoIterator,
+ I::Item: ToSql,
+{
+ ParamsFromIter(iter)
+}
+
+impl<I> Sealed for ParamsFromIter<I>
+where
+ I: IntoIterator,
+ I::Item: ToSql,
+{
+}
+
+impl<I> Params for ParamsFromIter<I>
+where
+ I: IntoIterator,
+ I::Item: ToSql,
+{
+ #[inline]
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
+ stmt.bind_parameters(self.0)
+ }
+}
diff --git a/src/pragma.rs b/src/pragma.rs
index 4855154..16a6307 100644
--- a/src/pragma.rs
+++ b/src/pragma.rs
@@ -5,7 +5,7 @@ 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};
+use crate::{Connection, DatabaseName, Result, Row};
pub struct Sql {
buf: String,
@@ -176,7 +176,7 @@ impl Connection {
{
let mut query = Sql::new();
query.push_pragma(schema_name, pragma_name)?;
- self.query_row(&query, NO_PARAMS, f)
+ self.query_row(&query, [], f)
}
/// Query the current rows/values of `pragma_name`.
@@ -195,7 +195,7 @@ impl Connection {
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)?;
+ let mut rows = stmt.query([])?;
while let Some(result_row) = rows.next()? {
let row = result_row;
f(&row)?;
@@ -231,7 +231,7 @@ impl Connection {
sql.push_value(pragma_value)?;
sql.close_brace();
let mut stmt = self.prepare(&sql)?;
- let mut rows = stmt.query(NO_PARAMS)?;
+ let mut rows = stmt.query([])?;
while let Some(result_row) = rows.next()? {
let row = result_row;
f(&row)?;
@@ -279,7 +279,7 @@ impl Connection {
// The two syntaxes yield identical results.
sql.push_equal_sign();
sql.push_value(pragma_value)?;
- self.query_row(&sql, NO_PARAMS, f)
+ self.query_row(&sql, [], f)
}
}
@@ -298,15 +298,15 @@ fn is_identifier(s: &str) -> bool {
}
fn is_identifier_start(c: char) -> bool {
- (c >= 'A' && c <= 'Z') || c == '_' || (c >= 'a' && c <= 'z') || c > '\x7F'
+ ('A'..='Z').contains(&c) || c == '_' || ('a'..='z').contains(&c) || c > '\x7F'
}
fn is_identifier_continue(c: char) -> bool {
c == '$'
- || (c >= '0' && c <= '9')
- || (c >= 'A' && c <= 'Z')
+ || ('0'..='9').contains(&c)
+ || ('A'..='Z').contains(&c)
|| c == '_'
- || (c >= 'a' && c <= 'z')
+ || ('a'..='z').contains(&c)
|| c > '\x7F'
}
@@ -314,99 +314,95 @@ fn is_identifier_continue(c: char) -> bool {
mod test {
use super::Sql;
use crate::pragma;
- use crate::{Connection, DatabaseName};
+ use crate::{Connection, DatabaseName, Result};
#[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();
+ fn pragma_query_value() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ let user_version: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
assert_eq!(0, user_version);
+ Ok(())
}
#[test]
#[cfg(feature = "modern_sqlite")]
- 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();
+ fn pragma_func_query_value() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ let user_version: i32 =
+ db.query_row("SELECT user_version FROM pragma_user_version", [], |row| {
+ row.get(0)
+ })?;
assert_eq!(0, user_version);
+ Ok(())
}
#[test]
- fn pragma_query_no_schema() {
- let db = Connection::open_in_memory().unwrap();
+ fn pragma_query_no_schema() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let mut user_version = -1;
db.pragma_query(None, "user_version", |row| {
user_version = row.get(0)?;
Ok(())
- })
- .unwrap();
+ })?;
assert_eq!(0, user_version);
+ Ok(())
}
#[test]
- fn pragma_query_with_schema() {
- let db = Connection::open_in_memory().unwrap();
+ fn pragma_query_with_schema() -> Result<()> {
+ let db = Connection::open_in_memory()?;
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);
+ Ok(())
}
#[test]
- fn pragma() {
- let db = Connection::open_in_memory().unwrap();
+ fn pragma() -> Result<()> {
+ let db = Connection::open_in_memory()?;
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());
+ Ok(())
}
#[test]
#[cfg(feature = "modern_sqlite")]
- fn pragma_func() {
- let db = Connection::open_in_memory().unwrap();
- let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap();
+ fn pragma_func() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)")?;
let mut columns = Vec::new();
- let mut rows = table_info.query(&["sqlite_master"]).unwrap();
+ let mut rows = table_info.query(["sqlite_master"])?;
- while let Some(row) = rows.next().unwrap() {
+ while let Some(row) = rows.next()? {
let row = row;
- let column: String = row.get(1).unwrap();
+ let column: String = row.get(1)?;
columns.push(column);
}
assert_eq!(5, columns.len());
+ Ok(())
}
#[test]
- fn pragma_update() {
- let db = Connection::open_in_memory().unwrap();
- db.pragma_update(None, "user_version", &1).unwrap();
+ fn pragma_update() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.pragma_update(None, "user_version", &1)
}
#[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();
+ fn pragma_update_and_check() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ let journal_mode: String =
+ db.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row.get(0))?;
assert_eq!("off", &journal_mode);
+ Ok(())
}
#[test]
@@ -432,13 +428,14 @@ mod test {
}
#[test]
- fn locking_mode() {
- let db = Connection::open_in_memory().unwrap();
+ fn locking_mode() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let r = db.pragma_update(None, "locking_mode", &"exclusive");
if cfg!(feature = "extra_check") {
r.unwrap_err();
} else {
- r.unwrap();
+ r?;
}
+ Ok(())
}
}
diff --git a/src/raw_statement.rs b/src/raw_statement.rs
index c02dcd9..ef94d81 100644
--- a/src/raw_statement.rs
+++ b/src/raw_statement.rs
@@ -29,6 +29,7 @@ pub struct RawStatement {
}
impl RawStatement {
+ #[inline]
pub unsafe fn new(stmt: *mut ffi::sqlite3_stmt, tail: usize) -> RawStatement {
RawStatement {
ptr: stmt,
@@ -38,31 +39,38 @@ impl RawStatement {
}
}
+ #[inline]
pub fn is_null(&self) -> bool {
self.ptr.is_null()
}
+ #[inline]
pub(crate) fn set_statement_cache_key(&mut self, p: impl Into<Arc<str>>) {
self.statement_cache_key = Some(p.into());
}
+ #[inline]
pub(crate) fn statement_cache_key(&self) -> Option<Arc<str>> {
self.statement_cache_key.clone()
}
+ #[inline]
pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt {
self.ptr
}
+ #[inline]
pub fn column_count(&self) -> usize {
// Note: Can't cache this as it changes if the schema is altered.
unsafe { ffi::sqlite3_column_count(self.ptr) as usize }
}
+ #[inline]
pub fn column_type(&self, idx: usize) -> c_int {
unsafe { ffi::sqlite3_column_type(self.ptr, idx as c_int) }
}
+ #[inline]
#[cfg(feature = "column_decltype")]
pub fn column_decltype(&self, idx: usize) -> Option<&CStr> {
unsafe {
@@ -75,6 +83,7 @@ impl RawStatement {
}
}
+ #[inline]
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 {
@@ -92,6 +101,7 @@ impl RawStatement {
}
}
+ #[cfg_attr(not(feature = "unlock_notify"), inline)]
pub fn step(&self) -> c_int {
if cfg!(feature = "unlock_notify") {
let db = unsafe { ffi::sqlite3_db_handle(self.ptr) };
@@ -113,14 +123,17 @@ impl RawStatement {
}
}
+ #[inline]
pub fn reset(&self) -> c_int {
unsafe { ffi::sqlite3_reset(self.ptr) }
}
+ #[inline]
pub fn bind_parameter_count(&self) -> usize {
unsafe { ffi::sqlite3_bind_parameter_count(self.ptr) as usize }
}
+ #[inline]
pub fn bind_parameter_index(&self, name: &str) -> Option<usize> {
self.cache.get_or_insert_with(name, |param_cstr| {
let r = unsafe { ffi::sqlite3_bind_parameter_index(self.ptr, param_cstr.as_ptr()) };
@@ -131,10 +144,24 @@ impl RawStatement {
})
}
+ #[inline]
+ pub fn bind_parameter_name(&self, index: i32) -> Option<&CStr> {
+ unsafe {
+ let name = ffi::sqlite3_bind_parameter_name(self.ptr, index);
+ if name.is_null() {
+ None
+ } else {
+ Some(CStr::from_ptr(name))
+ }
+ }
+ }
+
+ #[inline]
pub fn clear_bindings(&self) -> c_int {
unsafe { ffi::sqlite3_clear_bindings(self.ptr) }
}
+ #[inline]
pub fn sql(&self) -> Option<&CStr> {
if self.ptr.is_null() {
None
@@ -143,36 +170,44 @@ impl RawStatement {
}
}
+ #[inline]
pub fn finalize(mut self) -> c_int {
self.finalize_()
}
+ #[inline]
fn finalize_(&mut self) -> c_int {
let r = unsafe { ffi::sqlite3_finalize(self.ptr) };
self.ptr = ptr::null_mut();
r
}
+ // does not work for PRAGMA
+ #[inline]
#[cfg(all(feature = "extra_check", feature = "modern_sqlite"))] // 3.7.4
pub fn readonly(&self) -> bool {
unsafe { ffi::sqlite3_stmt_readonly(self.ptr) != 0 }
}
+ #[inline]
#[cfg(feature = "modern_sqlite")] // 3.14.0
pub(crate) fn expanded_sql(&self) -> Option<SqliteMallocString> {
unsafe { SqliteMallocString::from_raw(ffi::sqlite3_expanded_sql(self.ptr)) }
}
+ #[inline]
pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 {
assert!(!self.ptr.is_null());
unsafe { ffi::sqlite3_stmt_status(self.ptr, status as i32, reset as i32) }
}
+ #[inline]
#[cfg(feature = "extra_check")]
pub fn has_tail(&self) -> bool {
self.tail != 0
}
+ #[inline]
pub fn tail(&self) -> usize {
self.tail
}
diff --git a/src/row.rs b/src/row.rs
index 36aa1a6..137f13d 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -13,6 +13,7 @@ pub struct Rows<'stmt> {
}
impl<'stmt> Rows<'stmt> {
+ #[inline]
fn reset(&mut self) {
if let Some(stmt) = self.stmt.take() {
stmt.reset();
@@ -28,9 +29,11 @@ impl<'stmt> Rows<'stmt> {
/// 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
+ /// consider using [`query_map`](crate::Statement::query_map) or
+ /// [`query_and_then`](crate::Statement::query_and_then) instead, which
/// return types that implement `Iterator`.
#[allow(clippy::should_implement_trait)] // cannot implement Iterator
+ #[inline]
pub fn next(&mut self) -> Result<Option<&Row<'stmt>>> {
self.advance()?;
Ok((*self).get())
@@ -40,13 +43,14 @@ impl<'stmt> Rows<'stmt> {
/// implements `FallibleIterator`.
/// ```rust,no_run
/// use fallible_iterator::FallibleIterator;
- /// # use rusqlite::{Result, Statement, NO_PARAMS};
+ /// # use rusqlite::{Result, Statement};
/// fn query(stmt: &mut Statement) -> Result<Vec<i64>> {
- /// let rows = stmt.query(NO_PARAMS)?;
+ /// let rows = stmt.query([])?;
/// rows.map(|r| r.get(0)).collect()
/// }
/// ```
// FIXME Hide FallibleStreamingIterator::map
+ #[inline]
pub fn map<F, B>(self, f: F) -> Map<'stmt, F>
where
F: FnMut(&Row<'_>) -> Result<B>,
@@ -56,6 +60,7 @@ impl<'stmt> Rows<'stmt> {
/// Map over this `Rows`, converting it to a [`MappedRows`], which
/// implements `Iterator`.
+ #[inline]
pub fn mapped<F, B>(self, f: F) -> MappedRows<'stmt, F>
where
F: FnMut(&Row<'_>) -> Result<B>,
@@ -66,6 +71,7 @@ impl<'stmt> Rows<'stmt> {
/// Map over this `Rows` with a fallible function, converting it to a
/// [`AndThenRows`], which implements `Iterator` (instead of
/// `FallibleStreamingIterator`).
+ #[inline]
pub fn and_then<F, T, E>(self, f: F) -> AndThenRows<'stmt, F>
where
F: FnMut(&Row<'_>) -> Result<T, E>,
@@ -75,6 +81,7 @@ impl<'stmt> Rows<'stmt> {
}
impl<'stmt> Rows<'stmt> {
+ #[inline]
pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
Rows {
stmt: Some(stmt),
@@ -82,6 +89,7 @@ impl<'stmt> Rows<'stmt> {
}
}
+ #[inline]
pub(crate) fn get_expected_row(&mut self) -> Result<&Row<'stmt>> {
match self.next()? {
Some(row) => Ok(row),
@@ -91,12 +99,14 @@ impl<'stmt> Rows<'stmt> {
}
impl Drop for Rows<'_> {
+ #[inline]
fn drop(&mut self) {
self.reset();
}
}
-/// `F` is used to tranform the _streaming_ iterator into a _fallible_ iterator.
+/// `F` is used to transform the _streaming_ iterator into a _fallible_
+/// iterator.
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct Map<'stmt, F> {
rows: Rows<'stmt>,
@@ -110,6 +120,7 @@ where
type Error = Error;
type Item = B;
+ #[inline]
fn next(&mut self) -> Result<Option<B>> {
match self.rows.next()? {
Some(v) => Ok(Some((self.f)(v)?)),
@@ -120,28 +131,21 @@ where
/// An iterator over the mapped resulting rows of a query.
///
-/// `F` is used to tranform the _streaming_ iterator into a _standard_ iterator.
+/// `F` is used to transform the _streaming_ iterator into a _standard_
+/// iterator.
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct MappedRows<'stmt, F> {
rows: Rows<'stmt>,
map: F,
}
-impl<'stmt, T, F> MappedRows<'stmt, F>
-where
- F: FnMut(&Row<'_>) -> Result<T>,
-{
- pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
- MappedRows { rows, map: f }
- }
-}
-
impl<T, F> Iterator for MappedRows<'_, F>
where
F: FnMut(&Row<'_>) -> Result<T>,
{
type Item = Result<T>;
+ #[inline]
fn next(&mut self) -> Option<Result<T>> {
let map = &mut self.map;
self.rows
@@ -159,15 +163,6 @@ pub struct AndThenRows<'stmt, F> {
map: F,
}
-impl<'stmt, T, E, F> AndThenRows<'stmt, F>
-where
- F: FnMut(&Row<'_>) -> Result<T, E>,
-{
- pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
- AndThenRows { rows, map: f }
- }
-}
-
impl<T, E, F> Iterator for AndThenRows<'_, F>
where
E: convert::From<Error>,
@@ -175,6 +170,7 @@ where
{
type Item = Result<T, E>;
+ #[inline]
fn next(&mut self) -> Option<Self::Item> {
let map = &mut self.map;
self.rows
@@ -193,9 +189,9 @@ where
/// While these iterators cannot be used with Rust `for` loops, `while let`
/// loops offer a similar level of ergonomics:
/// ```rust,no_run
-/// # use rusqlite::{Result, Statement, NO_PARAMS};
+/// # use rusqlite::{Result, Statement};
/// fn query(stmt: &mut Statement) -> Result<()> {
-/// let mut rows = stmt.query(NO_PARAMS)?;
+/// let mut rows = stmt.query([])?;
/// while let Some(row) = rows.next()? {
/// // scan columns value
/// }
@@ -206,6 +202,7 @@ impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
type Error = Error;
type Item = Row<'stmt>;
+ #[inline]
fn advance(&mut self) -> Result<()> {
match self.stmt {
Some(ref stmt) => match stmt.step() {
@@ -231,6 +228,7 @@ impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
}
}
+ #[inline]
fn get(&self) -> Option<&Row<'stmt>> {
self.row.as_ref()
}
@@ -246,7 +244,7 @@ impl<'stmt> Row<'stmt> {
///
/// ## Failure
///
- /// Panics if calling `row.get(idx)` would return an error,
+ /// Panics if calling [`row.get(idx)`](Row::get) would return an error,
/// including:
///
/// * If the underlying SQLite column type is not a valid type as a source
@@ -308,7 +306,7 @@ impl<'stmt> Row<'stmt> {
/// 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`.
+ /// served by [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap).
///
/// ## Failure
///
@@ -317,7 +315,7 @@ impl<'stmt> Row<'stmt> {
///
/// 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<'_>> {
+ pub fn get_ref<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
@@ -332,22 +330,46 @@ impl<'stmt> Row<'stmt> {
/// 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`.
+ /// [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap).
///
/// ## Failure
///
- /// Panics if calling `row.get_raw_checked(idx)` would return an error,
- /// including:
+ /// Panics if calling [`row.get_ref(idx)`](Row::get_ref) 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_ref_unwrap<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
+ self.get_ref(idx).unwrap()
+ }
+
+ /// Renamed to [`get_ref`](Row::get_ref).
+ #[deprecated = "Use [`get_ref`](Row::get_ref) instead."]
+ #[inline]
+ pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
+ self.get_ref(idx)
+ }
+
+ /// Renamed to [`get_ref_unwrap`](Row::get_ref_unwrap).
+ #[deprecated = "Use [`get_ref_unwrap`](Row::get_ref_unwrap) instead."]
+ #[inline]
pub fn get_raw<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
- self.get_raw_checked(idx).unwrap()
+ self.get_ref_unwrap(idx)
}
}
+mod sealed {
+ /// This trait exists just to ensure that the only impls of `trait Params`
+ /// that are allowed are ones in this crate.
+ pub trait Sealed {}
+ impl Sealed for usize {}
+ impl Sealed for &str {}
+}
+
/// A trait implemented by types that can index into columns of a row.
-pub trait RowIndex {
+///
+/// It is only implemented for `usize` and `&str`.
+pub trait RowIndex: sealed::Sealed {
/// Returns the index of the appropriate column, or `None` if no such
/// column exists.
fn idx(&self, stmt: &Statement<'_>) -> Result<usize>;
@@ -408,74 +430,46 @@ tuples_try_from_row!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
#[cfg(test)]
mod tests {
#![allow(clippy::redundant_closure)] // false positives due to lifetime issues; clippy issue #5594
+ use crate::{Connection, Result};
#[test]
- fn test_try_from_row_for_tuple_1() {
- use crate::{Connection, ToSql};
+ fn test_try_from_row_for_tuple_1() -> Result<()> {
+ use crate::ToSql;
use std::convert::TryFrom;
- let conn = Connection::open_in_memory().expect("failed to create in-memoory database");
+ let conn = Connection::open_in_memory()?;
conn.execute(
"CREATE TABLE test (a INTEGER)",
- std::iter::empty::<&dyn ToSql>(),
- )
- .expect("failed to create table");
- conn.execute(
- "INSERT INTO test VALUES (42)",
- std::iter::empty::<&dyn ToSql>(),
- )
- .expect("failed to insert value");
- let val = conn
- .query_row(
- "SELECT a FROM test",
- std::iter::empty::<&dyn ToSql>(),
- |row| <(u32,)>::try_from(row),
- )
- .expect("failed to query row");
+ crate::params_from_iter(std::iter::empty::<&dyn ToSql>()),
+ )?;
+ conn.execute("INSERT INTO test VALUES (42)", [])?;
+ let val = conn.query_row("SELECT a FROM test", [], |row| <(u32,)>::try_from(row))?;
assert_eq!(val, (42,));
- let fail = conn.query_row(
- "SELECT a FROM test",
- std::iter::empty::<&dyn ToSql>(),
- |row| <(u32, u32)>::try_from(row),
- );
+ let fail = conn.query_row("SELECT a FROM test", [], |row| <(u32, u32)>::try_from(row));
assert!(fail.is_err());
+ Ok(())
}
#[test]
- fn test_try_from_row_for_tuple_2() {
- use crate::{Connection, ToSql};
+ fn test_try_from_row_for_tuple_2() -> Result<()> {
use std::convert::TryFrom;
- let conn = Connection::open_in_memory().expect("failed to create in-memoory database");
- conn.execute(
- "CREATE TABLE test (a INTEGER, b INTEGER)",
- std::iter::empty::<&dyn ToSql>(),
- )
- .expect("failed to create table");
- conn.execute(
- "INSERT INTO test VALUES (42, 47)",
- std::iter::empty::<&dyn ToSql>(),
- )
- .expect("failed to insert value");
- let val = conn
- .query_row(
- "SELECT a, b FROM test",
- std::iter::empty::<&dyn ToSql>(),
- |row| <(u32, u32)>::try_from(row),
- )
- .expect("failed to query row");
+ let conn = Connection::open_in_memory()?;
+ conn.execute("CREATE TABLE test (a INTEGER, b INTEGER)", [])?;
+ conn.execute("INSERT INTO test VALUES (42, 47)", [])?;
+ let val = conn.query_row("SELECT a, b FROM test", [], |row| {
+ <(u32, u32)>::try_from(row)
+ })?;
assert_eq!(val, (42, 47));
- let fail = conn.query_row(
- "SELECT a, b FROM test",
- std::iter::empty::<&dyn ToSql>(),
- |row| <(u32, u32, u32)>::try_from(row),
- );
+ let fail = conn.query_row("SELECT a, b FROM test", [], |row| {
+ <(u32, u32, u32)>::try_from(row)
+ });
assert!(fail.is_err());
+ Ok(())
}
#[test]
- fn test_try_from_row_for_tuple_16() {
- use crate::{Connection, ToSql};
+ fn test_try_from_row_for_tuple_16() -> Result<()> {
use std::convert::TryFrom;
let create_table = "CREATE TABLE test (
@@ -535,18 +529,10 @@ mod tests {
u32,
);
- let conn = Connection::open_in_memory().expect("failed to create in-memoory database");
- conn.execute(create_table, std::iter::empty::<&dyn ToSql>())
- .expect("failed to create table");
- conn.execute(insert_values, std::iter::empty::<&dyn ToSql>())
- .expect("failed to insert value");
- let val = conn
- .query_row(
- "SELECT * FROM test",
- std::iter::empty::<&dyn ToSql>(),
- |row| BigTuple::try_from(row),
- )
- .expect("failed to query row");
+ let conn = Connection::open_in_memory()?;
+ conn.execute(create_table, [])?;
+ conn.execute(insert_values, [])?;
+ let val = conn.query_row("SELECT * FROM test", [], |row| BigTuple::try_from(row))?;
// Debug is not implemented for tuples of 16
assert_eq!(val.0, 0);
assert_eq!(val.1, 1);
@@ -566,5 +552,6 @@ mod tests {
assert_eq!(val.15, 15);
// We don't test one bigger because it's unimplemented
+ Ok(())
}
}
diff --git a/src/session.rs b/src/session.rs
index 97ae3a5..4e4c354 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -29,11 +29,13 @@ pub struct Session<'conn> {
impl Session<'_> {
/// Create a new session object
- pub fn new<'conn>(db: &'conn Connection) -> Result<Session<'conn>> {
+ #[inline]
+ pub fn new(db: &Connection) -> Result<Session<'_>> {
Session::new_with_name(db, DatabaseName::Main)
}
/// Create a new session object
+ #[inline]
pub fn new_with_name<'conn>(
db: &'conn Connection,
name: DatabaseName<'_>,
@@ -120,6 +122,7 @@ impl Session<'_> {
}
/// Write the set of changes represented by this session to `output`.
+ #[inline]
pub fn changeset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
check!(unsafe {
@@ -133,6 +136,7 @@ impl Session<'_> {
}
/// Generate a Patchset
+ #[inline]
pub fn patchset(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut ps: *mut c_void = ptr::null_mut();
@@ -142,6 +146,7 @@ impl Session<'_> {
}
/// Write the set of patches represented by this session to `output`.
+ #[inline]
pub fn patchset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
check!(unsafe {
@@ -174,16 +179,19 @@ impl Session<'_> {
}
/// Test if a changeset has recorded any changes
+ #[inline]
pub fn is_empty(&self) -> bool {
unsafe { ffi::sqlite3session_isempty(self.s) != 0 }
}
/// Query the current state of the session
+ #[inline]
pub fn is_enabled(&self) -> bool {
unsafe { ffi::sqlite3session_enable(self.s, -1) != 0 }
}
/// Enable or disable the recording of changes
+ #[inline]
pub fn set_enabled(&mut self, enabled: bool) {
unsafe {
ffi::sqlite3session_enable(self.s, if enabled { 1 } else { 0 });
@@ -191,11 +199,13 @@ impl Session<'_> {
}
/// Query the current state of the indirect flag
+ #[inline]
pub fn is_indirect(&self) -> bool {
unsafe { ffi::sqlite3session_indirect(self.s, -1) != 0 }
}
/// Set or clear the indirect change flag
+ #[inline]
pub fn set_indirect(&mut self, indirect: bool) {
unsafe {
ffi::sqlite3session_indirect(self.s, if indirect { 1 } else { 0 });
@@ -204,6 +214,7 @@ impl Session<'_> {
}
impl Drop for Session<'_> {
+ #[inline]
fn drop(&mut self) {
if self.filter.is_some() {
self.table_filter(None::<fn(&str) -> bool>);
@@ -213,6 +224,7 @@ impl Drop for Session<'_> {
}
/// `feature = "session"` Invert a changeset
+#[inline]
pub fn invert_strm(input: &mut dyn Read, output: &mut dyn Write) -> Result<()> {
let input_ref = &input;
let output_ref = &output;
@@ -228,6 +240,7 @@ pub fn invert_strm(input: &mut dyn Read, output: &mut dyn Write) -> Result<()> {
}
/// `feature = "session"` Combine two changesets
+#[inline]
pub fn concat_strm(
input_a: &mut dyn Read,
input_b: &mut dyn Read,
@@ -257,6 +270,7 @@ pub struct Changeset {
impl Changeset {
/// Invert a changeset
+ #[inline]
pub fn invert(&self) -> Result<Changeset> {
let mut n = 0;
let mut cs = ptr::null_mut();
@@ -267,6 +281,7 @@ impl Changeset {
}
/// Create an iterator to traverse a changeset
+ #[inline]
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
let mut it = ptr::null_mut();
check!(unsafe { ffi::sqlite3changeset_start(&mut it as *mut *mut _, self.n, self.cs) });
@@ -278,6 +293,7 @@ impl Changeset {
}
/// Concatenate two changeset objects
+ #[inline]
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
let mut n = 0;
let mut cs = ptr::null_mut();
@@ -289,6 +305,7 @@ impl Changeset {
}
impl Drop for Changeset {
+ #[inline]
fn drop(&mut self) {
unsafe {
ffi::sqlite3_free(self.cs);
@@ -306,6 +323,7 @@ pub struct ChangesetIter<'changeset> {
impl ChangesetIter<'_> {
/// Create an iterator on `input`
+ #[inline]
pub fn start_strm<'input>(input: &&'input mut dyn Read) -> Result<ChangesetIter<'input>> {
let mut it = ptr::null_mut();
check!(unsafe {
@@ -327,6 +345,7 @@ impl FallibleStreamingIterator for ChangesetIter<'_> {
type Error = crate::error::Error;
type Item = ChangesetItem;
+ #[inline]
fn advance(&mut self) -> Result<()> {
let rc = unsafe { ffi::sqlite3changeset_next(self.it) };
match rc {
@@ -342,6 +361,7 @@ impl FallibleStreamingIterator for ChangesetIter<'_> {
}
}
+ #[inline]
fn get(&self) -> Option<&ChangesetItem> {
self.item.as_ref()
}
@@ -357,27 +377,32 @@ pub struct Operation<'item> {
impl Operation<'_> {
/// Returns the table name.
+ #[inline]
pub fn table_name(&self) -> &str {
self.table_name
}
/// Returns the number of columns in table
+ #[inline]
pub fn number_of_columns(&self) -> i32 {
self.number_of_columns
}
/// Returns the action code.
+ #[inline]
pub fn code(&self) -> Action {
self.code
}
/// Returns `true` for an 'indirect' change.
+ #[inline]
pub fn indirect(&self) -> bool {
self.indirect
}
}
impl Drop for ChangesetIter<'_> {
+ #[inline]
fn drop(&mut self) {
unsafe {
ffi::sqlite3changeset_finalize(self.it);
@@ -386,7 +411,8 @@ impl Drop for ChangesetIter<'_> {
}
/// `feature = "session"` An item passed to a conflict-handler by
-/// `Connection::apply`, or an item generated by `ChangesetIter::next`.
+/// [`Connection::apply`](crate::Connection::apply), or an item generated by
+/// [`ChangesetIter::next`](ChangesetIter::next).
// TODO enum ? Delete, Insert, Update, ...
pub struct ChangesetItem {
it: *mut ffi::sqlite3_changeset_iter,
@@ -397,6 +423,7 @@ impl ChangesetItem {
///
/// May only be called with an `SQLITE_CHANGESET_DATA` or
/// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
+ #[inline]
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
@@ -413,6 +440,7 @@ impl ChangesetItem {
///
/// May only be called with an `SQLITE_CHANGESET_FOREIGN_KEY` conflict
/// handler callback.
+ #[inline]
pub fn fk_conflicts(&self) -> Result<i32> {
unsafe {
let mut p_out = 0;
@@ -425,6 +453,7 @@ impl ChangesetItem {
///
/// May only be called if the type of change is either `SQLITE_UPDATE` or
/// `SQLITE_INSERT`.
+ #[inline]
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
@@ -437,6 +466,7 @@ impl ChangesetItem {
///
/// May only be called if the type of change is either `SQLITE_DELETE` or
/// `SQLITE_UPDATE`.
+ #[inline]
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
@@ -446,6 +476,7 @@ impl ChangesetItem {
}
/// Obtain the current operation
+ #[inline]
pub fn op(&self) -> Result<Operation<'_>> {
let mut number_of_columns = 0;
let mut code = 0;
@@ -471,6 +502,7 @@ impl ChangesetItem {
}
/// Obtain the primary key definition of a table
+ #[inline]
pub fn pk(&self) -> Result<&[u8]> {
let mut number_of_columns = 0;
unsafe {
@@ -493,6 +525,7 @@ pub struct Changegroup {
impl Changegroup {
/// Create a new change group.
+ #[inline]
pub fn new() -> Result<Self> {
let mut cg = ptr::null_mut();
check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) });
@@ -500,12 +533,14 @@ impl Changegroup {
}
/// Add a changeset
+ #[inline]
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.
+ #[inline]
pub fn add_stream(&mut self, input: &mut dyn Read) -> Result<()> {
let input_ref = &input;
check!(unsafe {
@@ -519,6 +554,7 @@ impl Changegroup {
}
/// Obtain a composite Changeset
+ #[inline]
pub fn output(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut output: *mut c_void = ptr::null_mut();
@@ -527,6 +563,7 @@ impl Changegroup {
}
/// Write the combined set of changes to `output`.
+ #[inline]
pub fn output_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
check!(unsafe {
@@ -541,6 +578,7 @@ impl Changegroup {
}
impl Drop for Changegroup {
+ #[inline]
fn drop(&mut self) {
unsafe {
ffi::sqlite3changegroup_delete(self.cg);
@@ -630,6 +668,7 @@ impl Connection {
#[repr(i32)]
#[derive(Debug, PartialEq)]
#[non_exhaustive]
+#[allow(clippy::upper_case_acronyms)]
pub enum ConflictType {
UNKNOWN = -1,
SQLITE_CHANGESET_DATA = ffi::SQLITE_CHANGESET_DATA,
@@ -657,6 +696,7 @@ impl From<i32> for ConflictType {
#[repr(i32)]
#[derive(Debug, PartialEq)]
#[non_exhaustive]
+#[allow(clippy::upper_case_acronyms)]
pub enum ConflictAction {
SQLITE_CHANGESET_OMIT = ffi::SQLITE_CHANGESET_OMIT,
SQLITE_CHANGESET_REPLACE = ffi::SQLITE_CHANGESET_REPLACE,
@@ -744,84 +784,79 @@ mod test {
use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session};
use crate::hooks::Action;
- use crate::Connection;
+ use crate::{Connection, Result};
- fn one_changeset() -> Changeset {
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
- .unwrap();
+ fn one_changeset() -> Result<Changeset> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
- let mut session = Session::new(&db).unwrap();
+ let mut session = Session::new(&db)?;
assert!(session.is_empty());
- session.attach(None).unwrap();
- db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
- .unwrap();
+ session.attach(None)?;
+ db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
- session.changeset().unwrap()
+ session.changeset()
}
- 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();
+ fn one_changeset_strm() -> Result<Vec<u8>> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
- let mut session = Session::new(&db).unwrap();
+ let mut session = Session::new(&db)?;
assert!(session.is_empty());
- session.attach(None).unwrap();
- db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
- .unwrap();
+ session.attach(None)?;
+ db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
let mut output = Vec::new();
- session.changeset_strm(&mut output).unwrap();
- output
+ session.changeset_strm(&mut output)?;
+ Ok(output)
}
#[test]
- fn test_changeset() {
- let changeset = one_changeset();
- let mut iter = changeset.iter().unwrap();
- let item = iter.next().unwrap();
+ fn test_changeset() -> Result<()> {
+ let changeset = one_changeset()?;
+ let mut iter = changeset.iter()?;
+ let item = iter.next()?;
assert!(item.is_some());
let item = item.unwrap();
- let op = item.op().unwrap();
+ let op = item.op()?;
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();
+ let pk = item.pk()?;
assert_eq!(&[1], pk);
- let new_value = item.new_value(0).unwrap();
+ let new_value = item.new_value(0)?;
assert_eq!(Ok("bar"), new_value.as_str());
+ Ok(())
}
#[test]
- fn test_changeset_strm() {
- let output = one_changeset_strm();
+ fn test_changeset_strm() -> Result<()> {
+ let output = one_changeset_strm()?;
assert!(!output.is_empty());
assert_eq!(14, output.len());
let input: &mut dyn Read = &mut output.as_slice();
- let mut iter = ChangesetIter::start_strm(&input).unwrap();
- let item = iter.next().unwrap();
+ let mut iter = ChangesetIter::start_strm(&input)?;
+ let item = iter.next()?;
assert!(item.is_some());
+ Ok(())
}
#[test]
- fn test_changeset_apply() {
- let changeset = one_changeset();
+ fn test_changeset_apply() -> Result<()> {
+ let changeset = one_changeset()?;
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
- .unwrap();
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
- lazy_static::lazy_static! {
- static ref CALLED: AtomicBool = AtomicBool::new(false);
- }
+ static CALLED: AtomicBool = AtomicBool::new(false);
db.apply(
&changeset,
None::<fn(&str) -> bool>,
@@ -829,15 +864,12 @@ mod test {
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();
+ let check = db.query_row("SELECT 1 FROM foo WHERE t = ?", ["bar"], |row| {
+ row.get::<_, i32>(0)
+ })?;
assert_eq!(1, check);
// conflict expected when same changeset applied again on the same db
@@ -851,68 +883,66 @@ mod test {
assert_eq!(Ok("bar"), conflict.as_str());
ConflictAction::SQLITE_CHANGESET_OMIT
},
- )
- .unwrap();
+ )?;
assert!(CALLED.load(Ordering::Relaxed));
+ Ok(())
}
#[test]
- fn test_changeset_apply_strm() {
- let output = one_changeset_strm();
+ fn test_changeset_apply_strm() -> Result<()> {
+ 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();
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
let mut input = output.as_slice();
db.apply_strm(
&mut input,
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();
+ let check = db.query_row("SELECT 1 FROM foo WHERE t = ?", ["bar"], |row| {
+ row.get::<_, i32>(0)
+ })?;
assert_eq!(1, check);
+ Ok(())
}
#[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();
+ fn test_session_empty() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
- let mut session = Session::new(&db).unwrap();
+ let mut session = Session::new(&db)?;
assert!(session.is_empty());
- session.attach(None).unwrap();
- db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
- .unwrap();
+ session.attach(None)?;
+ db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
assert!(!session.is_empty());
+ Ok(())
}
#[test]
- fn test_session_set_enabled() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_session_set_enabled() -> Result<()> {
+ let db = Connection::open_in_memory()?;
- let mut session = Session::new(&db).unwrap();
+ let mut session = Session::new(&db)?;
assert!(session.is_enabled());
session.set_enabled(false);
assert!(!session.is_enabled());
+ Ok(())
}
#[test]
- fn test_session_set_indirect() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_session_set_indirect() -> Result<()> {
+ let db = Connection::open_in_memory()?;
- let mut session = Session::new(&db).unwrap();
+ let mut session = Session::new(&db)?;
assert!(!session.is_indirect());
session.set_indirect(true);
assert!(session.is_indirect());
+ Ok(())
}
}
diff --git a/src/statement.rs b/src/statement.rs
index 648a9b7..139f504 100644
--- a/src/statement.rs
+++ b/src/statement.rs
@@ -8,7 +8,7 @@ use std::{convert, fmt, mem, ptr, str};
use super::ffi;
use super::{len_as_c_int, str_for_sqlite};
use super::{
- AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
+ AndThenRows, Connection, Error, MappedRows, Params, RawStatement, Result, Row, Rows, ValueRef,
};
use crate::types::{ToSql, ToSqlOutput};
#[cfg(feature = "array")]
@@ -28,14 +28,49 @@ impl Statement<'_> {
///
/// ## Example
///
+ /// ### Use with positional parameters
+ ///
/// ```rust,no_run
- /// # use rusqlite::{Connection, Result};
+ /// # use rusqlite::{Connection, Result, params};
/// fn update_rows(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("UPDATE foo SET bar = 'baz' WHERE qux = ?")?;
+ /// // The `rusqlite::params!` macro is mostly useful when the parameters do not
+ /// // all have the same type, or if there are more than 32 parameters
+ /// // at once.
+ /// stmt.execute(params![1i32])?;
+ /// // However, it's not required, many cases are fine as:
+ /// stmt.execute(&[&2i32])?;
+ /// // Or even:
+ /// stmt.execute([2i32])?;
+ /// Ok(())
+ /// }
+ /// ```
+ ///
+ /// ### Use with named parameters
+ ///
+ /// ```rust,no_run
+ /// # use rusqlite::{Connection, Result, named_params};
+ /// fn insert(conn: &Connection) -> Result<()> {
+ /// let mut stmt = conn.prepare("INSERT INTO test (key, value) VALUES (:key, :value)")?;
+ /// // The `rusqlite::named_params!` macro (like `params!`) is useful for heterogeneous
+ /// // sets of parameters (where all parameters are not the same type), or for queries
+ /// // with many (more than 32) statically known parameters.
+ /// stmt.execute(named_params!{ ":key": "one", ":val": 2 })?;
+ /// // However, named parameters can also be passed like:
+ /// stmt.execute(&[(":key", "three"), (":val", "four")])?;
+ /// // Or even: (note that a &T is required for the value type, currently)
+ /// stmt.execute(&[(":key", &100), (":val", &200)])?;
+ /// Ok(())
+ /// }
+ /// ```
///
- /// stmt.execute(&[1i32])?;
- /// stmt.execute(&[2i32])?;
+ /// ### Use without parameters
///
+ /// ```rust,no_run
+ /// # use rusqlite::{Connection, Result, params};
+ /// fn delete_all(conn: &Connection) -> Result<()> {
+ /// let mut stmt = conn.prepare("DELETE FROM users")?;
+ /// stmt.execute([])?;
/// Ok(())
/// }
/// ```
@@ -45,73 +80,51 @@ impl Statement<'_> {
/// Will return `Err` if binding parameters fails, the executed statement
/// returns rows (in which case `query` should be used instead), or the
/// underlying SQLite call fails.
- pub fn execute<P>(&mut self, params: P) -> Result<usize>
- where
- P: IntoIterator,
- P::Item: ToSql,
- {
- self.bind_parameters(params)?;
+ #[inline]
+ pub fn execute<P: Params>(&mut self, params: P) -> Result<usize> {
+ params.__bind_in(self)?;
self.execute_with_bound_parameters()
}
- /// Execute the prepared statement with named parameter(s). If any
- /// parameters that were in the prepared statement are not included in
- /// `params`, they will continue to use the most-recently bound value
- /// from a previous call to `execute_named`, or `NULL` if they have
- /// never been bound.
- ///
- /// On success, returns the number of rows that were changed or inserted or
- /// deleted (via `sqlite3_changes`).
- ///
- /// ## Example
+ /// Execute the prepared statement with named parameter(s).
///
- /// ```rust,no_run
- /// # use rusqlite::{Connection, Result};
- /// fn insert(conn: &Connection) -> Result<usize> {
- /// let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?;
- /// stmt.execute_named(&[(":name", &"one")])
- /// }
- /// ```
+ /// Note: This function is deprecated in favor of [`Statement::execute`],
+ /// which can now take named parameters directly.
///
- /// Note, the `named_params` macro is provided for syntactic convenience,
- /// and so the above example could also be written as:
+ /// If any parameters that were in the prepared statement are not included
+ /// in `params`, they will continue to use the most-recently bound value
+ /// from a previous call to `execute_named`, or `NULL` if they have never
+ /// been bound.
///
- /// ```rust,no_run
- /// # use rusqlite::{Connection, Result, named_params};
- /// fn insert(conn: &Connection) -> Result<usize> {
- /// let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?;
- /// stmt.execute_named(named_params!{":name": "one"})
- /// }
- /// ```
+ /// On success, returns the number of rows that were changed or inserted or
+ /// deleted (via `sqlite3_changes`).
///
/// # Failure
///
/// Will return `Err` if binding parameters fails, the executed statement
/// returns rows (in which case `query` should be used instead), or the
/// underlying SQLite call fails.
+ #[deprecated = "You can use `execute` with named params now."]
+ #[inline]
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
- self.bind_parameters_named(params)?;
- self.execute_with_bound_parameters()
+ self.execute(params)
}
/// Execute an INSERT and return the ROWID.
///
/// # Note
///
- /// This function is a convenience wrapper around `execute()` intended for
- /// queries that insert a single item. It is possible to misuse this
- /// function in a way that it cannot detect, such as by calling it on a
- /// statement which _updates_ a single
- /// item rather than inserting one. Please don't do that.
+ /// This function is a convenience wrapper around
+ /// [`execute()`](Statement::execute) intended for queries that insert a
+ /// single item. It is possible to misuse this function in a way that it
+ /// cannot detect, such as by calling it on a statement which _updates_
+ /// a single item rather than inserting one. Please don't do that.
///
/// # Failure
///
/// Will return `Err` if no row is inserted or many rows are inserted.
- pub fn insert<P>(&mut self, params: P) -> Result<i64>
- where
- P: IntoIterator,
- P::Item: ToSql,
- {
+ #[inline]
+ pub fn insert<P: Params>(&mut self, params: P) -> Result<i64> {
let changes = self.execute(params)?;
match changes {
1 => Ok(self.conn.last_insert_rowid()),
@@ -123,16 +136,19 @@ impl Statement<'_> {
/// rows.
///
/// Due to lifetime restricts, the rows handle returned by `query` does not
- /// implement the `Iterator` trait. Consider using `query_map` or
- /// `query_and_then` instead, which do.
+ /// implement the `Iterator` trait. Consider using
+ /// [`query_map`](Statement::query_map) or
+ /// [`query_and_then`](Statement::query_and_then) instead, which do.
///
/// ## Example
///
+ /// ### Use without parameters
+ ///
/// ```rust,no_run
- /// # use rusqlite::{Connection, Result, NO_PARAMS};
+ /// # use rusqlite::{Connection, Result};
/// fn get_names(conn: &Connection) -> Result<Vec<String>> {
/// let mut stmt = conn.prepare("SELECT name FROM people")?;
- /// let mut rows = stmt.query(NO_PARAMS)?;
+ /// let mut rows = stmt.query([])?;
///
/// let mut names = Vec::new();
/// while let Some(row) = rows.next()? {
@@ -143,32 +159,41 @@ impl Statement<'_> {
/// }
/// ```
///
- /// ## Failure
+ /// ### Use with positional parameters
///
- /// Will return `Err` if binding parameters fails.
- pub fn query<P>(&mut self, params: P) -> Result<Rows<'_>>
- where
- P: IntoIterator,
- P::Item: ToSql,
- {
- self.check_readonly()?;
- self.bind_parameters(params)?;
- Ok(Rows::new(self))
- }
-
- /// Execute the prepared statement with named parameter(s), returning a
- /// handle for the resulting rows. If any parameters that were in the
- /// prepared statement are not included in `params`, they will continue
- /// to use the most-recently bound value from a previous
- /// call to `query_named`, or `NULL` if they have never been bound.
+ /// ```rust,no_run
+ /// # use rusqlite::{Connection, Result};
+ /// fn query(conn: &Connection, name: &str) -> Result<()> {
+ /// let mut stmt = conn.prepare("SELECT * FROM test where name = ?")?;
+ /// let mut rows = stmt.query(rusqlite::params![name])?;
+ /// while let Some(row) = rows.next()? {
+ /// // ...
+ /// }
+ /// Ok(())
+ /// }
+ /// ```
///
- /// ## Example
+ /// Or, equivalently (but without the [`params!`] macro).
+ ///
+ /// ```rust,no_run
+ /// # use rusqlite::{Connection, Result};
+ /// fn query(conn: &Connection, name: &str) -> Result<()> {
+ /// let mut stmt = conn.prepare("SELECT * FROM test where name = ?")?;
+ /// let mut rows = stmt.query([name])?;
+ /// while let Some(row) = rows.next()? {
+ /// // ...
+ /// }
+ /// Ok(())
+ /// }
+ /// ```
+ ///
+ /// ### Use with named parameters
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn query(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
- /// let mut rows = stmt.query_named(&[(":name", &"one")])?;
+ /// let mut rows = stmt.query(&[(":name", "one")])?;
/// while let Some(row) = rows.next()? {
/// // ...
/// }
@@ -183,7 +208,7 @@ impl Statement<'_> {
/// # use rusqlite::{Connection, Result, named_params};
/// fn query(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
- /// let mut rows = stmt.query_named(named_params!{ ":name": "one" })?;
+ /// let mut rows = stmt.query(named_params!{ ":name": "one" })?;
/// while let Some(row) = rows.next()? {
/// // ...
/// }
@@ -191,25 +216,51 @@ impl Statement<'_> {
/// }
/// ```
///
+ /// ## Failure
+ ///
+ /// Will return `Err` if binding parameters fails.
+ #[inline]
+ pub fn query<P: Params>(&mut self, params: P) -> Result<Rows<'_>> {
+ params.__bind_in(self)?;
+ Ok(Rows::new(self))
+ }
+
+ /// Execute the prepared statement with named parameter(s), returning a
+ /// handle for the resulting rows.
+ ///
+ /// Note: This function is deprecated in favor of [`Statement::query`],
+ /// which can now take named parameters directly.
+ ///
+ /// If any parameters that were in the prepared statement are not included
+ /// in `params`, they will continue to use the most-recently bound value
+ /// from a previous call to `query_named`, or `NULL` if they have never been
+ /// bound.
+ ///
/// # Failure
///
/// Will return `Err` if binding parameters fails.
+ #[deprecated = "You can use `query` with named params now."]
pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'_>> {
- self.check_readonly()?;
- self.bind_parameters_named(params)?;
- Ok(Rows::new(self))
+ self.query(params)
}
/// Executes the prepared statement and maps a function over the resulting
/// rows, returning an iterator over the mapped function results.
///
+ /// `f` is used to transform the _streaming_ iterator into a _standard_
+ /// iterator.
+ ///
+ /// This is equivalent to `stmt.query(params)?.mapped(f)`.
+ ///
/// ## Example
///
+ /// ### Use with positional params
+ ///
/// ```rust,no_run
- /// # use rusqlite::{Connection, Result, NO_PARAMS};
+ /// # use rusqlite::{Connection, Result};
/// fn get_names(conn: &Connection) -> Result<Vec<String>> {
/// let mut stmt = conn.prepare("SELECT name FROM people")?;
- /// let rows = stmt.query_map(NO_PARAMS, |row| row.get(0))?;
+ /// let rows = stmt.query_map([], |row| row.get(0))?;
///
/// let mut names = Vec::new();
/// for name_result in rows {
@@ -219,51 +270,53 @@ impl Statement<'_> {
/// Ok(names)
/// }
/// ```
- /// `f` is used to tranform the _streaming_ iterator into a _standard_
- /// iterator.
///
+ /// ### Use with named params
+ ///
+ /// ```rust,no_run
+ /// # use rusqlite::{Connection, Result};
+ /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
+ /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
+ /// let rows = stmt.query_map(&[(":id", &"one")], |row| row.get(0))?;
+ ///
+ /// let mut names = Vec::new();
+ /// for name_result in rows {
+ /// names.push(name_result?);
+ /// }
+ ///
+ /// Ok(names)
+ /// }
+ /// ```
/// ## Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_map<T, P, F>(&mut self, params: P, f: F) -> Result<MappedRows<'_, F>>
where
- P: IntoIterator,
- P::Item: ToSql,
+ P: Params,
F: FnMut(&Row<'_>) -> Result<T>,
{
- let rows = self.query(params)?;
- Ok(MappedRows::new(rows, f))
+ self.query(params).map(|rows| rows.mapped(f))
}
/// Execute the prepared statement with named parameter(s), returning an
/// iterator over the result of calling the mapping function over the
- /// query's rows. If any parameters that were in the prepared statement
+ /// query's rows.
+ ///
+ /// Note: This function is deprecated in favor of [`Statement::query_map`],
+ /// which can now take named parameters directly.
+ ///
+ /// If any parameters that were in the prepared statement
/// are not included in `params`, they will continue to use the
/// most-recently bound value from a previous call to `query_named`,
/// or `NULL` if they have never been bound.
///
- /// ## Example
- ///
- /// ```rust,no_run
- /// # use rusqlite::{Connection, Result};
- /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
- /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
- /// let rows = stmt.query_map_named(&[(":id", &"one")], |row| row.get(0))?;
- ///
- /// let mut names = Vec::new();
- /// for name_result in rows {
- /// names.push(name_result?);
- /// }
- ///
- /// Ok(names)
- /// }
- /// ```
- /// `f` is used to tranform the _streaming_ iterator into a _standard_
+ /// `f` is used to transform the _streaming_ iterator into a _standard_
/// iterator.
///
/// ## Failure
///
/// Will return `Err` if binding parameters fails.
+ #[deprecated = "You can use `query_map` with named params now."]
pub fn query_map_named<T, F>(
&mut self,
params: &[(&str, &dyn ToSql)],
@@ -272,38 +325,19 @@ impl Statement<'_> {
where
F: FnMut(&Row<'_>) -> Result<T>,
{
- let rows = self.query_named(params)?;
- Ok(MappedRows::new(rows, f))
+ self.query_map(params, f)
}
/// Executes the prepared statement and maps a function over the resulting
/// rows, where the function returns a `Result` with `Error` type
/// implementing `std::convert::From<Error>` (so errors can be unified).
///
- /// # Failure
- ///
- /// Will return `Err` if binding parameters fails.
- pub fn query_and_then<T, E, P, F>(&mut self, params: P, f: F) -> Result<AndThenRows<'_, F>>
- where
- P: IntoIterator,
- P::Item: ToSql,
- E: convert::From<Error>,
- F: FnMut(&Row<'_>) -> Result<T, E>,
- {
- let rows = self.query(params)?;
- Ok(AndThenRows::new(rows, f))
- }
-
- /// Execute the prepared statement with named parameter(s), returning an
- /// iterator over the result of calling the mapping function over the
- /// query's rows. If any parameters that were in the prepared statement
- /// are not included in
- /// `params`, they will
- /// continue to use the most-recently bound value from a previous call
- /// to `query_named`, or `NULL` if they have never been bound.
+ /// This is equivalent to `stmt.query(params)?.and_then(f)`.
///
/// ## Example
///
+ /// ### Use with named params
+ ///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// struct Person {
@@ -318,7 +352,7 @@ impl Statement<'_> {
/// fn get_names(conn: &Connection) -> Result<Vec<Person>> {
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
/// let rows =
- /// stmt.query_and_then_named(&[(":id", &"one")], |row| name_to_person(row.get(0)?))?;
+ /// stmt.query_and_then(&[(":id", "one")], |row| name_to_person(row.get(0)?))?;
///
/// let mut persons = Vec::new();
/// for person_result in rows {
@@ -329,9 +363,53 @@ impl Statement<'_> {
/// }
/// ```
///
+ /// ### Use with positional params
+ ///
+ /// ```rust,no_run
+ /// # use rusqlite::{Connection, Result};
+ /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
+ /// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = ?")?;
+ /// let rows = stmt.query_and_then(["one"], |row| row.get::<_, String>(0))?;
+ ///
+ /// let mut persons = Vec::new();
+ /// for person_result in rows {
+ /// persons.push(person_result?);
+ /// }
+ ///
+ /// Ok(persons)
+ /// }
+ /// ```
+ ///
+ /// # Failure
+ ///
+ /// Will return `Err` if binding parameters fails.
+ #[inline]
+ pub fn query_and_then<T, E, P, F>(&mut self, params: P, f: F) -> Result<AndThenRows<'_, F>>
+ where
+ P: Params,
+ E: convert::From<Error>,
+ F: FnMut(&Row<'_>) -> Result<T, E>,
+ {
+ self.query(params).map(|rows| rows.and_then(f))
+ }
+
+ /// Execute the prepared statement with named parameter(s), returning an
+ /// iterator over the result of calling the mapping function over the
+ /// query's rows.
+ ///
+ /// Note: This function is deprecated in favor of
+ /// [`Statement::query_and_then`], which can now take named parameters
+ /// directly.
+ ///
+ /// If any parameters that were in the prepared statement are not included
+ /// in `params`, they will continue to use the most-recently bound value
+ /// from a previous call to `query_named`, or `NULL` if they have never been
+ /// bound.
+ ///
/// ## Failure
///
/// Will return `Err` if binding parameters fails.
+ #[deprecated = "You can use `query_and_then` with named params now."]
pub fn query_and_then_named<T, E, F>(
&mut self,
params: &[(&str, &dyn ToSql)],
@@ -341,17 +419,13 @@ impl Statement<'_> {
E: convert::From<Error>,
F: FnMut(&Row<'_>) -> Result<T, E>,
{
- let rows = self.query_named(params)?;
- Ok(AndThenRows::new(rows, f))
+ self.query_and_then(params, f)
}
/// Return `true` if a query in the SQL statement it executes returns one
/// or more rows and `false` if the SQL returns an empty set.
- pub fn exists<P>(&mut self, params: P) -> Result<bool>
- where
- P: IntoIterator,
- P::Item: ToSql,
- {
+ #[inline]
+ pub fn exists<P: Params>(&mut self, params: P) -> Result<bool> {
let mut rows = self.query(params)?;
let exists = rows.next()?.is_some();
Ok(exists)
@@ -364,16 +438,17 @@ impl Statement<'_> {
/// ignored.
///
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
- /// query truly is optional, you can call `.optional()` on the result of
- /// this to get a `Result<Option<T>>`.
+ /// query truly is optional, you can call
+ /// [`.optional()`](crate::OptionalExtension::optional) on the result of
+ /// this to get a `Result<Option<T>>` (requires that the trait
+ /// `rusqlite::OptionalExtension` is imported).
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn query_row<T, P, F>(&mut self, params: P, f: F) -> Result<T>
where
- P: IntoIterator,
- P::Item: ToSql,
+ P: Params,
F: FnOnce(&Row<'_>) -> Result<T>,
{
let mut rows = self.query(params)?;
@@ -384,24 +459,29 @@ impl Statement<'_> {
/// Convenience method to execute a query with named parameter(s) that is
/// expected to return a single row.
///
+ /// Note: This function is deprecated in favor of
+ /// [`Statement::query_and_then`], which can now take named parameters
+ /// directly.
+ ///
/// If the query returns more than one row, all rows except the first are
/// ignored.
///
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
- /// query truly is optional, you can call `.optional()` on the result of
- /// this to get a `Result<Option<T>>`.
+ /// query truly is optional, you can call
+ /// [`.optional()`](crate::OptionalExtension::optional) on the result of
+ /// this to get a `Result<Option<T>>` (requires that the trait
+ /// `rusqlite::OptionalExtension` is imported).
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
+ #[deprecated = "You can use `query_row` with named params now."]
pub fn query_row_named<T, F>(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
where
F: FnOnce(&Row<'_>) -> Result<T>,
{
- let mut rows = self.query_named(params)?;
-
- rows.get_expected_row().and_then(|r| f(&r))
+ self.query_row(params, f)
}
/// Consumes the statement.
@@ -412,11 +492,12 @@ impl Statement<'_> {
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
+ #[inline]
pub fn finalize(mut self) -> Result<()> {
self.finalize_()
}
- /// Return the (one-based) index of an SQL parameter given its name.
+ /// Return the (one-based) index of an SQL parameter given its name.
///
/// Note that the initial ":" or "$" or "@" or "?" used to specify the
/// parameter is included as part of the name.
@@ -435,11 +516,37 @@ impl Statement<'_> {
///
/// Will return Err if `name` is invalid. Will return Ok(None) if the name
/// is valid but not a bound parameter of this statement.
+ #[inline]
pub fn parameter_index(&self, name: &str) -> Result<Option<usize>> {
Ok(self.stmt.bind_parameter_index(name))
}
- fn bind_parameters<P>(&mut self, params: P) -> Result<()>
+ /// Return the SQL parameter name given its (one-based) index (the inverse
+ /// of [`Statement::parameter_index`]).
+ ///
+ /// ```rust,no_run
+ /// # use rusqlite::{Connection, Result};
+ /// fn example(conn: &Connection) -> Result<()> {
+ /// let stmt = conn.prepare("SELECT * FROM test WHERE name = :example")?;
+ /// let index = stmt.parameter_name(1);
+ /// assert_eq!(index, Some(":example"));
+ /// Ok(())
+ /// }
+ /// ```
+ ///
+ /// # Failure
+ ///
+ /// Will return `None` if the column index is out of bounds or if the
+ /// parameter is positional.
+ #[inline]
+ pub fn parameter_name(&self, index: usize) -> Option<&'_ str> {
+ self.stmt.bind_parameter_name(index as i32).map(|name| {
+ str::from_utf8(name.to_bytes()).expect("Invalid UTF-8 sequence in parameter name")
+ })
+ }
+
+ #[inline]
+ pub(crate) fn bind_parameters<P>(&mut self, params: P) -> Result<()>
where
P: IntoIterator,
P::Item: ToSql,
@@ -460,10 +567,15 @@ impl Statement<'_> {
}
}
- fn bind_parameters_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<()> {
+ #[inline]
+ pub(crate) fn bind_parameters_named<T: ?Sized + ToSql>(
+ &mut self,
+ params: &[(&str, &T)],
+ ) -> Result<()> {
for &(name, value) in params {
if let Some(i) = self.parameter_index(name)? {
- self.bind_parameter(value, i)?;
+ let ts: &dyn ToSql = &value;
+ self.bind_parameter(ts, i)?;
} else {
return Err(Error::InvalidParameterName(name.into()));
}
@@ -472,13 +584,14 @@ impl Statement<'_> {
}
/// Return the number of parameters that can be bound to this statement.
+ #[inline]
pub fn parameter_count(&self) -> usize {
self.stmt.bind_parameter_count()
}
/// Low level API to directly bind a parameter to a given index.
///
- /// Note that the index is one-based, that is, the first parameter index is
+ /// Note that the index is one-based, that is, the first parameter index is
/// 1 and not 0. This is consistent with the SQLite API and the values given
/// to parameters bound as `?NNN`.
///
@@ -514,6 +627,7 @@ impl Statement<'_> {
/// Ok(())
/// }
/// ```
+ #[inline]
pub fn raw_bind_parameter<T: ToSql>(
&mut self,
one_based_col_index: usize,
@@ -538,6 +652,7 @@ impl Statement<'_> {
///
/// Will return `Err` if the executed statement returns rows (in which case
/// `query` should be used instead), or the underlying SQLite call fails.
+ #[inline]
pub fn raw_execute(&mut self) -> Result<usize> {
self.execute_with_bound_parameters()
}
@@ -554,11 +669,13 @@ impl Statement<'_> {
///
/// Note that if the SQL does not return results, [`Statement::raw_execute`]
/// should be used instead.
+ #[inline]
pub fn raw_query(&mut self) -> Rows<'_> {
Rows::new(self)
}
- fn bind_parameter(&self, param: &dyn ToSql, col: usize) -> Result<()> {
+ // generic because many of these branches can constant fold away.
+ fn bind_parameter<P: ?Sized + ToSql>(&self, param: &P, col: usize) -> Result<()> {
let value = param.to_sql()?;
let ptr = unsafe { self.stmt.ptr() };
@@ -610,6 +727,7 @@ impl Statement<'_> {
})
}
+ #[inline]
fn execute_with_bound_parameters(&mut self) -> Result<usize> {
self.check_update()?;
let r = self.stmt.step();
@@ -621,27 +739,13 @@ impl Statement<'_> {
}
}
+ #[inline]
fn finalize_(&mut self) -> Result<()> {
let mut stmt = unsafe { RawStatement::new(ptr::null_mut(), 0) };
mem::swap(&mut stmt, &mut self.stmt);
self.conn.decode_result(stmt.finalize())
}
- #[cfg(not(feature = "modern_sqlite"))]
- #[inline]
- fn check_readonly(&self) -> Result<()> {
- Ok(())
- }
-
- #[cfg(feature = "modern_sqlite")]
- #[inline]
- fn check_readonly(&self) -> Result<()> {
- /*if !self.stmt.readonly() { does not work for PRAGMA
- return Err(Error::InvalidQuery);
- }*/
- Ok(())
- }
-
#[cfg(all(feature = "modern_sqlite", feature = "extra_check"))]
#[inline]
fn check_update(&self) -> Result<()> {
@@ -664,6 +768,7 @@ impl Statement<'_> {
#[cfg(not(feature = "extra_check"))]
#[inline]
+ #[allow(clippy::unnecessary_wraps)]
fn check_update(&self) -> Result<()> {
Ok(())
}
@@ -678,17 +783,20 @@ impl Statement<'_> {
}
/// Get the value for one of the status counters for this statement.
+ #[inline]
pub fn get_status(&self, status: StatementStatus) -> i32 {
self.stmt.get_status(status, false)
}
/// Reset the value of one of the status counters for this statement,
+ #[inline]
/// returning the value it had before resetting.
pub fn reset_status(&self, status: StatementStatus) -> i32 {
self.stmt.get_status(status, true)
}
#[cfg(feature = "extra_check")]
+ #[inline]
pub(crate) fn check_no_tail(&self) -> Result<()> {
if self.stmt.has_tail() {
Err(Error::MultipleStatement)
@@ -699,6 +807,7 @@ impl Statement<'_> {
#[cfg(not(feature = "extra_check"))]
#[inline]
+ #[allow(clippy::unnecessary_wraps)]
pub(crate) fn check_no_tail(&self) -> Result<()> {
Ok(())
}
@@ -706,6 +815,7 @@ impl Statement<'_> {
/// Safety: This is unsafe, because using `sqlite3_stmt` after the
/// connection has closed is illegal, but `RawStatement` does not enforce
/// this, as it loses our protective `'conn` lifetime bound.
+ #[inline]
pub(crate) unsafe fn into_raw(mut self) -> RawStatement {
let mut stmt = RawStatement::new(ptr::null_mut(), 0);
mem::swap(&mut stmt, &mut self.stmt);
@@ -730,12 +840,14 @@ impl fmt::Debug for Statement<'_> {
impl Drop for Statement<'_> {
#[allow(unused_must_use)]
+ #[inline]
fn drop(&mut self) {
self.finalize_();
}
}
impl Statement<'_> {
+ #[inline]
pub(super) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> {
Statement { conn, stmt }
}
@@ -796,6 +908,7 @@ impl Statement<'_> {
}
}
+ #[inline]
pub(super) fn step(&self) -> Result<bool> {
match self.stmt.step() {
ffi::SQLITE_ROW => Ok(true),
@@ -804,6 +917,7 @@ impl Statement<'_> {
}
}
+ #[inline]
pub(super) fn reset(&self) -> c_int {
self.stmt.reset()
}
@@ -811,7 +925,7 @@ impl Statement<'_> {
/// Prepared statement status counters.
///
-/// See https://www.sqlite.org/c3ref/c_stmtstatus_counter.html
+/// See `https://www.sqlite.org/c3ref/c_stmtstatus_counter.html`
/// for explanations of each.
///
/// Note that depending on your version of SQLite, all of these
@@ -839,124 +953,190 @@ pub enum StatementStatus {
#[cfg(test)]
mod test {
use crate::types::ToSql;
- use crate::{Connection, Error, Result, NO_PARAMS};
+ use crate::{params_from_iter, Connection, Error, Result};
#[test]
- fn test_execute_named() {
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
+ #[allow(deprecated)]
+ fn test_execute_named() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
assert_eq!(
- db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])
- .unwrap(),
+ db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])?,
1
);
assert_eq!(
- db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)])
- .unwrap(),
+ db.execute("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)])?,
+ 1
+ );
+ assert_eq!(
+ db.execute(
+ "INSERT INTO foo(x) VALUES (:x)",
+ crate::named_params! {":x": 3i32}
+ )?,
1
);
assert_eq!(
- 3i32,
+ 6i32,
db.query_row_named::<i32, _>(
"SELECT SUM(x) FROM foo WHERE x > :x",
&[(":x", &0i32)],
|r| r.get(0)
- )
- .unwrap()
+ )?
);
+ assert_eq!(
+ 5i32,
+ db.query_row::<i32, _, _>(
+ "SELECT SUM(x) FROM foo WHERE x > :x",
+ &[(":x", &1i32)],
+ |r| r.get(0)
+ )?
+ );
+ Ok(())
}
#[test]
- fn test_stmt_execute_named() {
- let db = Connection::open_in_memory().unwrap();
+ #[allow(deprecated)]
+ fn test_stmt_execute_named() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
INTEGER)";
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
- let mut stmt = db
- .prepare("INSERT INTO test (name) VALUES (:name)")
- .unwrap();
- stmt.execute_named(&[(":name", &"one")]).unwrap();
+ let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)")?;
+ stmt.execute_named(&[(":name", &"one")])?;
- let mut stmt = db
- .prepare("SELECT COUNT(*) FROM test WHERE name = :name")
- .unwrap();
+ let mut stmt = db.prepare("SELECT COUNT(*) FROM test WHERE name = :name")?;
assert_eq!(
1i32,
- stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))
- .unwrap()
+ stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))?
);
+ assert_eq!(
+ 1i32,
+ stmt.query_row::<i32, _, _>(&[(":name", "one")], |r| r.get(0))?
+ );
+ Ok(())
}
#[test]
- fn test_query_named() {
- let db = Connection::open_in_memory().unwrap();
+ #[allow(deprecated)]
+ fn test_query_named() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = r#"
CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
INSERT INTO test(id, name) VALUES (1, "one");
"#;
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
- let mut stmt = db
- .prepare("SELECT id FROM test where name = :name")
- .unwrap();
- let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap();
+ let mut stmt = db.prepare("SELECT id FROM test where name = :name")?;
+ // legacy `_named` api
+ {
+ let mut rows = stmt.query_named(&[(":name", &"one")])?;
+ let id: Result<i32> = rows.next()?.unwrap().get(0);
+ assert_eq!(Ok(1), id);
+ }
- let id: Result<i32> = rows.next().unwrap().unwrap().get(0);
- assert_eq!(Ok(1), id);
+ // plain api
+ {
+ let mut rows = stmt.query(&[(":name", "one")])?;
+ let id: Result<i32> = rows.next()?.unwrap().get(0);
+ assert_eq!(Ok(1), id);
+ }
+ Ok(())
}
#[test]
- fn test_query_map_named() {
- let db = Connection::open_in_memory().unwrap();
+ #[allow(deprecated)]
+ fn test_query_map_named() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = r#"
CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
INSERT INTO test(id, name) VALUES (1, "one");
"#;
- db.execute_batch(sql).unwrap();
+ db.execute_batch(sql)?;
+
+ let mut stmt = db.prepare("SELECT id FROM test where name = :name")?;
+ // legacy `_named` api
+ {
+ let mut rows = stmt.query_map_named(&[(":name", &"one")], |row| {
+ let id: Result<i32> = row.get(0);
+ id.map(|i| 2 * i)
+ })?;
- let mut stmt = db
- .prepare("SELECT id FROM test where name = :name")
- .unwrap();
- let mut rows = stmt
- .query_map_named(&[(":name", &"one")], |row| {
+ let doubled_id: i32 = rows.next().unwrap()?;
+ assert_eq!(2, doubled_id);
+ }
+ // plain api
+ {
+ let mut rows = stmt.query_map(&[(":name", "one")], |row| {
let id: Result<i32> = row.get(0);
id.map(|i| 2 * i)
- })
- .unwrap();
+ })?;
- let doubled_id: i32 = rows.next().unwrap().unwrap();
- assert_eq!(2, doubled_id);
+ let doubled_id: i32 = rows.next().unwrap()?;
+ assert_eq!(2, doubled_id);
+ }
+ Ok(())
}
#[test]
- fn test_query_and_then_named() {
- let db = Connection::open_in_memory().unwrap();
+ #[allow(deprecated)]
+ fn test_query_and_then_named() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = r#"
CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
INSERT INTO test(id, name) VALUES (1, "one");
INSERT INTO test(id, name) VALUES (2, "one");
"#;
- db.execute_batch(sql).unwrap();
-
- let mut stmt = db
- .prepare("SELECT id FROM test where name = :name ORDER BY id ASC")
- .unwrap();
- let mut rows = stmt
- .query_and_then_named(&[(":name", &"one")], |row| {
- let id: i32 = row.get(0)?;
- if id == 1 {
- Ok(id)
- } else {
- Err(Error::SqliteSingleThreadedMode)
- }
- })
- .unwrap();
+ db.execute_batch(sql)?;
+
+ let mut stmt = db.prepare("SELECT id FROM test where name = :name ORDER BY id ASC")?;
+ let mut rows = stmt.query_and_then_named(&[(":name", &"one")], |row| {
+ let id: i32 = row.get(0)?;
+ if id == 1 {
+ Ok(id)
+ } else {
+ Err(Error::SqliteSingleThreadedMode)
+ }
+ })?;
+
+ // first row should be Ok
+ let doubled_id: i32 = rows.next().unwrap()?;
+ assert_eq!(1, doubled_id);
+
+ // second row should be Err
+ #[allow(clippy::match_wild_err_arm)]
+ match rows.next().unwrap() {
+ Ok(_) => panic!("invalid Ok"),
+ Err(Error::SqliteSingleThreadedMode) => (),
+ Err(_) => panic!("invalid Err"),
+ }
+ Ok(())
+ }
+
+ #[test]
+ fn test_query_and_then_by_name() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ let sql = r#"
+ CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
+ INSERT INTO test(id, name) VALUES (1, "one");
+ INSERT INTO test(id, name) VALUES (2, "one");
+ "#;
+ db.execute_batch(sql)?;
+
+ let mut stmt = db.prepare("SELECT id FROM test where name = :name ORDER BY id ASC")?;
+ let mut rows = stmt.query_and_then(&[(":name", "one")], |row| {
+ let id: i32 = row.get(0)?;
+ if id == 1 {
+ Ok(id)
+ } else {
+ Err(Error::SqliteSingleThreadedMode)
+ }
+ })?;
// first row should be Ok
- let doubled_id: i32 = rows.next().unwrap().unwrap();
+ let doubled_id: i32 = rows.next().unwrap()?;
assert_eq!(1, doubled_id);
// second row should be Err
@@ -966,30 +1146,28 @@ mod test {
Err(Error::SqliteSingleThreadedMode) => (),
Err(_) => panic!("invalid Err"),
}
+ Ok(())
}
#[test]
- fn test_unbound_parameters_are_null() {
- let db = Connection::open_in_memory().unwrap();
+ #[allow(deprecated)]
+ fn test_unbound_parameters_are_null() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "CREATE TABLE test (x TEXT, y TEXT)";
- db.execute_batch(sql).unwrap();
-
- let mut stmt = db
- .prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
- .unwrap();
- stmt.execute_named(&[(":x", &"one")]).unwrap();
-
- let result: Option<String> = db
- .query_row("SELECT y FROM test WHERE x = 'one'", NO_PARAMS, |row| {
- row.get(0)
- })
- .unwrap();
+ db.execute_batch(sql)?;
+
+ let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")?;
+ stmt.execute_named(&[(":x", &"one")])?;
+
+ let result: Option<String> =
+ db.query_row("SELECT y FROM test WHERE x = 'one'", [], |row| row.get(0))?;
assert!(result.is_none());
+ Ok(())
}
#[test]
fn test_raw_binding() -> Result<()> {
- let db = Connection::open_in_memory().unwrap();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE test (name TEXT, value INTEGER)")?;
{
let mut stmt = db.prepare("INSERT INTO test (name, value) VALUES (:name, ?3)")?;
@@ -1019,232 +1197,233 @@ mod test {
}
#[test]
- fn test_unbound_parameters_are_reused() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_unbound_parameters_are_reused() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "CREATE TABLE test (x TEXT, y TEXT)";
- db.execute_batch(sql).unwrap();
-
- let mut stmt = db
- .prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
- .unwrap();
- stmt.execute_named(&[(":x", &"one")]).unwrap();
- stmt.execute_named(&[(":y", &"two")]).unwrap();
-
- let result: String = db
- .query_row("SELECT x FROM test WHERE y = 'two'", NO_PARAMS, |row| {
- row.get(0)
- })
- .unwrap();
+ db.execute_batch(sql)?;
+
+ let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")?;
+ stmt.execute(&[(":x", "one")])?;
+ stmt.execute(&[(":y", "two")])?;
+
+ let result: String =
+ db.query_row("SELECT x FROM test WHERE y = 'two'", [], |row| row.get(0))?;
assert_eq!(result, "one");
+ Ok(())
}
#[test]
- fn test_insert() {
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)")
- .unwrap();
- let mut stmt = db
- .prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)")
- .unwrap();
- assert_eq!(stmt.insert(&[1i32]).unwrap(), 1);
- assert_eq!(stmt.insert(&[2i32]).unwrap(), 2);
- match stmt.insert(&[1i32]).unwrap_err() {
+ fn test_insert() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)")?;
+ let mut stmt = db.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)")?;
+ assert_eq!(stmt.insert([1i32])?, 1);
+ assert_eq!(stmt.insert([2i32])?, 2);
+ match stmt.insert([1i32]).unwrap_err() {
Error::StatementChangedRows(0) => (),
err => panic!("Unexpected error {}", err),
}
- let mut multi = db
- .prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4")
- .unwrap();
- match multi.insert(NO_PARAMS).unwrap_err() {
+ let mut multi = db.prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4")?;
+ match multi.insert([]).unwrap_err() {
Error::StatementChangedRows(2) => (),
err => panic!("Unexpected error {}", err),
}
+ Ok(())
}
#[test]
- fn test_insert_different_tables() {
+ fn test_insert_different_tables() -> Result<()> {
// Test for https://github.com/rusqlite/rusqlite/issues/171
- let db = Connection::open_in_memory().unwrap();
+ let db = Connection::open_in_memory()?;
db.execute_batch(
r"
CREATE TABLE foo(x INTEGER);
CREATE TABLE bar(x INTEGER);
",
- )
- .unwrap();
+ )?;
- assert_eq!(
- db.prepare("INSERT INTO foo VALUES (10)")
- .unwrap()
- .insert(NO_PARAMS)
- .unwrap(),
- 1
- );
- assert_eq!(
- db.prepare("INSERT INTO bar VALUES (10)")
- .unwrap()
- .insert(NO_PARAMS)
- .unwrap(),
- 1
- );
+ assert_eq!(db.prepare("INSERT INTO foo VALUES (10)")?.insert([])?, 1);
+ assert_eq!(db.prepare("INSERT INTO bar VALUES (10)")?.insert([])?, 1);
+ Ok(())
}
#[test]
- fn test_exists() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_exists() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(1);
INSERT INTO foo VALUES(2);
END;";
- db.execute_batch(sql).unwrap();
- let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?").unwrap();
- assert!(stmt.exists(&[1i32]).unwrap());
- assert!(stmt.exists(&[2i32]).unwrap());
- assert!(!stmt.exists(&[0i32]).unwrap());
+ db.execute_batch(sql)?;
+ let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?")?;
+ assert!(stmt.exists([1i32])?);
+ assert!(stmt.exists([2i32])?);
+ assert!(!stmt.exists([0i32])?);
+ Ok(())
}
#[test]
- fn test_query_row() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_query_row() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y INTEGER);
INSERT INTO foo VALUES(1, 3);
INSERT INTO foo VALUES(2, 4);
END;";
- db.execute_batch(sql).unwrap();
- let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?").unwrap();
- let y: Result<i64> = stmt.query_row(&[1i32], |r| r.get(0));
- assert_eq!(3i64, y.unwrap());
+ db.execute_batch(sql)?;
+ let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?")?;
+ let y: Result<i64> = stmt.query_row([1i32], |r| r.get(0));
+ assert_eq!(3i64, y?);
+ Ok(())
}
#[test]
- fn test_query_by_column_name() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_query_by_column_name() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y INTEGER);
INSERT INTO foo VALUES(1, 3);
END;";
- db.execute_batch(sql).unwrap();
- let mut stmt = db.prepare("SELECT y FROM foo").unwrap();
- let y: Result<i64> = stmt.query_row(NO_PARAMS, |r| r.get("y"));
- assert_eq!(3i64, y.unwrap());
+ db.execute_batch(sql)?;
+ let mut stmt = db.prepare("SELECT y FROM foo")?;
+ let y: Result<i64> = stmt.query_row([], |r| r.get("y"));
+ assert_eq!(3i64, y?);
+ Ok(())
}
#[test]
- fn test_query_by_column_name_ignore_case() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_query_by_column_name_ignore_case() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y INTEGER);
INSERT INTO foo VALUES(1, 3);
END;";
- db.execute_batch(sql).unwrap();
- let mut stmt = db.prepare("SELECT y as Y FROM foo").unwrap();
- let y: Result<i64> = stmt.query_row(NO_PARAMS, |r| r.get("y"));
- assert_eq!(3i64, y.unwrap());
+ db.execute_batch(sql)?;
+ let mut stmt = db.prepare("SELECT y as Y FROM foo")?;
+ let y: Result<i64> = stmt.query_row([], |r| r.get("y"));
+ assert_eq!(3i64, y?);
+ Ok(())
}
#[test]
#[cfg(feature = "modern_sqlite")]
- fn test_expanded_sql() {
- let db = Connection::open_in_memory().unwrap();
- let stmt = db.prepare("SELECT ?").unwrap();
- stmt.bind_parameter(&1, 1).unwrap();
+ fn test_expanded_sql() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ let stmt = db.prepare("SELECT ?")?;
+ stmt.bind_parameter(&1, 1)?;
assert_eq!(Some("SELECT 1".to_owned()), stmt.expanded_sql());
+ Ok(())
}
#[test]
- fn test_bind_parameters() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_bind_parameters() -> Result<()> {
+ let db = Connection::open_in_memory()?;
// dynamic slice:
db.query_row(
"SELECT ?1, ?2, ?3",
&[&1u8 as &dyn ToSql, &"one", &Some("one")],
|row| row.get::<_, u8>(0),
- )
- .unwrap();
+ )?;
// existing collection:
let data = vec![1, 2, 3];
- db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, u8>(0))
- .unwrap();
- db.query_row("SELECT ?1, ?2, ?3", data.as_slice(), |row| {
+ db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| {
row.get::<_, u8>(0)
- })
- .unwrap();
- db.query_row("SELECT ?1, ?2, ?3", data, |row| row.get::<_, u8>(0))
- .unwrap();
+ })?;
+ db.query_row(
+ "SELECT ?1, ?2, ?3",
+ params_from_iter(data.as_slice()),
+ |row| row.get::<_, u8>(0),
+ )?;
+ db.query_row("SELECT ?1, ?2, ?3", params_from_iter(data), |row| {
+ row.get::<_, u8>(0)
+ })?;
use std::collections::BTreeSet;
let data: BTreeSet<String> = ["one", "two", "three"]
.iter()
.map(|s| (*s).to_string())
.collect();
- db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, String>(0))
- .unwrap();
+ db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| {
+ row.get::<_, String>(0)
+ })?;
let data = [0; 3];
- db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, u8>(0))
- .unwrap();
- db.query_row("SELECT ?1, ?2, ?3", data.iter(), |row| row.get::<_, u8>(0))
- .unwrap();
+ db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| {
+ row.get::<_, u8>(0)
+ })?;
+ db.query_row("SELECT ?1, ?2, ?3", params_from_iter(data.iter()), |row| {
+ row.get::<_, u8>(0)
+ })?;
+ Ok(())
}
#[test]
- fn test_empty_stmt() {
- let conn = Connection::open_in_memory().unwrap();
- let mut stmt = conn.prepare("").unwrap();
+ fn test_parameter_name() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE test (name TEXT, value INTEGER)")?;
+ let stmt = db.prepare("INSERT INTO test (name, value) VALUES (:name, ?3)")?;
+ assert_eq!(stmt.parameter_name(0), None);
+ assert_eq!(stmt.parameter_name(1), Some(":name"));
+ assert_eq!(stmt.parameter_name(2), None);
+ Ok(())
+ }
+
+ #[test]
+ fn test_empty_stmt() -> Result<()> {
+ let conn = Connection::open_in_memory()?;
+ let mut stmt = conn.prepare("")?;
assert_eq!(0, stmt.column_count());
assert!(stmt.parameter_index("test").is_ok());
assert!(stmt.step().is_err());
stmt.reset();
- assert!(stmt.execute(NO_PARAMS).is_err());
+ assert!(stmt.execute([]).is_err());
+ Ok(())
}
#[test]
- fn test_comment_stmt() {
- let conn = Connection::open_in_memory().unwrap();
- conn.prepare("/*SELECT 1;*/").unwrap();
+ fn test_comment_stmt() -> Result<()> {
+ let conn = Connection::open_in_memory()?;
+ conn.prepare("/*SELECT 1;*/")?;
+ Ok(())
}
#[test]
- fn test_comment_and_sql_stmt() {
- let conn = Connection::open_in_memory().unwrap();
- let stmt = conn.prepare("/*...*/ SELECT 1;").unwrap();
+ fn test_comment_and_sql_stmt() -> Result<()> {
+ let conn = Connection::open_in_memory()?;
+ let stmt = conn.prepare("/*...*/ SELECT 1;")?;
assert_eq!(1, stmt.column_count());
+ Ok(())
}
#[test]
- fn test_semi_colon_stmt() {
- let conn = Connection::open_in_memory().unwrap();
- let stmt = conn.prepare(";").unwrap();
+ fn test_semi_colon_stmt() -> Result<()> {
+ let conn = Connection::open_in_memory()?;
+ let stmt = conn.prepare(";")?;
assert_eq!(0, stmt.column_count());
+ Ok(())
}
#[test]
- fn test_utf16_conversion() {
- let db = Connection::open_in_memory().unwrap();
- db.pragma_update(None, "encoding", &"UTF-16le").unwrap();
- let encoding: String = db
- .pragma_query_value(None, "encoding", |row| row.get(0))
- .unwrap();
+ fn test_utf16_conversion() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.pragma_update(None, "encoding", &"UTF-16le")?;
+ let encoding: String = db.pragma_query_value(None, "encoding", |row| row.get(0))?;
assert_eq!("UTF-16le", encoding);
- db.execute_batch("CREATE TABLE foo(x TEXT)").unwrap();
+ db.execute_batch("CREATE TABLE foo(x TEXT)")?;
let expected = "テスト";
- db.execute("INSERT INTO foo(x) VALUES (?)", &[&expected])
- .unwrap();
- let actual: String = db
- .query_row("SELECT x FROM foo", NO_PARAMS, |row| row.get(0))
- .unwrap();
+ db.execute("INSERT INTO foo(x) VALUES (?)", &[&expected])?;
+ let actual: String = db.query_row("SELECT x FROM foo", [], |row| row.get(0))?;
assert_eq!(expected, actual);
+ Ok(())
}
#[test]
- fn test_nul_byte() {
- let db = Connection::open_in_memory().unwrap();
+ fn test_nul_byte() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let expected = "a\x00b";
- let actual: String = db
- .query_row("SELECT ?", &[&expected], |row| row.get(0))
- .unwrap();
+ let actual: String = db.query_row("SELECT ?", [expected], |row| row.get(0))?;
assert_eq!(expected, actual);
+ Ok(())
}
}
diff --git a/src/trace.rs b/src/trace.rs
index 76e0969..8e8fd3a 100644
--- a/src/trace.rs
+++ b/src/trace.rs
@@ -55,6 +55,7 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
/// `feature = "trace"` Write a message into the error log established by
/// `config_log`.
+#[inline]
pub fn log(err_code: c_int, msg: &str) {
let msg = CString::new(msg).expect("SQLite log messages cannot contain embedded zeroes");
unsafe {
@@ -127,10 +128,10 @@ mod test {
use std::sync::Mutex;
use std::time::Duration;
- use crate::Connection;
+ use crate::{Connection, Result};
#[test]
- fn test_trace() {
+ fn test_trace() -> Result<()> {
lazy_static! {
static ref TRACED_STMTS: Mutex<Vec<String>> = Mutex::new(Vec::new());
}
@@ -139,26 +140,27 @@ mod test {
traced_stmts.push(s.to_owned());
}
- let mut db = Connection::open_in_memory().unwrap();
+ let mut db = Connection::open_in_memory()?;
db.trace(Some(tracer));
{
- let _ = db.query_row("SELECT ?", &[1i32], |_| Ok(()));
- let _ = db.query_row("SELECT ?", &["hello"], |_| Ok(()));
+ let _ = db.query_row("SELECT ?", [1i32], |_| Ok(()));
+ let _ = db.query_row("SELECT ?", ["hello"], |_| Ok(()));
}
db.trace(None);
{
- let _ = db.query_row("SELECT ?", &[2i32], |_| Ok(()));
- let _ = db.query_row("SELECT ?", &["goodbye"], |_| Ok(()));
+ let _ = db.query_row("SELECT ?", [2i32], |_| Ok(()));
+ let _ = db.query_row("SELECT ?", ["goodbye"], |_| Ok(()));
}
let traced_stmts = TRACED_STMTS.lock().unwrap();
assert_eq!(traced_stmts.len(), 2);
assert_eq!(traced_stmts[0], "SELECT 1");
assert_eq!(traced_stmts[1], "SELECT 'hello'");
+ Ok(())
}
#[test]
- fn test_profile() {
+ fn test_profile() -> Result<()> {
lazy_static! {
static ref PROFILED: Mutex<Vec<(String, Duration)>> = Mutex::new(Vec::new());
}
@@ -167,14 +169,15 @@ mod test {
profiled.push((s.to_owned(), d));
}
- let mut db = Connection::open_in_memory().unwrap();
+ let mut db = Connection::open_in_memory()?;
db.profile(Some(profiler));
- db.execute_batch("PRAGMA application_id = 1").unwrap();
+ db.execute_batch("PRAGMA application_id = 1")?;
db.profile(None);
- db.execute_batch("PRAGMA application_id = 2").unwrap();
+ db.execute_batch("PRAGMA application_id = 2")?;
let profiled = PROFILED.lock().unwrap();
assert_eq!(profiled.len(), 1);
assert_eq!(profiled[0].0, "PRAGMA application_id = 1");
+ Ok(())
}
}
diff --git a/src/transaction.rs b/src/transaction.rs
index 5e649b7..a1b287d 100644
--- a/src/transaction.rs
+++ b/src/transaction.rs
@@ -102,6 +102,7 @@ impl Transaction<'_> {
/// Even though we don't mutate the connection, we take a `&mut Connection`
/// so as to prevent nested transactions on the same connection. For cases
/// where this is unacceptable, [`Transaction::new_unchecked`] is available.
+ #[inline]
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> {
Self::new_unchecked(conn, behavior)
}
@@ -111,6 +112,7 @@ impl Transaction<'_> {
/// If a transaction is already open, this will return an error. Where
/// possible, [`Transaction::new`] should be preferred, as it provides a
/// compile-time guarantee that transactions are not nested.
+ #[inline]
pub fn new_unchecked(
conn: &Connection,
behavior: TransactionBehavior,
@@ -153,42 +155,50 @@ impl Transaction<'_> {
/// tx.commit()
/// }
/// ```
+ #[inline]
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
Savepoint::with_depth(self.conn, 1)
}
/// Create a new savepoint with a custom savepoint name. See `savepoint()`.
+ #[inline]
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.
+ #[inline]
pub fn drop_behavior(&self) -> DropBehavior {
self.drop_behavior
}
/// Configure the transaction to perform the specified action when it is
/// dropped.
+ #[inline]
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
self.drop_behavior = drop_behavior
}
/// A convenience method which consumes and commits a transaction.
+ #[inline]
pub fn commit(mut self) -> Result<()> {
self.commit_()
}
+ #[inline]
fn commit_(&mut self) -> Result<()> {
self.conn.execute_batch("COMMIT")?;
Ok(())
}
/// A convenience method which consumes and rolls back a transaction.
+ #[inline]
pub fn rollback(mut self) -> Result<()> {
self.rollback_()
}
+ #[inline]
fn rollback_(&mut self) -> Result<()> {
self.conn.execute_batch("ROLLBACK")?;
Ok(())
@@ -199,10 +209,12 @@ impl Transaction<'_> {
///
/// Functionally equivalent to the `Drop` implementation, but allows
/// callers to see any errors that occur.
+ #[inline]
pub fn finish(mut self) -> Result<()> {
self.finish_()
}
+ #[inline]
fn finish_(&mut self) -> Result<()> {
if self.conn.is_autocommit() {
return Ok(());
@@ -219,6 +231,7 @@ impl Transaction<'_> {
impl Deref for Transaction<'_> {
type Target = Connection;
+ #[inline]
fn deref(&self) -> &Connection {
self.conn
}
@@ -226,12 +239,14 @@ impl Deref for Transaction<'_> {
#[allow(unused_must_use)]
impl Drop for Transaction<'_> {
+ #[inline]
fn drop(&mut self) {
self.finish_();
}
}
impl Savepoint<'_> {
+ #[inline]
fn with_depth_and_name<T: Into<String>>(
conn: &Connection,
depth: u32,
@@ -248,48 +263,57 @@ impl Savepoint<'_> {
})
}
+ #[inline]
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.
+ #[inline]
pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
Savepoint::with_depth(conn, 0)
}
/// Begin a new savepoint with a user-provided savepoint name.
+ #[inline]
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.
+ #[inline]
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.
+ #[inline]
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.
+ #[inline]
pub fn drop_behavior(&self) -> DropBehavior {
self.drop_behavior
}
/// Configure the savepoint to perform the specified action when it is
/// dropped.
+ #[inline]
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
self.drop_behavior = drop_behavior
}
/// A convenience method which consumes and commits a savepoint.
+ #[inline]
pub fn commit(mut self) -> Result<()> {
self.commit_()
}
+ #[inline]
fn commit_(&mut self) -> Result<()> {
self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
self.committed = true;
@@ -302,6 +326,7 @@ impl Savepoint<'_> {
///
/// Unlike `Transaction`s, savepoints remain active after they have been
/// rolled back, and can be rolled back again or committed.
+ #[inline]
pub fn rollback(&mut self) -> Result<()> {
self.conn
.execute_batch(&format!("ROLLBACK TO {}", self.name))
@@ -312,10 +337,12 @@ impl Savepoint<'_> {
///
/// Functionally equivalent to the `Drop` implementation, but allows
/// callers to see any errors that occur.
+ #[inline]
pub fn finish(mut self) -> Result<()> {
self.finish_()
}
+ #[inline]
fn finish_(&mut self) -> Result<()> {
if self.committed {
return Ok(());
@@ -332,6 +359,7 @@ impl Savepoint<'_> {
impl Deref for Savepoint<'_> {
type Target = Connection;
+ #[inline]
fn deref(&self) -> &Connection {
self.conn
}
@@ -339,6 +367,7 @@ impl Deref for Savepoint<'_> {
#[allow(unused_must_use)]
impl Drop for Savepoint<'_> {
+ #[inline]
fn drop(&mut self) {
self.finish_();
}
@@ -348,8 +377,9 @@ 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)`.
+ /// want the transaction to commit, you must call
+ /// [`commit`](Transaction::commit) or [`set_drop_behavior(DropBehavior:
+ /// :Commit)`](Transaction::set_drop_behavior).
///
/// ## Example
///
@@ -370,17 +400,19 @@ impl Connection {
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
+ #[inline]
pub fn transaction(&mut self) -> Result<Transaction<'_>> {
Transaction::new(self, TransactionBehavior::Deferred)
}
/// Begin a new transaction with a specified behavior.
///
- /// See `transaction`.
+ /// See [`transaction`](Connection::transaction).
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
+ #[inline]
pub fn transaction_with_behavior(
&mut self,
behavior: TransactionBehavior,
@@ -426,8 +458,9 @@ impl Connection {
/// 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 to commit, you must call [`commit`](Savepoint::commit) or
+ /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::
+ /// set_drop_behavior).
///
/// ## Example
///
@@ -448,17 +481,19 @@ impl Connection {
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
+ #[inline]
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
Savepoint::new(self)
}
/// Begin a new savepoint with a specified name.
///
- /// See `savepoint`.
+ /// See [`savepoint`](Connection::savepoint).
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
+ #[inline]
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_name(self, name)
}
@@ -467,35 +502,35 @@ impl Connection {
#[cfg(test)]
mod test {
use super::DropBehavior;
- use crate::{Connection, Error, NO_PARAMS};
+ use crate::{Connection, Error, Result};
- fn checked_memory_handle() -> Connection {
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo (x INTEGER)").unwrap();
- db
+ fn checked_memory_handle() -> Result<Connection> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (x INTEGER)")?;
+ Ok(db)
}
#[test]
- fn test_drop() {
- let mut db = checked_memory_handle();
+ fn test_drop() -> Result<()> {
+ let mut db = checked_memory_handle()?;
{
- let tx = db.transaction().unwrap();
- tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
+ let tx = db.transaction()?;
+ tx.execute_batch("INSERT INTO foo VALUES(1)")?;
// default: rollback
}
{
- let mut tx = db.transaction().unwrap();
- tx.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
+ let mut tx = db.transaction()?;
+ tx.execute_batch("INSERT INTO foo VALUES(2)")?;
tx.set_drop_behavior(DropBehavior::Commit)
}
{
- let tx = db.transaction().unwrap();
+ let tx = db.transaction()?;
assert_eq!(
2i32,
- tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap()
+ tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
}
+ Ok(())
}
fn assert_nested_tx_error(e: crate::Error) {
if let Error::SqliteFailure(e, Some(m)) = &e {
@@ -509,165 +544,168 @@ mod test {
}
#[test]
- fn test_unchecked_nesting() {
- let db = checked_memory_handle();
+ fn test_unchecked_nesting() -> Result<()> {
+ let db = checked_memory_handle()?;
{
- let tx = db.unchecked_transaction().unwrap();
+ let tx = db.unchecked_transaction()?;
let e = tx.unchecked_transaction().unwrap_err();
assert_nested_tx_error(e);
// default: rollback
}
{
- let tx = db.unchecked_transaction().unwrap();
- tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
+ let tx = db.unchecked_transaction()?;
+ tx.execute_batch("INSERT INTO foo VALUES(1)")?;
// Ensure this doesn't interfere with ongoing transaction
let e = tx.unchecked_transaction().unwrap_err();
assert_nested_tx_error(e);
- tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
- tx.commit().unwrap();
+ tx.execute_batch("INSERT INTO foo VALUES(1)")?;
+ tx.commit()?;
}
assert_eq!(
2i32,
- db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap()
+ db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
+ Ok(())
}
#[test]
- fn test_explicit_rollback_commit() {
- let mut db = checked_memory_handle();
+ fn test_explicit_rollback_commit() -> Result<()> {
+ let mut db = checked_memory_handle()?;
{
- let mut tx = db.transaction().unwrap();
+ let mut tx = db.transaction()?;
{
- let mut sp = tx.savepoint().unwrap();
- sp.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
- sp.rollback().unwrap();
- sp.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
- sp.commit().unwrap();
+ let mut sp = tx.savepoint()?;
+ sp.execute_batch("INSERT INTO foo VALUES(1)")?;
+ sp.rollback()?;
+ sp.execute_batch("INSERT INTO foo VALUES(2)")?;
+ sp.commit()?;
}
- tx.commit().unwrap();
+ tx.commit()?;
}
{
- let tx = db.transaction().unwrap();
- tx.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
- tx.commit().unwrap();
+ let tx = db.transaction()?;
+ tx.execute_batch("INSERT INTO foo VALUES(4)")?;
+ tx.commit()?;
}
{
- let tx = db.transaction().unwrap();
+ let tx = db.transaction()?;
assert_eq!(
6i32,
- tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap()
+ tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
}
+ Ok(())
}
#[test]
- fn test_savepoint() {
- let mut db = checked_memory_handle();
+ fn test_savepoint() -> Result<()> {
+ let mut db = checked_memory_handle()?;
{
- let mut tx = db.transaction().unwrap();
- tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
- assert_current_sum(1, &tx);
+ let mut tx = db.transaction()?;
+ tx.execute_batch("INSERT INTO foo VALUES(1)")?;
+ assert_current_sum(1, &tx)?;
tx.set_drop_behavior(DropBehavior::Commit);
{
- let mut sp1 = tx.savepoint().unwrap();
- sp1.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
- assert_current_sum(3, &sp1);
+ let mut sp1 = tx.savepoint()?;
+ sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
+ assert_current_sum(3, &sp1)?;
// will rollback sp1
{
- let mut sp2 = sp1.savepoint().unwrap();
- sp2.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
- assert_current_sum(7, &sp2);
+ let mut sp2 = sp1.savepoint()?;
+ sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
+ assert_current_sum(7, &sp2)?;
// will rollback sp2
{
- let sp3 = sp2.savepoint().unwrap();
- sp3.execute_batch("INSERT INTO foo VALUES(8)").unwrap();
- assert_current_sum(15, &sp3);
- sp3.commit().unwrap();
+ let sp3 = sp2.savepoint()?;
+ sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
+ assert_current_sum(15, &sp3)?;
+ sp3.commit()?;
// committed sp3, but will be erased by sp2 rollback
}
- assert_current_sum(15, &sp2);
+ assert_current_sum(15, &sp2)?;
}
- assert_current_sum(3, &sp1);
+ assert_current_sum(3, &sp1)?;
}
- assert_current_sum(1, &tx);
+ assert_current_sum(1, &tx)?;
}
- assert_current_sum(1, &db);
+ assert_current_sum(1, &db)?;
+ Ok(())
}
#[test]
- fn test_ignore_drop_behavior() {
- let mut db = checked_memory_handle();
+ fn test_ignore_drop_behavior() -> Result<()> {
+ let mut db = checked_memory_handle()?;
- let mut tx = db.transaction().unwrap();
+ let mut tx = db.transaction()?;
{
- let mut sp1 = tx.savepoint().unwrap();
- insert(1, &sp1);
- sp1.rollback().unwrap();
- insert(2, &sp1);
+ let mut sp1 = tx.savepoint()?;
+ insert(1, &sp1)?;
+ sp1.rollback()?;
+ insert(2, &sp1)?;
{
- let mut sp2 = sp1.savepoint().unwrap();
+ let mut sp2 = sp1.savepoint()?;
sp2.set_drop_behavior(DropBehavior::Ignore);
- insert(4, &sp2);
+ insert(4, &sp2)?;
}
- assert_current_sum(6, &sp1);
- sp1.commit().unwrap();
+ assert_current_sum(6, &sp1)?;
+ sp1.commit()?;
}
- assert_current_sum(6, &tx);
+ assert_current_sum(6, &tx)?;
+ Ok(())
}
#[test]
- fn test_savepoint_names() {
- let mut db = checked_memory_handle();
+ fn test_savepoint_names() -> Result<()> {
+ let mut db = checked_memory_handle()?;
{
- let mut sp1 = db.savepoint_with_name("my_sp").unwrap();
- insert(1, &sp1);
- assert_current_sum(1, &sp1);
+ let mut sp1 = db.savepoint_with_name("my_sp")?;
+ insert(1, &sp1)?;
+ assert_current_sum(1, &sp1)?;
{
- let mut sp2 = sp1.savepoint_with_name("my_sp").unwrap();
+ let mut sp2 = sp1.savepoint_with_name("my_sp")?;
sp2.set_drop_behavior(DropBehavior::Commit);
- insert(2, &sp2);
- assert_current_sum(3, &sp2);
- sp2.rollback().unwrap();
- assert_current_sum(1, &sp2);
- insert(4, &sp2);
+ insert(2, &sp2)?;
+ assert_current_sum(3, &sp2)?;
+ sp2.rollback()?;
+ assert_current_sum(1, &sp2)?;
+ insert(4, &sp2)?;
}
- assert_current_sum(5, &sp1);
- sp1.rollback().unwrap();
+ assert_current_sum(5, &sp1)?;
+ sp1.rollback()?;
{
- let mut sp2 = sp1.savepoint_with_name("my_sp").unwrap();
+ let mut sp2 = sp1.savepoint_with_name("my_sp")?;
sp2.set_drop_behavior(DropBehavior::Ignore);
- insert(8, &sp2);
+ insert(8, &sp2)?;
}
- assert_current_sum(8, &sp1);
- sp1.commit().unwrap();
+ assert_current_sum(8, &sp1)?;
+ sp1.commit()?;
}
- assert_current_sum(8, &db);
+ assert_current_sum(8, &db)?;
+ Ok(())
}
#[test]
- fn test_rc() {
+ fn test_rc() -> Result<()> {
use std::rc::Rc;
- let mut conn = Connection::open_in_memory().unwrap();
- let rc_txn = Rc::new(conn.transaction().unwrap());
+ let mut conn = Connection::open_in_memory()?;
+ let rc_txn = Rc::new(conn.transaction()?);
// This will compile only if Transaction is Debug
Rc::try_unwrap(rc_txn).unwrap();
+ Ok(())
}
- fn insert(x: i32, conn: &Connection) {
- conn.execute("INSERT INTO foo VALUES(?)", &[x]).unwrap();
+ fn insert(x: i32, conn: &Connection) -> Result<usize> {
+ conn.execute("INSERT INTO foo VALUES(?)", [x])
}
- fn assert_current_sum(x: i32, conn: &Connection) {
- let i = conn
- .query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
+ let i = conn.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
assert_eq!(x, i);
+ Ok(())
}
}
diff --git a/src/types/chrono.rs b/src/types/chrono.rs
index 3cba1e9..38276da 100644
--- a/src/types/chrono.rs
+++ b/src/types/chrono.rs
@@ -1,7 +1,5 @@
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
-use std::borrow::Cow;
-
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
@@ -9,18 +7,20 @@ use crate::Result;
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
impl ToSql for NaiveDate {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
- let date_str = self.format("%Y-%m-%d").to_string();
+ let date_str = self.format("%F").to_string();
Ok(ToSqlOutput::from(date_str))
}
}
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
impl FromSql for NaiveDate {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value
.as_str()
- .and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
+ .and_then(|s| match NaiveDate::parse_from_str(s, "%F") {
Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
})
@@ -29,8 +29,9 @@ impl FromSql for NaiveDate {
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
impl ToSql for NaiveTime {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
- let date_str = self.format("%H:%M:%S%.f").to_string();
+ let date_str = self.format("%T%.f").to_string();
Ok(ToSqlOutput::from(date_str))
}
}
@@ -41,8 +42,8 @@ impl FromSql for NaiveTime {
value.as_str().and_then(|s| {
let fmt = match s.len() {
5 => "%H:%M",
- 8 => "%H:%M:%S",
- _ => "%H:%M:%S%.f",
+ 8 => "%T",
+ _ => "%T%.f",
};
match NaiveTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt),
@@ -53,10 +54,11 @@ impl FromSql for NaiveTime {
}
/// ISO 8601 combined date and time without timezone =>
-/// "YYYY-MM-DDTHH:MM:SS.SSS"
+/// "YYYY-MM-DD HH:MM:SS.SSS"
impl ToSql for NaiveDateTime {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
- let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
+ let date_str = self.format("%F %T%.f").to_string();
Ok(ToSqlOutput::from(date_str))
}
}
@@ -68,9 +70,9 @@ 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"
+ "%FT%T%.f"
} else {
- "%Y-%m-%d %H:%M:%S%.f"
+ "%F %T%.f"
};
match NaiveDateTime::parse_from_str(s, fmt) {
@@ -82,33 +84,29 @@ impl FromSql for NaiveDateTime {
}
/// Date and time with time zone => UTC RFC3339 timestamp
-/// ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
+/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
impl<Tz: TimeZone> ToSql for DateTime<Tz> {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
- Ok(ToSqlOutput::from(self.with_timezone(&Utc).to_rfc3339()))
+ let date_str = self.with_timezone(&Utc).format("%F %T%.f%:z").to_string();
+ Ok(ToSqlOutput::from(date_str))
}
}
-/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`.
+/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`.
impl FromSql for DateTime<Utc> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
{
// Try to parse value as rfc3339 first.
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' ' {
- let mut s = s.to_string();
- unsafe {
- let sbytes = s.as_mut_vec();
- sbytes[10] = b'T';
- }
- Cow::Owned(s)
+ let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
+ "%FT%T%.f%#z"
} else {
- Cow::Borrowed(s)
+ "%F %T%.f%#z"
};
- if let Ok(dt) = DateTime::parse_from_rfc3339(&s) {
+ if let Ok(dt) = DateTime::parse_from_str(s, fmt) {
return Ok(dt.with_timezone(&Utc));
}
}
@@ -118,8 +116,9 @@ impl FromSql for DateTime<Utc> {
}
}
-/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`.
+/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`.
impl FromSql for DateTime<Local> {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let utc_dt = DateTime::<Utc>::column_result(value)?;
Ok(utc_dt.with_timezone(&Local))
@@ -128,153 +127,147 @@ impl FromSql for DateTime<Local> {
#[cfg(test)]
mod test {
- use crate::{Connection, Result, NO_PARAMS};
+ use crate::{
+ types::{FromSql, ValueRef},
+ Connection, Result,
+ };
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
- fn checked_memory_handle() -> Connection {
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)")
- .unwrap();
- db
+ fn checked_memory_handle() -> Result<Connection> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)")?;
+ Ok(db)
}
#[test]
- fn test_naive_date() {
- let db = checked_memory_handle();
+ fn test_naive_date() -> Result<()> {
+ let db = checked_memory_handle()?;
let date = NaiveDate::from_ymd(2016, 2, 23);
- db.execute("INSERT INTO foo (t) VALUES (?)", &[&date])
- .unwrap();
+ db.execute("INSERT INTO foo (t) VALUES (?)", [date])?;
- let s: String = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!("2016-02-23", s);
- let t: NaiveDate = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let t: NaiveDate = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!(date, t);
+ Ok(())
}
#[test]
- fn test_naive_time() {
- let db = checked_memory_handle();
+ fn test_naive_time() -> Result<()> {
+ let db = checked_memory_handle()?;
let time = NaiveTime::from_hms(23, 56, 4);
- db.execute("INSERT INTO foo (t) VALUES (?)", &[&time])
- .unwrap();
+ db.execute("INSERT INTO foo (t) VALUES (?)", [time])?;
- let s: String = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!("23:56:04", s);
- let v: NaiveTime = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let v: NaiveTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!(time, v);
+ Ok(())
}
#[test]
- fn test_naive_date_time() {
- let db = checked_memory_handle();
+ fn test_naive_date_time() -> Result<()> {
+ let db = checked_memory_handle()?;
let date = NaiveDate::from_ymd(2016, 2, 23);
let time = NaiveTime::from_hms(23, 56, 4);
let dt = NaiveDateTime::new(date, time);
- db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt])
- .unwrap();
+ db.execute("INSERT INTO foo (t) VALUES (?)", [dt])?;
- 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", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+ assert_eq!("2016-02-23 23:56:04", s);
+ let v: NaiveDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!(dt, v);
- 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", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ db.execute("UPDATE foo set b = datetime(t)", [])?; // "YYYY-MM-DD HH:MM:SS"
+ let hms: NaiveDateTime = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
assert_eq!(dt, hms);
+ Ok(())
}
#[test]
- fn test_date_time_utc() {
- let db = checked_memory_handle();
+ fn test_date_time_utc() -> Result<()> {
+ let db = checked_memory_handle()?;
let date = NaiveDate::from_ymd(2016, 2, 23);
let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
let dt = NaiveDateTime::new(date, time);
let utc = Utc.from_utc_datetime(&dt);
- db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc])
- .unwrap();
+ db.execute("INSERT INTO foo (t) VALUES (?)", [utc])?;
- 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 s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+ assert_eq!("2016-02-23 23:56:04.789+00:00", s);
- let v1: DateTime<Utc> = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let v1: DateTime<Utc> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!(utc, v1);
- let v2: DateTime<Utc> = db
- .query_row("SELECT '2016-02-23 23:56:04.789'", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let v2: DateTime<Utc> =
+ db.query_row("SELECT '2016-02-23 23:56:04.789'", [], |r| r.get(0))?;
assert_eq!(utc, v2);
- let v3: DateTime<Utc> = db
- .query_row("SELECT '2016-02-23 23:56:04'", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let v3: DateTime<Utc> = db.query_row("SELECT '2016-02-23 23:56:04'", [], |r| r.get(0))?;
assert_eq!(utc - Duration::milliseconds(789), v3);
- let v4: DateTime<Utc> = db
- .query_row("SELECT '2016-02-23 23:56:04.789+00:00'", NO_PARAMS, |r| {
- r.get(0)
- })
- .unwrap();
+ let v4: DateTime<Utc> =
+ db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", [], |r| r.get(0))?;
assert_eq!(utc, v4);
+ Ok(())
}
#[test]
- fn test_date_time_local() {
- let db = checked_memory_handle();
+ fn test_date_time_local() -> Result<()> {
+ let db = checked_memory_handle()?;
let date = NaiveDate::from_ymd(2016, 2, 23);
let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
let dt = NaiveDateTime::new(date, time);
let local = Local.from_local_datetime(&dt).single().unwrap();
- db.execute("INSERT INTO foo (t) VALUES (?)", &[&local])
- .unwrap();
+ db.execute("INSERT INTO foo (t) VALUES (?)", [local])?;
// Stored string should be in UTC
- let s: String = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert!(s.ends_with("+00:00"));
- let v: DateTime<Local> = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let v: DateTime<Local> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!(local, v);
+ Ok(())
}
#[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));
+ fn test_sqlite_functions() -> Result<()> {
+ let db = checked_memory_handle()?;
+ let result: Result<NaiveTime> = db.query_row("SELECT CURRENT_TIME", [], |r| r.get(0));
assert!(result.is_ok());
- let result: Result<NaiveDate> =
- db.query_row("SELECT CURRENT_DATE", NO_PARAMS, |r| r.get(0));
+ let result: Result<NaiveDate> = db.query_row("SELECT CURRENT_DATE", [], |r| r.get(0));
assert!(result.is_ok());
let result: Result<NaiveDateTime> =
- db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
+ db.query_row("SELECT CURRENT_TIMESTAMP", [], |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));
+ db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
+ assert!(result.is_ok());
+ Ok(())
+ }
+
+ #[test]
+ fn test_naive_date_time_param() -> Result<()> {
+ let db = checked_memory_handle()?;
+ let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()], |r| r.get(0));
assert!(result.is_ok());
+ Ok(())
+ }
+
+ #[test]
+ fn test_date_time_param() -> Result<()> {
+ let db = checked_memory_handle()?;
+ let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()], |r| r.get(0));
+ assert!(result.is_ok());
+ Ok(())
+ }
+
+ #[test]
+ fn test_lenient_parse_timezone() {
+ assert!(DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00Z")).is_ok());
+ assert!(DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00+00")).is_ok());
}
}
diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs
index 3fe74b4..7381fdf 100644
--- a/src/types/from_sql.rs
+++ b/src/types/from_sql.rs
@@ -1,8 +1,9 @@
use super::{Value, ValueRef};
+use std::convert::TryInto;
use std::error::Error;
use std::fmt;
-/// Enum listing possible errors from `FromSql` trait.
+/// Enum listing possible errors from [`FromSql`] trait.
#[derive(Debug)]
#[non_exhaustive]
pub enum FromSqlError {
@@ -25,7 +26,7 @@ pub enum FromSqlError {
#[cfg(feature = "uuid")]
InvalidUuidSize(usize),
- /// An error case available for implementors of the `FromSql` trait.
+ /// An error case available for implementors of the [`FromSql`] trait.
Other(Box<dyn Error + Send + Sync + 'static>),
}
@@ -71,48 +72,22 @@ impl Error for FromSqlError {
}
}
-/// Result type for implementors of the `FromSql` trait.
+/// Result type for implementors of the [`FromSql`] trait.
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.
pub trait FromSql: Sized {
/// Converts SQLite value into Rust value.
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
}
-impl FromSql for isize {
- 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))
- } else {
- Ok(i as isize)
- }
- })
- }
-}
-
macro_rules! from_sql_integral(
($t:ident) => (
impl FromSql for $t {
+ #[inline]
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))
- } else {
- Ok(i as $t)
- }
- })
+ let i = i64::column_result(value)?;
+ i.try_into().map_err(|_| FromSqlError::OutOfRange(i))
}
}
)
@@ -121,17 +96,34 @@ macro_rules! from_sql_integral(
from_sql_integral!(i8);
from_sql_integral!(i16);
from_sql_integral!(i32);
+// from_sql_integral!(i64); // Not needed because the native type is i64.
+from_sql_integral!(isize);
from_sql_integral!(u8);
from_sql_integral!(u16);
from_sql_integral!(u32);
+from_sql_integral!(u64);
+from_sql_integral!(usize);
impl FromSql for i64 {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_i64()
}
}
+impl FromSql for f32 {
+ #[inline]
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ match value {
+ ValueRef::Integer(i) => Ok(i as f32),
+ ValueRef::Real(f) => Ok(f as f32),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+}
+
impl FromSql for f64 {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => Ok(i as f64),
@@ -142,36 +134,42 @@ impl FromSql for f64 {
}
impl FromSql for bool {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- i64::column_result(value).map(|i| !matches!(i, 0))
+ i64::column_result(value).map(|i| i != 0)
}
}
impl FromSql for String {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(ToString::to_string)
}
}
impl FromSql for Box<str> {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(Into::into)
}
}
impl FromSql for std::rc::Rc<str> {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(Into::into)
}
}
impl FromSql for std::sync::Arc<str> {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().map(Into::into)
}
}
impl FromSql for Vec<u8> {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_blob().map(|b| b.to_vec())
}
@@ -179,6 +177,7 @@ impl FromSql for Vec<u8> {
#[cfg(feature = "i128_blob")]
impl FromSql for i128 {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
use byteorder::{BigEndian, ByteOrder};
@@ -194,6 +193,7 @@ impl FromSql for i128 {
#[cfg(feature = "uuid")]
impl FromSql for uuid::Uuid {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value
.as_blob()
@@ -206,6 +206,7 @@ impl FromSql for uuid::Uuid {
}
impl<T: FromSql> FromSql for Option<T> {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Null => Ok(None),
@@ -215,6 +216,7 @@ impl<T: FromSql> FromSql for Option<T> {
}
impl FromSql for Value {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
Ok(value.into())
}
@@ -223,15 +225,11 @@ impl FromSql for Value {
#[cfg(test)]
mod test {
use super::FromSql;
- use crate::{Connection, Error};
-
- fn checked_memory_handle() -> Connection {
- Connection::open_in_memory().unwrap()
- }
+ use crate::{Connection, Error, Result};
#[test]
- fn test_integral_ranges() {
- let db = checked_memory_handle();
+ fn test_integral_ranges() -> Result<()> {
+ let db = Connection::open_in_memory()?;
fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64])
where
@@ -266,5 +264,6 @@ mod test {
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, 4_294_967_296], &[0, 1, 4_294_967_295]);
+ Ok(())
}
}
diff --git a/src/types/mod.rs b/src/types/mod.rs
index 85d8ef2..706be0c 100644
--- a/src/types/mod.rs
+++ b/src/types/mod.rs
@@ -1,22 +1,39 @@
//! 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
+//! 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).
-//! * Reals (`f64`)
//! * Strings (`String` and `&str`)
//! * Blobs (`Vec<u8>` and `&[u8]`)
+//! * Numbers
//!
-//! Additionally, if the `time` feature is enabled, implementations are
+//! The number situation is a little complicated due to the fact that all
+//! numbers in SQLite are stored as `INTEGER` (`i64`) or `REAL` (`f64`).
+//!
+//! [`ToSql`] and [`FromSql`] are implemented for all primitive number types.
+//! [`FromSql`] has different behaviour depending on the SQL and Rust types, and
+//! the value.
+//!
+//! * `INTEGER` to integer: returns an
+//! [`Error::IntegralValueOutOfRange`](crate::Error::IntegralValueOutOfRange)
+//! error if the value does not fit in the Rust type.
+//! * `REAL` to integer: always returns an
+//! [`Error::InvalidColumnType`](crate::Error::InvalidColumnType) error.
+//! * `INTEGER` to float: casts using `as` operator. Never fails.
+//! * `REAL` to float: casts using `as` operator. Never fails.
+//!
+//! [`ToSql`] always succeeds except when storing a `u64` or `usize` value that
+//! cannot fit in an `INTEGER` (`i64`). Also note that SQLite ignores column
+//! types, so if you store an `i64` in a column with type `REAL` it will be
+//! stored as an `INTEGER`, not a `REAL`.
+//!
+//! If the `time` feature is enabled, implementations are
//! provided for `time::OffsetDateTime` 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 datetimes, you can use a newtype.
-//!
#![cfg_attr(
feature = "time",
doc = r##"
@@ -46,9 +63,9 @@ impl ToSql for DateTimeSql {
"##
)]
-//! `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};
@@ -79,7 +96,7 @@ mod value_ref;
/// # use rusqlite::types::{Null};
///
/// fn insert_null(conn: &Connection) -> Result<usize> {
-/// conn.execute("INSERT INTO people (name) VALUES (?)", &[Null])
+/// conn.execute("INSERT INTO people (name) VALUES (?)", [Null])
/// }
/// ```
#[derive(Copy, Clone)]
@@ -104,11 +121,11 @@ pub enum Type {
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
- Type::Null => write!(f, "Null"),
- Type::Integer => write!(f, "Integer"),
- Type::Real => write!(f, "Real"),
- Type::Text => write!(f, "Text"),
- Type::Blob => write!(f, "Blob"),
+ Type::Null => f.pad("Null"),
+ Type::Integer => f.pad("Integer"),
+ Type::Real => f.pad("Real"),
+ Type::Text => f.pad("Text"),
+ Type::Blob => f.pad("Blob"),
}
}
}
@@ -116,103 +133,92 @@ impl fmt::Display for Type {
#[cfg(test)]
mod test {
use super::Value;
- use crate::{Connection, Error, NO_PARAMS};
+ use crate::{params, Connection, Error, Result, Statement};
use std::f64::EPSILON;
use std::os::raw::{c_double, c_int};
- fn checked_memory_handle() -> Connection {
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT, i INTEGER, f FLOAT, n)")
- .unwrap();
- db
+ fn checked_memory_handle() -> Result<Connection> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT, i INTEGER, f FLOAT, n)")?;
+ Ok(db)
}
#[test]
- fn test_blob() {
- let db = checked_memory_handle();
+ fn test_blob() -> Result<()> {
+ let db = checked_memory_handle()?;
let v1234 = vec![1u8, 2, 3, 4];
- db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])
- .unwrap();
+ db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])?;
- let v: Vec<u8> = db
- .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
assert_eq!(v, v1234);
+ Ok(())
}
#[test]
- fn test_empty_blob() {
- let db = checked_memory_handle();
+ fn test_empty_blob() -> Result<()> {
+ let db = checked_memory_handle()?;
let empty = vec![];
- db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])
- .unwrap();
+ db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])?;
- let v: Vec<u8> = db
- .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
assert_eq!(v, empty);
+ Ok(())
}
#[test]
- fn test_str() {
- let db = checked_memory_handle();
+ fn test_str() -> Result<()> {
+ 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])?;
- let from: String = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!(from, s);
+ Ok(())
}
#[test]
- fn test_string() {
- let db = checked_memory_handle();
+ fn test_string() -> Result<()> {
+ let db = checked_memory_handle()?;
let s = "hello, world!";
- db.execute("INSERT INTO foo(t) VALUES (?)", &[s.to_owned()])
- .unwrap();
+ db.execute("INSERT INTO foo(t) VALUES (?)", [s.to_owned()])?;
- let from: String = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!(from, s);
+ Ok(())
}
#[test]
- fn test_value() {
- let db = checked_memory_handle();
+ fn test_value() -> Result<()> {
+ let db = checked_memory_handle()?;
- db.execute("INSERT INTO foo(i) VALUES (?)", &[Value::Integer(10)])
- .unwrap();
+ db.execute("INSERT INTO foo(i) VALUES (?)", [Value::Integer(10)])?;
assert_eq!(
10i64,
- db.query_row::<i64, _, _>("SELECT i FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap()
+ db.query_row::<i64, _, _>("SELECT i FROM foo", [], |r| r.get(0))?
);
+ Ok(())
}
#[test]
- fn test_option() {
- let db = checked_memory_handle();
+ fn test_option() -> Result<()> {
+ let db = checked_memory_handle()?;
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])?;
+ db.execute("INSERT INTO foo(b) VALUES (?)", &[&b])?;
- let mut stmt = db
- .prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")
- .unwrap();
- let mut rows = stmt.query(NO_PARAMS).unwrap();
+ let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")?;
+ let mut rows = stmt.query([])?;
{
- let row1 = rows.next().unwrap().unwrap();
+ let row1 = rows.next()?.unwrap();
let s1: Option<String> = row1.get_unwrap(0);
let b1: Option<Vec<u8>> = row1.get_unwrap(1);
assert_eq!(s.unwrap(), s1.unwrap());
@@ -220,42 +226,42 @@ mod test {
}
{
- let row2 = rows.next().unwrap().unwrap();
+ let row2 = rows.next()?.unwrap();
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);
}
+ Ok(())
}
#[test]
#[allow(clippy::cognitive_complexity)]
- fn test_mismatched_types() {
+ fn test_mismatched_types() -> Result<()> {
fn is_invalid_column_type(err: Error) -> bool {
matches!(err, Error::InvalidColumnType(..))
}
- let db = checked_memory_handle();
+ let db = checked_memory_handle()?;
db.execute(
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
- NO_PARAMS,
- )
- .unwrap();
+ [],
+ )?;
- let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
- let mut rows = stmt.query(NO_PARAMS).unwrap();
+ let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
+ let mut rows = stmt.query([])?;
- let row = rows.next().unwrap().unwrap();
+ let row = rows.next()?.unwrap();
// check the correct types come back as expected
- 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());
+ assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0)?);
+ assert_eq!("text", row.get::<_, String>(1)?);
+ assert_eq!(1, row.get::<_, c_int>(2)?);
+ assert!((1.5 - row.get::<_, c_double>(3)?).abs() < EPSILON);
+ assert_eq!(row.get::<_, Option<c_int>>(4)?, None);
+ assert_eq!(row.get::<_, Option<c_double>>(4)?, None);
+ assert_eq!(row.get::<_, Option<String>>(4)?, None);
// check some invalid types
@@ -340,33 +346,117 @@ mod test {
assert!(is_invalid_column_type(
row.get::<_, time::OffsetDateTime>(4).err().unwrap()
));
+ Ok(())
}
#[test]
- fn test_dynamic_type() {
+ fn test_dynamic_type() -> Result<()> {
use super::Value;
- let db = checked_memory_handle();
+ let db = checked_memory_handle()?;
db.execute(
"INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
- NO_PARAMS,
- )
- .unwrap();
+ [],
+ )?;
- let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
- let mut rows = stmt.query(NO_PARAMS).unwrap();
+ let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
+ let mut rows = stmt.query([])?;
- let row = rows.next().unwrap().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() {
+ let row = rows.next()?.unwrap();
+ assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0)?);
+ assert_eq!(Value::Text(String::from("text")), row.get::<_, Value>(1)?);
+ assert_eq!(Value::Integer(1), row.get::<_, Value>(2)?);
+ match row.get::<_, Value>(3)? {
Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
x => panic!("Invalid Value {:?}", x),
}
- assert_eq!(Value::Null, row.get::<_, Value>(4).unwrap());
+ assert_eq!(Value::Null, row.get::<_, Value>(4)?);
+ Ok(())
+ }
+
+ macro_rules! test_conversion {
+ ($db_etc:ident, $insert_value:expr, $get_type:ty,expect $expected_value:expr) => {
+ $db_etc.insert_statement.execute(params![$insert_value])?;
+ let res = $db_etc
+ .query_statement
+ .query_row([], |row| row.get::<_, $get_type>(0));
+ assert_eq!(res?, $expected_value);
+ $db_etc.delete_statement.execute([])?;
+ };
+ ($db_etc:ident, $insert_value:expr, $get_type:ty,expect_from_sql_error) => {
+ $db_etc.insert_statement.execute(params![$insert_value])?;
+ let res = $db_etc
+ .query_statement
+ .query_row([], |row| row.get::<_, $get_type>(0));
+ res.unwrap_err();
+ $db_etc.delete_statement.execute([])?;
+ };
+ ($db_etc:ident, $insert_value:expr, $get_type:ty,expect_to_sql_error) => {
+ $db_etc
+ .insert_statement
+ .execute(params![$insert_value])
+ .unwrap_err();
+ };
+ }
+
+ #[test]
+ fn test_numeric_conversions() -> Result<()> {
+ #![allow(clippy::float_cmp)]
+
+ // Test what happens when we store an f32 and retrieve an i32 etc.
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (x)")?;
+
+ // SQLite actually ignores the column types, so we just need to test
+ // different numeric values.
+
+ struct DbEtc<'conn> {
+ insert_statement: Statement<'conn>,
+ query_statement: Statement<'conn>,
+ delete_statement: Statement<'conn>,
+ }
+
+ let mut db_etc = DbEtc {
+ insert_statement: db.prepare("INSERT INTO foo VALUES (?1)")?,
+ query_statement: db.prepare("SELECT x FROM foo")?,
+ delete_statement: db.prepare("DELETE FROM foo")?,
+ };
+
+ // Basic non-converting test.
+ test_conversion!(db_etc, 0u8, u8, expect 0u8);
+
+ // In-range integral conversions.
+ test_conversion!(db_etc, 100u8, i8, expect 100i8);
+ test_conversion!(db_etc, 200u8, u8, expect 200u8);
+ test_conversion!(db_etc, 100u16, i8, expect 100i8);
+ test_conversion!(db_etc, 200u16, u8, expect 200u8);
+ test_conversion!(db_etc, u32::MAX, u64, expect u32::MAX as u64);
+ test_conversion!(db_etc, i64::MIN, i64, expect i64::MIN);
+ test_conversion!(db_etc, i64::MAX, i64, expect i64::MAX);
+ test_conversion!(db_etc, i64::MAX, u64, expect i64::MAX as u64);
+ test_conversion!(db_etc, 100usize, usize, expect 100usize);
+ test_conversion!(db_etc, 100u64, u64, expect 100u64);
+ test_conversion!(db_etc, i64::MAX as u64, u64, expect i64::MAX as u64);
+
+ // Out-of-range integral conversions.
+ test_conversion!(db_etc, 200u8, i8, expect_from_sql_error);
+ test_conversion!(db_etc, 400u16, i8, expect_from_sql_error);
+ test_conversion!(db_etc, 400u16, u8, expect_from_sql_error);
+ test_conversion!(db_etc, -1i8, u8, expect_from_sql_error);
+ test_conversion!(db_etc, i64::MIN, u64, expect_from_sql_error);
+ test_conversion!(db_etc, u64::MAX, i64, expect_to_sql_error);
+ test_conversion!(db_etc, u64::MAX, u64, expect_to_sql_error);
+ test_conversion!(db_etc, i64::MAX as u64 + 1, u64, expect_to_sql_error);
+
+ // FromSql integer to float, always works.
+ test_conversion!(db_etc, i64::MIN, f32, expect i64::MIN as f32);
+ test_conversion!(db_etc, i64::MAX, f32, expect i64::MAX as f32);
+ test_conversion!(db_etc, i64::MIN, f64, expect i64::MIN as f64);
+ test_conversion!(db_etc, i64::MAX, f64, expect i64::MAX as f64);
+
+ // FromSql float to int conversion, never works even if the actual value
+ // is an integer.
+ test_conversion!(db_etc, 0f64, i64, expect_from_sql_error);
+ Ok(())
}
}
diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs
index abaecda..f018032 100644
--- a/src/types/serde_json.rs
+++ b/src/types/serde_json.rs
@@ -1,4 +1,4 @@
-//! `ToSql` and `FromSql` implementation for JSON `Value`.
+//! [`ToSql`] and [`FromSql`] implementation for JSON `Value`.
use serde_json::Value;
@@ -7,6 +7,7 @@ use crate::Result;
/// Serialize JSON `Value` to text.
impl ToSql for Value {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
}
@@ -14,6 +15,7 @@ impl ToSql for Value {
/// Deserialize text/blob to JSON `Value`.
impl FromSql for Value {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(s) => serde_json::from_slice(s),
@@ -27,34 +29,29 @@ impl FromSql for Value {
#[cfg(test)]
mod test {
use crate::types::ToSql;
- use crate::{Connection, NO_PARAMS};
+ use crate::{Connection, Result};
- fn checked_memory_handle() -> Connection {
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo (t TEXT, b BLOB)")
- .unwrap();
- db
+ fn checked_memory_handle() -> Result<Connection> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (t TEXT, b BLOB)")?;
+ Ok(db)
}
#[test]
- fn test_json_value() {
- let db = checked_memory_handle();
+ fn test_json_value() -> Result<()> {
+ let db = checked_memory_handle()?;
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 as &dyn ToSql, &json.as_bytes()],
- )
- .unwrap();
+ )?;
- let t: serde_json::Value = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let t: serde_json::Value = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!(data, t);
- let b: serde_json::Value = db
- .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let b: serde_json::Value = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
assert_eq!(data, b);
+ Ok(())
}
}
diff --git a/src/types/time.rs b/src/types/time.rs
index 8589167..ac182a9 100644
--- a/src/types/time.rs
+++ b/src/types/time.rs
@@ -1,13 +1,14 @@
-//! `ToSql` and `FromSql` implementation for [`time::OffsetDateTime`].
+//! [`ToSql`] and [`FromSql`] implementation for [`time::OffsetDateTime`].
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S";
-const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%NZ";
+const SQLITE_DATETIME_FMT: &str = "%Y-%m-%d %H:%M:%S.%NZ";
const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%N %z";
impl ToSql for OffsetDateTime {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let time_string = self.to_offset(UtcOffset::UTC).format(SQLITE_DATETIME_FMT);
Ok(ToSqlOutput::from(time_string))
@@ -32,20 +33,19 @@ impl FromSql for OffsetDateTime {
#[cfg(test)]
mod test {
- use crate::{Connection, Result, NO_PARAMS};
+ use crate::{Connection, Result};
use std::time::Duration;
use time::OffsetDateTime;
- fn checked_memory_handle() -> Connection {
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")
- .unwrap();
- db
+ fn checked_memory_handle() -> Result<Connection> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
+ Ok(db)
}
#[test]
- fn test_offset_date_time() {
- let db = checked_memory_handle();
+ fn test_offset_date_time() -> Result<()> {
+ let db = checked_memory_handle()?;
let mut ts_vec = vec![];
@@ -60,23 +60,31 @@ mod test {
ts_vec.push(make_datetime(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: OffsetDateTime = db
- .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
- .unwrap();
+ let from: OffsetDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
- db.execute("DELETE FROM foo", NO_PARAMS).unwrap();
+ db.execute("DELETE FROM foo", [])?;
assert_eq!(from, ts);
}
+ Ok(())
}
#[test]
- fn test_sqlite_functions() {
- let db = checked_memory_handle();
+ fn test_sqlite_functions() -> Result<()> {
+ let db = checked_memory_handle()?;
let result: Result<OffsetDateTime> =
- db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
+ db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
assert!(result.is_ok());
+ Ok(())
+ }
+
+ #[test]
+ fn test_param() -> Result<()> {
+ let db = checked_memory_handle()?;
+ let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [OffsetDateTime::now_utc()], |r| r.get(0));
+ assert!(result.is_ok());
+ Ok(())
}
}
diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs
index 937c0f8..1bf7711 100644
--- a/src/types/to_sql.rs
+++ b/src/types/to_sql.rs
@@ -1,11 +1,12 @@
use super::{Null, Value, ValueRef};
#[cfg(feature = "array")]
use crate::vtab::array::Array;
-use crate::Result;
+use crate::{Error, Result};
use std::borrow::Cow;
+use std::convert::TryFrom;
-/// `ToSqlOutput` represents the possible output types for implementors of the
-/// `ToSql` trait.
+/// `ToSqlOutput` represents the possible output types for implementers of the
+/// [`ToSql`] trait.
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum ToSqlOutput<'a> {
@@ -31,6 +32,7 @@ impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a>
where
&'a T: Into<ValueRef<'a>>,
{
+ #[inline]
fn from(t: &'a T) -> Self {
ToSqlOutput::Borrowed(t.into())
}
@@ -44,6 +46,7 @@ where
macro_rules! from_value(
($t:ty) => (
impl From<$t> for ToSqlOutput<'_> {
+ #[inline]
fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
}
)
@@ -59,6 +62,7 @@ from_value!(isize);
from_value!(u8);
from_value!(u16);
from_value!(u32);
+from_value!(f32);
from_value!(f64);
from_value!(Vec<u8>);
@@ -72,6 +76,7 @@ from_value!(i128);
from_value!(uuid::Uuid);
impl ToSql for ToSqlOutput<'_> {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(match *self {
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
@@ -85,31 +90,36 @@ impl ToSql for ToSqlOutput<'_> {
}
}
-/// A trait for types that can be converted into SQLite values.
+/// A trait for types that can be converted into SQLite values. Returns
+/// [`Error::ToSqlConversionFailure`] if the conversion fails.
pub trait ToSql {
/// Converts Rust value to SQLite value
fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
}
impl<T: ToSql + ToOwned + ?Sized> ToSql for Cow<'_, T> {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
self.as_ref().to_sql()
}
}
impl<T: ToSql + ?Sized> ToSql for Box<T> {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
self.as_ref().to_sql()
}
}
impl<T: ToSql + ?Sized> ToSql for std::rc::Rc<T> {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
self.as_ref().to_sql()
}
}
impl<T: ToSql + ?Sized> ToSql for std::sync::Arc<T> {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
self.as_ref().to_sql()
}
@@ -130,6 +140,7 @@ impl<T: ToSql + ?Sized> ToSql for std::sync::Arc<T> {
macro_rules! to_sql_self(
($t:ty) => (
impl ToSql for $t {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(*self))
}
@@ -147,6 +158,7 @@ to_sql_self!(isize);
to_sql_self!(u8);
to_sql_self!(u16);
to_sql_self!(u32);
+to_sql_self!(f32);
to_sql_self!(f64);
#[cfg(feature = "i128_blob")]
@@ -155,46 +167,73 @@ to_sql_self!(i128);
#[cfg(feature = "uuid")]
to_sql_self!(uuid::Uuid);
+macro_rules! to_sql_self_fallible(
+ ($t:ty) => (
+ impl ToSql for $t {
+ #[inline]
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::Owned(Value::Integer(
+ i64::try_from(*self).map_err(
+ // TODO: Include the values in the error message.
+ |err| Error::ToSqlConversionFailure(err.into())
+ )?
+ )))
+ }
+ }
+ )
+);
+
+// Special implementations for usize and u64 because these conversions can fail.
+to_sql_self_fallible!(u64);
+to_sql_self_fallible!(usize);
+
impl<T: ?Sized> ToSql for &'_ T
where
T: ToSql,
{
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
(*self).to_sql()
}
}
impl ToSql for String {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.as_str()))
}
}
impl ToSql for str {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self))
}
}
impl ToSql for Vec<u8> {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.as_slice()))
}
}
impl ToSql for [u8] {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self))
}
}
impl ToSql for Value {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self))
}
}
impl<T: ToSql> ToSql for Option<T> {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
match *self {
None => Ok(ToSqlOutput::from(Null)),
@@ -300,12 +339,11 @@ mod test {
#[cfg(feature = "i128_blob")]
#[test]
- fn test_i128() {
- use crate::{Connection, NO_PARAMS};
+ fn test_i128() -> crate::Result<()> {
+ use crate::Connection;
use std::i128;
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")
- .unwrap();
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")?;
db.execute(
"
INSERT INTO foo(i128, desc) VALUES
@@ -313,21 +351,16 @@ mod test {
(?, 'neg one'), (?, 'neg two'),
(?, 'pos one'), (?, 'pos two'),
(?, 'min'), (?, 'max')",
- &[0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
- )
- .unwrap();
+ [0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
+ )?;
- let mut stmt = db
- .prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")
- .unwrap();
+ let mut stmt = db.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")?;
let res = stmt
- .query_map(NO_PARAMS, |row| {
+ .query_map([], |row| {
Ok((row.get::<_, i128>(0)?, row.get::<_, String>(1)?))
- })
- .unwrap()
- .collect::<Result<Vec<_>, _>>()
- .unwrap();
+ })?
+ .collect::<Result<Vec<_>, _>>()?;
assert_eq!(
res,
@@ -341,37 +374,35 @@ mod test {
(i128::MAX, "max".to_owned()),
]
);
+ Ok(())
}
#[cfg(feature = "uuid")]
#[test]
- fn test_uuid() {
+ fn test_uuid() -> crate::Result<()> {
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 db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);")?;
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 stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?")?;
- let mut rows = stmt.query(params![id]).unwrap();
- let row = rows.next().unwrap().unwrap();
+ let mut rows = stmt.query(params![id])?;
+ let row = rows.next()?.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");
+ Ok(())
}
}
diff --git a/src/types/url.rs b/src/types/url.rs
index 1c9c63a..fea8500 100644
--- a/src/types/url.rs
+++ b/src/types/url.rs
@@ -1,10 +1,11 @@
-//! `ToSql` and `FromSql` implementation for [`url::Url`].
+//! [`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 {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.as_str()))
}
@@ -12,6 +13,7 @@ impl ToSql for Url {
/// Deserialize text to `Url`.
impl FromSql for Url {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(s) => {
@@ -28,20 +30,19 @@ 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 checked_memory_handle() -> Result<Connection> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE urls (i INTEGER, v TEXT)")?;
+ Ok(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))
+ db.query_row("SELECT v FROM urls WHERE i = ?", [id], |r| r.get(0))
}
#[test]
- fn test_sql_url() {
- let db = &checked_memory_handle();
+ fn test_sql_url() -> Result<()> {
+ let db = &checked_memory_handle()?;
let url0 = Url::parse("http://www.example1.com").unwrap();
let url1 = Url::parse("http://www.example1.com/👌").unwrap();
@@ -52,16 +53,15 @@ mod test {
// 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, 0)?, url0);
- assert_eq!(get_url(db, 1).unwrap(), url1);
+ assert_eq!(get_url(db, 1)?, url1);
// Should successfully read it, even though it wasn't inserted as an
// escaped url.
- let out_url2: Url = get_url(db, 2).unwrap();
+ let out_url2: Url = get_url(db, 2)?;
assert_eq!(out_url2, Url::parse(url2).unwrap());
// Make sure the conversion error comes through correctly.
@@ -77,5 +77,6 @@ mod test {
panic!("Expected conversion failure, got {}", e);
}
}
+ Ok(())
}
}
diff --git a/src/types/value.rs b/src/types/value.rs
index 64dc203..944655c 100644
--- a/src/types/value.rs
+++ b/src/types/value.rs
@@ -3,7 +3,8 @@ use super::{Null, Type};
/// Owning [dynamic type value](http://sqlite.org/datatype3.html). Value's type is typically
/// dictated by SQLite (not by the caller).
///
-/// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value.
+/// See [`ValueRef`](crate::types::ValueRef) for a non-owning dynamic type
+/// value.
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
/// The value is a `NULL` value.
@@ -19,18 +20,21 @@ pub enum Value {
}
impl From<Null> for Value {
+ #[inline]
fn from(_: Null) -> Value {
Value::Null
}
}
impl From<bool> for Value {
+ #[inline]
fn from(i: bool) -> Value {
Value::Integer(i as i64)
}
}
impl From<isize> for Value {
+ #[inline]
fn from(i: isize) -> Value {
Value::Integer(i as i64)
}
@@ -38,6 +42,7 @@ impl From<isize> for Value {
#[cfg(feature = "i128_blob")]
impl From<i128> for Value {
+ #[inline]
fn from(i: i128) -> Value {
use byteorder::{BigEndian, ByteOrder};
let mut buf = vec![0u8; 16];
@@ -50,6 +55,7 @@ impl From<i128> for Value {
#[cfg(feature = "uuid")]
impl From<uuid::Uuid> for Value {
+ #[inline]
fn from(id: uuid::Uuid) -> Value {
Value::Blob(id.as_bytes().to_vec())
}
@@ -58,6 +64,7 @@ impl From<uuid::Uuid> for Value {
macro_rules! from_i64(
($t:ty) => (
impl From<$t> for Value {
+ #[inline]
fn from(i: $t) -> Value {
Value::Integer(i64::from(i))
}
@@ -73,24 +80,35 @@ from_i64!(u16);
from_i64!(u32);
impl From<i64> for Value {
+ #[inline]
fn from(i: i64) -> Value {
Value::Integer(i)
}
}
+impl From<f32> for Value {
+ #[inline]
+ fn from(f: f32) -> Value {
+ Value::Real(f.into())
+ }
+}
+
impl From<f64> for Value {
+ #[inline]
fn from(f: f64) -> Value {
Value::Real(f)
}
}
impl From<String> for Value {
+ #[inline]
fn from(s: String) -> Value {
Value::Text(s)
}
}
impl From<Vec<u8>> for Value {
+ #[inline]
fn from(v: Vec<u8>) -> Value {
Value::Blob(v)
}
@@ -100,6 +118,7 @@ impl<T> From<Option<T>> for Value
where
T: Into<Value>,
{
+ #[inline]
fn from(v: Option<T>) -> Value {
match v {
Some(x) => x.into(),
@@ -110,6 +129,7 @@ where
impl Value {
/// Returns SQLite fundamental datatype.
+ #[inline]
pub fn data_type(&self) -> Type {
match *self {
Value::Null => Type::Null,
diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs
index 2f32434..446ad08 100644
--- a/src/types/value_ref.rs
+++ b/src/types/value_ref.rs
@@ -4,7 +4,7 @@ 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.
+/// See [`Value`](Value) for an owning dynamic type value.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ValueRef<'a> {
/// The value is a `NULL` value.
@@ -21,6 +21,7 @@ pub enum ValueRef<'a> {
impl ValueRef<'_> {
/// Returns SQLite fundamental datatype.
+ #[inline]
pub fn data_type(&self) -> Type {
match *self {
ValueRef::Null => Type::Null,
@@ -34,7 +35,9 @@ impl ValueRef<'_> {
impl<'a> ValueRef<'a> {
/// If `self` is case `Integer`, returns the integral value. Otherwise,
- /// returns `Err(Error::InvalidColumnType)`.
+ /// returns [`Err(Error::InvalidColumnType)`](crate::Error::
+ /// InvalidColumnType).
+ #[inline]
pub fn as_i64(&self) -> FromSqlResult<i64> {
match *self {
ValueRef::Integer(i) => Ok(i),
@@ -43,7 +46,9 @@ impl<'a> ValueRef<'a> {
}
/// If `self` is case `Real`, returns the floating point value. Otherwise,
- /// returns `Err(Error::InvalidColumnType)`.
+ /// returns [`Err(Error::InvalidColumnType)`](crate::Error::
+ /// InvalidColumnType).
+ #[inline]
pub fn as_f64(&self) -> FromSqlResult<f64> {
match *self {
ValueRef::Real(f) => Ok(f),
@@ -52,7 +57,8 @@ impl<'a> ValueRef<'a> {
}
/// If `self` is case `Text`, returns the string value. Otherwise, returns
- /// `Err(Error::InvalidColumnType)`.
+ /// [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
+ #[inline]
pub fn as_str(&self) -> FromSqlResult<&'a str> {
match *self {
ValueRef::Text(t) => {
@@ -63,7 +69,8 @@ impl<'a> ValueRef<'a> {
}
/// If `self` is case `Blob`, returns the byte slice. Otherwise, returns
- /// `Err(Error::InvalidColumnType)`.
+ /// [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
+ #[inline]
pub fn as_blob(&self) -> FromSqlResult<&'a [u8]> {
match *self {
ValueRef::Blob(b) => Ok(b),
@@ -73,6 +80,7 @@ impl<'a> ValueRef<'a> {
}
impl From<ValueRef<'_>> for Value {
+ #[inline]
fn from(borrowed: ValueRef<'_>) -> Value {
match borrowed {
ValueRef::Null => Value::Null,
@@ -88,18 +96,21 @@ impl From<ValueRef<'_>> for Value {
}
impl<'a> From<&'a str> for ValueRef<'a> {
+ #[inline]
fn from(s: &str) -> ValueRef<'_> {
ValueRef::Text(s.as_bytes())
}
}
impl<'a> From<&'a [u8]> for ValueRef<'a> {
+ #[inline]
fn from(s: &[u8]) -> ValueRef<'_> {
ValueRef::Blob(s)
}
}
impl<'a> From<&'a Value> for ValueRef<'a> {
+ #[inline]
fn from(value: &'a Value) -> ValueRef<'a> {
match *value {
Value::Null => ValueRef::Null,
@@ -115,6 +126,7 @@ impl<'a, T> From<Option<T>> for ValueRef<'a>
where
T: Into<ValueRef<'a>>,
{
+ #[inline]
fn from(s: Option<T>) -> ValueRef<'a> {
match s {
Some(x) => x.into(),
diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs
index af2a06c..5f9cdbf 100644
--- a/src/unlock_notify.rs
+++ b/src/unlock_notify.rs
@@ -100,17 +100,17 @@ pub unsafe fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int {
#[cfg(feature = "unlock_notify")]
#[cfg(test)]
mod test {
- use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior, NO_PARAMS};
+ use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior};
use std::sync::mpsc::sync_channel;
use std::thread;
use std::time;
#[test]
- fn test_unlock_notify() {
+ fn test_unlock_notify() -> Result<()> {
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 db1 = Connection::open_with_flags(url, flags)?;
+ db1.execute_batch("CREATE TABLE foo (x)")?;
let (rx, tx) = sync_channel(0);
let child = thread::spawn(move || {
let mut db2 = Connection::open_with_flags(url, flags).unwrap();
@@ -122,8 +122,9 @@ mod test {
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());
+ let the_answer: Result<i64> = db1.query_row("SELECT x FROM foo", [], |r| r.get(0));
+ assert_eq!(42i64, the_answer?);
child.join().unwrap();
+ Ok(())
}
}
diff --git a/src/util/small_cstr.rs b/src/util/small_cstr.rs
index bc4d9cb..7349df0 100644
--- a/src/util/small_cstr.rs
+++ b/src/util/small_cstr.rs
@@ -100,6 +100,7 @@ impl std::fmt::Debug for SmallCString {
impl std::ops::Deref for SmallCString {
type Target = CStr;
+
#[inline]
fn deref(&self) -> &CStr {
self.as_cstr()
diff --git a/src/util/sqlite_string.rs b/src/util/sqlite_string.rs
index 18d462e..d131b56 100644
--- a/src/util/sqlite_string.rs
+++ b/src/util/sqlite_string.rs
@@ -130,9 +130,10 @@ impl SqliteMallocString {
// This is safe:
// - `align` is never 0
// - `align` is always a power of 2.
- // - `size` needs no realignment because it's guaranteed to be
- // aligned (everything is aligned to 1)
- // - `size` is also never zero, although this function doesn't actually require it now.
+ // - `size` needs no realignment because it's guaranteed to be aligned
+ // (everything is aligned to 1)
+ // - `size` is also never zero, although this function doesn't actually require
+ // it now.
let layout = Layout::from_size_align_unchecked(s.len().saturating_add(1), 1);
// Note: This call does not return.
handle_alloc_error(layout);
@@ -154,6 +155,7 @@ fn make_nonnull(v: &str) -> String {
}
impl Drop for SqliteMallocString {
+ #[inline]
fn drop(&mut self) {
unsafe { ffi::sqlite3_free(self.ptr.as_ptr().cast()) };
}
diff --git a/src/version.rs b/src/version.rs
index 215900b..6f56ee2 100644
--- a/src/version.rs
+++ b/src/version.rs
@@ -5,6 +5,7 @@ use std::ffi::CStr;
/// 3.16.2.
///
/// See [`sqlite3_libversion_number()`](https://www.sqlite.org/c3ref/libversion.html).
+#[inline]
pub fn version_number() -> i32 {
unsafe { ffi::sqlite3_libversion_number() }
}
@@ -12,6 +13,7 @@ pub fn version_number() -> i32 {
/// Returns the SQLite version as a string; e.g., `"3.16.2"` for version 3.16.2.
///
/// See [`sqlite3_libversion()`](https://www.sqlite.org/c3ref/libversion.html).
+#[inline]
pub fn version() -> &'static str {
let cstr = unsafe { CStr::from_ptr(ffi::sqlite3_libversion()) };
cstr.to_str()
diff --git a/src/vtab/array.rs b/src/vtab/array.rs
index 644b468..713604c 100644
--- a/src/vtab/array.rs
+++ b/src/vtab/array.rs
@@ -4,7 +4,7 @@
//! define.
//!
//! Port of [carray](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/carray.c)
-//! C extension: https://www.sqlite.org/carray.html
+//! C extension: `https://www.sqlite.org/carray.html`
//!
//! # Example
//!
@@ -18,7 +18,7 @@
//! // Note: A `Rc<Vec<Value>>` must be used as the parameter.
//! let values = Rc::new(v.iter().copied().map(Value::from).collect::<Vec<Value>>());
//! let mut stmt = db.prepare("SELECT value from rarray(?);")?;
-//! let rows = stmt.query_map(params![values], |row| row.get::<_, i64>(0))?;
+//! let rows = stmt.query_map([values], |row| row.get::<_, i64>(0))?;
//! for value in rows {
//! println!("{}", value?);
//! }
@@ -51,6 +51,7 @@ pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
pub type Array = Rc<Vec<Value>>;
impl ToSql for Array {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::Array(self.clone()))
}
@@ -156,7 +157,7 @@ impl ArrayTabCursor<'_> {
unsafe 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)?;
+ self.ptr = args.get_array(0);
} else {
self.ptr = None;
}
@@ -196,29 +197,30 @@ unsafe impl VTabCursor for ArrayTabCursor<'_> {
mod test {
use crate::types::Value;
use crate::vtab::array;
- use crate::Connection;
+ use crate::{Connection, Result};
use std::rc::Rc;
#[test]
- fn test_array_module() {
- let db = Connection::open_in_memory().unwrap();
- array::load_module(&db).unwrap();
+ fn test_array_module() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ array::load_module(&db)?;
let v = vec![1i64, 2, 3, 4];
let values: Vec<Value> = v.into_iter().map(Value::from).collect();
let ptr = Rc::new(values);
{
- let mut stmt = db.prepare("SELECT value from rarray(?);").unwrap();
+ let mut stmt = db.prepare("SELECT value from rarray(?);")?;
- let rows = stmt.query_map(&[&ptr], |row| row.get::<_, i64>(0)).unwrap();
+ let rows = stmt.query_map(&[&ptr], |row| row.get::<_, i64>(0))?;
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);
+ assert_eq!(i as i64, value? - 1);
count += 1;
}
assert_eq!(4, count);
}
assert_eq!(1, Rc::strong_count(&ptr));
+ Ok(())
}
}
diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs
index 79ec5da..096f272 100644
--- a/src/vtab/csvtab.rs
+++ b/src/vtab/csvtab.rs
@@ -1,7 +1,7 @@
//! `feature = "csvtab"` CSV Virtual Table.
//!
//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C
-//! extension: https://www.sqlite.org/csv.html
+//! extension: `https://www.sqlite.org/csv.html`
//!
//! # Example
//!
@@ -48,12 +48,12 @@ use crate::{Connection, Error, Result};
/// ```
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
- conn.create_module("csv", read_only_module::<CSVTab>(), aux)
+ conn.create_module("csv", read_only_module::<CsvTab>(), aux)
}
/// An instance of the CSV virtual table
#[repr(C)]
-struct CSVTab {
+struct CsvTab {
/// Base class. Must be first
base: ffi::sqlite3_vtab,
/// Name of the CSV file
@@ -65,7 +65,7 @@ struct CSVTab {
offset_first_row: csv::Position,
}
-impl CSVTab {
+impl CsvTab {
fn reader(&self) -> Result<csv::Reader<File>, csv::Error> {
csv::ReaderBuilder::new()
.has_headers(self.has_headers)
@@ -96,20 +96,20 @@ impl CSVTab {
}
}
-unsafe impl<'vtab> VTab<'vtab> for CSVTab {
+unsafe impl<'vtab> VTab<'vtab> for CsvTab {
type Aux = ();
- type Cursor = CSVTabCursor<'vtab>;
+ type Cursor = CsvTabCursor<'vtab>;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
args: &[&[u8]],
- ) -> Result<(String, CSVTab)> {
+ ) -> Result<(String, CsvTab)> {
if args.len() < 4 {
return Err(Error::ModuleError("no CSV file specified".to_owned()));
}
- let mut vtab = CSVTab {
+ let mut vtab = CsvTab {
base: ffi::sqlite3_vtab::default(),
filename: "".to_owned(),
has_headers: false,
@@ -122,7 +122,7 @@ unsafe impl<'vtab> VTab<'vtab> for CSVTab {
let args = &args[3..];
for c_slice in args {
- let (param, value) = CSVTab::parameter(c_slice)?;
+ let (param, value) = CsvTab::parameter(c_slice)?;
match param {
"filename" => {
if !Path::new(value).exists() {
@@ -166,7 +166,7 @@ unsafe impl<'vtab> VTab<'vtab> for CSVTab {
}
}
"delimiter" => {
- if let Some(b) = CSVTab::parse_byte(value) {
+ if let Some(b) = CsvTab::parse_byte(value) {
vtab.delimiter = b;
} else {
return Err(Error::ModuleError(format!(
@@ -176,7 +176,7 @@ unsafe impl<'vtab> VTab<'vtab> for CSVTab {
}
}
"quote" => {
- if let Some(b) = CSVTab::parse_byte(value) {
+ if let Some(b) = CsvTab::parse_byte(value) {
if b == b'0' {
vtab.quote = 0;
} else {
@@ -259,16 +259,16 @@ unsafe impl<'vtab> VTab<'vtab> for CSVTab {
Ok(())
}
- fn open(&self) -> Result<CSVTabCursor<'_>> {
- Ok(CSVTabCursor::new(self.reader()?))
+ fn open(&self) -> Result<CsvTabCursor<'_>> {
+ Ok(CsvTabCursor::new(self.reader()?))
}
}
-impl CreateVTab<'_> for CSVTab {}
+impl CreateVTab<'_> for CsvTab {}
/// A cursor for the CSV virtual table
#[repr(C)]
-struct CSVTabCursor<'vtab> {
+struct CsvTabCursor<'vtab> {
/// Base class. Must be first
base: ffi::sqlite3_vtab_cursor,
/// The CSV reader object
@@ -278,12 +278,12 @@ struct CSVTabCursor<'vtab> {
/// Values of the current row
cols: csv::StringRecord,
eof: bool,
- phantom: PhantomData<&'vtab CSVTab>,
+ phantom: PhantomData<&'vtab CsvTab>,
}
-impl CSVTabCursor<'_> {
- fn new<'vtab>(reader: csv::Reader<File>) -> CSVTabCursor<'vtab> {
- CSVTabCursor {
+impl CsvTabCursor<'_> {
+ fn new<'vtab>(reader: csv::Reader<File>) -> CsvTabCursor<'vtab> {
+ CsvTabCursor {
base: ffi::sqlite3_vtab_cursor::default(),
reader,
row_number: 0,
@@ -294,12 +294,12 @@ impl CSVTabCursor<'_> {
}
/// Accessor to the associated virtual table.
- fn vtab(&self) -> &CSVTab {
- unsafe { &*(self.base.pVtab as *const CSVTab) }
+ fn vtab(&self) -> &CsvTab {
+ unsafe { &*(self.base.pVtab as *const CsvTab) }
}
}
-unsafe impl VTabCursor for CSVTabCursor<'_> {
+unsafe impl VTabCursor for CsvTabCursor<'_> {
// Only a full table scan is supported. So `filter` simply rewinds to
// the beginning.
fn filter(
@@ -354,6 +354,7 @@ unsafe impl VTabCursor for CSVTabCursor<'_> {
}
impl From<csv::Error> for Error {
+ #[cold]
fn from(err: csv::Error) -> Error {
Error::ModuleError(err.to_string())
}
@@ -362,53 +363,45 @@ impl From<csv::Error> for Error {
#[cfg(test)]
mod test {
use crate::vtab::csvtab;
- use crate::{Connection, Result, NO_PARAMS};
+ use crate::{Connection, Result};
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();
+ fn test_csv_module() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ csvtab::load_module(&db)?;
+ db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")?;
{
- let mut s = db.prepare("SELECT rowid, * FROM vtab").unwrap();
+ let mut s = db.prepare("SELECT rowid, * FROM vtab")?;
{
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>();
+ let ids: Result<Vec<i32>> = s.query([])?.map(|row| row.get::<_, i32>(0)).collect();
+ let sum = ids?.iter().sum::<i32>();
assert_eq!(sum, 15);
}
- db.execute_batch("DROP TABLE vtab").unwrap();
+ db.execute_batch("DROP TABLE vtab")
}
#[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();
+ fn test_csv_cursor() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ csvtab::load_module(&db)?;
+ db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")?;
{
- let mut s = db
- .prepare(
- "SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
+ 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();
+ let mut rows = s.query([])?;
+ let row = rows.next()?.unwrap();
assert_eq!(row.get_unwrap::<_, i32>(0), 2);
}
- db.execute_batch("DROP TABLE vtab").unwrap();
+ db.execute_batch("DROP TABLE vtab")
}
}
diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs
index dc3bda6..f364f1f 100644
--- a/src/vtab/mod.rs
+++ b/src/vtab/mod.rs
@@ -1,10 +1,10 @@
//! `feature = "vtab"` Create virtual tables.
//!
//! Follow these steps to create your own virtual table:
-//! 1. Write implemenation of `VTab` and `VTabCursor` traits.
-//! 2. Create an instance of the `Module` structure specialized for `VTab` impl.
-//! from step 1.
-//! 3. Register your `Module` structure using `Connection.create_module`.
+//! 1. Write implementation of [`VTab`] and [`VTabCursor`] traits.
+//! 2. Create an instance of the [`Module`] structure specialized for [`VTab`]
+//! impl. from step 1.
+//! 3. Register your [`Module`] structure using [`Connection::create_module`].
//! 4. Run a `CREATE VIRTUAL TABLE` command that specifies the new module in the
//! `USING` clause.
//!
@@ -205,7 +205,7 @@ impl VTabConnection {
///
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
pub unsafe trait VTab<'vtab>: Sized {
- /// Client data passed to `Connection::create_module`.
+ /// Client data passed to [`Connection::create_module`].
type Aux;
/// Specific cursor implementation
type Cursor: VTabCursor;
@@ -237,7 +237,7 @@ pub trait CreateVTab<'vtab>: VTab<'vtab> {
/// database connection that is executing the CREATE VIRTUAL TABLE
/// statement.
///
- /// Call `connect` by default.
+ /// Call [`connect`](VTab::connect) by default.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xcreate_method))
fn create(
db: &mut VTabConnection,
@@ -248,7 +248,7 @@ pub trait CreateVTab<'vtab>: VTab<'vtab> {
}
/// Destroy the underlying table implementation. This method undoes the work
- /// of `create`.
+ /// of [`create`](CreateVTab::create).
///
/// Do nothing by default.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xdestroy_method))
@@ -261,7 +261,7 @@ pub trait CreateVTab<'vtab>: VTab<'vtab> {
/// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details.
#[derive(Debug, PartialEq)]
#[allow(non_snake_case, non_camel_case_types, missing_docs)]
-#[non_exhaustive]
+#[allow(clippy::upper_case_acronyms)]
pub enum IndexConstraintOp {
SQLITE_INDEX_CONSTRAINT_EQ,
SQLITE_INDEX_CONSTRAINT_GT,
@@ -303,13 +303,14 @@ impl From<u8> for IndexConstraintOp {
}
/// `feature = "vtab"` Pass information into and receive the reply from the
-/// `VTab.best_index` method.
+/// [`VTab::best_index`] method.
///
/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
pub struct IndexInfo(*mut ffi::sqlite3_index_info);
impl IndexInfo {
/// Record WHERE clause constraints.
+ #[inline]
pub fn constraints(&self) -> IndexConstraintIter<'_> {
let constraints =
unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
@@ -319,6 +320,7 @@ impl IndexInfo {
}
/// Information about the ORDER BY clause.
+ #[inline]
pub fn order_bys(&self) -> OrderByIter<'_> {
let order_bys =
unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) };
@@ -328,11 +330,13 @@ impl IndexInfo {
}
/// Number of terms in the ORDER BY clause
+ #[inline]
pub fn num_of_order_by(&self) -> usize {
unsafe { (*self.0).nOrderBy as usize }
}
- /// Information about what parameters to pass to `VTabCursor.filter`.
+ /// Information about what parameters to pass to [`VTabCursor::filter`].
+ #[inline]
pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage<'_> {
let constraint_usages = unsafe {
slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
@@ -341,6 +345,7 @@ impl IndexInfo {
}
/// Number used to identify the index
+ #[inline]
pub fn set_idx_num(&mut self, idx_num: c_int) {
unsafe {
(*self.0).idxNum = idx_num;
@@ -348,6 +353,7 @@ impl IndexInfo {
}
/// True if output is already ordered
+ #[inline]
pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
unsafe {
(*self.0).orderByConsumed = if order_by_consumed { 1 } else { 0 };
@@ -355,6 +361,7 @@ impl IndexInfo {
}
/// Estimated cost of using this index
+ #[inline]
pub fn set_estimated_cost(&mut self, estimated_ost: f64) {
unsafe {
(*self.0).estimatedCost = estimated_ost;
@@ -363,6 +370,7 @@ impl IndexInfo {
/// Estimated number of rows returned.
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.8.2
+ #[inline]
pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
unsafe {
(*self.0).estimatedRows = estimated_rows;
@@ -383,10 +391,12 @@ pub struct IndexConstraintIter<'a> {
impl<'a> Iterator for IndexConstraintIter<'a> {
type Item = IndexConstraint<'a>;
+ #[inline]
fn next(&mut self) -> Option<IndexConstraint<'a>> {
self.iter.next().map(|raw| IndexConstraint(raw))
}
+ #[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
@@ -397,32 +407,38 @@ pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
impl IndexConstraint<'_> {
/// Column constrained. -1 for ROWID
+ #[inline]
pub fn column(&self) -> c_int {
self.0.iColumn
}
/// Constraint operator
+ #[inline]
pub fn operator(&self) -> IndexConstraintOp {
IndexConstraintOp::from(self.0.op)
}
/// True if this constraint is usable
+ #[inline]
pub fn is_usable(&self) -> bool {
self.0.usable != 0
}
}
/// `feature = "vtab"` Information about what parameters to pass to
-/// `VTabCursor.filter`.
+/// [`VTabCursor::filter`].
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
impl IndexConstraintUsage<'_> {
- /// if `argv_index` > 0, constraint is part of argv to `VTabCursor.filter`
+ /// if `argv_index` > 0, constraint is part of argv to
+ /// [`VTabCursor::filter`]
+ #[inline]
pub fn set_argv_index(&mut self, argv_index: c_int) {
self.0.argvIndex = argv_index;
}
/// if `omit`, do not code a test for this constraint
+ #[inline]
pub fn set_omit(&mut self, omit: bool) {
self.0.omit = if omit { 1 } else { 0 };
}
@@ -436,10 +452,12 @@ pub struct OrderByIter<'a> {
impl<'a> Iterator for OrderByIter<'a> {
type Item = OrderBy<'a>;
+ #[inline]
fn next(&mut self) -> Option<OrderBy<'a>> {
self.iter.next().map(|raw| OrderBy(raw))
}
+ #[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
@@ -450,11 +468,13 @@ pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
impl OrderBy<'_> {
/// Column number
+ #[inline]
pub fn column(&self) -> c_int {
self.0.iColumn
}
/// True for DESC. False for ASC.
+ #[inline]
pub fn is_order_by_desc(&self) -> bool {
self.0.desc != 0
}
@@ -477,8 +497,8 @@ pub unsafe trait VTabCursor: Sized {
/// Begin a search of a virtual table.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xfilter_method))
fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Values<'_>) -> Result<()>;
- /// Advance cursor to the next row of a result set initiated by `filter`.
- /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method))
+ /// Advance cursor to the next row of a result set initiated by
+ /// [`filter`](VTabCursor::filter). (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method))
fn next(&mut self) -> Result<()>;
/// Must return `false` if the cursor currently points to a valid row of
/// data, or `true` otherwise.
@@ -494,12 +514,13 @@ pub unsafe trait VTabCursor: Sized {
fn rowid(&self) -> Result<i64>;
}
-/// `feature = "vtab"` Context is used by `VTabCursor.column` to specify the
+/// `feature = "vtab"` Context is used by [`VTabCursor::column`] to specify the
/// cell value.
pub struct Context(*mut ffi::sqlite3_context);
impl Context {
/// Set current cell value
+ #[inline]
pub fn set_result<T: ToSql>(&mut self, value: &T) -> Result<()> {
let t = value.to_sql()?;
unsafe { set_result(self.0, &t) };
@@ -509,19 +530,21 @@ impl Context {
// TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html)
}
-/// `feature = "vtab"` Wrapper to `VTabCursor.filter` arguments, the values
-/// requested by `VTab.best_index`.
+/// `feature = "vtab"` Wrapper to [`VTabCursor::filter`] arguments, the values
+/// requested by [`VTab::best_index`].
pub struct Values<'a> {
args: &'a [*mut ffi::sqlite3_value],
}
impl Values<'_> {
/// Returns the number of values.
+ #[inline]
pub fn len(&self) -> usize {
self.args.len()
}
/// Returns `true` if there is no value.
+ #[inline]
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
@@ -550,23 +573,24 @@ impl Values<'_> {
// `sqlite3_value_type` returns `SQLITE_NULL` for pointer.
// So it seems not possible to enhance `ValueRef::from_value`.
#[cfg(feature = "array")]
- fn get_array(&self, idx: usize) -> Result<Option<array::Array>> {
+ fn get_array(&self, idx: usize) -> Option<array::Array> {
use crate::types::Value;
let arg = self.args[idx];
let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) };
if ptr.is_null() {
- Ok(None)
+ None
} else {
- Ok(Some(unsafe {
+ Some(unsafe {
let rc = array::Array::from_raw(ptr as *const Vec<Value>);
let array = rc.clone();
array::Array::into_raw(rc); // don't consume it
array
- }))
+ })
}
}
/// Turns `Values` into an iterator.
+ #[inline]
pub fn iter(&self) -> ValueIter<'_> {
ValueIter {
iter: self.args.iter(),
@@ -578,12 +602,13 @@ impl<'a> IntoIterator for &'a Values<'a> {
type IntoIter = ValueIter<'a>;
type Item = ValueRef<'a>;
+ #[inline]
fn into_iter(self) -> ValueIter<'a> {
self.iter()
}
}
-/// `Values` iterator.
+/// [`Values`] iterator.
pub struct ValueIter<'a> {
iter: slice::Iter<'a, *mut ffi::sqlite3_value>,
}
@@ -591,12 +616,14 @@ pub struct ValueIter<'a> {
impl<'a> Iterator for ValueIter<'a> {
type Item = ValueRef<'a>;
+ #[inline]
fn next(&mut self) -> Option<ValueRef<'a>> {
self.iter
.next()
.map(|&raw| unsafe { ValueRef::from_value(raw) })
}
+ #[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
@@ -607,6 +634,7 @@ impl Connection {
///
/// Step 3 of [Creating New Virtual Table
/// Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
+ #[inline]
pub fn create_module<'vtab, T: VTab<'vtab>>(
&self,
module_name: &str,
@@ -652,7 +680,7 @@ impl InnerConnection {
}
}
-/// `feature = "vtab"` Escape double-quote (`"`) character occurences by
+/// `feature = "vtab"` Escape double-quote (`"`) character occurrences by
/// doubling them (`""`).
pub fn escape_double_quote(identifier: &str) -> Cow<'_, str> {
if identifier.contains('"') {
@@ -977,6 +1005,7 @@ where
/// Virtual table cursors can set an error message by assigning a string to
/// `zErrMsg`.
+#[cold]
unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
match result {
Ok(_) => ffi::SQLITE_OK,
@@ -995,6 +1024,7 @@ unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<
/// Virtual tables methods can set an error message by assigning a string to
/// `zErrMsg`.
+#[cold]
unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
if !(*vtab).zErrMsg.is_null() {
ffi::sqlite3_free((*vtab).zErrMsg as *mut c_void);
@@ -1004,6 +1034,7 @@ unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
/// To raise an error, the `column` method should use this method to set the
/// error message and return the error code.
+#[cold]
unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int {
match result {
Ok(_) => ffi::SQLITE_OK,
diff --git a/src/vtab/series.rs b/src/vtab/series.rs
index ed67f16..31ef86f 100644
--- a/src/vtab/series.rs
+++ b/src/vtab/series.rs
@@ -2,7 +2,7 @@
//!
//! Port of C [generate series
//! "function"](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/series.c):
-//! https://www.sqlite.org/series.html
+//! `https://www.sqlite.org/series.html`
use std::default::Default;
use std::marker::PhantomData;
use std::os::raw::c_int;
@@ -13,7 +13,7 @@ use crate::vtab::{
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConnection, VTabCursor,
Values,
};
-use crate::{Connection, Result};
+use crate::{Connection, Error, Result};
/// `feature = "series"` Register the "generate_series" module.
pub fn load_module(conn: &Connection) -> Result<()> {
@@ -38,6 +38,8 @@ bitflags::bitflags! {
const STEP = 4;
// output in descending order
const DESC = 8;
+ // output in descending order
+ const ASC = 16;
// Both start and stop
const BOTH = QueryPlanFlags::START.bits | QueryPlanFlags::STOP.bits;
}
@@ -71,54 +73,42 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
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;
+ // Mask of unusable constraints
+ let mut unusable_mask: QueryPlanFlags = QueryPlanFlags::empty();
+ // Constraints on start, stop, and step
+ let mut a_idx: [Option<usize>; 3] = [None, None, None];
for (i, constraint) in info.constraints().enumerate() {
- if !constraint.is_usable() {
+ if constraint.column() < SERIES_COLUMN_START {
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;
+ let (i_col, i_mask) = match constraint.column() {
+ SERIES_COLUMN_START => (0, QueryPlanFlags::START),
+ SERIES_COLUMN_STOP => (1, QueryPlanFlags::STOP),
+ SERIES_COLUMN_STEP => (2, QueryPlanFlags::STEP),
+ _ => {
+ unreachable!()
}
- SERIES_COLUMN_STEP => {
- step_idx = Some(i);
- idx_num |= QueryPlanFlags::STEP;
- }
- _ => {}
};
+ if !constraint.is_usable() {
+ unusable_mask |= i_mask;
+ } else if constraint.operator() == IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
+ idx_num |= i_mask;
+ a_idx[i_col] = Some(i);
+ }
}
-
- 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);
+ // Number of arguments that SeriesTabCursor::filter expects
+ let mut n_arg = 0;
+ for j in a_idx.iter().flatten() {
+ n_arg += 1;
+ let mut constraint_usage = info.constraint_usage(*j);
+ constraint_usage.set_argv_index(n_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 !(unusable_mask & !idx_num).is_empty() {
+ return Err(Error::SqliteFailure(
+ ffi::Error::new(ffi::SQLITE_CONSTRAINT),
+ None,
+ ));
}
if idx_num.contains(QueryPlanFlags::BOTH) {
// Both start= and stop= boundaries are available.
@@ -135,6 +125,8 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
if let Some(order_by) = order_bys.next() {
if order_by.is_order_by_desc() {
idx_num |= QueryPlanFlags::DESC;
+ } else {
+ idx_num |= QueryPlanFlags::ASC;
}
true
} else {
@@ -145,7 +137,9 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
info.set_order_by_consumed(true);
}
} else {
- info.set_estimated_cost(2_147_483_647f64);
+ // If either boundary is missing, we have to generate a huge span
+ // of numbers. Make this case very expensive so that the query
+ // planner will work hard to avoid it.
info.set_estimated_rows(2_147_483_647);
}
info.set_idx_num(idx_num.bits());
@@ -168,7 +162,7 @@ struct SeriesTabCursor<'vtab> {
row_id: i64,
/// Current value ("value")
value: i64,
- /// Mimimum value ("start")
+ /// Minimum value ("start")
min_value: i64,
/// Maximum value ("stop")
max_value: i64,
@@ -191,9 +185,10 @@ impl SeriesTabCursor<'_> {
}
}
}
+#[allow(clippy::comparison_chain)]
unsafe 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 idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
let mut i = 0;
if idx_num.contains(QueryPlanFlags::START) {
self.min_value = args.get(i)?;
@@ -209,8 +204,13 @@ unsafe impl VTabCursor for SeriesTabCursor<'_> {
}
if idx_num.contains(QueryPlanFlags::STEP) {
self.step = args.get(i)?;
- if self.step < 1 {
+ if self.step == 0 {
self.step = 1;
+ } else if self.step < 0 {
+ self.step = -self.step;
+ if !idx_num.contains(QueryPlanFlags::ASC) {
+ idx_num |= QueryPlanFlags::DESC;
+ }
}
} else {
self.step = 1;
@@ -273,26 +273,40 @@ unsafe impl VTabCursor for SeriesTabCursor<'_> {
mod test {
use crate::ffi;
use crate::vtab::series;
- use crate::{Connection, NO_PARAMS};
+ use crate::{Connection, Result};
+ use fallible_iterator::FallibleIterator;
#[test]
- fn test_series_module() {
+ fn test_series_module() -> Result<()> {
let version = unsafe { ffi::sqlite3_libversion_number() };
if version < 3_008_012 {
- return;
+ return Ok(());
}
- let db = Connection::open_in_memory().unwrap();
- series::load_module(&db).unwrap();
+ let db = Connection::open_in_memory()?;
+ series::load_module(&db)?;
- let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)").unwrap();
+ let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)")?;
- let series = s.query_map(NO_PARAMS, |row| row.get::<_, i32>(0)).unwrap();
+ let series = s.query_map([], |row| row.get::<_, i32>(0))?;
let mut expected = 0;
for value in series {
- assert_eq!(expected, value.unwrap());
+ assert_eq!(expected, value?);
expected += 5;
}
+
+ let mut s =
+ db.prepare("SELECT * FROM generate_series WHERE start=1 AND stop=9 AND step=2")?;
+ let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
+ assert_eq!(vec![1, 3, 5, 7, 9], series);
+ let mut s = db.prepare("SELECT * FROM generate_series LIMIT 5")?;
+ let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
+ assert_eq!(vec![0, 1, 2, 3, 4], series);
+ let mut s = db.prepare("SELECT * FROM generate_series(0,32,5) ORDER BY value DESC")?;
+ let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
+ assert_eq!(vec![30, 25, 20, 15, 10, 5, 0], series);
+
+ Ok(())
}
}
diff --git a/tests/vtab.rs b/tests/vtab.rs
index 4b31574..5c5bdef 100644
--- a/tests/vtab.rs
+++ b/tests/vtab.rs
@@ -2,8 +2,7 @@
#[cfg(feature = "vtab")]
#[test]
-fn test_dummy_module() {
- use rusqlite::types::ToSql;
+fn test_dummy_module() -> rusqlite::Result<()> {
use rusqlite::vtab::{
eponymous_only_module, sqlite3_vtab, sqlite3_vtab_cursor, Context, IndexInfo, VTab,
VTabConnection, VTabCursor, Values,
@@ -84,20 +83,18 @@ fn test_dummy_module() {
}
}
- let db = Connection::open_in_memory().unwrap();
+ let db = Connection::open_in_memory()?;
- db.create_module::<DummyTab>("dummy", &module, None)
- .unwrap();
+ db.create_module::<DummyTab>("dummy", &module, None)?;
let version = version_number();
if version < 3_008_012 {
- return;
+ return Ok(());
}
- let mut s = db.prepare("SELECT * FROM dummy()").unwrap();
+ let mut s = db.prepare("SELECT * FROM dummy()")?;
- let dummy = s
- .query_row(&[] as &[&dyn ToSql], |row| row.get::<_, i32>(0))
- .unwrap();
+ let dummy = s.query_row([], |row| row.get::<_, i32>(0))?;
assert_eq!(1, dummy);
+ Ok(())
}