aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-09 06:02:13 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-09 06:02:13 +0000
commit9d6c2950b8c8ecbb7275c9bc6056fbafa34aedbb (patch)
treeed3dae2c91a4d67fe43675ec1ee9eaf437cd8280
parent7a8b28cf9d468edeb3bacc1e304adf0ccd5bafb8 (diff)
parent93c8cc61e4753e8db9b57fbc2e3e9274495e9809 (diff)
downloadrusqlite-9d6c2950b8c8ecbb7275c9bc6056fbafa34aedbb.tar.gz
Snap for 8558685 from 93c8cc61e4753e8db9b57fbc2e3e9274495e9809 to tm-frc-extservices-releaset_frc_ext_330443000android13-frc-extservices-release
Change-Id: Ie2e85799f80534976178827100c43e040947171c
-rw-r--r--.cargo_vcs_info.json7
-rw-r--r--.gitattributes1
-rw-r--r--.github/workflows/main.yml153
-rw-r--r--Android.bp2
-rw-r--r--Cargo.toml117
-rw-r--r--Cargo.toml.orig45
-rw-r--r--Changelog.md332
-rw-r--r--METADATA10
-rw-r--r--README.md63
-rw-r--r--appveyor.yml42
-rw-r--r--clippy.toml1
-rw-r--r--codecov.yml11
-rwxr-xr-xpublish-ghp-docs.sh14
-rw-r--r--src/backup.rs29
-rw-r--r--src/blob/mod.rs18
-rw-r--r--src/blob/pos_io.rs23
-rw-r--r--src/busy.rs4
-rw-r--r--src/cache.rs11
-rw-r--r--src/collation.rs26
-rw-r--r--src/column.rs82
-rw-r--r--src/config.rs49
-rw-r--r--src/context.rs4
-rw-r--r--src/error.rs29
-rw-r--r--src/functions.rs113
-rw-r--r--src/hooks.rs564
-rw-r--r--src/inner_connection.rs160
-rw-r--r--src/lib.rs275
-rw-r--r--src/limits.rs110
-rw-r--r--src/load_extension_guard.rs20
-rw-r--r--src/params.rs10
-rw-r--r--src/pragma.rs46
-rw-r--r--src/raw_statement.rs45
-rw-r--r--src/row.rs46
-rw-r--r--src/session.rs126
-rw-r--r--src/statement.rs26
-rw-r--r--src/trace.rs29
-rw-r--r--src/transaction.rs10
-rw-r--r--src/types/chrono.rs58
-rw-r--r--src/types/from_sql.rs83
-rw-r--r--src/types/mod.rs14
-rw-r--r--src/types/serde_json.rs8
-rw-r--r--src/types/time.rs120
-rw-r--r--src/types/to_sql.rs24
-rw-r--r--src/types/value.rs8
-rw-r--r--src/types/value_ref.rs81
-rw-r--r--src/unlock_notify.rs31
-rw-r--r--src/util/small_cstr.rs6
-rw-r--r--src/util/sqlite_string.rs18
-rw-r--r--src/version.rs2
-rw-r--r--src/vtab/array.rs25
-rw-r--r--src/vtab/csvtab.rs6
-rw-r--r--src/vtab/mod.rs163
-rw-r--r--src/vtab/series.rs18
-rw-r--r--tests/vtab.rs2
54 files changed, 1837 insertions, 1483 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index f4d4931..de52e1b 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,6 @@
{
"git": {
- "sha1": "e7bb33a99ca6f122b5f338efd7889a6509b3dee0"
- }
-}
+ "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 2c2d7fc..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:detect_leaks=0'
- # Work around https://github.com/rust-lang/rust/issues/59125 by
- # disabling backtraces. In an ideal world we'd probably suppress the
- # leak sanitization, but we don't care about backtraces here, so long
- # 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/Android.bp b/Android.bp
index a83fce2..d5c31aa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,7 +23,7 @@ rust_library {
host_supported: true,
crate_name: "rusqlite",
cargo_env_compat: true,
- cargo_pkg_version: "0.25.3",
+ cargo_pkg_version: "0.27.0",
srcs: ["src/lib.rs"],
edition: "2018",
features: [
diff --git a/Cargo.toml b/Cargo.toml
index abcdbe1..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.25.3"
+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"
@@ -82,7 +96,7 @@ version = "1.4"
optional = true
[dependencies.libsqlite3-sys]
-version = "0.22.2"
+version = "0.24.0"
[dependencies.memchr]
version = "2.3"
@@ -95,7 +109,12 @@ optional = true
version = "1.6.1"
[dependencies.time]
-version = "0.2.23"
+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"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index c93656b..6ab1a7e 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
[package]
name = "rusqlite"
-version = "0.25.3"
+version = "0.27.0"
authors = ["The rusqlite developers"]
edition = "2018"
description = "Ergonomic wrapper for SQLite"
@@ -11,6 +11,15 @@ keywords = ["sqlite", "database", "ffi"]
license = "MIT"
categories = ["database"]
+exclude = [
+ "/.github/*",
+ "/.gitattributes",
+ "/appveyor.yml",
+ "/Changelog.md",
+ "/clippy.toml",
+ "/codecov.yml",
+]
+
[badges]
appveyor = { repository = "rusqlite/rusqlite" }
codecov = { repository = "rusqlite/rusqlite" }
@@ -34,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"]
@@ -62,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 (except `session`). This is useful for
-# running tests / clippy / etc. New features and optional dependencies that
-# don't conflict with anything else should be added here.
-bundled-full = [
+# 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",
@@ -83,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",
@@ -94,16 +103,17 @@ bundled-full = [
"window",
]
+bundled-full = ["modern-full", "bundled"]
+
[dependencies]
-time = { version = "0.2.23", optional = true }
+time = { version = "0.3.0", features = ["formatting", "macros", "parsing"], optional = true }
bitflags = "1.2"
hashlink = "0.7"
-chrono = { version = "0.4", optional = true }
+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"
@@ -123,7 +133,7 @@ bencher = "0.1"
[dependencies.libsqlite3-sys]
path = "libsqlite3-sys"
-version = "0.22.2"
+version = "0.24.0"
[[test]]
name = "config_log"
@@ -144,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/METADATA b/METADATA
index d84e059..77fbcb9 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/rusqlite/rusqlite-0.25.3.crate"
+ value: "https://static.crates.io/crates/rusqlite/rusqlite-0.27.0.crate"
}
- version: "0.25.3"
+ version: "0.27.0"
license_type: NOTICE
last_upgrade_date {
- year: 2021
- month: 5
- day: 19
+ year: 2022
+ month: 3
+ day: 1
}
}
diff --git a/README.md b/README.md
index e7d831c..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).
@@ -99,7 +98,11 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
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 SQLite. This is a good option for cases where linking to SQLite is complicated, such as Windows.
-* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`.
+* `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.
@@ -121,26 +124,28 @@ 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.35.4 (as of `rusqlite` 0.25.0 / `libsqlite3-sys`
- 0.22.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
+ 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.25.1"
+ version = "0.27.0"
features = ["bundled"]
```
-* When using the `bundled` feature, the build script will honor `SQLITE_MAX_VARIABLE_NUMBER` and `SQLITE_MAX_EXPR_DEPTH` variables. It will also honor a `LIBSQLITE3_FLAGS` variable, which can have a format like `"-USQLITE_ALPHA -DSQLITE_BETA SQLITE_GAMMA ..."`. That would disable the `SQLITE_ALPHA` flag, and set the `SQLITE_BETA` and `SQLITE_GAMMA` flags. (The initial `-D` can be omitted, as on the last one.)
+* When 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 library already on the system (so *not* using the `bundled` feature), you can set the `SQLITE3_LIB_DIR` environment variable to point to a directory containing the library. You can also set the `SQLITE3_INCLUDE_DIR` variable to point to the directory containing `sqlite3.h`.
+* 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 library already on the system, you can set the `SQLITE3_STATIC` environment variable to 1 to request that the library be statically instead of dynamically linked.
+* 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
@@ -168,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.
@@ -194,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 clippy --all-targets --workspace --features "bundled-full session buildtime_bindgen"` passes without warnings.
-- Ensure `cargo test --all-targets --workspace --features bundled` reports no failures.
-- Ensure `cargo test --all-targets --workspace --features "bundled-full session buildtime_bindgen"` reports no failures.
+- Ensure `cargo 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
@@ -206,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/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/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 72d54e5..6da01fd 100644
--- a/src/backup.rs
+++ b/src/backup.rs
@@ -1,4 +1,4 @@
-//! `feature = "backup"` Online SQLite backup API.
+//! Online SQLite backup API.
//!
//! 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
@@ -40,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
@@ -84,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.
///
@@ -107,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 {
@@ -131,7 +131,7 @@ impl Connection {
}
}
-/// `feature = "backup"` Possible successful results of calling
+/// Possible successful results of calling
/// [`Backup::step`].
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
@@ -152,7 +152,7 @@ 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`](Backup::step) - if the source database is modified after a call to
@@ -166,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,
}
@@ -203,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;
@@ -223,7 +223,7 @@ impl Backup<'_, '_> {
Ok(Backup {
phantom_from: PhantomData,
- phantom_to: PhantomData,
+ to,
b,
})
}
@@ -231,6 +231,7 @@ impl Backup<'_, '_> {
/// 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 {
@@ -263,7 +264,7 @@ 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),
}
}
@@ -296,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),
diff --git a/src/blob/mod.rs b/src/blob/mod.rs
index 202f65d..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.
@@ -196,7 +196,7 @@ use crate::{Connection, DatabaseName, Result};
mod pos_io;
-/// `feature = "blob"` Handle to an open BLOB. See
+/// Handle to an open BLOB. See
/// [`rusqlite::blob`](crate::blob) documentation for in-depth discussion.
pub struct Blob<'conn> {
conn: &'conn Connection,
@@ -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
@@ -223,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 {
@@ -265,12 +265,14 @@ 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()
@@ -278,6 +280,7 @@ impl Blob<'_> {
/// Return true if the BLOB is empty.
#[inline]
+ #[must_use]
pub fn is_empty(&self) -> bool {
self.size() == 0
}
@@ -318,8 +321,7 @@ impl io::Read for Blob<'_> {
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(|_| {
@@ -400,7 +402,7 @@ impl Drop for Blob<'_> {
}
}
-/// `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.
diff --git a/src/blob/pos_io.rs b/src/blob/pos_io.rs
index dd6167d..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))
}
}
diff --git a/src/busy.rs b/src/busy.rs
index 447610e..b394d01 100644
--- a/src/busy.rs
+++ b/src/busy.rs
@@ -64,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)
@@ -169,7 +169,7 @@ mod test {
let _ = db2
.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 89459ce..c80a708 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -46,13 +46,13 @@ impl Connection {
/// 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();
}
}
@@ -60,6 +60,9 @@ 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.
@@ -122,7 +125,7 @@ impl StatementCache {
#[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`.
@@ -169,7 +172,7 @@ impl StatementCache {
#[inline]
fn flush(&self) {
let mut cache = self.0.borrow_mut();
- cache.clear()
+ cache.clear();
}
}
diff --git a/src/collation.rs b/src/collation.rs
index 2b93a9a..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,22 +10,22 @@ 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, C>(&'c self, collation_name: &str, x_compare: C) -> Result<()>
+ pub fn create_collation<C>(&self, collation_name: &str, x_compare: C) -> Result<()>
where
- C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'c,
+ C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
{
self.db
.borrow_mut()
.create_collation(collation_name, x_compare)
}
- /// `feature = "collation"` Collation needed callback
+ /// Collation needed callback
#[inline]
pub fn collation_needed(
&self,
@@ -34,7 +34,7 @@ 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)
@@ -42,9 +42,9 @@ impl Connection {
}
impl InnerConnection {
- fn create_collation<'c, C>(&'c mut self, collation_name: &str, x_compare: C) -> Result<()>
+ fn create_collation<C>(&mut self, collation_name: &str, x_compare: C) -> Result<()>
where
- C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'c,
+ C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
{
unsafe extern "C" fn call_boxed_closure<C>(
arg1: *mut c_void,
@@ -57,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())
@@ -91,7 +91,7 @@ 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>),
)
diff --git a/src/column.rs b/src/column.rs
index b9122c4..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)]
@@ -12,12 +12,14 @@ 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
}
@@ -132,6 +134,7 @@ impl Statement<'_> {
/// 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);
@@ -147,72 +150,6 @@ impl Statement<'_> {
}
}
-impl<'stmt> Rows<'stmt> {
- /// Get all the column names.
- #[inline]
- pub fn column_names(&self) -> Option<Vec<&str>> {
- self.stmt.map(Statement::column_names)
- }
-
- /// Return the number of columns.
- #[inline]
- pub fn column_count(&self) -> Option<usize> {
- self.stmt.map(Statement::column_count)
- }
-
- /// Return the name of the column.
- #[inline]
- pub fn column_name(&self, col: usize) -> Option<Result<&str>> {
- self.stmt.map(|stmt| stmt.column_name(col))
- }
-
- /// Return the index of the column.
- #[inline]
- pub fn column_index(&self, name: &str) -> Option<Result<usize>> {
- self.stmt.map(|stmt| stmt.column_index(name))
- }
-
- /// Returns a slice describing the columns of the Rows.
- #[inline]
- #[cfg(feature = "column_decltype")]
- pub fn columns(&self) -> Option<Vec<Column>> {
- self.stmt.map(Statement::columns)
- }
-}
-
-impl<'stmt> Row<'stmt> {
- /// Get all the column names of the Row.
- #[inline]
- pub fn column_names(&self) -> Vec<&str> {
- self.stmt.column_names()
- }
-
- /// Return the number of columns in the current row.
- #[inline]
- pub fn column_count(&self) -> usize {
- self.stmt.column_count()
- }
-
- /// Return the name of the column.
- #[inline]
- pub fn column_name(&self, col: usize) -> Result<&str> {
- self.stmt.column_name(col)
- }
-
- /// Return the index of the column.
- #[inline]
- pub fn column_index(&self, name: &str) -> Result<usize> {
- self.stmt.column_index(name)
- }
-
- /// Returns a slice describing the columns of the Row.
- #[inline]
- #[cfg(feature = "column_decltype")]
- pub fn columns(&self) -> Vec<Column> {
- self.stmt.columns()
- }
-}
-
#[cfg(test)]
mod test {
use crate::{Connection, Result};
@@ -230,10 +167,17 @@ mod test {
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(())
}
diff --git a/src/config.rs b/src/config.rs
index ff4762e..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};
@@ -30,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.
@@ -63,58 +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)
}
}
diff --git a/src/context.rs b/src/context.rs
index ce60ebb..5f935fa 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -58,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 a8d3f23..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;
@@ -72,15 +72,18 @@ pub enum Error {
/// [`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`](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`](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>),
@@ -94,11 +97,13 @@ pub enum Error {
/// An error case available for implementors of custom modules (e.g.,
/// [`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
@@ -106,6 +111,7 @@ pub enum Error {
/// 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.
@@ -120,6 +126,7 @@ pub enum Error {
/// [`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,
}
@@ -195,12 +202,7 @@ impl From<FromSqlError> for Error {
// 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) => {
@@ -350,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 ce908d5..e613182 100644
--- a/src/functions.rs
+++ b/src/functions.rs
@@ -1,4 +1,4 @@
-//! `feature = "functions"` Create or redefine SQL functions.
+//! Create or redefine SQL functions.
//!
//! # Example
//!
@@ -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,
@@ -114,12 +111,14 @@ 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()
}
@@ -144,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))
}
})
@@ -162,6 +156,7 @@ impl Context<'_> {
/// 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) }
@@ -203,9 +198,9 @@ impl Context<'_> {
ffi::sqlite3_set_auxdata(
self.ctx,
arg,
- raw as *mut _,
+ raw.cast(),
Some(free_boxed_value::<AuxInner>),
- )
+ );
};
Ok(orig)
}
@@ -260,7 +255,7 @@ impl Deref for ConnectionRef<'_> {
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
@@ -292,9 +287,10 @@ where
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,
@@ -341,7 +337,7 @@ impl Default for FunctionFlags {
}
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.
@@ -379,15 +375,15 @@ impl Connection {
///
/// Will return Err if the function could not be attached to the connection.
#[inline]
- pub fn create_scalar_function<'c, F, T>(
- &'c self,
+ pub fn create_scalar_function<F, T>(
+ &self,
fn_name: &str,
n_arg: c_int,
flags: FunctionFlags,
x_func: F,
) -> Result<()>
where
- F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'c,
+ F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'static,
T: ToSql,
{
self.db
@@ -395,7 +391,7 @@ 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
@@ -411,7 +407,7 @@ impl Connection {
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
- D: Aggregate<A, T>,
+ D: Aggregate<A, T> + 'static,
T: ToSql,
{
self.db
@@ -419,12 +415,13 @@ 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
/// information.
#[cfg(feature = "window")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "window")))]
#[inline]
pub fn create_window_function<A, W, T>(
&self,
@@ -435,7 +432,7 @@ impl Connection {
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
- W: WindowAggregate<A, T>,
+ W: WindowAggregate<A, T> + 'static,
T: ToSql,
{
self.db
@@ -443,7 +440,7 @@ 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
@@ -460,15 +457,15 @@ impl Connection {
}
impl InnerConnection {
- fn create_scalar_function<'c, F, T>(
- &'c mut self,
+ fn create_scalar_function<F, T>(
+ &mut self,
fn_name: &str,
n_arg: c_int,
flags: FunctionFlags,
x_func: F,
) -> Result<()>
where
- F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'c,
+ F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'static,
T: ToSql,
{
unsafe extern "C" fn call_boxed_closure<F, T>(
@@ -480,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,
@@ -512,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,
@@ -531,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));
@@ -542,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>),
@@ -562,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));
@@ -573,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>),
@@ -620,16 +617,15 @@ 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"
@@ -668,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"
@@ -722,7 +717,7 @@ 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"
@@ -767,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"
@@ -883,7 +878,7 @@ mod test {
let result: Result<bool> =
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", [], |r| r.get(0));
- assert_eq!(true, result?);
+ assert!(result?);
let result: Result<i64> = db.query_row(
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
diff --git a/src/hooks.rs b/src/hooks.rs
index 71e2f4c..f0ae1f3 100644
--- a/src/hooks.rs
+++ b/src/hooks.rs
@@ -1,4 +1,4 @@
-//! `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};
@@ -9,7 +9,7 @@ use crate::ffi;
use crate::{Connection, InnerConnection};
-/// `feature = "hooks"` Action Codes
+/// Action Codes
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(i32)]
#[non_exhaustive]
@@ -37,50 +37,350 @@ 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<'c, F>(&'c self, hook: Option<F>)
+ pub fn commit_hook<F>(&self, hook: Option<F>)
where
- F: FnMut() -> bool + Send + 'c,
+ F: FnMut() -> bool + Send + 'static,
{
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<'c, F>(&'c self, hook: Option<F>)
+ pub fn rollback_hook<F>(&self, hook: Option<F>)
where
- F: FnMut() + Send + 'c,
+ F: FnMut() + Send + 'static,
{
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<'c, F>(&'c self, hook: Option<F>)
+ pub fn update_hook<F>(&self, hook: Option<F>)
where
- F: FnMut(Action, &str, &str, i64) + Send + 'c,
+ F: FnMut(Action, &str, &str, i64) + Send + 'static,
{
self.db.borrow_mut().update_hook(hook);
}
- /// `feature = "hooks"` Register a query progress callback.
+ /// 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
@@ -94,6 +394,16 @@ impl Connection {
{
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 {
@@ -103,18 +413,19 @@ impl InnerConnection {
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<'c, F>(&'c mut self, hook: Option<F>)
+ fn commit_hook<F>(&mut self, hook: Option<F>)
where
- F: FnMut() -> bool + Send + 'c,
+ F: FnMut() -> bool + Send + 'static,
{
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
where
F: FnMut() -> bool,
{
let r = catch_unwind(|| {
- let boxed_hook: *mut F = p_arg as *mut F;
+ let boxed_hook: *mut F = p_arg.cast::<F>();
(*boxed_hook)()
});
if let Ok(true) = r {
@@ -140,7 +451,7 @@ impl InnerConnection {
ffi::sqlite3_commit_hook(
self.db(),
Some(call_boxed_closure::<F>),
- boxed_hook as *mut _,
+ boxed_hook.cast(),
)
}
}
@@ -154,18 +465,18 @@ impl InnerConnection {
self.free_commit_hook = free_commit_hook;
}
- fn rollback_hook<'c, F>(&'c mut self, hook: Option<F>)
+ fn rollback_hook<F>(&mut self, hook: Option<F>)
where
- F: FnMut() + Send + 'c,
+ F: FnMut() + Send + 'static,
{
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
where
F: FnMut(),
{
- let _ = catch_unwind(|| {
- let boxed_hook: *mut F = p_arg as *mut F;
+ drop(catch_unwind(|| {
+ let boxed_hook: *mut F = p_arg.cast::<F>();
(*boxed_hook)();
- });
+ }));
}
let free_rollback_hook = if hook.is_some() {
@@ -181,7 +492,7 @@ impl InnerConnection {
ffi::sqlite3_rollback_hook(
self.db(),
Some(call_boxed_closure::<F>),
- boxed_hook as *mut _,
+ boxed_hook.cast(),
)
}
}
@@ -195,41 +506,29 @@ impl InnerConnection {
self.free_rollback_hook = free_rollback_hook;
}
- fn update_hook<'c, F>(&'c mut self, hook: Option<F>)
+ fn update_hook<F>(&mut self, hook: Option<F>)
where
- F: FnMut(Action, &str, &str, i64) + Send + 'c,
+ F: FnMut(Action, &str, &str, i64) + Send + 'static,
{
unsafe extern "C" fn call_boxed_closure<F>(
p_arg: *mut c_void,
action_code: c_int,
- db_str: *const c_char,
- tbl_str: *const c_char,
+ 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() {
@@ -245,7 +544,7 @@ impl InnerConnection {
ffi::sqlite3_update_hook(
self.db(),
Some(call_boxed_closure::<F>),
- boxed_hook as *mut _,
+ boxed_hook.cast(),
)
}
}
@@ -268,7 +567,7 @@ impl InnerConnection {
F: FnMut() -> bool,
{
let r = catch_unwind(|| {
- let boxed_handler: *mut F = p_arg as *mut F;
+ let boxed_handler: *mut F = p_arg.cast::<F>();
(*boxed_handler)()
});
if let Ok(true) = r {
@@ -278,29 +577,108 @@ impl InnerConnection {
}
}
- match handler {
- Some(handler) => {
- let boxed_handler = Box::new(handler);
- unsafe {
- ffi::sqlite3_progress_handler(
- self.db(),
- num_ops,
- Some(call_boxed_closure::<F>),
- &*boxed_handler as *const F as *mut _,
- )
- }
- self.progress_handler = Some(boxed_handler);
- }
- _ => {
- unsafe { ffi::sqlite3_progress_handler(self.db(), num_ops, None, ptr::null_mut()) }
- self.progress_handler = None;
+ 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)]
@@ -313,13 +691,13 @@ mod test {
fn test_commit_hook() -> Result<()> {
let db = Connection::open_in_memory()?;
- let mut called = false;
+ static CALLED: AtomicBool = AtomicBool::new(false);
db.commit_hook(Some(|| {
- called = true;
+ CALLED.store(true, Ordering::Relaxed);
false
}));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
- assert!(called);
+ assert!(CALLED.load(Ordering::Relaxed));
Ok(())
}
@@ -341,12 +719,12 @@ mod test {
fn test_rollback_hook() -> Result<()> {
let db = Connection::open_in_memory()?;
- let mut called = false;
+ static CALLED: AtomicBool = AtomicBool::new(false);
db.rollback_hook(Some(|| {
- called = true;
+ CALLED.store(true, Ordering::Relaxed);
}));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")?;
- assert!(called);
+ assert!(CALLED.load(Ordering::Relaxed));
Ok(())
}
@@ -354,17 +732,17 @@ mod test {
fn test_update_hook() -> Result<()> {
let db = Connection::open_in_memory()?;
- let mut called = 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);
assert_eq!("foo", tbl);
assert_eq!(1, row_id);
- called = true;
+ CALLED.store(true, Ordering::Relaxed);
}));
db.execute_batch("CREATE TABLE foo (t TEXT)")?;
db.execute_batch("INSERT INTO foo VALUES ('lisa')")?;
- assert!(called);
+ assert!(CALLED.load(Ordering::Relaxed));
Ok(())
}
@@ -398,4 +776,40 @@ mod test {
.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 0fbf5c1..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 {
@@ -33,9 +32,13 @@ pub struct InnerConnection {
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]
@@ -51,6 +54,8 @@ impl InnerConnection {
free_update_hook: None,
#[cfg(feature = "hooks")]
progress_handler: None,
+ #[cfg(feature = "hooks")]
+ authorizer: None,
owned,
}
}
@@ -60,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
@@ -132,7 +135,7 @@ impl InnerConnection {
}
#[inline]
- pub fn decode_result(&mut self, code: c_int) -> Result<()> {
+ pub fn decode_result(&self, code: c_int) -> Result<()> {
unsafe { InnerConnection::decode_result_raw(self.db(), code) }
}
@@ -182,34 +185,31 @@ impl InnerConnection {
#[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)))
}
}
@@ -222,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)?;
@@ -274,7 +276,7 @@ impl InnerConnection {
}
#[inline]
- pub fn changes(&mut self) -> usize {
+ pub fn changes(&self) -> usize {
unsafe { ffi::sqlite3_changes(self.db()) as usize }
}
@@ -298,6 +300,11 @@ 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) {}
@@ -319,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();
@@ -424,15 +382,13 @@ fn ensure_safe_sqlite_threading_mode() -> Result<()> {
}
unsafe {
- if ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) != ffi::SQLITE_OK || ffi::sqlite3_initialize() != ffi::SQLITE_OK {
- panic!(
+ 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 926cad3..1fa7a33 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -48,6 +48,7 @@
//! }
//! ```
#![warn(missing_docs)]
+#![cfg_attr(docsrs, feature(doc_cfg))]
pub use libsqlite3_sys as ffi;
@@ -73,8 +74,6 @@ 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};
@@ -84,27 +83,32 @@ pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBe
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;
@@ -113,15 +117,19 @@ 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;
@@ -152,9 +160,11 @@ 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(())
/// }
/// ```
@@ -186,11 +196,11 @@ macro_rules! params {
/// 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(())
/// }
@@ -248,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))
}
@@ -310,7 +320,7 @@ pub const TEMP_DB: DatabaseName<'static> = DatabaseName::Temp;
))]
impl DatabaseName<'_> {
#[inline]
- fn to_cstring(&self) -> Result<util::SmallCString> {
+ fn as_cstring(&self) -> Result<util::SmallCString> {
use self::DatabaseName::{Attached, Main, Temp};
match *self {
Main => str_to_cstring("main"),
@@ -440,7 +450,7 @@ 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> {
@@ -455,10 +465,11 @@ impl Connection {
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn create_tables(conn: &Connection) -> Result<()> {
- /// conn.execute_batch("BEGIN;
- /// CREATE TABLE foo(x INTEGER);
- /// CREATE TABLE bar(y TEXT);
- /// COMMIT;",
+ /// conn.execute_batch(
+ /// "BEGIN;
+ /// CREATE TABLE foo(x INTEGER);
+ /// CREATE TABLE bar(y TEXT);
+ /// COMMIT;",
/// )
/// }
/// ```
@@ -506,9 +517,12 @@ impl Connection {
/// ### With positional params of varying types
///
/// ```rust,no_run
- /// # use rusqlite::{Connection};
+ /// # use rusqlite::{params, 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 = ?1 AND quux = ?2",
+ /// params![1i32, 1.5f64],
+ /// ) {
/// Ok(updated) => println!("{} rows were updated", updated),
/// Err(err) => println!("update failed: {}", err),
/// }
@@ -537,6 +551,16 @@ impl Connection {
.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).
///
@@ -662,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.
@@ -704,69 +728,119 @@ impl Connection {
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")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
#[inline]
- pub fn load_extension_enable(&self) -> Result<()> {
+ 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")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
#[inline]
- pub fn load_extension<P: AsRef<Path>>(
+ pub unsafe fn load_extension<P: AsRef<Path>>(
&self,
dylib_path: P,
entry_point: Option<&str>,
@@ -822,7 +896,7 @@ impl Connection {
#[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
@@ -830,7 +904,7 @@ impl Connection {
/// connection.
#[inline]
fn changes(&self) -> usize {
- self.db.borrow_mut().changes()
+ self.db.borrow().changes()
}
/// Test for auto-commit mode.
@@ -843,9 +917,17 @@ impl Connection {
/// 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 {
@@ -945,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;
}
}
@@ -980,21 +1064,6 @@ 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>>,
@@ -1016,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
@@ -1054,7 +1123,7 @@ mod test {
ensure_sync::<InterruptHandle>();
}
- pub fn checked_memory_handle() -> Connection {
+ fn checked_memory_handle() -> Connection {
Connection::open_in_memory().unwrap()
}
@@ -1134,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);
@@ -1182,7 +1251,7 @@ mod test {
#[test]
fn test_close_retry() -> Result<()> {
- let db = checked_memory_handle();
+ 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
@@ -1235,7 +1304,7 @@ mod test {
#[test]
fn test_execute_batch() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(1);
@@ -1253,7 +1322,7 @@ mod test {
#[test]
fn test_execute() -> Result<()> {
- let db = checked_memory_handle();
+ 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])?);
@@ -1271,9 +1340,11 @@ mod test {
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);
- }
+ assert!(
+ err == Error::ExecuteReturnedResults,
+ "Unexpected error: {}",
+ err
+ );
}
#[test]
@@ -1294,7 +1365,7 @@ mod test {
#[test]
fn test_prepare_column_names() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let stmt = db.prepare("SELECT * FROM foo")?;
@@ -1309,7 +1380,7 @@ mod test {
#[test]
fn test_prepare_execute() -> Result<()> {
- let db = checked_memory_handle();
+ 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(?)")?;
@@ -1330,7 +1401,7 @@ mod test {
#[test]
fn test_prepare_query() -> Result<()> {
- let db = checked_memory_handle();
+ 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(?)")?;
@@ -1365,7 +1436,7 @@ mod test {
#[test]
fn test_query_map() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1384,7 +1455,7 @@ mod test {
#[test]
fn test_query_row() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(1);
@@ -1413,7 +1484,7 @@ mod test {
#[test]
fn test_optional() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 <> 0", [], |r| r.get(0));
let result = result.optional();
@@ -1437,7 +1508,7 @@ mod test {
#[test]
fn test_pragma_query_row() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
assert_eq!(
"memory",
@@ -1452,7 +1523,7 @@ mod test {
#[test]
fn test_prepare_failures() -> Result<()> {
- let db = checked_memory_handle();
+ 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();
@@ -1462,7 +1533,7 @@ mod test {
#[test]
fn test_last_insert_rowid() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
db.execute_batch("INSERT INTO foo DEFAULT VALUES")?;
@@ -1477,18 +1548,19 @@ mod test {
}
#[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() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
assert!(!db.is_busy());
let mut stmt = db.prepare("PRAGMA schema_version")?;
assert!(!db.is_busy());
@@ -1505,7 +1577,7 @@ mod test {
#[test]
fn test_statement_debugging() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let query = "SELECT 12345";
let stmt = db.prepare(query)?;
@@ -1524,7 +1596,7 @@ mod test {
#[cfg(not(feature = "modern_sqlite"))]
fn check_extended_code(_extended_code: c_int) {}
- let db = checked_memory_handle();
+ 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)", []);
@@ -1553,7 +1625,7 @@ mod test {
#[test]
#[cfg(feature = "functions")]
fn test_interrupt() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let interrupt_handle = db.get_interrupt_handle();
@@ -1601,7 +1673,7 @@ mod test {
#[test]
fn test_get_raw() -> Result<()> {
- let db = checked_memory_handle();
+ 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(?, ?)")?;
@@ -1634,7 +1706,7 @@ mod test {
#[test]
fn test_from_handle() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let handle = unsafe { db.handle() };
{
let db = unsafe { Connection::from_handle(handle) }?;
@@ -1686,7 +1758,7 @@ mod test {
#[test]
fn test_query_and_then() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1706,7 +1778,7 @@ mod test {
#[test]
fn test_query_and_then_fails() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1736,7 +1808,7 @@ mod test {
#[test]
fn test_query_and_then_custom_error() -> CustomResult<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1757,7 +1829,7 @@ mod test {
#[test]
fn test_query_and_then_custom_error_fails() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1799,7 +1871,7 @@ mod test {
#[test]
fn test_query_row_and_then_custom_error() -> CustomResult<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1816,7 +1888,7 @@ mod test {
#[test]
fn test_query_row_and_then_custom_error_fails() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1853,7 +1925,7 @@ mod test {
#[test]
fn test_dynamic() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1861,13 +1933,13 @@ mod test {
db.execute_batch(sql)?;
db.query_row("SELECT * FROM foo", [], |r| {
- assert_eq!(2, r.column_count());
+ assert_eq!(2, r.as_ref().column_count());
Ok(())
})
}
#[test]
fn test_dyn_box() -> Result<()> {
- let db = checked_memory_handle();
+ 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])?;
@@ -1879,7 +1951,7 @@ mod test {
#[test]
fn test_params() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.query_row(
"SELECT
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
@@ -1900,7 +1972,7 @@ mod test {
#[test]
#[cfg(not(feature = "extra_check"))]
fn test_alter_table() -> Result<()> {
- let db = checked_memory_handle();
+ 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;", [])?;
@@ -1909,7 +1981,7 @@ mod test {
#[test]
fn test_batch() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = r"
CREATE TABLE tbl1 (col);
CREATE TABLE tbl2 (col);
@@ -1923,9 +1995,9 @@ mod test {
}
#[test]
- #[cfg(feature = "bundled")] // SQLite >= 3.35.0
+ #[cfg(all(feature = "bundled", not(feature = "bundled-sqlcipher")))] // SQLite >= 3.35.0
fn test_returning() -> Result<()> {
- let db = checked_memory_handle();
+ 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| {
@@ -1934,4 +2006,11 @@ mod test {
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 cacaa90..93e0bb0 100644
--- a/src/limits.rs
+++ b/src/limits.rs
@@ -1,23 +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) }
@@ -26,10 +66,64 @@ impl Connection {
#[cfg(test)]
mod test {
- use crate::ffi::Limit;
+ 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() -> Result<()> {
let db = Connection::open_in_memory()?;
db.set_limit(Limit::SQLITE_LIMIT_LENGTH, 1024);
diff --git a/src/load_extension_guard.rs b/src/load_extension_guard.rs
index b4c38c2..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,8 +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.
+ ///
+ /// # 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 fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
+ pub unsafe fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
conn.load_extension_enable()
.map(|_| LoadExtensionGuard { conn })
}
diff --git a/src/params.rs b/src/params.rs
index 88fce97..54aa571 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -99,7 +99,7 @@ use sealed::Sealed;
///
/// - 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.
+/// pass this as `stmt`.
///
/// - As array references, similar to the positional params. This looks like
/// `thing.query(&[(":foo", &1i32), (":bar", &2i32)])` or
@@ -116,7 +116,7 @@ use sealed::Sealed;
/// 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 })?;
+/// stmt.execute(named_params! { ":key": "one", ":val": 2 })?;
/// // Alternatively:
/// stmt.execute(&[(":key", "three"), (":val", "four")])?;
/// // Or:
@@ -169,8 +169,8 @@ pub trait Params: Sealed {
// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
// unambiguous, this must be the *only* implementation for an empty array. This
// avoids `NO_PARAMS` being a necessary part of the API.
-impl Sealed for [&dyn ToSql; 0] {}
-impl Params for [&dyn ToSql; 0] {
+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`
@@ -251,7 +251,7 @@ impl_for_array_ref!(
/// ## Basic usage
///
/// ```rust,no_run
-/// use rusqlite::{Connection, Result, params_from_iter};
+/// use rusqlite::{params_from_iter, Connection, Result};
/// use std::collections::BTreeSet;
///
/// fn query(conn: &Connection, ids: &BTreeSet<String>) -> Result<()> {
diff --git a/src/pragma.rs b/src/pragma.rs
index 16a6307..1c81c95 100644
--- a/src/pragma.rs
+++ b/src/pragma.rs
@@ -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);
}
@@ -198,7 +198,7 @@ impl Connection {
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([])?;
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,7 +283,7 @@ 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)?;
+ sql.push_value(&pragma_value)?;
self.query_row(&sql, [], f)
}
}
@@ -393,15 +398,26 @@ mod test {
#[test]
fn pragma_update() -> Result<()> {
let db = Connection::open_in_memory()?;
- db.pragma_update(None, "user_version", &1)
+ db.pragma_update(None, "user_version", 1)
}
#[test]
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))?;
+ 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(())
}
diff --git a/src/raw_statement.rs b/src/raw_statement.rs
index ef94d81..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.
@@ -34,7 +34,7 @@ impl RawStatement {
RawStatement {
ptr: stmt,
tail,
- cache: Default::default(),
+ cache: ParamIndexCache::default(),
statement_cache_key: None,
}
}
@@ -101,25 +101,38 @@ impl RawStatement {
}
}
- #[cfg_attr(not(feature = "unlock_notify"), inline)]
+ #[inline]
+ #[cfg(not(feature = "unlock_notify"))]
+ pub fn step(&self) -> c_int {
+ unsafe { ffi::sqlite3_step(self.ptr) }
+ }
+
+ #[cfg(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;
+ 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;
}
- rc = unsafe { unlock_notify::wait_for_unlock_notify(db) };
+ if db.is_null() {
+ db = ffi::sqlite3_db_handle(self.ptr);
+ }
+ 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) }
}
}
diff --git a/src/row.rs b/src/row.rs
index 137f13d..c766e50 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -78,6 +78,12 @@ impl<'stmt> Rows<'stmt> {
{
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> {
@@ -151,7 +157,7 @@ where
self.rows
.next()
.transpose()
- .map(|row_result| row_result.and_then(|row| (map)(&row)))
+ .map(|row_result| row_result.and_then(map))
}
}
@@ -176,13 +182,13 @@ where
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.
///
@@ -204,8 +210,8 @@ impl<'stmt> FallibleStreamingIterator for Rows<'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(())
@@ -220,11 +226,10 @@ impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
self.row = None;
Err(e)
}
- },
- None => {
- self.row = None;
- Ok(())
}
+ } else {
+ self.row = None;
+ Ok(())
}
}
@@ -283,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(),
- ),
})
}
@@ -358,6 +354,12 @@ impl<'stmt> Row<'stmt> {
}
}
+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.
diff --git a/src/session.rs b/src/session.rs
index 4e4c354..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>,
@@ -40,12 +40,12 @@ impl Session<'_> {
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,
@@ -109,15 +109,14 @@ 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 })
}
@@ -125,14 +124,13 @@ impl Session<'_> {
#[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
@@ -140,7 +138,7 @@ impl Session<'_> {
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 })
}
@@ -149,19 +147,18 @@ impl Session<'_> {
#[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 {
@@ -223,23 +220,22 @@ 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,
@@ -249,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,
@@ -258,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,
@@ -274,9 +269,9 @@ impl Changeset {
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 })
}
@@ -284,7 +279,7 @@ impl 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,
@@ -297,9 +292,9 @@ impl Changeset {
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 })
}
}
@@ -313,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>,
@@ -326,13 +321,13 @@ impl ChangesetIter<'_> {
#[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,
@@ -367,7 +362,7 @@ impl FallibleStreamingIterator for ChangesetIter<'_> {
}
}
-/// `feature = "session"`
+/// Operation
pub struct Operation<'item> {
table_name: &'item str,
number_of_columns: i32,
@@ -410,7 +405,7 @@ impl Drop for ChangesetIter<'_> {
}
}
-/// `feature = "session"` An item passed to a conflict-handler by
+/// 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, ...
@@ -427,11 +422,11 @@ impl ChangesetItem {
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))
}
}
@@ -444,7 +439,7 @@ impl ChangesetItem {
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)
}
}
@@ -457,7 +452,7 @@ impl ChangesetItem {
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))
}
}
@@ -470,7 +465,7 @@ impl ChangesetItem {
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))
}
}
@@ -483,13 +478,13 @@ impl ChangesetItem {
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()?;
@@ -507,17 +502,17 @@ impl ChangesetItem {
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,
@@ -528,29 +523,27 @@ impl Changegroup {
#[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
@@ -558,7 +551,7 @@ impl Changegroup {
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 })
}
@@ -566,14 +559,13 @@ impl Changegroup {
#[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(())
+ })
}
}
@@ -587,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,
@@ -597,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,
@@ -617,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,
@@ -637,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,
@@ -657,12 +648,11 @@ 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)]
@@ -690,7 +680,7 @@ 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)]
@@ -826,7 +816,7 @@ mod test {
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()?;
assert_eq!(&[1], pk);
diff --git a/src/statement.rs b/src/statement.rs
index 139f504..60abd90 100644
--- a/src/statement.rs
+++ b/src/statement.rs
@@ -55,7 +55,7 @@ impl Statement<'_> {
/// // 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 })?;
+ /// 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)
@@ -135,8 +135,8 @@ 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
+ /// 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.
///
@@ -208,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_params!{ ":name": "one" })?;
+ /// let mut rows = stmt.query(named_params! { ":name": "one" })?;
/// while let Some(row) = rows.next()? {
/// // ...
/// }
@@ -346,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(&[(":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 {
@@ -453,7 +452,7 @@ impl Statement<'_> {
{
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
@@ -718,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(),
)
@@ -776,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()
@@ -875,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)
@@ -897,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.
@@ -946,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,
}
diff --git a/src/trace.rs b/src/trace.rs
index 8e8fd3a..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,7 +52,7 @@ 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) {
@@ -64,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
@@ -75,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();
@@ -89,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
@@ -109,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();
diff --git a/src/transaction.rs b/src/transaction.rs
index a1b287d..296b2aa 100644
--- a/src/transaction.rs
+++ b/src/transaction.rs
@@ -169,6 +169,7 @@ impl Transaction<'_> {
/// 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
}
@@ -177,7 +178,7 @@ impl Transaction<'_> {
/// 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.
@@ -296,6 +297,7 @@ impl Savepoint<'_> {
/// 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
}
@@ -304,7 +306,7 @@ impl Savepoint<'_> {
/// 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.
@@ -378,8 +380,8 @@ impl Connection {
///
/// The transaction defaults to rolling back when it is dropped. If you
/// want the transaction to commit, you must call
- /// [`commit`](Transaction::commit) or [`set_drop_behavior(DropBehavior:
- /// :Commit)`](Transaction::set_drop_behavior).
+ /// [`commit`](Transaction::commit) or
+ /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior).
///
/// ## Example
///
diff --git a/src/types/chrono.rs b/src/types/chrono.rs
index 38276da..6bfc2f4 100644
--- a/src/types/chrono.rs
+++ b/src/types/chrono.rs
@@ -1,6 +1,6 @@
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
-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;
@@ -83,9 +83,19 @@ impl FromSql for NaiveDateTime {
}
}
-/// Date and time with time zone => UTC RFC3339 timestamp
+/// UTC time => UTC RFC3339 timestamp
/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
-impl<Tz: TimeZone> ToSql for DateTime<Tz> {
+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();
@@ -93,6 +103,16 @@ impl<Tz: TimeZone> ToSql for DateTime<Tz> {
}
}
+/// 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<'_>> {
+ let date_str = self.format("%F %T%.f%:z").to_string();
+ Ok(ToSqlOutput::from(date_str))
+ }
+}
+
/// 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> {
@@ -125,13 +145,26 @@ impl FromSql for DateTime<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::{
types::{FromSql, ValueRef},
Connection, Result,
};
- use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
+ use chrono::{
+ DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
+ };
fn checked_memory_handle() -> Result<Connection> {
let db = Connection::open_in_memory()?;
@@ -234,6 +267,23 @@ mod test {
}
#[test]
+ 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));
diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs
index 7381fdf..88bdd14 100644
--- a/src/types/from_sql.rs
+++ b/src/types/from_sql.rs
@@ -15,16 +15,14 @@ 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),
-
- /// `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),
+ /// 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,
+ },
/// An error case available for implementors of the [`FromSql`] trait.
Other(Box<dyn Error + Send + Sync + 'static>),
@@ -35,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,
}
}
@@ -49,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),
}
@@ -171,37 +177,38 @@ impl FromSql for std::sync::Arc<str> {
impl FromSql for Vec<u8> {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- value.as_blob().map(|b| b.to_vec())
+ value.as_blob().map(<[u8]>::to_vec)
+ }
+}
+
+impl<const N: usize> FromSql for [u8; N] {
+ #[inline]
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ 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)))
}
}
diff --git a/src/types/mod.rs b/src/types/mod.rs
index 706be0c..4e524b2 100644
--- a/src/types/mod.rs
+++ b/src/types/mod.rs
@@ -41,22 +41,24 @@ 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())
}
}
```
@@ -75,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;
diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs
index f018032..a9761bd 100644
--- a/src/types/serde_json.rs
+++ b/src/types/serde_json.rs
@@ -17,12 +17,8 @@ impl ToSql for 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)))
}
}
diff --git a/src/types/time.rs b/src/types/time.rs
index ac182a9..4e2811e 100644
--- a/src/types/time.rs
+++ b/src/types/time.rs
@@ -1,16 +1,35 @@
//! [`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-%d %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))
}
}
@@ -18,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)))
})
@@ -34,23 +69,19 @@ impl FromSql for OffsetDateTime {
#[cfg(test)]
mod test {
use crate::{Connection, Result};
- use std::time::Duration;
+ use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
- fn checked_memory_handle() -> Result<Connection> {
- let db = Connection::open_in_memory()?;
- db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
- Ok(db)
- }
-
#[test]
fn test_offset_date_time() -> Result<()> {
- let db = checked_memory_handle()?;
+ 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)
@@ -72,8 +103,55 @@ mod test {
}
#[test]
+ 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 = checked_memory_handle()?;
+ let db = Connection::open_in_memory()?;
let result: Result<OffsetDateTime> =
db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
assert!(result.is_ok());
@@ -82,7 +160,7 @@ mod test {
#[test]
fn test_param() -> Result<()> {
- let db = checked_memory_handle()?;
+ 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 1bf7711..2445339 100644
--- a/src/types/to_sql.rs
+++ b/src/types/to_sql.rs
@@ -16,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),
}
@@ -70,9 +72,11 @@ 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<'_> {
@@ -162,9 +166,11 @@ 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(
@@ -218,6 +224,13 @@ impl ToSql for Vec<u8> {
}
}
+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<'_>> {
@@ -260,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";
diff --git a/src/types/value.rs b/src/types/value.rs
index 944655c..ca3ee9f 100644
--- a/src/types/value.rs
+++ b/src/types/value.rs
@@ -41,19 +41,18 @@ impl From<isize> for Value {
}
#[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 {
@@ -130,6 +129,7 @@ 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 446ad08..c0d81ca 100644
--- a/src/types/value_ref.rs
+++ b/src/types/value_ref.rs
@@ -22,6 +22,7 @@ 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,
@@ -45,6 +46,19 @@ 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)`](crate::Error::
/// InvalidColumnType).
@@ -56,6 +70,19 @@ 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)`](crate::Error::InvalidColumnType).
#[inline]
@@ -68,6 +95,21 @@ 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)`](crate::Error::InvalidColumnType).
#[inline]
@@ -77,6 +119,41 @@ impl<'a> ValueRef<'a> {
_ => 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 {
@@ -152,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 => {
@@ -170,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 5f9cdbf..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,17 +85,6 @@ 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};
diff --git a/src/util/small_cstr.rs b/src/util/small_cstr.rs
index 7349df0..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();
diff --git a/src/util/sqlite_string.rs b/src/util/sqlite_string.rs
index d131b56..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};
@@ -138,7 +138,7 @@ impl SqliteMallocString {
// 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);
diff --git a/src/version.rs b/src/version.rs
index 6f56ee2..d70af7e 100644
--- a/src/version.rs
+++ b/src/version.rs
@@ -6,6 +6,7 @@ use std::ffi::CStr;
///
/// See [`sqlite3_libversion_number()`](https://www.sqlite.org/c3ref/libversion.html).
#[inline]
+#[must_use]
pub fn version_number() -> i32 {
unsafe { ffi::sqlite3_libversion_number() }
}
@@ -14,6 +15,7 @@ pub fn version_number() -> i32 {
///
/// 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 713604c..adfd9c9 100644
--- a/src/vtab/array.rs
+++ b/src/vtab/array.rs
@@ -1,4 +1,4 @@
-//! `feature = "array"` Array Virtual Table.
+//! Array Virtual Table.
//!
//! Note: `rarray`, not `carray` is the name of the table valued function we
//! define.
@@ -41,10 +41,10 @@ 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
@@ -57,7 +57,7 @@ impl ToSql for Array {
}
}
-/// `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)
@@ -91,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;
}
@@ -100,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);
}
diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs
index 096f272..df3529a 100644
--- a/src/vtab/csvtab.rs
+++ b/src/vtab/csvtab.rs
@@ -1,4 +1,4 @@
-//! `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`
@@ -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
@@ -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();
}
}
diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs
index f364f1f..bdb6509 100644
--- a/src/vtab/mod.rs
+++ b/src/vtab/mod.rs
@@ -1,4 +1,4 @@
-//! `feature = "vtab"` Create virtual tables.
+//! Create virtual tables.
//!
//! Follow these steps to create your own virtual table:
//! 1. Write implementation of [`VTab`] and [`VTabCursor`] traits.
@@ -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,18 +191,18 @@ 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 */
/// }
/// ```
@@ -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> {
@@ -257,7 +261,7 @@ 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)]
@@ -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,20 +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
+/// 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) };
@@ -321,6 +343,7 @@ 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) };
@@ -331,6 +354,7 @@ 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 }
}
@@ -370,6 +394,7 @@ 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 {
@@ -383,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>,
@@ -393,7 +442,7 @@ impl<'a> Iterator for IndexConstraintIter<'a> {
#[inline]
fn next(&mut self) -> Option<IndexConstraint<'a>> {
- self.iter.next().map(|raw| IndexConstraint(raw))
+ self.iter.next().map(IndexConstraint)
}
#[inline]
@@ -402,30 +451,33 @@ impl<'a> Iterator for IndexConstraintIter<'a> {
}
}
-/// `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
+/// Information about what parameters to pass to
/// [`VTabCursor::filter`].
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
@@ -454,7 +506,7 @@ impl<'a> Iterator for OrderByIter<'a> {
#[inline]
fn next(&mut self) -> Option<OrderBy<'a>> {
- self.iter.next().map(|raw| OrderBy(raw))
+ self.iter.next().map(OrderBy)
}
#[inline]
@@ -463,31 +515,35 @@ impl<'a> Iterator for OrderByIter<'a> {
}
}
-/// `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 */
/// }
/// ```
@@ -514,7 +570,7 @@ 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);
@@ -530,7 +586,7 @@ impl Context {
// TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html)
}
-/// `feature = "vtab"` Wrapper to [`VTabCursor::filter`] arguments, the values
+/// Wrapper to [`VTabCursor::filter`] arguments, the values
/// requested by [`VTab::best_index`].
pub struct Values<'a> {
args: &'a [*mut ffi::sqlite3_value],
@@ -539,12 +595,14 @@ pub struct Values<'a> {
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()
}
@@ -558,21 +616,17 @@ 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")]
+ #[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];
@@ -591,6 +645,7 @@ impl Values<'_> {
/// Turns `Values` into an iterator.
#[inline]
+ #[must_use]
pub fn iter(&self) -> ValueIter<'_> {
ValueIter {
iter: self.args.iter(),
@@ -630,7 +685,7 @@ impl<'a> Iterator for ValueIter<'a> {
}
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).
@@ -661,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>),
)
}
@@ -680,17 +735,19 @@ impl InnerConnection {
}
}
-/// `feature = "vtab"` Escape double-quote (`"`) character occurrences 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;
@@ -703,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")
@@ -728,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>(
@@ -745,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()
@@ -757,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);
@@ -797,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()
@@ -809,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);
@@ -842,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,
@@ -866,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
}
@@ -878,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)) => {
@@ -904,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)) => {
@@ -928,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
}
@@ -969,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
}
@@ -981,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))
}
@@ -993,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;
@@ -1027,7 +1085,7 @@ unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<
#[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);
}
@@ -1072,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 31ef86f..f26212a 100644
--- a/src/vtab/series.rs
+++ b/src/vtab/series.rs
@@ -1,4 +1,4 @@
-//! `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):
@@ -15,7 +15,7 @@ use crate::vtab::{
};
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,7 +38,7 @@ bitflags::bitflags! {
const STEP = 4;
// output in descending order
const DESC = 8;
- // output in descending order
+ // output in ascending order
const ASC = 16;
// Both start and stop
const BOTH = QueryPlanFlags::START.bits | QueryPlanFlags::STOP.bits;
@@ -123,12 +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 {
- idx_num |= QueryPlanFlags::ASC;
+ false
}
- true
} else {
false
}
diff --git a/tests/vtab.rs b/tests/vtab.rs
index 5c5bdef..fa26459 100644
--- a/tests/vtab.rs
+++ b/tests/vtab.rs
@@ -85,7 +85,7 @@ fn test_dummy_module() -> rusqlite::Result<()> {
let db = Connection::open_in_memory()?;
- db.create_module::<DummyTab>("dummy", &module, None)?;
+ db.create_module::<DummyTab>("dummy", module, None)?;
let version = version_number();
if version < 3_008_012 {