diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-09 06:02:13 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-09 06:02:13 +0000 |
commit | 9d6c2950b8c8ecbb7275c9bc6056fbafa34aedbb (patch) | |
tree | ed3dae2c91a4d67fe43675ec1ee9eaf437cd8280 | |
parent | 7a8b28cf9d468edeb3bacc1e304adf0ccd5bafb8 (diff) | |
parent | 93c8cc61e4753e8db9b57fbc2e3e9274495e9809 (diff) | |
download | rusqlite-9d6c2950b8c8ecbb7275c9bc6056fbafa34aedbb.tar.gz |
Snap for 8558685 from 93c8cc61e4753e8db9b57fbc2e3e9274495e9809 to tm-frc-extservices-releaset_frc_ext_330443000android13-frc-extservices-release
Change-Id: Ie2e85799f80534976178827100c43e040947171c
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 @@ -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: [ @@ -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 @@ -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 } } @@ -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(()) @@ -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) } } } @@ -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 { |