aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-10 07:04:24 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-10 07:04:24 +0000
commita34dd32ebb7129bc4f139e2b7156b7813bb346d3 (patch)
treeed3dae2c91a4d67fe43675ec1ee9eaf437cd8280
parent1331359ccffa598a950a32edf7e1272a09f8845a (diff)
parent1f78690480e043c9a3bcc5ce5b11778280ddf3ad (diff)
downloadrusqlite-android13-mainline-extservices-release.tar.gz
Snap for 8564071 from 1f78690480e043c9a3bcc5ce5b11778280ddf3ad to mainline-extservices-releaseaml_ext_331814220aml_ext_331412000aml_ext_331312000aml_ext_331112010aml_ext_331012020android13-mainline-extservices-release
Change-Id: I7f28cc7749da81e10941ddfda366ad402b72b13c
-rw-r--r--.cargo_vcs_info.json7
-rw-r--r--.gitattributes1
-rw-r--r--.github/workflows/main.yml153
-rw-r--r--.travis.yml48
-rw-r--r--Android.bp43
-rw-r--r--Cargo.toml124
-rw-r--r--Cargo.toml.orig52
-rw-r--r--Changelog.md332
-rw-r--r--LICENSE2
-rw-r--r--METADATA10
-rw-r--r--README.md85
-rw-r--r--TEST_MAPPING19
-rw-r--r--appveyor.yml42
-rw-r--r--benches/exec.rs4
-rw-r--r--cargo2android-extra-module.bp21
-rw-r--r--cargo2android.json6
-rw-r--r--clippy.toml1
-rw-r--r--codecov.yml11
-rwxr-xr-xpublish-ghp-docs.sh14
-rw-r--r--src/backup.rs172
-rw-r--r--src/blob/mod.rs118
-rw-r--r--src/blob/pos_io.rs43
-rw-r--r--src/busy.rs54
-rw-r--r--src/cache.rs150
-rw-r--r--src/collation.rs65
-rw-r--r--src/column.rs161
-rw-r--r--src/config.rs65
-rw-r--r--src/context.rs8
-rw-r--r--src/error.rs66
-rw-r--r--src/functions.rs379
-rw-r--r--src/hooks.rs625
-rw-r--r--src/inner_connection.rs192
-rw-r--r--src/lib.rs961
-rw-r--r--src/limits.rs119
-rw-r--r--src/load_extension_guard.rs22
-rw-r--r--src/params.rs358
-rw-r--r--src/pragma.rs149
-rw-r--r--src/raw_statement.rs78
-rw-r--r--src/row.rs223
-rw-r--r--src/session.rs308
-rw-r--r--src/statement.rs923
-rw-r--r--src/trace.rs54
-rw-r--r--src/transaction.rs248
-rw-r--r--src/types/chrono.rs267
-rw-r--r--src/types/from_sql.rs162
-rw-r--r--src/types/mod.rs294
-rw-r--r--src/types/serde_json.rs39
-rw-r--r--src/types/time.rs148
-rw-r--r--src/types/to_sql.rs117
-rw-r--r--src/types/url.rs29
-rw-r--r--src/types/value.rs30
-rw-r--r--src/types/value_ref.rs103
-rw-r--r--src/unlock_notify.rs44
-rw-r--r--src/util/small_cstr.rs7
-rw-r--r--src/util/sqlite_string.rs26
-rw-r--r--src/version.rs4
-rw-r--r--src/vtab/array.rs47
-rw-r--r--src/vtab/csvtab.rs97
-rw-r--r--src/vtab/mod.rs234
-rw-r--r--src/vtab/series.rs136
-rw-r--r--tests/vtab.rs17
61 files changed, 4928 insertions, 3389 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index deaa273..de52e1b 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,6 @@
{
"git": {
- "sha1": "cef6dbbb26211baebedbebe6e114f5bcf9be2431"
- }
-}
+ "sha1": "8141b5e085bd3a02951588413e5569f1ddf17a8c"
+ },
+ "path_in_vcs": ""
+} \ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index c16503f..0000000
--- a/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-libsqlite3-sys/sqlite3/* linguist-vendored
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index 38c28c9..0000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,153 +0,0 @@
-name: CI
-
-on:
- push:
- branches:
- - master
- pull_request:
- branches:
- - master
- schedule:
- - cron: '00 01 * * *'
-env:
- RUST_BACKTRACE: 1
-jobs:
- test:
- name: Test ${{ matrix.target }}
-
- strategy:
- fail-fast: false
-
- matrix:
- include:
- - { target: x86_64-pc-windows-msvc, os: windows-latest }
- - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }
- - { target: x86_64-apple-darwin, os: macos-latest }
- - { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
-
- runs-on: ${{ matrix.os }}
-
- steps:
- - uses: actions/checkout@v2
- # This has a matcher for test panics, so we use it even though elsewhere
- # we use actions-rs/toolchain.
- - uses: hecrj/setup-rust-action@v1
- with:
- rust-version: stable${{ matrix.host }}
- targets: ${{ matrix.target }}
-
- - run: cargo build --features bundled --workspace --all-targets --verbose
- - run: cargo test --features bundled --workspace --all-targets --verbose
- - run: cargo test --features bundled --workspace --doc --verbose
-
- - name: Test Features
- # TODO: clang is installed on these -- but `bindgen` can't find it...
- if: matrix.os != 'windows-latest'
- run: |
- cargo test --features 'bundled-full session buildtime_bindgen time' --all-targets --workspace --verbose
- cargo test --features 'bundled-full session buildtime_bindgen time' --doc --workspace --verbose
-
- - name: Static build
- # Do we expect this to work / should we test with gnu toolchain?
- if: matrix.os == 'x86_64-pc-windows-msvc'
- env:
- RUSTFLAGS: -Ctarget-feature=+crt-static
- run: cargo build --features bundled
-
- winsqlite3:
- name: Test with winsqlite3
- runs-on: windows-latest
- steps:
- - uses: actions/checkout@v2
- - uses: hecrj/setup-rust-action@v1
- # TODO: Should this test GNU toolchain? What about +crt-static?
- # TODO: Is it worth testing other features?
- - run: cargo build --features winsqlite3 --workspace --all-targets --verbose
- - run: cargo test --features winsqlite3 --workspace --all-targets --verbose
-
- sqlcipher:
- name: Test with sqlcipher
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: hecrj/setup-rust-action@v1
- - run: sudo apt-get install sqlcipher libsqlcipher-dev
- - run: sqlcipher --version
- # TODO: Is it worth testing other features?
- - run: cargo build --features sqlcipher --workspace --all-targets --verbose
- - run: cargo test --features sqlcipher --workspace --all-targets --verbose
-
- sanitizer:
- name: Address Sanitizer
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- # Need nightly rust.
- - uses: hecrj/setup-rust-action@v1
- with:
- rust-version: nightly
- components: rust-src
- - name: Tests with asan
- env:
- RUSTFLAGS: -Zsanitizer=address
- RUSTDOCFLAGS: -Zsanitizer=address
- ASAN_OPTIONS: 'detect_stack_use_after_return=1'
- # 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
- # as the other tests have them.
- RUST_BACKTRACE: '0'
- run: cargo -Z build-std test --features 'bundled-full session buildtime_bindgen time with-asan' --target x86_64-unknown-linux-gnu
-
- # Ensure clippy doesn't complain.
- clippy:
- name: Clippy
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: hecrj/setup-rust-action@v1
- with:
- components: clippy
- - run: cargo clippy --all-targets --workspace --features bundled -- -D warnings
- # Clippy with all non-conflicting features
- - run: cargo clippy --all-targets --workspace --features 'bundled-full session buildtime_bindgen time' -- -D warnings
-
- # Ensure patch is formatted.
- fmt:
- name: Format
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: hecrj/setup-rust-action@v1
- with:
- components: rustfmt
- - run: cargo fmt --all -- --check
-
- # Detect cases where documentation links don't resolve and such.
- doc:
- name: Docs
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: hecrj/setup-rust-action@v1
- with:
- rust-version: nightly
- # Need to use `cargo rustdoc` to actually get it to respect -D
- # warnings... Note: this also requires nightly.
- - run: cargo rustdoc --features 'bundled-full session buildtime_bindgen time' -- -D warnings
-
- codecov:
- name: Generate code coverage
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: hecrj/setup-rust-action@v1
- - name: Run cargo-tarpaulin
- uses: actions-rs/tarpaulin@v0.1
- with:
- # Intentionally omit time feature until we're on time 0.3, at which
- # point it should be added to `bundled-full`.
- args: '--features "bundled-full session buildtime_bindgen"'
-
- - name: Upload to codecov.io
- uses: codecov/codecov-action@v1
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..d5c31aa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,4 +1,5 @@
-// This file is generated by cargo2android.py --device --run --dependencies --features=modern_sqlite.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
package {
default_applicable_licenses: ["external_rust_crates_rusqlite_license"],
@@ -21,9 +22,14 @@ rust_library {
name: "librusqlite",
host_supported: true,
crate_name: "rusqlite",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.27.0",
srcs: ["src/lib.rs"],
edition: "2018",
- features: ["modern_sqlite"],
+ features: [
+ "modern_sqlite",
+ "trace",
+ ],
rustlibs: [
"libbitflags",
"libfallible_iterator",
@@ -35,14 +41,25 @@ rust_library {
],
}
-// dependent_library ["feature_list"]
-// ahash-0.4.6
-// bitflags-1.2.1 "default"
-// 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"
-// pkg-config-0.3.19
-// smallvec-1.5.1
+rust_library {
+ name: "librusqlite_noicu",
+ host_supported: true,
+ crate_name: "rusqlite",
+ cargo_env_compat: true,
+ srcs: ["src/lib.rs"],
+ edition: "2018",
+ features: [
+ "modern_sqlite",
+ "trace",
+ ],
+ rustlibs: [
+ "libbitflags",
+ "libfallible_iterator",
+ "libfallible_streaming_iterator",
+ "libhashlink",
+ "liblibsqlite3_sys_noicu",
+ "libmemchr",
+ "libsmallvec",
+ ],
+}
+
diff --git a/Cargo.toml b/Cargo.toml
index 8efebab..f8b8dda 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,34 +3,50 @@
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
-# to registry (e.g., crates.io) dependencies
+# to registry (e.g., crates.io) dependencies.
#
-# If you believe there's an error in this file please file an
-# issue against the rust-lang/cargo repository. If you're
-# editing this file be aware that the upstream Cargo.toml
-# will likely look very different (and much more reasonable)
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "rusqlite"
-version = "0.24.2"
+version = "0.27.0"
authors = ["The rusqlite developers"]
+exclude = [
+ "/.github/*",
+ "/.gitattributes",
+ "/appveyor.yml",
+ "/Changelog.md",
+ "/clippy.toml",
+ "/codecov.yml",
+]
description = "Ergonomic wrapper for SQLite"
documentation = "http://docs.rs/rusqlite/"
readme = "README.md"
-keywords = ["sqlite", "database", "ffi"]
+keywords = [
+ "sqlite",
+ "database",
+ "ffi",
+]
categories = ["database"]
license = "MIT"
repository = "https://github.com/rusqlite/rusqlite"
+
[package.metadata.docs.rs]
+features = ["modern-full"]
all-features = false
-default-target = "x86_64-unknown-linux-gnu"
-features = ["array", "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "time", "trace", "url", "vtab", "window", "modern_sqlite", "column_decltype"]
no-default-features = true
+default-target = "x86_64-unknown-linux-gnu"
+rustdoc-args = [
+ "--cfg",
+ "docsrs",
+]
[package.metadata.playground]
-all-features = false
features = ["bundled-full"]
+all-features = false
[lib]
name = "rusqlite"
@@ -52,17 +68,15 @@ harness = false
[[bench]]
name = "exec"
harness = false
+
[dependencies.bitflags]
version = "1.2"
-[dependencies.byteorder]
-version = "1.3"
-features = ["i128"]
-optional = true
-
[dependencies.chrono]
version = "0.4"
+features = ["clock"]
optional = true
+default-features = false
[dependencies.csv]
version = "1.1"
@@ -75,14 +89,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.24.0"
[dependencies.memchr]
version = "2.3"
@@ -92,10 +106,15 @@ version = "1.0"
optional = true
[dependencies.smallvec]
-version = "1.0"
+version = "1.6.1"
[dependencies.time]
-version = "0.2"
+version = "0.3.0"
+features = [
+ "formatting",
+ "macros",
+ "parsing",
+]
optional = true
[dependencies.url]
@@ -105,6 +124,7 @@ optional = true
[dependencies.uuid]
version = "0.8"
optional = true
+
[dev-dependencies.bencher]
version = "0.1"
@@ -132,30 +152,79 @@ array = ["vtab"]
backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
-bundled = ["libsqlite3-sys/bundled", "modern_sqlite"]
-bundled-full = ["array", "backup", "blob", "bundled", "chrono", "collation", "column_decltype", "csvtab", "extra_check", "functions", "hooks", "i128_blob", "limits", "load_extension", "serde_json", "series", "trace", "unlock_notify", "url", "uuid", "vtab", "window"]
+bundled = [
+ "libsqlite3-sys/bundled",
+ "modern_sqlite",
+]
+bundled-full = [
+ "modern-full",
+ "bundled",
+]
+bundled-sqlcipher = [
+ "libsqlite3-sys/bundled-sqlcipher",
+ "bundled",
+]
+bundled-sqlcipher-vendored-openssl = [
+ "libsqlite3-sys/bundled-sqlcipher-vendored-openssl",
+ "bundled-sqlcipher",
+]
bundled-windows = ["libsqlite3-sys/bundled-windows"]
collation = []
column_decltype = []
-csvtab = ["csv", "vtab"]
+csvtab = [
+ "csv",
+ "vtab",
+]
extra_check = []
functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
hooks = []
-i128_blob = ["byteorder"]
-in_gecko = ["modern_sqlite", "libsqlite3-sys/in_gecko"]
+i128_blob = []
+in_gecko = [
+ "modern_sqlite",
+ "libsqlite3-sys/in_gecko",
+]
limits = []
load_extension = []
+modern-full = [
+ "array",
+ "backup",
+ "blob",
+ "modern_sqlite",
+ "chrono",
+ "collation",
+ "column_decltype",
+ "csvtab",
+ "extra_check",
+ "functions",
+ "hooks",
+ "i128_blob",
+ "limits",
+ "load_extension",
+ "serde_json",
+ "series",
+ "time",
+ "trace",
+ "unlock_notify",
+ "url",
+ "uuid",
+ "vtab",
+ "window",
+]
modern_sqlite = ["libsqlite3-sys/bundled_bindings"]
series = ["vtab"]
-session = ["libsqlite3-sys/session", "hooks"]
+session = [
+ "libsqlite3-sys/session",
+ "hooks",
+]
sqlcipher = ["libsqlite3-sys/sqlcipher"]
trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
unlock_notify = ["libsqlite3-sys/unlock_notify"]
-vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"]
+vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
window = ["functions"]
winsqlite3 = ["libsqlite3-sys/winsqlite3"]
with-asan = ["libsqlite3-sys/with-asan"]
+
[badges.appveyor]
repository = "rusqlite/rusqlite"
@@ -164,6 +233,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..6ab1a7e 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
[package]
name = "rusqlite"
-version = "0.24.2"
+version = "0.27.0"
authors = ["The rusqlite developers"]
edition = "2018"
description = "Ergonomic wrapper for SQLite"
@@ -11,8 +11,16 @@ keywords = ["sqlite", "database", "ffi"]
license = "MIT"
categories = ["database"]
+exclude = [
+ "/.github/*",
+ "/.gitattributes",
+ "/appveyor.yml",
+ "/Changelog.md",
+ "/clippy.toml",
+ "/codecov.yml",
+]
+
[badges]
-travis-ci = { repository = "rusqlite/rusqlite" }
appveyor = { repository = "rusqlite/rusqlite" }
codecov = { repository = "rusqlite/rusqlite" }
maintenance = { status = "actively-developed" }
@@ -35,14 +43,16 @@ functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
# sqlite3_log: 3.6.23 (2010-03-09)
trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
bundled = ["libsqlite3-sys/bundled", "modern_sqlite"]
+bundled-sqlcipher = ["libsqlite3-sys/bundled-sqlcipher", "bundled"]
+bundled-sqlcipher-vendored-openssl = ["libsqlite3-sys/bundled-sqlcipher-vendored-openssl", "bundled-sqlcipher"]
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
limits = []
hooks = []
-i128_blob = ["byteorder"]
+i128_blob = []
sqlcipher = ["libsqlite3-sys/sqlcipher"]
unlock_notify = ["libsqlite3-sys/unlock_notify"]
# xSavepoint, xRelease and xRollbackTo: 3.7.7 (2011-06-23)
-vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"]
+vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
csvtab = ["csv", "vtab"]
# pointer passing interfaces: 3.20.0
array = ["vtab"]
@@ -63,15 +73,15 @@ 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.
-bundled-full = [
+# Helper feature for enabling 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.
+modern-full = [
"array",
"backup",
"blob",
- "bundled",
+ "modern_sqlite",
"chrono",
"collation",
"column_decltype",
@@ -84,9 +94,7 @@ bundled-full = [
"load_extension",
"serde_json",
"series",
- # time v0.2 does not work with tarpaulin v0.14.0. See time-rs/time#265.
- # Re-enable when time v0.3 is released with the fix.
- # "time",
+ "time",
"trace",
"unlock_notify",
"url",
@@ -95,21 +103,22 @@ bundled-full = [
"window",
]
+bundled-full = ["modern-full", "bundled"]
+
[dependencies]
-time = { version = "0.2", optional = true }
+time = { version = "0.3.0", features = ["formatting", "macros", "parsing"], optional = true }
bitflags = "1.2"
-hashlink = "0.6"
-chrono = { version = "0.4", optional = true }
+hashlink = "0.7"
+chrono = { version = "0.4", optional = true, default-features = false, features = ["clock"] }
serde_json = { version = "1.0", optional = true }
csv = { version = "1.1", optional = true }
url = { version = "2.1", optional = true }
lazy_static = { version = "1.4", optional = true }
-byteorder = { version = "1.3", features = ["i128"], optional = true }
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 +127,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.24.0"
[[test]]
name = "config_log"
@@ -145,10 +154,11 @@ name = "exec"
harness = false
[package.metadata.docs.rs]
-features = [ "array", "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "time", "trace", "url", "vtab", "window", "modern_sqlite", "column_decltype" ]
+features = ["modern-full"]
all-features = false
no-default-features = true
default-target = "x86_64-unknown-linux-gnu"
+rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.playground]
features = ["bundled-full"]
diff --git a/Changelog.md b/Changelog.md
deleted file mode 100644
index 6ba11d7..0000000
--- a/Changelog.md
+++ /dev/null
@@ -1,332 +0,0 @@
-For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlite/releases) page.
-
-# Version 0.14.0 (2018-08-17)
-
-* BREAKING CHANGE: `ToSql` implementation for `time::Timespec` uses RFC 3339 (%Y-%m-%dT%H:%M:%S.%fZ).
- Previous format was %Y-%m-%d %H:%M:%S:%f %Z.
-* BREAKING CHANGE: Remove potentially conflicting impl of ToSqlOutput (#313).
-* BREAKING CHANGE: Replace column index/count type (i32) with usize.
-* BREAKING CHANGE: Replace parameter index/count type (i32) with usize.
-* BREAKING CHANGE: Replace row changes/count type (i32) with usize.
-* BREAKING CHANGE: Scalar functions must be `Send`able and `'static`.
-* Bugfix: Commit failure unhandled, database left in unusable state (#366).
-* Bugfix: `free_boxed_hook` does not work for `fn`.
-* Update the bundled SQLite version to 3.24.0 (#326).
-* Add DropBehavior::Panic to enforce intentional commit or rollback.
-* Implement `sqlite3_update_hook` (#260, #328), `sqlite3_commit_hook` and `sqlite3_rollback_hook`.
-* Add support to unlock notification behind `unlock_notify` feature (#294, #331).
-* Make `Statement::column_index` case insensitive (#330).
-* Add comment to justify `&mut Connection` in `Transaction`.
-* Fix `tyvar_behind_raw_pointer` warnings.
-* Fix handful of clippy warnings.
-* Fix `Connection::open` documentation (#332)
-* Add binding to `sqlite3_get_autocommit` and `sqlite3_stmt_busy`.
-* Add binding to `sqlite3_busy_timeout` and `sqlite3_busy_handler`.
-* Add binding to `sqlite3_expanded_sql`.
-* Use `rerun-if-env-changed` in libsqlite3-sys (#329).
-* Return an `InvalidQuery` error when SQL is not read only.
-
-# Version 0.13.0 (2017-11-13)
-
-* Added ToSqlConversionFailure case to Error enum.
-* Now depends on chrono 0.4, bitflats 1.0, and (optionally) cc 1.0 / bindgen 0.31.
-* The ToSql/FromSql implementations for time::Timespec now include
- and expect fractional seconds and timezone in the serialized string.
-* The RowIndex type used in Row::get is now publicly exported.
-* New `sqlcipher` feature allows linking against SQLCipher instead of SQLite.
-* Doc link in README now point to docs.rs.
-
-# Version 0.12.0 (2017-05-29)
-
-* Defines HAVE\_USLEEP when building with a bundled SQLite (#263).
-* Updates dependencies to their latest versions, particularly serde to 1.0.
-* Adds support for vcpkg on Windows.
-* Adds `ToSql` impls for `str` and `[u8]`.
-
-# Version 0.11.0 (2017-04-06)
-
-* Avoid publicly exporting SQLite constants multiple times from libsqlite3-sys.
-* Adds `FromSql` and `ToSql` impls for `isize`. Documents why `usize` and `u64` are not included.
-
-# Version 0.10.1 (2017-03-03)
-
-* Updates the `bundled` SQLite version to 3.17.0.
-* Changes the build process to no longer require `bindgen`. This should improve
- build times and no longer require a new-ish Clang. See the README for more
- details.
-
-# Version 0.10.0 (2017-02-28)
-
-* Re-export the `ErrorCode` enum from `libsqlite3-sys`.
-* Adds `version()` and `version_number()` functions for querying the version of SQLite in use.
-* Adds the `limits` feature, exposing `limit()` and `set_limit()` methods on `Connection`.
-* Updates to `libsqlite3-sys` 0.7.0, which runs rust-bindgen at build-time instead of assuming the
- precense of all expected SQLite constants and functions.
-* Clarifies supported SQLite versions. Running with SQLite older than 3.6.8 now panics, and
- some features will not compile unless a sufficiently-recent SQLite version is used. See
- the README for requirements of particular features.
-* When running with SQLite 3.6.x, rusqlite attempts to perform SQLite initialization. If it fails,
- rusqlite will panic since it cannot ensure the threading mode for SQLite. This check can by
- skipped by calling the unsafe function `rusqlite::bypass_sqlite_initialization()`. This is
- technically a breaking change but is unlikely to affect anyone in practice, since prior to this
- version the check that rusqlite was using would cause a segfault if linked against a SQLite
- older than 3.7.0.
-* rusqlite now performs a one-time check (prior to the first connection attempt) that the runtime
- SQLite version is at least as new as the SQLite version found at buildtime. This check can by
- skipped by calling the unsafe function `rusqlite::bypass_sqlite_version_check()`.
-* Removes the `libc` dependency in favor of using `std::os::raw`
-
-# Version 0.9.5 (2017-01-26)
-
-* Add impls of `Clone`, `Debug`, and `PartialEq` to `ToSqlOutput`.
-
-# Version 0.9.4 (2017-01-25)
-
-* Update dependencies.
-
-# Version 0.9.3 (2017-01-23)
-
-* Make `ToSqlOutput` itself implement `ToSql`.
-
-# Version 0.9.2 (2017-01-22)
-
-* Bugfix: The `FromSql` impl for `i32` now returns an error instead of
- truncating if the underlying SQLite value is out of `i32`'s range.
-* Added `FromSql` and `ToSql` impls for `i8`, `i16`, `u8`, `u16`, and `u32`.
- `i32` and `i64` already had impls. `u64` is omitted because their range
- cannot be represented by `i64`, which is the type we use to communicate with
- SQLite.
-
-# Version 0.9.1 (2017-01-20)
-
-* BREAKING CHANGE: `Connection::close()` now returns a `Result<(), (Connection, Error)>` instead
- of a `Result<(), Error>` so callers get the still-open connection back on failure.
-
-# Version 0.8.0 (2016-12-31)
-
-* BREAKING CHANGE: The `FromSql` trait has been redesigned. It now requires a single, safe
- method instead of the previous definition which required implementing one or two unsafe
- methods.
-* BREAKING CHANGE: The `ToSql` trait has been redesigned. It can now be implemented without
- `unsafe`, and implementors can choose to return either borrowed or owned results.
-* BREAKING CHANGE: The closure passed to `query_row`, `query_row_and_then`, `query_row_safe`,
- and `query_row_named` now expects a `&Row` instead of a `Row`. The vast majority of calls
- to these functions will probably not need to change; see
- https://github.com/jgallagher/rusqlite/pull/184.
-* BREAKING CHANGE: A few cases of the `Error` enum have sprouted additional information
- (e.g., `FromSqlConversionFailure` now also includes the column index and the type returned
- by SQLite).
-* Added `#[deprecated(since = "...", note = "...")]` flags (new in Rust 1.9 for libraries) to
- all deprecated APIs.
-* Added `query_row` convenience function to `Statement`.
-* Added `bundled` feature which will build SQLite from source instead of attempting to link
- against a SQLite that already exists on the system.
-* Fixed a bug where using cached prepared statements resulted in attempting to close a connection
- failing with `DatabaseBusy`; see https://github.com/jgallagher/rusqlite/issues/186.
-
-# Version 0.7.3 (2016-06-01)
-
-* Fixes an incorrect failure from the `insert()` convenience function when back-to-back inserts to
- different tables both returned the same row ID
- ([#171](https://github.com/jgallagher/rusqlite/issues/171)).
-
-# Version 0.7.2 (2016-05-19)
-
-* BREAKING CHANGE: `Rows` no longer implements `Iterator`. It still has a `next()` method, but
- the lifetime of the returned `Row` is now tied to the lifetime of the vending `Rows` object.
- This behavior is more correct. Previously there were runtime checks to prevent misuse, but
- other changes in this release to reset statements as soon as possible introduced yet another
- hazard related to the lack of these lifetime connections. We were already recommending the
- use of `query_map` and `query_and_then` over raw `query`; both of theose still return handles
- that implement `Iterator`.
-* BREAKING CHANGE: `Transaction::savepoint()` now returns a `Savepoint` instead of another
- `Transaction`. Unlike `Transaction`, `Savepoint`s can be rolled back while keeping the current
- savepoint active.
-* BREAKING CHANGE: Creating transactions from a `Connection` or savepoints from a `Transaction`
- now take `&mut self` instead of `&self` to correctly represent that transactions within a
- connection are inherently nested. While a transaction is alive, the parent connection or
- transaction is unusable, so `Transaction` now implements `Deref<Target=Connection>`, giving
- access to `Connection`'s methods via the `Transaction` itself.
-* BREAKING CHANGE: `Transaction::set_commit` and `Transaction::set_rollback` have been replaced
- by `Transaction::set_drop_behavior`.
-* Adds `Connection::prepare_cached`. `Connection` now keeps an internal cache of any statements
- prepared via this method. The size of this cache defaults to 16 (`prepare_cached` will always
- work but may re-prepare statements if more are prepared than the cache holds), and can be
- controlled via `Connection::set_prepared_statement_cache_capacity`.
-* Adds `query_map_named` and `query_and_then_named` to `Statement`.
-* Adds `insert` convenience method to `Statement` which returns the row ID of an inserted row.
-* Adds `exists` convenience method returning whether a query finds one or more rows.
-* Adds support for serializing types from the `serde_json` crate. Requires the `serde_json` feature.
-* Adds support for serializing types from the `chrono` crate. Requires the `chrono` feature.
-* Removes `load_extension` feature from `libsqlite3-sys`. `load_extension` is still available
- on rusqlite itself.
-* Fixes crash on nightly Rust when using the `trace` feature.
-* Adds optional `clippy` feature and addresses issues it found.
-* Adds `column_count()` method to `Statement` and `Row`.
-* Adds `types::Value` for dynamic column types.
-* Adds support for user-defined aggregate functions (behind the existing `functions` Cargo feature).
-* Introduces a `RowIndex` trait allowing columns to be fetched via index (as before) or name (new).
-* Introduces `ZeroBlob` type under the `blob` module/feature exposing SQLite's zeroblob API.
-* Adds CI testing for Windows via AppVeyor.
-* Fixes a warning building libsqlite3-sys under Rust 1.6.
-* Adds an unsafe `handle()` method to `Connection`. Please file an issue if you actually use it.
-
-# Version 0.6.0 (2015-12-17)
-
-* BREAKING CHANGE: `SqliteError` is now an enum instead of a struct. Previously, we were (ab)using
- the error code and message to send back both underlying SQLite errors and errors that occurred
- at the Rust level. Now those have been separated out; SQLite errors are returned as
- `SqliteFailure` cases (which still include the error code but also include a Rust-friendlier
- enum as well), and rusqlite-level errors are captured in other cases. Because of this change,
- `SqliteError` no longer implements `PartialEq`.
-* BREAKING CHANGE: When opening a new detection, rusqlite now detects if SQLite was compiled or
- configured for single-threaded use only; if it was, connection attempts will fail. If this
- affects you, please open an issue.
-* BREAKING CHANGE: `SqliteTransactionDeferred`, `SqliteTransactionImmediate`, and
- `SqliteTransactionExclusive` are no longer exported. Instead, use
- `TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and
- `TransactionBehavior::Exclusive`.
-* Removed `Sqlite` prefix on many types:
- * `SqliteConnection` is now `Connection`
- * `SqliteError` is now `Error`
- * `SqliteResult` is now `Result`
- * `SqliteStatement` is now `Statement`
- * `SqliteRows` is now `Rows`
- * `SqliteRow` is now `Row`
- * `SqliteOpenFlags` is now `OpenFlags`
- * `SqliteTransaction` is now `Transaction`.
- * `SqliteTransactionBehavior` is now `TransactionBehavior`.
- * `SqliteLoadExtensionGuard` is now `LoadExtensionGuard`.
- The old, prefixed names are still exported but are deprecated.
-* Adds a variety of `..._named` methods for executing queries using named placeholder parameters.
-* Adds `backup` feature that exposes SQLite's online backup API.
-* Adds `blob` feature that exposes SQLite's Incremental I/O for BLOB API.
-* Adds `functions` feature that allows user-defined scalar functions to be added to
- open `SqliteConnection`s.
-
-# Version 0.5.0 (2015-12-08)
-
-* Adds `trace` feature that allows the use of SQLite's logging, tracing, and profiling hooks.
-* Slight change to the closure types passed to `query_map` and `query_and_then`:
- * Remove the `'static` requirement on the closure's output type.
- * Give the closure a `&SqliteRow` instead of a `SqliteRow`.
-* When building, the environment variable `SQLITE3_LIB_DIR` now takes precedence over pkg-config.
-* If `pkg-config` is not available, we will try to find `libsqlite3` in `/usr/lib`.
-* Add more documentation for failure modes of functions that return `SqliteResult`s.
-* Updates `libc` dependency to 0.2, fixing builds on ARM for Rust 1.6 or newer.
-
-# Version 0.4.0 (2015-11-03)
-
-* Adds `Sized` bound to `FromSql` trait as required by RFC 1214.
-
-# Version 0.3.1 (2015-09-22)
-
-* Reset underlying SQLite statements as soon as possible after executing, as recommended by
- http://www.sqlite.org/cvstrac/wiki?p=ScrollingCursor.
-
-# Version 0.3.0 (2015-09-21)
-
-* Removes `get_opt`. Use `get_checked` instead.
-* Add `query_row_and_then` and `query_and_then` convenience functions. These are analogous to
- `query_row` and `query_map` but allow functions that can fail by returning `Result`s.
-* Relax uses of `P: AsRef<...>` from `&P` to `P`.
-* Add additional error check for calling `execute` when `query` was intended.
-* Improve debug formatting of `SqliteStatement` and `SqliteConnection`.
-* Changes documentation of `get_checked` to correctly indicate that it returns errors (not panics)
- when given invalid types or column indices.
-
-# Version 0.2.0 (2015-07-26)
-
-* Add `column_names()` to `SqliteStatement`.
-* By default, include `SQLITE_OPEN_NO_MUTEX` and `SQLITE_OPEN_URI` flags when opening a
- new conneciton.
-* Fix generated bindings (e.g., `sqlite3_exec` was wrong).
-* Use now-generated `sqlite3_destructor_type` to define `SQLITE_STATIC` and `SQLITE_TRANSIENT`.
-
-# Version 0.1.0 (2015-05-11)
-
-* [breaking-change] Modify `query_row` to return a `Result` instead of unwrapping.
-* Deprecate `query_row_safe` (use `query_row` instead).
-* Add `query_map`.
-* Add `get_checked`, which asks SQLite to do some basic type-checking of columns.
-
-# Version 0.0.17 (2015-04-03)
-
-* Publish version that builds on stable rust (beta). This version lives on the
- `stable` branch. Development continues on `master` and still requires a nightly
- version of Rust.
-
-# Version 0.0.16
-
-* Updates to track rustc nightly.
-
-# Version 0.0.15
-
-* Make SqliteConnection `Send`.
-
-# Version 0.0.14
-
-* Remove unneeded features (also involves switching to `libc` crate).
-
-# Version 0.0.13 (2015-03-26)
-
-* Updates to track rustc nightly.
-
-# Version 0.0.12 (2015-03-24)
-
-* Updates to track rustc stabilization.
-
-# Version 0.0.11 (2015-03-12)
-
-* Reexport `sqlite3_stmt` from `libsqlite3-sys` for easier `impl`-ing of `ToSql` and `FromSql`.
-* Updates to track latest rustc changes.
-* Update dependency versions.
-
-# Version 0.0.10 (2015-02-23)
-
-* BREAKING CHANGE: `open` now expects a `Path` rather than a `str`. There is a separate
- `open_in_memory` constructor for opening in-memory databases.
-* Added the ability to load SQLite extensions. This is behind the `load_extension` Cargo feature,
- because not all builds of sqlite3 include this ability. Notably the default libsqlite3 that
- ships with OS X 10.10 does not support extensions.
-
-# Version 0.0.9 (2015-02-13)
-
-* Updates to track latest rustc changes.
-* Implement standard `Error` trait for `SqliteError`.
-
-# Version 0.0.8 (2015-02-04)
-
-* Updates to track latest rustc changes.
-
-# Version 0.0.7 (2015-01-20)
-
-* Use external bitflags from crates.io.
-
-# Version 0.0.6 (2015-01-10)
-
-* Updates to track latest rustc changes (1.0.0-alpha).
-* Add `query_row_safe`, a `SqliteResult`-returning variant of `query_row`.
-
-# Version 0.0.5 (2015-01-07)
-
-* Updates to track latest rustc changes (closure syntax).
-* Updates to track latest rust stdlib changes (`std::c_str` -> `std::ffi`).
-
-# Version 0.0.4 (2015-01-05)
-
-* Updates to track latest rustc changes.
-
-# Version 0.0.3 (2014-12-23)
-
-* Updates to track latest rustc changes.
-* Add call to `sqlite3_busy_timeout`.
-
-# Version 0.0.2 (2014-12-04)
-
-* Remove use of now-deprecated `std::vec::raw::from_buf`.
-* Update to latest version of `time` crate.
-
-# Version 0.0.1 (2014-11-21)
-
-* Initial release
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..77fbcb9 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.27.0.crate"
}
- version: "0.24.2"
+ version: "0.27.0"
license_type: NOTICE
last_upgrade_date {
- year: 2020
- month: 12
- day: 5
+ year: 2022
+ month: 3
+ day: 1
}
}
diff --git a/README.md b/README.md
index 09f0c20..073c464 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,12 @@
# Rusqlite
-[![Travis Build Status](https://api.travis-ci.org/rusqlite/rusqlite.svg?branch=master)](https://travis-ci.org/rusqlite/rusqlite)
-[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/rusqlite/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/rusqlite/rusqlite)
-[![Build Status](https://github.com/rusqlite/rusqlite/workflows/CI/badge.svg)](https://github.com/rusqlite/rusqlite/actions)
-[![dependency status](https://deps.rs/repo/github/rusqlite/rusqlite/status.svg)](https://deps.rs/repo/github/rusqlite/rusqlite)
[![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite)
-[![Gitter](https://badges.gitter.im/rusqlite.svg)](https://gitter.im/rusqlite/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
-[![Docs](https://docs.rs/rusqlite/badge.svg)](https://docs.rs/rusqlite)
-[![codecov](https://codecov.io/gh/rusqlite/rusqlite/branch/master/graph/badge.svg)](https://codecov.io/gh/rusqlite/rusqlite)
+[![Documentation](https://docs.rs/rusqlite/badge.svg)](https://docs.rs/rusqlite)
+[![Build Status (GitHub)](https://github.com/rusqlite/rusqlite/workflows/CI/badge.svg)](https://github.com/rusqlite/rusqlite/actions)
+[![Build Status (AppVeyor)](https://ci.appveyor.com/api/projects/status/github/rusqlite/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/rusqlite/rusqlite)
+[![Code Coverage](https://codecov.io/gh/rusqlite/rusqlite/branch/master/graph/badge.svg)](https://codecov.io/gh/rusqlite/rusqlite)
+[![Dependency Status](https://deps.rs/repo/github/rusqlite/rusqlite/status.svg)](https://deps.rs/repo/github/rusqlite/rusqlite)
+[![Discord Chat](https://img.shields.io/discord/927966344266256434.svg?logo=discord)](https://discord.gg/nFYfGPB8g4)
Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
@@ -31,7 +30,7 @@ fn main() -> Result<()> {
name TEXT NOT NULL,
data BLOB
)",
- params![],
+ [],
)?;
let me = Person {
id: 0,
@@ -44,7 +43,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 +76,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 +97,24 @@ 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.
-* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`.
+* `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 overrides `bundled`.
+* `bundled-sqlcipher` uses a bundled version of SQLCipher. This searches for and links against a system-installed crypto library to provide the crypto implementation.
+* `bundled-sqlcipher-vendored-openssl` allows using bundled-sqlcipher with a vendored version of OpenSSL (via the `openssl-sys` crate) as the crypto provider.
+ - As the name implies this depends on the `bundled-sqlcipher` feature, and automatically turns it on.
+ - If turned on, this uses the [`openssl-sys`](https://crates.io/crates/openssl-sys) crate, with the `vendored` feature enabled in order to build and bundle the OpenSSL crypto library.
* `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
@@ -116,24 +124,29 @@ declarations for SQLite's C API. By default, `libsqlite3-sys` attempts to find a
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
+* If you use the `bundled`, `bundled-sqlcipher`, or `bundled-sqlcipher-vendored-openssl` features, `libsqlite3-sys` will use the
+ [cc](https://crates.io/crates/cc) crate to compile SQLite or SQLCipher 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.38.0 (as of `rusqlite` 0.27.0 / `libsqlite3-sys`
+ 0.24.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.27.0"
features = ["bundled"]
```
-* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite
- library.
+* When using any of the `bundled` features, the build script will honor `SQLITE_MAX_VARIABLE_NUMBER` and `SQLITE_MAX_EXPR_DEPTH` variables. It will also honor a `LIBSQLITE_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 using `bundled-sqlcipher` (and not also using `bundled-sqlcipher-vendored-openssl`), `libsqlite3-sys` will need to
+ link against crypto libraries on the system. If the build script can find a `libcrypto` from OpenSSL or LibreSSL (it will consult `OPENSSL_LIB_DIR`/`OPENSSL_INCLUDE_DIR` and `OPENSSL_DIR` environment variables), it will use that. If building on and for Macs, and none of those variables are set, it will use the system's SecurityFramework instead.
+
+* When linking against a SQLite (or SQLCipher) library already on the system (so *not* using any of the `bundled` features), you can set the `SQLITE3_LIB_DIR` (or `SQLCIPHER_LIB_DIR`) environment variable to point to a directory containing the library. You can also set the `SQLITE3_INCLUDE_DIR` (or `SQLCIPHER_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 (or SQLCipher) library already on the system, you can set the `SQLITE3_STATIC` (or `SQLCIPHER_STATIC`) environment variable to 1 to request that the library be statically instead of dynamically linked.
+
### Binding generation
@@ -160,8 +173,8 @@ pregenerated bindings are chosen:
* `min_sqlite_version_3_6_23` - SQLite 3.6.23 bindings
* `min_sqlite_version_3_7_7` - SQLite 3.7.7 bindings
-If you use the `bundled` feature, you will get pregenerated bindings for the
-bundled version of SQLite. If you need other specific pregenerated binding
+If you use any of the `bundled` features, you will get pregenerated bindings for the
+bundled version of SQLite/SQLCipher. If you need other specific pregenerated binding
versions, please file an issue. If you want to run `bindgen` at buildtime to
produce your own bindings, use the `buildtime_bindgen` Cargo feature.
@@ -169,7 +182,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 +190,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
@@ -186,10 +199,10 @@ instead.
### Checklist
- 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 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 clippy --workspace --features bundled` passes without warnings.
+- Ensure `cargo clippy --workspace --features "bundled-full session buildtime_bindgen"` passes without warnings.
+- Ensure `cargo test --workspace --features bundled` reports no failures.
+- Ensure `cargo test --workspace --features "bundled-full session buildtime_bindgen"` reports no failures.
## Author
@@ -198,8 +211,18 @@ here: https://github.com/rusqlite/rusqlite/graphs/contributors
## Community
-Currently there's a gitter channel set up for rusqlite [here](https://gitter.im/rusqlite/community).
+Feel free to join the [Rusqlite Discord Server](https://discord.gg/nFYfGPB8g4) to discuss or get help with `rusqlite` or `libsqlite3-sys`.
## License
-Rusqlite is available under the MIT license. See the LICENSE file for more info.
+Rusqlite and libsqlite3-sys are available under the MIT license. See the LICENSE file for more info.
+
+### Licenses of Bundled Software
+
+Depending on the set of enabled cargo `features`, rusqlite and libsqlite3-sys will also bundle other libraries, which have their own licensing terms:
+
+- If `--features=bundled-sqlcipher` is enabled, the vendored source of [SQLcipher](https://github.com/sqlcipher/sqlcipher) will be compiled and statically linked in. SQLcipher is distributed under a BSD-style license, as described [here](libsqlite3-sys/sqlcipher/LICENSE).
+
+- If `--features=bundled` is enabled, the vendored source of SQLite will be compiled and linked in. SQLite is in the public domain, as described [here](https://www.sqlite.org/copyright.html).
+
+Both of these are quite permissive, have no bearing on the license of the code in `rusqlite` or `libsqlite3-sys` themselves, and can be entirely ignored if you do not use the feature in question.
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..91f37bf
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,19 @@
+// Generated by update_crate_tests.py for tests that depend on this crate.
+{
+ "presubmit": [
+ {
+ "name": "keystore2_test"
+ },
+ {
+ "name": "legacykeystore_test"
+ }
+ ],
+ "presubmit-rust": [
+ {
+ "name": "keystore2_test"
+ },
+ {
+ "name": "legacykeystore_test"
+ }
+ ]
+}
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 2d88284..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-environment:
- matrix:
- - TARGET: x86_64-pc-windows-gnu
- MSYS2_BITS: 64
-# - TARGET: x86_64-pc-windows-msvc
-# VCPKG_DEFAULT_TRIPLET: x64-windows
-# VCPKGRS_DYNAMIC: 1
-# - TARGET: x86_64-pc-windows-msvc
-# VCPKG_DEFAULT_TRIPLET: x64-windows-static
-# RUSTFLAGS: -Ctarget-feature=+crt-static
-install:
- - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- - rustup-init.exe -y --default-host %TARGET%
- - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- - if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin
- - rustc -V
- - cargo -V
- # download SQLite dll (useful only when the `bundled` feature is not set)
- - appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-dll-win64-x64-3250200.zip -FileName sqlite-dll-win64-x64.zip
- - if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-dll-win64-x64.zip -y > nul
- # download SQLite headers (useful only when the `bundled` feature is not set)
- - appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-amalgamation-3250200.zip -FileName sqlite-amalgamation.zip
- - if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-amalgamation.zip -y > nul
- # specify where the SQLite dll has been downloaded (useful only when the `bundled` feature is not set)
- - if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_LIB_DIR=%APPVEYOR_BUILD_FOLDER%
- # specify where the SQLite headers have been downloaded (useful only when the `bundled` feature is not set)
- - if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_INCLUDE_DIR=%APPVEYOR_BUILD_FOLDER%
- # install sqlite3 package
- - if defined VCPKG_DEFAULT_TRIPLET vcpkg install sqlite3
-
-build: false
-
-test_script:
- - cargo test --lib --verbose
- - cargo test --lib --verbose --features bundled
- - cargo test --lib --features "backup blob chrono collation functions hooks limits load_extension serde_json trace"
- - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
- - cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
- - cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
-
-cache:
- - C:\Users\appveyor\.cargo
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/cargo2android-extra-module.bp b/cargo2android-extra-module.bp
new file mode 100644
index 0000000..33222cb
--- /dev/null
+++ b/cargo2android-extra-module.bp
@@ -0,0 +1,21 @@
+rust_library {
+ name: "librusqlite_noicu",
+ host_supported: true,
+ crate_name: "rusqlite",
+ cargo_env_compat: true,
+ srcs: ["src/lib.rs"],
+ edition: "2018",
+ features: [
+ "modern_sqlite",
+ "trace",
+ ],
+ rustlibs: [
+ "libbitflags",
+ "libfallible_iterator",
+ "libfallible_streaming_iterator",
+ "libhashlink",
+ "liblibsqlite3_sys_noicu",
+ "libmemchr",
+ "libsmallvec",
+ ],
+}
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..b515003
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,6 @@
+{
+ "add-toplevel-block": "cargo2android-extra-module.bp",
+ "device": true,
+ "features": "modern_sqlite,trace",
+ "run": true
+} \ No newline at end of file
diff --git a/clippy.toml b/clippy.toml
deleted file mode 100644
index 82447d9..0000000
--- a/clippy.toml
+++ /dev/null
@@ -1 +0,0 @@
-doc-valid-idents = ["SQLite", "lang_transaction"]
diff --git a/codecov.yml b/codecov.yml
deleted file mode 100644
index 7a4789e..0000000
--- a/codecov.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-ignore:
- - "libsqlite3-sys/bindgen-bindings"
- - "libsqlite3-sys/sqlite3"
-coverage:
- status:
- project:
- default:
- informational: true
- patch:
- default:
- informational: true
diff --git a/publish-ghp-docs.sh b/publish-ghp-docs.sh
deleted file mode 100755
index 14f358a..0000000
--- a/publish-ghp-docs.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-
-git describe --exact-match --tags $(git log -n1 --pretty='%h') >/dev/null 2>&1
-if [[ $? != 0 ]]; then
- echo "Should not publish tags from an untagged commit!"
- exit 1
-fi
-
-cd $(git rev-parse --show-toplevel)
-rm -rf target/doc/
-rustup run nightly cargo doc --no-deps --features "backup blob chrono functions limits load_extension serde_json trace"
-echo '<meta http-equiv=refresh content=0;url=rusqlite/index.html>' > target/doc/index.html
-ghp-import target/doc
-git push origin gh-pages:gh-pages
diff --git a/src/backup.rs b/src/backup.rs
index 4654907..6da01fd 100644
--- a/src/backup.rs
+++ b/src/backup.rs
@@ -1,13 +1,14 @@
-//! `feature = "backup"` Online SQLite backup API.
+//! 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
@@ -39,11 +40,11 @@ use std::time::Duration;
use crate::ffi;
-use crate::error::{error_from_handle, error_from_sqlite_code};
+use crate::error::error_from_handle;
use crate::{Connection, DatabaseName, Result};
impl Connection {
- /// `feature = "backup"` Back up the `name` database to the given
+ /// Back up the `name` database to the given
/// destination path.
///
/// If `progress` is not `None`, it will be called periodically
@@ -83,7 +84,7 @@ impl Connection {
}
}
- /// `feature = "backup"` Restore the given source path into the
+ /// Restore the given source path into the
/// `name` database. If `progress` is not `None`, it will be
/// called periodically until the restore completes.
///
@@ -106,7 +107,7 @@ impl Connection {
let restore = Backup::new_with_names(&src, DatabaseName::Main, self, name)?;
let mut r = More;
- let mut busy_count = 0i32;
+ let mut busy_count = 0_i32;
'restore_loop: while r == More || r == Busy {
r = restore.step(100)?;
if let Some(ref f) = progress {
@@ -130,7 +131,8 @@ impl Connection {
}
}
-/// `feature = "backup"` Possible successful results of calling `Backup::step`.
+/// 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,
@@ -150,11 +152,12 @@ pub enum StepResult {
Locked,
}
-/// `feature = "backup"` Struct specifying the progress of a backup. The
+/// 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.
@@ -163,10 +166,10 @@ pub struct Progress {
pub pagecount: c_int,
}
-/// `feature = "backup"` A handle to an online backup.
+/// A handle to an online backup.
pub struct Backup<'a, 'b> {
phantom_from: PhantomData<&'a Connection>,
- phantom_to: PhantomData<&'b Connection>,
+ to: &'b Connection,
b: *mut ffi::sqlite3_backup,
}
@@ -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)
}
@@ -199,8 +203,8 @@ impl Backup<'_, '_> {
to: &'b mut Connection,
to_name: DatabaseName<'_>,
) -> Result<Backup<'a, 'b>> {
- let to_name = to_name.to_cstring()?;
- let from_name = from_name.to_cstring()?;
+ let to_name = to_name.as_cstring()?;
+ let from_name = from_name.as_cstring()?;
let to_db = to.db.borrow_mut().db;
@@ -219,12 +223,15 @@ impl Backup<'_, '_> {
Ok(Backup {
phantom_from: PhantomData,
- phantom_to: PhantomData,
+ to,
b,
})
}
- /// Gets the progress of the backup as of the last call to `step`.
+ /// Gets the progress of the backup as of the last call to
+ /// [`step`](Backup::step).
+ #[inline]
+ #[must_use]
pub fn progress(&self) -> Progress {
unsafe {
Progress {
@@ -238,7 +245,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 +254,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};
@@ -255,16 +264,16 @@ impl Backup<'_, '_> {
ffi::SQLITE_OK => Ok(More),
ffi::SQLITE_BUSY => Ok(Busy),
ffi::SQLITE_LOCKED => Ok(Locked),
- _ => Err(error_from_sqlite_code(rc, None)),
+ _ => self.to.decode_result(rc).map(|_| More),
}
}
- /// 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 +282,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,
@@ -287,7 +297,7 @@ impl Backup<'_, '_> {
loop {
let r = self.step(pages_per_step)?;
if let Some(progress) = progress {
- progress(self.progress())
+ progress(self.progress());
}
match r {
More | Busy | Locked => thread::sleep(pause_between_pages),
@@ -298,6 +308,7 @@ impl Backup<'_, '_> {
}
impl Drop for Backup<'_, '_> {
+ #[inline]
fn drop(&mut self) {
unsafe { ffi::sqlite3_backup_finish(self.b) };
}
@@ -306,96 +317,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 +402,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 +417,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..81c6098 100644
--- a/src/blob/mod.rs
+++ b/src/blob/mod.rs
@@ -1,4 +1,4 @@
-//! `feature = "blob"` Incremental BLOB I/O.
+//! Incremental BLOB I/O.
//!
//! Note that SQLite does not provide API-level access to change the size of a
//! BLOB; that must be performed through SQL statements.
@@ -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.
+/// 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,
@@ -206,7 +206,7 @@ pub struct Blob<'conn> {
}
impl Connection {
- /// `feature = "blob"` Open a handle to the BLOB located in `row_id`,
+ /// Open a handle to the BLOB located in `row_id`,
/// `column`, `table` in database `db`.
///
/// # Failure
@@ -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<'_>,
@@ -222,9 +223,9 @@ impl Connection {
row_id: i64,
read_only: bool,
) -> Result<Blob<'a>> {
- let mut c = self.db.borrow_mut();
+ let c = self.db.borrow_mut();
let mut blob = ptr::null_mut();
- let db = db.to_cstring()?;
+ let db = db.as_cstring()?;
let table = super::str_to_cstring(table)?;
let column = super::str_to_cstring(column)?;
let rc = unsafe {
@@ -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,23 @@ impl Blob<'_> {
}
/// Return the size in bytes of the BLOB.
+ #[inline]
+ #[must_use]
pub fn size(&self) -> i32 {
unsafe { ffi::sqlite3_blob_bytes(self.blob) }
}
/// Return the current size in bytes of the BLOB.
+ #[inline]
+ #[must_use]
pub fn len(&self) -> usize {
use std::convert::TryInto;
self.size().try_into().unwrap()
}
/// Return true if the BLOB is empty.
+ #[inline]
+ #[must_use]
pub fn is_empty(&self) -> bool {
self.size() == 0
}
@@ -286,10 +294,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,14 +314,14 @@ 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;
if n <= 0 {
return Ok(0);
}
- let rc =
- unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_mut_ptr() as *mut _, n, self.pos) };
+ let rc = unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_mut_ptr().cast(), n, self.pos) };
self.conn
.decode_result(rc)
.map(|_| {
@@ -334,6 +344,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 +361,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 +369,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,12 +396,13 @@ impl io::Seek for Blob<'_> {
#[allow(unused_must_use)]
impl Drop for Blob<'_> {
+ #[inline]
fn drop(&mut self) {
self.close_();
}
}
-/// `feature = "blob"` BLOB of length N that is filled with zeroes.
+/// BLOB of length N that is filled with zeroes.
///
/// Zeroblobs are intended to serve as placeholders for BLOBs whose content is
/// later written using incremental BLOB I/O routines.
@@ -398,6 +412,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 +436,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 +468,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 +479,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 +505,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 +524,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 +541,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..ecc7d65 100644
--- a/src/blob/pos_io.rs
+++ b/src/blob/pos_io.rs
@@ -44,15 +44,14 @@ impl<'conn> Blob<'conn> {
// losslessly converted to i32, since `len` came from an i32.
// Sanity check the above.
debug_assert!(i32::try_from(write_start).is_ok() && i32::try_from(buf.len()).is_ok());
- unsafe {
- check!(ffi::sqlite3_blob_write(
+ self.conn.decode_result(unsafe {
+ ffi::sqlite3_blob_write(
self.blob,
- buf.as_ptr() as *const _,
+ buf.as_ptr().cast(),
buf.len() as i32,
write_start as i32,
- ));
- }
- Ok(())
+ )
+ })
}
/// An alias for `write_at` provided for compatibility with the conceptually
@@ -85,7 +84,7 @@ impl<'conn> Blob<'conn> {
// Safety: this is safe because `raw_read_at` never stores uninitialized
// data into `as_uninit`.
let as_uninit: &mut [MaybeUninit<u8>] =
- unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut _, buf.len()) };
+ unsafe { from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) };
self.raw_read_at(as_uninit, read_start).map(|s| s.len())
}
@@ -120,7 +119,7 @@ impl<'conn> Blob<'conn> {
// We could return `Ok(&mut [])`, but it seems confusing that the
// pointers don't match, so fabricate a empty slice of u8 with the
// same base pointer as `buf`.
- let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, 0) };
+ let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), 0) };
return Ok(empty);
}
@@ -151,14 +150,14 @@ impl<'conn> Blob<'conn> {
debug_assert!(i32::try_from(read_len).is_ok());
unsafe {
- check!(ffi::sqlite3_blob_read(
+ self.conn.decode_result(ffi::sqlite3_blob_read(
self.blob,
- buf.as_mut_ptr() as *mut _,
+ buf.as_mut_ptr().cast(),
read_len as i32,
read_start as i32,
- ));
+ ))?;
- Ok(from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, read_len))
+ Ok(from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), read_len))
}
}
@@ -194,25 +193,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 +269,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..b394d01 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);
@@ -57,7 +64,7 @@ impl Connection {
0
}
}
- let mut c = self.db.borrow_mut();
+ let c = self.db.borrow_mut();
let r = match callback {
Some(f) => unsafe {
ffi::sqlite3_busy_handler(c.db(), Some(busy_handler_callback), f as *mut c_void)
@@ -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,11 +167,9 @@ 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);
+ assert!(CALLED.load(Ordering::Relaxed));
child.join().unwrap();
}
diff --git a/src/cache.rs b/src/cache.rs
index 7dc9d23..c80a708 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,13 +44,15 @@ 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)
+ 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()
+ self.cache.flush();
}
}
@@ -57,10 +60,14 @@ impl Connection {
// #[derive(Debug)] // FIXME: https://github.com/kyren/hashlink/pull/4
pub struct StatementCache(RefCell<LruCache<Arc<str>, RawStatement>>);
+#[allow(clippy::non_send_fields_in_send_ty)]
+unsafe impl Send for StatementCache {}
+
/// 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 +76,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 +91,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 +100,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 +109,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,12 +118,14 @@ 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)
+ self.0.borrow_mut().set_capacity(capacity);
}
// Search the cache for a prepared-statement object that implements `sql`.
@@ -155,16 +169,17 @@ impl StatementCache {
}
}
+ #[inline]
fn flush(&self) {
let mut cache = self.0.borrow_mut();
- cache.clear()
+ cache.clear();
}
}
#[cfg(test)]
mod test {
use super::StatementCache;
- use crate::{Connection, NO_PARAMS};
+ use crate::{Connection, Result};
use fallible_iterator::FallibleIterator;
impl StatementCache {
@@ -182,8 +197,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 +206,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 +242,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 +296,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..c1fe3f7 100644
--- a/src/collation.rs
+++ b/src/collation.rs
@@ -1,4 +1,4 @@
-//! `feature = "collation"` Add, remove, or modify a collation
+//! Add, remove, or modify a collation
use std::cmp::Ordering;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, UnwindSafe};
@@ -10,11 +10,12 @@ use crate::{str_to_cstring, Connection, InnerConnection, Result};
// FIXME copy/paste from function.rs
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
- drop(Box::from_raw(p as *mut T));
+ drop(Box::from_raw(p.cast::<T>()));
}
impl Connection {
- /// `feature = "collation"` Add or modify a collation.
+ /// Add or modify a collation.
+ #[inline]
pub fn create_collation<C>(&self, collation_name: &str, x_compare: C) -> Result<()>
where
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
@@ -24,7 +25,8 @@ impl Connection {
.create_collation(collation_name, x_compare)
}
- /// `feature = "collation"` Collation needed callback
+ /// Collation needed callback
+ #[inline]
pub fn collation_needed(
&self,
x_coll_needed: fn(&Connection, &str) -> Result<()>,
@@ -32,7 +34,8 @@ impl Connection {
self.db.borrow_mut().collation_needed(x_coll_needed)
}
- /// `feature = "collation"` Remove collation.
+ /// Remove collation.
+ #[inline]
pub fn remove_collation(&self, collation_name: &str) -> Result<()> {
self.db.borrow_mut().remove_collation(collation_name)
}
@@ -54,14 +57,14 @@ impl InnerConnection {
C: Fn(&str, &str) -> Ordering,
{
let r = catch_unwind(|| {
- let boxed_f: *mut C = arg1 as *mut C;
+ let boxed_f: *mut C = arg1.cast::<C>();
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
let s1 = {
- let c_slice = slice::from_raw_parts(arg3 as *const u8, arg2 as usize);
+ let c_slice = slice::from_raw_parts(arg3.cast::<u8>(), arg2 as usize);
String::from_utf8_lossy(c_slice)
};
let s2 = {
- let c_slice = slice::from_raw_parts(arg5 as *const u8, arg4 as usize);
+ let c_slice = slice::from_raw_parts(arg5.cast::<u8>(), arg4 as usize);
String::from_utf8_lossy(c_slice)
};
(*boxed_f)(s1.as_ref(), s2.as_ref())
@@ -88,12 +91,18 @@ impl InnerConnection {
self.db(),
c_name.as_ptr(),
flags,
- boxed_f as *mut c_void,
+ boxed_f.cast::<c_void>(),
Some(call_boxed_closure::<C>),
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..aa1f5f7 100644
--- a/src/column.rs
+++ b/src/column.rs
@@ -1,6 +1,6 @@
use std::str;
-use crate::{Error, Result, Row, Rows, Statement};
+use crate::{Error, Result, Statement};
/// Information about a column of a SQLite query.
#[derive(Debug)]
@@ -11,11 +11,15 @@ pub struct Column<'stmt> {
impl Column<'_> {
/// Returns the name of the column.
+ #[inline]
+ #[must_use]
pub fn name(&self) -> &str {
self.name
}
/// Returns the type of the column (`None` for expression).
+ #[inline]
+ #[must_use]
pub fn decl_type(&self) -> Option<&str> {
self.decl_type
}
@@ -23,6 +27,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 +43,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 +81,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 +106,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,7 +129,12 @@ 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")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
pub fn columns(&self) -> Vec<Column> {
let n = self.column_count();
let mut cols = Vec::with_capacity(n as usize);
@@ -102,100 +150,51 @@ impl Statement<'_> {
}
}
-impl<'stmt> Rows<'stmt> {
- /// Get all the column names.
- pub fn column_names(&self) -> Option<Vec<&str>> {
- self.stmt.map(Statement::column_names)
- }
-
- /// Return the number of columns.
- pub fn column_count(&self) -> Option<usize> {
- self.stmt.map(Statement::column_count)
- }
-
- /// Return the name of the column.
- pub fn column_name(&self, col: usize) -> Option<Result<&str>> {
- self.stmt.map(|stmt| stmt.column_name(col))
- }
-
- /// Return the index of the column.
- pub fn column_index(&self, name: &str) -> Option<Result<usize>> {
- self.stmt.map(|stmt| stmt.column_index(name))
- }
-
- /// Returns a slice describing the columns of the Rows.
- #[cfg(feature = "column_decltype")]
- pub fn columns(&self) -> Option<Vec<Column>> {
- self.stmt.map(Statement::columns)
- }
-}
-
-impl<'stmt> Row<'stmt> {
- /// Get all the column names of the Row.
- pub fn column_names(&self) -> Vec<&str> {
- self.stmt.column_names()
- }
-
- /// Return the number of columns in the current row.
- pub fn column_count(&self) -> usize {
- self.stmt.column_count()
- }
-
- /// Return the name of the column.
- pub fn column_name(&self, col: usize) -> Result<&str> {
- self.stmt.column_name(col)
- }
-
- /// Return the index of the column.
- pub fn column_index(&self, name: &str) -> Result<usize> {
- self.stmt.column_index(name)
- }
-
- /// Returns a slice describing the columns of the Row.
- #[cfg(feature = "column_decltype")]
- pub fn columns(&self) -> Vec<Column> {
- self.stmt.columns()
- }
-}
-
#[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!(
column_names.as_slice(),
&["type", "name", "tbl_name", "rootpage", "sql"]
);
- let column_types: Vec<Option<&str>> = columns.iter().map(Column::decl_type).collect();
+ let column_types: Vec<Option<String>> = columns
+ .iter()
+ .map(|col| col.decl_type().map(str::to_lowercase))
+ .collect();
assert_eq!(
&column_types[..3],
- &[Some("text"), Some("text"), Some("text"),]
+ &[
+ Some("text".to_owned()),
+ Some("text".to_owned()),
+ Some("text".to_owned()),
+ ]
);
+ 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 +215,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..b59e5ef 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -2,6 +2,7 @@
use std::os::raw::c_int;
+use crate::error::check;
use crate::ffi;
use crate::{Connection, Result};
@@ -10,6 +11,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 */
@@ -29,7 +31,9 @@ pub enum DbConfig {
/// Includes or excludes output for any operations performed by trigger
/// programs from the output of EXPLAIN QUERY PLAN commands.
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
- //SQLITE_DBCONFIG_RESET_DATABASE = 1009,
+ /// Activates or deactivates the "reset" flag for a database connection.
+ /// Run VACUUM with this flag set to reset the database.
+ SQLITE_DBCONFIG_RESET_DATABASE = 1009,
/// Activates or deactivates the "defensive" flag for a database connection.
SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0
/// Activates or deactivates the "writable_schema" flag.
@@ -62,56 +66,58 @@ pub enum DbConfig {
impl Connection {
/// Returns the current value of a `config`.
///
- /// - SQLITE_DBCONFIG_ENABLE_FKEY: return `false` or `true` to indicate
+ /// - `SQLITE_DBCONFIG_ENABLE_FKEY`: return `false` or `true` to indicate
/// whether FK enforcement is off or on
- /// - SQLITE_DBCONFIG_ENABLE_TRIGGER: return `false` or `true` to indicate
+ /// - `SQLITE_DBCONFIG_ENABLE_TRIGGER`: return `false` or `true` to indicate
/// whether triggers are disabled or enabled
- /// - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: return `false` or `true` to
- /// indicate whether fts3_tokenizer are disabled or enabled
- /// - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: return `false` to indicate
+ /// - `SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER`: return `false` or `true` to
+ /// indicate whether `fts3_tokenizer` are disabled or enabled
+ /// - `SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`: return `false` to indicate
/// checkpoints-on-close are not disabled or `true` if they are
- /// - SQLITE_DBCONFIG_ENABLE_QPSG: return `false` or `true` to indicate
+ /// - `SQLITE_DBCONFIG_ENABLE_QPSG`: return `false` or `true` to indicate
/// whether the QPSG is disabled or enabled
- /// - SQLITE_DBCONFIG_TRIGGER_EQP: return `false` to indicate
+ /// - `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 {
let mut val = 0;
- check!(ffi::sqlite3_db_config(
+ check(ffi::sqlite3_db_config(
c.db(),
config as c_int,
-1,
- &mut val
- ));
+ &mut val,
+ ))?;
Ok(val != 0)
}
}
/// Make configuration changes to a database connection
///
- /// - SQLITE_DBCONFIG_ENABLE_FKEY: `false` to disable FK enforcement, `true`
- /// to enable FK enforcement
- /// - SQLITE_DBCONFIG_ENABLE_TRIGGER: `false` to disable triggers, `true` to
- /// enable triggers
- /// - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: `false` to disable
- /// fts3_tokenizer(), `true` to enable fts3_tokenizer()
- /// - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: `false` (the default) to enable
+ /// - `SQLITE_DBCONFIG_ENABLE_FKEY`: `false` to disable FK enforcement,
+ /// `true` to enable FK enforcement
+ /// - `SQLITE_DBCONFIG_ENABLE_TRIGGER`: `false` to disable triggers, `true`
+ /// to enable triggers
+ /// - `SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER`: `false` to disable
+ /// `fts3_tokenizer()`, `true` to enable `fts3_tokenizer()`
+ /// - `SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`: `false` (the default) to enable
/// checkpoints-on-close, `true` to disable them
- /// - SQLITE_DBCONFIG_ENABLE_QPSG: `false` to disable the QPSG, `true` to
+ /// - `SQLITE_DBCONFIG_ENABLE_QPSG`: `false` to disable the QPSG, `true` to
/// enable QPSG
- /// - SQLITE_DBCONFIG_TRIGGER_EQP: `false` to disable output for trigger
+ /// - `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 {
let mut val = 0;
- check!(ffi::sqlite3_db_config(
+ check(ffi::sqlite3_db_config(
c.db(),
config as c_int,
if new_val { 1 } else { 0 },
- &mut val
- ));
+ &mut val,
+ ))?;
Ok(val != 0)
}
}
@@ -120,13 +126,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 +142,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 +151,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..5f935fa 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,
@@ -54,11 +58,11 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
if length > c_int::max_value() as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else if length == 0 {
- ffi::sqlite3_result_zeroblob(ctx, 0)
+ ffi::sqlite3_result_zeroblob(ctx, 0);
} else {
ffi::sqlite3_result_blob(
ctx,
- b.as_ptr() as *const c_void,
+ b.as_ptr().cast::<c_void>(),
length as c_int,
ffi::SQLITE_TRANSIENT(),
);
diff --git a/src/error.rs b/src/error.rs
index 98583cb..129f697 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,6 +1,6 @@
use crate::types::FromSqlError;
use crate::types::Type;
-use crate::{errmsg_to_string, ffi};
+use crate::{errmsg_to_string, ffi, Result};
use std::error;
use std::fmt;
use std::os::raw::c_int;
@@ -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,40 +68,50 @@ 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")]
+ #[cfg_attr(docsrs, doc(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")]
+ #[cfg_attr(docsrs, doc(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")]
+ #[cfg_attr(docsrs, doc(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")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "vtab")))]
#[allow(dead_code)]
ModuleError(String),
/// An unwinding panic occurs in an UDF (user-defined function).
#[cfg(feature = "functions")]
+ #[cfg_attr(docsrs, doc(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")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
GetAuxWrongType,
/// Error when the SQL contains multiple statements.
@@ -111,9 +122,11 @@ 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")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "blob")))]
BlobSizeError,
}
@@ -165,12 +178,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,17 +196,13 @@ 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.
match err {
FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
- #[cfg(feature = "i128_blob")]
- FromSqlError::InvalidI128Size(_) => {
- Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
- }
- #[cfg(feature = "uuid")]
- FromSqlError::InvalidUuidSize(_) => {
+ FromSqlError::InvalidBlobSize { .. } => {
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
}
FromSqlError::Other(source) => {
@@ -326,10 +337,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
@@ -339,11 +352,10 @@ pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error {
error_from_sqlite_code(code, message)
}
-macro_rules! check {
- ($funcall:expr) => {{
- let rc = $funcall;
- if rc != crate::ffi::SQLITE_OK {
- return Err(crate::error::error_from_sqlite_code(rc, None).into());
- }
- }};
+pub fn check(code: c_int) -> Result<()> {
+ if code != crate::ffi::SQLITE_OK {
+ Err(crate::error::error_from_sqlite_code(code, None))
+ } else {
+ Ok(())
+ }
}
diff --git a/src/functions.rs b/src/functions.rs
index 3531391..e613182 100644
--- a/src/functions.rs
+++ b/src/functions.rs
@@ -1,17 +1,17 @@
-//! `feature = "functions"` Create or redefine SQL functions.
+//! Create or redefine SQL functions.
//!
//! # Example
//!
//! 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;
@@ -84,27 +84,24 @@ unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
ffi::SQLITE_CONSTRAINT
}
- match *err {
- Error::SqliteFailure(ref err, ref s) => {
- ffi::sqlite3_result_error_code(ctx, err.extended_code);
- if let Some(Ok(cstr)) = s.as_ref().map(|s| str_to_cstring(s)) {
- ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
- }
+ if let Error::SqliteFailure(ref err, ref s) = *err {
+ ffi::sqlite3_result_error_code(ctx, err.extended_code);
+ if let Some(Ok(cstr)) = s.as_ref().map(|s| str_to_cstring(s)) {
+ ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
- _ => {
- ffi::sqlite3_result_error_code(ctx, constraint_error_code());
- if let Ok(cstr) = str_to_cstring(&err.to_string()) {
- ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
- }
+ } else {
+ ffi::sqlite3_result_error_code(ctx, constraint_error_code());
+ if let Ok(cstr) = str_to_cstring(&err.to_string()) {
+ ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
}
}
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
- drop(Box::from_raw(p as *mut T));
+ drop(Box::from_raw(p.cast::<T>()));
}
-/// `feature = "functions"` Context is a wrapper for the SQLite function
+/// Context is a wrapper for the SQLite function
/// evaluation context.
pub struct Context<'a> {
ctx: *mut sqlite3_context,
@@ -113,11 +110,15 @@ pub struct Context<'a> {
impl Context<'_> {
/// Returns the number of arguments to the function.
+ #[inline]
+ #[must_use]
pub fn len(&self) -> usize {
self.args.len()
}
/// Returns `true` when there is no argument.
+ #[inline]
+ #[must_use]
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
@@ -126,7 +127,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`.
@@ -141,12 +143,7 @@ impl Context<'_> {
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err)
}
- #[cfg(feature = "i128_blob")]
- FromSqlError::InvalidI128Size(_) => {
- Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
- }
- #[cfg(feature = "uuid")]
- FromSqlError::InvalidUuidSize(_) => {
+ FromSqlError::InvalidBlobSize { .. } => {
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
}
})
@@ -156,17 +153,21 @@ 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]
+ #[must_use]
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 +186,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);
@@ -197,17 +198,17 @@ impl Context<'_> {
ffi::sqlite3_set_auxdata(
self.ctx,
arg,
- raw as *mut _,
+ raw.cast(),
Some(free_boxed_value::<AuxInner>),
- )
+ );
};
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,11 +220,42 @@ 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>;
-/// `feature = "functions"` Aggregate is the callback interface for user-defined
+/// Aggregate is the callback interface for user-defined
/// aggregate function.
///
/// `A` is the type of the aggregation context and `T` is the type of the final
@@ -234,25 +266,31 @@ 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
+/// `WindowAggregate` is the callback interface for
/// user-defined aggregate window function.
#[cfg(feature = "window")]
+#[cfg_attr(docsrs, doc(cfg(feature = "window")))]
pub trait WindowAggregate<A, T>: Aggregate<A, T>
where
A: RefUnwindSafe + UnwindSafe,
@@ -292,13 +330,14 @@ bitflags::bitflags! {
}
impl Default for FunctionFlags {
+ #[inline]
fn default() -> FunctionFlags {
FunctionFlags::SQLITE_UTF8
}
}
impl Connection {
- /// `feature = "functions"` Attach a user-defined scalar function to
+ /// Attach a user-defined scalar function to
/// this database connection.
///
/// `fn_name` is the name the function will be accessible from SQL.
@@ -307,12 +346,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 +365,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,6 +374,7 @@ impl Connection {
/// # Failure
///
/// Will return Err if the function could not be attached to the connection.
+ #[inline]
pub fn create_scalar_function<F, T>(
&self,
fn_name: &str,
@@ -350,12 +391,13 @@ impl Connection {
.create_scalar_function(fn_name, n_arg, flags, x_func)
}
- /// `feature = "functions"` Attach a user-defined aggregate function to this
+ /// Attach a user-defined aggregate function to this
/// database 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,
@@ -365,7 +407,7 @@ impl Connection {
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
- D: Aggregate<A, T>,
+ D: Aggregate<A, T> + 'static,
T: ToSql,
{
self.db
@@ -373,12 +415,14 @@ impl Connection {
.create_aggregate_function(fn_name, n_arg, flags, aggr)
}
- /// `feature = "window"` Attach a user-defined aggregate window function to
+ /// 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")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "window")))]
+ #[inline]
pub fn create_window_function<A, W, T>(
&self,
fn_name: &str,
@@ -388,7 +432,7 @@ impl Connection {
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
- W: WindowAggregate<A, T>,
+ W: WindowAggregate<A, T> + 'static,
T: ToSql,
{
self.db
@@ -396,15 +440,17 @@ impl Connection {
.create_window_function(fn_name, n_arg, flags, aggr)
}
- /// `feature = "functions"` Removes a user-defined function from this
+ /// Removes a user-defined function from this
/// 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)
}
@@ -431,7 +477,7 @@ impl InnerConnection {
T: ToSql,
{
let r = catch_unwind(|| {
- let boxed_f: *mut F = ffi::sqlite3_user_data(ctx) as *mut F;
+ let boxed_f: *mut F = ffi::sqlite3_user_data(ctx).cast::<F>();
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
let ctx = Context {
ctx,
@@ -463,7 +509,7 @@ impl InnerConnection {
c_name.as_ptr(),
n_arg,
flags.bits(),
- boxed_f as *mut c_void,
+ boxed_f.cast::<c_void>(),
Some(call_boxed_closure::<F, T>),
None,
None,
@@ -482,7 +528,7 @@ impl InnerConnection {
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
- D: Aggregate<A, T>,
+ D: Aggregate<A, T> + 'static,
T: ToSql,
{
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
@@ -493,7 +539,7 @@ impl InnerConnection {
c_name.as_ptr(),
n_arg,
flags.bits(),
- boxed_aggr as *mut c_void,
+ boxed_aggr.cast::<c_void>(),
None,
Some(call_boxed_step::<A, D, T>),
Some(call_boxed_final::<A, D, T>),
@@ -513,7 +559,7 @@ impl InnerConnection {
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
- W: WindowAggregate<A, T>,
+ W: WindowAggregate<A, T> + 'static,
T: ToSql,
{
let boxed_aggr: *mut W = Box::into_raw(Box::new(aggr));
@@ -524,7 +570,7 @@ impl InnerConnection {
c_name.as_ptr(),
n_arg,
flags.bits(),
- boxed_aggr as *mut c_void,
+ boxed_aggr.cast::<c_void>(),
Some(call_boxed_step::<A, W, T>),
Some(call_boxed_final::<A, W, T>),
Some(call_boxed_value::<A, W, T>),
@@ -571,27 +617,28 @@ unsafe extern "C" fn call_boxed_step<A, D, T>(
D: Aggregate<A, T>,
T: ToSql,
{
- let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
- Some(pac) => pac,
- None => {
- ffi::sqlite3_result_error_nomem(ctx);
- return;
- }
+ let pac = if let Some(pac) = aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
+ pac
+ } else {
+ ffi::sqlite3_result_error_nomem(ctx);
+ return;
};
let r = catch_unwind(|| {
- let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
+ let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx).cast::<D>();
assert!(
!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 {
@@ -617,16 +664,15 @@ unsafe extern "C" fn call_boxed_inverse<A, W, T>(
W: WindowAggregate<A, T>,
T: ToSql,
{
- let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
- Some(pac) => pac,
- None => {
- ffi::sqlite3_result_error_nomem(ctx);
- return;
- }
+ let pac = if let Some(pac) = aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
+ pac
+ } else {
+ ffi::sqlite3_result_error_nomem(ctx);
+ return;
};
let r = catch_unwind(|| {
- let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
+ let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx).cast::<W>();
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
@@ -671,12 +717,13 @@ where
};
let r = catch_unwind(|| {
- let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
+ let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx).cast::<D>();
assert!(
!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(_) => {
@@ -715,7 +762,7 @@ where
};
let r = catch_unwind(|| {
- let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
+ let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx).cast::<W>();
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
@@ -746,7 +793,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 +802,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 +858,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 +867,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!(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 +907,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 +956,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 +971,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 +1037,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 +1054,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 +1075,6 @@ mod test {
("e".to_owned(), 9),
];
assert_eq!(expected, results);
+ Ok(())
}
}
diff --git a/src/hooks.rs b/src/hooks.rs
index 53dc041..f0ae1f3 100644
--- a/src/hooks.rs
+++ b/src/hooks.rs
@@ -1,18 +1,19 @@
-//! `feature = "hooks"` Commit, Data Change and Rollback Notification Callbacks
+//! Commit, Data Change and Rollback Notification Callbacks
#![allow(non_camel_case_types)]
use std::os::raw::{c_char, c_int, c_void};
-use std::panic::catch_unwind;
+use std::panic::{catch_unwind, RefUnwindSafe};
use std::ptr;
use crate::ffi;
use crate::{Connection, InnerConnection};
-/// `feature = "hooks"` Action Codes
+/// Action Codes
#[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,
@@ -35,11 +37,314 @@ impl From<i32> for Action {
}
}
+/// The context recieved by an authorizer hook.
+///
+/// See <https://sqlite.org/c3ref/set_authorizer.html> for more info.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct AuthContext<'c> {
+ /// The action to be authorized.
+ pub action: AuthAction<'c>,
+
+ /// The database name, if applicable.
+ pub database_name: Option<&'c str>,
+
+ /// The inner-most trigger or view responsible for the access attempt.
+ /// `None` if the access attempt was made by top-level SQL code.
+ pub accessor: Option<&'c str>,
+}
+
+/// Actions and arguments found within a statement during
+/// preparation.
+///
+/// See <https://sqlite.org/c3ref/c_alter_table.html> for more info.
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[non_exhaustive]
+#[allow(missing_docs)]
+pub enum AuthAction<'c> {
+ /// This variant is not normally produced by SQLite. You may encounter it
+ // if you're using a different version than what's supported by this library.
+ Unknown {
+ /// The unknown authorization action code.
+ code: i32,
+ /// The third arg to the authorizer callback.
+ arg1: Option<&'c str>,
+ /// The fourth arg to the authorizer callback.
+ arg2: Option<&'c str>,
+ },
+ CreateIndex {
+ index_name: &'c str,
+ table_name: &'c str,
+ },
+ CreateTable {
+ table_name: &'c str,
+ },
+ CreateTempIndex {
+ index_name: &'c str,
+ table_name: &'c str,
+ },
+ CreateTempTable {
+ table_name: &'c str,
+ },
+ CreateTempTrigger {
+ trigger_name: &'c str,
+ table_name: &'c str,
+ },
+ CreateTempView {
+ view_name: &'c str,
+ },
+ CreateTrigger {
+ trigger_name: &'c str,
+ table_name: &'c str,
+ },
+ CreateView {
+ view_name: &'c str,
+ },
+ Delete {
+ table_name: &'c str,
+ },
+ DropIndex {
+ index_name: &'c str,
+ table_name: &'c str,
+ },
+ DropTable {
+ table_name: &'c str,
+ },
+ DropTempIndex {
+ index_name: &'c str,
+ table_name: &'c str,
+ },
+ DropTempTable {
+ table_name: &'c str,
+ },
+ DropTempTrigger {
+ trigger_name: &'c str,
+ table_name: &'c str,
+ },
+ DropTempView {
+ view_name: &'c str,
+ },
+ DropTrigger {
+ trigger_name: &'c str,
+ table_name: &'c str,
+ },
+ DropView {
+ view_name: &'c str,
+ },
+ Insert {
+ table_name: &'c str,
+ },
+ Pragma {
+ pragma_name: &'c str,
+ /// The pragma value, if present (e.g., `PRAGMA name = value;`).
+ pragma_value: Option<&'c str>,
+ },
+ Read {
+ table_name: &'c str,
+ column_name: &'c str,
+ },
+ Select,
+ Transaction {
+ operation: TransactionOperation,
+ },
+ Update {
+ table_name: &'c str,
+ column_name: &'c str,
+ },
+ Attach {
+ filename: &'c str,
+ },
+ Detach {
+ database_name: &'c str,
+ },
+ AlterTable {
+ database_name: &'c str,
+ table_name: &'c str,
+ },
+ Reindex {
+ index_name: &'c str,
+ },
+ Analyze {
+ table_name: &'c str,
+ },
+ CreateVtable {
+ table_name: &'c str,
+ module_name: &'c str,
+ },
+ DropVtable {
+ table_name: &'c str,
+ module_name: &'c str,
+ },
+ Function {
+ function_name: &'c str,
+ },
+ Savepoint {
+ operation: TransactionOperation,
+ savepoint_name: &'c str,
+ },
+ #[cfg(feature = "modern_sqlite")]
+ Recursive,
+}
+
+impl<'c> AuthAction<'c> {
+ fn from_raw(code: i32, arg1: Option<&'c str>, arg2: Option<&'c str>) -> Self {
+ match (code, arg1, arg2) {
+ (ffi::SQLITE_CREATE_INDEX, Some(index_name), Some(table_name)) => Self::CreateIndex {
+ index_name,
+ table_name,
+ },
+ (ffi::SQLITE_CREATE_TABLE, Some(table_name), _) => Self::CreateTable { table_name },
+ (ffi::SQLITE_CREATE_TEMP_INDEX, Some(index_name), Some(table_name)) => {
+ Self::CreateTempIndex {
+ index_name,
+ table_name,
+ }
+ }
+ (ffi::SQLITE_CREATE_TEMP_TABLE, Some(table_name), _) => {
+ Self::CreateTempTable { table_name }
+ }
+ (ffi::SQLITE_CREATE_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => {
+ Self::CreateTempTrigger {
+ trigger_name,
+ table_name,
+ }
+ }
+ (ffi::SQLITE_CREATE_TEMP_VIEW, Some(view_name), _) => {
+ Self::CreateTempView { view_name }
+ }
+ (ffi::SQLITE_CREATE_TRIGGER, Some(trigger_name), Some(table_name)) => {
+ Self::CreateTrigger {
+ trigger_name,
+ table_name,
+ }
+ }
+ (ffi::SQLITE_CREATE_VIEW, Some(view_name), _) => Self::CreateView { view_name },
+ (ffi::SQLITE_DELETE, Some(table_name), None) => Self::Delete { table_name },
+ (ffi::SQLITE_DROP_INDEX, Some(index_name), Some(table_name)) => Self::DropIndex {
+ index_name,
+ table_name,
+ },
+ (ffi::SQLITE_DROP_TABLE, Some(table_name), _) => Self::DropTable { table_name },
+ (ffi::SQLITE_DROP_TEMP_INDEX, Some(index_name), Some(table_name)) => {
+ Self::DropTempIndex {
+ index_name,
+ table_name,
+ }
+ }
+ (ffi::SQLITE_DROP_TEMP_TABLE, Some(table_name), _) => {
+ Self::DropTempTable { table_name }
+ }
+ (ffi::SQLITE_DROP_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => {
+ Self::DropTempTrigger {
+ trigger_name,
+ table_name,
+ }
+ }
+ (ffi::SQLITE_DROP_TEMP_VIEW, Some(view_name), _) => Self::DropTempView { view_name },
+ (ffi::SQLITE_DROP_TRIGGER, Some(trigger_name), Some(table_name)) => Self::DropTrigger {
+ trigger_name,
+ table_name,
+ },
+ (ffi::SQLITE_DROP_VIEW, Some(view_name), _) => Self::DropView { view_name },
+ (ffi::SQLITE_INSERT, Some(table_name), _) => Self::Insert { table_name },
+ (ffi::SQLITE_PRAGMA, Some(pragma_name), pragma_value) => Self::Pragma {
+ pragma_name,
+ pragma_value,
+ },
+ (ffi::SQLITE_READ, Some(table_name), Some(column_name)) => Self::Read {
+ table_name,
+ column_name,
+ },
+ (ffi::SQLITE_SELECT, ..) => Self::Select,
+ (ffi::SQLITE_TRANSACTION, Some(operation_str), _) => Self::Transaction {
+ operation: TransactionOperation::from_str(operation_str),
+ },
+ (ffi::SQLITE_UPDATE, Some(table_name), Some(column_name)) => Self::Update {
+ table_name,
+ column_name,
+ },
+ (ffi::SQLITE_ATTACH, Some(filename), _) => Self::Attach { filename },
+ (ffi::SQLITE_DETACH, Some(database_name), _) => Self::Detach { database_name },
+ (ffi::SQLITE_ALTER_TABLE, Some(database_name), Some(table_name)) => Self::AlterTable {
+ database_name,
+ table_name,
+ },
+ (ffi::SQLITE_REINDEX, Some(index_name), _) => Self::Reindex { index_name },
+ (ffi::SQLITE_ANALYZE, Some(table_name), _) => Self::Analyze { table_name },
+ (ffi::SQLITE_CREATE_VTABLE, Some(table_name), Some(module_name)) => {
+ Self::CreateVtable {
+ table_name,
+ module_name,
+ }
+ }
+ (ffi::SQLITE_DROP_VTABLE, Some(table_name), Some(module_name)) => Self::DropVtable {
+ table_name,
+ module_name,
+ },
+ (ffi::SQLITE_FUNCTION, _, Some(function_name)) => Self::Function { function_name },
+ (ffi::SQLITE_SAVEPOINT, Some(operation_str), Some(savepoint_name)) => Self::Savepoint {
+ operation: TransactionOperation::from_str(operation_str),
+ savepoint_name,
+ },
+ #[cfg(feature = "modern_sqlite")]
+ (ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
+ (code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
+ }
+ }
+}
+
+pub(crate) type BoxedAuthorizer =
+ Box<dyn for<'c> FnMut(AuthContext<'c>) -> Authorization + Send + 'static>;
+
+/// A transaction operation.
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[non_exhaustive]
+#[allow(missing_docs)]
+pub enum TransactionOperation {
+ Unknown,
+ Begin,
+ Release,
+ Rollback,
+}
+
+impl TransactionOperation {
+ fn from_str(op_str: &str) -> Self {
+ match op_str {
+ "BEGIN" => Self::Begin,
+ "RELEASE" => Self::Release,
+ "ROLLBACK" => Self::Rollback,
+ _ => Self::Unknown,
+ }
+ }
+}
+
+/// [`authorizer`](Connection::authorizer) return code
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[non_exhaustive]
+pub enum Authorization {
+ /// Authorize the action.
+ Allow,
+ /// Don't allow access, but don't trigger an error either.
+ Ignore,
+ /// Trigger an error.
+ Deny,
+}
+
+impl Authorization {
+ fn into_raw(self) -> c_int {
+ match self {
+ Self::Allow => ffi::SQLITE_OK,
+ Self::Ignore => ffi::SQLITE_IGNORE,
+ Self::Deny => ffi::SQLITE_DENY,
+ }
+ }
+}
+
impl Connection {
- /// `feature = "hooks"` Register a callback function to be invoked whenever
+ /// Register a callback function to be invoked whenever
/// a transaction is committed.
///
/// The callback returns `true` to rollback.
+ #[inline]
pub fn commit_hook<F>(&self, hook: Option<F>)
where
F: FnMut() -> bool + Send + 'static,
@@ -47,10 +352,9 @@ impl Connection {
self.db.borrow_mut().commit_hook(hook);
}
- /// `feature = "hooks"` Register a callback function to be invoked whenever
+ /// Register a callback function to be invoked whenever
/// a transaction is committed.
- ///
- /// The callback returns `true` to rollback.
+ #[inline]
pub fn rollback_hook<F>(&self, hook: Option<F>)
where
F: FnMut() + Send + 'static,
@@ -58,29 +362,58 @@ impl Connection {
self.db.borrow_mut().rollback_hook(hook);
}
- /// `feature = "hooks"` Register a callback function to be invoked whenever
+ /// Register a callback function to be invoked whenever
/// a row is updated, inserted or deleted in a rowid table.
///
/// The callback parameters are:
///
- /// - the type of database update (SQLITE_INSERT, SQLITE_UPDATE or
- /// SQLITE_DELETE),
+ /// - the type of database update (`SQLITE_INSERT`, `SQLITE_UPDATE` or
+ /// `SQLITE_DELETE`),
/// - the name of the database ("main", "temp", ...),
/// - the name of the table that is updated,
/// - the ROWID of the row that is updated.
+ #[inline]
pub fn update_hook<F>(&self, hook: Option<F>)
where
F: FnMut(Action, &str, &str, i64) + Send + 'static,
{
self.db.borrow_mut().update_hook(hook);
}
+
+ /// 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);
+ }
+
+ /// Register an authorizer callback that's invoked
+ /// as a statement is being prepared.
+ #[inline]
+ pub fn authorizer<'c, F>(&self, hook: Option<F>)
+ where
+ F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,
+ {
+ self.db.borrow_mut().authorizer(hook);
+ }
}
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>);
+ self.authorizer(None::<fn(AuthContext<'_>) -> Authorization>);
}
fn commit_hook<F>(&mut self, hook: Option<F>)
@@ -92,7 +425,7 @@ impl InnerConnection {
F: FnMut() -> bool,
{
let r = catch_unwind(|| {
- let boxed_hook: *mut F = p_arg as *mut F;
+ let boxed_hook: *mut F = p_arg.cast::<F>();
(*boxed_hook)()
});
if let Ok(true) = r {
@@ -118,7 +451,7 @@ impl InnerConnection {
ffi::sqlite3_commit_hook(
self.db(),
Some(call_boxed_closure::<F>),
- boxed_hook as *mut _,
+ boxed_hook.cast(),
)
}
}
@@ -140,10 +473,10 @@ impl InnerConnection {
where
F: FnMut(),
{
- let _ = catch_unwind(|| {
- let boxed_hook: *mut F = p_arg as *mut F;
+ drop(catch_unwind(|| {
+ let boxed_hook: *mut F = p_arg.cast::<F>();
(*boxed_hook)();
- });
+ }));
}
let free_rollback_hook = if hook.is_some() {
@@ -159,7 +492,7 @@ impl InnerConnection {
ffi::sqlite3_rollback_hook(
self.db(),
Some(call_boxed_closure::<F>),
- boxed_hook as *mut _,
+ boxed_hook.cast(),
)
}
}
@@ -180,34 +513,22 @@ impl InnerConnection {
unsafe extern "C" fn call_boxed_closure<F>(
p_arg: *mut c_void,
action_code: c_int,
- db_str: *const c_char,
- tbl_str: *const c_char,
+ p_db_name: *const c_char,
+ p_table_name: *const c_char,
row_id: i64,
) where
F: FnMut(Action, &str, &str, i64),
{
- use std::ffi::CStr;
- use std::str;
-
let action = Action::from(action_code);
- let db_name = {
- let c_slice = CStr::from_ptr(db_str).to_bytes();
- str::from_utf8(c_slice)
- };
- let tbl_name = {
- let c_slice = CStr::from_ptr(tbl_str).to_bytes();
- str::from_utf8(c_slice)
- };
-
- let _ = catch_unwind(|| {
- let boxed_hook: *mut F = p_arg as *mut F;
+ drop(catch_unwind(|| {
+ let boxed_hook: *mut F = p_arg.cast::<F>();
(*boxed_hook)(
action,
- db_name.expect("illegal db name"),
- tbl_name.expect("illegal table name"),
+ expect_utf8(p_db_name, "database name"),
+ expect_utf8(p_table_name, "table name"),
row_id,
);
- });
+ }));
}
let free_update_hook = if hook.is_some() {
@@ -223,7 +544,7 @@ impl InnerConnection {
ffi::sqlite3_update_hook(
self.db(),
Some(call_boxed_closure::<F>),
- boxed_hook as *mut _,
+ boxed_hook.cast(),
)
}
}
@@ -236,38 +557,153 @@ 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.cast::<F>();
+ (*boxed_handler)()
+ });
+ if let Ok(true) = r {
+ 1
+ } else {
+ 0
+ }
+ }
+
+ if let Some(handler) = 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);
+ } else {
+ unsafe { ffi::sqlite3_progress_handler(self.db(), num_ops, None, ptr::null_mut()) }
+ self.progress_handler = None;
+ };
+ }
+
+ fn authorizer<'c, F>(&'c mut self, authorizer: Option<F>)
+ where
+ F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,
+ {
+ unsafe extern "C" fn call_boxed_closure<'c, F>(
+ p_arg: *mut c_void,
+ action_code: c_int,
+ param1: *const c_char,
+ param2: *const c_char,
+ db_name: *const c_char,
+ trigger_or_view_name: *const c_char,
+ ) -> c_int
+ where
+ F: FnMut(AuthContext<'c>) -> Authorization + Send + 'static,
+ {
+ catch_unwind(|| {
+ let action = AuthAction::from_raw(
+ action_code,
+ expect_optional_utf8(param1, "authorizer param 1"),
+ expect_optional_utf8(param2, "authorizer param 2"),
+ );
+ let auth_ctx = AuthContext {
+ action,
+ database_name: expect_optional_utf8(db_name, "database name"),
+ accessor: expect_optional_utf8(
+ trigger_or_view_name,
+ "accessor (inner-most trigger or view)",
+ ),
+ };
+ let boxed_hook: *mut F = p_arg.cast::<F>();
+ (*boxed_hook)(auth_ctx)
+ })
+ .map_or_else(|_| ffi::SQLITE_ERROR, Authorization::into_raw)
+ }
+
+ let callback_fn = authorizer
+ .as_ref()
+ .map(|_| call_boxed_closure::<'c, F> as unsafe extern "C" fn(_, _, _, _, _, _) -> _);
+ let boxed_authorizer = authorizer.map(Box::new);
+
+ match unsafe {
+ ffi::sqlite3_set_authorizer(
+ self.db(),
+ callback_fn,
+ boxed_authorizer
+ .as_ref()
+ .map_or_else(ptr::null_mut, |f| &**f as *const F as *mut _),
+ )
+ } {
+ ffi::SQLITE_OK => {
+ self.authorizer = boxed_authorizer.map(|ba| ba as _);
+ }
+ err_code => {
+ // The only error that `sqlite3_set_authorizer` returns is `SQLITE_MISUSE`
+ // when compiled with `ENABLE_API_ARMOR` and the db pointer is invalid.
+ // This library does not allow constructing a null db ptr, so if this branch
+ // is hit, something very bad has happened. Panicking instead of returning
+ // `Result` keeps this hook's API consistent with the others.
+ panic!("unexpectedly failed to set_authorizer: {}", unsafe {
+ crate::error::error_from_handle(self.db(), err_code)
+ });
+ }
+ }
+ }
}
unsafe fn free_boxed_hook<F>(p: *mut c_void) {
- drop(Box::from_raw(p as *mut F));
+ drop(Box::from_raw(p.cast::<F>()));
+}
+
+unsafe fn expect_utf8<'a>(p_str: *const c_char, description: &'static str) -> &'a str {
+ expect_optional_utf8(p_str, description)
+ .unwrap_or_else(|| panic!("received empty {}", description))
+}
+
+unsafe fn expect_optional_utf8<'a>(
+ p_str: *const c_char,
+ description: &'static str,
+) -> Option<&'a str> {
+ if p_str.is_null() {
+ return None;
+ }
+ std::str::from_utf8(std::ffi::CStr::from_ptr(p_str).to_bytes())
+ .unwrap_or_else(|_| panic!("received non-utf8 string as {}", description))
+ .into()
}
#[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);
- }
+ static CALLED: AtomicBool = AtomicBool::new(false);
db.commit_hook(Some(|| {
CALLED.store(true, Ordering::Relaxed);
false
}));
- db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
- .unwrap();
+ db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
assert!(CALLED.load(Ordering::Relaxed));
+ 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,30 +712,27 @@ 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);
- }
+ static CALLED: AtomicBool = AtomicBool::new(false);
db.rollback_hook(Some(|| {
CALLED.store(true, Ordering::Relaxed);
}));
- db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
- .unwrap();
+ db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")?;
assert!(CALLED.load(Ordering::Relaxed));
+ 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);
- }
+ static CALLED: AtomicBool = AtomicBool::new(false);
db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
assert_eq!(Action::SQLITE_INSERT, action);
assert_eq!("main", db);
@@ -307,8 +740,76 @@ mod test {
assert_eq!(1, row_id);
CALLED.store(true, Ordering::Relaxed);
}));
- db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
- db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
+ db.execute_batch("CREATE TABLE foo (t TEXT)")?;
+ db.execute_batch("INSERT INTO foo VALUES ('lisa')")?;
assert!(CALLED.load(Ordering::Relaxed));
+ 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(())
+ }
+
+ #[test]
+ fn test_authorizer() -> Result<()> {
+ use super::{AuthAction, AuthContext, Authorization};
+
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (public TEXT, private TEXT)")
+ .unwrap();
+
+ let authorizer = move |ctx: AuthContext<'_>| match ctx.action {
+ AuthAction::Read { column_name, .. } if column_name == "private" => {
+ Authorization::Ignore
+ }
+ AuthAction::DropTable { .. } => Authorization::Deny,
+ AuthAction::Pragma { .. } => panic!("shouldn't be called"),
+ _ => Authorization::Allow,
+ };
+
+ db.authorizer(Some(authorizer));
+ db.execute_batch(
+ "BEGIN TRANSACTION; INSERT INTO foo VALUES ('pub txt', 'priv txt'); COMMIT;",
+ )
+ .unwrap();
+ db.query_row_and_then("SELECT * FROM foo", [], |row| -> Result<()> {
+ assert_eq!(row.get::<_, String>("public")?, "pub txt");
+ assert!(row.get::<_, Option<String>>("private")?.is_none());
+ Ok(())
+ })
+ .unwrap();
+ db.execute_batch("DROP TABLE foo").unwrap_err();
+
+ db.authorizer(None::<fn(AuthContext<'_>) -> Authorization>);
+ db.execute_batch("PRAGMA user_version=1").unwrap(); // Disallowed by first authorizer, but it's now removed.
+
+ Ok(())
}
}
diff --git a/src/inner_connection.rs b/src/inner_connection.rs
index dd786fe..0ea630e 100644
--- a/src/inner_connection.rs
+++ b/src/inner_connection.rs
@@ -13,7 +13,6 @@ use super::{Connection, InterruptHandle, OpenFlags, Result};
use crate::error::{error_from_handle, error_from_sqlite_code, Error};
use crate::raw_statement::RawStatement;
use crate::statement::Statement;
-use crate::unlock_notify;
use crate::version::version_number;
pub struct InnerConnection {
@@ -31,11 +30,18 @@ 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>>,
+ #[cfg(feature = "hooks")]
+ pub authorizer: Option<crate::hooks::BoxedAuthorizer>,
owned: bool,
}
+unsafe impl Send for InnerConnection {}
+
impl InnerConnection {
#[allow(clippy::mutex_atomic)]
+ #[inline]
pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
InnerConnection {
db,
@@ -46,6 +52,10 @@ impl InnerConnection {
free_rollback_hook: None,
#[cfg(feature = "hooks")]
free_update_hook: None,
+ #[cfg(feature = "hooks")]
+ progress_handler: None,
+ #[cfg(feature = "hooks")]
+ authorizer: None,
owned,
}
}
@@ -55,8 +65,6 @@ impl InnerConnection {
flags: OpenFlags,
vfs: Option<&CStr>,
) -> Result<InnerConnection> {
- #[cfg(not(feature = "bundled"))]
- ensure_valid_sqlite_version();
ensure_safe_sqlite_threading_mode()?;
// Replicate the check for sane open flags from SQLite, because the check in
@@ -121,14 +129,17 @@ impl InnerConnection {
}
}
+ #[inline]
pub fn db(&self) -> *mut ffi::sqlite3 {
self.db
}
- pub fn decode_result(&mut self, code: c_int) -> Result<()> {
+ #[inline]
+ pub fn decode_result(&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,44 +176,44 @@ 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) };
+ pub unsafe fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
+ let r = ffi::sqlite3_enable_load_extension(self.db, onoff);
self.decode_result(r)
}
#[cfg(feature = "load_extension")]
- pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> {
+ pub unsafe fn load_extension(
+ &self,
+ dylib_path: &Path,
+ entry_point: Option<&str>,
+ ) -> Result<()> {
let dylib_str = super::path_to_cstring(dylib_path)?;
- unsafe {
- let mut errmsg: *mut c_char = ptr::null_mut();
- let r = if let Some(entry_point) = entry_point {
- let c_entry = crate::str_to_cstring(entry_point)?;
- ffi::sqlite3_load_extension(
- self.db,
- dylib_str.as_ptr(),
- c_entry.as_ptr(),
- &mut errmsg,
- )
- } else {
- ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg)
- };
- if r == ffi::SQLITE_OK {
- Ok(())
- } else {
- let message = super::errmsg_to_string(errmsg);
- ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
- Err(error_from_sqlite_code(r, Some(message)))
- }
+ let mut errmsg: *mut c_char = ptr::null_mut();
+ let r = if let Some(entry_point) = entry_point {
+ let c_entry = crate::str_to_cstring(entry_point)?;
+ ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), c_entry.as_ptr(), &mut errmsg)
+ } else {
+ ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg)
+ };
+ if r == ffi::SQLITE_OK {
+ Ok(())
+ } else {
+ let message = super::errmsg_to_string(errmsg);
+ ffi::sqlite3_free(errmsg.cast::<::std::os::raw::c_void>());
+ Err(error_from_sqlite_code(r, Some(message)))
}
}
+ #[inline]
pub fn last_insert_rowid(&self) -> i64 {
unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
}
@@ -211,35 +222,37 @@ impl InnerConnection {
let mut c_stmt = ptr::null_mut();
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
let mut c_tail = ptr::null();
+ #[cfg(not(feature = "unlock_notify"))]
let r = unsafe {
- if cfg!(feature = "unlock_notify") {
- let mut rc;
- loop {
- rc = ffi::sqlite3_prepare_v2(
- self.db(),
- c_sql,
- len,
- &mut c_stmt as *mut *mut ffi::sqlite3_stmt,
- &mut c_tail as *mut *const c_char,
- );
- if !unlock_notify::is_locked(self.db, rc) {
- break;
- }
- rc = unlock_notify::wait_for_unlock_notify(self.db);
- if rc != ffi::SQLITE_OK {
- break;
- }
- }
- rc
- } else {
- ffi::sqlite3_prepare_v2(
+ ffi::sqlite3_prepare_v2(
+ self.db(),
+ c_sql,
+ len,
+ &mut c_stmt as *mut *mut ffi::sqlite3_stmt,
+ &mut c_tail as *mut *const c_char,
+ )
+ };
+ #[cfg(feature = "unlock_notify")]
+ let r = unsafe {
+ use crate::unlock_notify;
+ let mut rc;
+ loop {
+ rc = ffi::sqlite3_prepare_v2(
self.db(),
c_sql,
len,
&mut c_stmt as *mut *mut ffi::sqlite3_stmt,
&mut c_tail as *mut *const c_char,
- )
+ );
+ if !unlock_notify::is_locked(self.db, rc) {
+ break;
+ }
+ rc = unlock_notify::wait_for_unlock_notify(self.db);
+ if rc != ffi::SQLITE_OK {
+ break;
+ }
}
+ rc
};
// If there is an error, *ppStmt is set to NULL.
self.decode_result(r)?;
@@ -250,7 +263,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 +275,12 @@ impl InnerConnection {
}))
}
- pub fn changes(&mut self) -> usize {
+ #[inline]
+ pub fn changes(&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 }
}
@@ -286,12 +300,19 @@ impl InnerConnection {
false
}
+ #[cfg(feature = "modern_sqlite")] // 3.10.0
+ pub fn cache_flush(&mut self) -> Result<()> {
+ crate::error::check(unsafe { ffi::sqlite3_db_cacheflush(self.db()) })
+ }
+
#[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;
@@ -305,55 +326,6 @@ impl Drop for InnerConnection {
}
}
-#[cfg(not(feature = "bundled"))]
-static SQLITE_VERSION_CHECK: std::sync::Once = std::sync::Once::new();
-#[cfg(not(feature = "bundled"))]
-pub static BYPASS_VERSION_CHECK: AtomicBool = AtomicBool::new(false);
-
-#[cfg(not(feature = "bundled"))]
-fn ensure_valid_sqlite_version() {
- use crate::version::version;
-
- SQLITE_VERSION_CHECK.call_once(|| {
- let version_number = version_number();
-
- // Check our hard floor.
- if version_number < 3_006_008 {
- panic!("rusqlite requires SQLite 3.6.8 or newer");
- }
-
- // Check that the major version number for runtime and buildtime match.
- let buildtime_major = ffi::SQLITE_VERSION_NUMBER / 1_000_000;
- let runtime_major = version_number / 1_000_000;
- if buildtime_major != runtime_major {
- panic!(
- "rusqlite was built against SQLite {} but is running with SQLite {}",
- str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
- version()
- );
- }
-
- if BYPASS_VERSION_CHECK.load(Ordering::Relaxed) {
- return;
- }
-
- // Check that the runtime version number is compatible with the version number
- // we found at build-time.
- if version_number < ffi::SQLITE_VERSION_NUMBER {
- panic!(
- "\
-rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fix this, either:
-* Recompile rusqlite and link against the SQLite version you are using at runtime, or
-* Call rusqlite::bypass_sqlite_version_check() prior to your first connection attempt. Doing this
- means you're sure everything will work correctly even though the runtime version is older than
- the version we found at build time.",
- str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
- version()
- );
- }
- });
-}
-
#[cfg(not(any(target_arch = "wasm32")))]
static SQLITE_INIT: std::sync::Once = std::sync::Once::new();
@@ -410,19 +382,13 @@ 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);
- }
+ assert!(ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) == ffi::SQLITE_OK && ffi::sqlite3_initialize() == ffi::SQLITE_OK,
+ "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."
+ );
}
});
Ok(())
diff --git a/src/lib.rs b/src/lib.rs
index 53f1773..1fa7a33 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)?,
@@ -48,6 +48,7 @@
//! }
//! ```
#![warn(missing_docs)]
+#![cfg_attr(docsrs, feature(doc_cfg))]
pub use libsqlite3_sys as ffi;
@@ -73,53 +74,62 @@ pub use crate::cache::CachedStatement;
pub use crate::column::Column;
pub use crate::error::Error;
pub use crate::ffi::ErrorCode;
-#[cfg(feature = "hooks")]
-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};
pub use crate::types::ToSql;
pub use crate::version::*;
-#[macro_use]
mod error;
#[cfg(feature = "backup")]
+#[cfg_attr(docsrs, doc(cfg(feature = "backup")))]
pub mod backup;
#[cfg(feature = "blob")]
+#[cfg_attr(docsrs, doc(cfg(feature = "blob")))]
pub mod blob;
mod busy;
mod cache;
#[cfg(feature = "collation")]
+#[cfg_attr(docsrs, doc(cfg(feature = "collation")))]
mod collation;
mod column;
pub mod config;
#[cfg(any(feature = "functions", feature = "vtab"))]
mod context;
#[cfg(feature = "functions")]
+#[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
pub mod functions;
#[cfg(feature = "hooks")]
-mod hooks;
+#[cfg_attr(docsrs, doc(cfg(feature = "hooks")))]
+pub mod hooks;
mod inner_connection;
#[cfg(feature = "limits")]
+#[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
pub mod limits;
#[cfg(feature = "load_extension")]
mod load_extension_guard;
+mod params;
mod pragma;
mod raw_statement;
mod row;
#[cfg(feature = "session")]
+#[cfg_attr(docsrs, doc(cfg(feature = "session")))]
pub mod session;
mod statement;
#[cfg(feature = "trace")]
+#[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
pub mod trace;
mod transaction;
pub mod types;
+#[cfg(feature = "unlock_notify")]
mod unlock_notify;
mod version;
#[cfg(feature = "vtab")]
+#[cfg_attr(docsrs, doc(cfg(feature = "vtab")))]
pub mod vtab;
pub(crate) mod util;
@@ -127,11 +137,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
///
@@ -145,16 +160,18 @@ pub const NO_PARAMS: &[&dyn ToSql] = &[];
/// }
///
/// fn add_person(conn: &Connection, person: &Person) -> Result<()> {
-/// conn.execute("INSERT INTO person (name, age_in_years, data)
+/// conn.execute(
+/// "INSERT INTO person (name, age_in_years, data)
/// VALUES (?1, ?2, ?3)",
-/// params![person.name, person.age_in_years, person.data])?;
+/// params![person.name, person.age_in_years, person.data],
+/// )?;
/// Ok(())
/// }
/// ```
#[macro_export]
macro_rules! params {
() => {
- $crate::NO_PARAMS
+ &[] as &[&dyn $crate::ToSql]
};
($($param:expr),+ $(,)?) => {
&[$(&$param as &dyn $crate::ToSql),+] as &[&dyn $crate::ToSql]
@@ -176,14 +193,14 @@ 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!{
+/// named_params! {
/// ":name": person.name,
/// ":age": person.age_in_years,
/// ":data": person.data,
-/// }
+/// },
/// )?;
/// Ok(())
/// }
@@ -191,12 +208,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)]
};
}
@@ -241,10 +258,10 @@ fn str_to_cstring(s: &str) -> Result<SmallCString> {
fn str_for_sqlite(s: &[u8]) -> Result<(*const c_char, c_int, ffi::sqlite3_destructor_type)> {
let len = len_as_c_int(s.len())?;
let (ptr, dtor_info) = if len != 0 {
- (s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT())
+ (s.as_ptr().cast::<c_char>(), ffi::SQLITE_TRANSIENT())
} else {
// Return a pointer guaranteed to live forever
- ("".as_ptr() as *const c_char, ffi::SQLITE_STATIC())
+ ("".as_ptr().cast::<c_char>(), ffi::SQLITE_STATIC())
};
Ok((ptr, len, dtor_info))
}
@@ -275,7 +292,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 +304,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,7 +319,8 @@ pub enum DatabaseName<'a> {
feature = "modern_sqlite"
))]
impl DatabaseName<'_> {
- fn to_cstring(&self) -> Result<util::SmallCString> {
+ #[inline]
+ fn as_cstring(&self) -> Result<util::SmallCString> {
use self::DatabaseName::{Attached, Main, Temp};
match *self {
Main => str_to_cstring("main"),
@@ -316,6 +340,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 +368,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 +379,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 +394,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 +414,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 +437,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)
}
@@ -420,8 +450,9 @@ impl Connection {
///
/// # Failure
///
- /// Will return `Err` if vfs` cannot be converted to a C-compatible
+ /// 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)
}
@@ -436,9 +467,9 @@ impl Connection {
/// fn create_tables(conn: &Connection) -> Result<()> {
/// conn.execute_batch(
/// "BEGIN;
- /// CREATE TABLE foo(x INTEGER);
- /// CREATE TABLE bar(y TEXT);
- /// COMMIT;",
+ /// CREATE TABLE foo(x INTEGER);
+ /// CREATE TABLE bar(y TEXT);
+ /// COMMIT;",
/// )
/// }
/// ```
@@ -471,52 +502,79 @@ 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::{params, Connection};
+ /// fn update_rows(conn: &Connection) {
+ /// match conn.execute(
+ /// "UPDATE foo SET bar = 'baz' WHERE qux = ?1 AND quux = ?2",
+ /// params![1i32, 1.5f64],
+ /// ) {
+ /// 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)))
}
+ /// Returns the path to the database file, if one exists and is known.
+ ///
+ /// Note that in some cases [PRAGMA
+ /// database_list](https://sqlite.org/pragma.html#pragma_database_list) is
+ /// likely to be more robust.
+ #[inline]
+ pub fn path(&self) -> Option<&Path> {
+ self.path.as_deref()
+ }
+
/// Convenience method to prepare and execute a single SQL statement with
/// named parameter(s).
///
/// 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 +585,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 +596,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 +617,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 +642,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 +658,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 +675,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>,
{
@@ -628,7 +686,7 @@ impl Connection {
stmt.check_no_tail()?;
let mut rows = stmt.query(params)?;
- rows.get_expected_row().map_err(E::from).and_then(|r| f(&r))
+ rows.get_expected_row().map_err(E::from).and_then(f)
}
/// Prepare a SQL statement for execution.
@@ -639,8 +697,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 +707,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,72 +721,126 @@ 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();
r.map_err(move |err| (self, err))
}
- /// `feature = "load_extension"` Enable loading of SQLite extensions.
- /// Strongly consider using `LoadExtensionGuard` instead of this function.
+ /// Enable loading of SQLite extensions from both SQL queries and Rust.
///
- /// ## Example
+ /// You must call [`Connection::load_extension_disable`] when you're
+ /// finished loading extensions (failure to call it can lead to bad things,
+ /// see "Safety"), so you should strongly consider using
+ /// [`LoadExtensionGuard`] instead of this function, automatically disables
+ /// extension loading when it goes out of scope.
+ ///
+ /// # Example
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
- /// # use std::path::{Path};
/// fn load_my_extension(conn: &Connection) -> Result<()> {
- /// conn.load_extension_enable()?;
- /// conn.load_extension(Path::new("my_sqlite_extension"), None)?;
- /// conn.load_extension_disable()
+ /// // Safety: We fully trust the loaded extension and execute no untrusted SQL
+ /// // while extension loading is enabled.
+ /// unsafe {
+ /// conn.load_extension_enable()?;
+ /// let r = conn.load_extension("my/trusted/extension", None);
+ /// conn.load_extension_disable()?;
+ /// r
+ /// }
/// }
/// ```
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
+ ///
+ /// # Safety
+ ///
+ /// TLDR: Don't execute any untrusted queries between this call and
+ /// [`Connection::load_extension_disable`].
+ ///
+ /// Perhaps surprisingly, this function does not only allow the use of
+ /// [`Connection::load_extension`] from Rust, but it also allows SQL queries
+ /// to perform [the same operation][loadext]. For example, in the period
+ /// between `load_extension_enable` and `load_extension_disable`, the
+ /// following operation will load and call some function in some dynamic
+ /// library:
+ ///
+ /// ```sql
+ /// SELECT load_extension('why_is_this_possible.dll', 'dubious_func');
+ /// ```
+ ///
+ /// This means that while this is enabled a carefully crafted SQL query can
+ /// be used to escalate a SQL injection attack into code execution.
+ ///
+ /// Safely using this function requires that you trust all SQL queries run
+ /// between when it is called, and when loading is disabled (by
+ /// [`Connection::load_extension_disable`]).
+ ///
+ /// [loadext]: https://www.sqlite.org/lang_corefunc.html#load_extension
#[cfg(feature = "load_extension")]
- pub fn load_extension_enable(&self) -> Result<()> {
+ #[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
+ #[inline]
+ pub unsafe fn load_extension_enable(&self) -> Result<()> {
self.db.borrow_mut().enable_load_extension(1)
}
- /// `feature = "load_extension"` Disable loading of SQLite extensions.
+ /// Disable loading of SQLite extensions.
///
- /// See `load_extension_enable` for an example.
+ /// See [`Connection::load_extension_enable`] for an example.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
+ #[inline]
pub fn load_extension_disable(&self) -> Result<()> {
- self.db.borrow_mut().enable_load_extension(0)
+ // It's always safe to turn off extension loading.
+ unsafe { self.db.borrow_mut().enable_load_extension(0) }
}
- /// `feature = "load_extension"` Load the SQLite extension at `dylib_path`.
- /// `dylib_path` is passed through to `sqlite3_load_extension`, which may
- /// attempt OS-specific modifications if the file cannot be loaded directly.
+ /// Load the SQLite extension at `dylib_path`. `dylib_path` is passed
+ /// through to `sqlite3_load_extension`, which may attempt OS-specific
+ /// modifications if the file cannot be loaded directly (for example
+ /// converting `"some/ext"` to `"some/ext.so"`, `"some\\ext.dll"`, ...).
///
- /// If `entry_point` is `None`, SQLite will attempt to find the entry
- /// point. If it is not `None`, the entry point will be passed through
- /// to `sqlite3_load_extension`.
+ /// If `entry_point` is `None`, SQLite will attempt to find the entry point.
+ /// If it is not `None`, the entry point will be passed through to
+ /// `sqlite3_load_extension`.
///
/// ## Example
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result, LoadExtensionGuard};
- /// # use std::path::{Path};
/// fn load_my_extension(conn: &Connection) -> Result<()> {
- /// let _guard = LoadExtensionGuard::new(conn)?;
- ///
- /// conn.load_extension("my_sqlite_extension", None)
+ /// // Safety: we don't execute any SQL statements while
+ /// // extension loading is enabled.
+ /// let _guard = unsafe { LoadExtensionGuard::new(conn)? };
+ /// // Safety: `my_sqlite_extension` is highly trustworthy.
+ /// unsafe { conn.load_extension("my_sqlite_extension", None) }
/// }
/// ```
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
+ ///
+ /// # Safety
+ ///
+ /// This is equivalent to performing a `dlopen`/`LoadLibrary` on a shared
+ /// library, and calling a function inside, and thus requires that you trust
+ /// the library that you're loading.
+ ///
+ /// That is to say: to safely use this, the code in the extension must be
+ /// sound, trusted, correctly use the SQLite APIs, and not contain any
+ /// memory or thread safety errors.
#[cfg(feature = "load_extension")]
- pub fn load_extension<P: AsRef<Path>>(
+ #[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
+ #[inline]
+ pub unsafe fn load_extension<P: AsRef<Path>>(
&self,
dylib_path: P,
entry_point: Option<&str>,
@@ -750,6 +863,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 +876,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,32 +889,45 @@ 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)
+ self.db.borrow().decode_result(code)
}
/// 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()
+ self.db.borrow().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
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
pub fn is_busy(&self) -> bool {
self.db.borrow().is_busy()
}
+
+ /// Flush caches to disk mid-transaction
+ #[cfg(feature = "modern_sqlite")] // 3.10.0
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+ pub fn cache_flush(&self) -> Result<()> {
+ self.db.borrow_mut().cache_flush()
+ }
}
impl fmt::Debug for Connection {
@@ -810,6 +938,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.
@@ -838,6 +1027,8 @@ bitflags::bitflags! {
const SQLITE_OPEN_PRIVATE_CACHE = 0x0004_0000;
/// The database filename is not allowed to be a symbolic link.
const SQLITE_OPEN_NOFOLLOW = 0x0100_0000;
+ /// Extended result codes.
+ const SQLITE_OPEN_EXRESCODE = 0x0200_0000;
}
}
@@ -867,27 +1058,12 @@ 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);
}
-/// rusqlite performs a one-time check that the runtime SQLite version is at
-/// least as new as the version of SQLite found when rusqlite was built.
-/// Bypassing this check may be dangerous; e.g., if you use features of SQLite
-/// that are not present in the runtime version.
-///
-/// # Safety
-///
-/// If you are sure the runtime version is compatible with the
-/// build-time version for your usage, you can bypass the version check by
-/// calling this function before your first connection attempt.
-pub unsafe fn bypass_sqlite_version_check() {
- #[cfg(not(feature = "bundled"))]
- inner_connection::BYPASS_VERSION_CHECK.store(true, Ordering::Relaxed);
-}
-
/// Allows interrupting a long-running computation.
pub struct InterruptHandle {
db_lock: Arc<Mutex<*mut ffi::sqlite3>>,
@@ -909,7 +1085,7 @@ impl InterruptHandle {
#[cfg(feature = "modern_sqlite")] // 3.7.10
unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
- let db_name = DatabaseName::Main.to_cstring().unwrap();
+ let db_name = DatabaseName::Main.as_cstring().unwrap();
let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
if db_filename.is_null() {
None
@@ -947,44 +1123,38 @@ mod test {
ensure_sync::<InterruptHandle>();
}
- pub fn checked_memory_handle() -> Connection {
+ fn checked_memory_handle() -> Connection {
Connection::open_in_memory().unwrap()
}
#[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 +1166,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]
@@ -1031,7 +1203,7 @@ mod test {
fn test_open_failure() {
let filename = "no_such_file.db";
let result = Connection::open_with_flags(filename, OpenFlags::SQLITE_OPEN_READ_ONLY);
- assert!(!result.is_ok());
+ assert!(result.is_err());
let err = result.err().unwrap();
if let Error::SqliteFailure(e, Some(msg)) = err {
assert_eq!(ErrorCode::CannotOpen, e.code);
@@ -1049,7 +1221,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,27 +1230,28 @@ 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() {
- let db = checked_memory_handle();
+ fn test_close_retry() -> Result<()> {
+ let db = Connection::open_in_memory()?;
// force the DB to be busy by preparing a statement; this must be done at the
// FFI level to allow us to call .close() without dropping the prepared
@@ -1091,7 +1264,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 +1288,7 @@ mod test {
assert_eq!(ffi::SQLITE_OK, unsafe { ffi::sqlite3_finalize(raw_stmt) });
db.close().unwrap();
+ Ok(())
}
#[test]
@@ -1129,8 +1303,8 @@ mod test {
}
#[test]
- fn test_execute_batch() {
- let db = checked_memory_handle();
+ fn test_execute_batch() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(1);
@@ -1138,45 +1312,39 @@ 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() {
- let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
+ fn test_execute() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ 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();
- if err != Error::ExecuteReturnedResults {
- panic!("Unexpected error: {}", err);
- }
+ let err = db.execute("SELECT 1 WHERE 1 < ?", [1i32]).unwrap_err();
+ assert!(
+ err == Error::ExecuteReturnedResults,
+ "Unexpected error: {}",
+ err
+ );
}
#[test]
@@ -1186,7 +1354,7 @@ mod test {
let err = db
.execute(
"CREATE TABLE foo(x INTEGER); CREATE TABLE foo(x INTEGER)",
- NO_PARAMS,
+ [],
)
.unwrap_err();
match err {
@@ -1196,78 +1364,79 @@ mod test {
}
#[test]
- fn test_prepare_column_names() {
- let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
+ fn test_prepare_column_names() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ 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() {
- let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
-
- 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);
-
- 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);
-
- 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);
+ fn test_prepare_execute() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
+
+ 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"])?, 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<?")?;
+ 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() {
- let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
+ fn test_prepare_query() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ 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() {
- let db = checked_memory_handle();
+ fn test_query_map() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1275,21 +1444,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(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() {
- let db = checked_memory_handle();
+ fn test_query_row() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(1);
@@ -1297,128 +1463,130 @@ 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() {
- let db = checked_memory_handle();
+ fn test_optional() -> Result<()> {
+ let db = Connection::open_in_memory()?;
- 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() {
- let db = checked_memory_handle();
+ fn test_pragma_query_row() -> Result<()> {
+ let db = Connection::open_in_memory()?;
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() {
- let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
+ fn test_prepare_failures() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ 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() {
- 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();
+ fn test_last_insert_rowid() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ 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]
- fn test_is_autocommit() {
- let db = checked_memory_handle();
+ fn test_is_autocommit() -> Result<()> {
+ let db = Connection::open_in_memory()?;
assert!(
db.is_autocommit(),
"autocommit expected to be active by default"
);
+ Ok(())
}
#[test]
#[cfg(feature = "modern_sqlite")]
- fn test_is_busy() {
- let db = checked_memory_handle();
+ fn test_is_busy() -> Result<()> {
+ let db = Connection::open_in_memory()?;
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() {
- let db = checked_memory_handle();
+ fn test_statement_debugging() -> Result<()> {
+ let db = Connection::open_in_memory()?;
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")]
@@ -1428,10 +1596,10 @@ mod test {
#[cfg(not(feature = "modern_sqlite"))]
fn check_extended_code(_extended_code: c_int) {}
- let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(x NOT NULL)").unwrap();
+ let db = Connection::open_in_memory()?;
+ 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 +1609,7 @@ mod test {
}
err => panic!("Unexpected error {}", err),
}
+ Ok(())
}
#[test]
@@ -1455,8 +1624,8 @@ mod test {
#[test]
#[cfg(feature = "functions")]
- fn test_interrupt() {
- let db = checked_memory_handle();
+ fn test_interrupt() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let interrupt_handle = db.get_interrupt_handle();
@@ -1468,14 +1637,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 +1652,7 @@ mod test {
panic!("Unexpected error {}", err);
}
}
+ Ok(())
}
#[test]
@@ -1504,36 +1672,48 @@ mod test {
}
#[test]
- fn test_get_raw() {
- let db = checked_memory_handle();
- db.execute_batch("CREATE TABLE foo(i, x);").unwrap();
+ fn test_get_raw() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ 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() {
- let db = checked_memory_handle();
+ fn test_from_handle() -> Result<()> {
+ let db = Connection::open_in_memory()?;
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,8 +1757,8 @@ mod test {
type CustomResult<T> = Result<T, CustomError>;
#[test]
- fn test_query_and_then() {
- let db = checked_memory_handle();
+ fn test_query_and_then() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1586,20 +1766,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 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() {
- let db = checked_memory_handle();
+ fn test_query_and_then_fails() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1607,33 +1786,29 @@ 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() {
- let db = checked_memory_handle();
+ fn test_query_and_then_custom_error() -> CustomResult<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1641,20 +1816,20 @@ 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() {
- let db = checked_memory_handle();
+ fn test_query_and_then_custom_error_fails() -> Result<()> {
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1662,12 +1837,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 +1850,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 +1859,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() {
- let db = checked_memory_handle();
+ fn test_query_row_and_then_custom_error() -> CustomResult<()> {
+ let db = Connection::open_in_memory()?;
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() {
- let db = checked_memory_handle();
+ fn test_query_row_and_then_custom_error_fails() -> Result<()> {
+ let db = Connection::open_in_memory()?;
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 +1913,104 @@ 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 = Connection::open_in_memory()?;
+ 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.as_ref().column_count());
+ Ok(())
+ })
+ }
+ #[test]
+ fn test_dyn_box() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ 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 = Connection::open_in_memory()?;
+ 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 = Connection::open_in_memory()?;
+ 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 = Connection::open_in_memory()?;
+ 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(all(feature = "bundled", not(feature = "bundled-sqlcipher")))] // SQLite >= 3.35.0
+ fn test_returning() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ 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(())
+ }
+
+ #[test]
+ #[cfg(feature = "modern_sqlite")]
+ fn test_cache_flush() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.cache_flush()
}
}
diff --git a/src/limits.rs b/src/limits.rs
index 238ce56..93e0bb0 100644
--- a/src/limits.rs
+++ b/src/limits.rs
@@ -1,21 +1,63 @@
-//! `feature = "limits"` Run-Time Limits
+//! Run-Time Limits
+use crate::{ffi, Connection};
use std::os::raw::c_int;
-use crate::ffi;
-pub use crate::ffi::Limit;
-
-use crate::Connection;
+/// Run-Time limit categories, for use with [`Connection::limit`] and
+/// [`Connection::set_limit`].
+///
+/// See the official documentation for more information:
+/// - <https://www.sqlite.org/c3ref/c_limit_attached.html>
+/// - <https://www.sqlite.org/limits.html>
+#[repr(i32)]
+#[non_exhaustive]
+#[allow(clippy::upper_case_acronyms, non_camel_case_types)]
+#[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
+pub enum Limit {
+ /// The maximum size of any string or BLOB or table row, in bytes.
+ SQLITE_LIMIT_LENGTH = ffi::SQLITE_LIMIT_LENGTH,
+ /// The maximum length of an SQL statement, in bytes.
+ SQLITE_LIMIT_SQL_LENGTH = ffi::SQLITE_LIMIT_SQL_LENGTH,
+ /// The maximum number of columns in a table definition or in the result set
+ /// of a SELECT or the maximum number of columns in an index or in an
+ /// ORDER BY or GROUP BY clause.
+ SQLITE_LIMIT_COLUMN = ffi::SQLITE_LIMIT_COLUMN,
+ /// The maximum depth of the parse tree on any expression.
+ SQLITE_LIMIT_EXPR_DEPTH = ffi::SQLITE_LIMIT_EXPR_DEPTH,
+ /// The maximum number of terms in a compound SELECT statement.
+ SQLITE_LIMIT_COMPOUND_SELECT = ffi::SQLITE_LIMIT_COMPOUND_SELECT,
+ /// The maximum number of instructions in a virtual machine program used to
+ /// implement an SQL statement.
+ SQLITE_LIMIT_VDBE_OP = ffi::SQLITE_LIMIT_VDBE_OP,
+ /// The maximum number of arguments on a function.
+ SQLITE_LIMIT_FUNCTION_ARG = ffi::SQLITE_LIMIT_FUNCTION_ARG,
+ /// The maximum number of attached databases.
+ SQLITE_LIMIT_ATTACHED = ffi::SQLITE_LIMIT_ATTACHED,
+ /// The maximum length of the pattern argument to the LIKE or GLOB
+ /// operators.
+ SQLITE_LIMIT_LIKE_PATTERN_LENGTH = ffi::SQLITE_LIMIT_LIKE_PATTERN_LENGTH,
+ /// The maximum index number of any parameter in an SQL statement.
+ SQLITE_LIMIT_VARIABLE_NUMBER = ffi::SQLITE_LIMIT_VARIABLE_NUMBER,
+ /// The maximum depth of recursion for triggers.
+ SQLITE_LIMIT_TRIGGER_DEPTH = 10,
+ /// The maximum number of auxiliary worker threads that a single prepared
+ /// statement may start.
+ SQLITE_LIMIT_WORKER_THREADS = 11,
+}
impl Connection {
- /// `feature = "limits"` Returns the current value of a limit.
+ /// Returns the current value of a [`Limit`].
+ #[inline]
+ #[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
pub fn limit(&self, limit: Limit) -> i32 {
let c = self.db.borrow();
unsafe { ffi::sqlite3_limit(c.db(), limit as c_int, -1) }
}
- /// `feature = "limits"` Changes the limit to `new_val`, returning the prior
+ /// Changes the [`Limit`] to `new_val`, returning the prior
/// value of the limit.
+ #[inline]
+ #[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
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) }
@@ -24,12 +66,66 @@ impl Connection {
#[cfg(test)]
mod test {
- use crate::ffi::Limit;
- use crate::Connection;
+ use super::*;
+ use crate::{Connection, Result};
+
+ #[test]
+ fn test_limit_values() {
+ assert_eq!(
+ Limit::SQLITE_LIMIT_LENGTH as i32,
+ ffi::SQLITE_LIMIT_LENGTH as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_SQL_LENGTH as i32,
+ ffi::SQLITE_LIMIT_SQL_LENGTH as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_COLUMN as i32,
+ ffi::SQLITE_LIMIT_COLUMN as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_EXPR_DEPTH as i32,
+ ffi::SQLITE_LIMIT_EXPR_DEPTH as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_COMPOUND_SELECT as i32,
+ ffi::SQLITE_LIMIT_COMPOUND_SELECT as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_VDBE_OP as i32,
+ ffi::SQLITE_LIMIT_VDBE_OP as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_FUNCTION_ARG as i32,
+ ffi::SQLITE_LIMIT_FUNCTION_ARG as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_ATTACHED as i32,
+ ffi::SQLITE_LIMIT_ATTACHED as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_LIKE_PATTERN_LENGTH as i32,
+ ffi::SQLITE_LIMIT_LIKE_PATTERN_LENGTH as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_VARIABLE_NUMBER as i32,
+ ffi::SQLITE_LIMIT_VARIABLE_NUMBER as i32,
+ );
+ #[cfg(feature = "bundled")]
+ assert_eq!(
+ Limit::SQLITE_LIMIT_TRIGGER_DEPTH as i32,
+ ffi::SQLITE_LIMIT_TRIGGER_DEPTH as i32,
+ );
+ #[cfg(feature = "bundled")]
+ assert_eq!(
+ Limit::SQLITE_LIMIT_WORKER_THREADS as i32,
+ ffi::SQLITE_LIMIT_WORKER_THREADS as i32,
+ );
+ }
#[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 +164,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..deed3b4 100644
--- a/src/load_extension_guard.rs
+++ b/src/load_extension_guard.rs
@@ -1,7 +1,6 @@
use crate::{Connection, Result};
-/// `feature = "load_extension"` RAII guard temporarily enabling SQLite
-/// extensions to be loaded.
+/// RAII guard temporarily enabling SQLite extensions to be loaded.
///
/// ## Example
///
@@ -9,11 +8,13 @@ use crate::{Connection, Result};
/// # use rusqlite::{Connection, Result, LoadExtensionGuard};
/// # use std::path::{Path};
/// fn load_my_extension(conn: &Connection) -> Result<()> {
-/// let _guard = LoadExtensionGuard::new(conn)?;
-///
-/// conn.load_extension(Path::new("my_sqlite_extension"), None)
+/// unsafe {
+/// let _guard = LoadExtensionGuard::new(conn)?;
+/// conn.load_extension("trusted/sqlite/extension", None)
+/// }
/// }
/// ```
+#[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
pub struct LoadExtensionGuard<'conn> {
conn: &'conn Connection,
}
@@ -22,7 +23,15 @@ impl LoadExtensionGuard<'_> {
/// Attempt to enable loading extensions. Loading extensions will be
/// disabled when this guard goes out of scope. Cannot be meaningfully
/// nested.
- pub fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
+ ///
+ /// # Safety
+ ///
+ /// You must not run untrusted queries while extension loading is enabled.
+ ///
+ /// See the safety comment on [`Connection::load_extension_enable`] for more
+ /// details.
+ #[inline]
+ pub unsafe fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
conn.load_extension_enable()
.map(|_| LoadExtensionGuard { conn })
}
@@ -30,6 +39,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..54aa571
--- /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 + Send + Sync); 0] {}
+impl Params for [&(dyn ToSql + Send + Sync); 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::{params_from_iter, Connection, Result};
+/// 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..1c81c95 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,
@@ -143,7 +143,7 @@ impl Sql {
if ch == quote {
self.buf.push(ch);
}
- self.buf.push(ch)
+ self.buf.push(ch);
}
self.buf.push(quote);
}
@@ -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,10 +195,10 @@ 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)?;
+ f(row)?;
}
Ok(())
}
@@ -212,15 +212,16 @@ impl Connection {
///
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
/// `SELECT * FROM pragma_table_info(?);`
- pub fn pragma<F>(
+ pub fn pragma<F, V>(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
- pragma_value: &dyn ToSql,
+ pragma_value: V,
mut f: F,
) -> Result<()>
where
F: FnMut(&Row<'_>) -> Result<()>,
+ V: ToSql,
{
let mut sql = Sql::new();
sql.push_pragma(schema_name, pragma_name)?;
@@ -228,13 +229,13 @@ impl Connection {
// or it may be separated from the pragma name by an equal sign.
// The two syntaxes yield identical results.
sql.open_brace();
- sql.push_value(pragma_value)?;
+ sql.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)?;
+ f(row)?;
}
Ok(())
}
@@ -243,34 +244,38 @@ impl Connection {
///
/// Some pragmas will return the updated value which cannot be retrieved
/// with this method.
- pub fn pragma_update(
+ pub fn pragma_update<V>(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
- pragma_value: &dyn ToSql,
- ) -> Result<()> {
+ pragma_value: V,
+ ) -> Result<()>
+ where
+ V: ToSql,
+ {
let mut sql = Sql::new();
sql.push_pragma(schema_name, pragma_name)?;
// The argument may be either in parentheses
// or it may be separated from the pragma name by an equal sign.
// The two syntaxes yield identical results.
sql.push_equal_sign();
- sql.push_value(pragma_value)?;
+ sql.push_value(&pragma_value)?;
self.execute_batch(&sql)
}
/// Set a new value to `pragma_name` and return the updated value.
///
/// Only few pragmas automatically return the updated value.
- pub fn pragma_update_and_check<F, T>(
+ pub fn pragma_update_and_check<F, T, V>(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
- pragma_value: &dyn ToSql,
+ pragma_value: V,
f: F,
) -> Result<T>
where
F: FnOnce(&Row<'_>) -> Result<T>,
+ V: ToSql,
{
let mut sql = Sql::new();
sql.push_pragma(schema_name, pragma_name)?;
@@ -278,8 +283,8 @@ impl Connection {
// or it may be separated from the pragma name by an equal sign.
// The two syntaxes yield identical results.
sql.push_equal_sign();
- sql.push_value(pragma_value)?;
- self.query_row(&sql, NO_PARAMS, f)
+ sql.push_value(&pragma_value)?;
+ self.query_row(&sql, [], f)
}
}
@@ -298,15 +303,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 +319,106 @@ 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);
+ // Sanity checks to ensure the move to a generic `ToSql` wasn't breaking
+ assert_eq!(
+ "off",
+ db.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row
+ .get::<_, String>(0))?,
+ );
+ let param: &dyn crate::ToSql = &"OFF";
+ assert_eq!(
+ "off",
+ db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?,
+ );
+ Ok(())
}
#[test]
@@ -432,13 +444,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..8e624dc 100644
--- a/src/raw_statement.rs
+++ b/src/raw_statement.rs
@@ -1,6 +1,6 @@
use super::ffi;
-use super::unlock_notify;
use super::StatementStatus;
+use crate::util::ParamIndexCache;
#[cfg(feature = "modern_sqlite")]
use crate::util::SqliteMallocString;
use std::ffi::CStr;
@@ -14,7 +14,7 @@ pub struct RawStatement {
ptr: *mut ffi::sqlite3_stmt,
tail: usize,
// Cached indices of named parameters, computed on the fly.
- cache: crate::util::ParamIndexCache,
+ cache: ParamIndexCache,
// Cached SQL (trimmed) that we use as the key when we're in the statement
// cache. This is None for statements which didn't come from the statement
// cache.
@@ -29,40 +29,48 @@ pub struct RawStatement {
}
impl RawStatement {
+ #[inline]
pub unsafe fn new(stmt: *mut ffi::sqlite3_stmt, tail: usize) -> RawStatement {
RawStatement {
ptr: stmt,
tail,
- cache: Default::default(),
+ cache: ParamIndexCache::default(),
statement_cache_key: None,
}
}
+ #[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,35 +101,52 @@ impl RawStatement {
}
}
+ #[inline]
+ #[cfg(not(feature = "unlock_notify"))]
pub fn step(&self) -> c_int {
- if cfg!(feature = "unlock_notify") {
- let db = unsafe { ffi::sqlite3_db_handle(self.ptr) };
- let mut rc;
- loop {
- rc = unsafe { ffi::sqlite3_step(self.ptr) };
- if unsafe { !unlock_notify::is_locked(db, rc) } {
- break;
+ unsafe { ffi::sqlite3_step(self.ptr) }
+ }
+
+ #[cfg(feature = "unlock_notify")]
+ pub fn step(&self) -> c_int {
+ use crate::unlock_notify;
+ let mut db = core::ptr::null_mut::<ffi::sqlite3>();
+ loop {
+ unsafe {
+ let mut rc = ffi::sqlite3_step(self.ptr);
+ // Bail out early for success and errors unrelated to locking. We
+ // still need check `is_locked` after this, but checking now lets us
+ // avoid one or two (admittedly cheap) calls into SQLite that we
+ // don't need to make.
+ if (rc & 0xff) != ffi::SQLITE_LOCKED {
+ break rc;
+ }
+ if db.is_null() {
+ db = ffi::sqlite3_db_handle(self.ptr);
}
- rc = unsafe { unlock_notify::wait_for_unlock_notify(db) };
+ if !unlock_notify::is_locked(db, rc) {
+ break rc;
+ }
+ rc = unlock_notify::wait_for_unlock_notify(db);
if rc != ffi::SQLITE_OK {
- break;
+ break rc;
}
self.reset();
}
- rc
- } else {
- unsafe { ffi::sqlite3_step(self.ptr) }
}
}
+ #[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 +157,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 +183,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..c766e50 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,15 +71,23 @@ 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>,
{
AndThenRows { rows: self, map: f }
}
+
+ /// Give access to the underlying statement
+ #[must_use]
+ pub fn as_ref(&self) -> Option<&Statement<'stmt>> {
+ self.stmt
+ }
}
impl<'stmt> Rows<'stmt> {
+ #[inline]
pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
Rows {
stmt: Some(stmt),
@@ -82,6 +95,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 +105,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 +126,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,34 +137,27 @@ 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
.next()
.transpose()
- .map(|row_result| row_result.and_then(|row| (map)(&row)))
+ .map(|row_result| row_result.and_then(map))
}
}
@@ -159,15 +169,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,27 +176,28 @@ where
{
type Item = Result<T, E>;
+ #[inline]
fn next(&mut self) -> Option<Self::Item> {
let map = &mut self.map;
self.rows
.next()
.transpose()
- .map(|row_result| row_result.map_err(E::from).and_then(|row| (map)(&row)))
+ .map(|row_result| row_result.map_err(E::from).and_then(map))
}
}
/// `FallibleStreamingIterator` differs from the standard library's `Iterator`
/// in two ways:
-/// * each call to `next` (sqlite3_step) can fail.
+/// * each call to `next` (`sqlite3_step`) can fail.
/// * returned `Row` is valid until `next` is called again or `Statement` is
/// reset or finalized.
///
/// 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,9 +208,10 @@ 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() {
+ if let Some(stmt) = self.stmt {
+ match stmt.step() {
Ok(true) => {
self.row = Some(Row { stmt });
Ok(())
@@ -223,14 +226,14 @@ impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
self.row = None;
Err(e)
}
- },
- None => {
- self.row = None;
- Ok(())
}
+ } else {
+ self.row = None;
+ Ok(())
}
}
+ #[inline]
fn get(&self) -> Option<&Row<'stmt>> {
self.row.as_ref()
}
@@ -246,7 +249,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
@@ -285,20 +288,11 @@ impl<'stmt> Row<'stmt> {
),
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
FromSqlError::Other(err) => {
- Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
+ Error::FromSqlConversionFailure(idx, value.data_type(), err)
+ }
+ FromSqlError::InvalidBlobSize { .. } => {
+ Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
}
- #[cfg(feature = "i128_blob")]
- FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(
- idx,
- self.stmt.column_name_unwrap(idx).into(),
- value.data_type(),
- ),
- #[cfg(feature = "uuid")]
- FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(
- idx,
- self.stmt.column_name_unwrap(idx).into(),
- value.data_type(),
- ),
})
}
@@ -308,7 +302,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 +311,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 +326,52 @@ 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)
}
}
+impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
+ fn as_ref(&self) -> &Statement<'stmt> {
+ self.stmt
+ }
+}
+
+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 +432,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 +531,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 +554,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..b02d306 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -1,4 +1,4 @@
-//! `feature = "session"` [Session Extension](https://sqlite.org/sessionintro.html)
+//! [Session Extension](https://sqlite.org/sessionintro.html)
#![allow(non_camel_case_types)]
use std::ffi::CStr;
@@ -11,7 +11,7 @@ use std::slice::{from_raw_parts, from_raw_parts_mut};
use fallible_streaming_iterator::FallibleStreamingIterator;
-use crate::error::error_from_sqlite_code;
+use crate::error::{check, error_from_sqlite_code};
use crate::ffi;
use crate::hooks::Action;
use crate::types::ValueRef;
@@ -19,7 +19,7 @@ use crate::{errmsg_to_string, str_to_cstring, Connection, DatabaseName, Result};
// https://sqlite.org/session.html
-/// `feature = "session"` An instance of this object is a session that can be
+/// An instance of this object is a session that can be
/// used to record changes to a database.
pub struct Session<'conn> {
phantom: PhantomData<&'conn Connection>,
@@ -29,21 +29,23 @@ 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<'_>,
) -> Result<Session<'conn>> {
- let name = name.to_cstring()?;
+ let name = name.as_cstring()?;
let db = db.db.borrow_mut().db;
let mut s: *mut ffi::sqlite3_session = ptr::null_mut();
- check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) });
+ check(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) })?;
Ok(Session {
phantom: PhantomData,
@@ -107,56 +109,56 @@ impl Session<'_> {
None
};
let table = table.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
- unsafe { check!(ffi::sqlite3session_attach(self.s, table)) };
- Ok(())
+ check(unsafe { ffi::sqlite3session_attach(self.s, table) })
}
/// Generate a Changeset
pub fn changeset(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut cs: *mut c_void = ptr::null_mut();
- check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) });
+ check(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) })?;
Ok(Changeset { cs, n })
}
/// Write the set of changes represented by this session to `output`.
+ #[inline]
pub fn changeset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3session_changeset_strm(
self.s,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
- });
- Ok(())
+ })
}
/// Generate a Patchset
+ #[inline]
pub fn patchset(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut ps: *mut c_void = ptr::null_mut();
- check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) });
+ check(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) })?;
// TODO Validate: same struct
Ok(Changeset { cs: ps, n })
}
/// Write the set of patches represented by this session to `output`.
+ #[inline]
pub fn patchset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3session_patchset_strm(
self.s,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
- });
- Ok(())
+ })
}
/// Load the difference between tables.
pub fn diff(&mut self, from: DatabaseName<'_>, table: &str) -> Result<()> {
- let from = from.to_cstring()?;
+ let from = from.as_cstring()?;
let table = str_to_cstring(table)?;
let table = table.as_ptr();
unsafe {
@@ -174,16 +176,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 +196,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 +211,7 @@ impl Session<'_> {
}
impl Drop for Session<'_> {
+ #[inline]
fn drop(&mut self) {
if self.filter.is_some() {
self.table_filter(None::<fn(&str) -> bool>);
@@ -212,22 +220,23 @@ impl Drop for Session<'_> {
}
}
-/// `feature = "session"` Invert a changeset
+/// 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;
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changeset_invert_strm(
Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
- });
- Ok(())
+ })
}
-/// `feature = "session"` Combine two changesets
+/// Combine two changesets
+#[inline]
pub fn concat_strm(
input_a: &mut dyn Read,
input_b: &mut dyn Read,
@@ -236,7 +245,7 @@ pub fn concat_strm(
let input_a_ref = &input_a;
let input_b_ref = &input_b;
let output_ref = &output;
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changeset_concat_strm(
Some(x_input),
input_a_ref as *const &mut dyn Read as *mut c_void,
@@ -245,11 +254,10 @@ pub fn concat_strm(
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
- });
- Ok(())
+ })
}
-/// `feature = "session"` Changeset or Patchset
+/// Changeset or Patchset
pub struct Changeset {
cs: *mut c_void,
n: c_int,
@@ -257,19 +265,21 @@ 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();
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs as *mut *mut _)
- });
+ })?;
Ok(Changeset { cs, n })
}
/// 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) });
+ check(unsafe { ffi::sqlite3changeset_start(&mut it as *mut *mut _, self.n, self.cs) })?;
Ok(ChangesetIter {
phantom: PhantomData,
it,
@@ -278,17 +288,19 @@ 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();
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs as *mut *mut _)
- });
+ })?;
Ok(Changeset { cs, n })
}
}
impl Drop for Changeset {
+ #[inline]
fn drop(&mut self) {
unsafe {
ffi::sqlite3_free(self.cs);
@@ -296,7 +308,7 @@ impl Drop for Changeset {
}
}
-/// `feature = "session"` Cursor for iterating over the elements of a changeset
+/// Cursor for iterating over the elements of a changeset
/// or patchset.
pub struct ChangesetIter<'changeset> {
phantom: PhantomData<&'changeset Changeset>,
@@ -306,15 +318,16 @@ 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 {
+ check(unsafe {
ffi::sqlite3changeset_start_strm(
&mut it as *mut *mut _,
Some(x_input),
input as *const &mut dyn Read as *mut c_void,
)
- });
+ })?;
Ok(ChangesetIter {
phantom: PhantomData,
it,
@@ -327,6 +340,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,12 +356,13 @@ impl FallibleStreamingIterator for ChangesetIter<'_> {
}
}
+ #[inline]
fn get(&self) -> Option<&ChangesetItem> {
self.item.as_ref()
}
}
-/// `feature = "session"`
+/// Operation
pub struct Operation<'item> {
table_name: &'item str,
number_of_columns: i32,
@@ -357,27 +372,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);
@@ -385,8 +405,9 @@ impl Drop for ChangesetIter<'_> {
}
}
-/// `feature = "session"` An item passed to a conflict-handler by
-/// `Connection::apply`, or an item generated by `ChangesetIter::next`.
+/// An item passed to a conflict-handler by
+/// [`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,14 +418,15 @@ 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();
- check!(ffi::sqlite3changeset_conflict(
+ check(ffi::sqlite3changeset_conflict(
self.it,
col as i32,
&mut p_value,
- ));
+ ))?;
Ok(ValueRef::from_value(p_value))
}
}
@@ -413,10 +435,11 @@ 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;
- check!(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out));
+ check(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out))?;
Ok(p_out)
}
}
@@ -425,10 +448,11 @@ 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();
- check!(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value,));
+ check(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value))?;
Ok(ValueRef::from_value(p_value))
}
}
@@ -437,28 +461,30 @@ 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();
- check!(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value,));
+ check(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value))?;
Ok(ValueRef::from_value(p_value))
}
}
/// Obtain the current operation
+ #[inline]
pub fn op(&self) -> Result<Operation<'_>> {
let mut number_of_columns = 0;
let mut code = 0;
let mut indirect = 0;
let tab = unsafe {
let mut pz_tab: *const c_char = ptr::null();
- check!(ffi::sqlite3changeset_op(
+ check(ffi::sqlite3changeset_op(
self.it,
&mut pz_tab,
&mut number_of_columns,
&mut code,
- &mut indirect
- ));
+ &mut indirect,
+ ))?;
CStr::from_ptr(pz_tab)
};
let table_name = tab.to_str()?;
@@ -471,21 +497,22 @@ impl ChangesetItem {
}
/// Obtain the primary key definition of a table
+ #[inline]
pub fn pk(&self) -> Result<&[u8]> {
let mut number_of_columns = 0;
unsafe {
let mut pks: *mut c_uchar = ptr::null_mut();
- check!(ffi::sqlite3changeset_pk(
+ check(ffi::sqlite3changeset_pk(
self.it,
&mut pks,
- &mut number_of_columns
- ));
+ &mut number_of_columns,
+ ))?;
Ok(from_raw_parts(pks, number_of_columns as usize))
}
}
}
-/// `feature = "session"` Used to combine two or more changesets or
+/// Used to combine two or more changesets or
/// patchsets
pub struct Changegroup {
cg: *mut ffi::sqlite3_changegroup,
@@ -493,54 +520,57 @@ 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) });
+ check(unsafe { ffi::sqlite3changegroup_new(&mut cg) })?;
Ok(Changegroup { cg })
}
/// Add a changeset
+ #[inline]
pub fn add(&mut self, cs: &Changeset) -> Result<()> {
- check!(unsafe { ffi::sqlite3changegroup_add(self.cg, cs.n, cs.cs) });
- Ok(())
+ check(unsafe { ffi::sqlite3changegroup_add(self.cg, cs.n, cs.cs) })
}
/// 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 {
+ check(unsafe {
ffi::sqlite3changegroup_add_strm(
self.cg,
Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void,
)
- });
- Ok(())
+ })
}
/// Obtain a composite Changeset
+ #[inline]
pub fn output(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut output: *mut c_void = ptr::null_mut();
- check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) });
+ check(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) })?;
Ok(Changeset { cs: output, n })
}
/// 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 {
+ check(unsafe {
ffi::sqlite3changegroup_output_strm(
self.cg,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
- });
- Ok(())
+ })
}
}
impl Drop for Changegroup {
+ #[inline]
fn drop(&mut self) {
unsafe {
ffi::sqlite3changegroup_delete(self.cg);
@@ -549,7 +579,7 @@ impl Drop for Changegroup {
}
impl Connection {
- /// `feature = "session"` Apply a changeset to a database
+ /// Apply a changeset to a database
pub fn apply<F, C>(&self, cs: &Changeset, filter: Option<F>, conflict: C) -> Result<()>
where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
@@ -559,7 +589,7 @@ impl Connection {
let filtered = filter.is_some();
let tuple = &mut (filter, conflict);
- check!(unsafe {
+ check(unsafe {
if filtered {
ffi::sqlite3changeset_apply(
db,
@@ -579,11 +609,10 @@ impl Connection {
tuple as *mut (Option<F>, C) as *mut c_void,
)
}
- });
- Ok(())
+ })
}
- /// `feature = "session"` Apply a changeset to a database
+ /// Apply a changeset to a database
pub fn apply_strm<F, C>(
&self,
input: &mut dyn Read,
@@ -599,7 +628,7 @@ impl Connection {
let filtered = filter.is_some();
let tuple = &mut (filter, conflict);
- check!(unsafe {
+ check(unsafe {
if filtered {
ffi::sqlite3changeset_apply_strm(
db,
@@ -619,17 +648,17 @@ impl Connection {
tuple as *mut (Option<F>, C) as *mut c_void,
)
}
- });
- Ok(())
+ })
}
}
-/// `feature = "session"` Constants passed to the conflict handler
+/// Constants passed to the conflict handler
/// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_CONFLICT) for details.
#[allow(missing_docs)]
#[repr(i32)]
#[derive(Debug, PartialEq)]
#[non_exhaustive]
+#[allow(clippy::upper_case_acronyms)]
pub enum ConflictType {
UNKNOWN = -1,
SQLITE_CHANGESET_DATA = ffi::SQLITE_CHANGESET_DATA,
@@ -651,12 +680,13 @@ impl From<i32> for ConflictType {
}
}
-/// `feature = "session"` Constants returned by the conflict handler
+/// Constants returned by the conflict handler
/// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_ABORT) for details.
#[allow(missing_docs)]
#[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 +774,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());
+ assert!(!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 +854,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 +873,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..60abd90 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()),
@@ -122,17 +135,20 @@ impl Statement<'_> {
/// Execute the prepared statement, returning a handle to the resulting
/// 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.
+ /// Due to lifetime restrictions, the rows handle returned by `query` does
+ /// not 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 {
@@ -312,13 +346,12 @@ impl Statement<'_> {
///
/// fn name_to_person(name: String) -> Result<Person> {
/// // ... check for valid name
- /// Ok(Person { name: name })
+ /// Ok(Person { name })
/// }
///
/// 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)?))?;
+ /// let rows = 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 +362,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 +418,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,44 +437,50 @@ 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)?;
- rows.get_expected_row().and_then(|r| f(&r))
+ rows.get_expected_row().and_then(f)
}
/// 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 +491,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 +515,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 +566,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 +583,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 +626,7 @@ impl Statement<'_> {
/// Ok(())
/// }
/// ```
+ #[inline]
pub fn raw_bind_parameter<T: ToSql>(
&mut self,
one_based_col_index: usize,
@@ -538,6 +651,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 +668,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() };
@@ -601,7 +717,7 @@ impl Statement<'_> {
ffi::sqlite3_bind_blob(
ptr,
col as c_int,
- b.as_ptr() as *const c_void,
+ b.as_ptr().cast::<c_void>(),
length,
ffi::SQLITE_TRANSIENT(),
)
@@ -610,6 +726,7 @@ impl Statement<'_> {
})
}
+ #[inline]
fn execute_with_bound_parameters(&mut self) -> Result<usize> {
self.check_update()?;
let r = self.stmt.step();
@@ -621,27 +738,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 +767,7 @@ impl Statement<'_> {
#[cfg(not(feature = "extra_check"))]
#[inline]
+ #[allow(clippy::unnecessary_wraps)]
fn check_update(&self) -> Result<()> {
Ok(())
}
@@ -671,6 +775,7 @@ impl Statement<'_> {
/// Returns a string containing the SQL text of prepared statement with
/// bound parameters expanded.
#[cfg(feature = "modern_sqlite")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
pub fn expanded_sql(&self) -> Option<String> {
self.stmt
.expanded_sql()
@@ -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 }
}
@@ -763,7 +875,7 @@ impl Statement<'_> {
!text.is_null(),
"unexpected SQLITE_TEXT column type with NULL data"
);
- from_raw_parts(text as *const u8, len as usize)
+ from_raw_parts(text.cast::<u8>(), len as usize)
};
ValueRef::Text(s)
@@ -785,7 +897,7 @@ impl Statement<'_> {
!blob.is_null(),
"unexpected SQLITE_BLOB column type with NULL data"
);
- ValueRef::Blob(unsafe { from_raw_parts(blob as *const u8, len as usize) })
+ ValueRef::Blob(unsafe { from_raw_parts(blob.cast::<u8>(), len as usize) })
} else {
// The return value from sqlite3_column_blob() for a zero-length BLOB
// is a NULL pointer.
@@ -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
@@ -832,6 +946,10 @@ pub enum StatementStatus {
RePrepare = 5,
/// Equivalent to SQLITE_STMTSTATUS_RUN
Run = 6,
+ /// Equivalent to SQLITE_STMTSTATUS_FILTER_MISS
+ FilterMiss = 7,
+ /// Equivalent to SQLITE_STMTSTATUS_FILTER_HIT
+ FilterHit = 8,
/// Equivalent to SQLITE_STMTSTATUS_MEMUSED
MemUsed = 99,
}
@@ -839,124 +957,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 +1150,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 +1201,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..3932976 100644
--- a/src/trace.rs
+++ b/src/trace.rs
@@ -1,4 +1,4 @@
-//! `feature = "trace"` Tracing and profiling functions. Error and warning log.
+//! Tracing and profiling functions. Error and warning log.
use std::ffi::{CStr, CString};
use std::mem;
@@ -11,7 +11,7 @@ use super::ffi;
use crate::error::error_from_sqlite_code;
use crate::{Connection, Result};
-/// `feature = "trace"` Set up the process-wide SQLite error logging callback.
+/// Set up the process-wide SQLite error logging callback.
///
/// # Safety
///
@@ -31,19 +31,18 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
let s = String::from_utf8_lossy(c_slice);
- let _ = catch_unwind(|| callback(err, &s));
+ drop(catch_unwind(|| callback(err, &s)));
}
- let rc = match callback {
- Some(f) => ffi::sqlite3_config(
+ let rc = if let Some(f) = callback {
+ ffi::sqlite3_config(
ffi::SQLITE_CONFIG_LOG,
log_callback as extern "C" fn(_, _, _),
f as *mut c_void,
- ),
- None => {
- let nullptr: *mut c_void = ptr::null_mut();
- ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
- }
+ )
+ } else {
+ let nullptr: *mut c_void = ptr::null_mut();
+ ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
};
if rc == ffi::SQLITE_OK {
@@ -53,8 +52,9 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
}
}
-/// `feature = "trace"` Write a message into the error log established by
+/// 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 {
@@ -63,7 +63,7 @@ pub fn log(err_code: c_int, msg: &str) {
}
impl Connection {
- /// `feature = "trace"` Register or clear a callback function that can be
+ /// Register or clear a callback function that can be
/// used for tracing the execution of SQL statements.
///
/// Prepared statement placeholders are replaced/logged with their assigned
@@ -74,7 +74,7 @@ impl Connection {
let trace_fn: fn(&str) = mem::transmute(p_arg);
let c_slice = CStr::from_ptr(z_sql).to_bytes();
let s = String::from_utf8_lossy(c_slice);
- let _ = catch_unwind(|| trace_fn(&s));
+ drop(catch_unwind(|| trace_fn(&s)));
}
let c = self.db.borrow_mut();
@@ -88,7 +88,7 @@ impl Connection {
}
}
- /// `feature = "trace"` Register or clear a callback function that can be
+ /// Register or clear a callback function that can be
/// used for profiling the execution of SQL statements.
///
/// There can only be a single profiler defined for each database
@@ -108,7 +108,7 @@ impl Connection {
nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32,
);
- let _ = catch_unwind(|| profile_fn(&s, duration));
+ drop(catch_unwind(|| profile_fn(&s, duration)));
}
let c = self.db.borrow_mut();
@@ -127,10 +127,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 +139,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 +168,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..296b2aa 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,51 @@ 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]
+ #[must_use]
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
+ 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 +210,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 +232,7 @@ impl Transaction<'_> {
impl Deref for Transaction<'_> {
type Target = Connection;
+ #[inline]
fn deref(&self) -> &Connection {
self.conn
}
@@ -226,12 +240,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 +264,58 @@ 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]
+ #[must_use]
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
+ 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 +328,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 +339,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 +361,7 @@ impl Savepoint<'_> {
impl Deref for Savepoint<'_> {
type Target = Connection;
+ #[inline]
fn deref(&self) -> &Connection {
self.conn
}
@@ -339,6 +369,7 @@ impl Deref for Savepoint<'_> {
#[allow(unused_must_use)]
impl Drop for Savepoint<'_> {
+ #[inline]
fn drop(&mut self) {
self.finish_();
}
@@ -348,8 +379,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 +402,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 +460,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 +483,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 +504,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 +546,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..6bfc2f4 100644
--- a/src/types/chrono.rs
+++ b/src/types/chrono.rs
@@ -1,26 +1,26 @@
//! 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 chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
impl ToSql for NaiveDate {
+ #[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) {
@@ -81,34 +83,50 @@ impl FromSql for NaiveDateTime {
}
}
-/// Date and time with time zone => UTC RFC3339 timestamp
-/// ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
-impl<Tz: TimeZone> ToSql for DateTime<Tz> {
+/// UTC time => UTC RFC3339 timestamp
+/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
+impl ToSql for DateTime<Utc> {
+ #[inline]
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ let date_str = self.format("%F %T%.f%:z").to_string();
+ Ok(ToSqlOutput::from(date_str))
+ }
+}
+
+/// Local time => UTC RFC3339 timestamp
+/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
+impl ToSql for DateTime<Local> {
+ #[inline]
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ let date_str = self.with_timezone(&Utc).format("%F %T%.f%:z").to_string();
+ Ok(ToSqlOutput::from(date_str))
+ }
+}
+
+/// Date and time with time zone => RFC3339 timestamp
+/// ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM").
+impl ToSql for DateTime<FixedOffset> {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
- Ok(ToSqlOutput::from(self.with_timezone(&Utc).to_rfc3339()))
+ let date_str = self.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,163 +136,188 @@ 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))
}
}
+/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<FixedOffset>`.
+impl FromSql for DateTime<FixedOffset> {
+ #[inline]
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ let s = String::column_result(value)?;
+ Self::parse_from_rfc3339(s.as_str())
+ .or_else(|_| Self::parse_from_str(s.as_str(), "%F %T%.f%:z"))
+ .map_err(|e| FromSqlError::Other(Box::new(e)))
+ }
+}
+
#[cfg(test)]
mod test {
- use crate::{Connection, Result, NO_PARAMS};
- use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
-
- fn checked_memory_handle() -> Connection {
- let db = Connection::open_in_memory().unwrap();
- db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)")
- .unwrap();
- db
+ use crate::{
+ types::{FromSql, ValueRef},
+ Connection, Result,
+ };
+ use chrono::{
+ DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
+ };
+
+ 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_date_time_fixed() -> Result<()> {
+ let db = checked_memory_handle()?;
+ let time = DateTime::parse_from_rfc3339("2020-04-07T11:23:45+04:00").unwrap();
+
+ db.execute("INSERT INTO foo (t) VALUES (?)", [time])?;
+
+ // Stored string should preserve timezone offset
+ let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+ assert!(s.ends_with("+04:00"));
+
+ let v: DateTime<FixedOffset> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+ assert_eq!(time.offset(), v.offset());
+ assert_eq!(time, v);
+ Ok(())
+ }
+
+ #[test]
+ 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..88bdd14 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 {
@@ -14,18 +15,16 @@ pub enum FromSqlError {
/// requested type.
OutOfRange(i64),
- /// `feature = "i128_blob"` Error returned when reading an `i128` from a
- /// blob with a size other than 16. Only available when the `i128_blob`
- /// feature is enabled.
- #[cfg(feature = "i128_blob")]
- InvalidI128Size(usize),
+ /// Error when the blob result returned by SQLite cannot be stored into the
+ /// requested type due to a size mismatch.
+ InvalidBlobSize {
+ /// The expected size of the blob.
+ expected_size: usize,
+ /// The actual size of the blob that was returned.
+ blob_size: usize,
+ },
- /// `feature = "uuid"` Error returned when reading a `uuid` from a blob with
- /// a size other than 16. Only available when the `uuid` feature is enabled.
- #[cfg(feature = "uuid")]
- InvalidUuidSize(usize),
-
- /// An error case available for implementors of the `FromSql` trait.
+ /// An error case available for implementors of the [`FromSql`] trait.
Other(Box<dyn Error + Send + Sync + 'static>),
}
@@ -34,10 +33,16 @@ impl PartialEq for FromSqlError {
match (self, other) {
(FromSqlError::InvalidType, FromSqlError::InvalidType) => true,
(FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
- #[cfg(feature = "i128_blob")]
- (FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
- #[cfg(feature = "uuid")]
- (FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
+ (
+ FromSqlError::InvalidBlobSize {
+ expected_size: es1,
+ blob_size: bs1,
+ },
+ FromSqlError::InvalidBlobSize {
+ expected_size: es2,
+ blob_size: bs2,
+ },
+ ) => es1 == es2 && bs1 == bs2,
(..) => false,
}
}
@@ -48,13 +53,15 @@ impl fmt::Display for FromSqlError {
match *self {
FromSqlError::InvalidType => write!(f, "Invalid type"),
FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i),
- #[cfg(feature = "i128_blob")]
- FromSqlError::InvalidI128Size(s) => {
- write!(f, "Cannot read 128bit value out of {} byte blob", s)
- }
- #[cfg(feature = "uuid")]
- FromSqlError::InvalidUuidSize(s) => {
- write!(f, "Cannot read UUID value out of {} byte blob", s)
+ FromSqlError::InvalidBlobSize {
+ expected_size,
+ blob_size,
+ } => {
+ write!(
+ f,
+ "Cannot read {} byte value out of {} byte blob",
+ expected_size, blob_size
+ )
}
FromSqlError::Other(ref err) => err.fmt(f),
}
@@ -71,48 +78,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 +102,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,70 +140,80 @@ 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(<[u8]>::to_vec)
+ }
+}
+
+impl<const N: usize> FromSql for [u8; N] {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- value.as_blob().map(|b| b.to_vec())
+ let slice = value.as_blob()?;
+ slice.try_into().map_err(|_| FromSqlError::InvalidBlobSize {
+ expected_size: N,
+ blob_size: slice.len(),
+ })
}
}
#[cfg(feature = "i128_blob")]
+#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
impl FromSql for i128 {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- use byteorder::{BigEndian, ByteOrder};
-
- value.as_blob().and_then(|bytes| {
- if bytes.len() == 16 {
- Ok(BigEndian::read_i128(bytes) ^ (1i128 << 127))
- } else {
- Err(FromSqlError::InvalidI128Size(bytes.len()))
- }
- })
+ let bytes = <[u8; 16]>::column_result(value)?;
+ Ok(i128::from_be_bytes(bytes) ^ (1_i128 << 127))
}
}
#[cfg(feature = "uuid")]
+#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
impl FromSql for uuid::Uuid {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- value
- .as_blob()
- .and_then(|bytes| {
- uuid::Builder::from_slice(bytes)
- .map_err(|_| FromSqlError::InvalidUuidSize(bytes.len()))
- })
- .map(|mut builder| builder.build())
+ let bytes = <[u8; 16]>::column_result(value)?;
+ Ok(uuid::Uuid::from_u128(u128::from_be_bytes(bytes)))
}
}
impl<T: FromSql> FromSql for Option<T> {
+ #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Null => Ok(None),
@@ -215,6 +223,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 +232,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 +271,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..4e524b2 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##"
@@ -24,31 +41,33 @@ For example, to store datetimes as `i64`s counting the number of seconds since
the Unix epoch:
```
-use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use rusqlite::Result;
pub struct DateTimeSql(pub time::OffsetDateTime);
impl FromSql for DateTimeSql {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
- i64::column_result(value).map(|as_i64| {
- DateTimeSql(time::OffsetDateTime::from_unix_timestamp(as_i64))
+ i64::column_result(value).and_then(|as_i64| {
+ time::OffsetDateTime::from_unix_timestamp(as_i64)
+ .map(|odt| DateTimeSql(odt))
+ .map_err(|err| FromSqlError::Other(Box::new(err)))
})
}
}
impl ToSql for DateTimeSql {
fn to_sql(&self) -> Result<ToSqlOutput> {
- Ok(self.0.timestamp().into())
+ Ok(self.0.unix_timestamp().into())
}
}
```
"##
)]
-//! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T`
-//! implements `ToSql` or `FromSql` for the cases where you want to know if a
-//! value was NULL (which gets translated to `None`).
+//! [`ToSql`] and [`FromSql`] are also implemented for `Option<T>` where `T`
+//! implements [`ToSql`] or [`FromSql`] for the cases where you want to know if
+//! a value was NULL (which gets translated to `None`).
pub use self::from_sql::{FromSql, FromSqlError, FromSqlResult};
pub use self::to_sql::{ToSql, ToSqlOutput};
@@ -58,14 +77,18 @@ pub use self::value_ref::ValueRef;
use std::fmt;
#[cfg(feature = "chrono")]
+#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
mod chrono;
mod from_sql;
#[cfg(feature = "serde_json")]
+#[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
mod serde_json;
#[cfg(feature = "time")]
+#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
mod time;
mod to_sql;
#[cfg(feature = "url")]
+#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
mod url;
mod value;
mod value_ref;
@@ -79,7 +102,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 +127,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 +139,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 +232,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 +352,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..a9761bd 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,47 +15,39 @@ 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),
- ValueRef::Blob(b) => serde_json::from_slice(b),
- _ => return Err(FromSqlError::InvalidType),
- }
- .map_err(|err| FromSqlError::Other(Box::new(err)))
+ let bytes = value.as_bytes()?;
+ serde_json::from_slice(bytes).map_err(|err| FromSqlError::Other(Box::new(err)))
}
}
#[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..4e2811e 100644
--- a/src/types/time.rs
+++ b/src/types/time.rs
@@ -1,15 +1,35 @@
-//! `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 crate::{Error, Result};
+use time::format_description::well_known::Rfc3339;
+use time::format_description::FormatItem;
+use time::macros::format_description;
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_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%N %z";
+const PRIMITIVE_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] =
+ format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
+const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] =
+ format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
+const PRIMITIVE_DATE_TIME_Z_FORMAT: &[FormatItem<'_>] =
+ format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]Z");
+const OFFSET_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
+ "[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]"
+);
+const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
+ "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour sign:mandatory]:[offset_minute]"
+);
+const LEGACY_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
+ "[year]-[month]-[day] [hour]:[minute]:[second]:[subsecond] [offset_hour sign:mandatory]:[offset_minute]"
+);
impl ToSql for OffsetDateTime {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
- let time_string = self.to_offset(UtcOffset::UTC).format(SQLITE_DATETIME_FMT);
+ // FIXME keep original offset
+ let time_string = self
+ .to_offset(UtcOffset::UTC)
+ .format(&PRIMITIVE_DATE_TIME_Z_FORMAT)
+ .map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(time_string))
}
}
@@ -17,13 +37,29 @@ impl ToSql for OffsetDateTime {
impl FromSql for OffsetDateTime {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
+ if s.len() > 10 && s.as_bytes()[10] == b'T' {
+ // YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM
+ return OffsetDateTime::parse(s, &Rfc3339)
+ .map_err(|err| FromSqlError::Other(Box::new(err)));
+ }
+ let s = s.strip_suffix('Z').unwrap_or(s);
match s.len() {
- 19 => PrimitiveDateTime::parse(s, CURRENT_TIMESTAMP_FMT).map(|d| d.assume_utc()),
- _ => PrimitiveDateTime::parse(s, SQLITE_DATETIME_FMT)
- .map(|d| d.assume_utc())
+ len if len <= 19 => {
+ // TODO YYYY-MM-DDTHH:MM:SS
+ PrimitiveDateTime::parse(s, &PRIMITIVE_SHORT_DATE_TIME_FORMAT)
+ .map(PrimitiveDateTime::assume_utc)
+ }
+ _ if s.as_bytes()[19] == b':' => {
+ // legacy
+ OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
+ }
+ _ if s.as_bytes()[19] == b'.' => OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
.or_else(|err| {
- OffsetDateTime::parse(s, SQLITE_DATETIME_FMT_LEGACY).map_err(|_| err)
+ PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
+ .map(PrimitiveDateTime::assume_utc)
+ .map_err(|_| err)
}),
+ _ => OffsetDateTime::parse(s, &OFFSET_SHORT_DATE_TIME_FORMAT),
}
.map_err(|err| FromSqlError::Other(Box::new(err)))
})
@@ -32,25 +68,20 @@ impl FromSql for OffsetDateTime {
#[cfg(test)]
mod test {
- use crate::{Connection, Result, NO_PARAMS};
- use std::time::Duration;
+ use crate::{Connection, Result};
+ use time::format_description::well_known::Rfc3339;
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
- }
-
#[test]
- fn test_offset_date_time() {
- let db = checked_memory_handle();
+ fn test_offset_date_time() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
let mut ts_vec = vec![];
- let make_datetime =
- |secs, nanos| OffsetDateTime::from_unix_timestamp(secs) + Duration::from_nanos(nanos);
+ let make_datetime = |secs: i128, nanos: i128| {
+ OffsetDateTime::from_unix_timestamp_nanos(1_000_000_000 * secs + nanos).unwrap()
+ };
ts_vec.push(make_datetime(10_000, 0)); //January 1, 1970 2:46:40 AM
ts_vec.push(make_datetime(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
@@ -60,23 +91,78 @@ 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_string_values() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ for (s, t) in vec![
+ (
+ "2013-10-07 08:23:19",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07 08:23:19Z",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07T08:23:19Z",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07 08:23:19.120",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07 08:23:19.120Z",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07T08:23:19.120Z",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07 04:23:19-04:00",
+ Ok(OffsetDateTime::parse("2013-10-07T04:23:19-04:00", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07 04:23:19.120-04:00",
+ Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07T04:23:19.120-04:00",
+ Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
+ ),
+ ] {
+ let result: Result<OffsetDateTime> = db.query_row("SELECT ?", [s], |r| r.get(0));
+ assert_eq!(result, t);
+ }
+ Ok(())
+ }
+
+ #[test]
+ fn test_sqlite_functions() -> Result<()> {
+ let db = Connection::open_in_memory()?;
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 = Connection::open_in_memory()?;
+ 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..2445339 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> {
@@ -15,13 +16,15 @@ pub enum ToSqlOutput<'a> {
/// An owned SQLite-representable value.
Owned(Value),
- /// `feature = "blob"` A BLOB of the given length that is filled with
+ /// A BLOB of the given length that is filled with
/// zeroes.
#[cfg(feature = "blob")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "blob")))]
ZeroBlob(i32),
/// `feature = "array"`
#[cfg(feature = "array")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "array")))]
Array(Array),
}
@@ -31,6 +34,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 +48,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 +64,7 @@ from_value!(isize);
from_value!(u8);
from_value!(u16);
from_value!(u32);
+from_value!(f32);
from_value!(f64);
from_value!(Vec<u8>);
@@ -66,12 +72,15 @@ from_value!(Vec<u8>);
// `i128` needs in `Into<Value>`, but it's probably fine for the moment, and not
// worth adding another case to Value.
#[cfg(feature = "i128_blob")]
+#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
from_value!(i128);
#[cfg(feature = "uuid")]
+#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
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 +94,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 +144,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,54 +162,91 @@ 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")]
+#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
to_sql_self!(i128);
#[cfg(feature = "uuid")]
+#[cfg_attr(docsrs, doc(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<const N: usize> ToSql for [u8; N] {
+ #[inline]
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(&self[..]))
+ }
+}
+
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)),
@@ -221,6 +273,15 @@ mod test {
}
#[test]
+ fn test_u8_array() {
+ let a: [u8; 99] = [0u8; 99];
+ let _a: &[&dyn ToSql] = crate::params![a];
+ let r = ToSql::to_sql(&a);
+
+ assert!(r.is_ok());
+ }
+
+ #[test]
fn test_cow_str() {
use std::borrow::Cow;
let s = "str";
@@ -300,12 +361,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 +373,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 +396,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..ca3ee9f 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,37 +20,41 @@ 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)
}
}
#[cfg(feature = "i128_blob")]
+#[cfg_attr(docsrs, doc(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];
// We store these biased (e.g. with the most significant bit flipped)
// so that comparisons with negative numbers work properly.
- BigEndian::write_i128(&mut buf, i ^ (1i128 << 127));
- Value::Blob(buf)
+ Value::Blob(i128::to_be_bytes(i ^ (1_i128 << 127)).to_vec())
}
}
#[cfg(feature = "uuid")]
+#[cfg_attr(docsrs, doc(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 +63,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 +79,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 +117,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 +128,8 @@ where
impl Value {
/// Returns SQLite fundamental datatype.
+ #[inline]
+ #[must_use]
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..c0d81ca 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,8 @@ pub enum ValueRef<'a> {
impl ValueRef<'_> {
/// Returns SQLite fundamental datatype.
+ #[inline]
+ #[must_use]
pub fn data_type(&self) -> Type {
match *self {
ValueRef::Null => Type::Null,
@@ -34,7 +36,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),
@@ -42,8 +46,23 @@ impl<'a> ValueRef<'a> {
}
}
+ /// If `self` is case `Null` returns None.
+ /// If `self` is case `Integer`, returns the integral value.
+ /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
+ /// InvalidColumnType).
+ #[inline]
+ pub fn as_i64_or_null(&self) -> FromSqlResult<Option<i64>> {
+ match *self {
+ ValueRef::Null => Ok(None),
+ ValueRef::Integer(i) => Ok(Some(i)),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
/// 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),
@@ -51,8 +70,22 @@ impl<'a> ValueRef<'a> {
}
}
+ /// If `self` is case `Null` returns None.
+ /// If `self` is case `Real`, returns the floating point value.
+ /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
+ /// InvalidColumnType).
+ #[inline]
+ pub fn as_f64_or_null(&self) -> FromSqlResult<Option<f64>> {
+ match *self {
+ ValueRef::Null => Ok(None),
+ ValueRef::Real(f) => Ok(Some(f)),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
/// 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) => {
@@ -62,17 +95,69 @@ impl<'a> ValueRef<'a> {
}
}
+ /// If `self` is case `Null` returns None.
+ /// If `self` is case `Text`, returns the string value.
+ /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
+ /// InvalidColumnType).
+ #[inline]
+ pub fn as_str_or_null(&self) -> FromSqlResult<Option<&'a str>> {
+ match *self {
+ ValueRef::Null => Ok(None),
+ ValueRef::Text(t) => std::str::from_utf8(t)
+ .map_err(|e| FromSqlError::Other(Box::new(e)))
+ .map(Some),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
/// 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),
_ => Err(FromSqlError::InvalidType),
}
}
+
+ /// If `self` is case `Null` returns None.
+ /// If `self` is case `Blob`, returns the byte slice.
+ /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
+ /// InvalidColumnType).
+ #[inline]
+ pub fn as_blob_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
+ match *self {
+ ValueRef::Null => Ok(None),
+ ValueRef::Blob(b) => Ok(Some(b)),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
+ /// Returns the byte slice that makes up this ValueRef if it's either
+ /// [`ValueRef::Blob`] or [`ValueRef::Text`].
+ #[inline]
+ pub fn as_bytes(&self) -> FromSqlResult<&'a [u8]> {
+ match self {
+ ValueRef::Text(s) | ValueRef::Blob(s) => Ok(s),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
+ /// If `self` is case `Null` returns None.
+ /// If `self` is [`ValueRef::Blob`] or [`ValueRef::Text`] returns the byte
+ /// slice that makes up this value
+ #[inline]
+ pub fn as_bytes_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
+ match *self {
+ ValueRef::Null => Ok(None),
+ ValueRef::Text(s) | ValueRef::Blob(s) => Ok(Some(s)),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
}
impl From<ValueRef<'_>> for Value {
+ #[inline]
fn from(borrowed: ValueRef<'_>) -> Value {
match borrowed {
ValueRef::Null => Value::Null,
@@ -88,18 +173,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 +203,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(),
@@ -140,7 +229,7 @@ impl<'a> ValueRef<'a> {
!text.is_null(),
"unexpected SQLITE_TEXT value type with NULL data"
);
- let s = from_raw_parts(text as *const u8, len as usize);
+ let s = from_raw_parts(text.cast::<u8>(), len as usize);
ValueRef::Text(s)
}
ffi::SQLITE_BLOB => {
@@ -158,7 +247,7 @@ impl<'a> ValueRef<'a> {
!blob.is_null(),
"unexpected SQLITE_BLOB value type with NULL data"
);
- ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
+ ValueRef::Blob(from_raw_parts(blob.cast::<u8>(), len as usize))
} else {
// The return value from sqlite3_value_blob() for a zero-length BLOB
// is a NULL pointer.
diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs
index af2a06c..8fba6b3 100644
--- a/src/unlock_notify.rs
+++ b/src/unlock_notify.rs
@@ -1,22 +1,17 @@
//! [Unlock Notification](http://sqlite.org/unlock_notify.html)
use std::os::raw::c_int;
-#[cfg(feature = "unlock_notify")]
use std::os::raw::c_void;
-#[cfg(feature = "unlock_notify")]
use std::panic::catch_unwind;
-#[cfg(feature = "unlock_notify")]
use std::sync::{Condvar, Mutex};
use crate::ffi;
-#[cfg(feature = "unlock_notify")]
struct UnlockNotification {
cond: Condvar, // Condition variable to wait on
mutex: Mutex<bool>, // Mutex to protect structure
}
-#[cfg(feature = "unlock_notify")]
#[allow(clippy::mutex_atomic)]
impl UnlockNotification {
fn new() -> UnlockNotification {
@@ -27,30 +22,33 @@ impl UnlockNotification {
}
fn fired(&self) {
- let mut flag = self.mutex.lock().unwrap();
+ let mut flag = unpoison(self.mutex.lock());
*flag = true;
self.cond.notify_one();
}
fn wait(&self) {
- let mut fired = self.mutex.lock().unwrap();
+ let mut fired = unpoison(self.mutex.lock());
while !*fired {
- fired = self.cond.wait(fired).unwrap();
+ fired = unpoison(self.cond.wait(fired));
}
}
}
+#[inline]
+fn unpoison<T>(r: Result<T, std::sync::PoisonError<T>>) -> T {
+ r.unwrap_or_else(std::sync::PoisonError::into_inner)
+}
+
/// This function is an unlock-notify callback
-#[cfg(feature = "unlock_notify")]
unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) {
use std::slice::from_raw_parts;
let args = from_raw_parts(ap_arg as *const &UnlockNotification, n_arg as usize);
for un in args {
- let _ = catch_unwind(std::panic::AssertUnwindSafe(|| un.fired()));
+ drop(catch_unwind(std::panic::AssertUnwindSafe(|| un.fired())));
}
}
-#[cfg(feature = "unlock_notify")]
pub unsafe fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool {
rc == ffi::SQLITE_LOCKED_SHAREDCACHE
|| (rc & 0xFF) == ffi::SQLITE_LOCKED
@@ -87,30 +85,19 @@ pub unsafe fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int {
rc
}
-#[cfg(not(feature = "unlock_notify"))]
-pub unsafe fn is_locked(_db: *mut ffi::sqlite3, _rc: c_int) -> bool {
- unreachable!()
-}
-
-#[cfg(not(feature = "unlock_notify"))]
-pub unsafe fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int {
- unreachable!()
-}
-
-#[cfg(feature = "unlock_notify")]
#[cfg(test)]
mod test {
- use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior, NO_PARAMS};
+ use 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 +109,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..4543c62 100644
--- a/src/util/small_cstr.rs
+++ b/src/util/small_cstr.rs
@@ -1,7 +1,7 @@
use smallvec::{smallvec, SmallVec};
use std::ffi::{CStr, CString, NulError};
-/// Similar to std::ffi::CString, but avoids heap allocating if the string is
+/// Similar to `std::ffi::CString`, but avoids heap allocating if the string is
/// small enough. Also guarantees it's input is UTF-8 -- used for cases where we
/// need to pass a NUL-terminated string to SQLite, and we have a `&str`.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -10,7 +10,7 @@ pub(crate) struct SmallCString(smallvec::SmallVec<[u8; 16]>);
impl SmallCString {
#[inline]
pub fn new(s: &str) -> Result<Self, NulError> {
- if s.as_bytes().contains(&0u8) {
+ if s.as_bytes().contains(&0_u8) {
return Err(Self::fabricate_nul_error(s));
}
let mut buf = SmallVec::with_capacity(s.len() + 1);
@@ -31,7 +31,7 @@ impl SmallCString {
/// Get the bytes not including the NUL terminator. E.g. the bytes which
/// make up our `str`:
/// - `SmallCString::new("foo").as_bytes_without_nul() == b"foo"`
- /// - `SmallCString::new("foo").as_bytes_with_nul() == b"foo\0"
+ /// - `SmallCString::new("foo").as_bytes_with_nul() == b"foo\0"`
#[inline]
pub fn as_bytes_without_nul(&self) -> &[u8] {
self.debug_checks();
@@ -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..da261ba 100644
--- a/src/util/sqlite_string.rs
+++ b/src/util/sqlite_string.rs
@@ -38,7 +38,7 @@ pub(crate) struct SqliteMallocString {
impl SqliteMallocString {
/// SAFETY: Caller must be certain that `m` a nul-terminated c string
- /// allocated by sqlite3_malloc, and that SQLite expects us to free it!
+ /// allocated by `sqlite3_malloc`, and that SQLite expects us to free it!
#[inline]
pub(crate) unsafe fn from_raw_nonnull(ptr: NonNull<c_char>) -> Self {
Self {
@@ -48,7 +48,7 @@ impl SqliteMallocString {
}
/// SAFETY: Caller must be certain that `m` a nul-terminated c string
- /// allocated by sqlite3_malloc, and that SQLite expects us to free it!
+ /// allocated by `sqlite3_malloc`, and that SQLite expects us to free it!
#[inline]
pub(crate) unsafe fn from_raw(ptr: *mut c_char) -> Option<Self> {
NonNull::new(ptr).map(|p| Self::from_raw_nonnull(p))
@@ -95,13 +95,13 @@ impl SqliteMallocString {
/// If `s` contains internal NULs, we'll replace them with
/// `NUL_REPLACE_CHAR`.
///
- /// Except for debug_asserts which may trigger during testing, this function
- /// never panics. If we hit integer overflow or the allocation fails, we
- /// call `handle_alloc_error` which aborts the program after calling a
- /// global hook.
+ /// Except for `debug_assert`s which may trigger during testing, this
+ /// function never panics. If we hit integer overflow or the allocation
+ /// fails, we call `handle_alloc_error` which aborts the program after
+ /// calling a global hook.
///
/// This means it's safe to use in extern "C" functions even outside of
- /// catch_unwind.
+ /// `catch_unwind`.
pub(crate) fn from_str(s: &str) -> Self {
use std::convert::TryFrom;
let s = if s.as_bytes().contains(&0) {
@@ -120,7 +120,7 @@ impl SqliteMallocString {
// `>` because we added 1.
debug_assert!(len_to_alloc > 0);
debug_assert_eq!((len_to_alloc - 1) as usize, src_len);
- NonNull::new(ffi::sqlite3_malloc(len_to_alloc) as *mut c_char)
+ NonNull::new(ffi::sqlite3_malloc(len_to_alloc).cast::<c_char>())
})
.unwrap_or_else(|| {
use std::alloc::{handle_alloc_error, Layout};
@@ -130,14 +130,15 @@ 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);
});
- let buf: *mut c_char = res_ptr.as_ptr() as *mut c_char;
+ let buf: *mut c_char = res_ptr.as_ptr().cast::<c_char>();
src_ptr.copy_to_nonoverlapping(buf, src_len);
buf.add(src_len).write(0);
debug_assert_eq!(std::ffi::CStr::from_ptr(res_ptr.as_ptr()).to_bytes(), bytes);
@@ -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..d70af7e 100644
--- a/src/version.rs
+++ b/src/version.rs
@@ -5,6 +5,8 @@ use std::ffi::CStr;
/// 3.16.2.
///
/// See [`sqlite3_libversion_number()`](https://www.sqlite.org/c3ref/libversion.html).
+#[inline]
+#[must_use]
pub fn version_number() -> i32 {
unsafe { ffi::sqlite3_libversion_number() }
}
@@ -12,6 +14,8 @@ 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]
+#[must_use]
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..adfd9c9 100644
--- a/src/vtab/array.rs
+++ b/src/vtab/array.rs
@@ -1,10 +1,10 @@
-//! `feature = "array"` Array Virtual Table.
+//! Array Virtual Table.
//!
//! Note: `rarray`, not `carray` is the name of the table valued function we
//! 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?);
//! }
@@ -41,22 +41,23 @@ use crate::{Connection, Result};
// http://sqlite.org/bindptr.html
-pub(crate) const ARRAY_TYPE: *const c_char = b"rarray\0" as *const u8 as *const c_char;
+pub(crate) const ARRAY_TYPE: *const c_char = (b"rarray\0" as *const u8).cast::<c_char>();
pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
- let _: Array = Rc::from_raw(p as *const Vec<Value>);
+ drop(Rc::from_raw(p as *const Vec<Value>));
}
/// Array parameter / pointer
pub type Array = Rc<Vec<Value>>;
impl ToSql for Array {
+ #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::Array(self.clone()))
}
}
-/// `feature = "array"` Register the "rarray" module.
+/// Register the "rarray" module.
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
conn.create_module("rarray", eponymous_only_module::<ArrayTab>(), aux)
@@ -90,8 +91,8 @@ unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
// Index of the pointer= constraint
- let mut ptr_idx = None;
- for (i, constraint) in info.constraints().enumerate() {
+ let mut ptr_idx = false;
+ for (constraint, mut constraint_usage) in info.constraints_and_usages() {
if !constraint.is_usable() {
continue;
}
@@ -99,20 +100,17 @@ unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
continue;
}
if let CARRAY_COLUMN_POINTER = constraint.column() {
- ptr_idx = Some(i);
- }
- }
- if let Some(ptr_idx) = ptr_idx {
- {
- let mut constraint_usage = info.constraint_usage(ptr_idx);
+ ptr_idx = true;
constraint_usage.set_argv_index(1);
constraint_usage.set_omit(true);
}
- info.set_estimated_cost(1f64);
+ }
+ if ptr_idx {
+ info.set_estimated_cost(1_f64);
info.set_estimated_rows(100);
info.set_idx_num(1);
} else {
- info.set_estimated_cost(2_147_483_647f64);
+ info.set_estimated_cost(2_147_483_647_f64);
info.set_estimated_rows(2_147_483_647);
info.set_idx_num(0);
}
@@ -156,7 +154,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 +194,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..df3529a 100644
--- a/src/vtab/csvtab.rs
+++ b/src/vtab/csvtab.rs
@@ -1,7 +1,7 @@
-//! `feature = "csvtab"` CSV Virtual Table.
+//! 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
//!
@@ -35,7 +35,7 @@ use crate::vtab::{
};
use crate::{Connection, Error, Result};
-/// `feature = "csvtab"` Register the "csv" module.
+/// Register the "csv" module.
/// ```sql
/// CREATE VIRTUAL TABLE vtab USING csv(
/// filename=FILENAME -- Name of file containing CSV content
@@ -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 {
@@ -212,7 +212,7 @@ unsafe impl<'vtab> VTab<'vtab> for CSVTab {
if n_col.is_none() && schema.is_none() {
cols = headers
.into_iter()
- .map(|header| escape_double_quote(&header).into_owned())
+ .map(|header| escape_double_quote(header).into_owned())
.collect();
}
}
@@ -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..bdb6509 100644
--- a/src/vtab/mod.rs
+++ b/src/vtab/mod.rs
@@ -1,10 +1,10 @@
-//! `feature = "vtab"` Create virtual tables.
+//! 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.
//!
@@ -57,7 +57,7 @@ use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
// ffi::sqlite3_vtab => VTab
// ffi::sqlite3_vtab_cursor => VTabCursor
-/// `feature = "vtab"` Virtual table module
+/// Virtual table module
///
/// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
#[repr(transparent)]
@@ -79,17 +79,19 @@ union ModuleZeroHack {
// structs are allowed to be zeroed.
const ZERO_MODULE: ffi::sqlite3_module = unsafe {
ModuleZeroHack {
- bytes: [0u8; std::mem::size_of::<ffi::sqlite3_module>()],
+ bytes: [0_u8; std::mem::size_of::<ffi::sqlite3_module>()],
}
.module
};
-/// `feature = "vtab"` Create a read-only virtual table implementation.
+/// Create a read-only virtual table implementation.
///
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
+#[must_use]
pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T> {
// The xConnect and xCreate methods do the same thing, but they must be
// different so that the virtual table is not an eponymous virtual table.
+ #[allow(clippy::needless_update)]
&Module {
base: ffi::sqlite3_module {
// We don't use V3
@@ -122,13 +124,15 @@ pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab,
}
}
-/// `feature = "vtab"` Create an eponymous only virtual table implementation.
+/// Create an eponymous only virtual table implementation.
///
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
+#[must_use]
pub fn eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, T> {
// A virtual table is eponymous if its xCreate method is the exact same function
// as the xConnect method For eponymous-only virtual tables, the xCreate
// method is NULL
+ #[allow(clippy::needless_update)]
&Module {
base: ffi::sqlite3_module {
// We don't use V3
@@ -187,25 +191,25 @@ impl VTabConnection {
}
}
-/// `feature = "vtab"` Virtual table instance trait.
+/// Virtual table instance trait.
///
/// # Safety
///
-/// The first item in a struct implementing VTab must be
+/// The first item in a struct implementing `VTab` must be
/// `rusqlite::sqlite3_vtab`, and the struct must be `#[repr(C)]`.
///
/// ```rust,ignore
/// #[repr(C)]
/// struct MyTab {
/// /// Base class. Must be first
-/// base: ffi::sqlite3_vtab,
+/// base: rusqlite::vtab::sqlite3_vtab,
/// /* Virtual table implementations will typically add additional fields */
/// }
/// ```
///
/// (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;
@@ -228,7 +232,7 @@ pub unsafe trait VTab<'vtab>: Sized {
fn open(&'vtab self) -> Result<Self::Cursor>;
}
-/// `feature = "vtab"` Non-eponymous virtual table instance trait.
+/// Non-eponymous virtual table instance trait.
///
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
pub trait CreateVTab<'vtab>: VTab<'vtab> {
@@ -237,7 +241,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 +252,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))
@@ -257,11 +261,11 @@ pub trait CreateVTab<'vtab>: VTab<'vtab> {
}
}
-/// `feature = "vtab"` Index constraint operator.
+/// Index constraint operator.
/// 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,
@@ -277,6 +281,8 @@ pub enum IndexConstraintOp {
SQLITE_INDEX_CONSTRAINT_ISNOTNULL, // 3.21.0
SQLITE_INDEX_CONSTRAINT_ISNULL, // 3.21.0
SQLITE_INDEX_CONSTRAINT_IS, // 3.21.0
+ SQLITE_INDEX_CONSTRAINT_LIMIT, // 3.38.0
+ SQLITE_INDEX_CONSTRAINT_OFFSET, // 3.38.0
SQLITE_INDEX_CONSTRAINT_FUNCTION(u8), // 3.25.0
}
@@ -297,19 +303,36 @@ impl From<u8> for IndexConstraintOp {
70 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOTNULL,
71 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNULL,
72 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_IS,
+ 73 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIMIT,
+ 74 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_OFFSET,
v => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_FUNCTION(v),
}
}
}
-/// `feature = "vtab"` Pass information into and receive the reply from the
-/// `VTab.best_index` method.
+/// Pass information into and receive the reply from the
+/// [`VTab::best_index`] method.
///
/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
pub struct IndexInfo(*mut ffi::sqlite3_index_info);
impl IndexInfo {
+ /// Iterate on index constraint and its associated usage.
+ #[inline]
+ pub fn constraints_and_usages(&mut self) -> IndexConstraintAndUsageIter<'_> {
+ let constraints =
+ unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
+ let constraint_usages = unsafe {
+ slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
+ };
+ IndexConstraintAndUsageIter {
+ iter: constraints.iter().zip(constraint_usages.iter_mut()),
+ }
+ }
+
/// Record WHERE clause constraints.
+ #[inline]
+ #[must_use]
pub fn constraints(&self) -> IndexConstraintIter<'_> {
let constraints =
unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
@@ -319,6 +342,8 @@ impl IndexInfo {
}
/// Information about the ORDER BY clause.
+ #[inline]
+ #[must_use]
pub fn order_bys(&self) -> OrderByIter<'_> {
let order_bys =
unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) };
@@ -328,11 +353,14 @@ impl IndexInfo {
}
/// Number of terms in the ORDER BY clause
+ #[inline]
+ #[must_use]
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 +369,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 +377,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 +385,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 +394,8 @@ impl IndexInfo {
/// Estimated number of rows returned.
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.8.2
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+ #[inline]
pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
unsafe {
(*self.0).estimatedRows = estimated_rows;
@@ -375,6 +408,30 @@ impl IndexInfo {
// TODO sqlite3_vtab_collation (http://sqlite.org/c3ref/vtab_collation.html)
}
+/// Iterate on index constraint and its associated usage.
+pub struct IndexConstraintAndUsageIter<'a> {
+ iter: std::iter::Zip<
+ slice::Iter<'a, ffi::sqlite3_index_constraint>,
+ slice::IterMut<'a, ffi::sqlite3_index_constraint_usage>,
+ >,
+}
+
+impl<'a> Iterator for IndexConstraintAndUsageIter<'a> {
+ type Item = (IndexConstraint<'a>, IndexConstraintUsage<'a>);
+
+ #[inline]
+ fn next(&mut self) -> Option<(IndexConstraint<'a>, IndexConstraintUsage<'a>)> {
+ self.iter
+ .next()
+ .map(|raw| (IndexConstraint(raw.0), IndexConstraintUsage(raw.1)))
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
/// `feature = "vtab"`
pub struct IndexConstraintIter<'a> {
iter: slice::Iter<'a, ffi::sqlite3_index_constraint>,
@@ -383,46 +440,57 @@ 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))
+ self.iter.next().map(IndexConstraint)
}
+ #[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
-/// `feature = "vtab"` WHERE clause constraint.
+/// WHERE clause constraint.
pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
impl IndexConstraint<'_> {
/// Column constrained. -1 for ROWID
+ #[inline]
+ #[must_use]
pub fn column(&self) -> c_int {
self.0.iColumn
}
/// Constraint operator
+ #[inline]
+ #[must_use]
pub fn operator(&self) -> IndexConstraintOp {
IndexConstraintOp::from(self.0.op)
}
/// True if this constraint is usable
+ #[inline]
+ #[must_use]
pub fn is_usable(&self) -> bool {
self.0.usable != 0
}
}
-/// `feature = "vtab"` Information about what parameters to pass to
-/// `VTabCursor.filter`.
+/// Information about what parameters to pass to
+/// [`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,38 +504,46 @@ 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))
+ self.iter.next().map(OrderBy)
}
+ #[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
-/// `feature = "vtab"` A column of the ORDER BY clause.
+/// A column of the ORDER BY clause.
pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
impl OrderBy<'_> {
/// Column number
+ #[inline]
+ #[must_use]
pub fn column(&self) -> c_int {
self.0.iColumn
}
/// True for DESC. False for ASC.
+ #[inline]
+ #[must_use]
pub fn is_order_by_desc(&self) -> bool {
self.0.desc != 0
}
}
-/// `feature = "vtab"` Virtual table cursor trait.
+/// Virtual table cursor trait.
+///
+/// # Safety
///
/// Implementations must be like:
/// ```rust,ignore
/// #[repr(C)]
/// struct MyTabCursor {
/// /// Base class. Must be first
-/// base: ffi::sqlite3_vtab_cursor,
+/// base: rusqlite::vtab::sqlite3_vtab_cursor,
/// /* Virtual table implementations will typically add additional fields */
/// }
/// ```
@@ -477,8 +553,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 +570,13 @@ pub unsafe trait VTabCursor: Sized {
fn rowid(&self) -> Result<i64>;
}
-/// `feature = "vtab"` Context is used by `VTabCursor.column` to specify the
+/// 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 +586,23 @@ 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`.
+/// 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]
+ #[must_use]
pub fn len(&self) -> usize {
self.args.len()
}
/// Returns `true` if there is no value.
+ #[inline]
+ #[must_use]
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
@@ -535,38 +616,36 @@ impl Values<'_> {
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err)
}
- FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
- #[cfg(feature = "i128_blob")]
- FromSqlError::InvalidI128Size(_) => {
- Error::InvalidColumnType(idx, idx.to_string(), value.data_type())
- }
- #[cfg(feature = "uuid")]
- FromSqlError::InvalidUuidSize(_) => {
+ FromSqlError::InvalidBlobSize { .. } => {
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
}
+ FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
})
}
// `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>> {
+ #[cfg_attr(docsrs, doc(cfg(feature = "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]
+ #[must_use]
pub fn iter(&self) -> ValueIter<'_> {
ValueIter {
iter: self.args.iter(),
@@ -578,12 +657,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,22 +671,25 @@ 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()
}
}
impl Connection {
- /// `feature = "vtab"` Register a virtual table implementation.
+ /// Register a virtual table implementation.
///
/// 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,
@@ -633,7 +716,7 @@ impl InnerConnection {
self.db(),
c_name.as_ptr(),
&module.base,
- boxed_aux as *mut c_void,
+ boxed_aux.cast::<c_void>(),
Some(free_boxed_value::<T::Aux>),
)
}
@@ -652,17 +735,19 @@ impl InnerConnection {
}
}
-/// `feature = "vtab"` Escape double-quote (`"`) character occurences by
+/// Escape double-quote (`"`) character occurrences by
/// doubling them (`""`).
+#[must_use]
pub fn escape_double_quote(identifier: &str) -> Cow<'_, str> {
if identifier.contains('"') {
// escape quote by doubling them
- Owned(identifier.replace("\"", "\"\""))
+ Owned(identifier.replace('"', "\"\""))
} else {
Borrowed(identifier)
}
}
-/// `feature = "vtab"` Dequote string
+/// Dequote string
+#[must_use]
pub fn dequote(s: &str) -> &str {
if s.len() < 2 {
return s;
@@ -675,11 +760,12 @@ pub fn dequote(s: &str) -> &str {
_ => s,
}
}
-/// `feature = "vtab"` The boolean can be one of:
+/// The boolean can be one of:
/// ```text
/// 1 yes true on
/// 0 no false off
/// ```
+#[must_use]
pub fn parse_boolean(s: &str) -> Option<bool> {
if s.eq_ignore_ascii_case("yes")
|| s.eq_ignore_ascii_case("on")
@@ -700,7 +786,7 @@ pub fn parse_boolean(s: &str) -> Option<bool> {
// FIXME copy/paste from function.rs
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
- let _: Box<T> = Box::from_raw(p as *mut T);
+ drop(Box::from_raw(p.cast::<T>()));
}
unsafe extern "C" fn rust_create<'vtab, T>(
@@ -717,7 +803,7 @@ where
use std::ffi::CStr;
let mut conn = VTabConnection(db);
- let aux = aux as *mut T::Aux;
+ let aux = aux.cast::<T::Aux>();
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args
.iter()
@@ -729,7 +815,7 @@ where
let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
if rc == ffi::SQLITE_OK {
let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
- *pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab;
+ *pp_vtab = boxed_vtab.cast::<ffi::sqlite3_vtab>();
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
@@ -769,7 +855,7 @@ where
use std::ffi::CStr;
let mut conn = VTabConnection(db);
- let aux = aux as *mut T::Aux;
+ let aux = aux.cast::<T::Aux>();
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args
.iter()
@@ -781,7 +867,7 @@ where
let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
if rc == ffi::SQLITE_OK {
let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
- *pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab;
+ *pp_vtab = boxed_vtab.cast::<ffi::sqlite3_vtab>();
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
@@ -814,7 +900,7 @@ unsafe extern "C" fn rust_best_index<'vtab, T>(
where
T: VTab<'vtab>,
{
- let vt = vtab as *mut T;
+ let vt = vtab.cast::<T>();
let mut idx_info = IndexInfo(info);
match (*vt).best_index(&mut idx_info) {
Ok(_) => ffi::SQLITE_OK,
@@ -838,8 +924,8 @@ where
if vtab.is_null() {
return ffi::SQLITE_OK;
}
- let vtab = vtab as *mut T;
- let _: Box<T> = Box::from_raw(vtab);
+ let vtab = vtab.cast::<T>();
+ drop(Box::from_raw(vtab));
ffi::SQLITE_OK
}
@@ -850,10 +936,10 @@ where
if vtab.is_null() {
return ffi::SQLITE_OK;
}
- let vt = vtab as *mut T;
+ let vt = vtab.cast::<T>();
match (*vt).destroy() {
Ok(_) => {
- let _: Box<T> = Box::from_raw(vt);
+ drop(Box::from_raw(vt));
ffi::SQLITE_OK
}
Err(Error::SqliteFailure(err, s)) => {
@@ -876,11 +962,11 @@ unsafe extern "C" fn rust_open<'vtab, T: 'vtab>(
where
T: VTab<'vtab>,
{
- let vt = vtab as *mut T;
+ let vt = vtab.cast::<T>();
match (*vt).open() {
Ok(cursor) => {
let boxed_cursor: *mut T::Cursor = Box::into_raw(Box::new(cursor));
- *pp_cursor = boxed_cursor as *mut ffi::sqlite3_vtab_cursor;
+ *pp_cursor = boxed_cursor.cast::<ffi::sqlite3_vtab_cursor>();
ffi::SQLITE_OK
}
Err(Error::SqliteFailure(err, s)) => {
@@ -900,8 +986,8 @@ unsafe extern "C" fn rust_close<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_i
where
C: VTabCursor,
{
- let cr = cursor as *mut C;
- let _: Box<C> = Box::from_raw(cr);
+ let cr = cursor.cast::<C>();
+ drop(Box::from_raw(cr));
ffi::SQLITE_OK
}
@@ -941,7 +1027,7 @@ unsafe extern "C" fn rust_eof<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
- let cr = cursor as *mut C;
+ let cr = cursor.cast::<C>();
(*cr).eof() as c_int
}
@@ -953,7 +1039,7 @@ unsafe extern "C" fn rust_column<C>(
where
C: VTabCursor,
{
- let cr = cursor as *mut C;
+ let cr = cursor.cast::<C>();
let mut ctxt = Context(ctx);
result_error(ctx, (*cr).column(&mut ctxt, i))
}
@@ -965,7 +1051,7 @@ unsafe extern "C" fn rust_rowid<C>(
where
C: VTabCursor,
{
- let cr = cursor as *mut C;
+ let cr = cursor.cast::<C>();
match (*cr).rowid() {
Ok(rowid) => {
*p_rowid = rowid;
@@ -977,6 +1063,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,15 +1082,17 @@ 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);
+ ffi::sqlite3_free((*vtab).zErrMsg.cast::<c_void>());
}
(*vtab).zErrMsg = alloc(err_msg);
}
/// 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,
@@ -1041,10 +1130,13 @@ fn alloc(s: &str) -> *mut c_char {
}
#[cfg(feature = "array")]
+#[cfg_attr(docsrs, doc(cfg(feature = "array")))]
pub mod array;
#[cfg(feature = "csvtab")]
+#[cfg_attr(docsrs, doc(cfg(feature = "csvtab")))]
pub mod csvtab;
#[cfg(feature = "series")]
+#[cfg_attr(docsrs, doc(cfg(feature = "series")))]
pub mod series; // SQLite >= 3.9.0
#[cfg(test)]
diff --git a/src/vtab/series.rs b/src/vtab/series.rs
index ed67f16..f26212a 100644
--- a/src/vtab/series.rs
+++ b/src/vtab/series.rs
@@ -1,8 +1,8 @@
-//! `feature = "series"` Generate series virtual table.
+//! Generate series virtual table.
//!
//! 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,9 +13,9 @@ 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.
+/// Register the "generate_series" module.
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
conn.create_module("generate_series", eponymous_only_module::<SeriesTab>(), aux)
@@ -38,6 +38,8 @@ bitflags::bitflags! {
const STEP = 4;
// output in descending order
const DESC = 8;
+ // output in ascending 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() {
- continue;
- }
- if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
+ if constraint.column() < SERIES_COLUMN_START {
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.
@@ -133,10 +123,16 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
let order_by_consumed = {
let mut order_bys = info.order_bys();
if let Some(order_by) = order_bys.next() {
- if order_by.is_order_by_desc() {
- idx_num |= QueryPlanFlags::DESC;
+ if order_by.column() == 0 {
+ if order_by.is_order_by_desc() {
+ idx_num |= QueryPlanFlags::DESC;
+ } else {
+ idx_num |= QueryPlanFlags::ASC;
+ }
+ true
+ } else {
+ false
}
- true
} else {
false
}
@@ -145,7 +141,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 +166,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 +189,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 +208,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 +277,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..fa26459 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(())
}