Update rusqlite to 0.27.0 am: 82c86ae98e am: 8386405f4f am: 8e934d15c4 am: 93c8cc61e4

Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/rusqlite/+/2006011

Change-Id: I51755a378ec473338367ada10f64c2fa4c642863
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index f4d4931..de52e1b 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,6 @@
 {
   "git": {
-    "sha1": "e7bb33a99ca6f122b5f338efd7889a6509b3dee0"
-  }
-}
+    "sha1": "8141b5e085bd3a02951588413e5569f1ddf17a8c"
+  },
+  "path_in_vcs": ""
+}
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index c16503f..0000000
--- a/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-libsqlite3-sys/sqlite3/* linguist-vendored
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index 2c2d7fc..0000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,153 +0,0 @@
-name: CI
-
-on:
-  push:
-    branches:
-      - master
-  pull_request:
-    branches:
-      - master
-  schedule:
-    - cron: '00 01 * * *'
-env:
-  RUST_BACKTRACE: 1
-jobs:
-  test:
-    name: Test ${{ matrix.target }}
-
-    strategy:
-      fail-fast: false
-
-      matrix:
-        include:
-          - { target: x86_64-pc-windows-msvc, os: windows-latest }
-          - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }
-          - { target: x86_64-apple-darwin, os: macos-latest }
-          - { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
-
-    runs-on: ${{ matrix.os }}
-
-    steps:
-      - uses: actions/checkout@v2
-      # This has a matcher for test panics, so we use it even though elsewhere
-      # we use actions-rs/toolchain.
-      - uses: hecrj/setup-rust-action@v1
-        with:
-          rust-version: stable${{ matrix.host }}
-          targets: ${{ matrix.target }}
-
-      - run: cargo build --features bundled --workspace --all-targets --verbose
-      - run: cargo test --features bundled --workspace --all-targets --verbose
-      - run: cargo test --features bundled --workspace --doc --verbose
-
-      - name: Test Features
-        # TODO: clang is installed on these -- but `bindgen` can't find it...
-        if: matrix.os != 'windows-latest'
-        run: |
-          cargo test --features 'bundled-full session buildtime_bindgen time' --all-targets --workspace --verbose
-          cargo test --features 'bundled-full session buildtime_bindgen time' --doc --workspace --verbose
-
-      - name: Static build
-        # Do we expect this to work / should we test with gnu toolchain?
-        if: matrix.os == 'x86_64-pc-windows-msvc'
-        env:
-          RUSTFLAGS: -Ctarget-feature=+crt-static
-        run: cargo build --features bundled
-
-  winsqlite3:
-    name: Test with winsqlite3
-    runs-on: windows-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: hecrj/setup-rust-action@v1
-      # TODO: Should this test GNU toolchain? What about +crt-static?
-      # TODO: Is it worth testing other features?
-      - run: cargo build --features winsqlite3 --workspace --all-targets --verbose
-      - run: cargo test --features winsqlite3 --workspace --all-targets --verbose
-
-  sqlcipher:
-    name: Test with sqlcipher
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: hecrj/setup-rust-action@v1
-      - run: sudo apt-get install sqlcipher libsqlcipher-dev
-      - run: sqlcipher --version
-      # TODO: Is it worth testing other features?
-      - run: cargo build --features sqlcipher --workspace --all-targets --verbose
-      - run: cargo test --features sqlcipher --workspace --all-targets --verbose
-
-  sanitizer:
-    name: Address Sanitizer
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      # Need nightly rust.
-      - uses: hecrj/setup-rust-action@v1
-        with:
-          rust-version: nightly
-          components: rust-src
-      - name: Tests with asan
-        env:
-          RUSTFLAGS: -Zsanitizer=address
-          RUSTDOCFLAGS: -Zsanitizer=address
-          ASAN_OPTIONS: 'detect_stack_use_after_return=1:detect_leaks=0'
-          # Work around https://github.com/rust-lang/rust/issues/59125 by
-          # disabling backtraces. In an ideal world we'd probably suppress the
-          # leak sanitization, but we don't care about backtraces here, so long
-          # as the other tests have them.
-          RUST_BACKTRACE: '0'
-        run: cargo -Z build-std test --features 'bundled-full session buildtime_bindgen time with-asan' --target x86_64-unknown-linux-gnu
-
-  # Ensure clippy doesn't complain.
-  clippy:
-    name: Clippy
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: hecrj/setup-rust-action@v1
-        with:
-          components: clippy
-      - run: cargo clippy --all-targets --workspace --features bundled -- -D warnings
-      # Clippy with all non-conflicting features
-      - run: cargo clippy --all-targets --workspace --features 'bundled-full session buildtime_bindgen time' -- -D warnings
-
-  # Ensure patch is formatted.
-  fmt:
-    name: Format
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: hecrj/setup-rust-action@v1
-        with:
-          components: rustfmt
-      - run: cargo fmt --all -- --check
-
-  # Detect cases where documentation links don't resolve and such.
-  doc:
-    name: Docs
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: hecrj/setup-rust-action@v1
-        with:
-          rust-version: nightly
-      # Need to use `cargo rustdoc` to actually get it to respect -D
-      # warnings... Note: this also requires nightly.
-      - run: cargo rustdoc --features 'bundled-full session buildtime_bindgen time' -- -D warnings
-
-  codecov:
-    name: Generate code coverage
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: hecrj/setup-rust-action@v1
-      - name: Run cargo-tarpaulin
-        uses: actions-rs/tarpaulin@v0.1
-        with:
-          # Intentionally omit time feature until we're on time 0.3, at which
-          # point it should be added to `bundled-full`.
-          args: '--features "bundled-full session buildtime_bindgen"'
-
-      - name: Upload to codecov.io
-        uses: codecov/codecov-action@v1
diff --git a/Android.bp b/Android.bp
index a83fce2..d5c31aa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,7 +23,7 @@
     host_supported: true,
     crate_name: "rusqlite",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.25.3",
+    cargo_pkg_version: "0.27.0",
     srcs: ["src/lib.rs"],
     edition: "2018",
     features: [
diff --git a/Cargo.toml b/Cargo.toml
index abcdbe1..f8b8dda 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,34 +3,50 @@
 # When uploading crates to the registry Cargo will automatically
 # "normalize" Cargo.toml files for maximal compatibility
 # with all versions of Cargo and also rewrite `path` dependencies
-# to registry (e.g., crates.io) dependencies
+# to registry (e.g., crates.io) dependencies.
 #
-# If you believe there's an error in this file please file an
-# issue against the rust-lang/cargo repository. If you're
-# editing this file be aware that the upstream Cargo.toml
-# will likely look very different (and much more reasonable)
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
 
 [package]
 edition = "2018"
 name = "rusqlite"
-version = "0.25.3"
+version = "0.27.0"
 authors = ["The rusqlite developers"]
+exclude = [
+    "/.github/*",
+    "/.gitattributes",
+    "/appveyor.yml",
+    "/Changelog.md",
+    "/clippy.toml",
+    "/codecov.yml",
+]
 description = "Ergonomic wrapper for SQLite"
 documentation = "http://docs.rs/rusqlite/"
 readme = "README.md"
-keywords = ["sqlite", "database", "ffi"]
+keywords = [
+    "sqlite",
+    "database",
+    "ffi",
+]
 categories = ["database"]
 license = "MIT"
 repository = "https://github.com/rusqlite/rusqlite"
+
 [package.metadata.docs.rs]
+features = ["modern-full"]
 all-features = false
-default-target = "x86_64-unknown-linux-gnu"
-features = ["array", "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "time", "trace", "url", "vtab", "window", "modern_sqlite", "column_decltype"]
 no-default-features = true
+default-target = "x86_64-unknown-linux-gnu"
+rustdoc-args = [
+    "--cfg",
+    "docsrs",
+]
 
 [package.metadata.playground]
-all-features = false
 features = ["bundled-full"]
+all-features = false
 
 [lib]
 name = "rusqlite"
@@ -52,17 +68,15 @@
 [[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 @@
 optional = true
 
 [dependencies.libsqlite3-sys]
-version = "0.22.2"
+version = "0.24.0"
 
 [dependencies.memchr]
 version = "2.3"
@@ -95,7 +109,12 @@
 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 @@
 [dependencies.uuid]
 version = "0.8"
 optional = true
+
 [dev-dependencies.bencher]
 version = "0.1"
 
@@ -132,30 +152,79 @@
 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 @@
 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 @@
 # 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 @@
 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 @@
     "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 @@
     "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 @@
 
 [dependencies.libsqlite3-sys]
 path = "libsqlite3-sys"
-version = "0.22.2"
+version = "0.24.0"
 
 [[test]]
 name = "config_log"
@@ -144,10 +154,11 @@
 harness = false
 
 [package.metadata.docs.rs]
-features = [ "array", "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "time", "trace", "url", "vtab", "window", "modern_sqlite", "column_decltype" ]
+features = ["modern-full"]
 all-features = false
 no-default-features = true
 default-target = "x86_64-unknown-linux-gnu"
+rustdoc-args = ["--cfg", "docsrs"]
 
 [package.metadata.playground]
 features = ["bundled-full"]
diff --git a/Changelog.md b/Changelog.md
deleted file mode 100644
index 6ba11d7..0000000
--- a/Changelog.md
+++ /dev/null
@@ -1,332 +0,0 @@
-For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlite/releases) page.
-
-# Version 0.14.0 (2018-08-17)
-
-* BREAKING CHANGE: `ToSql` implementation for `time::Timespec` uses RFC 3339 (%Y-%m-%dT%H:%M:%S.%fZ).
-  Previous format was %Y-%m-%d %H:%M:%S:%f %Z.
-* BREAKING CHANGE: Remove potentially conflicting impl of ToSqlOutput (#313).
-* BREAKING CHANGE: Replace column index/count type (i32) with usize.
-* BREAKING CHANGE: Replace parameter index/count type (i32) with usize.
-* BREAKING CHANGE: Replace row changes/count type (i32) with usize.
-* BREAKING CHANGE: Scalar functions must be `Send`able and `'static`.
-* Bugfix: Commit failure unhandled, database left in unusable state (#366).
-* Bugfix: `free_boxed_hook` does not work for `fn`.
-* Update the bundled SQLite version to 3.24.0 (#326).
-* Add DropBehavior::Panic to enforce intentional commit or rollback.
-* Implement `sqlite3_update_hook` (#260, #328), `sqlite3_commit_hook` and `sqlite3_rollback_hook`.
-* Add support to unlock notification behind `unlock_notify` feature (#294, #331).
-* Make `Statement::column_index` case insensitive (#330).
-* Add comment to justify `&mut Connection` in `Transaction`.
-* Fix `tyvar_behind_raw_pointer` warnings.
-* Fix handful of clippy warnings.
-* Fix `Connection::open` documentation (#332)
-* Add binding to `sqlite3_get_autocommit` and `sqlite3_stmt_busy`.
-* Add binding to `sqlite3_busy_timeout` and `sqlite3_busy_handler`.
-* Add binding to `sqlite3_expanded_sql`.
-* Use `rerun-if-env-changed` in libsqlite3-sys (#329).
-* Return an `InvalidQuery` error when SQL is not read only.
-
-# Version 0.13.0 (2017-11-13)
-
-* Added ToSqlConversionFailure case to Error enum.
-* Now depends on chrono 0.4, bitflats 1.0, and (optionally) cc 1.0 / bindgen 0.31.
-* The ToSql/FromSql implementations for time::Timespec now include
-  and expect fractional seconds and timezone in the serialized string.
-* The RowIndex type used in Row::get is now publicly exported.
-* New `sqlcipher` feature allows linking against SQLCipher instead of SQLite.
-* Doc link in README now point to docs.rs.
-
-# Version 0.12.0 (2017-05-29)
-
-* Defines HAVE\_USLEEP when building with a bundled SQLite (#263).
-* Updates dependencies to their latest versions, particularly serde to 1.0.
-* Adds support for vcpkg on Windows.
-* Adds `ToSql` impls for `str` and `[u8]`.
-
-# Version 0.11.0 (2017-04-06)
-
-* Avoid publicly exporting SQLite constants multiple times from libsqlite3-sys.
-* Adds `FromSql` and `ToSql` impls for `isize`. Documents why `usize` and `u64` are not included.
-
-# Version 0.10.1 (2017-03-03)
-
-* Updates the `bundled` SQLite version to 3.17.0.
-* Changes the build process to no longer require `bindgen`. This should improve
-  build times and no longer require a new-ish Clang. See the README for more
-  details.
-
-# Version 0.10.0 (2017-02-28)
-
-* Re-export the `ErrorCode` enum from `libsqlite3-sys`.
-* Adds `version()` and `version_number()` functions for querying the version of SQLite in use.
-* Adds the `limits` feature, exposing `limit()` and `set_limit()` methods on `Connection`.
-* Updates to `libsqlite3-sys` 0.7.0, which runs rust-bindgen at build-time instead of assuming the
-  precense of all expected SQLite constants and functions.
-* Clarifies supported SQLite versions. Running with SQLite older than 3.6.8 now panics, and
-  some features will not compile unless a sufficiently-recent SQLite version is used. See
-  the README for requirements of particular features.
-* When running with SQLite 3.6.x, rusqlite attempts to perform SQLite initialization. If it fails,
-  rusqlite will panic since it cannot ensure the threading mode for SQLite. This check can by
-  skipped by calling the unsafe function `rusqlite::bypass_sqlite_initialization()`. This is
-  technically a breaking change but is unlikely to affect anyone in practice, since prior to this
-  version the check that rusqlite was using would cause a segfault if linked against a SQLite
-  older than 3.7.0.
-* rusqlite now performs a one-time check (prior to the first connection attempt) that the runtime
-  SQLite version is at least as new as the SQLite version found at buildtime. This check can by
-  skipped by calling the unsafe function `rusqlite::bypass_sqlite_version_check()`.
-* Removes the `libc` dependency in favor of using `std::os::raw`
-
-# Version 0.9.5 (2017-01-26)
-
-* Add impls of `Clone`, `Debug`, and `PartialEq` to `ToSqlOutput`.
-
-# Version 0.9.4 (2017-01-25)
-
-* Update dependencies.
-
-# Version 0.9.3 (2017-01-23)
-
-* Make `ToSqlOutput` itself implement `ToSql`.
-
-# Version 0.9.2 (2017-01-22)
-
-* Bugfix: The `FromSql` impl for `i32` now returns an error instead of
-  truncating if the underlying SQLite value is out of `i32`'s range.
-* Added `FromSql` and `ToSql` impls for `i8`, `i16`, `u8`, `u16`, and `u32`.
-  `i32` and `i64` already had impls. `u64` is omitted because their range
-  cannot be represented by `i64`, which is the type we use to communicate with
-  SQLite.
-
-# Version 0.9.1 (2017-01-20)
-
-* BREAKING CHANGE: `Connection::close()` now returns a `Result<(), (Connection, Error)>` instead
-  of a `Result<(), Error>` so callers get the still-open connection back on failure.
-
-# Version 0.8.0 (2016-12-31)
-
-* BREAKING CHANGE: The `FromSql` trait has been redesigned. It now requires a single, safe
-  method instead of the previous definition which required implementing one or two unsafe
-  methods.
-* BREAKING CHANGE: The `ToSql` trait has been redesigned. It can now be implemented without
-  `unsafe`, and implementors can choose to return either borrowed or owned results.
-* BREAKING CHANGE: The closure passed to `query_row`, `query_row_and_then`, `query_row_safe`,
-  and `query_row_named` now expects a `&Row` instead of a `Row`. The vast majority of calls
-  to these functions will probably not need to change; see
-  https://github.com/jgallagher/rusqlite/pull/184.
-* BREAKING CHANGE: A few cases of the `Error` enum have sprouted additional information
-  (e.g., `FromSqlConversionFailure` now also includes the column index and the type returned
-  by SQLite).
-* Added `#[deprecated(since = "...", note = "...")]` flags (new in Rust 1.9 for libraries) to
-  all deprecated APIs.
-* Added `query_row` convenience function to `Statement`.
-* Added `bundled` feature which will build SQLite from source instead of attempting to link
-  against a SQLite that already exists on the system.
-* Fixed a bug where using cached prepared statements resulted in attempting to close a connection
-  failing with `DatabaseBusy`; see https://github.com/jgallagher/rusqlite/issues/186.
-
-# Version 0.7.3 (2016-06-01)
-
-* Fixes an incorrect failure from the `insert()` convenience function when back-to-back inserts to
-  different tables both returned the same row ID
-  ([#171](https://github.com/jgallagher/rusqlite/issues/171)).
-
-# Version 0.7.2 (2016-05-19)
-
-* BREAKING CHANGE: `Rows` no longer implements `Iterator`. It still has a `next()` method, but
-  the lifetime of the returned `Row` is now tied to the lifetime of the vending `Rows` object.
-  This behavior is more correct. Previously there were runtime checks to prevent misuse, but
-  other changes in this release to reset statements as soon as possible introduced yet another
-  hazard related to the lack of these lifetime connections. We were already recommending the
-  use of `query_map` and `query_and_then` over raw `query`; both of theose still return handles
-  that implement `Iterator`.
-* BREAKING CHANGE: `Transaction::savepoint()` now returns a `Savepoint` instead of another
-  `Transaction`. Unlike `Transaction`, `Savepoint`s can be rolled back while keeping the current
-  savepoint active.
-* BREAKING CHANGE: Creating transactions from a `Connection` or savepoints from a `Transaction`
-  now take `&mut self` instead of `&self` to correctly represent that transactions within a
-  connection are inherently nested. While a transaction is alive, the parent connection or
-  transaction is unusable, so `Transaction` now implements `Deref<Target=Connection>`, giving
-  access to `Connection`'s methods via the `Transaction` itself.
-* BREAKING CHANGE: `Transaction::set_commit` and `Transaction::set_rollback` have been replaced
-  by `Transaction::set_drop_behavior`.
-* Adds `Connection::prepare_cached`. `Connection` now keeps an internal cache of any statements
-  prepared via this method. The size of this cache defaults to 16 (`prepare_cached` will always
-  work but may re-prepare statements if more are prepared than the cache holds), and can be
-  controlled via `Connection::set_prepared_statement_cache_capacity`.
-* Adds `query_map_named` and `query_and_then_named` to `Statement`.
-* Adds `insert` convenience method to `Statement` which returns the row ID of an inserted row.
-* Adds `exists` convenience method returning whether a query finds one or more rows.
-* Adds support for serializing types from the `serde_json` crate. Requires the `serde_json` feature.
-* Adds support for serializing types from the `chrono` crate. Requires the `chrono` feature.
-* Removes `load_extension` feature from `libsqlite3-sys`. `load_extension` is still available
-  on rusqlite itself.
-* Fixes crash on nightly Rust when using the `trace` feature.
-* Adds optional `clippy` feature and addresses issues it found.
-* Adds `column_count()` method to `Statement` and `Row`.
-* Adds `types::Value` for dynamic column types.
-* Adds support for user-defined aggregate functions (behind the existing `functions` Cargo feature).
-* Introduces a `RowIndex` trait allowing columns to be fetched via index (as before) or name (new).
-* Introduces `ZeroBlob` type under the `blob` module/feature exposing SQLite's zeroblob API.
-* Adds CI testing for Windows via AppVeyor.
-* Fixes a warning building libsqlite3-sys under Rust 1.6.
-* Adds an unsafe `handle()` method to `Connection`. Please file an issue if you actually use it.
-
-# Version 0.6.0 (2015-12-17)
-
-* BREAKING CHANGE: `SqliteError` is now an enum instead of a struct. Previously, we were (ab)using
-  the error code and message to send back both underlying SQLite errors and errors that occurred
-  at the Rust level. Now those have been separated out; SQLite errors are returned as 
-  `SqliteFailure` cases (which still include the error code but also include a Rust-friendlier
-  enum as well), and rusqlite-level errors are captured in other cases. Because of this change,
-  `SqliteError` no longer implements `PartialEq`.
-* BREAKING CHANGE: When opening a new detection, rusqlite now detects if SQLite was compiled or
-  configured for single-threaded use only; if it was, connection attempts will fail. If this
-  affects you, please open an issue.
-* BREAKING CHANGE: `SqliteTransactionDeferred`, `SqliteTransactionImmediate`, and
-  `SqliteTransactionExclusive` are no longer exported. Instead, use
-  `TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and
-  `TransactionBehavior::Exclusive`.
-* Removed `Sqlite` prefix on many types:
-    * `SqliteConnection` is now `Connection`
-    * `SqliteError` is now `Error`
-    * `SqliteResult` is now `Result`
-    * `SqliteStatement` is now `Statement`
-    * `SqliteRows` is now `Rows`
-    * `SqliteRow` is now `Row`
-    * `SqliteOpenFlags` is now `OpenFlags`
-    * `SqliteTransaction` is now `Transaction`.
-    * `SqliteTransactionBehavior` is now `TransactionBehavior`.
-    * `SqliteLoadExtensionGuard` is now `LoadExtensionGuard`.
-  The old, prefixed names are still exported but are deprecated.
-* Adds a variety of `..._named` methods for executing queries using named placeholder parameters.
-* Adds `backup` feature that exposes SQLite's online backup API.
-* Adds `blob` feature that exposes SQLite's Incremental I/O for BLOB API.
-* Adds `functions` feature that allows user-defined scalar functions to be added to
-  open `SqliteConnection`s.
-
-# Version 0.5.0 (2015-12-08)
-
-* Adds `trace` feature that allows the use of SQLite's logging, tracing, and profiling hooks.
-* Slight change to the closure types passed to `query_map` and `query_and_then`:
-    * Remove the `'static` requirement on the closure's output type.
-    * Give the closure a `&SqliteRow` instead of a `SqliteRow`.
-* When building, the environment variable `SQLITE3_LIB_DIR` now takes precedence over pkg-config.
-* If `pkg-config` is not available, we will try to find `libsqlite3` in `/usr/lib`.
-* Add more documentation for failure modes of functions that return `SqliteResult`s.
-* Updates `libc` dependency to 0.2, fixing builds on ARM for Rust 1.6 or newer.
-
-# Version 0.4.0 (2015-11-03)
-
-* Adds `Sized` bound to `FromSql` trait as required by RFC 1214.
-
-# Version 0.3.1 (2015-09-22)
-
-* Reset underlying SQLite statements as soon as possible after executing, as recommended by
-  http://www.sqlite.org/cvstrac/wiki?p=ScrollingCursor.
-
-# Version 0.3.0 (2015-09-21)
-
-* Removes `get_opt`. Use `get_checked` instead.
-* Add `query_row_and_then` and `query_and_then` convenience functions. These are analogous to
-  `query_row` and `query_map` but allow functions that can fail by returning `Result`s.
-* Relax uses of `P: AsRef<...>` from `&P` to `P`.
-* Add additional error check for calling `execute` when `query` was intended.
-* Improve debug formatting of `SqliteStatement` and `SqliteConnection`.
-* Changes documentation of `get_checked` to correctly indicate that it returns errors (not panics)
-  when given invalid types or column indices.
-
-# Version 0.2.0 (2015-07-26)
-
-* Add `column_names()` to `SqliteStatement`.
-* By default, include `SQLITE_OPEN_NO_MUTEX` and `SQLITE_OPEN_URI` flags when opening a
-  new conneciton.
-* Fix generated bindings (e.g., `sqlite3_exec` was wrong).
-* Use now-generated `sqlite3_destructor_type` to define `SQLITE_STATIC` and `SQLITE_TRANSIENT`.
-
-# Version 0.1.0 (2015-05-11)
-
-* [breaking-change] Modify `query_row` to return a `Result` instead of unwrapping.
-* Deprecate `query_row_safe` (use `query_row` instead).
-* Add `query_map`.
-* Add `get_checked`, which asks SQLite to do some basic type-checking of columns.
-
-# Version 0.0.17 (2015-04-03)
-
-* Publish version that builds on stable rust (beta). This version lives on the
-  `stable` branch. Development continues on `master` and still requires a nightly
-  version of Rust.
-
-# Version 0.0.16
-
-* Updates to track rustc nightly.
-
-# Version 0.0.15
-
-* Make SqliteConnection `Send`.
-
-# Version 0.0.14
-
-* Remove unneeded features (also involves switching to `libc` crate).
-
-# Version 0.0.13 (2015-03-26)
-
-* Updates to track rustc nightly.
-
-# Version 0.0.12 (2015-03-24)
-
-* Updates to track rustc stabilization.
-
-# Version 0.0.11 (2015-03-12)
-
-* Reexport `sqlite3_stmt` from `libsqlite3-sys` for easier `impl`-ing of `ToSql` and `FromSql`.
-* Updates to track latest rustc changes.
-* Update dependency versions.
-
-# Version 0.0.10 (2015-02-23)
-
-* BREAKING CHANGE: `open` now expects a `Path` rather than a `str`. There is a separate
-  `open_in_memory` constructor for opening in-memory databases.
-* Added the ability to load SQLite extensions. This is behind the `load_extension` Cargo feature,
-  because not all builds of sqlite3 include this ability. Notably the default libsqlite3 that
-	ships with OS X 10.10 does not support extensions.
-
-# Version 0.0.9 (2015-02-13)
-
-* Updates to track latest rustc changes.
-* Implement standard `Error` trait for `SqliteError`.
-
-# Version 0.0.8 (2015-02-04)
-
-* Updates to track latest rustc changes.
-
-# Version 0.0.7 (2015-01-20)
-
-* Use external bitflags from crates.io.
-
-# Version 0.0.6 (2015-01-10)
-
-* Updates to track latest rustc changes (1.0.0-alpha).
-* Add `query_row_safe`, a `SqliteResult`-returning variant of `query_row`.
-
-# Version 0.0.5 (2015-01-07)
-
-* Updates to track latest rustc changes (closure syntax).
-* Updates to track latest rust stdlib changes (`std::c_str` -> `std::ffi`).
-
-# Version 0.0.4 (2015-01-05)
-
-* Updates to track latest rustc changes.
-
-# Version 0.0.3 (2014-12-23)
-
-* Updates to track latest rustc changes.
-* Add call to `sqlite3_busy_timeout`.
-
-# Version 0.0.2 (2014-12-04)
-
-* Remove use of now-deprecated `std::vec::raw::from_buf`.
-* Update to latest version of `time` crate.
-
-# Version 0.0.1 (2014-11-21)
-
-* Initial release
diff --git a/METADATA b/METADATA
index d84e059..77fbcb9 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://static.crates.io/crates/rusqlite/rusqlite-0.25.3.crate"
+    value: "https://static.crates.io/crates/rusqlite/rusqlite-0.27.0.crate"
   }
-  version: "0.25.3"
+  version: "0.27.0"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2021
-    month: 5
-    day: 19
+    year: 2022
+    month: 3
+    day: 1
   }
 }
diff --git a/README.md b/README.md
index e7d831c..073c464 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,12 @@
 # Rusqlite
 
-[![Travis Build Status](https://api.travis-ci.org/rusqlite/rusqlite.svg?branch=master)](https://travis-ci.org/rusqlite/rusqlite)
-[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/rusqlite/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/rusqlite/rusqlite)
-[![Build Status](https://github.com/rusqlite/rusqlite/workflows/CI/badge.svg)](https://github.com/rusqlite/rusqlite/actions)
-[![dependency status](https://deps.rs/repo/github/rusqlite/rusqlite/status.svg)](https://deps.rs/repo/github/rusqlite/rusqlite)
 [![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite)
-[![Gitter](https://badges.gitter.im/rusqlite.svg)](https://gitter.im/rusqlite/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
-[![Docs](https://docs.rs/rusqlite/badge.svg)](https://docs.rs/rusqlite)
-[![codecov](https://codecov.io/gh/rusqlite/rusqlite/branch/master/graph/badge.svg)](https://codecov.io/gh/rusqlite/rusqlite)
+[![Documentation](https://docs.rs/rusqlite/badge.svg)](https://docs.rs/rusqlite)
+[![Build Status (GitHub)](https://github.com/rusqlite/rusqlite/workflows/CI/badge.svg)](https://github.com/rusqlite/rusqlite/actions)
+[![Build Status (AppVeyor)](https://ci.appveyor.com/api/projects/status/github/rusqlite/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/rusqlite/rusqlite)
+[![Code Coverage](https://codecov.io/gh/rusqlite/rusqlite/branch/master/graph/badge.svg)](https://codecov.io/gh/rusqlite/rusqlite)
+[![Dependency Status](https://deps.rs/repo/github/rusqlite/rusqlite/status.svg)](https://deps.rs/repo/github/rusqlite/rusqlite)
+[![Discord Chat](https://img.shields.io/discord/927966344266256434.svg?logo=discord)](https://discord.gg/nFYfGPB8g4)
 
 Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
 an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
@@ -99,7 +98,11 @@
   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 @@
 
 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 @@
 * `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 @@
 ### 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 @@
 
 ## 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 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 @@
         }
     }
 
-    /// `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 @@
         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 @@
     }
 }
 
-/// `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 @@
     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 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 @@
         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 @@
 
         Ok(Backup {
             phantom_from: PhantomData,
-            phantom_to: PhantomData,
+            to,
             b,
         })
     }
@@ -231,6 +231,7 @@
     /// 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 @@
             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 @@
         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 @@
 
 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 @@
 }
 
 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 @@
         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 @@
 
     /// 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 @@
 
     /// Return true if the BLOB is empty.
     #[inline]
+    #[must_use]
     pub fn is_empty(&self) -> bool {
         self.size() == 0
     }
@@ -318,8 +321,7 @@
         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 @@
     }
 }
 
-/// `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 @@
         //    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 @@
         // 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 @@
             // 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 @@
         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 @@
                 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 @@
         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 @@
     /// 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 @@
 // #[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 @@
 
     #[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 @@
     #[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 @@
 
 // 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 @@
         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 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 @@
             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 @@
                 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 @@
 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 @@
     /// 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<'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 @@
             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 @@
     /// 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 @@
 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 @@
             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 @@
     /// [`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 @@
     /// 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 @@
     /// 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 @@
     /// [`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 @@
         // 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 @@
     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 @@
         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 @@
 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 @@
             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 @@
     /// 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 @@
             ffi::sqlite3_set_auxdata(
                 self.ctx,
                 arg,
-                raw as *mut _,
+                raw.cast(),
                 Some(free_boxed_value::<AuxInner>),
-            )
+            );
         };
         Ok(orig)
     }
@@ -260,7 +255,7 @@
 
 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 @@
     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 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 @@
     ///
     /// 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 @@
             .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 @@
     ) -> Result<()>
     where
         A: RefUnwindSafe + UnwindSafe,
-        D: Aggregate<A, T>,
+        D: Aggregate<A, T> + 'static,
         T: ToSql,
     {
         self.db
@@ -419,12 +415,13 @@
             .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 @@
     ) -> Result<()>
     where
         A: RefUnwindSafe + UnwindSafe,
-        W: WindowAggregate<A, T>,
+        W: WindowAggregate<A, T> + 'static,
         T: ToSql,
     {
         self.db
@@ -443,7 +440,7 @@
             .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 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 @@
             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 @@
                 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 @@
     ) -> 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 @@
                 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 @@
     ) -> 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 @@
                 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 @@
     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 @@
     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 @@
     };
 
     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 @@
     };
 
     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 @@
         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::{Connection, InnerConnection};
 
-/// `feature = "hooks"` Action Codes
+/// Action Codes
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[repr(i32)]
 #[non_exhaustive]
@@ -37,50 +37,350 @@
     }
 }
 
+/// 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 @@
     {
         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 @@
         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 @@
                     ffi::sqlite3_commit_hook(
                         self.db(),
                         Some(call_boxed_closure::<F>),
-                        boxed_hook as *mut _,
+                        boxed_hook.cast(),
                     )
                 }
             }
@@ -154,18 +465,18 @@
         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 @@
                     ffi::sqlite3_rollback_hook(
                         self.db(),
                         Some(call_boxed_closure::<F>),
-                        boxed_hook as *mut _,
+                        boxed_hook.cast(),
                     )
                 }
             }
@@ -195,41 +506,29 @@
         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 @@
                     ffi::sqlite3_update_hook(
                         self.db(),
                         Some(call_boxed_closure::<F>),
-                        boxed_hook as *mut _,
+                        boxed_hook.cast(),
                     )
                 }
             }
@@ -268,7 +567,7 @@
             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 @@
             }
         }
 
-        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);
+        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 _,
+                );
             }
-            _ => {
-                unsafe { ffi::sqlite3_progress_handler(self.db(), num_ops, None, ptr::null_mut()) }
-                self.progress_handler = None;
-            }
+            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 @@
     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 @@
     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 @@
     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 @@
             .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 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 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 @@
             free_update_hook: None,
             #[cfg(feature = "hooks")]
             progress_handler: None,
+            #[cfg(feature = "hooks")]
+            authorizer: None,
             owned,
         }
     }
@@ -60,8 +65,6 @@
         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 @@
     }
 
     #[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 @@
 
     #[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 @@
         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 @@
     }
 
     #[inline]
-    pub fn changes(&mut self) -> usize {
+    pub fn changes(&self) -> usize {
         unsafe { ffi::sqlite3_changes(self.db()) as usize }
     }
 
@@ -298,6 +300,11 @@
         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 @@
     }
 }
 
-#[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 @@
             }
 
             unsafe {
-                if ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) != ffi::SQLITE_OK || ffi::sqlite3_initialize() != ffi::SQLITE_OK {
-                    panic!(
+                assert!(ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) == ffi::SQLITE_OK && ffi::sqlite3_initialize() == ffi::SQLITE_OK,
                         "Could not ensure safe initialization of SQLite.\n\
                          To fix this, either:\n\
                          * Upgrade SQLite to at least version 3.7.0\n\
                          * Ensure that SQLite has been initialized in Multi-thread or Serialized mode and call\n\
                            rusqlite::bypass_sqlite_initialization() prior to your first connection attempt."
                     );
-                }
             }
         });
         Ok(())
diff --git a/src/lib.rs b/src/lib.rs
index 926cad3..1fa7a33 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -48,6 +48,7 @@
 //! }
 //! ```
 #![warn(missing_docs)]
+#![cfg_attr(docsrs, feature(doc_cfg))]
 
 pub use libsqlite3_sys as ffi;
 
@@ -73,8 +74,6 @@
 pub use crate::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::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 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 @@
 /// }
 ///
 /// 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 @@
 ///     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_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 @@
 ))]
 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 @@
     ///
     /// # 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 @@
     /// ```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 @@
     /// ### 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 @@
             .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 @@
         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 @@
         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 @@
 
     #[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 @@
     /// connection.
     #[inline]
     fn changes(&self) -> usize {
-        self.db.borrow_mut().changes()
+        self.db.borrow().changes()
     }
 
     /// Test for auto-commit mode.
@@ -843,9 +917,17 @@
     /// 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 @@
         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 @@
     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 @@
 
 #[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 @@
         ensure_sync::<InterruptHandle>();
     }
 
-    pub fn checked_memory_handle() -> Connection {
+    fn checked_memory_handle() -> Connection {
         Connection::open_in_memory().unwrap()
     }
 
@@ -1134,7 +1203,7 @@
     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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
     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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
     }
 
     #[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 @@
 
     #[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 @@
         #[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 @@
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
         #[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 @@
 
         #[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 @@
 
         #[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 @@
 
         #[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 @@
 
         #[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 @@
 
         #[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 @@
 
     #[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 @@
         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 @@
 
     #[test]
     fn test_params() -> Result<()> {
-        let db = checked_memory_handle();
+        let db = Connection::open_in_memory()?;
         db.query_row(
             "SELECT
             ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
@@ -1900,7 +1972,7 @@
     #[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 @@
 
     #[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 @@
     }
 
     #[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 @@
         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 @@
 
 #[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 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 @@
     /// 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 @@
 ///
 /// - 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 @@
 /// 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 @@
 // 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 @@
 /// ## 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 @@
             if ch == quote {
                 self.buf.push(ch);
             }
-            self.buf.push(ch)
+            self.buf.push(ch);
         }
         self.buf.push(quote);
     }
@@ -198,7 +198,7 @@
         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 @@
     ///
     /// 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 @@
         // 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,14 @@
     ///
     /// 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<()> {
-        let mut sql = Sql::new();
-        sql.push_pragma(schema_name, pragma_name)?;
-        // The argument may be either in parentheses
-        // or it may be separated from the pragma name by an equal sign.
-        // The two syntaxes yield identical results.
-        sql.push_equal_sign();
-        sql.push_value(pragma_value)?;
-        self.execute_batch(&sql)
-    }
-
-    /// Set a new value to `pragma_name` and return the updated value.
-    ///
-    /// Only few pragmas automatically return the updated value.
-    pub fn pragma_update_and_check<F, T>(
-        &self,
-        schema_name: Option<DatabaseName<'_>>,
-        pragma_name: &str,
-        pragma_value: &dyn ToSql,
-        f: F,
-    ) -> Result<T>
+        pragma_value: V,
+    ) -> Result<()>
     where
-        F: FnOnce(&Row<'_>) -> Result<T>,
+        V: ToSql,
     {
         let mut sql = Sql::new();
         sql.push_pragma(schema_name, pragma_name)?;
@@ -278,7 +259,31 @@
         // 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, V>(
+        &self,
+        schema_name: Option<DatabaseName<'_>>,
+        pragma_name: &str,
+        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)?;
+        // The argument may be either in parentheses
+        // or it may be separated from the pragma name by an equal sign.
+        // The two syntaxes yield identical results.
+        sql.push_equal_sign();
+        sql.push_value(&pragma_value)?;
         self.query_row(&sql, [], f)
     }
 }
@@ -393,15 +398,26 @@
     #[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 @@
     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 @@
         RawStatement {
             ptr: stmt,
             tail,
-            cache: Default::default(),
+            cache: ParamIndexCache::default(),
             statement_cache_key: None,
         }
     }
@@ -101,25 +101,38 @@
         }
     }
 
-    #[cfg_attr(not(feature = "unlock_notify"), inline)]
+    #[inline]
+    #[cfg(not(feature = "unlock_notify"))]
     pub fn step(&self) -> c_int {
-        if cfg!(feature = "unlock_notify") {
-            let db = unsafe { ffi::sqlite3_db_handle(self.ptr) };
-            let mut rc;
-            loop {
-                rc = unsafe { ffi::sqlite3_step(self.ptr) };
-                if unsafe { !unlock_notify::is_locked(db, rc) } {
-                    break;
+        unsafe { ffi::sqlite3_step(self.ptr) }
+    }
+
+    #[cfg(feature = "unlock_notify")]
+    pub fn step(&self) -> c_int {
+        use crate::unlock_notify;
+        let mut db = core::ptr::null_mut::<ffi::sqlite3>();
+        loop {
+            unsafe {
+                let mut rc = ffi::sqlite3_step(self.ptr);
+                // Bail out early for success and errors unrelated to locking. We
+                // still need check `is_locked` after this, but checking now lets us
+                // avoid one or two (admittedly cheap) calls into SQLite that we
+                // don't need to make.
+                if (rc & 0xff) != ffi::SQLITE_LOCKED {
+                    break rc;
                 }
-                rc = unsafe { unlock_notify::wait_for_unlock_notify(db) };
+                if db.is_null() {
+                    db = ffi::sqlite3_db_handle(self.ptr);
+                }
+                if !unlock_notify::is_locked(db, rc) {
+                    break rc;
+                }
+                rc = unlock_notify::wait_for_unlock_notify(db);
                 if rc != ffi::SQLITE_OK {
-                    break;
+                    break rc;
                 }
                 self.reset();
             }
-            rc
-        } else {
-            unsafe { ffi::sqlite3_step(self.ptr) }
         }
     }
 
diff --git a/src/row.rs b/src/row.rs
index 137f13d..c766e50 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -78,6 +78,12 @@
     {
         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 @@
         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 @@
         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 @@
 
     #[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 @@
                     self.row = None;
                     Err(e)
                 }
-            },
-            None => {
-                self.row = None;
-                Ok(())
             }
+        } else {
+            self.row = None;
+            Ok(())
         }
     }
 
@@ -283,20 +288,11 @@
             ),
             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)
             }
-            #[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(),
-            ),
+            FromSqlError::InvalidBlobSize { .. } => {
+                Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
+            }
         })
     }
 
@@ -358,6 +354,12 @@
     }
 }
 
+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 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 @@
 
 // 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 @@
         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 @@
             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 @@
     #[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 @@
     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 @@
     #[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 @@
     }
 }
 
-/// `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 @@
     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 @@
             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 @@
     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 @@
     #[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 @@
     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 @@
     }
 }
 
-/// `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 @@
     #[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 @@
     }
 }
 
-/// `feature = "session"`
+/// Operation
 pub struct Operation<'item> {
     table_name: &'item str,
     number_of_columns: i32,
@@ -410,7 +405,7 @@
     }
 }
 
-/// `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 @@
     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 @@
     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 @@
     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 @@
     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 @@
         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 @@
         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 @@
     #[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 @@
     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 @@
     #[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 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 @@
 
         let filtered = filter.is_some();
         let tuple = &mut (filter, conflict);
-        check!(unsafe {
+        check(unsafe {
             if filtered {
                 ffi::sqlite3changeset_apply(
                     db,
@@ -617,11 +609,10 @@
                     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 @@
 
         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 @@
                     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 @@
     }
 }
 
-/// `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 @@
         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 @@
     ///     // 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 @@
     /// 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 @@
     /// # 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 @@
     ///
     /// 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 @@
     {
         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 @@
                     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 @@
     /// 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 @@
                         !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 @@
                         !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 @@
     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 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 @@
         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 @@
     }
 }
 
-/// `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 @@
 }
 
 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 @@
             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 @@
         }
     }
 
-    /// `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 @@
                 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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     ///
     /// 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 @@
     }
 }
 
-/// 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 @@
     }
 }
 
+/// 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 @@
     }
 }
 
+/// 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 @@
     }
 
     #[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 @@
     /// 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 @@
         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 @@
         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 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)
     }
 }
 
-#[cfg(feature = "i128_blob")]
-impl FromSql for i128 {
+impl<const N: usize> FromSql for [u8; N] {
     #[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 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> {
+        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 @@
 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 @@
 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 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 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 @@
 #[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 @@
     }
 
     #[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 @@
 
     #[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 @@
     /// 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 @@
 // `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!(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<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 @@
     }
 
     #[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 @@
 }
 
 #[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 @@
 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 @@
 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 @@
         }
     }
 
+    /// 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 @@
         }
     }
 
+    /// 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 @@
         }
     }
 
+    /// 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 @@
             _ => 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 @@
                     !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 @@
                         !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 @@
     }
 
     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 @@
     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 @@
 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 @@
     /// 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 @@
 
 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 @@
     }
 
     /// 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 @@
     /// 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 @@
                     // `>` 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 @@
                     // 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 @@
 ///
 /// 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 @@
 ///
 /// 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 @@
 
 // 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 @@
     }
 }
 
-/// `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 @@
 
     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 @@
                 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::{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 @@
                     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 @@
 // 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 @@
 // 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 @@
     }
 }
 
-/// `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 @@
     }
 }
 
-/// `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 @@
     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 @@
     }
 }
 
-/// `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 @@
     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 @@
             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 @@
 
     /// 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 @@
 
     /// 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 @@
 
     /// 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 @@
     // 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 @@
 
     #[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 @@
     }
 }
 
-/// `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 @@
 
     #[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 @@
     }
 }
 
-/// `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 @@
     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 @@
     // 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 @@
 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 @@
             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 @@
 
     /// Turns `Values` into an iterator.
     #[inline]
+    #[must_use]
     pub fn iter(&self) -> ValueIter<'_> {
         ValueIter {
             iter: self.args.iter(),
@@ -630,7 +685,7 @@
 }
 
 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 @@
                         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 @@
     }
 }
 
-/// `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 @@
         _ => 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 @@
 
 // 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 @@
     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 @@
                 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 @@
     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 @@
                 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 @@
 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 @@
     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 @@
     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 @@
 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 @@
 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 @@
 where
     C: VTabCursor,
 {
-    let cr = cursor as *mut C;
+    let cr = cursor.cast::<C>();
     (*cr).eof() as c_int
 }
 
@@ -981,7 +1039,7 @@
 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 @@
 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 @@
 #[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 @@
 }
 
 #[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::{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 @@
         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 @@
             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 @@
 
     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 {