Snap for 8730993 from 1331359ccffa598a950a32edf7e1272a09f8845a to mainline-tzdata3-release

Change-Id: If55c8b5dab223d0f0e10034fe2ac919ae11c6d86
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index de52e1b..deaa273 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,5 @@
 {
   "git": {
-    "sha1": "8141b5e085bd3a02951588413e5569f1ddf17a8c"
-  },
-  "path_in_vcs": ""
-}
\ No newline at end of file
+    "sha1": "cef6dbbb26211baebedbebe6e114f5bcf9be2431"
+  }
+}
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..c16503f
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+libsqlite3-sys/sqlite3/* linguist-vendored
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..38c28c9
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,153 @@
+name: CI
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+  schedule:
+    - cron: '00 01 * * *'
+env:
+  RUST_BACKTRACE: 1
+jobs:
+  test:
+    name: Test ${{ matrix.target }}
+
+    strategy:
+      fail-fast: false
+
+      matrix:
+        include:
+          - { target: x86_64-pc-windows-msvc, os: windows-latest }
+          - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }
+          - { target: x86_64-apple-darwin, os: macos-latest }
+          - { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
+
+    runs-on: ${{ matrix.os }}
+
+    steps:
+      - uses: actions/checkout@v2
+      # This has a matcher for test panics, so we use it even though elsewhere
+      # we use actions-rs/toolchain.
+      - uses: hecrj/setup-rust-action@v1
+        with:
+          rust-version: stable${{ matrix.host }}
+          targets: ${{ matrix.target }}
+
+      - run: cargo build --features bundled --workspace --all-targets --verbose
+      - run: cargo test --features bundled --workspace --all-targets --verbose
+      - run: cargo test --features bundled --workspace --doc --verbose
+
+      - name: Test Features
+        # TODO: clang is installed on these -- but `bindgen` can't find it...
+        if: matrix.os != 'windows-latest'
+        run: |
+          cargo test --features 'bundled-full session buildtime_bindgen time' --all-targets --workspace --verbose
+          cargo test --features 'bundled-full session buildtime_bindgen time' --doc --workspace --verbose
+
+      - name: Static build
+        # Do we expect this to work / should we test with gnu toolchain?
+        if: matrix.os == 'x86_64-pc-windows-msvc'
+        env:
+          RUSTFLAGS: -Ctarget-feature=+crt-static
+        run: cargo build --features bundled
+
+  winsqlite3:
+    name: Test with winsqlite3
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: hecrj/setup-rust-action@v1
+      # TODO: Should this test GNU toolchain? What about +crt-static?
+      # TODO: Is it worth testing other features?
+      - run: cargo build --features winsqlite3 --workspace --all-targets --verbose
+      - run: cargo test --features winsqlite3 --workspace --all-targets --verbose
+
+  sqlcipher:
+    name: Test with sqlcipher
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: hecrj/setup-rust-action@v1
+      - run: sudo apt-get install sqlcipher libsqlcipher-dev
+      - run: sqlcipher --version
+      # TODO: Is it worth testing other features?
+      - run: cargo build --features sqlcipher --workspace --all-targets --verbose
+      - run: cargo test --features sqlcipher --workspace --all-targets --verbose
+
+  sanitizer:
+    name: Address Sanitizer
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      # Need nightly rust.
+      - uses: hecrj/setup-rust-action@v1
+        with:
+          rust-version: nightly
+          components: rust-src
+      - name: Tests with asan
+        env:
+          RUSTFLAGS: -Zsanitizer=address
+          RUSTDOCFLAGS: -Zsanitizer=address
+          ASAN_OPTIONS: 'detect_stack_use_after_return=1'
+          # Work around https://github.com/rust-lang/rust/issues/59125 by
+          # disabling backtraces. In an ideal world we'd probably suppress the
+          # leak sanitization, but we don't care about backtraces here, so long
+          # as the other tests have them.
+          RUST_BACKTRACE: '0'
+        run: cargo -Z build-std test --features 'bundled-full session buildtime_bindgen time with-asan' --target x86_64-unknown-linux-gnu
+
+  # Ensure clippy doesn't complain.
+  clippy:
+    name: Clippy
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: hecrj/setup-rust-action@v1
+        with:
+          components: clippy
+      - run: cargo clippy --all-targets --workspace --features bundled -- -D warnings
+      # Clippy with all non-conflicting features
+      - run: cargo clippy --all-targets --workspace --features 'bundled-full session buildtime_bindgen time' -- -D warnings
+
+  # Ensure patch is formatted.
+  fmt:
+    name: Format
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: hecrj/setup-rust-action@v1
+        with:
+          components: rustfmt
+      - run: cargo fmt --all -- --check
+
+  # Detect cases where documentation links don't resolve and such.
+  doc:
+    name: Docs
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: hecrj/setup-rust-action@v1
+        with:
+          rust-version: nightly
+      # Need to use `cargo rustdoc` to actually get it to respect -D
+      # warnings... Note: this also requires nightly.
+      - run: cargo rustdoc --features 'bundled-full session buildtime_bindgen time' -- -D warnings
+
+  codecov:
+    name: Generate code coverage
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: hecrj/setup-rust-action@v1
+      - name: Run cargo-tarpaulin
+        uses: actions-rs/tarpaulin@v0.1
+        with:
+          # Intentionally omit time feature until we're on time 0.3, at which
+          # point it should be added to `bundled-full`.
+          args: '--features "bundled-full session buildtime_bindgen"'
+
+      - name: Upload to codecov.io
+        uses: codecov/codecov-action@v1
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e51abea
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,48 @@
+sudo: false
+
+language: rust
+
+rust:
+  - stable
+  - beta
+  - nightly
+
+matrix:
+  fast_finish: true
+  allow_failures:
+    - rust: nightly
+
+addons:
+  apt:
+    packages: # recommanded versions for rust-bindgen
+      - llvm-3.9-dev
+      - libclang-3.9-dev
+      - libsqlcipher-dev
+
+env: # specify the clang path for rust-bindgen
+  - LIBCLANG_PATH=/usr/lib/llvm-3.9/lib
+
+script:
+  - cargo build
+  - cargo build --features bundled
+  - cargo build --features sqlcipher
+  - cargo build --features "bundled sqlcipher"
+  - cargo test
+  - cargo test --features "backup blob extra_check"
+  - cargo test --features "collation functions"
+  - cargo test --features "hooks limits"
+  - cargo test --features load_extension
+  - cargo test --features trace
+  - cargo test --features chrono
+  - cargo test --features serde_json
+  - cargo test --features url
+  - cargo test --features bundled
+  - cargo test --features sqlcipher
+  - cargo test --features i128_blob
+  - cargo test --features uuid
+  - cargo test --features "bundled unlock_notify window"
+  - cargo test --features "array bundled csvtab series vtab"
+  - cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab"
+  - cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab buildtime_bindgen"
+  - cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension serde_json trace url uuid vtab bundled"
+  - cargo test --features "backup blob chrono collation csvtab functions hooks limits load_extension session serde_json trace url uuid vtab bundled buildtime_bindgen"
diff --git a/Android.bp b/Android.bp
index d5c31aa..db8ee02 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,5 +1,4 @@
-// This file is generated by cargo2android.py --config cargo2android.json.
-// Do not modify this file as changes will be overridden on upgrade.
+// This file is generated by cargo2android.py --device --run --dependencies --features=modern_sqlite.
 
 package {
     default_applicable_licenses: ["external_rust_crates_rusqlite_license"],
@@ -22,14 +21,9 @@
     name: "librusqlite",
     host_supported: true,
     crate_name: "rusqlite",
-    cargo_env_compat: true,
-    cargo_pkg_version: "0.27.0",
     srcs: ["src/lib.rs"],
     edition: "2018",
-    features: [
-        "modern_sqlite",
-        "trace",
-    ],
+    features: ["modern_sqlite"],
     rustlibs: [
         "libbitflags",
         "libfallible_iterator",
@@ -41,25 +35,14 @@
     ],
 }
 
-rust_library {
-    name: "librusqlite_noicu",
-    host_supported: true,
-    crate_name: "rusqlite",
-    cargo_env_compat: true,
-    srcs: ["src/lib.rs"],
-    edition: "2018",
-    features: [
-        "modern_sqlite",
-        "trace",
-    ],
-    rustlibs: [
-        "libbitflags",
-        "libfallible_iterator",
-        "libfallible_streaming_iterator",
-        "libhashlink",
-        "liblibsqlite3_sys_noicu",
-        "libmemchr",
-        "libsmallvec",
-    ],
-}
-
+// dependent_library ["feature_list"]
+//   ahash-0.4.6
+//   bitflags-1.2.1 "default"
+//   fallible-iterator-0.2.0 "default,std"
+//   fallible-streaming-iterator-0.1.9
+//   hashbrown-0.9.1 "ahash,default,inline-more"
+//   hashlink-0.6.0
+//   libsqlite3-sys-0.20.1 "bundled_bindings,default,min_sqlite_version_3_6_8,pkg-config,vcpkg"
+//   memchr-2.3.4 "default,std,use_std"
+//   pkg-config-0.3.19
+//   smallvec-1.5.1
diff --git a/Cargo.toml b/Cargo.toml
index f8b8dda..8efebab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,50 +3,34 @@
 # 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 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.
+# 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)
 
 [package]
 edition = "2018"
 name = "rusqlite"
-version = "0.27.0"
+version = "0.24.2"
 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
-no-default-features = true
 default-target = "x86_64-unknown-linux-gnu"
-rustdoc-args = [
-    "--cfg",
-    "docsrs",
-]
+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
 
 [package.metadata.playground]
-features = ["bundled-full"]
 all-features = false
+features = ["bundled-full"]
 
 [lib]
 name = "rusqlite"
@@ -68,15 +52,17 @@
 [[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"
@@ -89,14 +75,14 @@
 version = "0.1"
 
 [dependencies.hashlink]
-version = "0.7"
+version = "0.6"
 
 [dependencies.lazy_static]
 version = "1.4"
 optional = true
 
 [dependencies.libsqlite3-sys]
-version = "0.24.0"
+version = "0.20.1"
 
 [dependencies.memchr]
 version = "2.3"
@@ -106,15 +92,10 @@
 optional = true
 
 [dependencies.smallvec]
-version = "1.6.1"
+version = "1.0"
 
 [dependencies.time]
-version = "0.3.0"
-features = [
-    "formatting",
-    "macros",
-    "parsing",
-]
+version = "0.2"
 optional = true
 
 [dependencies.url]
@@ -124,7 +105,6 @@
 [dependencies.uuid]
 version = "0.8"
 optional = true
-
 [dev-dependencies.bencher]
 version = "0.1"
 
@@ -152,79 +132,30 @@
 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 = [
-    "modern-full",
-    "bundled",
-]
-bundled-sqlcipher = [
-    "libsqlite3-sys/bundled-sqlcipher",
-    "bundled",
-]
-bundled-sqlcipher-vendored-openssl = [
-    "libsqlite3-sys/bundled-sqlcipher-vendored-openssl",
-    "bundled-sqlcipher",
-]
+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-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 = []
-in_gecko = [
-    "modern_sqlite",
-    "libsqlite3-sys/in_gecko",
-]
+i128_blob = ["byteorder"]
+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"]
+vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"]
 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"
 
@@ -233,3 +164,6 @@
 
 [badges.maintenance]
 status = "actively-developed"
+
+[badges.travis-ci]
+repository = "rusqlite/rusqlite"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 6ab1a7e..857bb86 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
 [package]
 name = "rusqlite"
-version = "0.27.0"
+version = "0.24.2"
 authors = ["The rusqlite developers"]
 edition = "2018"
 description = "Ergonomic wrapper for SQLite"
@@ -11,16 +11,8 @@
 license = "MIT"
 categories = ["database"]
 
-exclude = [
-  "/.github/*",
-  "/.gitattributes",
-  "/appveyor.yml",
-  "/Changelog.md",
-  "/clippy.toml",
-  "/codecov.yml",
-]
-
 [badges]
+travis-ci = { repository = "rusqlite/rusqlite" }
 appveyor = { repository = "rusqlite/rusqlite" }
 codecov = { repository = "rusqlite/rusqlite" }
 maintenance = { status = "actively-developed" }
@@ -43,16 +35,14 @@
 # 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 = []
+i128_blob = ["byteorder"]
 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"]
+vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"]
 csvtab = ["csv", "vtab"]
 # pointer passing interfaces: 3.20.0
 array = ["vtab"]
@@ -73,15 +63,15 @@
 wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
 winsqlite3 = ["libsqlite3-sys/winsqlite3"]
 
-# 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 = [
+# Helper feature for enabling both `bundled` and most non-build-related optional
+# features or dependencies. This is useful for running tests / clippy / etc. New
+# features and optional dependencies that don't conflict with anything else
+# should be added here.
+bundled-full = [
     "array",
     "backup",
     "blob",
-    "modern_sqlite",
+    "bundled",
     "chrono",
     "collation",
     "column_decltype",
@@ -94,7 +84,9 @@
     "load_extension",
     "serde_json",
     "series",
-    "time",
+    # 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",
     "trace",
     "unlock_notify",
     "url",
@@ -103,22 +95,21 @@
     "window",
 ]
 
-bundled-full = ["modern-full", "bundled"]
-
 [dependencies]
-time = { version = "0.3.0", features = ["formatting", "macros", "parsing"], optional = true }
+time = { version = "0.2", optional = true }
 bitflags = "1.2"
-hashlink = "0.7"
-chrono = { version = "0.4", optional = true, default-features = false, features = ["clock"] }
+hashlink = "0.6"
+chrono = { version = "0.4", optional = true }
 serde_json = { version = "1.0", optional = true }
 csv = { version = "1.1", optional = true }
 url = { version = "2.1", optional = true }
 lazy_static = { version = "1.4", optional = true }
+byteorder = { version = "1.3", features = ["i128"], optional = true }
 fallible-iterator = "0.2"
 fallible-streaming-iterator = "0.1"
 memchr = "2.3"
 uuid = { version = "0.8", optional = true }
-smallvec = "1.6.1"
+smallvec = "1.0"
 
 [dev-dependencies]
 doc-comment = "0.3"
@@ -127,13 +118,13 @@
 regex = "1.3"
 uuid = { version = "0.8", features = ["v4"] }
 unicase = "2.6.0"
-# Use `bencher` over criterion because it builds much faster and we don't have
+# Use `bencher` over criterion becasue it builds much faster and we don't have
 # many benchmarks
 bencher = "0.1"
 
 [dependencies.libsqlite3-sys]
 path = "libsqlite3-sys"
-version = "0.24.0"
+version = "0.20.1"
 
 [[test]]
 name = "config_log"
@@ -154,11 +145,10 @@
 harness = false
 
 [package.metadata.docs.rs]
-features = ["modern-full"]
+features = [ "array", "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "time", "trace", "url", "vtab", "window", "modern_sqlite", "column_decltype" ]
 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
new file mode 100644
index 0000000..6ba11d7
--- /dev/null
+++ b/Changelog.md
@@ -0,0 +1,332 @@
+For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlite/releases) page.
+
+# Version 0.14.0 (2018-08-17)
+
+* BREAKING CHANGE: `ToSql` implementation for `time::Timespec` uses RFC 3339 (%Y-%m-%dT%H:%M:%S.%fZ).
+  Previous format was %Y-%m-%d %H:%M:%S:%f %Z.
+* BREAKING CHANGE: Remove potentially conflicting impl of ToSqlOutput (#313).
+* BREAKING CHANGE: Replace column index/count type (i32) with usize.
+* BREAKING CHANGE: Replace parameter index/count type (i32) with usize.
+* BREAKING CHANGE: Replace row changes/count type (i32) with usize.
+* BREAKING CHANGE: Scalar functions must be `Send`able and `'static`.
+* Bugfix: Commit failure unhandled, database left in unusable state (#366).
+* Bugfix: `free_boxed_hook` does not work for `fn`.
+* Update the bundled SQLite version to 3.24.0 (#326).
+* Add DropBehavior::Panic to enforce intentional commit or rollback.
+* Implement `sqlite3_update_hook` (#260, #328), `sqlite3_commit_hook` and `sqlite3_rollback_hook`.
+* Add support to unlock notification behind `unlock_notify` feature (#294, #331).
+* Make `Statement::column_index` case insensitive (#330).
+* Add comment to justify `&mut Connection` in `Transaction`.
+* Fix `tyvar_behind_raw_pointer` warnings.
+* Fix handful of clippy warnings.
+* Fix `Connection::open` documentation (#332)
+* Add binding to `sqlite3_get_autocommit` and `sqlite3_stmt_busy`.
+* Add binding to `sqlite3_busy_timeout` and `sqlite3_busy_handler`.
+* Add binding to `sqlite3_expanded_sql`.
+* Use `rerun-if-env-changed` in libsqlite3-sys (#329).
+* Return an `InvalidQuery` error when SQL is not read only.
+
+# Version 0.13.0 (2017-11-13)
+
+* Added ToSqlConversionFailure case to Error enum.
+* Now depends on chrono 0.4, bitflats 1.0, and (optionally) cc 1.0 / bindgen 0.31.
+* The ToSql/FromSql implementations for time::Timespec now include
+  and expect fractional seconds and timezone in the serialized string.
+* The RowIndex type used in Row::get is now publicly exported.
+* New `sqlcipher` feature allows linking against SQLCipher instead of SQLite.
+* Doc link in README now point to docs.rs.
+
+# Version 0.12.0 (2017-05-29)
+
+* Defines HAVE\_USLEEP when building with a bundled SQLite (#263).
+* Updates dependencies to their latest versions, particularly serde to 1.0.
+* Adds support for vcpkg on Windows.
+* Adds `ToSql` impls for `str` and `[u8]`.
+
+# Version 0.11.0 (2017-04-06)
+
+* Avoid publicly exporting SQLite constants multiple times from libsqlite3-sys.
+* Adds `FromSql` and `ToSql` impls for `isize`. Documents why `usize` and `u64` are not included.
+
+# Version 0.10.1 (2017-03-03)
+
+* Updates the `bundled` SQLite version to 3.17.0.
+* Changes the build process to no longer require `bindgen`. This should improve
+  build times and no longer require a new-ish Clang. See the README for more
+  details.
+
+# Version 0.10.0 (2017-02-28)
+
+* Re-export the `ErrorCode` enum from `libsqlite3-sys`.
+* Adds `version()` and `version_number()` functions for querying the version of SQLite in use.
+* Adds the `limits` feature, exposing `limit()` and `set_limit()` methods on `Connection`.
+* Updates to `libsqlite3-sys` 0.7.0, which runs rust-bindgen at build-time instead of assuming the
+  precense of all expected SQLite constants and functions.
+* Clarifies supported SQLite versions. Running with SQLite older than 3.6.8 now panics, and
+  some features will not compile unless a sufficiently-recent SQLite version is used. See
+  the README for requirements of particular features.
+* When running with SQLite 3.6.x, rusqlite attempts to perform SQLite initialization. If it fails,
+  rusqlite will panic since it cannot ensure the threading mode for SQLite. This check can by
+  skipped by calling the unsafe function `rusqlite::bypass_sqlite_initialization()`. This is
+  technically a breaking change but is unlikely to affect anyone in practice, since prior to this
+  version the check that rusqlite was using would cause a segfault if linked against a SQLite
+  older than 3.7.0.
+* rusqlite now performs a one-time check (prior to the first connection attempt) that the runtime
+  SQLite version is at least as new as the SQLite version found at buildtime. This check can by
+  skipped by calling the unsafe function `rusqlite::bypass_sqlite_version_check()`.
+* Removes the `libc` dependency in favor of using `std::os::raw`
+
+# Version 0.9.5 (2017-01-26)
+
+* Add impls of `Clone`, `Debug`, and `PartialEq` to `ToSqlOutput`.
+
+# Version 0.9.4 (2017-01-25)
+
+* Update dependencies.
+
+# Version 0.9.3 (2017-01-23)
+
+* Make `ToSqlOutput` itself implement `ToSql`.
+
+# Version 0.9.2 (2017-01-22)
+
+* Bugfix: The `FromSql` impl for `i32` now returns an error instead of
+  truncating if the underlying SQLite value is out of `i32`'s range.
+* Added `FromSql` and `ToSql` impls for `i8`, `i16`, `u8`, `u16`, and `u32`.
+  `i32` and `i64` already had impls. `u64` is omitted because their range
+  cannot be represented by `i64`, which is the type we use to communicate with
+  SQLite.
+
+# Version 0.9.1 (2017-01-20)
+
+* BREAKING CHANGE: `Connection::close()` now returns a `Result<(), (Connection, Error)>` instead
+  of a `Result<(), Error>` so callers get the still-open connection back on failure.
+
+# Version 0.8.0 (2016-12-31)
+
+* BREAKING CHANGE: The `FromSql` trait has been redesigned. It now requires a single, safe
+  method instead of the previous definition which required implementing one or two unsafe
+  methods.
+* BREAKING CHANGE: The `ToSql` trait has been redesigned. It can now be implemented without
+  `unsafe`, and implementors can choose to return either borrowed or owned results.
+* BREAKING CHANGE: The closure passed to `query_row`, `query_row_and_then`, `query_row_safe`,
+  and `query_row_named` now expects a `&Row` instead of a `Row`. The vast majority of calls
+  to these functions will probably not need to change; see
+  https://github.com/jgallagher/rusqlite/pull/184.
+* BREAKING CHANGE: A few cases of the `Error` enum have sprouted additional information
+  (e.g., `FromSqlConversionFailure` now also includes the column index and the type returned
+  by SQLite).
+* Added `#[deprecated(since = "...", note = "...")]` flags (new in Rust 1.9 for libraries) to
+  all deprecated APIs.
+* Added `query_row` convenience function to `Statement`.
+* Added `bundled` feature which will build SQLite from source instead of attempting to link
+  against a SQLite that already exists on the system.
+* Fixed a bug where using cached prepared statements resulted in attempting to close a connection
+  failing with `DatabaseBusy`; see https://github.com/jgallagher/rusqlite/issues/186.
+
+# Version 0.7.3 (2016-06-01)
+
+* Fixes an incorrect failure from the `insert()` convenience function when back-to-back inserts to
+  different tables both returned the same row ID
+  ([#171](https://github.com/jgallagher/rusqlite/issues/171)).
+
+# Version 0.7.2 (2016-05-19)
+
+* BREAKING CHANGE: `Rows` no longer implements `Iterator`. It still has a `next()` method, but
+  the lifetime of the returned `Row` is now tied to the lifetime of the vending `Rows` object.
+  This behavior is more correct. Previously there were runtime checks to prevent misuse, but
+  other changes in this release to reset statements as soon as possible introduced yet another
+  hazard related to the lack of these lifetime connections. We were already recommending the
+  use of `query_map` and `query_and_then` over raw `query`; both of theose still return handles
+  that implement `Iterator`.
+* BREAKING CHANGE: `Transaction::savepoint()` now returns a `Savepoint` instead of another
+  `Transaction`. Unlike `Transaction`, `Savepoint`s can be rolled back while keeping the current
+  savepoint active.
+* BREAKING CHANGE: Creating transactions from a `Connection` or savepoints from a `Transaction`
+  now take `&mut self` instead of `&self` to correctly represent that transactions within a
+  connection are inherently nested. While a transaction is alive, the parent connection or
+  transaction is unusable, so `Transaction` now implements `Deref<Target=Connection>`, giving
+  access to `Connection`'s methods via the `Transaction` itself.
+* BREAKING CHANGE: `Transaction::set_commit` and `Transaction::set_rollback` have been replaced
+  by `Transaction::set_drop_behavior`.
+* Adds `Connection::prepare_cached`. `Connection` now keeps an internal cache of any statements
+  prepared via this method. The size of this cache defaults to 16 (`prepare_cached` will always
+  work but may re-prepare statements if more are prepared than the cache holds), and can be
+  controlled via `Connection::set_prepared_statement_cache_capacity`.
+* Adds `query_map_named` and `query_and_then_named` to `Statement`.
+* Adds `insert` convenience method to `Statement` which returns the row ID of an inserted row.
+* Adds `exists` convenience method returning whether a query finds one or more rows.
+* Adds support for serializing types from the `serde_json` crate. Requires the `serde_json` feature.
+* Adds support for serializing types from the `chrono` crate. Requires the `chrono` feature.
+* Removes `load_extension` feature from `libsqlite3-sys`. `load_extension` is still available
+  on rusqlite itself.
+* Fixes crash on nightly Rust when using the `trace` feature.
+* Adds optional `clippy` feature and addresses issues it found.
+* Adds `column_count()` method to `Statement` and `Row`.
+* Adds `types::Value` for dynamic column types.
+* Adds support for user-defined aggregate functions (behind the existing `functions` Cargo feature).
+* Introduces a `RowIndex` trait allowing columns to be fetched via index (as before) or name (new).
+* Introduces `ZeroBlob` type under the `blob` module/feature exposing SQLite's zeroblob API.
+* Adds CI testing for Windows via AppVeyor.
+* Fixes a warning building libsqlite3-sys under Rust 1.6.
+* Adds an unsafe `handle()` method to `Connection`. Please file an issue if you actually use it.
+
+# Version 0.6.0 (2015-12-17)
+
+* BREAKING CHANGE: `SqliteError` is now an enum instead of a struct. Previously, we were (ab)using
+  the error code and message to send back both underlying SQLite errors and errors that occurred
+  at the Rust level. Now those have been separated out; SQLite errors are returned as 
+  `SqliteFailure` cases (which still include the error code but also include a Rust-friendlier
+  enum as well), and rusqlite-level errors are captured in other cases. Because of this change,
+  `SqliteError` no longer implements `PartialEq`.
+* BREAKING CHANGE: When opening a new detection, rusqlite now detects if SQLite was compiled or
+  configured for single-threaded use only; if it was, connection attempts will fail. If this
+  affects you, please open an issue.
+* BREAKING CHANGE: `SqliteTransactionDeferred`, `SqliteTransactionImmediate`, and
+  `SqliteTransactionExclusive` are no longer exported. Instead, use
+  `TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and
+  `TransactionBehavior::Exclusive`.
+* Removed `Sqlite` prefix on many types:
+    * `SqliteConnection` is now `Connection`
+    * `SqliteError` is now `Error`
+    * `SqliteResult` is now `Result`
+    * `SqliteStatement` is now `Statement`
+    * `SqliteRows` is now `Rows`
+    * `SqliteRow` is now `Row`
+    * `SqliteOpenFlags` is now `OpenFlags`
+    * `SqliteTransaction` is now `Transaction`.
+    * `SqliteTransactionBehavior` is now `TransactionBehavior`.
+    * `SqliteLoadExtensionGuard` is now `LoadExtensionGuard`.
+  The old, prefixed names are still exported but are deprecated.
+* Adds a variety of `..._named` methods for executing queries using named placeholder parameters.
+* Adds `backup` feature that exposes SQLite's online backup API.
+* Adds `blob` feature that exposes SQLite's Incremental I/O for BLOB API.
+* Adds `functions` feature that allows user-defined scalar functions to be added to
+  open `SqliteConnection`s.
+
+# Version 0.5.0 (2015-12-08)
+
+* Adds `trace` feature that allows the use of SQLite's logging, tracing, and profiling hooks.
+* Slight change to the closure types passed to `query_map` and `query_and_then`:
+    * Remove the `'static` requirement on the closure's output type.
+    * Give the closure a `&SqliteRow` instead of a `SqliteRow`.
+* When building, the environment variable `SQLITE3_LIB_DIR` now takes precedence over pkg-config.
+* If `pkg-config` is not available, we will try to find `libsqlite3` in `/usr/lib`.
+* Add more documentation for failure modes of functions that return `SqliteResult`s.
+* Updates `libc` dependency to 0.2, fixing builds on ARM for Rust 1.6 or newer.
+
+# Version 0.4.0 (2015-11-03)
+
+* Adds `Sized` bound to `FromSql` trait as required by RFC 1214.
+
+# Version 0.3.1 (2015-09-22)
+
+* Reset underlying SQLite statements as soon as possible after executing, as recommended by
+  http://www.sqlite.org/cvstrac/wiki?p=ScrollingCursor.
+
+# Version 0.3.0 (2015-09-21)
+
+* Removes `get_opt`. Use `get_checked` instead.
+* Add `query_row_and_then` and `query_and_then` convenience functions. These are analogous to
+  `query_row` and `query_map` but allow functions that can fail by returning `Result`s.
+* Relax uses of `P: AsRef<...>` from `&P` to `P`.
+* Add additional error check for calling `execute` when `query` was intended.
+* Improve debug formatting of `SqliteStatement` and `SqliteConnection`.
+* Changes documentation of `get_checked` to correctly indicate that it returns errors (not panics)
+  when given invalid types or column indices.
+
+# Version 0.2.0 (2015-07-26)
+
+* Add `column_names()` to `SqliteStatement`.
+* By default, include `SQLITE_OPEN_NO_MUTEX` and `SQLITE_OPEN_URI` flags when opening a
+  new conneciton.
+* Fix generated bindings (e.g., `sqlite3_exec` was wrong).
+* Use now-generated `sqlite3_destructor_type` to define `SQLITE_STATIC` and `SQLITE_TRANSIENT`.
+
+# Version 0.1.0 (2015-05-11)
+
+* [breaking-change] Modify `query_row` to return a `Result` instead of unwrapping.
+* Deprecate `query_row_safe` (use `query_row` instead).
+* Add `query_map`.
+* Add `get_checked`, which asks SQLite to do some basic type-checking of columns.
+
+# Version 0.0.17 (2015-04-03)
+
+* Publish version that builds on stable rust (beta). This version lives on the
+  `stable` branch. Development continues on `master` and still requires a nightly
+  version of Rust.
+
+# Version 0.0.16
+
+* Updates to track rustc nightly.
+
+# Version 0.0.15
+
+* Make SqliteConnection `Send`.
+
+# Version 0.0.14
+
+* Remove unneeded features (also involves switching to `libc` crate).
+
+# Version 0.0.13 (2015-03-26)
+
+* Updates to track rustc nightly.
+
+# Version 0.0.12 (2015-03-24)
+
+* Updates to track rustc stabilization.
+
+# Version 0.0.11 (2015-03-12)
+
+* Reexport `sqlite3_stmt` from `libsqlite3-sys` for easier `impl`-ing of `ToSql` and `FromSql`.
+* Updates to track latest rustc changes.
+* Update dependency versions.
+
+# Version 0.0.10 (2015-02-23)
+
+* BREAKING CHANGE: `open` now expects a `Path` rather than a `str`. There is a separate
+  `open_in_memory` constructor for opening in-memory databases.
+* Added the ability to load SQLite extensions. This is behind the `load_extension` Cargo feature,
+  because not all builds of sqlite3 include this ability. Notably the default libsqlite3 that
+	ships with OS X 10.10 does not support extensions.
+
+# Version 0.0.9 (2015-02-13)
+
+* Updates to track latest rustc changes.
+* Implement standard `Error` trait for `SqliteError`.
+
+# Version 0.0.8 (2015-02-04)
+
+* Updates to track latest rustc changes.
+
+# Version 0.0.7 (2015-01-20)
+
+* Use external bitflags from crates.io.
+
+# Version 0.0.6 (2015-01-10)
+
+* Updates to track latest rustc changes (1.0.0-alpha).
+* Add `query_row_safe`, a `SqliteResult`-returning variant of `query_row`.
+
+# Version 0.0.5 (2015-01-07)
+
+* Updates to track latest rustc changes (closure syntax).
+* Updates to track latest rust stdlib changes (`std::c_str` -> `std::ffi`).
+
+# Version 0.0.4 (2015-01-05)
+
+* Updates to track latest rustc changes.
+
+# Version 0.0.3 (2014-12-23)
+
+* Updates to track latest rustc changes.
+* Add call to `sqlite3_busy_timeout`.
+
+# Version 0.0.2 (2014-12-04)
+
+* Remove use of now-deprecated `std::vec::raw::from_buf`.
+* Update to latest version of `time` crate.
+
+# Version 0.0.1 (2014-11-21)
+
+* Initial release
diff --git a/LICENSE b/LICENSE
index 9e5b9f7..0245a83 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2021 The rusqlite developers
+Copyright (c) 2014-2020 The rusqlite developers
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/METADATA b/METADATA
index 77fbcb9..c2f666c 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://static.crates.io/crates/rusqlite/rusqlite-0.27.0.crate"
+    value: "https://static.crates.io/crates/rusqlite/rusqlite-0.24.2.crate"
   }
-  version: "0.27.0"
+  version: "0.24.2"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2022
-    month: 3
-    day: 1
+    year: 2020
+    month: 12
+    day: 5
   }
 }
diff --git a/README.md b/README.md
index 073c464..09f0c20 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,13 @@
 # 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)
-[![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)
+[![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)
 
 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).
@@ -30,7 +31,7 @@
                   name            TEXT NOT NULL,
                   data            BLOB
                   )",
-        [],
+        params![],
     )?;
     let me = Person {
         id: 0,
@@ -43,7 +44,7 @@
     )?;
 
     let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
-    let person_iter = stmt.query_map([], |row| {
+    let person_iter = stmt.query_map(params![], |row| {
         Ok(Person {
             id: row.get(0)?,
             name: row.get(1)?,
@@ -76,7 +77,6 @@
 * [`functions`](https://docs.rs/rusqlite/~0/rusqlite/functions/index.html)
   allows you to load Rust closures into SQLite connections for use in queries.
   Note: This feature requires SQLite 3.7.3 or later.
-* `window` for [window function](https://www.sqlite.org/windowfunctions.html) support (`fun(...) OVER ...`). (Implies `functions`.)
 * [`trace`](https://docs.rs/rusqlite/~0/rusqlite/trace/index.html)
   allows hooks into SQLite's tracing and profiling APIs. Note: This feature
   requires SQLite 3.6.23 or later.
@@ -97,24 +97,16 @@
 * `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
   and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the
   `Url` type from the [`url` crate](https://crates.io/crates/url).
-* `bundled` uses a bundled version of SQLite.  This is a good option for cases where linking to SQLite is complicated, such as Windows.
-* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature overrides `bundled`.
-* `bundled-sqlcipher` uses a bundled version of SQLCipher. This searches for and links against a system-installed crypto library to provide the crypto implementation.
-* `bundled-sqlcipher-vendored-openssl` allows using bundled-sqlcipher with a vendored version of OpenSSL (via the `openssl-sys` crate) as the crypto provider.
-  - As the name implies this depends on the `bundled-sqlcipher` feature, and automatically turns it on.
-  - If turned on, this uses the [`openssl-sys`](https://crates.io/crates/openssl-sys) crate, with the `vendored` feature enabled in order to build and bundle the OpenSSL crypto library.
+* `bundled` uses a bundled version of sqlite3.  This is a good option for cases where linking to sqlite3 is complicated, such as Windows.
+* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`.
 * `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.
-* `series` exposes [`generate_series(...)`](https://www.sqlite.org/series.html) Table-Valued Function. (Implies `vtab`.)
-* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust. (Implies `vtab`.)
-* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function. (Implies `vtab`.)
+* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
+* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function.
 * `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected.
 * `uuid` allows storing and retrieving `Uuid` values from the [`uuid`](https://docs.rs/uuid/) crate using blobs.
-* [`session`](https://sqlite.org/sessionintro.html), Session module extension. Requires `buildtime_bindgen` feature. (Implies `hooks`.)
-* `extra_check` fail when a query passed to execute is readonly or has a column count > 0.
-* `column_decltype` provides `columns()` method for Statements and Rows; omit if linking to a version of SQLite/SQLCipher compiled with `-DSQLITE_OMIT_DECLTYPE`.
-* `collation` exposes [`sqlite3_create_collation_v2`](https://sqlite.org/c3ref/create_collation.html).
+* [`session`](https://sqlite.org/sessionintro.html), Session module extension. Requires `buildtime_bindgen` feature.
 
 ## Notes on building rusqlite and libsqlite3-sys
 
@@ -124,29 +116,24 @@
 
 You can adjust this behavior in a number of ways:
 
-* 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
+* If you use the `bundled` feature, `libsqlite3-sys` will use the
+  [cc](https://crates.io/crates/cc) crate to compile SQLite from source and
   link against that. This source is embedded in the `libsqlite3-sys` crate and
-  is currently SQLite 3.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:
+  is currently SQLite 3.33.0 (as of `rusqlite` 0.24.1 / `libsqlite3-sys`
+  0.20.0).  This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
   ```toml
   [dependencies.rusqlite]
-  version = "0.27.0"
+  version = "0.24.2"
   features = ["bundled"]
   ```
-* When using any of the `bundled` features, the build script will honor `SQLITE_MAX_VARIABLE_NUMBER` and `SQLITE_MAX_EXPR_DEPTH` variables. It will also honor a `LIBSQLITE_FLAGS` variable, which can have a format like `"-USQLITE_ALPHA -DSQLITE_BETA SQLITE_GAMMA ..."`. That would disable the `SQLITE_ALPHA` flag, and set the `SQLITE_BETA` and `SQLITE_GAMMA` flags. (The initial `-D` can be omitted, as on the last one.)
-* When using `bundled-sqlcipher` (and not also using `bundled-sqlcipher-vendored-openssl`), `libsqlite3-sys` will need to
-  link against crypto libraries on the system. If the build script can find a `libcrypto` from OpenSSL or LibreSSL (it will consult `OPENSSL_LIB_DIR`/`OPENSSL_INCLUDE_DIR` and `OPENSSL_DIR` environment variables), it will use that. If building on and for Macs, and none of those variables are set, it will use the system's SecurityFramework instead.
-
-* When linking against a SQLite (or SQLCipher) library already on the system (so *not* using any of the `bundled` features), you can set the `SQLITE3_LIB_DIR` (or `SQLCIPHER_LIB_DIR`) environment variable to point to a directory containing the library. You can also set the `SQLITE3_INCLUDE_DIR` (or `SQLCIPHER_INCLUDE_DIR`) variable to point to the directory containing `sqlite3.h`.
+* You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite
+  library.
 * Installing the sqlite3 development packages will usually be all that is required, but
   the build helpers for [pkg-config](https://github.com/alexcrichton/pkg-config-rs)
   and [vcpkg](https://github.com/mcgoo/vcpkg-rs) have some additional configuration
   options. The default when using vcpkg is to dynamically link,
   which must be enabled by setting `VCPKGRS_DYNAMIC=1` environment variable before build.
   `vcpkg install sqlite3:x64-windows` will install the required library.
-* When linking against a SQLite (or SQLCipher) library already on the system, you can set the `SQLITE3_STATIC` (or `SQLCIPHER_STATIC`) environment variable to 1 to request that the library be statically instead of dynamically linked.
-
 
 ### Binding generation
 
@@ -173,8 +160,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 any of the `bundled` features, you will get pregenerated bindings for the
-bundled version of SQLite/SQLCipher. If you need other specific pregenerated binding
+If you use the `bundled` feature, you will get pregenerated bindings for the
+bundled version of SQLite. 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.
 
@@ -182,7 +169,7 @@
 included with the bundled build. You generally should have `buildtime_bindgen`
 enabled if you turn this on, as otherwise you'll need to keep the version of
 SQLite you link with in sync with what rusqlite would have bundled, (usually the
-most recent release of SQLite). Failing to do this will cause a runtime error.
+most recent release of sqlite). Failing to do this will cause a runtime error.
 
 ## Contributing
 
@@ -190,7 +177,7 @@
 incompatible ways. This is unfortunate, and makes testing changes hard.
 
 To help here: you generally should ensure that you run tests/lint for
-`--features bundled`, and `--features "bundled-full session buildtime_bindgen"`.
+`--features bundled`, and `--features bundled-full session buildtime_bindgen`.
 
 If running bindgen is problematic for you, `--features bundled-full` enables
 bundled and all features which don't require binding generation, and can be used
@@ -199,10 +186,10 @@
 ### Checklist
 
 - Run `cargo fmt` to ensure your Rust code is correctly formatted.
-- 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.
+- Ensure `cargo clippy --all-targets --workspace --features bundled` passes without warnings.
+- Ensure `cargo test --all-targets --workspace --features bundled-full session buildtime_bindgen` reports no failures.
+- Ensure `cargo test --all-targets --workspace --features bundled` reports no failures.
+- Ensure `cargo test --all-targets --workspace --features bundled-full session buildtime_bindgen` reports no failures.
 
 ## Author
 
@@ -211,18 +198,8 @@
 
 ## Community
 
-Feel free to join the [Rusqlite Discord Server](https://discord.gg/nFYfGPB8g4) to discuss or get help with `rusqlite` or `libsqlite3-sys`.
+Currently there's a gitter channel set up for rusqlite [here](https://gitter.im/rusqlite/community).
 
 ## License
 
-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.
+Rusqlite is available under the MIT license. See the LICENSE file for more info.
diff --git a/TEST_MAPPING b/TEST_MAPPING
deleted file mode 100644
index 91f37bf..0000000
--- a/TEST_MAPPING
+++ /dev/null
@@ -1,19 +0,0 @@
-// Generated by update_crate_tests.py for tests that depend on this crate.
-{
-  "presubmit": [
-    {
-      "name": "keystore2_test"
-    },
-    {
-      "name": "legacykeystore_test"
-    }
-  ],
-  "presubmit-rust": [
-    {
-      "name": "keystore2_test"
-    },
-    {
-      "name": "legacykeystore_test"
-    }
-  ]
-}
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..2d88284
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,42 @@
+environment:
+  matrix:
+  - TARGET: x86_64-pc-windows-gnu
+    MSYS2_BITS: 64
+#  - TARGET: x86_64-pc-windows-msvc
+#    VCPKG_DEFAULT_TRIPLET: x64-windows
+#    VCPKGRS_DYNAMIC: 1
+#  - TARGET: x86_64-pc-windows-msvc
+#    VCPKG_DEFAULT_TRIPLET: x64-windows-static
+#    RUSTFLAGS: -Ctarget-feature=+crt-static
+install:
+  - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
+  - rustup-init.exe -y --default-host %TARGET%
+  - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
+  - if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin
+  - rustc -V
+  - cargo -V
+  # download SQLite dll (useful only when the `bundled` feature is not set)
+  - appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-dll-win64-x64-3250200.zip -FileName sqlite-dll-win64-x64.zip
+  - if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-dll-win64-x64.zip -y > nul
+  # download SQLite headers (useful only when the `bundled` feature is not set)
+  - appveyor-retry appveyor DownloadFile https://sqlite.org/2018/sqlite-amalgamation-3250200.zip -FileName sqlite-amalgamation.zip
+  - if not defined VCPKG_DEFAULT_TRIPLET 7z e sqlite-amalgamation.zip -y > nul
+  # specify where the SQLite dll has been downloaded (useful only when the `bundled` feature is not set)
+  - if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_LIB_DIR=%APPVEYOR_BUILD_FOLDER%
+  # specify where the SQLite headers have been downloaded (useful only when the `bundled` feature is not set)
+  - if not defined VCPKG_DEFAULT_TRIPLET SET SQLITE3_INCLUDE_DIR=%APPVEYOR_BUILD_FOLDER%
+  # install sqlite3 package
+  - if defined VCPKG_DEFAULT_TRIPLET vcpkg install sqlite3
+
+build: false
+
+test_script:
+  - cargo test --lib --verbose
+  - cargo test --lib --verbose --features bundled
+  - cargo test --lib --features "backup blob chrono collation functions hooks limits load_extension serde_json trace"
+  - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
+  - cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
+  - cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
+
+cache:
+  - C:\Users\appveyor\.cargo
diff --git a/benches/exec.rs b/benches/exec.rs
index b95cb35..360a98b 100644
--- a/benches/exec.rs
+++ b/benches/exec.rs
@@ -1,10 +1,10 @@
 use bencher::{benchmark_group, benchmark_main, Bencher};
-use rusqlite::Connection;
+use rusqlite::{Connection, NO_PARAMS};
 
 fn bench_execute(b: &mut Bencher) {
     let db = Connection::open_in_memory().unwrap();
     let sql = "PRAGMA user_version=1";
-    b.iter(|| db.execute(sql, []).unwrap());
+    b.iter(|| db.execute(sql, NO_PARAMS).unwrap());
 }
 
 fn bench_execute_batch(b: &mut Bencher) {
diff --git a/cargo2android-extra-module.bp b/cargo2android-extra-module.bp
deleted file mode 100644
index 33222cb..0000000
--- a/cargo2android-extra-module.bp
+++ /dev/null
@@ -1,21 +0,0 @@
-rust_library {
-    name: "librusqlite_noicu",
-    host_supported: true,
-    crate_name: "rusqlite",
-    cargo_env_compat: true,
-    srcs: ["src/lib.rs"],
-    edition: "2018",
-    features: [
-        "modern_sqlite",
-        "trace",
-    ],
-    rustlibs: [
-        "libbitflags",
-        "libfallible_iterator",
-        "libfallible_streaming_iterator",
-        "libhashlink",
-        "liblibsqlite3_sys_noicu",
-        "libmemchr",
-        "libsmallvec",
-    ],
-}
diff --git a/cargo2android.json b/cargo2android.json
deleted file mode 100644
index b515003..0000000
--- a/cargo2android.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "add-toplevel-block": "cargo2android-extra-module.bp",
-  "device": true,
-  "features": "modern_sqlite,trace",
-  "run": true
-}
\ No newline at end of file
diff --git a/clippy.toml b/clippy.toml
new file mode 100644
index 0000000..82447d9
--- /dev/null
+++ b/clippy.toml
@@ -0,0 +1 @@
+doc-valid-idents = ["SQLite", "lang_transaction"]
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..7a4789e
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,11 @@
+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
new file mode 100755
index 0000000..14f358a
--- /dev/null
+++ b/publish-ghp-docs.sh
@@ -0,0 +1,14 @@
+#!/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 6da01fd..4654907 100644
--- a/src/backup.rs
+++ b/src/backup.rs
@@ -1,14 +1,13 @@
-//! Online SQLite backup API.
+//! `feature = "backup"` Online SQLite backup API.
 //!
-//! To create a [`Backup`], you must have two distinct [`Connection`]s - one
+//! To create a `Backup`, you must have two distinct `Connection`s - one
 //! for the source (which can be used while the backup is running) and one for
-//! the destination (which cannot).  A [`Backup`] handle exposes three methods:
-//! [`step`](Backup::step) will attempt to back up a specified number of pages,
-//! [`progress`](Backup::progress) gets the current progress of the backup as of
-//! the last call to [`step`](Backup::step), and
-//! [`run_to_completion`](Backup::run_to_completion) will attempt to back up the
-//! entire source database, allowing you to specify how many pages are backed up
-//! at a time and how long the thread should sleep between chunks of pages.
+//! the destination (which cannot).  A `Backup` handle exposes three methods:
+//! `step` will attempt to back up a specified number of pages, `progress` gets
+//! the current progress of the backup as of the last call to `step`, and
+//! `run_to_completion` will attempt to back up the entire source database,
+//! allowing you to specify how many pages are backed up at a time and how long
+//! the thread should sleep between chunks of pages.
 //!
 //! The following example is equivalent to "Example 2: Online Backup of a
 //! Running Database" from [SQLite's Online Backup API
@@ -40,11 +39,11 @@
 
 use crate::ffi;
 
-use crate::error::error_from_handle;
+use crate::error::{error_from_handle, error_from_sqlite_code};
 use crate::{Connection, DatabaseName, Result};
 
 impl Connection {
-    /// Back up the `name` database to the given
+    /// `feature = "backup"` Back up the `name` database to the given
     /// destination path.
     ///
     /// If `progress` is not `None`, it will be called periodically
@@ -84,7 +83,7 @@
         }
     }
 
-    /// Restore the given source path into the
+    /// `feature = "backup"` 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 +106,7 @@
         let restore = Backup::new_with_names(&src, DatabaseName::Main, self, name)?;
 
         let mut r = More;
-        let mut busy_count = 0_i32;
+        let mut busy_count = 0i32;
         'restore_loop: while r == More || r == Busy {
             r = restore.step(100)?;
             if let Some(ref f) = progress {
@@ -131,8 +130,7 @@
     }
 }
 
-/// Possible successful results of calling
-/// [`Backup::step`].
+/// `feature = "backup"` Possible successful results of calling `Backup::step`.
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 #[non_exhaustive]
 pub enum StepResult {
@@ -143,7 +141,7 @@
     /// backed up.
     More,
 
-    /// The step failed because appropriate locks could not be acquired. This is
+    /// The step failed because appropriate locks could not be aquired. This is
     /// not a fatal error - the step can be retried.
     Busy,
 
@@ -152,12 +150,11 @@
     Locked,
 }
 
-/// Struct specifying the progress of a backup. The
+/// `feature = "backup"` Struct specifying the progress of a backup. The
 /// percentage completion can be calculated as `(pagecount - remaining) /
-/// pagecount`. The progress of a backup is as of the last call to
-/// [`step`](Backup::step) - if the source database is modified after a call to
-/// [`step`](Backup::step), the progress value will become outdated and
-/// potentially incorrect.
+/// pagecount`. The progress of a backup is as of the last call to `step` - if
+/// the source database is modified after a call to `step`, the progress value
+/// will become outdated and potentially incorrect.
 #[derive(Copy, Clone, Debug)]
 pub struct Progress {
     /// Number of pages in the source database that still need to be backed up.
@@ -166,10 +163,10 @@
     pub pagecount: c_int,
 }
 
-/// A handle to an online backup.
+/// `feature = "backup"` A handle to an online backup.
 pub struct Backup<'a, 'b> {
     phantom_from: PhantomData<&'a Connection>,
-    to: &'b Connection,
+    phantom_to: PhantomData<&'b Connection>,
     b: *mut ffi::sqlite3_backup,
 }
 
@@ -183,7 +180,6 @@
     ///
     /// Will return `Err` if the underlying `sqlite3_backup_init` call returns
     /// `NULL`.
-    #[inline]
     pub fn new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
         Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
     }
@@ -203,8 +199,8 @@
         to: &'b mut Connection,
         to_name: DatabaseName<'_>,
     ) -> Result<Backup<'a, 'b>> {
-        let to_name = to_name.as_cstring()?;
-        let from_name = from_name.as_cstring()?;
+        let to_name = to_name.to_cstring()?;
+        let from_name = from_name.to_cstring()?;
 
         let to_db = to.db.borrow_mut().db;
 
@@ -223,15 +219,12 @@
 
         Ok(Backup {
             phantom_from: PhantomData,
-            to,
+            phantom_to: PhantomData,
             b,
         })
     }
 
-    /// Gets the progress of the backup as of the last call to
-    /// [`step`](Backup::step).
-    #[inline]
-    #[must_use]
+    /// Gets the progress of the backup as of the last call to `step`.
     pub fn progress(&self) -> Progress {
         unsafe {
             Progress {
@@ -245,8 +238,7 @@
     /// negative, will attempt to back up all remaining pages. This will hold a
     /// lock on the source database for the duration, so it is probably not
     /// what you want for databases that are currently active (see
-    /// [`run_to_completion`](Backup::run_to_completion) for a better
-    /// alternative).
+    /// `run_to_completion` for a better alternative).
     ///
     /// # Failure
     ///
@@ -254,7 +246,6 @@
     /// an error code other than `DONE`, `OK`, `BUSY`, or `LOCKED`. `BUSY` and
     /// `LOCKED` are transient errors and are therefore returned as possible
     /// `Ok` values.
-    #[inline]
     pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
         use self::StepResult::{Busy, Done, Locked, More};
 
@@ -264,16 +255,16 @@
             ffi::SQLITE_OK => Ok(More),
             ffi::SQLITE_BUSY => Ok(Busy),
             ffi::SQLITE_LOCKED => Ok(Locked),
-            _ => self.to.decode_result(rc).map(|_| More),
+            _ => Err(error_from_sqlite_code(rc, None)),
         }
     }
 
-    /// Attempts to run the entire backup. Will call
-    /// [`step(pages_per_step)`](Backup::step) as many times as necessary,
-    /// sleeping for `pause_between_pages` between each call to give the
-    /// source database time to process any pending queries. This is a
-    /// direct implementation of "Example 2: Online Backup of a Running
-    /// Database" from [SQLite's Online Backup API documentation](https://www.sqlite.org/backup.html).
+    /// Attempts to run the entire backup. Will call `step(pages_per_step)` as
+    /// many times as necessary, sleeping for `pause_between_pages` between
+    /// each call to give the source database time to process any pending
+    /// queries. This is a direct implementation of "Example 2: Online Backup
+    /// of a Running Database" from [SQLite's Online Backup API
+    /// documentation](https://www.sqlite.org/backup.html).
     ///
     /// If `progress` is not `None`, it will be called after each step with the
     /// current progress of the backup. Note that is possible the progress may
@@ -282,8 +273,7 @@
     ///
     /// # Failure
     ///
-    /// Will return `Err` if any of the calls to [`step`](Backup::step) return
-    /// `Err`.
+    /// Will return `Err` if any of the calls to `step` return `Err`.
     pub fn run_to_completion(
         &self,
         pages_per_step: c_int,
@@ -297,7 +287,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),
@@ -308,7 +298,6 @@
 }
 
 impl Drop for Backup<'_, '_> {
-    #[inline]
     fn drop(&mut self) {
         unsafe { ffi::sqlite3_backup_finish(self.b) };
     }
@@ -317,84 +306,96 @@
 #[cfg(test)]
 mod test {
     use super::Backup;
-    use crate::{Connection, DatabaseName, Result};
+    use crate::{Connection, DatabaseName, NO_PARAMS};
     use std::time::Duration;
 
     #[test]
-    fn test_backup() -> Result<()> {
-        let src = Connection::open_in_memory()?;
+    fn test_backup() {
+        let src = Connection::open_in_memory().unwrap();
         let sql = "BEGIN;
                    CREATE TABLE foo(x INTEGER);
                    INSERT INTO foo VALUES(42);
                    END;";
-        src.execute_batch(sql)?;
+        src.execute_batch(sql).unwrap();
 
-        let mut dst = Connection::open_in_memory()?;
+        let mut dst = Connection::open_in_memory().unwrap();
 
         {
-            let backup = Backup::new(&src, &mut dst)?;
-            backup.step(-1)?;
+            let backup = Backup::new(&src, &mut dst).unwrap();
+            backup.step(-1).unwrap();
         }
 
-        let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst
+            .query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(42, the_answer);
 
-        src.execute_batch("INSERT INTO foo VALUES(43)")?;
+        src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
 
         {
-            let backup = Backup::new(&src, &mut dst)?;
-            backup.run_to_completion(5, Duration::from_millis(250), None)?;
+            let backup = Backup::new(&src, &mut dst).unwrap();
+            backup
+                .run_to_completion(5, Duration::from_millis(250), None)
+                .unwrap();
         }
 
-        let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst
+            .query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(42 + 43, the_answer);
-        Ok(())
     }
 
     #[test]
-    fn test_backup_temp() -> Result<()> {
-        let src = Connection::open_in_memory()?;
+    fn test_backup_temp() {
+        let src = Connection::open_in_memory().unwrap();
         let sql = "BEGIN;
                    CREATE TEMPORARY TABLE foo(x INTEGER);
                    INSERT INTO foo VALUES(42);
                    END;";
-        src.execute_batch(sql)?;
+        src.execute_batch(sql).unwrap();
 
-        let mut dst = Connection::open_in_memory()?;
+        let mut dst = Connection::open_in_memory().unwrap();
 
         {
             let backup =
-                Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)?;
-            backup.step(-1)?;
+                Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
+                    .unwrap();
+            backup.step(-1).unwrap();
         }
 
-        let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst
+            .query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(42, the_answer);
 
-        src.execute_batch("INSERT INTO foo VALUES(43)")?;
+        src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
 
         {
             let backup =
-                Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)?;
-            backup.run_to_completion(5, Duration::from_millis(250), None)?;
+                Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
+                    .unwrap();
+            backup
+                .run_to_completion(5, Duration::from_millis(250), None)
+                .unwrap();
         }
 
-        let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst
+            .query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(42 + 43, the_answer);
-        Ok(())
     }
 
     #[test]
-    fn test_backup_attached() -> Result<()> {
-        let src = Connection::open_in_memory()?;
+    fn test_backup_attached() {
+        let src = Connection::open_in_memory().unwrap();
         let sql = "ATTACH DATABASE ':memory:' AS my_attached;
                    BEGIN;
                    CREATE TABLE my_attached.foo(x INTEGER);
                    INSERT INTO my_attached.foo VALUES(42);
                    END;";
-        src.execute_batch(sql)?;
+        src.execute_batch(sql).unwrap();
 
-        let mut dst = Connection::open_in_memory()?;
+        let mut dst = Connection::open_in_memory().unwrap();
 
         {
             let backup = Backup::new_with_names(
@@ -402,14 +403,17 @@
                 DatabaseName::Attached("my_attached"),
                 &mut dst,
                 DatabaseName::Main,
-            )?;
-            backup.step(-1)?;
+            )
+            .unwrap();
+            backup.step(-1).unwrap();
         }
 
-        let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst
+            .query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(42, the_answer);
 
-        src.execute_batch("INSERT INTO foo VALUES(43)")?;
+        src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
 
         {
             let backup = Backup::new_with_names(
@@ -417,12 +421,16 @@
                 DatabaseName::Attached("my_attached"),
                 &mut dst,
                 DatabaseName::Main,
-            )?;
-            backup.run_to_completion(5, Duration::from_millis(250), None)?;
+            )
+            .unwrap();
+            backup
+                .run_to_completion(5, Duration::from_millis(250), None)
+                .unwrap();
         }
 
-        let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst
+            .query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(42 + 43, the_answer);
-        Ok(())
     }
 }
diff --git a/src/blob/mod.rs b/src/blob/mod.rs
index 81c6098..7d7ec3d 100644
--- a/src/blob/mod.rs
+++ b/src/blob/mod.rs
@@ -1,4 +1,4 @@
-//! Incremental BLOB I/O.
+//! `feature = "blob"` Incremental BLOB I/O.
 //!
 //! Note that SQLite does not provide API-level access to change the size of a
 //! BLOB; that must be performed through SQL statements.
@@ -47,18 +47,18 @@
 //!    functions take a `&mut [MaybeUninit<u8>]` as the destination buffer,
 //!    where the "normal" functions take a `&mut [u8]`.
 //!
-//!    Using `MaybeUninit` here can be more efficient in some cases, but is
+//!    Using `MaybeUninit` here can be more efficient in some cases, but is
 //!    often inconvenient, so both are provided.
 //!
 //! 2. Exact/inexact refers to to whether or not the entire buffer must be
 //!    filled in order for the call to be considered a success.
 //!
 //!    The "exact" functions require the provided buffer be entirely filled, or
-//!    they return an error, whereas the "inexact" functions read as much out of
+//!    they return an error, wheras the "inexact" functions read as much out of
 //!    the blob as is available, and return how much they were able to read.
 //!
-//!    The inexact functions are preferable if you do not know the size of the
-//!    blob already, and the exact functions are preferable if you do.
+//!    The inexact functions are preferrable if you do not know the size of the
+//!    blob already, and the exact functions are preferrable if you do.
 //!
 //! ### Comparison to using the `std::io` traits:
 //!
@@ -101,7 +101,7 @@
 //!
 //! ```rust
 //! # use rusqlite::blob::ZeroBlob;
-//! # use rusqlite::{Connection, DatabaseName};
+//! # use rusqlite::{Connection, DatabaseName, NO_PARAMS};
 //! # use std::error::Error;
 //! # use std::io::{Read, Seek, SeekFrom, Write};
 //! # fn main() -> Result<(), Box<dyn Error>> {
@@ -111,7 +111,10 @@
 //! // Insert a BLOB into the `content` column of `test_table`. Note that the Blob
 //! // I/O API provides no way of inserting or resizing BLOBs in the DB -- this
 //! // must be done via SQL.
-//! db.execute("INSERT INTO test_table (content) VALUES (ZEROBLOB(10))", [])?;
+//! db.execute(
+//!     "INSERT INTO test_table (content) VALUES (ZEROBLOB(10))",
+//!     NO_PARAMS,
+//! )?;
 //!
 //! // Get the row id off the BLOB we just inserted.
 //! let rowid = db.last_insert_rowid();
@@ -133,10 +136,7 @@
 //!
 //! // Insert another BLOB, this time using a parameter passed in from
 //! // rust (potentially with a dynamic size).
-//! db.execute(
-//!     "INSERT INTO test_table (content) VALUES (?)",
-//!     [ZeroBlob(64)],
-//! )?;
+//! db.execute("INSERT INTO test_table (content) VALUES (?)", &[ZeroBlob(64)])?;
 //!
 //! // given a new row ID, we can reopen the blob on that row
 //! let rowid = db.last_insert_rowid();
@@ -151,7 +151,7 @@
 //!
 //! ```rust
 //! # use rusqlite::blob::ZeroBlob;
-//! # use rusqlite::{Connection, DatabaseName};
+//! # use rusqlite::{Connection, DatabaseName, NO_PARAMS};
 //! # use std::error::Error;
 //! # fn main() -> Result<(), Box<dyn Error>> {
 //! let db = Connection::open_in_memory()?;
@@ -159,7 +159,10 @@
 //! // Insert a blob into the `content` column of `test_table`. Note that the Blob
 //! // I/O API provides no way of inserting or resizing blobs in the DB -- this
 //! // must be done via SQL.
-//! db.execute("INSERT INTO test_table (content) VALUES (ZEROBLOB(10))", [])?;
+//! db.execute(
+//!     "INSERT INTO test_table (content) VALUES (ZEROBLOB(10))",
+//!     NO_PARAMS,
+//! )?;
 //! // Get the row id off the blob we just inserted.
 //! let rowid = db.last_insert_rowid();
 //! // Open the blob we just inserted for IO.
@@ -174,10 +177,7 @@
 //!
 //! // Insert another blob, this time using a parameter passed in from
 //! // rust (potentially with a dynamic size).
-//! db.execute(
-//!     "INSERT INTO test_table (content) VALUES (?)",
-//!     [ZeroBlob(64)],
-//! )?;
+//! db.execute("INSERT INTO test_table (content) VALUES (?)", &[ZeroBlob(64)])?;
 //!
 //! // given a new row ID, we can reopen the blob on that row
 //! let rowid = db.last_insert_rowid();
@@ -196,8 +196,8 @@
 
 mod pos_io;
 
-/// Handle to an open BLOB. See
-/// [`rusqlite::blob`](crate::blob) documentation for in-depth discussion.
+/// `feature = "blob"` Handle to an open BLOB. See [`rusqlite::blob`](crate::blob) documentation for
+/// in-depth discussion.
 pub struct Blob<'conn> {
     conn: &'conn Connection,
     blob: *mut ffi::sqlite3_blob,
@@ -206,7 +206,7 @@
 }
 
 impl Connection {
-    /// Open a handle to the BLOB located in `row_id`,
+    /// `feature = "blob"` Open a handle to the BLOB located in `row_id`,
     /// `column`, `table` in database `db`.
     ///
     /// # Failure
@@ -214,7 +214,6 @@
     /// Will return `Err` if `db`/`table`/`column` cannot be converted to a
     /// C-compatible string or if the underlying SQLite BLOB open call
     /// fails.
-    #[inline]
     pub fn blob_open<'a>(
         &'a self,
         db: DatabaseName<'_>,
@@ -223,9 +222,9 @@
         row_id: i64,
         read_only: bool,
     ) -> Result<Blob<'a>> {
-        let c = self.db.borrow_mut();
+        let mut c = self.db.borrow_mut();
         let mut blob = ptr::null_mut();
-        let db = db.as_cstring()?;
+        let db = db.to_cstring()?;
         let table = super::str_to_cstring(table)?;
         let column = super::str_to_cstring(column)?;
         let rc = unsafe {
@@ -253,7 +252,6 @@
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite BLOB reopen call fails.
-    #[inline]
     pub fn reopen(&mut self, row: i64) -> Result<()> {
         let rc = unsafe { ffi::sqlite3_blob_reopen(self.blob, row) };
         if rc != ffi::SQLITE_OK {
@@ -264,23 +262,17 @@
     }
 
     /// Return the size in bytes of the BLOB.
-    #[inline]
-    #[must_use]
     pub fn size(&self) -> i32 {
         unsafe { ffi::sqlite3_blob_bytes(self.blob) }
     }
 
     /// Return the current size in bytes of the BLOB.
-    #[inline]
-    #[must_use]
     pub fn len(&self) -> usize {
         use std::convert::TryInto;
         self.size().try_into().unwrap()
     }
 
     /// Return true if the BLOB is empty.
-    #[inline]
-    #[must_use]
     pub fn is_empty(&self) -> bool {
         self.size() == 0
     }
@@ -294,12 +286,10 @@
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite close call fails.
-    #[inline]
     pub fn close(mut self) -> Result<()> {
         self.close_()
     }
 
-    #[inline]
     fn close_(&mut self) -> Result<()> {
         let rc = unsafe { ffi::sqlite3_blob_close(self.blob) };
         self.blob = ptr::null_mut();
@@ -314,14 +304,14 @@
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite read call fails.
-    #[inline]
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         let max_allowed_len = (self.size() - self.pos) as usize;
         let n = min(buf.len(), max_allowed_len) as i32;
         if n <= 0 {
             return Ok(0);
         }
-        let rc = unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_mut_ptr().cast(), n, self.pos) };
+        let rc =
+            unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_mut_ptr() as *mut _, n, self.pos) };
         self.conn
             .decode_result(rc)
             .map(|_| {
@@ -344,7 +334,6 @@
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite write call fails.
-    #[inline]
     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
         let max_allowed_len = (self.size() - self.pos) as usize;
         let n = min(buf.len(), max_allowed_len) as i32;
@@ -361,7 +350,6 @@
             .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
     }
 
-    #[inline]
     fn flush(&mut self) -> io::Result<()> {
         Ok(())
     }
@@ -369,7 +357,6 @@
 
 impl io::Seek for Blob<'_> {
     /// Seek to an offset, in bytes, in BLOB.
-    #[inline]
     fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
         let pos = match pos {
             io::SeekFrom::Start(offset) => offset as i64,
@@ -396,13 +383,12 @@
 
 #[allow(unused_must_use)]
 impl Drop for Blob<'_> {
-    #[inline]
     fn drop(&mut self) {
         self.close_();
     }
 }
 
-/// BLOB of length N that is filled with zeroes.
+/// `feature = "blob"` 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.
@@ -412,7 +398,6 @@
 pub struct ZeroBlob(pub i32);
 
 impl ToSql for ZeroBlob {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         let ZeroBlob(length) = *self;
         Ok(ToSqlOutput::ZeroBlob(length))
@@ -436,18 +421,22 @@
     }
 
     #[test]
-    fn test_blob() -> Result<()> {
-        let (db, rowid) = db_with_test_blob()?;
+    fn test_blob() {
+        let (db, rowid) = db_with_test_blob().unwrap();
 
-        let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
+        let mut blob = db
+            .blob_open(DatabaseName::Main, "test", "content", rowid, false)
+            .unwrap();
         assert_eq!(4, blob.write(b"Clob").unwrap());
         assert_eq!(6, blob.write(b"567890xxxxxx").unwrap()); // cannot write past 10
         assert_eq!(0, blob.write(b"5678").unwrap()); // still cannot write past 10
 
-        blob.reopen(rowid)?;
-        blob.close()?;
+        blob.reopen(rowid).unwrap();
+        blob.close().unwrap();
 
-        blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, true)?;
+        blob = db
+            .blob_open(DatabaseName::Main, "test", "content", rowid, true)
+            .unwrap();
         let mut bytes = [0u8; 5];
         assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
         assert_eq!(&bytes, b"Clob5");
@@ -468,7 +457,7 @@
         assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
         assert_eq!(&bytes, b"56789");
 
-        blob.reopen(rowid)?;
+        blob.reopen(rowid).unwrap();
         assert_eq!(5, blob.read(&mut bytes[..]).unwrap());
         assert_eq!(&bytes, b"Clob5");
 
@@ -479,19 +468,20 @@
 
         // write_all should detect when we return Ok(0) because there is no space left,
         // and return a write error
-        blob.reopen(rowid)?;
+        blob.reopen(rowid).unwrap();
         assert!(blob.write_all(b"0123456789x").is_err());
-        Ok(())
     }
 
     #[test]
-    fn test_blob_in_bufreader() -> Result<()> {
-        let (db, rowid) = db_with_test_blob()?;
+    fn test_blob_in_bufreader() {
+        let (db, rowid) = db_with_test_blob().unwrap();
 
-        let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
+        let mut blob = db
+            .blob_open(DatabaseName::Main, "test", "content", rowid, false)
+            .unwrap();
         assert_eq!(8, blob.write(b"one\ntwo\n").unwrap());
 
-        blob.reopen(rowid)?;
+        blob.reopen(rowid).unwrap();
         let mut reader = BufReader::new(blob);
 
         let mut line = String::new();
@@ -505,15 +495,16 @@
         line.truncate(0);
         assert_eq!(2, reader.read_line(&mut line).unwrap());
         assert_eq!("\0\0", line);
-        Ok(())
     }
 
     #[test]
-    fn test_blob_in_bufwriter() -> Result<()> {
-        let (db, rowid) = db_with_test_blob()?;
+    fn test_blob_in_bufwriter() {
+        let (db, rowid) = db_with_test_blob().unwrap();
 
         {
-            let blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
+            let blob = db
+                .blob_open(DatabaseName::Main, "test", "content", rowid, false)
+                .unwrap();
             let mut writer = BufWriter::new(blob);
 
             // trying to write too much and then flush should fail
@@ -524,14 +515,18 @@
 
         {
             // ... but it should've written the first 10 bytes
-            let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
+            let mut blob = db
+                .blob_open(DatabaseName::Main, "test", "content", rowid, false)
+                .unwrap();
             let mut bytes = [0u8; 10];
             assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
             assert_eq!(b"0123456701", &bytes);
         }
 
         {
-            let blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
+            let blob = db
+                .blob_open(DatabaseName::Main, "test", "content", rowid, false)
+                .unwrap();
             let mut writer = BufWriter::new(blob);
 
             // trying to write_all too much should fail
@@ -541,11 +536,12 @@
 
         {
             // ... but it should've written the first 10 bytes
-            let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)?;
+            let mut blob = db
+                .blob_open(DatabaseName::Main, "test", "content", rowid, false)
+                .unwrap();
             let mut bytes = [0u8; 10];
             assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
             assert_eq!(b"aaaaaaaaaa", &bytes);
-            Ok(())
         }
     }
 }
diff --git a/src/blob/pos_io.rs b/src/blob/pos_io.rs
index ecc7d65..9f1f994 100644
--- a/src/blob/pos_io.rs
+++ b/src/blob/pos_io.rs
@@ -44,14 +44,15 @@
         //    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());
-        self.conn.decode_result(unsafe {
-            ffi::sqlite3_blob_write(
+        unsafe {
+            check!(ffi::sqlite3_blob_write(
                 self.blob,
-                buf.as_ptr().cast(),
+                buf.as_ptr() as *const _,
                 buf.len() as i32,
                 write_start as i32,
-            )
-        })
+            ));
+        }
+        Ok(())
     }
 
     /// An alias for `write_at` provided for compatibility with the conceptually
@@ -84,7 +85,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().cast(), buf.len()) };
+            unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut _, buf.len()) };
         self.raw_read_at(as_uninit, read_start).map(|s| s.len())
     }
 
@@ -119,7 +120,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().cast::<u8>(), 0) };
+            let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, 0) };
             return Ok(empty);
         }
 
@@ -150,14 +151,14 @@
         debug_assert!(i32::try_from(read_len).is_ok());
 
         unsafe {
-            self.conn.decode_result(ffi::sqlite3_blob_read(
+            check!(ffi::sqlite3_blob_read(
                 self.blob,
-                buf.as_mut_ptr().cast(),
+                buf.as_mut_ptr() as *mut _,
                 read_len as i32,
                 read_start as i32,
-            ))?;
+            ));
 
-            Ok(from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), read_len))
+            Ok(from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, read_len))
         }
     }
 
@@ -193,18 +194,25 @@
 
 #[cfg(test)]
 mod test {
-    use crate::{Connection, DatabaseName, Result};
+    use crate::{Connection, DatabaseName, NO_PARAMS};
     // to ensure we don't modify seek pos
     use std::io::Seek as _;
 
     #[test]
-    fn test_pos_io() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE test_table(content BLOB);")?;
-        db.execute("INSERT INTO test_table(content) VALUES (ZEROBLOB(10))", [])?;
+    fn test_pos_io() {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE test_table(content BLOB);")
+            .unwrap();
+        db.execute(
+            "INSERT INTO test_table(content) VALUES (ZEROBLOB(10))",
+            NO_PARAMS,
+        )
+        .unwrap();
 
         let rowid = db.last_insert_rowid();
-        let mut blob = db.blob_open(DatabaseName::Main, "test_table", "content", rowid, false)?;
+        let mut blob = db
+            .blob_open(DatabaseName::Main, "test_table", "content", rowid, false)
+            .unwrap();
         // modify the seek pos to ensure we aren't using it or modifying it.
         blob.seek(std::io::SeekFrom::Start(1)).unwrap();
 
@@ -269,6 +277,5 @@
 
         let end_pos = blob.seek(std::io::SeekFrom::Current(0)).unwrap();
         assert_eq!(end_pos, 1);
-        Ok(())
     }
 }
diff --git a/src/busy.rs b/src/busy.rs
index b394d01..b87504a 100644
--- a/src/busy.rs
+++ b/src/busy.rs
@@ -19,11 +19,8 @@
     ///
     /// There can only be a single busy handler for a particular database
     /// connection at any given moment. If another busy handler was defined
-    /// (using [`busy_handler`](Connection::busy_handler)) prior to calling this
-    /// routine, that other busy handler is cleared.
-    ///
-    /// Newly created connections currently have a default busy timeout of
-    /// 5000ms, but this may be subject to change.
+    /// (using `busy_handler`) prior to calling this routine, that other
+    /// busy handler is cleared.
     pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
         let ms: i32 = timeout
             .as_secs()
@@ -36,8 +33,8 @@
 
     /// Register a callback to handle `SQLITE_BUSY` errors.
     ///
-    /// If the busy callback is `None`, then `SQLITE_BUSY` is returned
-    /// immediately upon encountering the lock. The argument to the busy
+    /// If the busy callback is `None`, then `SQLITE_BUSY is returned
+    /// immediately upon encountering the lock.` The argument to the busy
     /// handler callback is the number of times that the
     /// busy handler has been invoked previously for the
     /// same locking event. If the busy callback returns `false`, then no
@@ -48,13 +45,9 @@
     ///
     /// There can only be a single busy handler defined for each database
     /// connection. Setting a new busy handler clears any previously set
-    /// handler. Note that calling [`busy_timeout()`](Connection::busy_timeout)
-    /// or evaluating `PRAGMA busy_timeout=N` will change the busy handler
-    /// and thus clear any previously set busy handler.
-    ///
-    /// Newly created connections default to a
-    /// [`busy_timeout()`](Connection::busy_timeout) handler with a timeout
-    /// of 5000ms, although this is subject to change.
+    /// handler. Note that calling `busy_timeout()` or evaluating `PRAGMA
+    /// busy_timeout=N` will change the busy handler and thus
+    /// clear any previously set busy handler.
     pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> {
         unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int {
             let handler_fn: fn(i32) -> bool = mem::transmute(p_arg);
@@ -64,7 +57,7 @@
                 0
             }
         }
-        let c = self.db.borrow_mut();
+        let mut 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)
@@ -76,7 +69,6 @@
 }
 
 impl InnerConnection {
-    #[inline]
     fn busy_timeout(&mut self, timeout: c_int) -> Result<()> {
         let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) };
         self.decode_result(r)
@@ -90,24 +82,26 @@
     use std::thread;
     use std::time::Duration;
 
-    use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior};
+    use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS};
 
     #[test]
-    fn test_default_busy() -> Result<()> {
+    fn test_default_busy() {
         let temp_dir = tempfile::tempdir().unwrap();
         let path = temp_dir.path().join("test.db3");
 
-        let mut db1 = Connection::open(&path)?;
-        let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?;
-        let db2 = Connection::open(&path)?;
-        let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!());
+        let mut db1 = Connection::open(&path).unwrap();
+        let tx1 = db1
+            .transaction_with_behavior(TransactionBehavior::Exclusive)
+            .unwrap();
+        let db2 = Connection::open(&path).unwrap();
+        let r: Result<()> = db2.query_row("PRAGMA schema_version", NO_PARAMS, |_| unreachable!());
         match r.unwrap_err() {
             Error::SqliteFailure(err, _) => {
                 assert_eq!(err.code, ErrorCode::DatabaseBusy);
             }
             err => panic!("Unexpected error {}", err),
         }
-        tx1.rollback()
+        tx1.rollback().unwrap();
     }
 
     #[test]
@@ -132,7 +126,9 @@
 
         assert_eq!(tx.recv().unwrap(), 1);
         let _ = db2
-            .query_row("PRAGMA schema_version", [], |row| row.get::<_, i32>(0))
+            .query_row("PRAGMA schema_version", NO_PARAMS, |row| {
+                row.get::<_, i32>(0)
+            })
             .expect("unexpected error");
 
         child.join().unwrap();
@@ -141,7 +137,9 @@
     #[test]
     #[ignore] // FIXME: unstable
     fn test_busy_handler() {
-        static CALLED: AtomicBool = AtomicBool::new(false);
+        lazy_static::lazy_static! {
+            static ref CALLED: AtomicBool = AtomicBool::new(false);
+        }
         fn busy_handler(_: i32) -> bool {
             CALLED.store(true, Ordering::Relaxed);
             thread::sleep(Duration::from_millis(100));
@@ -167,9 +165,11 @@
 
         assert_eq!(tx.recv().unwrap(), 1);
         let _ = db2
-            .query_row("PRAGMA schema_version", [], |row| row.get::<_, i32>(0))
+            .query_row("PRAGMA schema_version", NO_PARAMS, |row| {
+                row.get::<_, i32>(0)
+            })
             .expect("unexpected error");
-        assert!(CALLED.load(Ordering::Relaxed));
+        assert_eq!(CALLED.load(Ordering::Relaxed), true);
 
         child.join().unwrap();
     }
diff --git a/src/cache.rs b/src/cache.rs
index c80a708..7dc9d23 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -11,20 +11,20 @@
     /// Prepare a SQL statement for execution, returning a previously prepared
     /// (but not currently in-use) statement if one is available. The
     /// returned statement will be cached for reuse by future calls to
-    /// [`prepare_cached`](Connection::prepare_cached) once it is dropped.
+    /// `prepare_cached` once it is dropped.
     ///
     /// ```rust,no_run
     /// # use rusqlite::{Connection, Result};
     /// fn insert_new_people(conn: &Connection) -> Result<()> {
     ///     {
     ///         let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
-    ///         stmt.execute(["Joe Smith"])?;
+    ///         stmt.execute(&["Joe Smith"])?;
     ///     }
     ///     {
     ///         // This will return the same underlying SQLite statement handle without
     ///         // having to prepare it again.
     ///         let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
-    ///         stmt.execute(["Bob Jones"])?;
+    ///         stmt.execute(&["Bob Jones"])?;
     ///     }
     ///     Ok(())
     /// }
@@ -34,7 +34,6 @@
     ///
     /// Will return `Err` if `sql` cannot be converted to a C-compatible string
     /// or if the underlying SQLite call fails.
-    #[inline]
     pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>> {
         self.cache.get(self, sql)
     }
@@ -44,15 +43,13 @@
     /// number of cached statements. If you need more, or know that you
     /// will not use cached statements, you
     /// can set the capacity manually using this method.
-    #[inline]
     pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) {
-        self.cache.set_capacity(capacity);
+        self.cache.set_capacity(capacity)
     }
 
     /// Remove/finalize all prepared statements currently in the cache.
-    #[inline]
     pub fn flush_prepared_statement_cache(&self) {
-        self.cache.flush();
+        self.cache.flush()
     }
 }
 
@@ -60,14 +57,10 @@
 // #[derive(Debug)] // FIXME: https://github.com/kyren/hashlink/pull/4
 pub struct StatementCache(RefCell<LruCache<Arc<str>, RawStatement>>);
 
-#[allow(clippy::non_send_fields_in_send_ty)]
-unsafe impl Send for StatementCache {}
-
 /// Cacheable statement.
 ///
 /// Statement will return automatically to the cache by default.
-/// If you want the statement to be discarded, call
-/// [`discard()`](CachedStatement::discard) on it.
+/// If you want the statement to be discarded, call `discard()` on it.
 pub struct CachedStatement<'conn> {
     stmt: Option<Statement<'conn>>,
     cache: &'conn StatementCache,
@@ -76,14 +69,12 @@
 impl<'conn> Deref for CachedStatement<'conn> {
     type Target = Statement<'conn>;
 
-    #[inline]
     fn deref(&self) -> &Statement<'conn> {
         self.stmt.as_ref().unwrap()
     }
 }
 
 impl<'conn> DerefMut for CachedStatement<'conn> {
-    #[inline]
     fn deref_mut(&mut self) -> &mut Statement<'conn> {
         self.stmt.as_mut().unwrap()
     }
@@ -91,7 +82,6 @@
 
 impl Drop for CachedStatement<'_> {
     #[allow(unused_must_use)]
-    #[inline]
     fn drop(&mut self) {
         if let Some(stmt) = self.stmt.take() {
             self.cache.cache_stmt(unsafe { stmt.into_raw() });
@@ -100,7 +90,6 @@
 }
 
 impl CachedStatement<'_> {
-    #[inline]
     fn new<'conn>(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
         CachedStatement {
             stmt: Some(stmt),
@@ -109,8 +98,7 @@
     }
 
     /// Discard the statement, preventing it from being returned to its
-    /// [`Connection`]'s collection of cached statements.
-    #[inline]
+    /// `Connection`'s collection of cached statements.
     pub fn discard(mut self) {
         self.stmt = None;
     }
@@ -118,14 +106,12 @@
 
 impl StatementCache {
     /// Create a statement cache.
-    #[inline]
     pub fn with_capacity(capacity: usize) -> StatementCache {
         StatementCache(RefCell::new(LruCache::new(capacity)))
     }
 
-    #[inline]
     fn set_capacity(&self, capacity: usize) {
-        self.0.borrow_mut().set_capacity(capacity);
+        self.0.borrow_mut().set_capacity(capacity)
     }
 
     // Search the cache for a prepared-statement object that implements `sql`.
@@ -169,17 +155,16 @@
         }
     }
 
-    #[inline]
     fn flush(&self) {
         let mut cache = self.0.borrow_mut();
-        cache.clear();
+        cache.clear()
     }
 }
 
 #[cfg(test)]
 mod test {
     use super::StatementCache;
-    use crate::{Connection, Result};
+    use crate::{Connection, NO_PARAMS};
     use fallible_iterator::FallibleIterator;
 
     impl StatementCache {
@@ -197,8 +182,8 @@
     }
 
     #[test]
-    fn test_cache() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_cache() {
+        let db = Connection::open_in_memory().unwrap();
         let cache = &db.cache;
         let initial_capacity = cache.capacity();
         assert_eq!(0, cache.len());
@@ -206,35 +191,43 @@
 
         let sql = "PRAGMA schema_version";
         {
-            let mut stmt = db.prepare_cached(sql)?;
+            let mut stmt = db.prepare_cached(sql).unwrap();
             assert_eq!(0, cache.len());
-            assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
+            assert_eq!(
+                0,
+                stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
+            );
         }
         assert_eq!(1, cache.len());
 
         {
-            let mut stmt = db.prepare_cached(sql)?;
+            let mut stmt = db.prepare_cached(sql).unwrap();
             assert_eq!(0, cache.len());
-            assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
+            assert_eq!(
+                0,
+                stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
+            );
         }
         assert_eq!(1, cache.len());
 
         cache.clear();
         assert_eq!(0, cache.len());
         assert_eq!(initial_capacity, cache.capacity());
-        Ok(())
     }
 
     #[test]
-    fn test_set_capacity() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_set_capacity() {
+        let db = Connection::open_in_memory().unwrap();
         let cache = &db.cache;
 
         let sql = "PRAGMA schema_version";
         {
-            let mut stmt = db.prepare_cached(sql)?;
+            let mut stmt = db.prepare_cached(sql).unwrap();
             assert_eq!(0, cache.len());
-            assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
+            assert_eq!(
+                0,
+                stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
+            );
         }
         assert_eq!(1, cache.len());
 
@@ -242,53 +235,64 @@
         assert_eq!(0, cache.len());
 
         {
-            let mut stmt = db.prepare_cached(sql)?;
+            let mut stmt = db.prepare_cached(sql).unwrap();
             assert_eq!(0, cache.len());
-            assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
+            assert_eq!(
+                0,
+                stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
+            );
         }
         assert_eq!(0, cache.len());
 
         db.set_prepared_statement_cache_capacity(8);
         {
-            let mut stmt = db.prepare_cached(sql)?;
+            let mut stmt = db.prepare_cached(sql).unwrap();
             assert_eq!(0, cache.len());
-            assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
+            assert_eq!(
+                0,
+                stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
+            );
         }
         assert_eq!(1, cache.len());
-        Ok(())
     }
 
     #[test]
-    fn test_discard() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_discard() {
+        let db = Connection::open_in_memory().unwrap();
         let cache = &db.cache;
 
         let sql = "PRAGMA schema_version";
         {
-            let mut stmt = db.prepare_cached(sql)?;
+            let mut stmt = db.prepare_cached(sql).unwrap();
             assert_eq!(0, cache.len());
-            assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
+            assert_eq!(
+                0,
+                stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
+            );
             stmt.discard();
         }
         assert_eq!(0, cache.len());
-        Ok(())
     }
 
     #[test]
-    fn test_ddl() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_ddl() {
+        let db = Connection::open_in_memory().unwrap();
         db.execute_batch(
             r#"
             CREATE TABLE foo (x INT);
             INSERT INTO foo VALUES (1);
         "#,
-        )?;
+        )
+        .unwrap();
 
         let sql = "SELECT * FROM foo";
 
         {
-            let mut stmt = db.prepare_cached(sql)?;
-            assert_eq!(Ok(Some(1i32)), stmt.query([])?.map(|r| r.get(0)).next());
+            let mut stmt = db.prepare_cached(sql).unwrap();
+            assert_eq!(
+                Ok(Some(1i32)),
+                stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).next()
+            );
         }
 
         db.execute_batch(
@@ -296,55 +300,61 @@
             ALTER TABLE foo ADD COLUMN y INT;
             UPDATE foo SET y = 2;
         "#,
-        )?;
+        )
+        .unwrap();
 
         {
-            let mut stmt = db.prepare_cached(sql)?;
+            let mut stmt = db.prepare_cached(sql).unwrap();
             assert_eq!(
                 Ok(Some((1i32, 2i32))),
-                stmt.query([])?.map(|r| Ok((r.get(0)?, r.get(1)?))).next()
+                stmt.query(NO_PARAMS)
+                    .unwrap()
+                    .map(|r| Ok((r.get(0)?, r.get(1)?)))
+                    .next()
             );
         }
-        Ok(())
     }
 
     #[test]
-    fn test_connection_close() -> Result<()> {
-        let conn = Connection::open_in_memory()?;
-        conn.prepare_cached("SELECT * FROM sqlite_master;")?;
+    fn test_connection_close() {
+        let conn = Connection::open_in_memory().unwrap();
+        conn.prepare_cached("SELECT * FROM sqlite_master;").unwrap();
 
         conn.close().expect("connection not closed");
-        Ok(())
     }
 
     #[test]
-    fn test_cache_key() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_cache_key() {
+        let db = Connection::open_in_memory().unwrap();
         let cache = &db.cache;
         assert_eq!(0, cache.len());
 
         //let sql = " PRAGMA schema_version; -- comment";
         let sql = "PRAGMA schema_version; ";
         {
-            let mut stmt = db.prepare_cached(sql)?;
+            let mut stmt = db.prepare_cached(sql).unwrap();
             assert_eq!(0, cache.len());
-            assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
+            assert_eq!(
+                0,
+                stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
+            );
         }
         assert_eq!(1, cache.len());
 
         {
-            let mut stmt = db.prepare_cached(sql)?;
+            let mut stmt = db.prepare_cached(sql).unwrap();
             assert_eq!(0, cache.len());
-            assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
+            assert_eq!(
+                0,
+                stmt.query_row(NO_PARAMS, |r| r.get::<_, i64>(0)).unwrap()
+            );
         }
         assert_eq!(1, cache.len());
-        Ok(())
     }
 
     #[test]
-    fn test_empty_stmt() -> Result<()> {
-        let conn = Connection::open_in_memory()?;
-        conn.prepare_cached("")?;
-        Ok(())
+    fn test_empty_stmt() {
+        let conn = Connection::open_in_memory().unwrap();
+        conn.prepare_cached("").unwrap();
     }
 }
diff --git a/src/collation.rs b/src/collation.rs
index c1fe3f7..1168b75 100644
--- a/src/collation.rs
+++ b/src/collation.rs
@@ -1,4 +1,4 @@
-//! Add, remove, or modify a collation
+//! `feature = "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,12 +10,11 @@
 
 // FIXME copy/paste from function.rs
 unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
-    drop(Box::from_raw(p.cast::<T>()));
+    drop(Box::from_raw(p as *mut T));
 }
 
 impl Connection {
-    /// Add or modify a collation.
-    #[inline]
+    /// `feature = "collation"` Add or modify a collation.
     pub fn create_collation<C>(&self, collation_name: &str, x_compare: C) -> Result<()>
     where
         C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
@@ -25,8 +24,7 @@
             .create_collation(collation_name, x_compare)
     }
 
-    /// Collation needed callback
-    #[inline]
+    /// `feature = "collation"` Collation needed callback
     pub fn collation_needed(
         &self,
         x_coll_needed: fn(&Connection, &str) -> Result<()>,
@@ -34,8 +32,7 @@
         self.db.borrow_mut().collation_needed(x_coll_needed)
     }
 
-    /// Remove collation.
-    #[inline]
+    /// `feature = "collation"` Remove collation.
     pub fn remove_collation(&self, collation_name: &str) -> Result<()> {
         self.db.borrow_mut().remove_collation(collation_name)
     }
@@ -57,14 +54,14 @@
             C: Fn(&str, &str) -> Ordering,
         {
             let r = catch_unwind(|| {
-                let boxed_f: *mut C = arg1.cast::<C>();
+                let boxed_f: *mut C = arg1 as *mut C;
                 assert!(!boxed_f.is_null(), "Internal error - null function pointer");
                 let s1 = {
-                    let c_slice = slice::from_raw_parts(arg3.cast::<u8>(), arg2 as usize);
+                    let c_slice = slice::from_raw_parts(arg3 as *const u8, arg2 as usize);
                     String::from_utf8_lossy(c_slice)
                 };
                 let s2 = {
-                    let c_slice = slice::from_raw_parts(arg5.cast::<u8>(), arg4 as usize);
+                    let c_slice = slice::from_raw_parts(arg5 as *const u8, arg4 as usize);
                     String::from_utf8_lossy(c_slice)
                 };
                 (*boxed_f)(s1.as_ref(), s2.as_ref())
@@ -91,18 +88,12 @@
                 self.db(),
                 c_name.as_ptr(),
                 flags,
-                boxed_f.cast::<c_void>(),
+                boxed_f as *mut c_void,
                 Some(call_boxed_closure::<C>),
                 Some(free_boxed_value::<C>),
             )
         };
-        let res = self.decode_result(r);
-        // The xDestroy callback is not called if the sqlite3_create_collation_v2()
-        // function fails.
-        if res.is_err() {
-            drop(unsafe { Box::from_raw(boxed_f) });
-        }
-        res
+        self.decode_result(r)
     }
 
     fn collation_needed(
@@ -110,7 +101,6 @@
         x_coll_needed: fn(&Connection, &str) -> Result<()>,
     ) -> Result<()> {
         use std::mem;
-        #[allow(clippy::needless_return)]
         unsafe extern "C" fn collation_needed_callback(
             arg1: *mut c_void,
             arg2: *mut ffi::sqlite3,
@@ -130,7 +120,7 @@
                 let conn = Connection::from_handle(arg2).unwrap();
                 let collation_name = {
                     let c_slice = CStr::from_ptr(arg3).to_bytes();
-                    str::from_utf8(c_slice).expect("illegal collation sequence name")
+                    str::from_utf8(c_slice).expect("illegal coallation sequence name")
                 };
                 callback(&conn, collation_name)
             });
@@ -149,7 +139,6 @@
         self.decode_result(r)
     }
 
-    #[inline]
     fn remove_collation(&mut self, collation_name: &str) -> Result<()> {
         let c_name = str_to_cstring(collation_name)?;
         let r = unsafe {
@@ -168,7 +157,7 @@
 
 #[cfg(test)]
 mod test {
-    use crate::{Connection, Result};
+    use crate::{Connection, Result, NO_PARAMS};
     use fallible_streaming_iterator::FallibleStreamingIterator;
     use std::cmp::Ordering;
     use unicase::UniCase;
@@ -178,24 +167,26 @@
     }
 
     #[test]
-    fn test_unicase() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_unicase() {
+        let db = Connection::open_in_memory().unwrap();
 
-        db.create_collation("unicase", unicase_compare)?;
+        db.create_collation("unicase", unicase_compare).unwrap();
 
-        collate(db)
+        collate(db);
     }
 
-    fn collate(db: Connection) -> Result<()> {
+    fn collate(db: Connection) {
         db.execute_batch(
             "CREATE TABLE foo (bar);
              INSERT INTO foo (bar) VALUES ('Maße');
              INSERT INTO foo (bar) VALUES ('MASSE');",
-        )?;
-        let mut stmt = db.prepare("SELECT DISTINCT bar COLLATE unicase FROM foo ORDER BY 1")?;
-        let rows = stmt.query([])?;
-        assert_eq!(rows.count()?, 1);
-        Ok(())
+        )
+        .unwrap();
+        let mut stmt = db
+            .prepare("SELECT DISTINCT bar COLLATE unicase FROM foo ORDER BY 1")
+            .unwrap();
+        let rows = stmt.query(NO_PARAMS).unwrap();
+        assert_eq!(rows.count().unwrap(), 1);
     }
 
     fn collation_needed(db: &Connection, collation_name: &str) -> Result<()> {
@@ -207,9 +198,9 @@
     }
 
     #[test]
-    fn test_collation_needed() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.collation_needed(collation_needed)?;
-        collate(db)
+    fn test_collation_needed() {
+        let db = Connection::open_in_memory().unwrap();
+        db.collation_needed(collation_needed).unwrap();
+        collate(db);
     }
 }
diff --git a/src/column.rs b/src/column.rs
index aa1f5f7..4f6daac 100644
--- a/src/column.rs
+++ b/src/column.rs
@@ -1,6 +1,6 @@
 use std::str;
 
-use crate::{Error, Result, Statement};
+use crate::{Error, Result, Row, Rows, Statement};
 
 /// Information about a column of a SQLite query.
 #[derive(Debug)]
@@ -11,15 +11,11 @@
 
 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
     }
@@ -27,10 +23,6 @@
 
 impl Statement<'_> {
     /// Get all the column names in the result set of the prepared statement.
-    ///
-    /// If associated DB schema can be altered concurrently, you should make
-    /// sure that current statement has already been stepped once before
-    /// calling this method.
     pub fn column_names(&self) -> Vec<&str> {
         let n = self.column_count();
         let mut cols = Vec::with_capacity(n as usize);
@@ -43,35 +35,10 @@
 
     /// Return the number of columns in the result set returned by the prepared
     /// statement.
-    ///
-    /// If associated DB schema can be altered concurrently, you should make
-    /// sure that current statement has already been stepped once before
-    /// calling this method.
-    #[inline]
     pub fn column_count(&self) -> usize {
         self.stmt.column_count()
     }
 
-    /// Check that column name reference lifetime is limited:
-    /// https://www.sqlite.org/c3ref/column_name.html
-    /// > The returned string pointer is valid...
-    ///
-    /// `column_name` reference can become invalid if `stmt` is reprepared
-    /// (because of schema change) when `query_row` is called. So we assert
-    /// that a compilation error happens if this reference is kept alive:
-    /// ```compile_fail
-    /// use rusqlite::{Connection, Result};
-    /// fn main() -> Result<()> {
-    ///     let db = Connection::open_in_memory()?;
-    ///     let mut stmt = db.prepare("SELECT 1 as x")?;
-    ///     let column_name = stmt.column_name(0)?;
-    ///     let x = stmt.query_row([], |r| r.get::<_, i64>(0))?; // E0502
-    ///     assert_eq!(1, x);
-    ///     assert_eq!("x", column_name);
-    ///     Ok(())
-    /// }
-    /// ```
-    #[inline]
     pub(super) fn column_name_unwrap(&self, col: usize) -> &str {
         // Just panic if the bounds are wrong for now, we never call this
         // without checking first.
@@ -81,17 +48,12 @@
     /// Returns the name assigned to a particular column in the result set
     /// returned by the prepared statement.
     ///
-    /// If associated DB schema can be altered concurrently, you should make
-    /// sure that current statement has already been stepped once before
-    /// calling this method.
-    ///
     /// ## Failure
     ///
     /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
     /// column range for this row.
     ///
     /// Panics when column name is not valid UTF-8.
-    #[inline]
     pub fn column_name(&self, col: usize) -> Result<&str> {
         self.stmt
             .column_name(col)
@@ -106,15 +68,10 @@
     /// If there is no AS clause then the name of the column is unspecified and
     /// may change from one release of SQLite to the next.
     ///
-    /// If associated DB schema can be altered concurrently, you should make
-    /// sure that current statement has already been stepped once before
-    /// calling this method.
-    ///
     /// # Failure
     ///
     /// Will return an `Error::InvalidColumnName` when there is no column with
     /// the specified `name`.
-    #[inline]
     pub fn column_index(&self, name: &str) -> Result<usize> {
         let bytes = name.as_bytes();
         let n = self.column_count();
@@ -129,12 +86,7 @@
     }
 
     /// Returns a slice describing the columns of the result of the query.
-    ///
-    /// If associated DB schema can be altered concurrently, you should make
-    /// sure that current statement has already been stepped once before
-    /// calling this method.
     #[cfg(feature = "column_decltype")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
     pub fn columns(&self) -> Vec<Column> {
         let n = self.column_count();
         let mut cols = Vec::with_capacity(n as usize);
@@ -150,51 +102,100 @@
     }
 }
 
+impl<'stmt> Rows<'stmt> {
+    /// Get all the column names.
+    pub fn column_names(&self) -> Option<Vec<&str>> {
+        self.stmt.map(Statement::column_names)
+    }
+
+    /// Return the number of columns.
+    pub fn column_count(&self) -> Option<usize> {
+        self.stmt.map(Statement::column_count)
+    }
+
+    /// Return the name of the column.
+    pub fn column_name(&self, col: usize) -> Option<Result<&str>> {
+        self.stmt.map(|stmt| stmt.column_name(col))
+    }
+
+    /// Return the index of the column.
+    pub fn column_index(&self, name: &str) -> Option<Result<usize>> {
+        self.stmt.map(|stmt| stmt.column_index(name))
+    }
+
+    /// Returns a slice describing the columns of the Rows.
+    #[cfg(feature = "column_decltype")]
+    pub fn columns(&self) -> Option<Vec<Column>> {
+        self.stmt.map(Statement::columns)
+    }
+}
+
+impl<'stmt> Row<'stmt> {
+    /// Get all the column names of the Row.
+    pub fn column_names(&self) -> Vec<&str> {
+        self.stmt.column_names()
+    }
+
+    /// Return the number of columns in the current row.
+    pub fn column_count(&self) -> usize {
+        self.stmt.column_count()
+    }
+
+    /// Return the name of the column.
+    pub fn column_name(&self, col: usize) -> Result<&str> {
+        self.stmt.column_name(col)
+    }
+
+    /// Return the index of the column.
+    pub fn column_index(&self, name: &str) -> Result<usize> {
+        self.stmt.column_index(name)
+    }
+
+    /// Returns a slice describing the columns of the Row.
+    #[cfg(feature = "column_decltype")]
+    pub fn columns(&self) -> Vec<Column> {
+        self.stmt.columns()
+    }
+}
+
 #[cfg(test)]
 mod test {
-    use crate::{Connection, Result};
+    use crate::Connection;
 
     #[test]
     #[cfg(feature = "column_decltype")]
-    fn test_columns() -> Result<()> {
+    fn test_columns() {
         use super::Column;
 
-        let db = Connection::open_in_memory()?;
-        let query = db.prepare("SELECT * FROM sqlite_master")?;
+        let db = Connection::open_in_memory().unwrap();
+        let query = db.prepare("SELECT * FROM sqlite_master").unwrap();
         let columns = query.columns();
         let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
         assert_eq!(
             column_names.as_slice(),
             &["type", "name", "tbl_name", "rootpage", "sql"]
         );
-        let column_types: Vec<Option<String>> = columns
-            .iter()
-            .map(|col| col.decl_type().map(str::to_lowercase))
-            .collect();
+        let column_types: Vec<Option<&str>> = columns.iter().map(Column::decl_type).collect();
         assert_eq!(
             &column_types[..3],
-            &[
-                Some("text".to_owned()),
-                Some("text".to_owned()),
-                Some("text".to_owned()),
-            ]
+            &[Some("text"), Some("text"), Some("text"),]
         );
-        Ok(())
     }
 
     #[test]
-    fn test_column_name_in_error() -> Result<()> {
+    fn test_column_name_in_error() {
         use crate::{types::Type, Error};
-        let db = Connection::open_in_memory()?;
+        let db = Connection::open_in_memory().unwrap();
         db.execute_batch(
             "BEGIN;
              CREATE TABLE foo(x INTEGER, y TEXT);
              INSERT INTO foo VALUES(4, NULL);
              END;",
-        )?;
-        let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?;
-        let mut rows = stmt.query([])?;
-        let row = rows.next()?.unwrap();
+        )
+        .unwrap();
+        let mut stmt = db.prepare("SELECT x as renamed, y FROM foo").unwrap();
+        let mut rows = stmt.query(crate::NO_PARAMS).unwrap();
+        let row = rows.next().unwrap().unwrap();
         match row.get::<_, String>(0).unwrap_err() {
             Error::InvalidColumnType(idx, name, ty) => {
                 assert_eq!(idx, 0);
@@ -215,27 +216,5 @@
                 panic!("Unexpected error type: {:?}", e);
             }
         }
-        Ok(())
-    }
-
-    /// `column_name` reference should stay valid until `stmt` is reprepared (or
-    /// reset) even if DB schema is altered (SQLite documentation is
-    /// ambiguous here because it says reference "is valid until (...) the next
-    /// call to sqlite3_column_name() or sqlite3_column_name16() on the same
-    /// column.". We assume that reference is valid if only
-    /// `sqlite3_column_name()` is used):
-    #[test]
-    #[cfg(feature = "modern_sqlite")]
-    fn test_column_name_reference() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE y (x);")?;
-        let stmt = db.prepare("SELECT x FROM y;")?;
-        let column_name = stmt.column_name(0)?;
-        assert_eq!("x", column_name);
-        db.execute_batch("ALTER TABLE y RENAME COLUMN x TO z;")?;
-        // column name is not refreshed until statement is re-prepared
-        let same_column_name = stmt.column_name(0)?;
-        assert_eq!(same_column_name, column_name);
-        Ok(())
     }
 }
diff --git a/src/config.rs b/src/config.rs
index b59e5ef..797069e 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -2,7 +2,6 @@
 
 use std::os::raw::c_int;
 
-use crate::error::check;
 use crate::ffi;
 use crate::{Connection, Result};
 
@@ -11,7 +10,6 @@
 #[repr(i32)]
 #[allow(non_snake_case, non_camel_case_types)]
 #[non_exhaustive]
-#[allow(clippy::upper_case_acronyms)]
 pub enum DbConfig {
     //SQLITE_DBCONFIG_MAINDBNAME = 1000, /* const char* */
     //SQLITE_DBCONFIG_LOOKASIDE = 1001,  /* void* int int */
@@ -31,9 +29,7 @@
     /// 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
-    /// 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,
+    //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.
@@ -66,58 +62,56 @@
 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)
         }
     }
@@ -126,13 +120,13 @@
 #[cfg(test)]
 mod test {
     use super::DbConfig;
-    use crate::{Connection, Result};
+    use crate::Connection;
 
     #[test]
-    fn test_db_config() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_db_config() {
+        let db = Connection::open_in_memory().unwrap();
 
-        let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY)?;
+        let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY).unwrap();
         assert_eq!(
             db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_FKEY, opposite),
             Ok(opposite)
@@ -142,7 +136,9 @@
             Ok(opposite)
         );
 
-        let opposite = !db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER)?;
+        let opposite = !db
+            .db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER)
+            .unwrap();
         assert_eq!(
             db.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER, opposite),
             Ok(opposite)
@@ -151,6 +147,5 @@
             db.db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER),
             Ok(opposite)
         );
-        Ok(())
     }
 }
diff --git a/src/context.rs b/src/context.rs
index 5f935fa..b7e8bc8 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -12,10 +12,6 @@
 #[cfg(feature = "array")]
 use crate::vtab::array::{free_array, ARRAY_TYPE};
 
-// This function is inline despite it's size because what's in the ToSqlOutput
-// is often known to the compiler, and thus const prop/DCE can substantially
-// simplify the function.
-#[inline]
 pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<'_>) {
     let value = match *result {
         ToSqlOutput::Borrowed(v) => v,
@@ -58,11 +54,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().cast::<c_void>(),
+                    b.as_ptr() as *const c_void,
                     length as c_int,
                     ffi::SQLITE_TRANSIENT(),
                 );
diff --git a/src/error.rs b/src/error.rs
index 129f697..98583cb 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, Result};
+use crate::{errmsg_to_string, ffi};
 use std::error;
 use std::fmt;
 use std::os::raw::c_int;
@@ -43,12 +43,11 @@
     /// Error converting a file path to a string.
     InvalidPath(PathBuf),
 
-    /// Error returned when an [`execute`](crate::Connection::execute) call
-    /// returns rows.
+    /// Error returned when an `execute` call returns rows.
     ExecuteReturnedResults,
 
     /// Error when a query that was expected to return at least one row (e.g.,
-    /// for [`query_row`](crate::Connection::query_row)) did not return any.
+    /// for `query_row`) did not return any.
     QueryReturnedNoRows,
 
     /// Error when the value of a particular column is requested, but the index
@@ -68,50 +67,40 @@
     /// any or insert many.
     StatementChangedRows(usize),
 
-    /// Error returned by
-    /// [`functions::Context::get`](crate::functions::Context::get) when the
-    /// function argument cannot be converted to the requested type.
+    /// Error returned by `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.
+    /// Error returned by `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)).
+    /// `create_scalar_function`).
     #[cfg(feature = "functions")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
     #[allow(dead_code)]
     UserFunctionError(Box<dyn error::Error + Send + Sync + 'static>),
 
-    /// Error available for the implementors of the
-    /// [`ToSql`](crate::types::ToSql) trait.
+    /// Error available for the implementors of the `ToSql` trait.
     ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
 
     /// Error when the SQL is not a `SELECT`, is not read-only.
     InvalidQuery,
 
     /// An error case available for implementors of custom modules (e.g.,
-    /// [`create_module`](crate::Connection::create_module)).
+    /// `create_module`).
     #[cfg(feature = "vtab")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "vtab")))]
     #[allow(dead_code)]
     ModuleError(String),
 
     /// An unwinding panic occurs in an UDF (user-defined function).
     #[cfg(feature = "functions")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
     UnwindingPanic,
 
-    /// An error returned when
-    /// [`Context::get_aux`](crate::functions::Context::get_aux) attempts to
-    /// retrieve data of a different type than what had been stored using
-    /// [`Context::set_aux`](crate::functions::Context::set_aux).
+    /// An error returned when `Context::get_aux` attempts to retrieve data
+    /// of a different type than what had been stored using `Context::set_aux`.
     #[cfg(feature = "functions")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
     GetAuxWrongType,
 
     /// Error when the SQL contains multiple statements.
@@ -122,11 +111,9 @@
     InvalidParameterCount(usize, usize),
 
     /// Returned from various functions in the Blob IO positional API. For
-    /// example,
-    /// [`Blob::raw_read_at_exact`](crate::blob::Blob::raw_read_at_exact) will
-    /// return it if the blob has insufficient data.
+    /// example, [`Blob::raw_read_at_exact`](crate::blob::Blob::raw_read_at_exact)
+    /// will return it if the blob has insufficient data.
     #[cfg(feature = "blob")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "blob")))]
     BlobSizeError,
 }
 
@@ -178,14 +165,12 @@
 }
 
 impl From<str::Utf8Error> for Error {
-    #[cold]
     fn from(err: str::Utf8Error) -> Error {
         Error::Utf8Error(err)
     }
 }
 
 impl From<::std::ffi::NulError> for Error {
-    #[cold]
     fn from(err: ::std::ffi::NulError) -> Error {
         Error::NulError(err)
     }
@@ -196,13 +181,17 @@
 /// The conversion isn't precise, but it's convenient to have it
 /// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
 impl From<FromSqlError> for Error {
-    #[cold]
     fn from(err: FromSqlError) -> Error {
         // The error type requires index and type fields, but they aren't known in this
         // context.
         match err {
             FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
-            FromSqlError::InvalidBlobSize { .. } => {
+            #[cfg(feature = "i128_blob")]
+            FromSqlError::InvalidI128Size(_) => {
+                Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
+            }
+            #[cfg(feature = "uuid")]
+            FromSqlError::InvalidUuidSize(_) => {
                 Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
             }
             FromSqlError::Other(source) => {
@@ -337,12 +326,10 @@
 
 // These are public but not re-exported by lib.rs, so only visible within crate.
 
-#[cold]
 pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
     Error::SqliteFailure(ffi::Error::new(code), message)
 }
 
-#[cold]
 pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error {
     let message = if db.is_null() {
         None
@@ -352,10 +339,11 @@
     error_from_sqlite_code(code, message)
 }
 
-pub fn check(code: c_int) -> Result<()> {
-    if code != crate::ffi::SQLITE_OK {
-        Err(crate::error::error_from_sqlite_code(code, None))
-    } else {
-        Ok(())
-    }
+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());
+        }
+    }};
 }
diff --git a/src/functions.rs b/src/functions.rs
index e613182..3531391 100644
--- a/src/functions.rs
+++ b/src/functions.rs
@@ -1,17 +1,17 @@
-//! Create or redefine SQL functions.
+//! `feature = "functions"` Create or redefine SQL functions.
 //!
 //! # Example
 //!
 //! Adding a `regexp` function to a connection in which compiled regular
 //! expressions are cached in a `HashMap`. For an alternative implementation
-//! that uses SQLite's [Function Auxiliary Data](https://www.sqlite.org/c3ref/get_auxdata.html) interface
+//! that uses SQLite's [Function Auxilliary Data](https://www.sqlite.org/c3ref/get_auxdata.html) interface
 //! to avoid recompiling regular expressions, see the unit tests for this
 //! module.
 //!
 //! ```rust
 //! use regex::Regex;
 //! use rusqlite::functions::FunctionFlags;
-//! use rusqlite::{Connection, Error, Result};
+//! use rusqlite::{Connection, Error, Result, NO_PARAMS};
 //! use std::sync::Arc;
 //! type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
 //!
@@ -22,9 +22,10 @@
 //!         FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
 //!         move |ctx| {
 //!             assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
-//!             let regexp: Arc<Regex> = ctx.get_or_create_aux(0, |vr| -> Result<_, BoxError> {
-//!                 Ok(Regex::new(vr.as_str()?)?)
-//!             })?;
+//!             let regexp: Arc<Regex> = ctx
+//!                 .get_or_create_aux(0, |vr| -> Result<_, BoxError> {
+//!                     Ok(Regex::new(vr.as_str()?)?)
+//!                 })?;
 //!             let is_match = {
 //!                 let text = ctx
 //!                     .get_raw(1)
@@ -43,18 +44,17 @@
 //!     let db = Connection::open_in_memory()?;
 //!     add_regexp_function(&db)?;
 //!
-//!     let is_match: bool =
-//!         db.query_row("SELECT regexp('[aeiou]*', 'aaaaeeeiii')", [], |row| {
-//!             row.get(0)
-//!         })?;
+//!     let is_match: bool = db.query_row(
+//!         "SELECT regexp('[aeiou]*', 'aaaaeeeiii')",
+//!         NO_PARAMS,
+//!         |row| row.get(0),
+//!     )?;
 //!
 //!     assert!(is_match);
 //!     Ok(())
 //! }
 //! ```
 use std::any::Any;
-use std::marker::PhantomData;
-use std::ops::Deref;
 use std::os::raw::{c_int, c_void};
 use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe};
 use std::ptr;
@@ -84,24 +84,27 @@
         ffi::SQLITE_CONSTRAINT
     }
 
-    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);
+    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);
+            }
         }
-    } 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);
+        _ => {
+            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.cast::<T>()));
+    drop(Box::from_raw(p as *mut T));
 }
 
-/// Context is a wrapper for the SQLite function
+/// `feature = "functions"` Context is a wrapper for the SQLite function
 /// evaluation context.
 pub struct Context<'a> {
     ctx: *mut sqlite3_context,
@@ -110,15 +113,11 @@
 
 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()
     }
@@ -127,8 +126,7 @@
     ///
     /// # Failure
     ///
-    /// Will panic if `idx` is greater than or equal to
-    /// [`self.len()`](Context::len).
+    /// Will panic if `idx` is greater than or equal to `self.len()`.
     ///
     /// Will return Err if the underlying SQLite type cannot be converted to a
     /// `T`.
@@ -143,7 +141,12 @@
             FromSqlError::Other(err) => {
                 Error::FromSqlConversionFailure(idx, value.data_type(), err)
             }
-            FromSqlError::InvalidBlobSize { .. } => {
+            #[cfg(feature = "i128_blob")]
+            FromSqlError::InvalidI128Size(_) => {
+                Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
+            }
+            #[cfg(feature = "uuid")]
+            FromSqlError::InvalidUuidSize(_) => {
                 Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
             }
         })
@@ -153,21 +156,17 @@
     ///
     /// # Failure
     ///
-    /// Will panic if `idx` is greater than or equal to
-    /// [`self.len()`](Context::len).
-    #[inline]
-    #[must_use]
+    /// Will panic if `idx` is greater than or equal to `self.len()`.
     pub fn get_raw(&self, idx: usize) -> ValueRef<'_> {
         let arg = self.args[idx];
         unsafe { ValueRef::from_value(arg) }
     }
 
-    /// Fetch or insert the auxiliary data associated with a particular
+    /// Fetch or insert the the auxilliary data associated with a particular
     /// parameter. This is intended to be an easier-to-use way of fetching it
-    /// compared to calling [`get_aux`](Context::get_aux) and
-    /// [`set_aux`](Context::set_aux) separately.
+    /// compared to calling `get_aux` and `set_aux` separately.
     ///
-    /// See `https://www.sqlite.org/c3ref/get_auxdata.html` for a discussion of
+    /// See https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of
     /// this feature, or the unit tests of this module for an example.
     pub fn get_or_create_aux<T, E, F>(&self, arg: c_int, func: F) -> Result<Arc<T>>
     where
@@ -186,8 +185,8 @@
         }
     }
 
-    /// Sets the auxiliary data associated with a particular parameter. See
-    /// `https://www.sqlite.org/c3ref/get_auxdata.html` for a discussion of
+    /// Sets the auxilliary data associated with a particular parameter. See
+    /// https://www.sqlite.org/c3ref/get_auxdata.html for a discussion of
     /// this feature, or the unit tests of this module for an example.
     pub fn set_aux<T: Send + Sync + 'static>(&self, arg: c_int, value: T) -> Result<Arc<T>> {
         let orig: Arc<T> = Arc::new(value);
@@ -198,17 +197,17 @@
             ffi::sqlite3_set_auxdata(
                 self.ctx,
                 arg,
-                raw.cast(),
+                raw as *mut _,
                 Some(free_boxed_value::<AuxInner>),
-            );
+            )
         };
         Ok(orig)
     }
 
-    /// Gets the auxiliary data that was associated with a given parameter via
-    /// [`set_aux`](Context::set_aux). Returns `Ok(None)` if no data has been
-    /// associated, and Ok(Some(v)) if it has. Returns an error if the
-    /// requested type does not match.
+    /// Gets the auxilliary data that was associated with a given parameter via
+    /// `set_aux`. Returns `Ok(None)` if no data has been associated, and
+    /// Ok(Some(v)) if it has. Returns an error if the requested type does not
+    /// match.
     pub fn get_aux<T: Send + Sync + 'static>(&self, arg: c_int) -> Result<Option<Arc<T>>> {
         let p = unsafe { ffi::sqlite3_get_auxdata(self.ctx, arg) as *const AuxInner };
         if p.is_null() {
@@ -220,42 +219,11 @@
                 .map_err(|_| Error::GetAuxWrongType)
         }
     }
-
-    /// Get the db connection handle via [sqlite3_context_db_handle](https://www.sqlite.org/c3ref/context_db_handle.html)
-    ///
-    /// # Safety
-    ///
-    /// This function is marked unsafe because there is a potential for other
-    /// references to the connection to be sent across threads, [see this comment](https://github.com/rusqlite/rusqlite/issues/643#issuecomment-640181213).
-    pub unsafe fn get_connection(&self) -> Result<ConnectionRef<'_>> {
-        let handle = ffi::sqlite3_context_db_handle(self.ctx);
-        Ok(ConnectionRef {
-            conn: Connection::from_handle(handle)?,
-            phantom: PhantomData,
-        })
-    }
-}
-
-/// A reference to a connection handle with a lifetime bound to something.
-pub struct ConnectionRef<'ctx> {
-    // comes from Connection::from_handle(sqlite3_context_db_handle(...))
-    // and is non-owning
-    conn: Connection,
-    phantom: PhantomData<&'ctx Context<'ctx>>,
-}
-
-impl Deref for ConnectionRef<'_> {
-    type Target = Connection;
-
-    #[inline]
-    fn deref(&self) -> &Connection {
-        &self.conn
-    }
 }
 
 type AuxInner = Arc<dyn Any + Send + Sync + 'static>;
 
-/// Aggregate is the callback interface for user-defined
+/// `feature = "functions"` 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
@@ -266,31 +234,25 @@
     T: ToSql,
 {
     /// Initializes the aggregation context. Will be called prior to the first
-    /// call to [`step()`](Aggregate::step) to set up the context for an
-    /// invocation of the function. (Note: `init()` will not be called if
-    /// there are no rows.)
-    fn init(&self, _: &mut Context<'_>) -> Result<A>;
+    /// call to `step()` to set up the context for an invocation of the
+    /// function. (Note: `init()` will not be called if there are no rows.)
+    fn init(&self) -> A;
 
     /// "step" function called once for each row in an aggregate group. May be
     /// called 0 times if there are no rows.
     fn step(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
 
     /// Computes and returns the final result. Will be called exactly once for
-    /// each invocation of the function. If [`step()`](Aggregate::step) was
-    /// called at least once, will be given `Some(A)` (the same `A` as was
-    /// created by [`init`](Aggregate::init) and given to
-    /// [`step`](Aggregate::step)); if [`step()`](Aggregate::step) was not
-    /// called (because the function is running against 0 rows), will be
-    /// given `None`.
-    ///
-    /// The passed context will have no arguments.
-    fn finalize(&self, _: &mut Context<'_>, _: Option<A>) -> Result<T>;
+    /// each invocation of the function. If `step()` was called at least
+    /// once, will be given `Some(A)` (the same `A` as was created by
+    /// `init` and given to `step`); if `step()` was not called (because
+    /// the function is running against 0 rows), will be given `None`.
+    fn finalize(&self, _: Option<A>) -> Result<T>;
 }
 
-/// `WindowAggregate` is the callback interface for
+/// `feature = "window"` 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,
@@ -330,14 +292,13 @@
 }
 
 impl Default for FunctionFlags {
-    #[inline]
     fn default() -> FunctionFlags {
         FunctionFlags::SQLITE_UTF8
     }
 }
 
 impl Connection {
-    /// Attach a user-defined scalar function to
+    /// `feature = "functions"` Attach a user-defined scalar function to
     /// this database connection.
     ///
     /// `fn_name` is the name the function will be accessible from SQL.
@@ -346,13 +307,12 @@
     /// given the same input, `deterministic` should be `true`.
     ///
     /// The function will remain available until the connection is closed or
-    /// until it is explicitly removed via
-    /// [`remove_function`](Connection::remove_function).
+    /// until it is explicitly removed via `remove_function`.
     ///
     /// # Example
     ///
     /// ```rust
-    /// # use rusqlite::{Connection, Result};
+    /// # use rusqlite::{Connection, Result, NO_PARAMS};
     /// # use rusqlite::functions::FunctionFlags;
     /// fn scalar_function_example(db: Connection) -> Result<()> {
     ///     db.create_scalar_function(
@@ -365,7 +325,7 @@
     ///         },
     ///     )?;
     ///
-    ///     let six_halved: f64 = db.query_row("SELECT halve(6)", [], |r| r.get(0))?;
+    ///     let six_halved: f64 = db.query_row("SELECT halve(6)", NO_PARAMS, |r| r.get(0))?;
     ///     assert_eq!(six_halved, 3f64);
     ///     Ok(())
     /// }
@@ -374,7 +334,6 @@
     /// # Failure
     ///
     /// Will return Err if the function could not be attached to the connection.
-    #[inline]
     pub fn create_scalar_function<F, T>(
         &self,
         fn_name: &str,
@@ -391,13 +350,12 @@
             .create_scalar_function(fn_name, n_arg, flags, x_func)
     }
 
-    /// Attach a user-defined aggregate function to this
+    /// `feature = "functions"` Attach a user-defined aggregate function to this
     /// database connection.
     ///
     /// # Failure
     ///
     /// Will return Err if the function could not be attached to the connection.
-    #[inline]
     pub fn create_aggregate_function<A, D, T>(
         &self,
         fn_name: &str,
@@ -407,7 +365,7 @@
     ) -> Result<()>
     where
         A: RefUnwindSafe + UnwindSafe,
-        D: Aggregate<A, T> + 'static,
+        D: Aggregate<A, T>,
         T: ToSql,
     {
         self.db
@@ -415,14 +373,12 @@
             .create_aggregate_function(fn_name, n_arg, flags, aggr)
     }
 
-    /// Attach a user-defined aggregate window function to
+    /// `feature = "window"` Attach a user-defined aggregate window function to
     /// this database connection.
     ///
-    /// See `https://sqlite.org/windowfunctions.html#udfwinfunc` for more
+    /// See https://sqlite.org/windowfunctions.html#udfwinfunc for more
     /// information.
     #[cfg(feature = "window")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "window")))]
-    #[inline]
     pub fn create_window_function<A, W, T>(
         &self,
         fn_name: &str,
@@ -432,7 +388,7 @@
     ) -> Result<()>
     where
         A: RefUnwindSafe + UnwindSafe,
-        W: WindowAggregate<A, T> + 'static,
+        W: WindowAggregate<A, T>,
         T: ToSql,
     {
         self.db
@@ -440,17 +396,15 @@
             .create_window_function(fn_name, n_arg, flags, aggr)
     }
 
-    /// Removes a user-defined function from this
+    /// `feature = "functions"` Removes a user-defined function from this
     /// database connection.
     ///
     /// `fn_name` and `n_arg` should match the name and number of arguments
-    /// given to [`create_scalar_function`](Connection::create_scalar_function)
-    /// or [`create_aggregate_function`](Connection::create_aggregate_function).
+    /// given to `create_scalar_function` or `create_aggregate_function`.
     ///
     /// # Failure
     ///
     /// Will return Err if the function could not be removed.
-    #[inline]
     pub fn remove_function(&self, fn_name: &str, n_arg: c_int) -> Result<()> {
         self.db.borrow_mut().remove_function(fn_name, n_arg)
     }
@@ -477,7 +431,7 @@
             T: ToSql,
         {
             let r = catch_unwind(|| {
-                let boxed_f: *mut F = ffi::sqlite3_user_data(ctx).cast::<F>();
+                let boxed_f: *mut F = ffi::sqlite3_user_data(ctx) as *mut F;
                 assert!(!boxed_f.is_null(), "Internal error - null function pointer");
                 let ctx = Context {
                     ctx,
@@ -509,7 +463,7 @@
                 c_name.as_ptr(),
                 n_arg,
                 flags.bits(),
-                boxed_f.cast::<c_void>(),
+                boxed_f as *mut c_void,
                 Some(call_boxed_closure::<F, T>),
                 None,
                 None,
@@ -528,7 +482,7 @@
     ) -> Result<()>
     where
         A: RefUnwindSafe + UnwindSafe,
-        D: Aggregate<A, T> + 'static,
+        D: Aggregate<A, T>,
         T: ToSql,
     {
         let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
@@ -539,7 +493,7 @@
                 c_name.as_ptr(),
                 n_arg,
                 flags.bits(),
-                boxed_aggr.cast::<c_void>(),
+                boxed_aggr as *mut c_void,
                 None,
                 Some(call_boxed_step::<A, D, T>),
                 Some(call_boxed_final::<A, D, T>),
@@ -559,7 +513,7 @@
     ) -> Result<()>
     where
         A: RefUnwindSafe + UnwindSafe,
-        W: WindowAggregate<A, T> + 'static,
+        W: WindowAggregate<A, T>,
         T: ToSql,
     {
         let boxed_aggr: *mut W = Box::into_raw(Box::new(aggr));
@@ -570,7 +524,7 @@
                 c_name.as_ptr(),
                 n_arg,
                 flags.bits(),
-                boxed_aggr.cast::<c_void>(),
+                boxed_aggr as *mut c_void,
                 Some(call_boxed_step::<A, W, T>),
                 Some(call_boxed_final::<A, W, T>),
                 Some(call_boxed_value::<A, W, T>),
@@ -617,28 +571,27 @@
     D: Aggregate<A, T>,
     T: ToSql,
 {
-    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 pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
+        Some(pac) => pac,
+        None => {
+            ffi::sqlite3_result_error_nomem(ctx);
+            return;
+        }
     };
 
     let r = catch_unwind(|| {
-        let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx).cast::<D>();
+        let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
         assert!(
             !boxed_aggr.is_null(),
             "Internal error - null aggregate pointer"
         );
+        if (*pac as *mut A).is_null() {
+            *pac = Box::into_raw(Box::new((*boxed_aggr).init()));
+        }
         let mut ctx = Context {
             ctx,
             args: slice::from_raw_parts(argv, argc as usize),
         };
-
-        if (*pac as *mut A).is_null() {
-            *pac = Box::into_raw(Box::new((*boxed_aggr).init(&mut ctx)?));
-        }
-
         (*boxed_aggr).step(&mut ctx, &mut **pac)
     });
     let r = match r {
@@ -664,15 +617,16 @@
     W: WindowAggregate<A, T>,
     T: ToSql,
 {
-    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 pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
+        Some(pac) => pac,
+        None => {
+            ffi::sqlite3_result_error_nomem(ctx);
+            return;
+        }
     };
 
     let r = catch_unwind(|| {
-        let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx).cast::<W>();
+        let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
         assert!(
             !boxed_aggr.is_null(),
             "Internal error - null aggregate pointer"
@@ -717,13 +671,12 @@
     };
 
     let r = catch_unwind(|| {
-        let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx).cast::<D>();
+        let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
         assert!(
             !boxed_aggr.is_null(),
             "Internal error - null aggregate pointer"
         );
-        let mut ctx = Context { ctx, args: &mut [] };
-        (*boxed_aggr).finalize(&mut ctx, a)
+        (*boxed_aggr).finalize(a)
     });
     let t = match r {
         Err(_) => {
@@ -762,7 +715,7 @@
     };
 
     let r = catch_unwind(|| {
-        let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx).cast::<W>();
+        let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
         assert!(
             !boxed_aggr.is_null(),
             "Internal error - null aggregate pointer"
@@ -793,7 +746,7 @@
     #[cfg(feature = "window")]
     use crate::functions::WindowAggregate;
     use crate::functions::{Aggregate, Context, FunctionFlags};
-    use crate::{Connection, Error, Result};
+    use crate::{Connection, Error, Result, NO_PARAMS};
 
     fn half(ctx: &Context<'_>) -> Result<c_double> {
         assert_eq!(ctx.len(), 1, "called with unexpected number of arguments");
@@ -802,39 +755,39 @@
     }
 
     #[test]
-    fn test_function_half() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_function_half() {
+        let db = Connection::open_in_memory().unwrap();
         db.create_scalar_function(
             "half",
             1,
             FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
             half,
-        )?;
-        let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
+        )
+        .unwrap();
+        let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
 
-        assert!((3f64 - result?).abs() < EPSILON);
-        Ok(())
+        assert!((3f64 - result.unwrap()).abs() < EPSILON);
     }
 
     #[test]
-    fn test_remove_function() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_remove_function() {
+        let db = Connection::open_in_memory().unwrap();
         db.create_scalar_function(
             "half",
             1,
             FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
             half,
-        )?;
-        let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
-        assert!((3f64 - result?).abs() < EPSILON);
+        )
+        .unwrap();
+        let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
+        assert!((3f64 - result.unwrap()).abs() < EPSILON);
 
-        db.remove_function("half", 1)?;
-        let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
+        db.remove_function("half", 1).unwrap();
+        let result: Result<f64> = db.query_row("SELECT half(6)", NO_PARAMS, |r| r.get(0));
         assert!(result.is_err());
-        Ok(())
     }
 
-    // This implementation of a regexp scalar function uses SQLite's auxiliary data
+    // This implementation of a regexp scalar function uses SQLite's auxilliary data
     // (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular
     // expression multiple times within one query.
     fn regexp_with_auxilliary(ctx: &Context<'_>) -> Result<bool> {
@@ -858,8 +811,8 @@
     }
 
     #[test]
-    fn test_function_regexp_with_auxilliary() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_function_regexp_with_auxilliary() {
+        let db = Connection::open_in_memory().unwrap();
         db.execute_batch(
             "BEGIN;
              CREATE TABLE foo (x string);
@@ -867,32 +820,35 @@
              INSERT INTO foo VALUES ('lXsi');
              INSERT INTO foo VALUES ('lisX');
              END;",
-        )?;
+        )
+        .unwrap();
         db.create_scalar_function(
             "regexp",
             2,
             FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
             regexp_with_auxilliary,
-        )?;
+        )
+        .unwrap();
 
         let result: Result<bool> =
-            db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", [], |r| r.get(0));
+            db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", NO_PARAMS, |r| {
+                r.get(0)
+            });
 
-        assert!(result?);
+        assert_eq!(true, result.unwrap());
 
         let result: Result<i64> = db.query_row(
             "SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
-            [],
+            NO_PARAMS,
             |r| r.get(0),
         );
 
-        assert_eq!(2, result?);
-        Ok(())
+        assert_eq!(2, result.unwrap());
     }
 
     #[test]
-    fn test_varargs_function() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_varargs_function() {
+        let db = Connection::open_in_memory().unwrap();
         db.create_scalar_function(
             "my_concat",
             -1,
@@ -907,48 +863,50 @@
 
                 Ok(ret)
             },
-        )?;
+        )
+        .unwrap();
 
         for &(expected, query) in &[
             ("", "SELECT my_concat()"),
             ("onetwo", "SELECT my_concat('one', 'two')"),
             ("abc", "SELECT my_concat('a', 'b', 'c')"),
         ] {
-            let result: String = db.query_row(query, [], |r| r.get(0))?;
+            let result: String = db.query_row(query, NO_PARAMS, |r| r.get(0)).unwrap();
             assert_eq!(expected, result);
         }
-        Ok(())
     }
 
     #[test]
-    fn test_get_aux_type_checking() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_get_aux_type_checking() {
+        let db = Connection::open_in_memory().unwrap();
         db.create_scalar_function("example", 2, FunctionFlags::default(), |ctx| {
             if !ctx.get::<bool>(1)? {
                 ctx.set_aux::<i64>(0, 100)?;
             } else {
                 assert_eq!(ctx.get_aux::<String>(0), Err(Error::GetAuxWrongType));
-                assert_eq!(*ctx.get_aux::<i64>(0)?.unwrap(), 100);
+                assert_eq!(*ctx.get_aux::<i64>(0).unwrap().unwrap(), 100);
             }
             Ok(true)
-        })?;
+        })
+        .unwrap();
 
-        let res: bool = db.query_row(
-            "SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)",
-            [],
-            |r| r.get(0),
-        )?;
+        let res: bool = db
+            .query_row(
+                "SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)",
+                NO_PARAMS,
+                |r| r.get(0),
+            )
+            .unwrap();
         // Doesn't actually matter, we'll assert in the function if there's a problem.
         assert!(res);
-        Ok(())
     }
 
     struct Sum;
     struct Count;
 
     impl Aggregate<i64, Option<i64>> for Sum {
-        fn init(&self, _: &mut Context<'_>) -> Result<i64> {
-            Ok(0)
+        fn init(&self) -> i64 {
+            0
         }
 
         fn step(&self, ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
@@ -956,14 +914,14 @@
             Ok(())
         }
 
-        fn finalize(&self, _: &mut Context<'_>, sum: Option<i64>) -> Result<Option<i64>> {
+        fn finalize(&self, sum: Option<i64>) -> Result<Option<i64>> {
             Ok(sum)
         }
     }
 
     impl Aggregate<i64, i64> for Count {
-        fn init(&self, _: &mut Context<'_>) -> Result<i64> {
-            Ok(0)
+        fn init(&self) -> i64 {
+            0
         }
 
         fn step(&self, _ctx: &mut Context<'_>, sum: &mut i64) -> Result<()> {
@@ -971,56 +929,58 @@
             Ok(())
         }
 
-        fn finalize(&self, _: &mut Context<'_>, sum: Option<i64>) -> Result<i64> {
+        fn finalize(&self, sum: Option<i64>) -> Result<i64> {
             Ok(sum.unwrap_or(0))
         }
     }
 
     #[test]
-    fn test_sum() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_sum() {
+        let db = Connection::open_in_memory().unwrap();
         db.create_aggregate_function(
             "my_sum",
             1,
             FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
             Sum,
-        )?;
+        )
+        .unwrap();
 
         // sum should return NULL when given no columns (contrast with count below)
         let no_result = "SELECT my_sum(i) FROM (SELECT 2 AS i WHERE 1 <> 1)";
-        let result: Option<i64> = db.query_row(no_result, [], |r| r.get(0))?;
+        let result: Option<i64> = db.query_row(no_result, NO_PARAMS, |r| r.get(0)).unwrap();
         assert!(result.is_none());
 
         let single_sum = "SELECT my_sum(i) FROM (SELECT 2 AS i UNION ALL SELECT 2)";
-        let result: i64 = db.query_row(single_sum, [], |r| r.get(0))?;
+        let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
         assert_eq!(4, result);
 
         let dual_sum = "SELECT my_sum(i), my_sum(j) FROM (SELECT 2 AS i, 1 AS j UNION ALL SELECT \
                         2, 1)";
-        let result: (i64, i64) = db.query_row(dual_sum, [], |r| Ok((r.get(0)?, r.get(1)?)))?;
+        let result: (i64, i64) = db
+            .query_row(dual_sum, NO_PARAMS, |r| Ok((r.get(0)?, r.get(1)?)))
+            .unwrap();
         assert_eq!((4, 2), result);
-        Ok(())
     }
 
     #[test]
-    fn test_count() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_count() {
+        let db = Connection::open_in_memory().unwrap();
         db.create_aggregate_function(
             "my_count",
             -1,
             FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
             Count,
-        )?;
+        )
+        .unwrap();
 
         // count should return 0 when given no columns (contrast with sum above)
         let no_result = "SELECT my_count(i) FROM (SELECT 2 AS i WHERE 1 <> 1)";
-        let result: i64 = db.query_row(no_result, [], |r| r.get(0))?;
+        let result: i64 = db.query_row(no_result, NO_PARAMS, |r| r.get(0)).unwrap();
         assert_eq!(result, 0);
 
         let single_sum = "SELECT my_count(i) FROM (SELECT 2 AS i UNION ALL SELECT 2)";
-        let result: i64 = db.query_row(single_sum, [], |r| r.get(0))?;
+        let result: i64 = db.query_row(single_sum, NO_PARAMS, |r| r.get(0)).unwrap();
         assert_eq!(2, result);
-        Ok(())
     }
 
     #[cfg(feature = "window")]
@@ -1037,16 +997,17 @@
 
     #[test]
     #[cfg(feature = "window")]
-    fn test_window() -> Result<()> {
+    fn test_window() {
         use fallible_iterator::FallibleIterator;
 
-        let db = Connection::open_in_memory()?;
+        let db = Connection::open_in_memory().unwrap();
         db.create_window_function(
             "sumint",
             1,
             FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
             Sum,
-        )?;
+        )
+        .unwrap();
         db.execute_batch(
             "CREATE TABLE t3(x, y);
              INSERT INTO t3 VALUES('a', 4),
@@ -1054,19 +1015,24 @@
                      ('c', 3),
                      ('d', 8),
                      ('e', 1);",
-        )?;
+        )
+        .unwrap();
 
-        let mut stmt = db.prepare(
-            "SELECT x, sumint(y) OVER (
+        let mut stmt = db
+            .prepare(
+                "SELECT x, sumint(y) OVER (
                    ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
                  ) AS sum_y
                  FROM t3 ORDER BY x;",
-        )?;
+            )
+            .unwrap();
 
         let results: Vec<(String, i64)> = stmt
-            .query([])?
+            .query(NO_PARAMS)
+            .unwrap()
             .map(|row| Ok((row.get("x")?, row.get("sum_y")?)))
-            .collect()?;
+            .collect()
+            .unwrap();
         let expected = vec![
             ("a".to_owned(), 9),
             ("b".to_owned(), 12),
@@ -1075,6 +1041,5 @@
             ("e".to_owned(), 9),
         ];
         assert_eq!(expected, results);
-        Ok(())
     }
 }
diff --git a/src/hooks.rs b/src/hooks.rs
index f0ae1f3..53dc041 100644
--- a/src/hooks.rs
+++ b/src/hooks.rs
@@ -1,19 +1,18 @@
-//! Commit, Data Change and Rollback Notification Callbacks
+//! `feature = "hooks"` Commit, Data Change and Rollback Notification Callbacks
 #![allow(non_camel_case_types)]
 
 use std::os::raw::{c_char, c_int, c_void};
-use std::panic::{catch_unwind, RefUnwindSafe};
+use std::panic::catch_unwind;
 use std::ptr;
 
 use crate::ffi;
 
 use crate::{Connection, InnerConnection};
 
-/// Action Codes
+/// `feature = "hooks"` Action Codes
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[repr(i32)]
 #[non_exhaustive]
-#[allow(clippy::upper_case_acronyms)]
 pub enum Action {
     /// Unsupported / unexpected action
     UNKNOWN = -1,
@@ -26,7 +25,6 @@
 }
 
 impl From<i32> for Action {
-    #[inline]
     fn from(code: i32) -> Action {
         match code {
             ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
@@ -37,314 +35,11 @@
     }
 }
 
-/// 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 {
-    /// Register a callback function to be invoked whenever
+    /// `feature = "hooks"` Register a callback function to be invoked whenever
     /// a transaction is committed.
     ///
     /// The callback returns `true` to rollback.
-    #[inline]
     pub fn commit_hook<F>(&self, hook: Option<F>)
     where
         F: FnMut() -> bool + Send + 'static,
@@ -352,9 +47,10 @@
         self.db.borrow_mut().commit_hook(hook);
     }
 
-    /// Register a callback function to be invoked whenever
+    /// `feature = "hooks"` Register a callback function to be invoked whenever
     /// a transaction is committed.
-    #[inline]
+    ///
+    /// The callback returns `true` to rollback.
     pub fn rollback_hook<F>(&self, hook: Option<F>)
     where
         F: FnMut() + Send + 'static,
@@ -362,58 +58,29 @@
         self.db.borrow_mut().rollback_hook(hook);
     }
 
-    /// Register a callback function to be invoked whenever
+    /// `feature = "hooks"` Register a callback function to be invoked whenever
     /// a row is updated, inserted or deleted in a rowid table.
     ///
     /// The callback parameters are:
     ///
-    /// - the type of database update (`SQLITE_INSERT`, `SQLITE_UPDATE` or
-    /// `SQLITE_DELETE`),
+    /// - the type of database update (SQLITE_INSERT, SQLITE_UPDATE or
+    /// SQLITE_DELETE),
     /// - the name of the database ("main", "temp", ...),
     /// - the name of the table that is updated,
     /// - the ROWID of the row that is updated.
-    #[inline]
     pub fn update_hook<F>(&self, hook: Option<F>)
     where
         F: FnMut(Action, &str, &str, i64) + Send + 'static,
     {
         self.db.borrow_mut().update_hook(hook);
     }
-
-    /// Register a query progress callback.
-    ///
-    /// The parameter `num_ops` is the approximate number of virtual machine
-    /// instructions that are evaluated between successive invocations of the
-    /// `handler`. If `num_ops` is less than one then the progress handler
-    /// is disabled.
-    ///
-    /// If the progress callback returns `true`, the operation is interrupted.
-    pub fn progress_handler<F>(&self, num_ops: c_int, handler: Option<F>)
-    where
-        F: FnMut() -> bool + Send + RefUnwindSafe + 'static,
-    {
-        self.db.borrow_mut().progress_handler(num_ops, handler);
-    }
-
-    /// Register an authorizer callback that's invoked
-    /// as a statement is being prepared.
-    #[inline]
-    pub fn authorizer<'c, F>(&self, hook: Option<F>)
-    where
-        F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,
-    {
-        self.db.borrow_mut().authorizer(hook);
-    }
 }
 
 impl InnerConnection {
-    #[inline]
     pub fn remove_hooks(&mut self) {
         self.update_hook(None::<fn(Action, &str, &str, i64)>);
         self.commit_hook(None::<fn() -> bool>);
         self.rollback_hook(None::<fn()>);
-        self.progress_handler(0, None::<fn() -> bool>);
-        self.authorizer(None::<fn(AuthContext<'_>) -> Authorization>);
     }
 
     fn commit_hook<F>(&mut self, hook: Option<F>)
@@ -425,7 +92,7 @@
             F: FnMut() -> bool,
         {
             let r = catch_unwind(|| {
-                let boxed_hook: *mut F = p_arg.cast::<F>();
+                let boxed_hook: *mut F = p_arg as *mut F;
                 (*boxed_hook)()
             });
             if let Ok(true) = r {
@@ -451,7 +118,7 @@
                     ffi::sqlite3_commit_hook(
                         self.db(),
                         Some(call_boxed_closure::<F>),
-                        boxed_hook.cast(),
+                        boxed_hook as *mut _,
                     )
                 }
             }
@@ -473,10 +140,10 @@
         where
             F: FnMut(),
         {
-            drop(catch_unwind(|| {
-                let boxed_hook: *mut F = p_arg.cast::<F>();
+            let _ = catch_unwind(|| {
+                let boxed_hook: *mut F = p_arg as *mut F;
                 (*boxed_hook)();
-            }));
+            });
         }
 
         let free_rollback_hook = if hook.is_some() {
@@ -492,7 +159,7 @@
                     ffi::sqlite3_rollback_hook(
                         self.db(),
                         Some(call_boxed_closure::<F>),
-                        boxed_hook.cast(),
+                        boxed_hook as *mut _,
                     )
                 }
             }
@@ -513,22 +180,34 @@
         unsafe extern "C" fn call_boxed_closure<F>(
             p_arg: *mut c_void,
             action_code: c_int,
-            p_db_name: *const c_char,
-            p_table_name: *const c_char,
+            db_str: *const c_char,
+            tbl_str: *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);
-            drop(catch_unwind(|| {
-                let boxed_hook: *mut F = p_arg.cast::<F>();
+            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;
                 (*boxed_hook)(
                     action,
-                    expect_utf8(p_db_name, "database name"),
-                    expect_utf8(p_table_name, "table name"),
+                    db_name.expect("illegal db name"),
+                    tbl_name.expect("illegal table name"),
                     row_id,
                 );
-            }));
+            });
         }
 
         let free_update_hook = if hook.is_some() {
@@ -544,7 +223,7 @@
                     ffi::sqlite3_update_hook(
                         self.db(),
                         Some(call_boxed_closure::<F>),
-                        boxed_hook.cast(),
+                        boxed_hook as *mut _,
                     )
                 }
             }
@@ -557,153 +236,38 @@
         }
         self.free_update_hook = free_update_hook;
     }
-
-    fn progress_handler<F>(&mut self, num_ops: c_int, handler: Option<F>)
-    where
-        F: FnMut() -> bool + Send + RefUnwindSafe + 'static,
-    {
-        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
-        where
-            F: FnMut() -> bool,
-        {
-            let r = catch_unwind(|| {
-                let boxed_handler: *mut F = p_arg.cast::<F>();
-                (*boxed_handler)()
-            });
-            if let Ok(true) = r {
-                1
-            } else {
-                0
-            }
-        }
-
-        if let Some(handler) = handler {
-            let boxed_handler = Box::new(handler);
-            unsafe {
-                ffi::sqlite3_progress_handler(
-                    self.db(),
-                    num_ops,
-                    Some(call_boxed_closure::<F>),
-                    &*boxed_handler as *const F as *mut _,
-                );
-            }
-            self.progress_handler = Some(boxed_handler);
-        } else {
-            unsafe { ffi::sqlite3_progress_handler(self.db(), num_ops, None, ptr::null_mut()) }
-            self.progress_handler = None;
-        };
-    }
-
-    fn authorizer<'c, F>(&'c mut self, authorizer: Option<F>)
-    where
-        F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,
-    {
-        unsafe extern "C" fn call_boxed_closure<'c, F>(
-            p_arg: *mut c_void,
-            action_code: c_int,
-            param1: *const c_char,
-            param2: *const c_char,
-            db_name: *const c_char,
-            trigger_or_view_name: *const c_char,
-        ) -> c_int
-        where
-            F: FnMut(AuthContext<'c>) -> Authorization + Send + 'static,
-        {
-            catch_unwind(|| {
-                let action = AuthAction::from_raw(
-                    action_code,
-                    expect_optional_utf8(param1, "authorizer param 1"),
-                    expect_optional_utf8(param2, "authorizer param 2"),
-                );
-                let auth_ctx = AuthContext {
-                    action,
-                    database_name: expect_optional_utf8(db_name, "database name"),
-                    accessor: expect_optional_utf8(
-                        trigger_or_view_name,
-                        "accessor (inner-most trigger or view)",
-                    ),
-                };
-                let boxed_hook: *mut F = p_arg.cast::<F>();
-                (*boxed_hook)(auth_ctx)
-            })
-            .map_or_else(|_| ffi::SQLITE_ERROR, Authorization::into_raw)
-        }
-
-        let callback_fn = authorizer
-            .as_ref()
-            .map(|_| call_boxed_closure::<'c, F> as unsafe extern "C" fn(_, _, _, _, _, _) -> _);
-        let boxed_authorizer = authorizer.map(Box::new);
-
-        match unsafe {
-            ffi::sqlite3_set_authorizer(
-                self.db(),
-                callback_fn,
-                boxed_authorizer
-                    .as_ref()
-                    .map_or_else(ptr::null_mut, |f| &**f as *const F as *mut _),
-            )
-        } {
-            ffi::SQLITE_OK => {
-                self.authorizer = boxed_authorizer.map(|ba| ba as _);
-            }
-            err_code => {
-                // The only error that `sqlite3_set_authorizer` returns is `SQLITE_MISUSE`
-                // when compiled with `ENABLE_API_ARMOR` and the db pointer is invalid.
-                // This library does not allow constructing a null db ptr, so if this branch
-                // is hit, something very bad has happened. Panicking instead of returning
-                // `Result` keeps this hook's API consistent with the others.
-                panic!("unexpectedly failed to set_authorizer: {}", unsafe {
-                    crate::error::error_from_handle(self.db(), err_code)
-                });
-            }
-        }
-    }
 }
 
 unsafe fn free_boxed_hook<F>(p: *mut c_void) {
-    drop(Box::from_raw(p.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()
+    drop(Box::from_raw(p as *mut F));
 }
 
 #[cfg(test)]
 mod test {
     use super::Action;
-    use crate::{Connection, Result};
+    use crate::Connection;
+    use lazy_static::lazy_static;
     use std::sync::atomic::{AtomicBool, Ordering};
 
     #[test]
-    fn test_commit_hook() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_commit_hook() {
+        let db = Connection::open_in_memory().unwrap();
 
-        static CALLED: AtomicBool = AtomicBool::new(false);
+        lazy_static! {
+            static ref CALLED: AtomicBool = AtomicBool::new(false);
+        }
         db.commit_hook(Some(|| {
             CALLED.store(true, Ordering::Relaxed);
             false
         }));
-        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
+        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
+            .unwrap();
         assert!(CALLED.load(Ordering::Relaxed));
-        Ok(())
     }
 
     #[test]
-    fn test_fn_commit_hook() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_fn_commit_hook() {
+        let db = Connection::open_in_memory().unwrap();
 
         fn hook() -> bool {
             true
@@ -712,27 +276,30 @@
         db.commit_hook(Some(hook));
         db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
             .unwrap_err();
-        Ok(())
     }
 
     #[test]
-    fn test_rollback_hook() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_rollback_hook() {
+        let db = Connection::open_in_memory().unwrap();
 
-        static CALLED: AtomicBool = AtomicBool::new(false);
+        lazy_static! {
+            static ref CALLED: AtomicBool = AtomicBool::new(false);
+        }
         db.rollback_hook(Some(|| {
             CALLED.store(true, Ordering::Relaxed);
         }));
-        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")?;
+        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
+            .unwrap();
         assert!(CALLED.load(Ordering::Relaxed));
-        Ok(())
     }
 
     #[test]
-    fn test_update_hook() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_update_hook() {
+        let db = Connection::open_in_memory().unwrap();
 
-        static CALLED: AtomicBool = AtomicBool::new(false);
+        lazy_static! {
+            static ref 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);
@@ -740,76 +307,8 @@
             assert_eq!(1, row_id);
             CALLED.store(true, Ordering::Relaxed);
         }));
-        db.execute_batch("CREATE TABLE foo (t TEXT)")?;
-        db.execute_batch("INSERT INTO foo VALUES ('lisa')")?;
+        db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
+        db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
         assert!(CALLED.load(Ordering::Relaxed));
-        Ok(())
-    }
-
-    #[test]
-    fn test_progress_handler() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-
-        static CALLED: AtomicBool = AtomicBool::new(false);
-        db.progress_handler(
-            1,
-            Some(|| {
-                CALLED.store(true, Ordering::Relaxed);
-                false
-            }),
-        );
-        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
-        assert!(CALLED.load(Ordering::Relaxed));
-        Ok(())
-    }
-
-    #[test]
-    fn test_progress_handler_interrupt() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-
-        fn handler() -> bool {
-            true
-        }
-
-        db.progress_handler(1, Some(handler));
-        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
-            .unwrap_err();
-        Ok(())
-    }
-
-    #[test]
-    fn test_authorizer() -> Result<()> {
-        use super::{AuthAction, AuthContext, Authorization};
-
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo (public TEXT, private TEXT)")
-            .unwrap();
-
-        let authorizer = move |ctx: AuthContext<'_>| match ctx.action {
-            AuthAction::Read { column_name, .. } if column_name == "private" => {
-                Authorization::Ignore
-            }
-            AuthAction::DropTable { .. } => Authorization::Deny,
-            AuthAction::Pragma { .. } => panic!("shouldn't be called"),
-            _ => Authorization::Allow,
-        };
-
-        db.authorizer(Some(authorizer));
-        db.execute_batch(
-            "BEGIN TRANSACTION; INSERT INTO foo VALUES ('pub txt', 'priv txt'); COMMIT;",
-        )
-        .unwrap();
-        db.query_row_and_then("SELECT * FROM foo", [], |row| -> Result<()> {
-            assert_eq!(row.get::<_, String>("public")?, "pub txt");
-            assert!(row.get::<_, Option<String>>("private")?.is_none());
-            Ok(())
-        })
-        .unwrap();
-        db.execute_batch("DROP TABLE foo").unwrap_err();
-
-        db.authorizer(None::<fn(AuthContext<'_>) -> Authorization>);
-        db.execute_batch("PRAGMA user_version=1").unwrap(); // Disallowed by first authorizer, but it's now removed.
-
-        Ok(())
     }
 }
diff --git a/src/inner_connection.rs b/src/inner_connection.rs
index 0ea630e..dd786fe 100644
--- a/src/inner_connection.rs
+++ b/src/inner_connection.rs
@@ -13,6 +13,7 @@
 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 {
@@ -30,18 +31,11 @@
     pub free_rollback_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>,
     #[cfg(feature = "hooks")]
     pub free_update_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>,
-    #[cfg(feature = "hooks")]
-    pub progress_handler: Option<Box<dyn FnMut() -> bool + Send>>,
-    #[cfg(feature = "hooks")]
-    pub authorizer: Option<crate::hooks::BoxedAuthorizer>,
     owned: bool,
 }
 
-unsafe impl Send for InnerConnection {}
-
 impl InnerConnection {
     #[allow(clippy::mutex_atomic)]
-    #[inline]
     pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
         InnerConnection {
             db,
@@ -52,10 +46,6 @@
             free_rollback_hook: None,
             #[cfg(feature = "hooks")]
             free_update_hook: None,
-            #[cfg(feature = "hooks")]
-            progress_handler: None,
-            #[cfg(feature = "hooks")]
-            authorizer: None,
             owned,
         }
     }
@@ -65,6 +55,8 @@
         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
@@ -129,17 +121,14 @@
         }
     }
 
-    #[inline]
     pub fn db(&self) -> *mut ffi::sqlite3 {
         self.db
     }
 
-    #[inline]
-    pub fn decode_result(&self, code: c_int) -> Result<()> {
+    pub fn decode_result(&mut self, code: c_int) -> Result<()> {
         unsafe { InnerConnection::decode_result_raw(self.db(), code) }
     }
 
-    #[inline]
     unsafe fn decode_result_raw(db: *mut ffi::sqlite3, code: c_int) -> Result<()> {
         if code == ffi::SQLITE_OK {
             Ok(())
@@ -176,44 +165,44 @@
         }
     }
 
-    #[inline]
     pub fn get_interrupt_handle(&self) -> InterruptHandle {
         InterruptHandle {
             db_lock: Arc::clone(&self.interrupt_lock),
         }
     }
 
-    #[inline]
     #[cfg(feature = "load_extension")]
-    pub unsafe fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
-        let r = ffi::sqlite3_enable_load_extension(self.db, onoff);
+    pub fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
+        let r = unsafe { ffi::sqlite3_enable_load_extension(self.db, onoff) };
         self.decode_result(r)
     }
 
     #[cfg(feature = "load_extension")]
-    pub unsafe fn load_extension(
-        &self,
-        dylib_path: &Path,
-        entry_point: Option<&str>,
-    ) -> Result<()> {
+    pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> {
         let dylib_str = super::path_to_cstring(dylib_path)?;
-        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)))
+        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)))
+            }
         }
     }
 
-    #[inline]
     pub fn last_insert_rowid(&self) -> i64 {
         unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
     }
@@ -222,37 +211,35 @@
         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 {
-            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(
+            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(
                     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)?;
@@ -263,6 +250,7 @@
         let tail = if c_tail.is_null() {
             0
         } else {
+            // TODO nightly feature ptr_offset_from #41079
             let n = (c_tail as isize) - (c_sql as isize);
             if n <= 0 || n >= len as isize {
                 0
@@ -275,12 +263,10 @@
         }))
     }
 
-    #[inline]
-    pub fn changes(&self) -> usize {
+    pub fn changes(&mut self) -> usize {
         unsafe { ffi::sqlite3_changes(self.db()) as usize }
     }
 
-    #[inline]
     pub fn is_autocommit(&self) -> bool {
         unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
     }
@@ -300,19 +286,12 @@
         false
     }
 
-    #[cfg(feature = "modern_sqlite")] // 3.10.0
-    pub fn cache_flush(&mut self) -> Result<()> {
-        crate::error::check(unsafe { ffi::sqlite3_db_cacheflush(self.db()) })
-    }
-
     #[cfg(not(feature = "hooks"))]
-    #[inline]
     fn remove_hooks(&mut self) {}
 }
 
 impl Drop for InnerConnection {
     #[allow(unused_must_use)]
-    #[inline]
     fn drop(&mut self) {
         use std::thread::panicking;
 
@@ -326,6 +305,55 @@
     }
 }
 
+#[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();
 
@@ -382,13 +410,19 @@
             }
 
             unsafe {
-                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."
-                    );
+                let msg = "\
+Could not ensure safe initialization of SQLite.
+To fix this, either:
+* Upgrade SQLite to at least version 3.7.0
+* Ensure that SQLite has been initialized in Multi-thread or Serialized mode and call
+  rusqlite::bypass_sqlite_initialization() prior to your first connection attempt.";
+
+                if ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) != ffi::SQLITE_OK {
+                    panic!(msg);
+                }
+                if ffi::sqlite3_initialize() != ffi::SQLITE_OK {
+                    panic!(msg);
+                }
             }
         });
         Ok(())
diff --git a/src/lib.rs b/src/lib.rs
index 1fa7a33..53f1773 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,7 +20,7 @@
 //!                   name            TEXT NOT NULL,
 //!                   data            BLOB
 //!                   )",
-//!         [],
+//!         params![],
 //!     )?;
 //!     let me = Person {
 //!         id: 0,
@@ -33,7 +33,7 @@
 //!     )?;
 //!
 //!     let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
-//!     let person_iter = stmt.query_map([], |row| {
+//!     let person_iter = stmt.query_map(params![], |row| {
 //!         Ok(Person {
 //!             id: row.get(0)?,
 //!             name: row.get(1)?,
@@ -48,7 +48,6 @@
 //! }
 //! ```
 #![warn(missing_docs)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
 
 pub use libsqlite3_sys as ffi;
 
@@ -74,62 +73,53 @@
 pub use crate::column::Column;
 pub use crate::error::Error;
 pub use crate::ffi::ErrorCode;
+#[cfg(feature = "hooks")]
+pub use crate::hooks::Action;
 #[cfg(feature = "load_extension")]
 pub use crate::load_extension_guard::LoadExtensionGuard;
-pub use crate::params::{params_from_iter, Params, ParamsFromIter};
 pub use crate::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows};
 pub use crate::statement::{Statement, StatementStatus};
 pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
 pub use crate::types::ToSql;
 pub use crate::version::*;
 
+#[macro_use]
 mod error;
 
 #[cfg(feature = "backup")]
-#[cfg_attr(docsrs, doc(cfg(feature = "backup")))]
 pub mod backup;
 #[cfg(feature = "blob")]
-#[cfg_attr(docsrs, doc(cfg(feature = "blob")))]
 pub mod blob;
 mod busy;
 mod cache;
 #[cfg(feature = "collation")]
-#[cfg_attr(docsrs, doc(cfg(feature = "collation")))]
 mod collation;
 mod column;
 pub mod config;
 #[cfg(any(feature = "functions", feature = "vtab"))]
 mod context;
 #[cfg(feature = "functions")]
-#[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
 pub mod functions;
 #[cfg(feature = "hooks")]
-#[cfg_attr(docsrs, doc(cfg(feature = "hooks")))]
-pub mod hooks;
+mod hooks;
 mod inner_connection;
 #[cfg(feature = "limits")]
-#[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
 pub mod limits;
 #[cfg(feature = "load_extension")]
 mod load_extension_guard;
-mod params;
 mod pragma;
 mod raw_statement;
 mod row;
 #[cfg(feature = "session")]
-#[cfg_attr(docsrs, doc(cfg(feature = "session")))]
 pub mod session;
 mod statement;
 #[cfg(feature = "trace")]
-#[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
 pub mod trace;
 mod transaction;
 pub mod types;
-#[cfg(feature = "unlock_notify")]
 mod unlock_notify;
 mod version;
 #[cfg(feature = "vtab")]
-#[cfg_attr(docsrs, doc(cfg(feature = "vtab")))]
 pub mod vtab;
 
 pub(crate) mod util;
@@ -137,16 +127,11 @@
 
 // Number of cached prepared statements we'll hold on to.
 const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
-/// To be used when your statement has no [parameter][sqlite-varparam].
-///
-/// [sqlite-varparam]: https://sqlite.org/lang_expr.html#varparam
-///
-/// This is deprecated in favor of using an empty array literal.
-#[deprecated = "Use an empty array instead; `stmt.execute(NO_PARAMS)` => `stmt.execute([])`"]
+/// To be used when your statement has no [parameter](https://sqlite.org/lang_expr.html#varparam).
 pub const NO_PARAMS: &[&dyn ToSql] = &[];
 
-/// A macro making it more convenient to pass heterogeneous or long lists of
-/// parameters as a `&[&dyn ToSql]`.
+/// A macro making it more convenient to pass heterogeneous lists
+/// of parameters as a `&[&dyn ToSql]`.
 ///
 /// # Example
 ///
@@ -160,18 +145,16 @@
 /// }
 ///
 /// fn add_person(conn: &Connection, person: &Person) -> Result<()> {
-///     conn.execute(
-///         "INSERT INTO person (name, age_in_years, data)
+///     conn.execute("INSERT INTO person (name, age_in_years, data)
 ///                   VALUES (?1, ?2, ?3)",
-///         params![person.name, person.age_in_years, person.data],
-///     )?;
+///                  params![person.name, person.age_in_years, person.data])?;
 ///     Ok(())
 /// }
 /// ```
 #[macro_export]
 macro_rules! params {
     () => {
-        &[] as &[&dyn $crate::ToSql]
+        $crate::NO_PARAMS
     };
     ($($param:expr),+ $(,)?) => {
         &[$(&$param as &dyn $crate::ToSql),+] as &[&dyn $crate::ToSql]
@@ -193,14 +176,14 @@
 /// }
 ///
 /// fn add_person(conn: &Connection, person: &Person) -> Result<()> {
-///     conn.execute(
+///     conn.execute_named(
 ///         "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(())
 /// }
@@ -208,12 +191,12 @@
 #[macro_export]
 macro_rules! named_params {
     () => {
-        &[] as &[(&str, &dyn $crate::ToSql)]
+        &[]
     };
     // Note: It's a lot more work to support this as part of the same macro as
     // `params!`, unfortunately.
     ($($param_name:literal: $param_val:expr),+ $(,)?) => {
-        &[$(($param_name, &$param_val as &dyn $crate::ToSql)),+] as &[(&str, &dyn $crate::ToSql)]
+        &[$(($param_name, &$param_val as &dyn $crate::ToSql)),+]
     };
 }
 
@@ -258,10 +241,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().cast::<c_char>(), ffi::SQLITE_TRANSIENT())
+        (s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT())
     } else {
         // Return a pointer guaranteed to live forever
-        ("".as_ptr().cast::<c_char>(), ffi::SQLITE_STATIC())
+        ("".as_ptr() as *const c_char, ffi::SQLITE_STATIC())
     };
     Ok((ptr, len, dtor_info))
 }
@@ -292,7 +275,7 @@
 }
 
 /// Name for a database within a SQLite connection.
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone)]
 pub enum DatabaseName<'a> {
     /// The main database.
     Main,
@@ -304,12 +287,6 @@
     Attached(&'a str),
 }
 
-/// Shorthand for [`DatabaseName::Main`].
-pub const MAIN_DB: DatabaseName<'static> = DatabaseName::Main;
-
-/// Shorthand for [`DatabaseName::Temp`].
-pub const TEMP_DB: DatabaseName<'static> = DatabaseName::Temp;
-
 // Currently DatabaseName is only used by the backup and blob mods, so hide
 // this (private) impl to avoid dead code warnings.
 #[cfg(any(
@@ -319,8 +296,7 @@
     feature = "modern_sqlite"
 ))]
 impl DatabaseName<'_> {
-    #[inline]
-    fn as_cstring(&self) -> Result<util::SmallCString> {
+    fn to_cstring(&self) -> Result<util::SmallCString> {
         use self::DatabaseName::{Attached, Main, Temp};
         match *self {
             Main => str_to_cstring("main"),
@@ -340,7 +316,6 @@
 unsafe impl Send for Connection {}
 
 impl Drop for Connection {
-    #[inline]
     fn drop(&mut self) {
         self.flush_prepared_statement_cache();
     }
@@ -368,7 +343,6 @@
     ///
     /// Will return `Err` if `path` cannot be converted to a C-compatible
     /// string or if the underlying SQLite open call fails.
-    #[inline]
     pub fn open<P: AsRef<Path>>(path: P) -> Result<Connection> {
         let flags = OpenFlags::default();
         Connection::open_with_flags(path, flags)
@@ -379,7 +353,6 @@
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite open call fails.
-    #[inline]
     pub fn open_in_memory() -> Result<Connection> {
         let flags = OpenFlags::default();
         Connection::open_in_memory_with_flags(flags)
@@ -394,7 +367,6 @@
     ///
     /// Will return `Err` if `path` cannot be converted to a C-compatible
     /// string or if the underlying SQLite open call fails.
-    #[inline]
     pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: OpenFlags) -> Result<Connection> {
         let c_path = path_to_cstring(path.as_ref())?;
         InnerConnection::open_with_flags(&c_path, flags, None).map(|db| Connection {
@@ -414,7 +386,6 @@
     ///
     /// Will return `Err` if either `path` or `vfs` cannot be converted to a
     /// C-compatible string or if the underlying SQLite open call fails.
-    #[inline]
     pub fn open_with_flags_and_vfs<P: AsRef<Path>>(
         path: P,
         flags: OpenFlags,
@@ -437,7 +408,6 @@
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite open call fails.
-    #[inline]
     pub fn open_in_memory_with_flags(flags: OpenFlags) -> Result<Connection> {
         Connection::open_with_flags(":memory:", flags)
     }
@@ -450,9 +420,8 @@
     ///
     /// # Failure
     ///
-    /// Will return `Err` if `vfs` cannot be converted to a C-compatible
+    /// Will return `Err` if vfs` cannot be converted to a C-compatible
     /// string or if the underlying SQLite open call fails.
-    #[inline]
     pub fn open_in_memory_with_flags_and_vfs(flags: OpenFlags, vfs: &str) -> Result<Connection> {
         Connection::open_with_flags_and_vfs(":memory:", flags, vfs)
     }
@@ -467,9 +436,9 @@
     /// fn create_tables(conn: &Connection) -> Result<()> {
     ///     conn.execute_batch(
     ///         "BEGIN;
-    ///          CREATE TABLE foo(x INTEGER);
-    ///          CREATE TABLE bar(y TEXT);
-    ///          COMMIT;",
+    ///                         CREATE TABLE foo(x INTEGER);
+    ///                         CREATE TABLE bar(y TEXT);
+    ///                         COMMIT;",
     ///     )
     /// }
     /// ```
@@ -502,79 +471,52 @@
     ///
     /// ## Example
     ///
-    /// ### With positional params
-    ///
     /// ```rust,no_run
     /// # use rusqlite::{Connection};
     /// fn update_rows(conn: &Connection) {
-    ///     match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?", [1i32]) {
+    ///     match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?", &[1i32]) {
     ///         Ok(updated) => println!("{} rows were updated", updated),
     ///         Err(err) => println!("update failed: {}", err),
     ///     }
     /// }
     /// ```
     ///
-    /// ### With positional params of varying types
-    ///
-    /// ```rust,no_run
-    /// # use rusqlite::{params, Connection};
-    /// fn update_rows(conn: &Connection) {
-    ///     match conn.execute(
-    ///         "UPDATE foo SET bar = 'baz' WHERE qux = ?1 AND quux = ?2",
-    ///         params![1i32, 1.5f64],
-    ///     ) {
-    ///         Ok(updated) => println!("{} rows were updated", updated),
-    ///         Err(err) => println!("update failed: {}", err),
-    ///     }
-    /// }
-    /// ```
-    ///
-    /// ### With named params
-    ///
-    /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result};
-    /// fn insert(conn: &Connection) -> Result<usize> {
-    ///     conn.execute(
-    ///         "INSERT INTO test (name) VALUES (:name)",
-    ///         &[(":name", "one")],
-    ///     )
-    /// }
-    /// ```
-    ///
     /// # Failure
     ///
     /// Will return `Err` if `sql` cannot be converted to a C-compatible string
     /// or if the underlying SQLite call fails.
-    #[inline]
-    pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize> {
+    pub fn execute<P>(&self, sql: &str, params: P) -> Result<usize>
+    where
+        P: IntoIterator,
+        P::Item: ToSql,
+    {
         self.prepare(sql)
             .and_then(|mut stmt| stmt.check_no_tail().and_then(|_| stmt.execute(params)))
     }
 
-    /// Returns the path to the database file, if one exists and is known.
-    ///
-    /// Note that in some cases [PRAGMA
-    /// database_list](https://sqlite.org/pragma.html#pragma_database_list) is
-    /// likely to be more robust.
-    #[inline]
-    pub fn path(&self) -> Option<&Path> {
-        self.path.as_deref()
-    }
-
     /// Convenience method to prepare and execute a single SQL statement with
     /// named parameter(s).
     ///
     /// On success, returns the number of rows that were changed or inserted or
     /// deleted (via `sqlite3_changes`).
     ///
+    /// ## Example
+    ///
+    /// ```rust,no_run
+    /// # use rusqlite::{Connection, Result};
+    /// fn insert(conn: &Connection) -> Result<usize> {
+    ///     conn.execute_named(
+    ///         "INSERT INTO test (name) VALUES (:name)",
+    ///         &[(":name", &"one")],
+    ///     )
+    /// }
+    /// ```
+    ///
     /// # Failure
     ///
     /// Will return `Err` if `sql` cannot be converted to a C-compatible string
     /// or if the underlying SQLite call fails.
-    #[deprecated = "You can use `execute` with named params now."]
     pub fn execute_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
-        // This function itself is deprecated, so it's fine
-        #![allow(deprecated)]
         self.prepare(sql).and_then(|mut stmt| {
             stmt.check_no_tail()
                 .and_then(|_| stmt.execute_named(params))
@@ -585,7 +527,6 @@
     ///
     /// Uses [sqlite3_last_insert_rowid](https://www.sqlite.org/c3ref/last_insert_rowid.html) under
     /// the hood.
-    #[inline]
     pub fn last_insert_rowid(&self) -> i64 {
         self.db.borrow_mut().last_insert_rowid()
     }
@@ -596,11 +537,11 @@
     /// ## Example
     ///
     /// ```rust,no_run
-    /// # use rusqlite::{Result, Connection};
+    /// # use rusqlite::{Result,Connection, NO_PARAMS};
     /// fn preferred_locale(conn: &Connection) -> Result<String> {
     ///     conn.query_row(
     ///         "SELECT value FROM preferences WHERE name='locale'",
-    ///         [],
+    ///         NO_PARAMS,
     ///         |row| row.get(0),
     ///     )
     /// }
@@ -617,10 +558,10 @@
     ///
     /// Will return `Err` if `sql` cannot be converted to a C-compatible string
     /// or if the underlying SQLite call fails.
-    #[inline]
     pub fn query_row<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T>
     where
-        P: Params,
+        P: IntoIterator,
+        P::Item: ToSql,
         F: FnOnce(&Row<'_>) -> Result<T>,
     {
         let mut stmt = self.prepare(sql)?;
@@ -642,12 +583,13 @@
     ///
     /// Will return `Err` if `sql` cannot be converted to a C-compatible string
     /// or if the underlying SQLite call fails.
-    #[deprecated = "You can use `query_row` with named params now."]
     pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
     where
         F: FnOnce(&Row<'_>) -> Result<T>,
     {
-        self.query_row(sql, params, f)
+        let mut stmt = self.prepare(sql)?;
+        stmt.check_no_tail()?;
+        stmt.query_row_named(params, f)
     }
 
     /// Convenience method to execute a query that is expected to return a
@@ -658,11 +600,11 @@
     /// ## Example
     ///
     /// ```rust,no_run
-    /// # use rusqlite::{Result, Connection};
+    /// # use rusqlite::{Result,Connection, NO_PARAMS};
     /// fn preferred_locale(conn: &Connection) -> Result<String> {
     ///     conn.query_row_and_then(
     ///         "SELECT value FROM preferences WHERE name='locale'",
-    ///         [],
+    ///         NO_PARAMS,
     ///         |row| row.get(0),
     ///     )
     /// }
@@ -675,10 +617,10 @@
     ///
     /// Will return `Err` if `sql` cannot be converted to a C-compatible string
     /// or if the underlying SQLite call fails.
-    #[inline]
     pub fn query_row_and_then<T, E, P, F>(&self, sql: &str, params: P, f: F) -> Result<T, E>
     where
-        P: Params,
+        P: IntoIterator,
+        P::Item: ToSql,
         F: FnOnce(&Row<'_>) -> Result<T, E>,
         E: convert::From<Error>,
     {
@@ -686,7 +628,7 @@
         stmt.check_no_tail()?;
         let mut rows = stmt.query(params)?;
 
-        rows.get_expected_row().map_err(E::from).and_then(f)
+        rows.get_expected_row().map_err(E::from).and_then(|r| f(&r))
     }
 
     /// Prepare a SQL statement for execution.
@@ -697,8 +639,8 @@
     /// # use rusqlite::{Connection, Result};
     /// fn insert_new_people(conn: &Connection) -> Result<()> {
     ///     let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?)")?;
-    ///     stmt.execute(["Joe Smith"])?;
-    ///     stmt.execute(["Bob Jones"])?;
+    ///     stmt.execute(&["Joe Smith"])?;
+    ///     stmt.execute(&["Bob Jones"])?;
     ///     Ok(())
     /// }
     /// ```
@@ -707,7 +649,6 @@
     ///
     /// Will return `Err` if `sql` cannot be converted to a C-compatible string
     /// or if the underlying SQLite call fails.
-    #[inline]
     pub fn prepare(&self, sql: &str) -> Result<Statement<'_>> {
         self.db.borrow_mut().prepare(self, sql)
     }
@@ -721,126 +662,72 @@
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite call fails.
-    #[inline]
     pub fn close(self) -> Result<(), (Connection, Error)> {
         self.flush_prepared_statement_cache();
         let r = self.db.borrow_mut().close();
         r.map_err(move |err| (self, err))
     }
 
-    /// Enable loading of SQLite extensions from both SQL queries and Rust.
+    /// `feature = "load_extension"` Enable loading of SQLite extensions.
+    /// Strongly consider using `LoadExtensionGuard` instead of this function.
     ///
-    /// 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
+    /// ## Example
     ///
     /// ```rust,no_run
     /// # use rusqlite::{Connection, Result};
+    /// # use std::path::{Path};
     /// fn load_my_extension(conn: &Connection) -> Result<()> {
-    ///     // 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
-    ///     }
+    ///     conn.load_extension_enable()?;
+    ///     conn.load_extension(Path::new("my_sqlite_extension"), None)?;
+    ///     conn.load_extension_disable()
     /// }
     /// ```
     ///
     /// # 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 unsafe fn load_extension_enable(&self) -> Result<()> {
+    pub fn load_extension_enable(&self) -> Result<()> {
         self.db.borrow_mut().enable_load_extension(1)
     }
 
-    /// Disable loading of SQLite extensions.
+    /// `feature = "load_extension"` Disable loading of SQLite extensions.
     ///
-    /// See [`Connection::load_extension_enable`] for an example.
+    /// See `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<()> {
-        // It's always safe to turn off extension loading.
-        unsafe { self.db.borrow_mut().enable_load_extension(0) }
+        self.db.borrow_mut().enable_load_extension(0)
     }
 
-    /// 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"`, ...).
+    /// `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.
     ///
-    /// 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<()> {
-    ///     // 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) }
+    ///     let _guard = LoadExtensionGuard::new(conn)?;
+    ///
+    ///     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 unsafe fn load_extension<P: AsRef<Path>>(
+    pub fn load_extension<P: AsRef<Path>>(
         &self,
         dylib_path: P,
         entry_point: Option<&str>,
@@ -863,7 +750,6 @@
     /// This function is unsafe because it gives you raw access
     /// to the SQLite connection, and what you do with it could impact the
     /// safety of this `Connection`.
-    #[inline]
     pub unsafe fn handle(&self) -> *mut ffi::sqlite3 {
         self.db.borrow().db()
     }
@@ -876,7 +762,6 @@
     /// # Safety
     ///
     /// This function is unsafe because improper use may impact the Connection.
-    #[inline]
     pub unsafe fn from_handle(db: *mut ffi::sqlite3) -> Result<Connection> {
         let db_path = db_filename(db);
         let db = InnerConnection::new(db, false);
@@ -889,45 +774,32 @@
 
     /// Get access to a handle that can be used to interrupt long running
     /// queries from another thread.
-    #[inline]
     pub fn get_interrupt_handle(&self) -> InterruptHandle {
         self.db.borrow().get_interrupt_handle()
     }
 
-    #[inline]
     fn decode_result(&self, code: c_int) -> Result<()> {
-        self.db.borrow().decode_result(code)
+        self.db.borrow_mut().decode_result(code)
     }
 
     /// Return the number of rows modified, inserted or deleted by the most
     /// recently completed INSERT, UPDATE or DELETE statement on the database
     /// connection.
-    #[inline]
     fn changes(&self) -> usize {
-        self.db.borrow().changes()
+        self.db.borrow_mut().changes()
     }
 
     /// Test for auto-commit mode.
     /// Autocommit mode is on by default.
-    #[inline]
     pub fn is_autocommit(&self) -> bool {
         self.db.borrow().is_autocommit()
     }
 
     /// Determine if all associated prepared statements have been reset.
-    #[inline]
     #[cfg(feature = "modern_sqlite")] // 3.8.6
-    #[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 {
@@ -938,67 +810,6 @@
     }
 }
 
-/// Batch iterator
-/// ```rust
-/// use rusqlite::{Batch, Connection, Result};
-///
-/// fn main() -> Result<()> {
-///     let conn = Connection::open_in_memory()?;
-///     let sql = r"
-///     CREATE TABLE tbl1 (col);
-///     CREATE TABLE tbl2 (col);
-///     ";
-///     let mut batch = Batch::new(&conn, sql);
-///     while let Some(mut stmt) = batch.next()? {
-///         stmt.execute([])?;
-///     }
-///     Ok(())
-/// }
-/// ```
-#[derive(Debug)]
-pub struct Batch<'conn, 'sql> {
-    conn: &'conn Connection,
-    sql: &'sql str,
-    tail: usize,
-}
-
-impl<'conn, 'sql> Batch<'conn, 'sql> {
-    /// Constructor
-    pub fn new(conn: &'conn Connection, sql: &'sql str) -> Batch<'conn, 'sql> {
-        Batch { conn, sql, tail: 0 }
-    }
-
-    /// Iterates on each batch statements.
-    ///
-    /// Returns `Ok(None)` when batch is completed.
-    #[allow(clippy::should_implement_trait)] // fallible iterator
-    pub fn next(&mut self) -> Result<Option<Statement<'conn>>> {
-        while self.tail < self.sql.len() {
-            let sql = &self.sql[self.tail..];
-            let next = self.conn.prepare(sql)?;
-            let tail = next.stmt.tail();
-            if tail == 0 {
-                self.tail = self.sql.len();
-            } else {
-                self.tail += tail;
-            }
-            if next.stmt.is_null() {
-                continue;
-            }
-            return Ok(Some(next));
-        }
-        Ok(None)
-    }
-}
-
-impl<'conn> Iterator for Batch<'conn, '_> {
-    type Item = Result<Statement<'conn>>;
-
-    fn next(&mut self) -> Option<Result<Statement<'conn>>> {
-        self.next().transpose()
-    }
-}
-
 bitflags::bitflags! {
     /// Flags for opening SQLite database connections.
     /// See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
@@ -1027,8 +838,6 @@
         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;
     }
 }
 
@@ -1058,12 +867,27 @@
 ///
 /// This function is unsafe because if you call it and SQLite has actually been
 /// configured to run in single-thread mode,
-/// you may encounter memory errors or data corruption or any number of terrible
+/// you may enounter memory errors or data corruption or any number of terrible
 /// things that should not be possible when you're using Rust.
 pub unsafe fn bypass_sqlite_initialization() {
     BYPASS_SQLITE_INIT.store(true, Ordering::Relaxed);
 }
 
+/// rusqlite performs a one-time check that the runtime SQLite version is at
+/// least as new as the version of SQLite found when rusqlite was built.
+/// Bypassing this check may be dangerous; e.g., if you use features of SQLite
+/// that are not present in the runtime version.
+///
+/// # Safety
+///
+/// If you are sure the runtime version is compatible with the
+/// build-time version for your usage, you can bypass the version check by
+/// calling this function before your first connection attempt.
+pub unsafe fn bypass_sqlite_version_check() {
+    #[cfg(not(feature = "bundled"))]
+    inner_connection::BYPASS_VERSION_CHECK.store(true, Ordering::Relaxed);
+}
+
 /// Allows interrupting a long-running computation.
 pub struct InterruptHandle {
     db_lock: Arc<Mutex<*mut ffi::sqlite3>>,
@@ -1085,7 +909,7 @@
 
 #[cfg(feature = "modern_sqlite")] // 3.7.10
 unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
-    let db_name = DatabaseName::Main.as_cstring().unwrap();
+    let db_name = DatabaseName::Main.to_cstring().unwrap();
     let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
     if db_filename.is_null() {
         None
@@ -1123,38 +947,44 @@
         ensure_sync::<InterruptHandle>();
     }
 
-    fn checked_memory_handle() -> Connection {
+    pub fn checked_memory_handle() -> Connection {
         Connection::open_in_memory().unwrap()
     }
 
     #[test]
-    fn test_concurrent_transactions_busy_commit() -> Result<()> {
+    fn test_concurrent_transactions_busy_commit() {
         use std::time::Duration;
         let tmp = tempfile::tempdir().unwrap();
         let path = tmp.path().join("transactions.db3");
 
-        Connection::open(&path)?.execute_batch(
-            "
+        Connection::open(&path)
+            .expect("create temp db")
+            .execute_batch(
+                "
             BEGIN; CREATE TABLE foo(x INTEGER);
             INSERT INTO foo VALUES(42); END;",
-        )?;
+            )
+            .expect("create temp db");
 
-        let mut db1 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE)?;
-        let mut db2 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY)?;
+        let mut db1 =
+            Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_WRITE).unwrap();
+        let mut db2 = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY).unwrap();
 
-        db1.busy_timeout(Duration::from_millis(0))?;
-        db2.busy_timeout(Duration::from_millis(0))?;
+        db1.busy_timeout(Duration::from_millis(0)).unwrap();
+        db2.busy_timeout(Duration::from_millis(0)).unwrap();
 
         {
-            let tx1 = db1.transaction()?;
-            let tx2 = db2.transaction()?;
+            let tx1 = db1.transaction().unwrap();
+            let tx2 = db2.transaction().unwrap();
 
             // SELECT first makes sqlite lock with a shared lock
-            tx1.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(()))?;
-            tx2.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(()))?;
+            tx1.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| Ok(()))
+                .unwrap();
+            tx2.query_row("SELECT x FROM foo LIMIT 1", NO_PARAMS, |_| Ok(()))
+                .unwrap();
 
-            tx1.execute("INSERT INTO foo VALUES(?1)", [1])?;
-            let _ = tx2.execute("INSERT INTO foo VALUES(?1)", [2]);
+            tx1.execute("INSERT INTO foo VALUES(?1)", &[1]).unwrap();
+            let _ = tx2.execute("INSERT INTO foo VALUES(?1)", &[2]);
 
             let _ = tx1.commit();
             let _ = tx2.commit();
@@ -1166,29 +996,27 @@
         let _ = db2
             .transaction()
             .expect("commit should have closed transaction");
-        Ok(())
     }
 
     #[test]
-    fn test_persistence() -> Result<()> {
+    fn test_persistence() {
         let temp_dir = tempfile::tempdir().unwrap();
         let path = temp_dir.path().join("test.db3");
 
         {
-            let db = Connection::open(&path)?;
+            let db = Connection::open(&path).unwrap();
             let sql = "BEGIN;
                    CREATE TABLE foo(x INTEGER);
                    INSERT INTO foo VALUES(42);
                    END;";
-            db.execute_batch(sql)?;
+            db.execute_batch(sql).unwrap();
         }
 
         let path_string = path.to_str().unwrap();
-        let db = Connection::open(&path_string)?;
-        let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
+        let db = Connection::open(&path_string).unwrap();
+        let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0));
 
-        assert_eq!(42i64, the_answer?);
-        Ok(())
+        assert_eq!(42i64, the_answer.unwrap());
     }
 
     #[test]
@@ -1203,7 +1031,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_err());
+        assert!(!result.is_ok());
         let err = result.err().unwrap();
         if let Error::SqliteFailure(e, Some(msg)) = err {
             assert_eq!(ErrorCode::CannotOpen, e.code);
@@ -1221,7 +1049,7 @@
 
     #[cfg(unix)]
     #[test]
-    fn test_invalid_unicode_file_names() -> Result<()> {
+    fn test_invalid_unicode_file_names() {
         use std::ffi::OsStr;
         use std::fs::File;
         use std::os::unix::ffi::OsStrExt;
@@ -1230,28 +1058,27 @@
         let path = temp_dir.path();
         if File::create(path.join(OsStr::from_bytes(&[0xFE]))).is_err() {
             // Skip test, filesystem doesn't support invalid Unicode
-            return Ok(());
+            return;
         }
         let db_path = path.join(OsStr::from_bytes(&[0xFF]));
         {
-            let db = Connection::open(&db_path)?;
+            let db = Connection::open(&db_path).unwrap();
             let sql = "BEGIN;
                    CREATE TABLE foo(x INTEGER);
                    INSERT INTO foo VALUES(42);
                    END;";
-            db.execute_batch(sql)?;
+            db.execute_batch(sql).unwrap();
         }
 
-        let db = Connection::open(&db_path)?;
-        let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
+        let db = Connection::open(&db_path).unwrap();
+        let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0));
 
-        assert_eq!(42i64, the_answer?);
-        Ok(())
+        assert_eq!(42i64, the_answer.unwrap());
     }
 
     #[test]
-    fn test_close_retry() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_close_retry() {
+        let db = checked_memory_handle();
 
         // 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
@@ -1264,7 +1091,7 @@
             let raw_db = db.db.borrow_mut().db;
             let sql = "SELECT 1";
             let mut raw_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
-            let cstring = str_to_cstring(sql)?;
+            let cstring = str_to_cstring(sql).unwrap();
             let rc = unsafe {
                 ffi::sqlite3_prepare_v2(
                     raw_db,
@@ -1288,7 +1115,6 @@
         assert_eq!(ffi::SQLITE_OK, unsafe { ffi::sqlite3_finalize(raw_stmt) });
 
         db.close().unwrap();
-        Ok(())
     }
 
     #[test]
@@ -1303,8 +1129,8 @@
     }
 
     #[test]
-    fn test_execute_batch() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_execute_batch() {
+        let db = checked_memory_handle();
         let sql = "BEGIN;
                    CREATE TABLE foo(x INTEGER);
                    INSERT INTO foo VALUES(1);
@@ -1312,39 +1138,45 @@
                    INSERT INTO foo VALUES(3);
                    INSERT INTO foo VALUES(4);
                    END;";
-        db.execute_batch(sql)?;
+        db.execute_batch(sql).unwrap();
 
-        db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3")?;
+        db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3")
+            .unwrap();
 
         assert!(db.execute_batch("INVALID SQL").is_err());
-        Ok(())
     }
 
     #[test]
-    fn test_execute() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
+    fn test_execute() {
+        let db = checked_memory_handle();
+        db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
 
-        assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [1i32])?);
-        assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [2i32])?);
+        assert_eq!(
+            1,
+            db.execute("INSERT INTO foo(x) VALUES (?)", &[1i32])
+                .unwrap()
+        );
+        assert_eq!(
+            1,
+            db.execute("INSERT INTO foo(x) VALUES (?)", &[2i32])
+                .unwrap()
+        );
 
         assert_eq!(
             3i32,
-            db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
+            db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
+                .unwrap()
         );
-        Ok(())
     }
 
     #[test]
     #[cfg(feature = "extra_check")]
     fn test_execute_select() {
         let db = checked_memory_handle();
-        let err = db.execute("SELECT 1 WHERE 1 < ?", [1i32]).unwrap_err();
-        assert!(
-            err == Error::ExecuteReturnedResults,
-            "Unexpected error: {}",
-            err
-        );
+        let err = db.execute("SELECT 1 WHERE 1 < ?", &[1i32]).unwrap_err();
+        if err != Error::ExecuteReturnedResults {
+            panic!("Unexpected error: {}", err);
+        }
     }
 
     #[test]
@@ -1354,7 +1186,7 @@
         let err = db
             .execute(
                 "CREATE TABLE foo(x INTEGER); CREATE TABLE foo(x INTEGER)",
-                [],
+                NO_PARAMS,
             )
             .unwrap_err();
         match err {
@@ -1364,79 +1196,78 @@
     }
 
     #[test]
-    fn test_prepare_column_names() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
+    fn test_prepare_column_names() {
+        let db = checked_memory_handle();
+        db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
 
-        let stmt = db.prepare("SELECT * FROM foo")?;
+        let stmt = db.prepare("SELECT * FROM foo").unwrap();
         assert_eq!(stmt.column_count(), 1);
         assert_eq!(stmt.column_names(), vec!["x"]);
 
-        let stmt = db.prepare("SELECT x AS a, x AS b FROM foo")?;
+        let stmt = db.prepare("SELECT x AS a, x AS b FROM foo").unwrap();
         assert_eq!(stmt.column_count(), 2);
         assert_eq!(stmt.column_names(), vec!["a", "b"]);
-        Ok(())
     }
 
     #[test]
-    fn test_prepare_execute() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
+    fn test_prepare_execute() {
+        let db = checked_memory_handle();
+        db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
 
-        let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
-        assert_eq!(insert_stmt.execute([1i32])?, 1);
-        assert_eq!(insert_stmt.execute([2i32])?, 1);
-        assert_eq!(insert_stmt.execute([3i32])?, 1);
+        let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)").unwrap();
+        assert_eq!(insert_stmt.execute(&[1i32]).unwrap(), 1);
+        assert_eq!(insert_stmt.execute(&[2i32]).unwrap(), 1);
+        assert_eq!(insert_stmt.execute(&[3i32]).unwrap(), 1);
 
-        assert_eq!(insert_stmt.execute(["hello"])?, 1);
-        assert_eq!(insert_stmt.execute(["goodbye"])?, 1);
-        assert_eq!(insert_stmt.execute([types::Null])?, 1);
+        assert_eq!(insert_stmt.execute(&["hello".to_string()]).unwrap(), 1);
+        assert_eq!(insert_stmt.execute(&["goodbye".to_string()]).unwrap(), 1);
+        assert_eq!(insert_stmt.execute(&[types::Null]).unwrap(), 1);
 
-        let mut update_stmt = db.prepare("UPDATE foo SET x=? WHERE x<?")?;
-        assert_eq!(update_stmt.execute([3i32, 3i32])?, 2);
-        assert_eq!(update_stmt.execute([3i32, 3i32])?, 0);
-        assert_eq!(update_stmt.execute([8i32, 8i32])?, 3);
-        Ok(())
+        let mut update_stmt = db.prepare("UPDATE foo SET x=? WHERE x<?").unwrap();
+        assert_eq!(update_stmt.execute(&[3i32, 3i32]).unwrap(), 2);
+        assert_eq!(update_stmt.execute(&[3i32, 3i32]).unwrap(), 0);
+        assert_eq!(update_stmt.execute(&[8i32, 8i32]).unwrap(), 3);
     }
 
     #[test]
-    fn test_prepare_query() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
+    fn test_prepare_query() {
+        let db = checked_memory_handle();
+        db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
 
-        let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
-        assert_eq!(insert_stmt.execute([1i32])?, 1);
-        assert_eq!(insert_stmt.execute([2i32])?, 1);
-        assert_eq!(insert_stmt.execute([3i32])?, 1);
+        let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)").unwrap();
+        assert_eq!(insert_stmt.execute(&[1i32]).unwrap(), 1);
+        assert_eq!(insert_stmt.execute(&[2i32]).unwrap(), 1);
+        assert_eq!(insert_stmt.execute(&[3i32]).unwrap(), 1);
 
-        let mut query = db.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC")?;
+        let mut query = db
+            .prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC")
+            .unwrap();
         {
-            let mut rows = query.query([4i32])?;
+            let mut rows = query.query(&[4i32]).unwrap();
             let mut v = Vec::<i32>::new();
 
-            while let Some(row) = rows.next()? {
-                v.push(row.get(0)?);
+            while let Some(row) = rows.next().unwrap() {
+                v.push(row.get(0).unwrap());
             }
 
             assert_eq!(v, [3i32, 2, 1]);
         }
 
         {
-            let mut rows = query.query([3i32])?;
+            let mut rows = query.query(&[3i32]).unwrap();
             let mut v = Vec::<i32>::new();
 
-            while let Some(row) = rows.next()? {
-                v.push(row.get(0)?);
+            while let Some(row) = rows.next().unwrap() {
+                v.push(row.get(0).unwrap());
             }
 
             assert_eq!(v, [2i32, 1]);
         }
-        Ok(())
     }
 
     #[test]
-    fn test_query_map() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_query_map() {
+        let db = checked_memory_handle();
         let sql = "BEGIN;
                    CREATE TABLE foo(x INTEGER, y TEXT);
                    INSERT INTO foo VALUES(4, \"hello\");
@@ -1444,18 +1275,21 @@
                    INSERT INTO foo VALUES(2, \"world\");
                    INSERT INTO foo VALUES(1, \"!\");
                    END;";
-        db.execute_batch(sql)?;
+        db.execute_batch(sql).unwrap();
 
-        let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
-        let results: Result<Vec<String>> = query.query([])?.map(|row| row.get(1)).collect();
+        let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
+        let results: Result<Vec<String>> = query
+            .query(NO_PARAMS)
+            .unwrap()
+            .map(|row| row.get(1))
+            .collect();
 
-        assert_eq!(results?.concat(), "hello, world!");
-        Ok(())
+        assert_eq!(results.unwrap().concat(), "hello, world!");
     }
 
     #[test]
-    fn test_query_row() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_query_row() {
+        let db = checked_memory_handle();
         let sql = "BEGIN;
                    CREATE TABLE foo(x INTEGER);
                    INSERT INTO foo VALUES(1);
@@ -1463,130 +1297,128 @@
                    INSERT INTO foo VALUES(3);
                    INSERT INTO foo VALUES(4);
                    END;";
-        db.execute_batch(sql)?;
+        db.execute_batch(sql).unwrap();
 
         assert_eq!(
             10i64,
-            db.query_row::<i64, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
+            db.query_row::<i64, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
+                .unwrap()
         );
 
-        let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", [], |r| r.get(0));
+        let result: Result<i64> =
+            db.query_row("SELECT x FROM foo WHERE x > 5", NO_PARAMS, |r| r.get(0));
         match result.unwrap_err() {
             Error::QueryReturnedNoRows => (),
             err => panic!("Unexpected error {}", err),
         }
 
-        let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()));
+        let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", NO_PARAMS, |_| Ok(()));
 
         assert!(bad_query_result.is_err());
-        Ok(())
     }
 
     #[test]
-    fn test_optional() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_optional() {
+        let db = checked_memory_handle();
 
-        let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 <> 0", [], |r| r.get(0));
+        let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 <> 0", NO_PARAMS, |r| r.get(0));
         let result = result.optional();
-        match result? {
+        match result.unwrap() {
             None => (),
             _ => panic!("Unexpected result"),
         }
 
-        let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 == 0", [], |r| r.get(0));
+        let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 == 0", NO_PARAMS, |r| r.get(0));
         let result = result.optional();
-        match result? {
+        match result.unwrap() {
             Some(1) => (),
             _ => panic!("Unexpected result"),
         }
 
-        let bad_query_result: Result<i64> = db.query_row("NOT A PROPER QUERY", [], |r| r.get(0));
+        let bad_query_result: Result<i64> =
+            db.query_row("NOT A PROPER QUERY", NO_PARAMS, |r| r.get(0));
         let bad_query_result = bad_query_result.optional();
         assert!(bad_query_result.is_err());
-        Ok(())
     }
 
     #[test]
-    fn test_pragma_query_row() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_pragma_query_row() {
+        let db = checked_memory_handle();
 
         assert_eq!(
             "memory",
-            db.query_row::<String, _, _>("PRAGMA journal_mode", [], |r| r.get(0))?
+            db.query_row::<String, _, _>("PRAGMA journal_mode", NO_PARAMS, |r| r.get(0))
+                .unwrap()
         );
         assert_eq!(
             "off",
-            db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?
+            db.query_row::<String, _, _>("PRAGMA journal_mode=off", NO_PARAMS, |r| r.get(0))
+                .unwrap()
         );
-        Ok(())
     }
 
     #[test]
-    fn test_prepare_failures() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
+    fn test_prepare_failures() {
+        let db = checked_memory_handle();
+        db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
 
         let err = db.prepare("SELECT * FROM does_not_exist").unwrap_err();
         assert!(format!("{}", err).contains("does_not_exist"));
-        Ok(())
     }
 
     #[test]
-    fn test_last_insert_rowid() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
-        db.execute_batch("INSERT INTO foo DEFAULT VALUES")?;
+    fn test_last_insert_rowid() {
+        let db = checked_memory_handle();
+        db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")
+            .unwrap();
+        db.execute_batch("INSERT INTO foo DEFAULT VALUES").unwrap();
 
         assert_eq!(db.last_insert_rowid(), 1);
 
-        let mut stmt = db.prepare("INSERT INTO foo DEFAULT VALUES")?;
+        let mut stmt = db.prepare("INSERT INTO foo DEFAULT VALUES").unwrap();
         for _ in 0i32..9 {
-            stmt.execute([])?;
+            stmt.execute(NO_PARAMS).unwrap();
         }
         assert_eq!(db.last_insert_rowid(), 10);
-        Ok(())
     }
 
     #[test]
-    fn test_is_autocommit() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_is_autocommit() {
+        let db = checked_memory_handle();
         assert!(
             db.is_autocommit(),
             "autocommit expected to be active by default"
         );
-        Ok(())
     }
 
     #[test]
     #[cfg(feature = "modern_sqlite")]
-    fn test_is_busy() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_is_busy() {
+        let db = checked_memory_handle();
         assert!(!db.is_busy());
-        let mut stmt = db.prepare("PRAGMA schema_version")?;
+        let mut stmt = db.prepare("PRAGMA schema_version").unwrap();
         assert!(!db.is_busy());
         {
-            let mut rows = stmt.query([])?;
+            let mut rows = stmt.query(NO_PARAMS).unwrap();
             assert!(!db.is_busy());
-            let row = rows.next()?;
+            let row = rows.next().unwrap();
             assert!(db.is_busy());
             assert!(row.is_some());
         }
         assert!(!db.is_busy());
-        Ok(())
     }
 
     #[test]
-    fn test_statement_debugging() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_statement_debugging() {
+        let db = checked_memory_handle();
         let query = "SELECT 12345";
-        let stmt = db.prepare(query)?;
+        let stmt = db.prepare(query).unwrap();
 
         assert!(format!("{:?}", stmt).contains(query));
-        Ok(())
     }
 
     #[test]
-    fn test_notnull_constraint_error() -> Result<()> {
+    fn test_notnull_constraint_error() {
         // extended error codes for constraints were added in SQLite 3.7.16; if we're
         // running on our bundled version, we know the extended error code exists.
         #[cfg(feature = "modern_sqlite")]
@@ -1596,10 +1428,10 @@
         #[cfg(not(feature = "modern_sqlite"))]
         fn check_extended_code(_extended_code: c_int) {}
 
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x NOT NULL)")?;
+        let db = checked_memory_handle();
+        db.execute_batch("CREATE TABLE foo(x NOT NULL)").unwrap();
 
-        let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", []);
+        let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", NO_PARAMS);
         assert!(result.is_err());
 
         match result.unwrap_err() {
@@ -1609,7 +1441,6 @@
             }
             err => panic!("Unexpected error {}", err),
         }
-        Ok(())
     }
 
     #[test]
@@ -1624,8 +1455,8 @@
 
     #[test]
     #[cfg(feature = "functions")]
-    fn test_interrupt() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_interrupt() {
+        let db = checked_memory_handle();
 
         let interrupt_handle = db.get_interrupt_handle();
 
@@ -1637,12 +1468,14 @@
                 interrupt_handle.interrupt();
                 Ok(0)
             },
-        )?;
+        )
+        .unwrap();
 
-        let mut stmt =
-            db.prepare("SELECT interrupt() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)")?;
+        let mut stmt = db
+            .prepare("SELECT interrupt() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)")
+            .unwrap();
 
-        let result: Result<Vec<i32>> = stmt.query([])?.map(|r| r.get(0)).collect();
+        let result: Result<Vec<i32>> = stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).collect();
 
         match result.unwrap_err() {
             Error::SqliteFailure(err, _) => {
@@ -1652,7 +1485,6 @@
                 panic!("Unexpected error {}", err);
             }
         }
-        Ok(())
     }
 
     #[test]
@@ -1672,48 +1504,36 @@
     }
 
     #[test]
-    fn test_get_raw() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(i, x);")?;
+    fn test_get_raw() {
+        let db = checked_memory_handle();
+        db.execute_batch("CREATE TABLE foo(i, x);").unwrap();
         let vals = ["foobar", "1234", "qwerty"];
-        let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?, ?)")?;
+        let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?, ?)").unwrap();
         for (i, v) in vals.iter().enumerate() {
             let i_to_insert = i as i64;
-            assert_eq!(insert_stmt.execute(params![i_to_insert, v])?, 1);
+            assert_eq!(insert_stmt.execute(params![i_to_insert, v]).unwrap(), 1);
         }
 
-        let mut query = db.prepare("SELECT i, x FROM foo")?;
-        let mut rows = query.query([])?;
+        let mut query = db.prepare("SELECT i, x FROM foo").unwrap();
+        let mut rows = query.query(NO_PARAMS).unwrap();
 
-        while let Some(row) = rows.next()? {
-            let i = row.get_ref(0)?.as_i64()?;
+        while let Some(row) = rows.next().unwrap() {
+            let i = row.get_raw(0).as_i64().unwrap();
             let expect = vals[i as usize];
-            let x = row.get_ref("x")?.as_str()?;
+            let x = row.get_raw("x").as_str().unwrap();
             assert_eq!(x, expect);
         }
-
-        let mut query = db.prepare("SELECT x FROM foo")?;
-        let rows = query.query_map([], |row| {
-            let x = row.get_ref(0)?.as_str()?; // check From<FromSqlError> for Error
-            Ok(x[..].to_owned())
-        })?;
-
-        for (i, row) in rows.enumerate() {
-            assert_eq!(row?, vals[i]);
-        }
-        Ok(())
     }
 
     #[test]
-    fn test_from_handle() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_from_handle() {
+        let db = checked_memory_handle();
         let handle = unsafe { db.handle() };
         {
-            let db = unsafe { Connection::from_handle(handle) }?;
-            db.execute_batch("PRAGMA VACUUM")?;
+            let db = unsafe { Connection::from_handle(handle) }.unwrap();
+            db.execute_batch("PRAGMA VACUUM").unwrap();
         }
         db.close().unwrap();
-        Ok(())
     }
 
     mod query_and_then_tests {
@@ -1757,8 +1577,8 @@
         type CustomResult<T> = Result<T, CustomError>;
 
         #[test]
-        fn test_query_and_then() -> Result<()> {
-            let db = Connection::open_in_memory()?;
+        fn test_query_and_then() {
+            let db = checked_memory_handle();
             let sql = "BEGIN;
                        CREATE TABLE foo(x INTEGER, y TEXT);
                        INSERT INTO foo VALUES(4, \"hello\");
@@ -1766,19 +1586,20 @@
                        INSERT INTO foo VALUES(2, \"world\");
                        INSERT INTO foo VALUES(1, \"!\");
                        END;";
-            db.execute_batch(sql)?;
+            db.execute_batch(sql).unwrap();
 
-            let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
-            let results: Result<Vec<String>> =
-                query.query_and_then([], |row| row.get(1))?.collect();
+            let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
+            let results: Result<Vec<String>> = query
+                .query_and_then(NO_PARAMS, |row| row.get(1))
+                .unwrap()
+                .collect();
 
-            assert_eq!(results?.concat(), "hello, world!");
-            Ok(())
+            assert_eq!(results.unwrap().concat(), "hello, world!");
         }
 
         #[test]
-        fn test_query_and_then_fails() -> Result<()> {
-            let db = Connection::open_in_memory()?;
+        fn test_query_and_then_fails() {
+            let db = checked_memory_handle();
             let sql = "BEGIN;
                        CREATE TABLE foo(x INTEGER, y TEXT);
                        INSERT INTO foo VALUES(4, \"hello\");
@@ -1786,29 +1607,33 @@
                        INSERT INTO foo VALUES(2, \"world\");
                        INSERT INTO foo VALUES(1, \"!\");
                        END;";
-            db.execute_batch(sql)?;
+            db.execute_batch(sql).unwrap();
 
-            let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
-            let bad_type: Result<Vec<f64>> = query.query_and_then([], |row| row.get(1))?.collect();
+            let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
+            let bad_type: Result<Vec<f64>> = query
+                .query_and_then(NO_PARAMS, |row| row.get(1))
+                .unwrap()
+                .collect();
 
             match bad_type.unwrap_err() {
                 Error::InvalidColumnType(..) => (),
                 err => panic!("Unexpected error {}", err),
             }
 
-            let bad_idx: Result<Vec<String>> =
-                query.query_and_then([], |row| row.get(3))?.collect();
+            let bad_idx: Result<Vec<String>> = query
+                .query_and_then(NO_PARAMS, |row| row.get(3))
+                .unwrap()
+                .collect();
 
             match bad_idx.unwrap_err() {
                 Error::InvalidColumnIndex(_) => (),
                 err => panic!("Unexpected error {}", err),
             }
-            Ok(())
         }
 
         #[test]
-        fn test_query_and_then_custom_error() -> CustomResult<()> {
-            let db = Connection::open_in_memory()?;
+        fn test_query_and_then_custom_error() {
+            let db = checked_memory_handle();
             let sql = "BEGIN;
                        CREATE TABLE foo(x INTEGER, y TEXT);
                        INSERT INTO foo VALUES(4, \"hello\");
@@ -1816,20 +1641,20 @@
                        INSERT INTO foo VALUES(2, \"world\");
                        INSERT INTO foo VALUES(1, \"!\");
                        END;";
-            db.execute_batch(sql)?;
+            db.execute_batch(sql).unwrap();
 
-            let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
+            let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
             let results: CustomResult<Vec<String>> = query
-                .query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite))?
+                .query_and_then(NO_PARAMS, |row| row.get(1).map_err(CustomError::Sqlite))
+                .unwrap()
                 .collect();
 
-            assert_eq!(results?.concat(), "hello, world!");
-            Ok(())
+            assert_eq!(results.unwrap().concat(), "hello, world!");
         }
 
         #[test]
-        fn test_query_and_then_custom_error_fails() -> Result<()> {
-            let db = Connection::open_in_memory()?;
+        fn test_query_and_then_custom_error_fails() {
+            let db = checked_memory_handle();
             let sql = "BEGIN;
                        CREATE TABLE foo(x INTEGER, y TEXT);
                        INSERT INTO foo VALUES(4, \"hello\");
@@ -1837,11 +1662,12 @@
                        INSERT INTO foo VALUES(2, \"world\");
                        INSERT INTO foo VALUES(1, \"!\");
                        END;";
-            db.execute_batch(sql)?;
+            db.execute_batch(sql).unwrap();
 
-            let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
+            let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
             let bad_type: CustomResult<Vec<f64>> = query
-                .query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite))?
+                .query_and_then(NO_PARAMS, |row| row.get(1).map_err(CustomError::Sqlite))
+                .unwrap()
                 .collect();
 
             match bad_type.unwrap_err() {
@@ -1850,7 +1676,8 @@
             }
 
             let bad_idx: CustomResult<Vec<String>> = query
-                .query_and_then([], |row| row.get(3).map_err(CustomError::Sqlite))?
+                .query_and_then(NO_PARAMS, |row| row.get(3).map_err(CustomError::Sqlite))
+                .unwrap()
                 .collect();
 
             match bad_idx.unwrap_err() {
@@ -1859,53 +1686,55 @@
             }
 
             let non_sqlite_err: CustomResult<Vec<String>> = query
-                .query_and_then([], |_| Err(CustomError::SomeError))?
+                .query_and_then(NO_PARAMS, |_| Err(CustomError::SomeError))
+                .unwrap()
                 .collect();
 
             match non_sqlite_err.unwrap_err() {
                 CustomError::SomeError => (),
                 err => panic!("Unexpected error {}", err),
             }
-            Ok(())
         }
 
         #[test]
-        fn test_query_row_and_then_custom_error() -> CustomResult<()> {
-            let db = Connection::open_in_memory()?;
+        fn test_query_row_and_then_custom_error() {
+            let db = checked_memory_handle();
             let sql = "BEGIN;
                        CREATE TABLE foo(x INTEGER, y TEXT);
                        INSERT INTO foo VALUES(4, \"hello\");
                        END;";
-            db.execute_batch(sql)?;
+            db.execute_batch(sql).unwrap();
 
             let query = "SELECT x, y FROM foo ORDER BY x DESC";
-            let results: CustomResult<String> =
-                db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite));
+            let results: CustomResult<String> = db.query_row_and_then(query, NO_PARAMS, |row| {
+                row.get(1).map_err(CustomError::Sqlite)
+            });
 
-            assert_eq!(results?, "hello");
-            Ok(())
+            assert_eq!(results.unwrap(), "hello");
         }
 
         #[test]
-        fn test_query_row_and_then_custom_error_fails() -> Result<()> {
-            let db = Connection::open_in_memory()?;
+        fn test_query_row_and_then_custom_error_fails() {
+            let db = checked_memory_handle();
             let sql = "BEGIN;
                        CREATE TABLE foo(x INTEGER, y TEXT);
                        INSERT INTO foo VALUES(4, \"hello\");
                        END;";
-            db.execute_batch(sql)?;
+            db.execute_batch(sql).unwrap();
 
             let query = "SELECT x, y FROM foo ORDER BY x DESC";
-            let bad_type: CustomResult<f64> =
-                db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite));
+            let bad_type: CustomResult<f64> = db.query_row_and_then(query, NO_PARAMS, |row| {
+                row.get(1).map_err(CustomError::Sqlite)
+            });
 
             match bad_type.unwrap_err() {
                 CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
                 err => panic!("Unexpected error {}", err),
             }
 
-            let bad_idx: CustomResult<String> =
-                db.query_row_and_then(query, [], |row| row.get(3).map_err(CustomError::Sqlite));
+            let bad_idx: CustomResult<String> = db.query_row_and_then(query, NO_PARAMS, |row| {
+                row.get(3).map_err(CustomError::Sqlite)
+            });
 
             match bad_idx.unwrap_err() {
                 CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
@@ -1913,104 +1742,70 @@
             }
 
             let non_sqlite_err: CustomResult<String> =
-                db.query_row_and_then(query, [], |_| Err(CustomError::SomeError));
+                db.query_row_and_then(query, NO_PARAMS, |_| Err(CustomError::SomeError));
 
             match non_sqlite_err.unwrap_err() {
                 CustomError::SomeError => (),
                 err => panic!("Unexpected error {}", err),
             }
-            Ok(())
         }
-    }
 
-    #[test]
-    fn test_dynamic() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        let sql = "BEGIN;
+        #[test]
+        fn test_dynamic() {
+            let db = checked_memory_handle();
+            let sql = "BEGIN;
                        CREATE TABLE foo(x INTEGER, y TEXT);
                        INSERT INTO foo VALUES(4, \"hello\");
                        END;";
-        db.execute_batch(sql)?;
+            db.execute_batch(sql).unwrap();
 
-        db.query_row("SELECT * FROM foo", [], |r| {
-            assert_eq!(2, r.as_ref().column_count());
-            Ok(())
-        })
-    }
-    #[test]
-    fn test_dyn_box() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
-        let b: Box<dyn ToSql> = Box::new(5);
-        db.execute("INSERT INTO foo VALUES(?)", [b])?;
-        db.query_row("SELECT x FROM foo", [], |r| {
-            assert_eq!(5, r.get_unwrap::<_, i32>(0));
-            Ok(())
-        })
-    }
+            db.query_row("SELECT * FROM foo", params![], |r| {
+                assert_eq!(2, r.column_count());
+                Ok(())
+            })
+            .unwrap();
+        }
+        #[test]
+        fn test_dyn_box() {
+            let db = checked_memory_handle();
+            db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
+            let b: Box<dyn ToSql> = Box::new(5);
+            db.execute("INSERT INTO foo VALUES(?)", &[b]).unwrap();
+            db.query_row("SELECT x FROM foo", params![], |r| {
+                assert_eq!(5, r.get_unwrap::<_, i32>(0));
+                Ok(())
+            })
+            .unwrap();
+        }
 
-    #[test]
-    fn test_params() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.query_row(
-            "SELECT
+        #[test]
+        fn test_params() {
+            let db = checked_memory_handle();
+            db.query_row(
+                "SELECT
             ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
             ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
             ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
             ?, ?, ?, ?;",
-            params![
-                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-                1, 1, 1, 1, 1, 1,
-            ],
-            |r| {
-                assert_eq!(1, r.get_unwrap::<_, i32>(0));
-                Ok(())
-            },
-        )
-    }
-
-    #[test]
-    #[cfg(not(feature = "extra_check"))]
-    fn test_alter_table() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE x(t);")?;
-        // `execute_batch` should be used but `execute` should also work
-        db.execute("ALTER TABLE x RENAME TO y;", [])?;
-        Ok(())
-    }
-
-    #[test]
-    fn test_batch() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        let sql = r"
-             CREATE TABLE tbl1 (col);
-             CREATE TABLE tbl2 (col);
-             ";
-        let batch = Batch::new(&db, sql);
-        for stmt in batch {
-            let mut stmt = stmt?;
-            stmt.execute([])?;
+                params![
+                    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                    1, 1, 1, 1, 1, 1, 1, 1,
+                ],
+                |r| {
+                    assert_eq!(1, r.get_unwrap::<_, i32>(0));
+                    Ok(())
+                },
+            )
+            .unwrap();
         }
-        Ok(())
-    }
 
-    #[test]
-    #[cfg(all(feature = "bundled", not(feature = "bundled-sqlcipher")))] // SQLite >= 3.35.0
-    fn test_returning() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
-        let row_id =
-            db.query_row::<i64, _, _>("INSERT INTO foo DEFAULT VALUES RETURNING ROWID", [], |r| {
-                r.get(0)
-            })?;
-        assert_eq!(row_id, 1);
-        Ok(())
-    }
-
-    #[test]
-    #[cfg(feature = "modern_sqlite")]
-    fn test_cache_flush() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.cache_flush()
+        #[test]
+        #[cfg(not(feature = "extra_check"))]
+        fn test_alter_table() {
+            let db = checked_memory_handle();
+            db.execute_batch("CREATE TABLE x(t);").unwrap();
+            // `execute_batch` should be used but `execute` should also work
+            db.execute("ALTER TABLE x RENAME TO y;", params![]).unwrap();
+        }
     }
 }
diff --git a/src/limits.rs b/src/limits.rs
index 93e0bb0..238ce56 100644
--- a/src/limits.rs
+++ b/src/limits.rs
@@ -1,63 +1,21 @@
-//! Run-Time Limits
+//! `feature = "limits"` Run-Time Limits
 
-use crate::{ffi, Connection};
 use std::os::raw::c_int;
 
-/// 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,
-}
+use crate::ffi;
+pub use crate::ffi::Limit;
+
+use crate::Connection;
 
 impl Connection {
-    /// Returns the current value of a [`Limit`].
-    #[inline]
-    #[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
+    /// `feature = "limits"` Returns the current value of a limit.
     pub fn limit(&self, limit: Limit) -> i32 {
         let c = self.db.borrow();
         unsafe { ffi::sqlite3_limit(c.db(), limit as c_int, -1) }
     }
 
-    /// Changes the [`Limit`] to `new_val`, returning the prior
+    /// `feature = "limits"` 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) }
@@ -66,66 +24,12 @@
 
 #[cfg(test)]
 mod test {
-    use super::*;
-    use crate::{Connection, Result};
+    use crate::ffi::Limit;
+    use crate::Connection;
 
     #[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()?;
+    fn test_limit() {
+        let db = Connection::open_in_memory().unwrap();
         db.set_limit(Limit::SQLITE_LIMIT_LENGTH, 1024);
         assert_eq!(1024, db.limit(Limit::SQLITE_LIMIT_LENGTH));
 
@@ -164,6 +68,5 @@
             db.set_limit(Limit::SQLITE_LIMIT_WORKER_THREADS, 2);
             assert_eq!(2, db.limit(Limit::SQLITE_LIMIT_WORKER_THREADS));
         }
-        Ok(())
     }
 }
diff --git a/src/load_extension_guard.rs b/src/load_extension_guard.rs
index deed3b4..f4f67d1 100644
--- a/src/load_extension_guard.rs
+++ b/src/load_extension_guard.rs
@@ -1,6 +1,7 @@
 use crate::{Connection, Result};
 
-/// RAII guard temporarily enabling SQLite extensions to be loaded.
+/// `feature = "load_extension"` RAII guard temporarily enabling SQLite
+/// extensions to be loaded.
 ///
 /// ## Example
 ///
@@ -8,13 +9,11 @@
 /// # use rusqlite::{Connection, Result, LoadExtensionGuard};
 /// # use std::path::{Path};
 /// fn load_my_extension(conn: &Connection) -> Result<()> {
-///     unsafe {
-///         let _guard = LoadExtensionGuard::new(conn)?;
-///         conn.load_extension("trusted/sqlite/extension", None)
-///     }
+///     let _guard = LoadExtensionGuard::new(conn)?;
+///
+///     conn.load_extension(Path::new("my_sqlite_extension"), None)
 /// }
 /// ```
-#[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
 pub struct LoadExtensionGuard<'conn> {
     conn: &'conn Connection,
 }
@@ -23,15 +22,7 @@
     /// 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 unsafe fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
+    pub fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
         conn.load_extension_enable()
             .map(|_| LoadExtensionGuard { conn })
     }
@@ -39,7 +30,6 @@
 
 #[allow(unused_must_use)]
 impl Drop for LoadExtensionGuard<'_> {
-    #[inline]
     fn drop(&mut self) {
         self.conn.load_extension_disable();
     }
diff --git a/src/params.rs b/src/params.rs
deleted file mode 100644
index 54aa571..0000000
--- a/src/params.rs
+++ /dev/null
@@ -1,358 +0,0 @@
-use crate::{Result, Statement, ToSql};
-
-mod sealed {
-    /// This trait exists just to ensure that the only impls of `trait Params`
-    /// that are allowed are ones in this crate.
-    pub trait Sealed {}
-}
-use sealed::Sealed;
-
-/// Trait used for [sets of parameter][params] passed into SQL
-/// statements/queries.
-///
-/// [params]: https://www.sqlite.org/c3ref/bind_blob.html
-///
-/// Note: Currently, this trait can only be implemented inside this crate.
-/// Additionally, it's methods (which are `doc(hidden)`) should currently not be
-/// considered part of the stable API, although it's possible they will
-/// stabilize in the future.
-///
-/// # Passing parameters to SQLite
-///
-/// Many functions in this library let you pass parameters to SQLite. Doing this
-/// lets you avoid any risk of SQL injection, and is simpler than escaping
-/// things manually. Aside from deprecated functions and a few helpers, this is
-/// indicated by the function taking a generic argument that implements `Params`
-/// (this trait).
-///
-/// ## Positional parameters
-///
-/// For cases where you want to pass a list of parameters where the number of
-/// parameters is known at compile time, this can be done in one of the
-/// following ways:
-///
-/// - Using the [`rusqlite::params!`](crate::params!) macro, e.g.
-///   `thing.query(rusqlite::params![1, "foo", bar])`. This is mostly useful for
-///   heterogeneous lists of parameters, or lists where the number of parameters
-///   exceeds 32.
-///
-/// - For small heterogeneous lists of parameters, they can either be passed as:
-///
-///     - an array, as in `thing.query([1i32, 2, 3, 4])` or `thing.query(["foo",
-///       "bar", "baz"])`.
-///
-///     - a reference to an array of references, as in `thing.query(&["foo",
-///       "bar", "baz"])` or `thing.query(&[&1i32, &2, &3])`.
-///
-///         (Note: in this case we don't implement this for slices for coherence
-///         reasons, so it really is only for the "reference to array" types —
-///         hence why the number of parameters must be <= 32 or you need to
-///         reach for `rusqlite::params!`)
-///
-///     Unfortunately, in the current design it's not possible to allow this for
-///     references to arrays of non-references (e.g. `&[1i32, 2, 3]`). Code like
-///     this should instead either use `params!`, an array literal, a `&[&dyn
-///     ToSql]` or if none of those work, [`ParamsFromIter`].
-///
-/// - As a slice of `ToSql` trait object references, e.g. `&[&dyn ToSql]`. This
-///   is mostly useful for passing parameter lists around as arguments without
-///   having every function take a generic `P: Params`.
-///
-/// ### Example (positional)
-///
-/// ```rust,no_run
-/// # use rusqlite::{Connection, Result, params};
-/// fn update_rows(conn: &Connection) -> Result<()> {
-///     let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?, ?)")?;
-///
-///     // Using `rusqlite::params!`:
-///     stmt.execute(params![1i32, "blah"])?;
-///
-///     // array literal — non-references
-///     stmt.execute([2i32, 3i32])?;
-///
-///     // array literal — references
-///     stmt.execute(["foo", "bar"])?;
-///
-///     // Slice literal, references:
-///     stmt.execute(&[&2i32, &3i32])?;
-///
-///     // Note: The types behind the references don't have to be `Sized`
-///     stmt.execute(&["foo", "bar"])?;
-///
-///     // However, this doesn't work (see above):
-///     // stmt.execute(&[1i32, 2i32])?;
-///     Ok(())
-/// }
-/// ```
-///
-/// ## Named parameters
-///
-/// SQLite lets you name parameters using a number of conventions (":foo",
-/// "@foo", "$foo"). You can pass named parameters in to SQLite using rusqlite
-/// in a few ways:
-///
-/// - Using the [`rusqlite::named_params!`](crate::named_params!) macro, as in
-///   `stmt.execute(named_params!{ ":name": "foo", ":age": 99 })`. Similar to
-///   the `params` macro, this is most useful for heterogeneous lists of
-///   parameters, or lists where the number of parameters exceeds 32.
-///
-/// - As a slice of `&[(&str, &dyn ToSql)]`. This is what essentially all of
-///   these boil down to in the end, conceptually at least. In theory you can
-///   pass this as `stmt`.
-///
-/// - As array references, similar to the positional params. This looks like
-///   `thing.query(&[(":foo", &1i32), (":bar", &2i32)])` or
-///   `thing.query(&[(":foo", "abc"), (":bar", "def")])`.
-///
-/// Note: Unbound named parameters will be left to the value they previously
-/// were bound with, falling back to `NULL` for parameters which have never been
-/// bound.
-///
-/// ### Example (named)
-///
-/// ```rust,no_run
-/// # use rusqlite::{Connection, Result, named_params};
-/// fn insert(conn: &Connection) -> Result<()> {
-///     let mut stmt = conn.prepare("INSERT INTO test (key, value) VALUES (:key, :value)")?;
-///     // Using `rusqlite::params!`:
-///     stmt.execute(named_params! { ":key": "one", ":val": 2 })?;
-///     // Alternatively:
-///     stmt.execute(&[(":key", "three"), (":val", "four")])?;
-///     // Or:
-///     stmt.execute(&[(":key", &100), (":val", &200)])?;
-///     Ok(())
-/// }
-/// ```
-///
-/// ## No parameters
-///
-/// You can just use an empty array literal for no params. The
-/// `rusqlite::NO_PARAMS` constant which was so common in previous versions of
-/// this library is no longer needed (and is now deprecated).
-///
-/// ### Example (no parameters)
-///
-/// ```rust,no_run
-/// # use rusqlite::{Connection, Result, params};
-/// fn delete_all_users(conn: &Connection) -> Result<()> {
-///     // Just use an empty array (e.g. `[]`) for no params.
-///     conn.execute("DELETE FROM users", [])?;
-///     Ok(())
-/// }
-/// ```
-///
-/// ## Dynamic parameter list
-///
-/// If you have a number of parameters which is unknown at compile time (for
-/// example, building a dynamic query at runtime), you have two choices:
-///
-/// - Use a `&[&dyn ToSql]`, which is nice if you have one otherwise might be
-///   annoying.
-/// - Use the [`ParamsFromIter`] type. This essentially lets you wrap an
-///   iterator some `T: ToSql` with something that implements `Params`.
-///
-/// A lot of the considerations here are similar either way, so you should see
-/// the [`ParamsFromIter`] documentation for more info / examples.
-pub trait Params: Sealed {
-    // XXX not public api, might not need to expose.
-    //
-    // Binds the parameters to the statement. It is unlikely calling this
-    // explicitly will do what you want. Please use `Statement::query` or
-    // similar directly.
-    //
-    // For now, just hide the function in the docs...
-    #[doc(hidden)]
-    fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()>;
-}
-
-// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
-// unambiguous, this must be the *only* implementation for an empty array. This
-// avoids `NO_PARAMS` being a necessary part of the API.
-impl Sealed for [&(dyn ToSql + Send + Sync); 0] {}
-impl Params for [&(dyn ToSql + Send + Sync); 0] {
-    #[inline]
-    fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
-        // Note: Can't just return `Ok(())` — `Statement::bind_parameters`
-        // checks that the right number of params were passed too.
-        // TODO: we should have tests for `Error::InvalidParameterCount`...
-        stmt.bind_parameters(&[] as &[&dyn ToSql])
-    }
-}
-
-impl Sealed for &[&dyn ToSql] {}
-impl Params for &[&dyn ToSql] {
-    #[inline]
-    fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
-        stmt.bind_parameters(self)
-    }
-}
-
-impl Sealed for &[(&str, &dyn ToSql)] {}
-impl Params for &[(&str, &dyn ToSql)] {
-    #[inline]
-    fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
-        stmt.bind_parameters_named(self)
-    }
-}
-
-macro_rules! impl_for_array_ref {
-    ($($N:literal)+) => {$(
-        // These are already generic, and there's a shedload of them, so lets
-        // avoid the compile time hit from making them all inline for now.
-        impl<T: ToSql + ?Sized> Sealed for &[&T; $N] {}
-        impl<T: ToSql + ?Sized> Params for &[&T; $N] {
-            fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
-                stmt.bind_parameters(self)
-            }
-        }
-        impl<T: ToSql + ?Sized> Sealed for &[(&str, &T); $N] {}
-        impl<T: ToSql + ?Sized> Params for &[(&str, &T); $N] {
-            fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
-                stmt.bind_parameters_named(self)
-            }
-        }
-        impl<T: ToSql> Sealed for [T; $N] {}
-        impl<T: ToSql> Params for [T; $N] {
-            #[inline]
-            fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
-                stmt.bind_parameters(&self)
-            }
-        }
-    )+};
-}
-
-// Following libstd/libcore's (old) lead, implement this for arrays up to `[_;
-// 32]`. Note `[_; 0]` is intentionally omitted for coherence reasons, see the
-// note above the impl of `[&dyn ToSql; 0]` for more information.
-impl_for_array_ref!(
-    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
-    18 19 20 21 22 23 24 25 26 27 29 30 31 32
-);
-
-/// Adapter type which allows any iterator over [`ToSql`] values to implement
-/// [`Params`].
-///
-/// This struct is created by the [`params_from_iter`] function.
-///
-/// This can be useful if you have something like an `&[String]` (of unknown
-/// length), and you want to use them with an API that wants something
-/// implementing `Params`. This way, you can avoid having to allocate storage
-/// for something like a `&[&dyn ToSql]`.
-///
-/// This essentially is only ever actually needed when dynamically generating
-/// SQL — static SQL (by definition) has the number of parameters known
-/// statically. As dynamically generating SQL is itself pretty advanced, this
-/// API is itself for advanced use cases (See "Realistic use case" in the
-/// examples).
-///
-/// # Example
-///
-/// ## Basic usage
-///
-/// ```rust,no_run
-/// use rusqlite::{params_from_iter, Connection, Result};
-/// use std::collections::BTreeSet;
-///
-/// fn query(conn: &Connection, ids: &BTreeSet<String>) -> Result<()> {
-///     assert_eq!(ids.len(), 3, "Unrealistic sample code");
-///
-///     let mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?, ?, ?)")?;
-///     let _rows = stmt.query(params_from_iter(ids.iter()))?;
-///
-///     // use _rows...
-///     Ok(())
-/// }
-/// ```
-///
-/// ## Realistic use case
-///
-/// Here's how you'd use `ParamsFromIter` to call [`Statement::exists`] with a
-/// dynamic number of parameters.
-///
-/// ```rust,no_run
-/// use rusqlite::{Connection, Result};
-///
-/// pub fn any_active_users(conn: &Connection, usernames: &[String]) -> Result<bool> {
-///     if usernames.is_empty() {
-///         return Ok(false);
-///     }
-///
-///     // Note: `repeat_vars` never returns anything attacker-controlled, so
-///     // it's fine to use it in a dynamically-built SQL string.
-///     let vars = repeat_vars(usernames.len());
-///
-///     let sql = format!(
-///         // In practice this would probably be better as an `EXISTS` query.
-///         "SELECT 1 FROM user WHERE is_active AND name IN ({}) LIMIT 1",
-///         vars,
-///     );
-///     let mut stmt = conn.prepare(&sql)?;
-///     stmt.exists(rusqlite::params_from_iter(usernames))
-/// }
-///
-/// // Helper function to return a comma-separated sequence of `?`.
-/// // - `repeat_vars(0) => panic!(...)`
-/// // - `repeat_vars(1) => "?"`
-/// // - `repeat_vars(2) => "?,?"`
-/// // - `repeat_vars(3) => "?,?,?"`
-/// // - ...
-/// fn repeat_vars(count: usize) -> String {
-///     assert_ne!(count, 0);
-///     let mut s = "?,".repeat(count);
-///     // Remove trailing comma
-///     s.pop();
-///     s
-/// }
-/// ```
-///
-/// That is fairly complex, and even so would need even more work to be fully
-/// production-ready:
-///
-/// - production code should ensure `usernames` isn't so large that it will
-///   surpass [`conn.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)`][limits]),
-///   chunking if too large. (Note that the limits api requires rusqlite to have
-///   the "limits" feature).
-///
-/// - `repeat_vars` can be implemented in a way that avoids needing to allocate
-///   a String.
-///
-/// - Etc...
-///
-/// [limits]: crate::Connection::limit
-///
-/// This complexity reflects the fact that `ParamsFromIter` is mainly intended
-/// for advanced use cases — most of the time you should know how many
-/// parameters you have statically (and if you don't, you're either doing
-/// something tricky, or should take a moment to think about the design).
-#[derive(Clone, Debug)]
-pub struct ParamsFromIter<I>(I);
-
-/// Constructor function for a [`ParamsFromIter`]. See its documentation for
-/// more.
-#[inline]
-pub fn params_from_iter<I>(iter: I) -> ParamsFromIter<I>
-where
-    I: IntoIterator,
-    I::Item: ToSql,
-{
-    ParamsFromIter(iter)
-}
-
-impl<I> Sealed for ParamsFromIter<I>
-where
-    I: IntoIterator,
-    I::Item: ToSql,
-{
-}
-
-impl<I> Params for ParamsFromIter<I>
-where
-    I: IntoIterator,
-    I::Item: ToSql,
-{
-    #[inline]
-    fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
-        stmt.bind_parameters(self.0)
-    }
-}
diff --git a/src/pragma.rs b/src/pragma.rs
index 1c81c95..4855154 100644
--- a/src/pragma.rs
+++ b/src/pragma.rs
@@ -5,7 +5,7 @@
 use crate::error::Error;
 use crate::ffi;
 use crate::types::{ToSql, ToSqlOutput, ValueRef};
-use crate::{Connection, DatabaseName, Result, Row};
+use crate::{Connection, DatabaseName, Result, Row, NO_PARAMS};
 
 pub struct Sql {
     buf: String,
@@ -143,7 +143,7 @@
             if ch == quote {
                 self.buf.push(ch);
             }
-            self.buf.push(ch);
+            self.buf.push(ch)
         }
         self.buf.push(quote);
     }
@@ -176,7 +176,7 @@
     {
         let mut query = Sql::new();
         query.push_pragma(schema_name, pragma_name)?;
-        self.query_row(&query, [], f)
+        self.query_row(&query, NO_PARAMS, f)
     }
 
     /// Query the current rows/values of `pragma_name`.
@@ -195,10 +195,10 @@
         let mut query = Sql::new();
         query.push_pragma(schema_name, pragma_name)?;
         let mut stmt = self.prepare(&query)?;
-        let mut rows = stmt.query([])?;
+        let mut rows = stmt.query(NO_PARAMS)?;
         while let Some(result_row) = rows.next()? {
             let row = result_row;
-            f(row)?;
+            f(&row)?;
         }
         Ok(())
     }
@@ -212,16 +212,15 @@
     ///
     /// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
     /// `SELECT * FROM pragma_table_info(?);`
-    pub fn pragma<F, V>(
+    pub fn pragma<F>(
         &self,
         schema_name: Option<DatabaseName<'_>>,
         pragma_name: &str,
-        pragma_value: V,
+        pragma_value: &dyn ToSql,
         mut f: F,
     ) -> Result<()>
     where
         F: FnMut(&Row<'_>) -> Result<()>,
-        V: ToSql,
     {
         let mut sql = Sql::new();
         sql.push_pragma(schema_name, pragma_name)?;
@@ -229,13 +228,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([])?;
+        let mut rows = stmt.query(NO_PARAMS)?;
         while let Some(result_row) = rows.next()? {
             let row = result_row;
-            f(row)?;
+            f(&row)?;
         }
         Ok(())
     }
@@ -244,38 +243,34 @@
     ///
     /// Some pragmas will return the updated value which cannot be retrieved
     /// with this method.
-    pub fn pragma_update<V>(
+    pub fn pragma_update(
         &self,
         schema_name: Option<DatabaseName<'_>>,
         pragma_name: &str,
-        pragma_value: V,
-    ) -> Result<()>
-    where
-        V: ToSql,
-    {
+        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)?;
+        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>(
+    pub fn pragma_update_and_check<F, T>(
         &self,
         schema_name: Option<DatabaseName<'_>>,
         pragma_name: &str,
-        pragma_value: V,
+        pragma_value: &dyn ToSql,
         f: F,
     ) -> Result<T>
     where
         F: FnOnce(&Row<'_>) -> Result<T>,
-        V: ToSql,
     {
         let mut sql = Sql::new();
         sql.push_pragma(schema_name, pragma_name)?;
@@ -283,8 +278,8 @@
         // 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)
+        sql.push_value(pragma_value)?;
+        self.query_row(&sql, NO_PARAMS, f)
     }
 }
 
@@ -303,15 +298,15 @@
 }
 
 fn is_identifier_start(c: char) -> bool {
-    ('A'..='Z').contains(&c) || c == '_' || ('a'..='z').contains(&c) || c > '\x7F'
+    (c >= 'A' && c <= 'Z') || c == '_' || (c >= 'a' && c <= 'z') || c > '\x7F'
 }
 
 fn is_identifier_continue(c: char) -> bool {
     c == '$'
-        || ('0'..='9').contains(&c)
-        || ('A'..='Z').contains(&c)
+        || (c >= '0' && c <= '9')
+        || (c >= 'A' && c <= 'Z')
         || c == '_'
-        || ('a'..='z').contains(&c)
+        || (c >= 'a' && c <= 'z')
         || c > '\x7F'
 }
 
@@ -319,106 +314,99 @@
 mod test {
     use super::Sql;
     use crate::pragma;
-    use crate::{Connection, DatabaseName, Result};
+    use crate::{Connection, DatabaseName};
 
     #[test]
-    fn pragma_query_value() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        let user_version: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
+    fn pragma_query_value() {
+        let db = Connection::open_in_memory().unwrap();
+        let user_version: i32 = db
+            .pragma_query_value(None, "user_version", |row| row.get(0))
+            .unwrap();
         assert_eq!(0, user_version);
-        Ok(())
     }
 
     #[test]
     #[cfg(feature = "modern_sqlite")]
-    fn pragma_func_query_value() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        let user_version: i32 =
-            db.query_row("SELECT user_version FROM pragma_user_version", [], |row| {
-                row.get(0)
-            })?;
+    fn pragma_func_query_value() {
+        use crate::NO_PARAMS;
+
+        let db = Connection::open_in_memory().unwrap();
+        let user_version: i32 = db
+            .query_row(
+                "SELECT user_version FROM pragma_user_version",
+                NO_PARAMS,
+                |row| row.get(0),
+            )
+            .unwrap();
         assert_eq!(0, user_version);
-        Ok(())
     }
 
     #[test]
-    fn pragma_query_no_schema() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn pragma_query_no_schema() {
+        let db = Connection::open_in_memory().unwrap();
         let mut user_version = -1;
         db.pragma_query(None, "user_version", |row| {
             user_version = row.get(0)?;
             Ok(())
-        })?;
+        })
+        .unwrap();
         assert_eq!(0, user_version);
-        Ok(())
     }
 
     #[test]
-    fn pragma_query_with_schema() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn pragma_query_with_schema() {
+        let db = Connection::open_in_memory().unwrap();
         let mut user_version = -1;
         db.pragma_query(Some(DatabaseName::Main), "user_version", |row| {
             user_version = row.get(0)?;
             Ok(())
-        })?;
+        })
+        .unwrap();
         assert_eq!(0, user_version);
-        Ok(())
     }
 
     #[test]
-    fn pragma() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn pragma() {
+        let db = Connection::open_in_memory().unwrap();
         let mut columns = Vec::new();
         db.pragma(None, "table_info", &"sqlite_master", |row| {
             let column: String = row.get(1)?;
             columns.push(column);
             Ok(())
-        })?;
+        })
+        .unwrap();
         assert_eq!(5, columns.len());
-        Ok(())
     }
 
     #[test]
     #[cfg(feature = "modern_sqlite")]
-    fn pragma_func() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)")?;
+    fn pragma_func() {
+        let db = Connection::open_in_memory().unwrap();
+        let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap();
         let mut columns = Vec::new();
-        let mut rows = table_info.query(["sqlite_master"])?;
+        let mut rows = table_info.query(&["sqlite_master"]).unwrap();
 
-        while let Some(row) = rows.next()? {
+        while let Some(row) = rows.next().unwrap() {
             let row = row;
-            let column: String = row.get(1)?;
+            let column: String = row.get(1).unwrap();
             columns.push(column);
         }
         assert_eq!(5, columns.len());
-        Ok(())
     }
 
     #[test]
-    fn pragma_update() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.pragma_update(None, "user_version", 1)
+    fn pragma_update() {
+        let db = Connection::open_in_memory().unwrap();
+        db.pragma_update(None, "user_version", &1).unwrap();
     }
 
     #[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))?;
+    fn pragma_update_and_check() {
+        let db = Connection::open_in_memory().unwrap();
+        let journal_mode: String = db
+            .pragma_update_and_check(None, "journal_mode", &"OFF", |row| row.get(0))
+            .unwrap();
         assert_eq!("off", &journal_mode);
-        // Sanity checks to ensure the move to a generic `ToSql` wasn't breaking
-        assert_eq!(
-            "off",
-            db.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row
-                .get::<_, String>(0))?,
-        );
-        let param: &dyn crate::ToSql = &"OFF";
-        assert_eq!(
-            "off",
-            db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?,
-        );
-        Ok(())
     }
 
     #[test]
@@ -444,14 +432,13 @@
     }
 
     #[test]
-    fn locking_mode() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn locking_mode() {
+        let db = Connection::open_in_memory().unwrap();
         let r = db.pragma_update(None, "locking_mode", &"exclusive");
         if cfg!(feature = "extra_check") {
             r.unwrap_err();
         } else {
-            r?;
+            r.unwrap();
         }
-        Ok(())
     }
 }
diff --git a/src/raw_statement.rs b/src/raw_statement.rs
index 8e624dc..c02dcd9 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: ParamIndexCache,
+    cache: crate::util::ParamIndexCache,
     // Cached SQL (trimmed) that we use as the key when we're in the statement
     // cache. This is None for statements which didn't come from the statement
     // cache.
@@ -29,48 +29,40 @@
 }
 
 impl RawStatement {
-    #[inline]
     pub unsafe fn new(stmt: *mut ffi::sqlite3_stmt, tail: usize) -> RawStatement {
         RawStatement {
             ptr: stmt,
             tail,
-            cache: ParamIndexCache::default(),
+            cache: Default::default(),
             statement_cache_key: None,
         }
     }
 
-    #[inline]
     pub fn is_null(&self) -> bool {
         self.ptr.is_null()
     }
 
-    #[inline]
     pub(crate) fn set_statement_cache_key(&mut self, p: impl Into<Arc<str>>) {
         self.statement_cache_key = Some(p.into());
     }
 
-    #[inline]
     pub(crate) fn statement_cache_key(&self) -> Option<Arc<str>> {
         self.statement_cache_key.clone()
     }
 
-    #[inline]
     pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt {
         self.ptr
     }
 
-    #[inline]
     pub fn column_count(&self) -> usize {
         // Note: Can't cache this as it changes if the schema is altered.
         unsafe { ffi::sqlite3_column_count(self.ptr) as usize }
     }
 
-    #[inline]
     pub fn column_type(&self, idx: usize) -> c_int {
         unsafe { ffi::sqlite3_column_type(self.ptr, idx as c_int) }
     }
 
-    #[inline]
     #[cfg(feature = "column_decltype")]
     pub fn column_decltype(&self, idx: usize) -> Option<&CStr> {
         unsafe {
@@ -83,7 +75,6 @@
         }
     }
 
-    #[inline]
     pub fn column_name(&self, idx: usize) -> Option<&CStr> {
         let idx = idx as c_int;
         if idx < 0 || idx >= self.column_count() as c_int {
@@ -101,52 +92,35 @@
         }
     }
 
-    #[inline]
-    #[cfg(not(feature = "unlock_notify"))]
     pub fn step(&self) -> c_int {
-        unsafe { ffi::sqlite3_step(self.ptr) }
-    }
-
-    #[cfg(feature = "unlock_notify")]
-    pub fn step(&self) -> c_int {
-        use crate::unlock_notify;
-        let mut db = core::ptr::null_mut::<ffi::sqlite3>();
-        loop {
-            unsafe {
-                let mut rc = ffi::sqlite3_step(self.ptr);
-                // Bail out early for success and errors unrelated to locking. We
-                // still need check `is_locked` after this, but checking now lets us
-                // avoid one or two (admittedly cheap) calls into SQLite that we
-                // don't need to make.
-                if (rc & 0xff) != ffi::SQLITE_LOCKED {
-                    break rc;
+        if 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;
                 }
-                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);
+                rc = unsafe { unlock_notify::wait_for_unlock_notify(db) };
                 if rc != ffi::SQLITE_OK {
-                    break rc;
+                    break;
                 }
                 self.reset();
             }
+            rc
+        } else {
+            unsafe { ffi::sqlite3_step(self.ptr) }
         }
     }
 
-    #[inline]
     pub fn reset(&self) -> c_int {
         unsafe { ffi::sqlite3_reset(self.ptr) }
     }
 
-    #[inline]
     pub fn bind_parameter_count(&self) -> usize {
         unsafe { ffi::sqlite3_bind_parameter_count(self.ptr) as usize }
     }
 
-    #[inline]
     pub fn bind_parameter_index(&self, name: &str) -> Option<usize> {
         self.cache.get_or_insert_with(name, |param_cstr| {
             let r = unsafe { ffi::sqlite3_bind_parameter_index(self.ptr, param_cstr.as_ptr()) };
@@ -157,24 +131,10 @@
         })
     }
 
-    #[inline]
-    pub fn bind_parameter_name(&self, index: i32) -> Option<&CStr> {
-        unsafe {
-            let name = ffi::sqlite3_bind_parameter_name(self.ptr, index);
-            if name.is_null() {
-                None
-            } else {
-                Some(CStr::from_ptr(name))
-            }
-        }
-    }
-
-    #[inline]
     pub fn clear_bindings(&self) -> c_int {
         unsafe { ffi::sqlite3_clear_bindings(self.ptr) }
     }
 
-    #[inline]
     pub fn sql(&self) -> Option<&CStr> {
         if self.ptr.is_null() {
             None
@@ -183,44 +143,36 @@
         }
     }
 
-    #[inline]
     pub fn finalize(mut self) -> c_int {
         self.finalize_()
     }
 
-    #[inline]
     fn finalize_(&mut self) -> c_int {
         let r = unsafe { ffi::sqlite3_finalize(self.ptr) };
         self.ptr = ptr::null_mut();
         r
     }
 
-    // does not work for PRAGMA
-    #[inline]
     #[cfg(all(feature = "extra_check", feature = "modern_sqlite"))] // 3.7.4
     pub fn readonly(&self) -> bool {
         unsafe { ffi::sqlite3_stmt_readonly(self.ptr) != 0 }
     }
 
-    #[inline]
     #[cfg(feature = "modern_sqlite")] // 3.14.0
     pub(crate) fn expanded_sql(&self) -> Option<SqliteMallocString> {
         unsafe { SqliteMallocString::from_raw(ffi::sqlite3_expanded_sql(self.ptr)) }
     }
 
-    #[inline]
     pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 {
         assert!(!self.ptr.is_null());
         unsafe { ffi::sqlite3_stmt_status(self.ptr, status as i32, reset as i32) }
     }
 
-    #[inline]
     #[cfg(feature = "extra_check")]
     pub fn has_tail(&self) -> bool {
         self.tail != 0
     }
 
-    #[inline]
     pub fn tail(&self) -> usize {
         self.tail
     }
diff --git a/src/row.rs b/src/row.rs
index c766e50..36aa1a6 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -13,7 +13,6 @@
 }
 
 impl<'stmt> Rows<'stmt> {
-    #[inline]
     fn reset(&mut self) {
         if let Some(stmt) = self.stmt.take() {
             stmt.reset();
@@ -29,11 +28,9 @@
     /// This interface is not compatible with Rust's `Iterator` trait, because
     /// the lifetime of the returned row is tied to the lifetime of `self`.
     /// This is a fallible "streaming iterator". For a more natural interface,
-    /// consider using [`query_map`](crate::Statement::query_map) or
-    /// [`query_and_then`](crate::Statement::query_and_then) instead, which
+    /// consider using `query_map` or `query_and_then` instead, which
     /// return types that implement `Iterator`.
     #[allow(clippy::should_implement_trait)] // cannot implement Iterator
-    #[inline]
     pub fn next(&mut self) -> Result<Option<&Row<'stmt>>> {
         self.advance()?;
         Ok((*self).get())
@@ -43,14 +40,13 @@
     /// implements `FallibleIterator`.
     /// ```rust,no_run
     /// use fallible_iterator::FallibleIterator;
-    /// # use rusqlite::{Result, Statement};
+    /// # use rusqlite::{Result, Statement, NO_PARAMS};
     /// fn query(stmt: &mut Statement) -> Result<Vec<i64>> {
-    ///     let rows = stmt.query([])?;
+    ///     let rows = stmt.query(NO_PARAMS)?;
     ///     rows.map(|r| r.get(0)).collect()
     /// }
     /// ```
     // FIXME Hide FallibleStreamingIterator::map
-    #[inline]
     pub fn map<F, B>(self, f: F) -> Map<'stmt, F>
     where
         F: FnMut(&Row<'_>) -> Result<B>,
@@ -60,7 +56,6 @@
 
     /// Map over this `Rows`, converting it to a [`MappedRows`], which
     /// implements `Iterator`.
-    #[inline]
     pub fn mapped<F, B>(self, f: F) -> MappedRows<'stmt, F>
     where
         F: FnMut(&Row<'_>) -> Result<B>,
@@ -71,23 +66,15 @@
     /// Map over this `Rows` with a fallible function, converting it to a
     /// [`AndThenRows`], which implements `Iterator` (instead of
     /// `FallibleStreamingIterator`).
-    #[inline]
     pub fn and_then<F, T, E>(self, f: F) -> AndThenRows<'stmt, F>
     where
         F: FnMut(&Row<'_>) -> Result<T, E>,
     {
         AndThenRows { rows: self, map: f }
     }
-
-    /// Give access to the underlying statement
-    #[must_use]
-    pub fn as_ref(&self) -> Option<&Statement<'stmt>> {
-        self.stmt
-    }
 }
 
 impl<'stmt> Rows<'stmt> {
-    #[inline]
     pub(crate) fn new(stmt: &'stmt Statement<'stmt>) -> Rows<'stmt> {
         Rows {
             stmt: Some(stmt),
@@ -95,7 +82,6 @@
         }
     }
 
-    #[inline]
     pub(crate) fn get_expected_row(&mut self) -> Result<&Row<'stmt>> {
         match self.next()? {
             Some(row) => Ok(row),
@@ -105,14 +91,12 @@
 }
 
 impl Drop for Rows<'_> {
-    #[inline]
     fn drop(&mut self) {
         self.reset();
     }
 }
 
-/// `F` is used to transform the _streaming_ iterator into a _fallible_
-/// iterator.
+/// `F` is used to tranform the _streaming_ iterator into a _fallible_ iterator.
 #[must_use = "iterators are lazy and do nothing unless consumed"]
 pub struct Map<'stmt, F> {
     rows: Rows<'stmt>,
@@ -126,7 +110,6 @@
     type Error = Error;
     type Item = B;
 
-    #[inline]
     fn next(&mut self) -> Result<Option<B>> {
         match self.rows.next()? {
             Some(v) => Ok(Some((self.f)(v)?)),
@@ -137,27 +120,34 @@
 
 /// An iterator over the mapped resulting rows of a query.
 ///
-/// `F` is used to transform the _streaming_ iterator into a _standard_
-/// iterator.
+/// `F` is used to tranform the _streaming_ iterator into a _standard_ iterator.
 #[must_use = "iterators are lazy and do nothing unless consumed"]
 pub struct MappedRows<'stmt, F> {
     rows: Rows<'stmt>,
     map: F,
 }
 
+impl<'stmt, T, F> MappedRows<'stmt, F>
+where
+    F: FnMut(&Row<'_>) -> Result<T>,
+{
+    pub(crate) fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> {
+        MappedRows { rows, map: f }
+    }
+}
+
 impl<T, F> Iterator for MappedRows<'_, F>
 where
     F: FnMut(&Row<'_>) -> Result<T>,
 {
     type Item = Result<T>;
 
-    #[inline]
     fn next(&mut self) -> Option<Result<T>> {
         let map = &mut self.map;
         self.rows
             .next()
             .transpose()
-            .map(|row_result| row_result.and_then(map))
+            .map(|row_result| row_result.and_then(|row| (map)(&row)))
     }
 }
 
@@ -169,6 +159,15 @@
     map: F,
 }
 
+impl<'stmt, T, E, F> AndThenRows<'stmt, F>
+where
+    F: FnMut(&Row<'_>) -> Result<T, E>,
+{
+    pub(crate) fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> {
+        AndThenRows { rows, map: f }
+    }
+}
+
 impl<T, E, F> Iterator for AndThenRows<'_, F>
 where
     E: convert::From<Error>,
@@ -176,28 +175,27 @@
 {
     type Item = Result<T, E>;
 
-    #[inline]
     fn next(&mut self) -> Option<Self::Item> {
         let map = &mut self.map;
         self.rows
             .next()
             .transpose()
-            .map(|row_result| row_result.map_err(E::from).and_then(map))
+            .map(|row_result| row_result.map_err(E::from).and_then(|row| (map)(&row)))
     }
 }
 
 /// `FallibleStreamingIterator` differs from the standard library's `Iterator`
 /// in two ways:
-/// * each call to `next` (`sqlite3_step`) can fail.
+/// * each call to `next` (sqlite3_step) can fail.
 /// * returned `Row` is valid until `next` is called again or `Statement` is
 ///   reset or finalized.
 ///
 /// While these iterators cannot be used with Rust `for` loops, `while let`
 /// loops offer a similar level of ergonomics:
 /// ```rust,no_run
-/// # use rusqlite::{Result, Statement};
+/// # use rusqlite::{Result, Statement, NO_PARAMS};
 /// fn query(stmt: &mut Statement) -> Result<()> {
-///     let mut rows = stmt.query([])?;
+///     let mut rows = stmt.query(NO_PARAMS)?;
 ///     while let Some(row) = rows.next()? {
 ///         // scan columns value
 ///     }
@@ -208,10 +206,9 @@
     type Error = Error;
     type Item = Row<'stmt>;
 
-    #[inline]
     fn advance(&mut self) -> Result<()> {
-        if let Some(stmt) = self.stmt {
-            match stmt.step() {
+        match self.stmt {
+            Some(ref stmt) => match stmt.step() {
                 Ok(true) => {
                     self.row = Some(Row { stmt });
                     Ok(())
@@ -226,14 +223,14 @@
                     self.row = None;
                     Err(e)
                 }
+            },
+            None => {
+                self.row = None;
+                Ok(())
             }
-        } else {
-            self.row = None;
-            Ok(())
         }
     }
 
-    #[inline]
     fn get(&self) -> Option<&Row<'stmt>> {
         self.row.as_ref()
     }
@@ -249,7 +246,7 @@
     ///
     /// ## Failure
     ///
-    /// Panics if calling [`row.get(idx)`](Row::get) would return an error,
+    /// Panics if calling `row.get(idx)` would return an error,
     /// including:
     ///
     /// * If the underlying SQLite column type is not a valid type as a source
@@ -288,11 +285,20 @@
             ),
             FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
             FromSqlError::Other(err) => {
-                Error::FromSqlConversionFailure(idx, value.data_type(), err)
+                Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
             }
-            FromSqlError::InvalidBlobSize { .. } => {
-                Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
-            }
+            #[cfg(feature = "i128_blob")]
+            FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(
+                idx,
+                self.stmt.column_name_unwrap(idx).into(),
+                value.data_type(),
+            ),
+            #[cfg(feature = "uuid")]
+            FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(
+                idx,
+                self.stmt.column_name_unwrap(idx).into(),
+                value.data_type(),
+            ),
         })
     }
 
@@ -302,7 +308,7 @@
     /// This `ValueRef` is valid only as long as this Row, which is enforced by
     /// it's lifetime. This means that while this method is completely safe,
     /// it can be somewhat difficult to use, and most callers will be better
-    /// served by [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap).
+    /// served by `get` or `get`.
     ///
     /// ## Failure
     ///
@@ -311,7 +317,7 @@
     ///
     /// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
     /// name for this row.
-    pub fn get_ref<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
+    pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
         let idx = idx.idx(self.stmt)?;
         // Narrowing from `ValueRef<'stmt>` (which `self.stmt.value_ref(idx)`
         // returns) to `ValueRef<'a>` is needed because it's only valid until
@@ -326,52 +332,22 @@
     /// This `ValueRef` is valid only as long as this Row, which is enforced by
     /// it's lifetime. This means that while this method is completely safe,
     /// it can be difficult to use, and most callers will be better served by
-    /// [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap).
+    /// `get` or `get`.
     ///
     /// ## Failure
     ///
-    /// Panics if calling [`row.get_ref(idx)`](Row::get_ref) would return an
-    /// error, including:
+    /// Panics if calling `row.get_raw_checked(idx)` would return an error,
+    /// including:
     ///
     /// * If `idx` is outside the range of columns in the returned query.
     /// * If `idx` is not a valid column name for this row.
-    pub fn get_ref_unwrap<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
-        self.get_ref(idx).unwrap()
-    }
-
-    /// Renamed to [`get_ref`](Row::get_ref).
-    #[deprecated = "Use [`get_ref`](Row::get_ref) instead."]
-    #[inline]
-    pub fn get_raw_checked<I: RowIndex>(&self, idx: I) -> Result<ValueRef<'_>> {
-        self.get_ref(idx)
-    }
-
-    /// Renamed to [`get_ref_unwrap`](Row::get_ref_unwrap).
-    #[deprecated = "Use [`get_ref_unwrap`](Row::get_ref_unwrap) instead."]
-    #[inline]
     pub fn get_raw<I: RowIndex>(&self, idx: I) -> ValueRef<'_> {
-        self.get_ref_unwrap(idx)
+        self.get_raw_checked(idx).unwrap()
     }
 }
 
-impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
-    fn as_ref(&self) -> &Statement<'stmt> {
-        self.stmt
-    }
-}
-
-mod sealed {
-    /// This trait exists just to ensure that the only impls of `trait Params`
-    /// that are allowed are ones in this crate.
-    pub trait Sealed {}
-    impl Sealed for usize {}
-    impl Sealed for &str {}
-}
-
 /// A trait implemented by types that can index into columns of a row.
-///
-/// It is only implemented for `usize` and `&str`.
-pub trait RowIndex: sealed::Sealed {
+pub trait RowIndex {
     /// Returns the index of the appropriate column, or `None` if no such
     /// column exists.
     fn idx(&self, stmt: &Statement<'_>) -> Result<usize>;
@@ -432,46 +408,74 @@
 #[cfg(test)]
 mod tests {
     #![allow(clippy::redundant_closure)] // false positives due to lifetime issues; clippy issue #5594
-    use crate::{Connection, Result};
 
     #[test]
-    fn test_try_from_row_for_tuple_1() -> Result<()> {
-        use crate::ToSql;
+    fn test_try_from_row_for_tuple_1() {
+        use crate::{Connection, ToSql};
         use std::convert::TryFrom;
 
-        let conn = Connection::open_in_memory()?;
+        let conn = Connection::open_in_memory().expect("failed to create in-memoory database");
         conn.execute(
             "CREATE TABLE test (a INTEGER)",
-            crate::params_from_iter(std::iter::empty::<&dyn ToSql>()),
-        )?;
-        conn.execute("INSERT INTO test VALUES (42)", [])?;
-        let val = conn.query_row("SELECT a FROM test", [], |row| <(u32,)>::try_from(row))?;
+            std::iter::empty::<&dyn ToSql>(),
+        )
+        .expect("failed to create table");
+        conn.execute(
+            "INSERT INTO test VALUES (42)",
+            std::iter::empty::<&dyn ToSql>(),
+        )
+        .expect("failed to insert value");
+        let val = conn
+            .query_row(
+                "SELECT a FROM test",
+                std::iter::empty::<&dyn ToSql>(),
+                |row| <(u32,)>::try_from(row),
+            )
+            .expect("failed to query row");
         assert_eq!(val, (42,));
-        let fail = conn.query_row("SELECT a FROM test", [], |row| <(u32, u32)>::try_from(row));
+        let fail = conn.query_row(
+            "SELECT a FROM test",
+            std::iter::empty::<&dyn ToSql>(),
+            |row| <(u32, u32)>::try_from(row),
+        );
         assert!(fail.is_err());
-        Ok(())
     }
 
     #[test]
-    fn test_try_from_row_for_tuple_2() -> Result<()> {
+    fn test_try_from_row_for_tuple_2() {
+        use crate::{Connection, ToSql};
         use std::convert::TryFrom;
 
-        let conn = Connection::open_in_memory()?;
-        conn.execute("CREATE TABLE test (a INTEGER, b INTEGER)", [])?;
-        conn.execute("INSERT INTO test VALUES (42, 47)", [])?;
-        let val = conn.query_row("SELECT a, b FROM test", [], |row| {
-            <(u32, u32)>::try_from(row)
-        })?;
+        let conn = Connection::open_in_memory().expect("failed to create in-memoory database");
+        conn.execute(
+            "CREATE TABLE test (a INTEGER, b INTEGER)",
+            std::iter::empty::<&dyn ToSql>(),
+        )
+        .expect("failed to create table");
+        conn.execute(
+            "INSERT INTO test VALUES (42, 47)",
+            std::iter::empty::<&dyn ToSql>(),
+        )
+        .expect("failed to insert value");
+        let val = conn
+            .query_row(
+                "SELECT a, b FROM test",
+                std::iter::empty::<&dyn ToSql>(),
+                |row| <(u32, u32)>::try_from(row),
+            )
+            .expect("failed to query row");
         assert_eq!(val, (42, 47));
-        let fail = conn.query_row("SELECT a, b FROM test", [], |row| {
-            <(u32, u32, u32)>::try_from(row)
-        });
+        let fail = conn.query_row(
+            "SELECT a, b FROM test",
+            std::iter::empty::<&dyn ToSql>(),
+            |row| <(u32, u32, u32)>::try_from(row),
+        );
         assert!(fail.is_err());
-        Ok(())
     }
 
     #[test]
-    fn test_try_from_row_for_tuple_16() -> Result<()> {
+    fn test_try_from_row_for_tuple_16() {
+        use crate::{Connection, ToSql};
         use std::convert::TryFrom;
 
         let create_table = "CREATE TABLE test (
@@ -531,10 +535,18 @@
             u32,
         );
 
-        let conn = Connection::open_in_memory()?;
-        conn.execute(create_table, [])?;
-        conn.execute(insert_values, [])?;
-        let val = conn.query_row("SELECT * FROM test", [], |row| BigTuple::try_from(row))?;
+        let conn = Connection::open_in_memory().expect("failed to create in-memoory database");
+        conn.execute(create_table, std::iter::empty::<&dyn ToSql>())
+            .expect("failed to create table");
+        conn.execute(insert_values, std::iter::empty::<&dyn ToSql>())
+            .expect("failed to insert value");
+        let val = conn
+            .query_row(
+                "SELECT * FROM test",
+                std::iter::empty::<&dyn ToSql>(),
+                |row| BigTuple::try_from(row),
+            )
+            .expect("failed to query row");
         // Debug is not implemented for tuples of 16
         assert_eq!(val.0, 0);
         assert_eq!(val.1, 1);
@@ -554,6 +566,5 @@
         assert_eq!(val.15, 15);
 
         // We don't test one bigger because it's unimplemented
-        Ok(())
     }
 }
diff --git a/src/session.rs b/src/session.rs
index b02d306..97ae3a5 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -1,4 +1,4 @@
-//! [Session Extension](https://sqlite.org/sessionintro.html)
+//! `feature = "session"` [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::{check, error_from_sqlite_code};
+use crate::error::error_from_sqlite_code;
 use crate::ffi;
 use crate::hooks::Action;
 use crate::types::ValueRef;
@@ -19,7 +19,7 @@
 
 // https://sqlite.org/session.html
 
-/// An instance of this object is a session that can be
+/// `feature = "session"` An instance of this object is a session that can be
 /// used to record changes to a database.
 pub struct Session<'conn> {
     phantom: PhantomData<&'conn Connection>,
@@ -29,23 +29,21 @@
 
 impl Session<'_> {
     /// Create a new session object
-    #[inline]
-    pub fn new(db: &Connection) -> Result<Session<'_>> {
+    pub fn new<'conn>(db: &'conn Connection) -> Result<Session<'conn>> {
         Session::new_with_name(db, DatabaseName::Main)
     }
 
     /// Create a new session object
-    #[inline]
     pub fn new_with_name<'conn>(
         db: &'conn Connection,
         name: DatabaseName<'_>,
     ) -> Result<Session<'conn>> {
-        let name = name.as_cstring()?;
+        let name = name.to_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,56 +107,56 @@
             None
         };
         let table = table.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
-        check(unsafe { ffi::sqlite3session_attach(self.s, table) })
+        unsafe { check!(ffi::sqlite3session_attach(self.s, table)) };
+        Ok(())
     }
 
     /// Generate a Changeset
     pub fn changeset(&mut self) -> Result<Changeset> {
         let mut n = 0;
         let mut cs: *mut c_void = ptr::null_mut();
-        check(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) })?;
+        check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) });
         Ok(Changeset { cs, n })
     }
 
     /// Write the set of changes represented by this session to `output`.
-    #[inline]
     pub fn changeset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
         let output_ref = &output;
-        check(unsafe {
+        check!(unsafe {
             ffi::sqlite3session_changeset_strm(
                 self.s,
                 Some(x_output),
                 output_ref as *const &mut dyn Write as *mut c_void,
             )
-        })
+        });
+        Ok(())
     }
 
     /// Generate a Patchset
-    #[inline]
     pub fn patchset(&mut self) -> Result<Changeset> {
         let mut n = 0;
         let mut ps: *mut c_void = ptr::null_mut();
-        check(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) })?;
+        check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) });
         // TODO Validate: same struct
         Ok(Changeset { cs: ps, n })
     }
 
     /// Write the set of patches represented by this session to `output`.
-    #[inline]
     pub fn patchset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
         let output_ref = &output;
-        check(unsafe {
+        check!(unsafe {
             ffi::sqlite3session_patchset_strm(
                 self.s,
                 Some(x_output),
                 output_ref as *const &mut dyn Write as *mut c_void,
             )
-        })
+        });
+        Ok(())
     }
 
     /// Load the difference between tables.
     pub fn diff(&mut self, from: DatabaseName<'_>, table: &str) -> Result<()> {
-        let from = from.as_cstring()?;
+        let from = from.to_cstring()?;
         let table = str_to_cstring(table)?;
         let table = table.as_ptr();
         unsafe {
@@ -176,19 +174,16 @@
     }
 
     /// Test if a changeset has recorded any changes
-    #[inline]
     pub fn is_empty(&self) -> bool {
         unsafe { ffi::sqlite3session_isempty(self.s) != 0 }
     }
 
     /// Query the current state of the session
-    #[inline]
     pub fn is_enabled(&self) -> bool {
         unsafe { ffi::sqlite3session_enable(self.s, -1) != 0 }
     }
 
     /// Enable or disable the recording of changes
-    #[inline]
     pub fn set_enabled(&mut self, enabled: bool) {
         unsafe {
             ffi::sqlite3session_enable(self.s, if enabled { 1 } else { 0 });
@@ -196,13 +191,11 @@
     }
 
     /// Query the current state of the indirect flag
-    #[inline]
     pub fn is_indirect(&self) -> bool {
         unsafe { ffi::sqlite3session_indirect(self.s, -1) != 0 }
     }
 
     /// Set or clear the indirect change flag
-    #[inline]
     pub fn set_indirect(&mut self, indirect: bool) {
         unsafe {
             ffi::sqlite3session_indirect(self.s, if indirect { 1 } else { 0 });
@@ -211,7 +204,6 @@
 }
 
 impl Drop for Session<'_> {
-    #[inline]
     fn drop(&mut self) {
         if self.filter.is_some() {
             self.table_filter(None::<fn(&str) -> bool>);
@@ -220,23 +212,22 @@
     }
 }
 
-/// Invert a changeset
-#[inline]
+/// `feature = "session"` Invert a changeset
 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(())
 }
 
-/// Combine two changesets
-#[inline]
+/// `feature = "session"` Combine two changesets
 pub fn concat_strm(
     input_a: &mut dyn Read,
     input_b: &mut dyn Read,
@@ -245,7 +236,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,
@@ -254,10 +245,11 @@
             Some(x_output),
             output_ref as *const &mut dyn Write as *mut c_void,
         )
-    })
+    });
+    Ok(())
 }
 
-/// Changeset or Patchset
+/// `feature = "session"` Changeset or Patchset
 pub struct Changeset {
     cs: *mut c_void,
     n: c_int,
@@ -265,21 +257,19 @@
 
 impl Changeset {
     /// Invert a changeset
-    #[inline]
     pub fn invert(&self) -> Result<Changeset> {
         let mut n = 0;
         let mut cs = ptr::null_mut();
-        check(unsafe {
+        check!(unsafe {
             ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs as *mut *mut _)
-        })?;
+        });
         Ok(Changeset { cs, n })
     }
 
     /// Create an iterator to traverse a changeset
-    #[inline]
     pub fn iter(&self) -> Result<ChangesetIter<'_>> {
         let mut it = ptr::null_mut();
-        check(unsafe { ffi::sqlite3changeset_start(&mut it as *mut *mut _, self.n, self.cs) })?;
+        check!(unsafe { ffi::sqlite3changeset_start(&mut it as *mut *mut _, self.n, self.cs) });
         Ok(ChangesetIter {
             phantom: PhantomData,
             it,
@@ -288,19 +278,17 @@
     }
 
     /// Concatenate two changeset objects
-    #[inline]
     pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
         let mut n = 0;
         let mut cs = ptr::null_mut();
-        check(unsafe {
+        check!(unsafe {
             ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs as *mut *mut _)
-        })?;
+        });
         Ok(Changeset { cs, n })
     }
 }
 
 impl Drop for Changeset {
-    #[inline]
     fn drop(&mut self) {
         unsafe {
             ffi::sqlite3_free(self.cs);
@@ -308,7 +296,7 @@
     }
 }
 
-/// Cursor for iterating over the elements of a changeset
+/// `feature = "session"` Cursor for iterating over the elements of a changeset
 /// or patchset.
 pub struct ChangesetIter<'changeset> {
     phantom: PhantomData<&'changeset Changeset>,
@@ -318,16 +306,15 @@
 
 impl ChangesetIter<'_> {
     /// Create an iterator on `input`
-    #[inline]
     pub fn start_strm<'input>(input: &&'input mut dyn Read) -> Result<ChangesetIter<'input>> {
         let mut it = ptr::null_mut();
-        check(unsafe {
+        check!(unsafe {
             ffi::sqlite3changeset_start_strm(
                 &mut it as *mut *mut _,
                 Some(x_input),
                 input as *const &mut dyn Read as *mut c_void,
             )
-        })?;
+        });
         Ok(ChangesetIter {
             phantom: PhantomData,
             it,
@@ -340,7 +327,6 @@
     type Error = crate::error::Error;
     type Item = ChangesetItem;
 
-    #[inline]
     fn advance(&mut self) -> Result<()> {
         let rc = unsafe { ffi::sqlite3changeset_next(self.it) };
         match rc {
@@ -356,13 +342,12 @@
         }
     }
 
-    #[inline]
     fn get(&self) -> Option<&ChangesetItem> {
         self.item.as_ref()
     }
 }
 
-/// Operation
+/// `feature = "session"`
 pub struct Operation<'item> {
     table_name: &'item str,
     number_of_columns: i32,
@@ -372,32 +357,27 @@
 
 impl Operation<'_> {
     /// Returns the table name.
-    #[inline]
     pub fn table_name(&self) -> &str {
         self.table_name
     }
 
     /// Returns the number of columns in table
-    #[inline]
     pub fn number_of_columns(&self) -> i32 {
         self.number_of_columns
     }
 
     /// Returns the action code.
-    #[inline]
     pub fn code(&self) -> Action {
         self.code
     }
 
     /// Returns `true` for an 'indirect' change.
-    #[inline]
     pub fn indirect(&self) -> bool {
         self.indirect
     }
 }
 
 impl Drop for ChangesetIter<'_> {
-    #[inline]
     fn drop(&mut self) {
         unsafe {
             ffi::sqlite3changeset_finalize(self.it);
@@ -405,9 +385,8 @@
     }
 }
 
-/// An item passed to a conflict-handler by
-/// [`Connection::apply`](crate::Connection::apply), or an item generated by
-/// [`ChangesetIter::next`](ChangesetIter::next).
+/// `feature = "session"` An item passed to a conflict-handler by
+/// `Connection::apply`, or an item generated by `ChangesetIter::next`.
 // TODO enum ? Delete, Insert, Update, ...
 pub struct ChangesetItem {
     it: *mut ffi::sqlite3_changeset_iter,
@@ -418,15 +397,14 @@
     ///
     /// May only be called with an `SQLITE_CHANGESET_DATA` or
     /// `SQLITE_CHANGESET_CONFLICT` conflict handler callback.
-    #[inline]
     pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
         unsafe {
             let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
-            check(ffi::sqlite3changeset_conflict(
+            check!(ffi::sqlite3changeset_conflict(
                 self.it,
                 col as i32,
                 &mut p_value,
-            ))?;
+            ));
             Ok(ValueRef::from_value(p_value))
         }
     }
@@ -435,11 +413,10 @@
     ///
     /// May only be called with an `SQLITE_CHANGESET_FOREIGN_KEY` conflict
     /// handler callback.
-    #[inline]
     pub fn fk_conflicts(&self) -> Result<i32> {
         unsafe {
             let mut p_out = 0;
-            check(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out))?;
+            check!(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out));
             Ok(p_out)
         }
     }
@@ -448,11 +425,10 @@
     ///
     /// May only be called if the type of change is either `SQLITE_UPDATE` or
     /// `SQLITE_INSERT`.
-    #[inline]
     pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
         unsafe {
             let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
-            check(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value))?;
+            check!(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value,));
             Ok(ValueRef::from_value(p_value))
         }
     }
@@ -461,30 +437,28 @@
     ///
     /// May only be called if the type of change is either `SQLITE_DELETE` or
     /// `SQLITE_UPDATE`.
-    #[inline]
     pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
         unsafe {
             let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
-            check(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value))?;
+            check!(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value,));
             Ok(ValueRef::from_value(p_value))
         }
     }
 
     /// Obtain the current operation
-    #[inline]
     pub fn op(&self) -> Result<Operation<'_>> {
         let mut number_of_columns = 0;
         let mut code = 0;
         let mut indirect = 0;
         let tab = unsafe {
             let mut pz_tab: *const c_char = ptr::null();
-            check(ffi::sqlite3changeset_op(
+            check!(ffi::sqlite3changeset_op(
                 self.it,
                 &mut pz_tab,
                 &mut number_of_columns,
                 &mut code,
-                &mut indirect,
-            ))?;
+                &mut indirect
+            ));
             CStr::from_ptr(pz_tab)
         };
         let table_name = tab.to_str()?;
@@ -497,22 +471,21 @@
     }
 
     /// Obtain the primary key definition of a table
-    #[inline]
     pub fn pk(&self) -> Result<&[u8]> {
         let mut number_of_columns = 0;
         unsafe {
             let mut pks: *mut c_uchar = ptr::null_mut();
-            check(ffi::sqlite3changeset_pk(
+            check!(ffi::sqlite3changeset_pk(
                 self.it,
                 &mut pks,
-                &mut number_of_columns,
-            ))?;
+                &mut number_of_columns
+            ));
             Ok(from_raw_parts(pks, number_of_columns as usize))
         }
     }
 }
 
-/// Used to combine two or more changesets or
+/// `feature = "session"` Used to combine two or more changesets or
 /// patchsets
 pub struct Changegroup {
     cg: *mut ffi::sqlite3_changegroup,
@@ -520,57 +493,54 @@
 
 impl Changegroup {
     /// Create a new change group.
-    #[inline]
     pub fn new() -> Result<Self> {
         let mut cg = ptr::null_mut();
-        check(unsafe { ffi::sqlite3changegroup_new(&mut cg) })?;
+        check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) });
         Ok(Changegroup { cg })
     }
 
     /// Add a changeset
-    #[inline]
     pub fn add(&mut self, cs: &Changeset) -> Result<()> {
-        check(unsafe { ffi::sqlite3changegroup_add(self.cg, cs.n, cs.cs) })
+        check!(unsafe { ffi::sqlite3changegroup_add(self.cg, cs.n, cs.cs) });
+        Ok(())
     }
 
     /// Add a changeset read from `input` to this change group.
-    #[inline]
     pub fn add_stream(&mut self, input: &mut dyn Read) -> Result<()> {
         let input_ref = &input;
-        check(unsafe {
+        check!(unsafe {
             ffi::sqlite3changegroup_add_strm(
                 self.cg,
                 Some(x_input),
                 input_ref as *const &mut dyn Read as *mut c_void,
             )
-        })
+        });
+        Ok(())
     }
 
     /// Obtain a composite Changeset
-    #[inline]
     pub fn output(&mut self) -> Result<Changeset> {
         let mut n = 0;
         let mut output: *mut c_void = ptr::null_mut();
-        check(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) })?;
+        check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) });
         Ok(Changeset { cs: output, n })
     }
 
     /// Write the combined set of changes to `output`.
-    #[inline]
     pub fn output_strm(&mut self, output: &mut dyn Write) -> Result<()> {
         let output_ref = &output;
-        check(unsafe {
+        check!(unsafe {
             ffi::sqlite3changegroup_output_strm(
                 self.cg,
                 Some(x_output),
                 output_ref as *const &mut dyn Write as *mut c_void,
             )
-        })
+        });
+        Ok(())
     }
 }
 
 impl Drop for Changegroup {
-    #[inline]
     fn drop(&mut self) {
         unsafe {
             ffi::sqlite3changegroup_delete(self.cg);
@@ -579,7 +549,7 @@
 }
 
 impl Connection {
-    /// Apply a changeset to a database
+    /// `feature = "session"` 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,
@@ -589,7 +559,7 @@
 
         let filtered = filter.is_some();
         let tuple = &mut (filter, conflict);
-        check(unsafe {
+        check!(unsafe {
             if filtered {
                 ffi::sqlite3changeset_apply(
                     db,
@@ -609,10 +579,11 @@
                     tuple as *mut (Option<F>, C) as *mut c_void,
                 )
             }
-        })
+        });
+        Ok(())
     }
 
-    /// Apply a changeset to a database
+    /// `feature = "session"` Apply a changeset to a database
     pub fn apply_strm<F, C>(
         &self,
         input: &mut dyn Read,
@@ -628,7 +599,7 @@
 
         let filtered = filter.is_some();
         let tuple = &mut (filter, conflict);
-        check(unsafe {
+        check!(unsafe {
             if filtered {
                 ffi::sqlite3changeset_apply_strm(
                     db,
@@ -648,17 +619,17 @@
                     tuple as *mut (Option<F>, C) as *mut c_void,
                 )
             }
-        })
+        });
+        Ok(())
     }
 }
 
-/// Constants passed to the conflict handler
+/// `feature = "session"` Constants passed to the conflict handler
 /// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_CONFLICT) for details.
 #[allow(missing_docs)]
 #[repr(i32)]
 #[derive(Debug, PartialEq)]
 #[non_exhaustive]
-#[allow(clippy::upper_case_acronyms)]
 pub enum ConflictType {
     UNKNOWN = -1,
     SQLITE_CHANGESET_DATA = ffi::SQLITE_CHANGESET_DATA,
@@ -680,13 +651,12 @@
     }
 }
 
-/// Constants returned by the conflict handler
+/// `feature = "session"` Constants returned by the conflict handler
 /// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_ABORT) for details.
 #[allow(missing_docs)]
 #[repr(i32)]
 #[derive(Debug, PartialEq)]
 #[non_exhaustive]
-#[allow(clippy::upper_case_acronyms)]
 pub enum ConflictAction {
     SQLITE_CHANGESET_OMIT = ffi::SQLITE_CHANGESET_OMIT,
     SQLITE_CHANGESET_REPLACE = ffi::SQLITE_CHANGESET_REPLACE,
@@ -774,79 +744,84 @@
 
     use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session};
     use crate::hooks::Action;
-    use crate::{Connection, Result};
+    use crate::Connection;
 
-    fn one_changeset() -> Result<Changeset> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
+    fn one_changeset() -> Changeset {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
+            .unwrap();
 
-        let mut session = Session::new(&db)?;
+        let mut session = Session::new(&db).unwrap();
         assert!(session.is_empty());
 
-        session.attach(None)?;
-        db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
+        session.attach(None).unwrap();
+        db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
+            .unwrap();
 
-        session.changeset()
+        session.changeset().unwrap()
     }
 
-    fn one_changeset_strm() -> Result<Vec<u8>> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
+    fn one_changeset_strm() -> Vec<u8> {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
+            .unwrap();
 
-        let mut session = Session::new(&db)?;
+        let mut session = Session::new(&db).unwrap();
         assert!(session.is_empty());
 
-        session.attach(None)?;
-        db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
+        session.attach(None).unwrap();
+        db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
+            .unwrap();
 
         let mut output = Vec::new();
-        session.changeset_strm(&mut output)?;
-        Ok(output)
+        session.changeset_strm(&mut output).unwrap();
+        output
     }
 
     #[test]
-    fn test_changeset() -> Result<()> {
-        let changeset = one_changeset()?;
-        let mut iter = changeset.iter()?;
-        let item = iter.next()?;
+    fn test_changeset() {
+        let changeset = one_changeset();
+        let mut iter = changeset.iter().unwrap();
+        let item = iter.next().unwrap();
         assert!(item.is_some());
 
         let item = item.unwrap();
-        let op = item.op()?;
+        let op = item.op().unwrap();
         assert_eq!("foo", op.table_name());
         assert_eq!(1, op.number_of_columns());
         assert_eq!(Action::SQLITE_INSERT, op.code());
-        assert!(!op.indirect());
+        assert_eq!(false, op.indirect());
 
-        let pk = item.pk()?;
+        let pk = item.pk().unwrap();
         assert_eq!(&[1], pk);
 
-        let new_value = item.new_value(0)?;
+        let new_value = item.new_value(0).unwrap();
         assert_eq!(Ok("bar"), new_value.as_str());
-        Ok(())
     }
 
     #[test]
-    fn test_changeset_strm() -> Result<()> {
-        let output = one_changeset_strm()?;
+    fn test_changeset_strm() {
+        let output = one_changeset_strm();
         assert!(!output.is_empty());
         assert_eq!(14, output.len());
 
         let input: &mut dyn Read = &mut output.as_slice();
-        let mut iter = ChangesetIter::start_strm(&input)?;
-        let item = iter.next()?;
+        let mut iter = ChangesetIter::start_strm(&input).unwrap();
+        let item = iter.next().unwrap();
         assert!(item.is_some());
-        Ok(())
     }
 
     #[test]
-    fn test_changeset_apply() -> Result<()> {
-        let changeset = one_changeset()?;
+    fn test_changeset_apply() {
+        let changeset = one_changeset();
 
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
+            .unwrap();
 
-        static CALLED: AtomicBool = AtomicBool::new(false);
+        lazy_static::lazy_static! {
+            static ref CALLED: AtomicBool = AtomicBool::new(false);
+        }
         db.apply(
             &changeset,
             None::<fn(&str) -> bool>,
@@ -854,12 +829,15 @@
                 CALLED.store(true, Ordering::Relaxed);
                 ConflictAction::SQLITE_CHANGESET_OMIT
             },
-        )?;
+        )
+        .unwrap();
 
         assert!(!CALLED.load(Ordering::Relaxed));
-        let check = db.query_row("SELECT 1 FROM foo WHERE t = ?", ["bar"], |row| {
-            row.get::<_, i32>(0)
-        })?;
+        let check = db
+            .query_row("SELECT 1 FROM foo WHERE t = ?", &["bar"], |row| {
+                row.get::<_, i32>(0)
+            })
+            .unwrap();
         assert_eq!(1, check);
 
         // conflict expected when same changeset applied again on the same db
@@ -873,66 +851,68 @@
                 assert_eq!(Ok("bar"), conflict.as_str());
                 ConflictAction::SQLITE_CHANGESET_OMIT
             },
-        )?;
+        )
+        .unwrap();
         assert!(CALLED.load(Ordering::Relaxed));
-        Ok(())
     }
 
     #[test]
-    fn test_changeset_apply_strm() -> Result<()> {
-        let output = one_changeset_strm()?;
+    fn test_changeset_apply_strm() {
+        let output = one_changeset_strm();
 
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
+            .unwrap();
 
         let mut input = output.as_slice();
         db.apply_strm(
             &mut input,
             None::<fn(&str) -> bool>,
             |_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
-        )?;
+        )
+        .unwrap();
 
-        let check = db.query_row("SELECT 1 FROM foo WHERE t = ?", ["bar"], |row| {
-            row.get::<_, i32>(0)
-        })?;
+        let check = db
+            .query_row("SELECT 1 FROM foo WHERE t = ?", &["bar"], |row| {
+                row.get::<_, i32>(0)
+            })
+            .unwrap();
         assert_eq!(1, check);
-        Ok(())
     }
 
     #[test]
-    fn test_session_empty() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")?;
+    fn test_session_empty() {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
+            .unwrap();
 
-        let mut session = Session::new(&db)?;
+        let mut session = Session::new(&db).unwrap();
         assert!(session.is_empty());
 
-        session.attach(None)?;
-        db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
+        session.attach(None).unwrap();
+        db.execute("INSERT INTO foo (t) VALUES (?);", &["bar"])
+            .unwrap();
 
         assert!(!session.is_empty());
-        Ok(())
     }
 
     #[test]
-    fn test_session_set_enabled() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_session_set_enabled() {
+        let db = Connection::open_in_memory().unwrap();
 
-        let mut session = Session::new(&db)?;
+        let mut session = Session::new(&db).unwrap();
         assert!(session.is_enabled());
         session.set_enabled(false);
         assert!(!session.is_enabled());
-        Ok(())
     }
 
     #[test]
-    fn test_session_set_indirect() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_session_set_indirect() {
+        let db = Connection::open_in_memory().unwrap();
 
-        let mut session = Session::new(&db)?;
+        let mut session = Session::new(&db).unwrap();
         assert!(!session.is_indirect());
         session.set_indirect(true);
         assert!(session.is_indirect());
-        Ok(())
     }
 }
diff --git a/src/statement.rs b/src/statement.rs
index 60abd90..648a9b7 100644
--- a/src/statement.rs
+++ b/src/statement.rs
@@ -8,7 +8,7 @@
 use super::ffi;
 use super::{len_as_c_int, str_for_sqlite};
 use super::{
-    AndThenRows, Connection, Error, MappedRows, Params, RawStatement, Result, Row, Rows, ValueRef,
+    AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
 };
 use crate::types::{ToSql, ToSqlOutput};
 #[cfg(feature = "array")]
@@ -28,49 +28,14 @@
     ///
     /// ## Example
     ///
-    /// ### Use with positional parameters
-    ///
     /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result, params};
+    /// # use rusqlite::{Connection, Result};
     /// fn update_rows(conn: &Connection) -> Result<()> {
     ///     let mut stmt = conn.prepare("UPDATE foo SET bar = 'baz' WHERE qux = ?")?;
-    ///     // The `rusqlite::params!` macro is mostly useful when the parameters do not
-    ///     // all have the same type, or if there are more than 32 parameters
-    ///     // at once.
-    ///     stmt.execute(params![1i32])?;
-    ///     // However, it's not required, many cases are fine as:
-    ///     stmt.execute(&[&2i32])?;
-    ///     // Or even:
-    ///     stmt.execute([2i32])?;
-    ///     Ok(())
-    /// }
-    /// ```
     ///
-    /// ### Use with named parameters
+    ///     stmt.execute(&[1i32])?;
+    ///     stmt.execute(&[2i32])?;
     ///
-    /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result, named_params};
-    /// fn insert(conn: &Connection) -> Result<()> {
-    ///     let mut stmt = conn.prepare("INSERT INTO test (key, value) VALUES (:key, :value)")?;
-    ///     // The `rusqlite::named_params!` macro (like `params!`) is useful for heterogeneous
-    ///     // sets of parameters (where all parameters are not the same type), or for queries
-    ///     // with many (more than 32) statically known parameters.
-    ///     stmt.execute(named_params! { ":key": "one", ":val": 2 })?;
-    ///     // However, named parameters can also be passed like:
-    ///     stmt.execute(&[(":key", "three"), (":val", "four")])?;
-    ///     // Or even: (note that a &T is required for the value type, currently)
-    ///     stmt.execute(&[(":key", &100), (":val", &200)])?;
-    ///     Ok(())
-    /// }
-    /// ```
-    ///
-    /// ### Use without parameters
-    ///
-    /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result, params};
-    /// fn delete_all(conn: &Connection) -> Result<()> {
-    ///     let mut stmt = conn.prepare("DELETE FROM users")?;
-    ///     stmt.execute([])?;
     ///     Ok(())
     /// }
     /// ```
@@ -80,51 +45,73 @@
     /// Will return `Err` if binding parameters fails, the executed statement
     /// returns rows (in which case `query` should be used instead), or the
     /// underlying SQLite call fails.
-    #[inline]
-    pub fn execute<P: Params>(&mut self, params: P) -> Result<usize> {
-        params.__bind_in(self)?;
+    pub fn execute<P>(&mut self, params: P) -> Result<usize>
+    where
+        P: IntoIterator,
+        P::Item: ToSql,
+    {
+        self.bind_parameters(params)?;
         self.execute_with_bound_parameters()
     }
 
-    /// Execute the prepared statement with named parameter(s).
-    ///
-    /// Note: This function is deprecated in favor of [`Statement::execute`],
-    /// which can now take named parameters directly.
-    ///
-    /// If any parameters that were in the prepared statement are not included
-    /// in `params`, they will continue to use the most-recently bound value
-    /// from a previous call to `execute_named`, or `NULL` if they have never
-    /// been bound.
+    /// Execute the prepared statement with named parameter(s). If any
+    /// parameters that were in the prepared statement are not included in
+    /// `params`, they will continue to use the most-recently bound value
+    /// from a previous call to `execute_named`, or `NULL` if they have
+    /// never been bound.
     ///
     /// On success, returns the number of rows that were changed or inserted or
     /// deleted (via `sqlite3_changes`).
     ///
+    /// ## Example
+    ///
+    /// ```rust,no_run
+    /// # use rusqlite::{Connection, Result};
+    /// fn insert(conn: &Connection) -> Result<usize> {
+    ///     let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?;
+    ///     stmt.execute_named(&[(":name", &"one")])
+    /// }
+    /// ```
+    ///
+    /// Note, the `named_params` macro is provided for syntactic convenience,
+    /// and so the above example could also be written as:
+    ///
+    /// ```rust,no_run
+    /// # use rusqlite::{Connection, Result, named_params};
+    /// fn insert(conn: &Connection) -> Result<usize> {
+    ///     let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?;
+    ///     stmt.execute_named(named_params!{":name": "one"})
+    /// }
+    /// ```
+    ///
     /// # Failure
     ///
     /// Will return `Err` if binding parameters fails, the executed statement
     /// returns rows (in which case `query` should be used instead), or the
     /// underlying SQLite call fails.
-    #[deprecated = "You can use `execute` with named params now."]
-    #[inline]
     pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
-        self.execute(params)
+        self.bind_parameters_named(params)?;
+        self.execute_with_bound_parameters()
     }
 
     /// Execute an INSERT and return the ROWID.
     ///
     /// # Note
     ///
-    /// This function is a convenience wrapper around
-    /// [`execute()`](Statement::execute) intended for queries that insert a
-    /// single item. It is possible to misuse this function in a way that it
-    /// cannot detect, such as by calling it on a statement which _updates_
-    /// a single item rather than inserting one. Please don't do that.
+    /// This function is a convenience wrapper around `execute()` intended for
+    /// queries that insert a single item. It is possible to misuse this
+    /// function in a way that it cannot detect, such as by calling it on a
+    /// statement which _updates_ a single
+    /// item rather than inserting one. Please don't do that.
     ///
     /// # Failure
     ///
     /// Will return `Err` if no row is inserted or many rows are inserted.
-    #[inline]
-    pub fn insert<P: Params>(&mut self, params: P) -> Result<i64> {
+    pub fn insert<P>(&mut self, params: P) -> Result<i64>
+    where
+        P: IntoIterator,
+        P::Item: ToSql,
+    {
         let changes = self.execute(params)?;
         match changes {
             1 => Ok(self.conn.last_insert_rowid()),
@@ -135,20 +122,17 @@
     /// Execute the prepared statement, returning a handle to the resulting
     /// rows.
     ///
-    /// 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.
+    /// Due to lifetime restricts, the rows handle returned by `query` does not
+    /// implement the `Iterator` trait. Consider using `query_map` or
+    /// `query_and_then` instead, which do.
     ///
     /// ## Example
     ///
-    /// ### Use without parameters
-    ///
     /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result};
+    /// # use rusqlite::{Connection, Result, NO_PARAMS};
     /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
     ///     let mut stmt = conn.prepare("SELECT name FROM people")?;
-    ///     let mut rows = stmt.query([])?;
+    ///     let mut rows = stmt.query(NO_PARAMS)?;
     ///
     ///     let mut names = Vec::new();
     ///     while let Some(row) = rows.next()? {
@@ -159,41 +143,32 @@
     /// }
     /// ```
     ///
-    /// ### Use with positional parameters
+    /// ## Failure
     ///
-    /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result};
-    /// fn query(conn: &Connection, name: &str) -> Result<()> {
-    ///     let mut stmt = conn.prepare("SELECT * FROM test where name = ?")?;
-    ///     let mut rows = stmt.query(rusqlite::params![name])?;
-    ///     while let Some(row) = rows.next()? {
-    ///         // ...
-    ///     }
-    ///     Ok(())
-    /// }
-    /// ```
+    /// Will return `Err` if binding parameters fails.
+    pub fn query<P>(&mut self, params: P) -> Result<Rows<'_>>
+    where
+        P: IntoIterator,
+        P::Item: ToSql,
+    {
+        self.check_readonly()?;
+        self.bind_parameters(params)?;
+        Ok(Rows::new(self))
+    }
+
+    /// Execute the prepared statement with named parameter(s), returning a
+    /// handle for the resulting rows. If any parameters that were in the
+    /// prepared statement are not included in `params`, they will continue
+    /// to use the most-recently bound value from a previous
+    /// call to `query_named`, or `NULL` if they have never been bound.
     ///
-    /// Or, equivalently (but without the [`params!`] macro).
-    ///
-    /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result};
-    /// fn query(conn: &Connection, name: &str) -> Result<()> {
-    ///     let mut stmt = conn.prepare("SELECT * FROM test where name = ?")?;
-    ///     let mut rows = stmt.query([name])?;
-    ///     while let Some(row) = rows.next()? {
-    ///         // ...
-    ///     }
-    ///     Ok(())
-    /// }
-    /// ```
-    ///
-    /// ### Use with named parameters
+    /// ## Example
     ///
     /// ```rust,no_run
     /// # use rusqlite::{Connection, Result};
     /// fn query(conn: &Connection) -> Result<()> {
     ///     let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
-    ///     let mut rows = stmt.query(&[(":name", "one")])?;
+    ///     let mut rows = stmt.query_named(&[(":name", &"one")])?;
     ///     while let Some(row) = rows.next()? {
     ///         // ...
     ///     }
@@ -208,7 +183,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(named_params!{ ":name": "one" })?;
     ///     while let Some(row) = rows.next()? {
     ///         // ...
     ///     }
@@ -216,51 +191,25 @@
     /// }
     /// ```
     ///
-    /// ## Failure
-    ///
-    /// Will return `Err` if binding parameters fails.
-    #[inline]
-    pub fn query<P: Params>(&mut self, params: P) -> Result<Rows<'_>> {
-        params.__bind_in(self)?;
-        Ok(Rows::new(self))
-    }
-
-    /// Execute the prepared statement with named parameter(s), returning a
-    /// handle for the resulting rows.
-    ///
-    /// Note: This function is deprecated in favor of [`Statement::query`],
-    /// which can now take named parameters directly.
-    ///
-    /// If any parameters that were in the prepared statement are not included
-    /// in `params`, they will continue to use the most-recently bound value
-    /// from a previous call to `query_named`, or `NULL` if they have never been
-    /// bound.
-    ///
     /// # Failure
     ///
     /// Will return `Err` if binding parameters fails.
-    #[deprecated = "You can use `query` with named params now."]
     pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'_>> {
-        self.query(params)
+        self.check_readonly()?;
+        self.bind_parameters_named(params)?;
+        Ok(Rows::new(self))
     }
 
     /// Executes the prepared statement and maps a function over the resulting
     /// rows, returning an iterator over the mapped function results.
     ///
-    /// `f` is used to transform the _streaming_ iterator into a _standard_
-    /// iterator.
-    ///
-    /// This is equivalent to `stmt.query(params)?.mapped(f)`.
-    ///
     /// ## Example
     ///
-    /// ### Use with positional params
-    ///
     /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result};
+    /// # use rusqlite::{Connection, Result, NO_PARAMS};
     /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
     ///     let mut stmt = conn.prepare("SELECT name FROM people")?;
-    ///     let rows = stmt.query_map([], |row| row.get(0))?;
+    ///     let rows = stmt.query_map(NO_PARAMS, |row| row.get(0))?;
     ///
     ///     let mut names = Vec::new();
     ///     for name_result in rows {
@@ -270,53 +219,51 @@
     ///     Ok(names)
     /// }
     /// ```
+    /// `f` is used to tranform the _streaming_ iterator into a _standard_
+    /// iterator.
     ///
-    /// ### Use with named params
-    ///
-    /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result};
-    /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
-    ///     let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
-    ///     let rows = stmt.query_map(&[(":id", &"one")], |row| row.get(0))?;
-    ///
-    ///     let mut names = Vec::new();
-    ///     for name_result in rows {
-    ///         names.push(name_result?);
-    ///     }
-    ///
-    ///     Ok(names)
-    /// }
-    /// ```
     /// ## Failure
     ///
     /// Will return `Err` if binding parameters fails.
     pub fn query_map<T, P, F>(&mut self, params: P, f: F) -> Result<MappedRows<'_, F>>
     where
-        P: Params,
+        P: IntoIterator,
+        P::Item: ToSql,
         F: FnMut(&Row<'_>) -> Result<T>,
     {
-        self.query(params).map(|rows| rows.mapped(f))
+        let rows = self.query(params)?;
+        Ok(MappedRows::new(rows, f))
     }
 
     /// Execute the prepared statement with named parameter(s), returning an
     /// iterator over the result of calling the mapping function over the
-    /// query's rows.
-    ///
-    /// Note: This function is deprecated in favor of [`Statement::query_map`],
-    /// which can now take named parameters directly.
-    ///
-    /// If any parameters that were in the prepared statement
+    /// query's rows. If any parameters that were in the prepared statement
     /// are not included in `params`, they will continue to use the
     /// most-recently bound value from a previous call to `query_named`,
     /// or `NULL` if they have never been bound.
     ///
-    /// `f` is used to transform the _streaming_ iterator into a _standard_
+    /// ## Example
+    ///
+    /// ```rust,no_run
+    /// # use rusqlite::{Connection, Result};
+    /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
+    ///     let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
+    ///     let rows = stmt.query_map_named(&[(":id", &"one")], |row| row.get(0))?;
+    ///
+    ///     let mut names = Vec::new();
+    ///     for name_result in rows {
+    ///         names.push(name_result?);
+    ///     }
+    ///
+    ///     Ok(names)
+    /// }
+    /// ```
+    /// `f` is used to tranform the _streaming_ iterator into a _standard_
     /// iterator.
     ///
     /// ## Failure
     ///
     /// Will return `Err` if binding parameters fails.
-    #[deprecated = "You can use `query_map` with named params now."]
     pub fn query_map_named<T, F>(
         &mut self,
         params: &[(&str, &dyn ToSql)],
@@ -325,19 +272,38 @@
     where
         F: FnMut(&Row<'_>) -> Result<T>,
     {
-        self.query_map(params, f)
+        let rows = self.query_named(params)?;
+        Ok(MappedRows::new(rows, f))
     }
 
     /// Executes the prepared statement and maps a function over the resulting
     /// rows, where the function returns a `Result` with `Error` type
     /// implementing `std::convert::From<Error>` (so errors can be unified).
     ///
-    /// This is equivalent to `stmt.query(params)?.and_then(f)`.
+    /// # Failure
+    ///
+    /// Will return `Err` if binding parameters fails.
+    pub fn query_and_then<T, E, P, F>(&mut self, params: P, f: F) -> Result<AndThenRows<'_, F>>
+    where
+        P: IntoIterator,
+        P::Item: ToSql,
+        E: convert::From<Error>,
+        F: FnMut(&Row<'_>) -> Result<T, E>,
+    {
+        let rows = self.query(params)?;
+        Ok(AndThenRows::new(rows, f))
+    }
+
+    /// Execute the prepared statement with named parameter(s), returning an
+    /// iterator over the result of calling the mapping function over the
+    /// query's rows. If any parameters that were in the prepared statement
+    /// are not included in
+    /// `params`, they will
+    /// continue to use the most-recently bound value from a previous call
+    /// to `query_named`, or `NULL` if they have never been bound.
     ///
     /// ## Example
     ///
-    /// ### Use with named params
-    ///
     /// ```rust,no_run
     /// # use rusqlite::{Connection, Result};
     /// struct Person {
@@ -346,12 +312,13 @@
     ///
     /// fn name_to_person(name: String) -> Result<Person> {
     ///     // ... check for valid name
-    ///     Ok(Person { name })
+    ///     Ok(Person { name: 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_named(&[(":id", &"one")], |row| name_to_person(row.get(0)?))?;
     ///
     ///     let mut persons = Vec::new();
     ///     for person_result in rows {
@@ -362,53 +329,9 @@
     /// }
     /// ```
     ///
-    /// ### Use with positional params
-    ///
-    /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result};
-    /// fn get_names(conn: &Connection) -> Result<Vec<String>> {
-    ///     let mut stmt = conn.prepare("SELECT name FROM people WHERE id = ?")?;
-    ///     let rows = stmt.query_and_then(["one"], |row| row.get::<_, String>(0))?;
-    ///
-    ///     let mut persons = Vec::new();
-    ///     for person_result in rows {
-    ///         persons.push(person_result?);
-    ///     }
-    ///
-    ///     Ok(persons)
-    /// }
-    /// ```
-    ///
-    /// # Failure
-    ///
-    /// Will return `Err` if binding parameters fails.
-    #[inline]
-    pub fn query_and_then<T, E, P, F>(&mut self, params: P, f: F) -> Result<AndThenRows<'_, F>>
-    where
-        P: Params,
-        E: convert::From<Error>,
-        F: FnMut(&Row<'_>) -> Result<T, E>,
-    {
-        self.query(params).map(|rows| rows.and_then(f))
-    }
-
-    /// Execute the prepared statement with named parameter(s), returning an
-    /// iterator over the result of calling the mapping function over the
-    /// query's rows.
-    ///
-    /// Note: This function is deprecated in favor of
-    /// [`Statement::query_and_then`], which can now take named parameters
-    /// directly.
-    ///
-    /// If any parameters that were in the prepared statement are not included
-    /// in `params`, they will continue to use the most-recently bound value
-    /// from a previous call to `query_named`, or `NULL` if they have never been
-    /// bound.
-    ///
     /// ## Failure
     ///
     /// Will return `Err` if binding parameters fails.
-    #[deprecated = "You can use `query_and_then` with named params now."]
     pub fn query_and_then_named<T, E, F>(
         &mut self,
         params: &[(&str, &dyn ToSql)],
@@ -418,13 +341,17 @@
         E: convert::From<Error>,
         F: FnMut(&Row<'_>) -> Result<T, E>,
     {
-        self.query_and_then(params, f)
+        let rows = self.query_named(params)?;
+        Ok(AndThenRows::new(rows, f))
     }
 
     /// Return `true` if a query in the SQL statement it executes returns one
     /// or more rows and `false` if the SQL returns an empty set.
-    #[inline]
-    pub fn exists<P: Params>(&mut self, params: P) -> Result<bool> {
+    pub fn exists<P>(&mut self, params: P) -> Result<bool>
+    where
+        P: IntoIterator,
+        P::Item: ToSql,
+    {
         let mut rows = self.query(params)?;
         let exists = rows.next()?.is_some();
         Ok(exists)
@@ -437,50 +364,44 @@
     /// ignored.
     ///
     /// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
-    /// query truly is optional, you can call
-    /// [`.optional()`](crate::OptionalExtension::optional) on the result of
-    /// this to get a `Result<Option<T>>` (requires that the trait
-    /// `rusqlite::OptionalExtension` is imported).
+    /// query truly is optional, you can call `.optional()` on the result of
+    /// this to get a `Result<Option<T>>`.
     ///
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite call fails.
     pub fn query_row<T, P, F>(&mut self, params: P, f: F) -> Result<T>
     where
-        P: Params,
+        P: IntoIterator,
+        P::Item: ToSql,
         F: FnOnce(&Row<'_>) -> Result<T>,
     {
         let mut rows = self.query(params)?;
 
-        rows.get_expected_row().and_then(f)
+        rows.get_expected_row().and_then(|r| f(&r))
     }
 
     /// Convenience method to execute a query with named parameter(s) that is
     /// expected to return a single row.
     ///
-    /// Note: This function is deprecated in favor of
-    /// [`Statement::query_and_then`], which can now take named parameters
-    /// directly.
-    ///
     /// If the query returns more than one row, all rows except the first are
     /// ignored.
     ///
     /// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
-    /// query truly is optional, you can call
-    /// [`.optional()`](crate::OptionalExtension::optional) on the result of
-    /// this to get a `Result<Option<T>>` (requires that the trait
-    /// `rusqlite::OptionalExtension` is imported).
+    /// query truly is optional, you can call `.optional()` on the result of
+    /// this to get a `Result<Option<T>>`.
     ///
     /// # Failure
     ///
     /// Will return `Err` if `sql` cannot be converted to a C-compatible string
     /// or if the underlying SQLite call fails.
-    #[deprecated = "You can use `query_row` with named params now."]
     pub fn query_row_named<T, F>(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
     where
         F: FnOnce(&Row<'_>) -> Result<T>,
     {
-        self.query_row(params, f)
+        let mut rows = self.query_named(params)?;
+
+        rows.get_expected_row().and_then(|r| f(&r))
     }
 
     /// Consumes the statement.
@@ -491,12 +412,11 @@
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite call fails.
-    #[inline]
     pub fn finalize(mut self) -> Result<()> {
         self.finalize_()
     }
 
-    /// Return the (one-based) index of an SQL parameter given its name.
+    /// Return the (one-based) index of an SQL parameter given its name.
     ///
     /// Note that the initial ":" or "$" or "@" or "?" used to specify the
     /// parameter is included as part of the name.
@@ -515,37 +435,11 @@
     ///
     /// Will return Err if `name` is invalid. Will return Ok(None) if the name
     /// is valid but not a bound parameter of this statement.
-    #[inline]
     pub fn parameter_index(&self, name: &str) -> Result<Option<usize>> {
         Ok(self.stmt.bind_parameter_index(name))
     }
 
-    /// Return the SQL parameter name given its (one-based) index (the inverse
-    /// of [`Statement::parameter_index`]).
-    ///
-    /// ```rust,no_run
-    /// # use rusqlite::{Connection, Result};
-    /// fn example(conn: &Connection) -> Result<()> {
-    ///     let stmt = conn.prepare("SELECT * FROM test WHERE name = :example")?;
-    ///     let index = stmt.parameter_name(1);
-    ///     assert_eq!(index, Some(":example"));
-    ///     Ok(())
-    /// }
-    /// ```
-    ///
-    /// # Failure
-    ///
-    /// Will return `None` if the column index is out of bounds or if the
-    /// parameter is positional.
-    #[inline]
-    pub fn parameter_name(&self, index: usize) -> Option<&'_ str> {
-        self.stmt.bind_parameter_name(index as i32).map(|name| {
-            str::from_utf8(name.to_bytes()).expect("Invalid UTF-8 sequence in parameter name")
-        })
-    }
-
-    #[inline]
-    pub(crate) fn bind_parameters<P>(&mut self, params: P) -> Result<()>
+    fn bind_parameters<P>(&mut self, params: P) -> Result<()>
     where
         P: IntoIterator,
         P::Item: ToSql,
@@ -566,15 +460,10 @@
         }
     }
 
-    #[inline]
-    pub(crate) fn bind_parameters_named<T: ?Sized + ToSql>(
-        &mut self,
-        params: &[(&str, &T)],
-    ) -> Result<()> {
+    fn bind_parameters_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<()> {
         for &(name, value) in params {
             if let Some(i) = self.parameter_index(name)? {
-                let ts: &dyn ToSql = &value;
-                self.bind_parameter(ts, i)?;
+                self.bind_parameter(value, i)?;
             } else {
                 return Err(Error::InvalidParameterName(name.into()));
             }
@@ -583,14 +472,13 @@
     }
 
     /// Return the number of parameters that can be bound to this statement.
-    #[inline]
     pub fn parameter_count(&self) -> usize {
         self.stmt.bind_parameter_count()
     }
 
     /// Low level API to directly bind a parameter to a given index.
     ///
-    /// Note that the index is one-based, that is, the first parameter index is
+    /// Note that the index is one-based, that is, the first parameter index is
     /// 1 and not 0. This is consistent with the SQLite API and the values given
     /// to parameters bound as `?NNN`.
     ///
@@ -626,7 +514,6 @@
     ///     Ok(())
     /// }
     /// ```
-    #[inline]
     pub fn raw_bind_parameter<T: ToSql>(
         &mut self,
         one_based_col_index: usize,
@@ -651,7 +538,6 @@
     ///
     /// Will return `Err` if the executed statement returns rows (in which case
     /// `query` should be used instead), or the underlying SQLite call fails.
-    #[inline]
     pub fn raw_execute(&mut self) -> Result<usize> {
         self.execute_with_bound_parameters()
     }
@@ -668,13 +554,11 @@
     ///
     /// Note that if the SQL does not return results, [`Statement::raw_execute`]
     /// should be used instead.
-    #[inline]
     pub fn raw_query(&mut self) -> Rows<'_> {
         Rows::new(self)
     }
 
-    // generic because many of these branches can constant fold away.
-    fn bind_parameter<P: ?Sized + ToSql>(&self, param: &P, col: usize) -> Result<()> {
+    fn bind_parameter(&self, param: &dyn ToSql, col: usize) -> Result<()> {
         let value = param.to_sql()?;
 
         let ptr = unsafe { self.stmt.ptr() };
@@ -717,7 +601,7 @@
                     ffi::sqlite3_bind_blob(
                         ptr,
                         col as c_int,
-                        b.as_ptr().cast::<c_void>(),
+                        b.as_ptr() as *const c_void,
                         length,
                         ffi::SQLITE_TRANSIENT(),
                     )
@@ -726,7 +610,6 @@
         })
     }
 
-    #[inline]
     fn execute_with_bound_parameters(&mut self) -> Result<usize> {
         self.check_update()?;
         let r = self.stmt.step();
@@ -738,13 +621,27 @@
         }
     }
 
-    #[inline]
     fn finalize_(&mut self) -> Result<()> {
         let mut stmt = unsafe { RawStatement::new(ptr::null_mut(), 0) };
         mem::swap(&mut stmt, &mut self.stmt);
         self.conn.decode_result(stmt.finalize())
     }
 
+    #[cfg(not(feature = "modern_sqlite"))]
+    #[inline]
+    fn check_readonly(&self) -> Result<()> {
+        Ok(())
+    }
+
+    #[cfg(feature = "modern_sqlite")]
+    #[inline]
+    fn check_readonly(&self) -> Result<()> {
+        /*if !self.stmt.readonly() { does not work for PRAGMA
+            return Err(Error::InvalidQuery);
+        }*/
+        Ok(())
+    }
+
     #[cfg(all(feature = "modern_sqlite", feature = "extra_check"))]
     #[inline]
     fn check_update(&self) -> Result<()> {
@@ -767,7 +664,6 @@
 
     #[cfg(not(feature = "extra_check"))]
     #[inline]
-    #[allow(clippy::unnecessary_wraps)]
     fn check_update(&self) -> Result<()> {
         Ok(())
     }
@@ -775,7 +671,6 @@
     /// 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()
@@ -783,20 +678,17 @@
     }
 
     /// Get the value for one of the status counters for this statement.
-    #[inline]
     pub fn get_status(&self, status: StatementStatus) -> i32 {
         self.stmt.get_status(status, false)
     }
 
     /// Reset the value of one of the status counters for this statement,
-    #[inline]
     /// returning the value it had before resetting.
     pub fn reset_status(&self, status: StatementStatus) -> i32 {
         self.stmt.get_status(status, true)
     }
 
     #[cfg(feature = "extra_check")]
-    #[inline]
     pub(crate) fn check_no_tail(&self) -> Result<()> {
         if self.stmt.has_tail() {
             Err(Error::MultipleStatement)
@@ -807,7 +699,6 @@
 
     #[cfg(not(feature = "extra_check"))]
     #[inline]
-    #[allow(clippy::unnecessary_wraps)]
     pub(crate) fn check_no_tail(&self) -> Result<()> {
         Ok(())
     }
@@ -815,7 +706,6 @@
     /// Safety: This is unsafe, because using `sqlite3_stmt` after the
     /// connection has closed is illegal, but `RawStatement` does not enforce
     /// this, as it loses our protective `'conn` lifetime bound.
-    #[inline]
     pub(crate) unsafe fn into_raw(mut self) -> RawStatement {
         let mut stmt = RawStatement::new(ptr::null_mut(), 0);
         mem::swap(&mut stmt, &mut self.stmt);
@@ -840,14 +730,12 @@
 
 impl Drop for Statement<'_> {
     #[allow(unused_must_use)]
-    #[inline]
     fn drop(&mut self) {
         self.finalize_();
     }
 }
 
 impl Statement<'_> {
-    #[inline]
     pub(super) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> {
         Statement { conn, stmt }
     }
@@ -875,7 +763,7 @@
                         !text.is_null(),
                         "unexpected SQLITE_TEXT column type with NULL data"
                     );
-                    from_raw_parts(text.cast::<u8>(), len as usize)
+                    from_raw_parts(text as *const u8, len as usize)
                 };
 
                 ValueRef::Text(s)
@@ -897,7 +785,7 @@
                         !blob.is_null(),
                         "unexpected SQLITE_BLOB column type with NULL data"
                     );
-                    ValueRef::Blob(unsafe { from_raw_parts(blob.cast::<u8>(), len as usize) })
+                    ValueRef::Blob(unsafe { from_raw_parts(blob as *const u8, len as usize) })
                 } else {
                     // The return value from sqlite3_column_blob() for a zero-length BLOB
                     // is a NULL pointer.
@@ -908,7 +796,6 @@
         }
     }
 
-    #[inline]
     pub(super) fn step(&self) -> Result<bool> {
         match self.stmt.step() {
             ffi::SQLITE_ROW => Ok(true),
@@ -917,7 +804,6 @@
         }
     }
 
-    #[inline]
     pub(super) fn reset(&self) -> c_int {
         self.stmt.reset()
     }
@@ -925,7 +811,7 @@
 
 /// Prepared statement status counters.
 ///
-/// See `https://www.sqlite.org/c3ref/c_stmtstatus_counter.html`
+/// See https://www.sqlite.org/c3ref/c_stmtstatus_counter.html
 /// for explanations of each.
 ///
 /// Note that depending on your version of SQLite, all of these
@@ -946,10 +832,6 @@
     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,
 }
@@ -957,156 +839,124 @@
 #[cfg(test)]
 mod test {
     use crate::types::ToSql;
-    use crate::{params_from_iter, Connection, Error, Result};
+    use crate::{Connection, Error, Result, NO_PARAMS};
 
     #[test]
-    #[allow(deprecated)]
-    fn test_execute_named() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
+    fn test_execute_named() {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
 
         assert_eq!(
-            db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])?,
+            db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])
+                .unwrap(),
             1
         );
         assert_eq!(
-            db.execute("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)])?,
-            1
-        );
-        assert_eq!(
-            db.execute(
-                "INSERT INTO foo(x) VALUES (:x)",
-                crate::named_params! {":x": 3i32}
-            )?,
+            db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)])
+                .unwrap(),
             1
         );
 
         assert_eq!(
-            6i32,
+            3i32,
             db.query_row_named::<i32, _>(
                 "SELECT SUM(x) FROM foo WHERE x > :x",
                 &[(":x", &0i32)],
                 |r| r.get(0)
-            )?
+            )
+            .unwrap()
         );
-        assert_eq!(
-            5i32,
-            db.query_row::<i32, _, _>(
-                "SELECT SUM(x) FROM foo WHERE x > :x",
-                &[(":x", &1i32)],
-                |r| r.get(0)
-            )?
-        );
-        Ok(())
     }
 
     #[test]
-    #[allow(deprecated)]
-    fn test_stmt_execute_named() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_stmt_execute_named() {
+        let db = Connection::open_in_memory().unwrap();
         let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
                    INTEGER)";
-        db.execute_batch(sql)?;
+        db.execute_batch(sql).unwrap();
 
-        let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)")?;
-        stmt.execute_named(&[(":name", &"one")])?;
+        let mut stmt = db
+            .prepare("INSERT INTO test (name) VALUES (:name)")
+            .unwrap();
+        stmt.execute_named(&[(":name", &"one")]).unwrap();
 
-        let mut stmt = db.prepare("SELECT COUNT(*) FROM test WHERE name = :name")?;
+        let mut stmt = db
+            .prepare("SELECT COUNT(*) FROM test WHERE name = :name")
+            .unwrap();
         assert_eq!(
             1i32,
-            stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))?
+            stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))
+                .unwrap()
         );
-        assert_eq!(
-            1i32,
-            stmt.query_row::<i32, _, _>(&[(":name", "one")], |r| r.get(0))?
-        );
-        Ok(())
     }
 
     #[test]
-    #[allow(deprecated)]
-    fn test_query_named() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_query_named() {
+        let db = Connection::open_in_memory().unwrap();
         let sql = r#"
         CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
         INSERT INTO test(id, name) VALUES (1, "one");
         "#;
-        db.execute_batch(sql)?;
+        db.execute_batch(sql).unwrap();
 
-        let mut stmt = db.prepare("SELECT id FROM test where name = :name")?;
-        // legacy `_named` api
-        {
-            let mut rows = stmt.query_named(&[(":name", &"one")])?;
-            let id: Result<i32> = rows.next()?.unwrap().get(0);
-            assert_eq!(Ok(1), id);
-        }
+        let mut stmt = db
+            .prepare("SELECT id FROM test where name = :name")
+            .unwrap();
+        let mut rows = stmt.query_named(&[(":name", &"one")]).unwrap();
 
-        // plain api
-        {
-            let mut rows = stmt.query(&[(":name", "one")])?;
-            let id: Result<i32> = rows.next()?.unwrap().get(0);
-            assert_eq!(Ok(1), id);
-        }
-        Ok(())
+        let id: Result<i32> = rows.next().unwrap().unwrap().get(0);
+        assert_eq!(Ok(1), id);
     }
 
     #[test]
-    #[allow(deprecated)]
-    fn test_query_map_named() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_query_map_named() {
+        let db = Connection::open_in_memory().unwrap();
         let sql = r#"
         CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
         INSERT INTO test(id, name) VALUES (1, "one");
         "#;
-        db.execute_batch(sql)?;
+        db.execute_batch(sql).unwrap();
 
-        let mut stmt = db.prepare("SELECT id FROM test where name = :name")?;
-        // legacy `_named` api
-        {
-            let mut rows = stmt.query_map_named(&[(":name", &"one")], |row| {
+        let mut stmt = db
+            .prepare("SELECT id FROM test where name = :name")
+            .unwrap();
+        let mut rows = stmt
+            .query_map_named(&[(":name", &"one")], |row| {
                 let id: Result<i32> = row.get(0);
                 id.map(|i| 2 * i)
-            })?;
+            })
+            .unwrap();
 
-            let doubled_id: i32 = rows.next().unwrap()?;
-            assert_eq!(2, doubled_id);
-        }
-        // plain api
-        {
-            let mut rows = stmt.query_map(&[(":name", "one")], |row| {
-                let id: Result<i32> = row.get(0);
-                id.map(|i| 2 * i)
-            })?;
-
-            let doubled_id: i32 = rows.next().unwrap()?;
-            assert_eq!(2, doubled_id);
-        }
-        Ok(())
+        let doubled_id: i32 = rows.next().unwrap().unwrap();
+        assert_eq!(2, doubled_id);
     }
 
     #[test]
-    #[allow(deprecated)]
-    fn test_query_and_then_named() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_query_and_then_named() {
+        let db = Connection::open_in_memory().unwrap();
         let sql = r#"
         CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
         INSERT INTO test(id, name) VALUES (1, "one");
         INSERT INTO test(id, name) VALUES (2, "one");
         "#;
-        db.execute_batch(sql)?;
+        db.execute_batch(sql).unwrap();
 
-        let mut stmt = db.prepare("SELECT id FROM test where name = :name ORDER BY id ASC")?;
-        let mut rows = stmt.query_and_then_named(&[(":name", &"one")], |row| {
-            let id: i32 = row.get(0)?;
-            if id == 1 {
-                Ok(id)
-            } else {
-                Err(Error::SqliteSingleThreadedMode)
-            }
-        })?;
+        let mut stmt = db
+            .prepare("SELECT id FROM test where name = :name ORDER BY id ASC")
+            .unwrap();
+        let mut rows = stmt
+            .query_and_then_named(&[(":name", &"one")], |row| {
+                let id: i32 = row.get(0)?;
+                if id == 1 {
+                    Ok(id)
+                } else {
+                    Err(Error::SqliteSingleThreadedMode)
+                }
+            })
+            .unwrap();
 
         // first row should be Ok
-        let doubled_id: i32 = rows.next().unwrap()?;
+        let doubled_id: i32 = rows.next().unwrap().unwrap();
         assert_eq!(1, doubled_id);
 
         // second row should be Err
@@ -1116,62 +966,30 @@
             Err(Error::SqliteSingleThreadedMode) => (),
             Err(_) => panic!("invalid Err"),
         }
-        Ok(())
     }
 
     #[test]
-    fn test_query_and_then_by_name() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        let sql = r#"
-        CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
-        INSERT INTO test(id, name) VALUES (1, "one");
-        INSERT INTO test(id, name) VALUES (2, "one");
-        "#;
-        db.execute_batch(sql)?;
-
-        let mut stmt = db.prepare("SELECT id FROM test where name = :name ORDER BY id ASC")?;
-        let mut rows = stmt.query_and_then(&[(":name", "one")], |row| {
-            let id: i32 = row.get(0)?;
-            if id == 1 {
-                Ok(id)
-            } else {
-                Err(Error::SqliteSingleThreadedMode)
-            }
-        })?;
-
-        // first row should be Ok
-        let doubled_id: i32 = rows.next().unwrap()?;
-        assert_eq!(1, doubled_id);
-
-        // second row should be Err
-        #[allow(clippy::match_wild_err_arm)]
-        match rows.next().unwrap() {
-            Ok(_) => panic!("invalid Ok"),
-            Err(Error::SqliteSingleThreadedMode) => (),
-            Err(_) => panic!("invalid Err"),
-        }
-        Ok(())
-    }
-
-    #[test]
-    #[allow(deprecated)]
-    fn test_unbound_parameters_are_null() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_unbound_parameters_are_null() {
+        let db = Connection::open_in_memory().unwrap();
         let sql = "CREATE TABLE test (x TEXT, y TEXT)";
-        db.execute_batch(sql)?;
+        db.execute_batch(sql).unwrap();
 
-        let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")?;
-        stmt.execute_named(&[(":x", &"one")])?;
+        let mut stmt = db
+            .prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
+            .unwrap();
+        stmt.execute_named(&[(":x", &"one")]).unwrap();
 
-        let result: Option<String> =
-            db.query_row("SELECT y FROM test WHERE x = 'one'", [], |row| row.get(0))?;
+        let result: Option<String> = db
+            .query_row("SELECT y FROM test WHERE x = 'one'", NO_PARAMS, |row| {
+                row.get(0)
+            })
+            .unwrap();
         assert!(result.is_none());
-        Ok(())
     }
 
     #[test]
     fn test_raw_binding() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+        let db = Connection::open_in_memory().unwrap();
         db.execute_batch("CREATE TABLE test (name TEXT, value INTEGER)")?;
         {
             let mut stmt = db.prepare("INSERT INTO test (name, value) VALUES (:name, ?3)")?;
@@ -1201,233 +1019,232 @@
     }
 
     #[test]
-    fn test_unbound_parameters_are_reused() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_unbound_parameters_are_reused() {
+        let db = Connection::open_in_memory().unwrap();
         let sql = "CREATE TABLE test (x TEXT, y TEXT)";
-        db.execute_batch(sql)?;
+        db.execute_batch(sql).unwrap();
 
-        let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")?;
-        stmt.execute(&[(":x", "one")])?;
-        stmt.execute(&[(":y", "two")])?;
+        let mut stmt = db
+            .prepare("INSERT INTO test (x, y) VALUES (:x, :y)")
+            .unwrap();
+        stmt.execute_named(&[(":x", &"one")]).unwrap();
+        stmt.execute_named(&[(":y", &"two")]).unwrap();
 
-        let result: String =
-            db.query_row("SELECT x FROM test WHERE y = 'two'", [], |row| row.get(0))?;
+        let result: String = db
+            .query_row("SELECT x FROM test WHERE y = 'two'", NO_PARAMS, |row| {
+                row.get(0)
+            })
+            .unwrap();
         assert_eq!(result, "one");
-        Ok(())
     }
 
     #[test]
-    fn test_insert() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)")?;
-        let mut stmt = db.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)")?;
-        assert_eq!(stmt.insert([1i32])?, 1);
-        assert_eq!(stmt.insert([2i32])?, 2);
-        match stmt.insert([1i32]).unwrap_err() {
+    fn test_insert() {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo(x INTEGER UNIQUE)")
+            .unwrap();
+        let mut stmt = db
+            .prepare("INSERT OR IGNORE INTO foo (x) VALUES (?)")
+            .unwrap();
+        assert_eq!(stmt.insert(&[1i32]).unwrap(), 1);
+        assert_eq!(stmt.insert(&[2i32]).unwrap(), 2);
+        match stmt.insert(&[1i32]).unwrap_err() {
             Error::StatementChangedRows(0) => (),
             err => panic!("Unexpected error {}", err),
         }
-        let mut multi = db.prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4")?;
-        match multi.insert([]).unwrap_err() {
+        let mut multi = db
+            .prepare("INSERT INTO foo (x) SELECT 3 UNION ALL SELECT 4")
+            .unwrap();
+        match multi.insert(NO_PARAMS).unwrap_err() {
             Error::StatementChangedRows(2) => (),
             err => panic!("Unexpected error {}", err),
         }
-        Ok(())
     }
 
     #[test]
-    fn test_insert_different_tables() -> Result<()> {
+    fn test_insert_different_tables() {
         // Test for https://github.com/rusqlite/rusqlite/issues/171
-        let db = Connection::open_in_memory()?;
+        let db = Connection::open_in_memory().unwrap();
         db.execute_batch(
             r"
             CREATE TABLE foo(x INTEGER);
             CREATE TABLE bar(x INTEGER);
         ",
-        )?;
+        )
+        .unwrap();
 
-        assert_eq!(db.prepare("INSERT INTO foo VALUES (10)")?.insert([])?, 1);
-        assert_eq!(db.prepare("INSERT INTO bar VALUES (10)")?.insert([])?, 1);
-        Ok(())
+        assert_eq!(
+            db.prepare("INSERT INTO foo VALUES (10)")
+                .unwrap()
+                .insert(NO_PARAMS)
+                .unwrap(),
+            1
+        );
+        assert_eq!(
+            db.prepare("INSERT INTO bar VALUES (10)")
+                .unwrap()
+                .insert(NO_PARAMS)
+                .unwrap(),
+            1
+        );
     }
 
     #[test]
-    fn test_exists() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_exists() {
+        let db = Connection::open_in_memory().unwrap();
         let sql = "BEGIN;
                    CREATE TABLE foo(x INTEGER);
                    INSERT INTO foo VALUES(1);
                    INSERT INTO foo VALUES(2);
                    END;";
-        db.execute_batch(sql)?;
-        let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?")?;
-        assert!(stmt.exists([1i32])?);
-        assert!(stmt.exists([2i32])?);
-        assert!(!stmt.exists([0i32])?);
-        Ok(())
+        db.execute_batch(sql).unwrap();
+        let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?").unwrap();
+        assert!(stmt.exists(&[1i32]).unwrap());
+        assert!(stmt.exists(&[2i32]).unwrap());
+        assert!(!stmt.exists(&[0i32]).unwrap());
     }
 
     #[test]
-    fn test_query_row() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_query_row() {
+        let db = Connection::open_in_memory().unwrap();
         let sql = "BEGIN;
                    CREATE TABLE foo(x INTEGER, y INTEGER);
                    INSERT INTO foo VALUES(1, 3);
                    INSERT INTO foo VALUES(2, 4);
                    END;";
-        db.execute_batch(sql)?;
-        let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?")?;
-        let y: Result<i64> = stmt.query_row([1i32], |r| r.get(0));
-        assert_eq!(3i64, y?);
-        Ok(())
+        db.execute_batch(sql).unwrap();
+        let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?").unwrap();
+        let y: Result<i64> = stmt.query_row(&[1i32], |r| r.get(0));
+        assert_eq!(3i64, y.unwrap());
     }
 
     #[test]
-    fn test_query_by_column_name() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_query_by_column_name() {
+        let db = Connection::open_in_memory().unwrap();
         let sql = "BEGIN;
                    CREATE TABLE foo(x INTEGER, y INTEGER);
                    INSERT INTO foo VALUES(1, 3);
                    END;";
-        db.execute_batch(sql)?;
-        let mut stmt = db.prepare("SELECT y FROM foo")?;
-        let y: Result<i64> = stmt.query_row([], |r| r.get("y"));
-        assert_eq!(3i64, y?);
-        Ok(())
+        db.execute_batch(sql).unwrap();
+        let mut stmt = db.prepare("SELECT y FROM foo").unwrap();
+        let y: Result<i64> = stmt.query_row(NO_PARAMS, |r| r.get("y"));
+        assert_eq!(3i64, y.unwrap());
     }
 
     #[test]
-    fn test_query_by_column_name_ignore_case() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_query_by_column_name_ignore_case() {
+        let db = Connection::open_in_memory().unwrap();
         let sql = "BEGIN;
                    CREATE TABLE foo(x INTEGER, y INTEGER);
                    INSERT INTO foo VALUES(1, 3);
                    END;";
-        db.execute_batch(sql)?;
-        let mut stmt = db.prepare("SELECT y as Y FROM foo")?;
-        let y: Result<i64> = stmt.query_row([], |r| r.get("y"));
-        assert_eq!(3i64, y?);
-        Ok(())
+        db.execute_batch(sql).unwrap();
+        let mut stmt = db.prepare("SELECT y as Y FROM foo").unwrap();
+        let y: Result<i64> = stmt.query_row(NO_PARAMS, |r| r.get("y"));
+        assert_eq!(3i64, y.unwrap());
     }
 
     #[test]
     #[cfg(feature = "modern_sqlite")]
-    fn test_expanded_sql() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        let stmt = db.prepare("SELECT ?")?;
-        stmt.bind_parameter(&1, 1)?;
+    fn test_expanded_sql() {
+        let db = Connection::open_in_memory().unwrap();
+        let stmt = db.prepare("SELECT ?").unwrap();
+        stmt.bind_parameter(&1, 1).unwrap();
         assert_eq!(Some("SELECT 1".to_owned()), stmt.expanded_sql());
-        Ok(())
     }
 
     #[test]
-    fn test_bind_parameters() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_bind_parameters() {
+        let db = Connection::open_in_memory().unwrap();
         // dynamic slice:
         db.query_row(
             "SELECT ?1, ?2, ?3",
             &[&1u8 as &dyn ToSql, &"one", &Some("one")],
             |row| row.get::<_, u8>(0),
-        )?;
+        )
+        .unwrap();
         // existing collection:
         let data = vec![1, 2, 3];
-        db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| {
+        db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, u8>(0))
+            .unwrap();
+        db.query_row("SELECT ?1, ?2, ?3", data.as_slice(), |row| {
             row.get::<_, u8>(0)
-        })?;
-        db.query_row(
-            "SELECT ?1, ?2, ?3",
-            params_from_iter(data.as_slice()),
-            |row| row.get::<_, u8>(0),
-        )?;
-        db.query_row("SELECT ?1, ?2, ?3", params_from_iter(data), |row| {
-            row.get::<_, u8>(0)
-        })?;
+        })
+        .unwrap();
+        db.query_row("SELECT ?1, ?2, ?3", data, |row| row.get::<_, u8>(0))
+            .unwrap();
 
         use std::collections::BTreeSet;
         let data: BTreeSet<String> = ["one", "two", "three"]
             .iter()
             .map(|s| (*s).to_string())
             .collect();
-        db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| {
-            row.get::<_, String>(0)
-        })?;
+        db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, String>(0))
+            .unwrap();
 
         let data = [0; 3];
-        db.query_row("SELECT ?1, ?2, ?3", params_from_iter(&data), |row| {
-            row.get::<_, u8>(0)
-        })?;
-        db.query_row("SELECT ?1, ?2, ?3", params_from_iter(data.iter()), |row| {
-            row.get::<_, u8>(0)
-        })?;
-        Ok(())
+        db.query_row("SELECT ?1, ?2, ?3", &data, |row| row.get::<_, u8>(0))
+            .unwrap();
+        db.query_row("SELECT ?1, ?2, ?3", data.iter(), |row| row.get::<_, u8>(0))
+            .unwrap();
     }
 
     #[test]
-    fn test_parameter_name() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE test (name TEXT, value INTEGER)")?;
-        let stmt = db.prepare("INSERT INTO test (name, value) VALUES (:name, ?3)")?;
-        assert_eq!(stmt.parameter_name(0), None);
-        assert_eq!(stmt.parameter_name(1), Some(":name"));
-        assert_eq!(stmt.parameter_name(2), None);
-        Ok(())
-    }
-
-    #[test]
-    fn test_empty_stmt() -> Result<()> {
-        let conn = Connection::open_in_memory()?;
-        let mut stmt = conn.prepare("")?;
+    fn test_empty_stmt() {
+        let conn = Connection::open_in_memory().unwrap();
+        let mut stmt = conn.prepare("").unwrap();
         assert_eq!(0, stmt.column_count());
         assert!(stmt.parameter_index("test").is_ok());
         assert!(stmt.step().is_err());
         stmt.reset();
-        assert!(stmt.execute([]).is_err());
-        Ok(())
+        assert!(stmt.execute(NO_PARAMS).is_err());
     }
 
     #[test]
-    fn test_comment_stmt() -> Result<()> {
-        let conn = Connection::open_in_memory()?;
-        conn.prepare("/*SELECT 1;*/")?;
-        Ok(())
+    fn test_comment_stmt() {
+        let conn = Connection::open_in_memory().unwrap();
+        conn.prepare("/*SELECT 1;*/").unwrap();
     }
 
     #[test]
-    fn test_comment_and_sql_stmt() -> Result<()> {
-        let conn = Connection::open_in_memory()?;
-        let stmt = conn.prepare("/*...*/ SELECT 1;")?;
+    fn test_comment_and_sql_stmt() {
+        let conn = Connection::open_in_memory().unwrap();
+        let stmt = conn.prepare("/*...*/ SELECT 1;").unwrap();
         assert_eq!(1, stmt.column_count());
-        Ok(())
     }
 
     #[test]
-    fn test_semi_colon_stmt() -> Result<()> {
-        let conn = Connection::open_in_memory()?;
-        let stmt = conn.prepare(";")?;
+    fn test_semi_colon_stmt() {
+        let conn = Connection::open_in_memory().unwrap();
+        let stmt = conn.prepare(";").unwrap();
         assert_eq!(0, stmt.column_count());
-        Ok(())
     }
 
     #[test]
-    fn test_utf16_conversion() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.pragma_update(None, "encoding", &"UTF-16le")?;
-        let encoding: String = db.pragma_query_value(None, "encoding", |row| row.get(0))?;
+    fn test_utf16_conversion() {
+        let db = Connection::open_in_memory().unwrap();
+        db.pragma_update(None, "encoding", &"UTF-16le").unwrap();
+        let encoding: String = db
+            .pragma_query_value(None, "encoding", |row| row.get(0))
+            .unwrap();
         assert_eq!("UTF-16le", encoding);
-        db.execute_batch("CREATE TABLE foo(x TEXT)")?;
+        db.execute_batch("CREATE TABLE foo(x TEXT)").unwrap();
         let expected = "テスト";
-        db.execute("INSERT INTO foo(x) VALUES (?)", &[&expected])?;
-        let actual: String = db.query_row("SELECT x FROM foo", [], |row| row.get(0))?;
+        db.execute("INSERT INTO foo(x) VALUES (?)", &[&expected])
+            .unwrap();
+        let actual: String = db
+            .query_row("SELECT x FROM foo", NO_PARAMS, |row| row.get(0))
+            .unwrap();
         assert_eq!(expected, actual);
-        Ok(())
     }
 
     #[test]
-    fn test_nul_byte() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_nul_byte() {
+        let db = Connection::open_in_memory().unwrap();
         let expected = "a\x00b";
-        let actual: String = db.query_row("SELECT ?", [expected], |row| row.get(0))?;
+        let actual: String = db
+            .query_row("SELECT ?", &[&expected], |row| row.get(0))
+            .unwrap();
         assert_eq!(expected, actual);
-        Ok(())
     }
 }
diff --git a/src/trace.rs b/src/trace.rs
index 3932976..76e0969 100644
--- a/src/trace.rs
+++ b/src/trace.rs
@@ -1,4 +1,4 @@
-//! Tracing and profiling functions. Error and warning log.
+//! `feature = "trace"` 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};
 
-/// Set up the process-wide SQLite error logging callback.
+/// `feature = "trace"` Set up the process-wide SQLite error logging callback.
 ///
 /// # Safety
 ///
@@ -31,18 +31,19 @@
         let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
 
         let s = String::from_utf8_lossy(c_slice);
-        drop(catch_unwind(|| callback(err, &s)));
+        let _ = catch_unwind(|| callback(err, &s));
     }
 
-    let rc = if let Some(f) = callback {
-        ffi::sqlite3_config(
+    let rc = match callback {
+        Some(f) => ffi::sqlite3_config(
             ffi::SQLITE_CONFIG_LOG,
             log_callback as extern "C" fn(_, _, _),
             f as *mut c_void,
-        )
-    } else {
-        let nullptr: *mut c_void = ptr::null_mut();
-        ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
+        ),
+        None => {
+            let nullptr: *mut c_void = ptr::null_mut();
+            ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
+        }
     };
 
     if rc == ffi::SQLITE_OK {
@@ -52,9 +53,8 @@
     }
 }
 
-/// Write a message into the error log established by
+/// `feature = "trace"` Write a message into the error log established by
 /// `config_log`.
-#[inline]
 pub fn log(err_code: c_int, msg: &str) {
     let msg = CString::new(msg).expect("SQLite log messages cannot contain embedded zeroes");
     unsafe {
@@ -63,7 +63,7 @@
 }
 
 impl Connection {
-    /// Register or clear a callback function that can be
+    /// `feature = "trace"` Register or clear a callback function that can be
     /// used for tracing the execution of SQL statements.
     ///
     /// Prepared statement placeholders are replaced/logged with their assigned
@@ -74,7 +74,7 @@
             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);
-            drop(catch_unwind(|| trace_fn(&s)));
+            let _ = catch_unwind(|| trace_fn(&s));
         }
 
         let c = self.db.borrow_mut();
@@ -88,7 +88,7 @@
         }
     }
 
-    /// Register or clear a callback function that can be
+    /// `feature = "trace"` Register or clear a callback function that can be
     /// used for profiling the execution of SQL statements.
     ///
     /// There can only be a single profiler defined for each database
@@ -108,7 +108,7 @@
                 nanoseconds / NANOS_PER_SEC,
                 (nanoseconds % NANOS_PER_SEC) as u32,
             );
-            drop(catch_unwind(|| profile_fn(&s, duration)));
+            let _ = catch_unwind(|| profile_fn(&s, duration));
         }
 
         let c = self.db.borrow_mut();
@@ -127,10 +127,10 @@
     use std::sync::Mutex;
     use std::time::Duration;
 
-    use crate::{Connection, Result};
+    use crate::Connection;
 
     #[test]
-    fn test_trace() -> Result<()> {
+    fn test_trace() {
         lazy_static! {
             static ref TRACED_STMTS: Mutex<Vec<String>> = Mutex::new(Vec::new());
         }
@@ -139,27 +139,26 @@
             traced_stmts.push(s.to_owned());
         }
 
-        let mut db = Connection::open_in_memory()?;
+        let mut db = Connection::open_in_memory().unwrap();
         db.trace(Some(tracer));
         {
-            let _ = db.query_row("SELECT ?", [1i32], |_| Ok(()));
-            let _ = db.query_row("SELECT ?", ["hello"], |_| Ok(()));
+            let _ = db.query_row("SELECT ?", &[1i32], |_| Ok(()));
+            let _ = db.query_row("SELECT ?", &["hello"], |_| Ok(()));
         }
         db.trace(None);
         {
-            let _ = db.query_row("SELECT ?", [2i32], |_| Ok(()));
-            let _ = db.query_row("SELECT ?", ["goodbye"], |_| Ok(()));
+            let _ = db.query_row("SELECT ?", &[2i32], |_| Ok(()));
+            let _ = db.query_row("SELECT ?", &["goodbye"], |_| Ok(()));
         }
 
         let traced_stmts = TRACED_STMTS.lock().unwrap();
         assert_eq!(traced_stmts.len(), 2);
         assert_eq!(traced_stmts[0], "SELECT 1");
         assert_eq!(traced_stmts[1], "SELECT 'hello'");
-        Ok(())
     }
 
     #[test]
-    fn test_profile() -> Result<()> {
+    fn test_profile() {
         lazy_static! {
             static ref PROFILED: Mutex<Vec<(String, Duration)>> = Mutex::new(Vec::new());
         }
@@ -168,15 +167,14 @@
             profiled.push((s.to_owned(), d));
         }
 
-        let mut db = Connection::open_in_memory()?;
+        let mut db = Connection::open_in_memory().unwrap();
         db.profile(Some(profiler));
-        db.execute_batch("PRAGMA application_id = 1")?;
+        db.execute_batch("PRAGMA application_id = 1").unwrap();
         db.profile(None);
-        db.execute_batch("PRAGMA application_id = 2")?;
+        db.execute_batch("PRAGMA application_id = 2").unwrap();
 
         let profiled = PROFILED.lock().unwrap();
         assert_eq!(profiled.len(), 1);
         assert_eq!(profiled[0].0, "PRAGMA application_id = 1");
-        Ok(())
     }
 }
diff --git a/src/transaction.rs b/src/transaction.rs
index 296b2aa..5e649b7 100644
--- a/src/transaction.rs
+++ b/src/transaction.rs
@@ -102,7 +102,6 @@
     /// Even though we don't mutate the connection, we take a `&mut Connection`
     /// so as to prevent nested transactions on the same connection. For cases
     /// where this is unacceptable, [`Transaction::new_unchecked`] is available.
-    #[inline]
     pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> {
         Self::new_unchecked(conn, behavior)
     }
@@ -112,7 +111,6 @@
     /// If a transaction is already open, this will return an error. Where
     /// possible, [`Transaction::new`] should be preferred, as it provides a
     /// compile-time guarantee that transactions are not nested.
-    #[inline]
     pub fn new_unchecked(
         conn: &Connection,
         behavior: TransactionBehavior,
@@ -155,51 +153,42 @@
     ///     tx.commit()
     /// }
     /// ```
-    #[inline]
     pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
         Savepoint::with_depth(self.conn, 1)
     }
 
     /// Create a new savepoint with a custom savepoint name. See `savepoint()`.
-    #[inline]
     pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
         Savepoint::with_depth_and_name(self.conn, 1, name)
     }
 
     /// Get the current setting for what happens to the transaction when it is
     /// dropped.
-    #[inline]
-    #[must_use]
     pub fn drop_behavior(&self) -> DropBehavior {
         self.drop_behavior
     }
 
     /// Configure the transaction to perform the specified action when it is
     /// dropped.
-    #[inline]
     pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
-        self.drop_behavior = drop_behavior;
+        self.drop_behavior = drop_behavior
     }
 
     /// A convenience method which consumes and commits a transaction.
-    #[inline]
     pub fn commit(mut self) -> Result<()> {
         self.commit_()
     }
 
-    #[inline]
     fn commit_(&mut self) -> Result<()> {
         self.conn.execute_batch("COMMIT")?;
         Ok(())
     }
 
     /// A convenience method which consumes and rolls back a transaction.
-    #[inline]
     pub fn rollback(mut self) -> Result<()> {
         self.rollback_()
     }
 
-    #[inline]
     fn rollback_(&mut self) -> Result<()> {
         self.conn.execute_batch("ROLLBACK")?;
         Ok(())
@@ -210,12 +199,10 @@
     ///
     /// Functionally equivalent to the `Drop` implementation, but allows
     /// callers to see any errors that occur.
-    #[inline]
     pub fn finish(mut self) -> Result<()> {
         self.finish_()
     }
 
-    #[inline]
     fn finish_(&mut self) -> Result<()> {
         if self.conn.is_autocommit() {
             return Ok(());
@@ -232,7 +219,6 @@
 impl Deref for Transaction<'_> {
     type Target = Connection;
 
-    #[inline]
     fn deref(&self) -> &Connection {
         self.conn
     }
@@ -240,14 +226,12 @@
 
 #[allow(unused_must_use)]
 impl Drop for Transaction<'_> {
-    #[inline]
     fn drop(&mut self) {
         self.finish_();
     }
 }
 
 impl Savepoint<'_> {
-    #[inline]
     fn with_depth_and_name<T: Into<String>>(
         conn: &Connection,
         depth: u32,
@@ -264,58 +248,48 @@
             })
     }
 
-    #[inline]
     fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> {
         let name = format!("_rusqlite_sp_{}", depth);
         Savepoint::with_depth_and_name(conn, depth, name)
     }
 
     /// Begin a new savepoint. Can be nested.
-    #[inline]
     pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
         Savepoint::with_depth(conn, 0)
     }
 
     /// Begin a new savepoint with a user-provided savepoint name.
-    #[inline]
     pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
         Savepoint::with_depth_and_name(conn, 0, name)
     }
 
     /// Begin a nested savepoint.
-    #[inline]
     pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
         Savepoint::with_depth(self.conn, self.depth + 1)
     }
 
     /// Begin a nested savepoint with a user-provided savepoint name.
-    #[inline]
     pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
         Savepoint::with_depth_and_name(self.conn, self.depth + 1, name)
     }
 
     /// Get the current setting for what happens to the savepoint when it is
     /// dropped.
-    #[inline]
-    #[must_use]
     pub fn drop_behavior(&self) -> DropBehavior {
         self.drop_behavior
     }
 
     /// Configure the savepoint to perform the specified action when it is
     /// dropped.
-    #[inline]
     pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
-        self.drop_behavior = drop_behavior;
+        self.drop_behavior = drop_behavior
     }
 
     /// A convenience method which consumes and commits a savepoint.
-    #[inline]
     pub fn commit(mut self) -> Result<()> {
         self.commit_()
     }
 
-    #[inline]
     fn commit_(&mut self) -> Result<()> {
         self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
         self.committed = true;
@@ -328,7 +302,6 @@
     ///
     /// Unlike `Transaction`s, savepoints remain active after they have been
     /// rolled back, and can be rolled back again or committed.
-    #[inline]
     pub fn rollback(&mut self) -> Result<()> {
         self.conn
             .execute_batch(&format!("ROLLBACK TO {}", self.name))
@@ -339,12 +312,10 @@
     ///
     /// Functionally equivalent to the `Drop` implementation, but allows
     /// callers to see any errors that occur.
-    #[inline]
     pub fn finish(mut self) -> Result<()> {
         self.finish_()
     }
 
-    #[inline]
     fn finish_(&mut self) -> Result<()> {
         if self.committed {
             return Ok(());
@@ -361,7 +332,6 @@
 impl Deref for Savepoint<'_> {
     type Target = Connection;
 
-    #[inline]
     fn deref(&self) -> &Connection {
         self.conn
     }
@@ -369,7 +339,6 @@
 
 #[allow(unused_must_use)]
 impl Drop for Savepoint<'_> {
-    #[inline]
     fn drop(&mut self) {
         self.finish_();
     }
@@ -379,9 +348,8 @@
     /// Begin a new transaction with the default behavior (DEFERRED).
     ///
     /// The transaction defaults to rolling back when it is dropped. If you
-    /// want the transaction to commit, you must call
-    /// [`commit`](Transaction::commit) or
-    /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior).
+    /// want the transaction to commit, you must call `commit` or
+    /// `set_drop_behavior(DropBehavior::Commit)`.
     ///
     /// ## Example
     ///
@@ -402,19 +370,17 @@
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite call fails.
-    #[inline]
     pub fn transaction(&mut self) -> Result<Transaction<'_>> {
         Transaction::new(self, TransactionBehavior::Deferred)
     }
 
     /// Begin a new transaction with a specified behavior.
     ///
-    /// See [`transaction`](Connection::transaction).
+    /// See `transaction`.
     ///
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite call fails.
-    #[inline]
     pub fn transaction_with_behavior(
         &mut self,
         behavior: TransactionBehavior,
@@ -460,9 +426,8 @@
     /// Begin a new savepoint with the default behavior (DEFERRED).
     ///
     /// The savepoint defaults to rolling back when it is dropped. If you want
-    /// the savepoint to commit, you must call [`commit`](Savepoint::commit) or
-    /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::
-    /// set_drop_behavior).
+    /// the savepoint to commit, you must call `commit` or
+    /// `set_drop_behavior(DropBehavior::Commit)`.
     ///
     /// ## Example
     ///
@@ -483,19 +448,17 @@
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite call fails.
-    #[inline]
     pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
         Savepoint::new(self)
     }
 
     /// Begin a new savepoint with a specified name.
     ///
-    /// See [`savepoint`](Connection::savepoint).
+    /// See `savepoint`.
     ///
     /// # Failure
     ///
     /// Will return `Err` if the underlying SQLite call fails.
-    #[inline]
     pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
         Savepoint::with_name(self, name)
     }
@@ -504,35 +467,35 @@
 #[cfg(test)]
 mod test {
     use super::DropBehavior;
-    use crate::{Connection, Error, Result};
+    use crate::{Connection, Error, NO_PARAMS};
 
-    fn checked_memory_handle() -> Result<Connection> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo (x INTEGER)")?;
-        Ok(db)
+    fn checked_memory_handle() -> Connection {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo (x INTEGER)").unwrap();
+        db
     }
 
     #[test]
-    fn test_drop() -> Result<()> {
-        let mut db = checked_memory_handle()?;
+    fn test_drop() {
+        let mut db = checked_memory_handle();
         {
-            let tx = db.transaction()?;
-            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
+            let tx = db.transaction().unwrap();
+            tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
             // default: rollback
         }
         {
-            let mut tx = db.transaction()?;
-            tx.execute_batch("INSERT INTO foo VALUES(2)")?;
+            let mut tx = db.transaction().unwrap();
+            tx.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
             tx.set_drop_behavior(DropBehavior::Commit)
         }
         {
-            let tx = db.transaction()?;
+            let tx = db.transaction().unwrap();
             assert_eq!(
                 2i32,
-                tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
+                tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
+                    .unwrap()
             );
         }
-        Ok(())
     }
     fn assert_nested_tx_error(e: crate::Error) {
         if let Error::SqliteFailure(e, Some(m)) = &e {
@@ -546,168 +509,165 @@
     }
 
     #[test]
-    fn test_unchecked_nesting() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_unchecked_nesting() {
+        let db = checked_memory_handle();
 
         {
-            let tx = db.unchecked_transaction()?;
+            let tx = db.unchecked_transaction().unwrap();
             let e = tx.unchecked_transaction().unwrap_err();
             assert_nested_tx_error(e);
             // default: rollback
         }
         {
-            let tx = db.unchecked_transaction()?;
-            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
+            let tx = db.unchecked_transaction().unwrap();
+            tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
             // Ensure this doesn't interfere with ongoing transaction
             let e = tx.unchecked_transaction().unwrap_err();
             assert_nested_tx_error(e);
 
-            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
-            tx.commit()?;
+            tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
+            tx.commit().unwrap();
         }
 
         assert_eq!(
             2i32,
-            db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
+            db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
+                .unwrap()
         );
-        Ok(())
     }
 
     #[test]
-    fn test_explicit_rollback_commit() -> Result<()> {
-        let mut db = checked_memory_handle()?;
+    fn test_explicit_rollback_commit() {
+        let mut db = checked_memory_handle();
         {
-            let mut tx = db.transaction()?;
+            let mut tx = db.transaction().unwrap();
             {
-                let mut sp = tx.savepoint()?;
-                sp.execute_batch("INSERT INTO foo VALUES(1)")?;
-                sp.rollback()?;
-                sp.execute_batch("INSERT INTO foo VALUES(2)")?;
-                sp.commit()?;
+                let mut sp = tx.savepoint().unwrap();
+                sp.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
+                sp.rollback().unwrap();
+                sp.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
+                sp.commit().unwrap();
             }
-            tx.commit()?;
+            tx.commit().unwrap();
         }
         {
-            let tx = db.transaction()?;
-            tx.execute_batch("INSERT INTO foo VALUES(4)")?;
-            tx.commit()?;
+            let tx = db.transaction().unwrap();
+            tx.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
+            tx.commit().unwrap();
         }
         {
-            let tx = db.transaction()?;
+            let tx = db.transaction().unwrap();
             assert_eq!(
                 6i32,
-                tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
+                tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
+                    .unwrap()
             );
         }
-        Ok(())
     }
 
     #[test]
-    fn test_savepoint() -> Result<()> {
-        let mut db = checked_memory_handle()?;
+    fn test_savepoint() {
+        let mut db = checked_memory_handle();
         {
-            let mut tx = db.transaction()?;
-            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
-            assert_current_sum(1, &tx)?;
+            let mut tx = db.transaction().unwrap();
+            tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
+            assert_current_sum(1, &tx);
             tx.set_drop_behavior(DropBehavior::Commit);
             {
-                let mut sp1 = tx.savepoint()?;
-                sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
-                assert_current_sum(3, &sp1)?;
+                let mut sp1 = tx.savepoint().unwrap();
+                sp1.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
+                assert_current_sum(3, &sp1);
                 // will rollback sp1
                 {
-                    let mut sp2 = sp1.savepoint()?;
-                    sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
-                    assert_current_sum(7, &sp2)?;
+                    let mut sp2 = sp1.savepoint().unwrap();
+                    sp2.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
+                    assert_current_sum(7, &sp2);
                     // will rollback sp2
                     {
-                        let sp3 = sp2.savepoint()?;
-                        sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
-                        assert_current_sum(15, &sp3)?;
-                        sp3.commit()?;
+                        let sp3 = sp2.savepoint().unwrap();
+                        sp3.execute_batch("INSERT INTO foo VALUES(8)").unwrap();
+                        assert_current_sum(15, &sp3);
+                        sp3.commit().unwrap();
                         // committed sp3, but will be erased by sp2 rollback
                     }
-                    assert_current_sum(15, &sp2)?;
+                    assert_current_sum(15, &sp2);
                 }
-                assert_current_sum(3, &sp1)?;
+                assert_current_sum(3, &sp1);
             }
-            assert_current_sum(1, &tx)?;
+            assert_current_sum(1, &tx);
         }
-        assert_current_sum(1, &db)?;
-        Ok(())
+        assert_current_sum(1, &db);
     }
 
     #[test]
-    fn test_ignore_drop_behavior() -> Result<()> {
-        let mut db = checked_memory_handle()?;
+    fn test_ignore_drop_behavior() {
+        let mut db = checked_memory_handle();
 
-        let mut tx = db.transaction()?;
+        let mut tx = db.transaction().unwrap();
         {
-            let mut sp1 = tx.savepoint()?;
-            insert(1, &sp1)?;
-            sp1.rollback()?;
-            insert(2, &sp1)?;
+            let mut sp1 = tx.savepoint().unwrap();
+            insert(1, &sp1);
+            sp1.rollback().unwrap();
+            insert(2, &sp1);
             {
-                let mut sp2 = sp1.savepoint()?;
+                let mut sp2 = sp1.savepoint().unwrap();
                 sp2.set_drop_behavior(DropBehavior::Ignore);
-                insert(4, &sp2)?;
+                insert(4, &sp2);
             }
-            assert_current_sum(6, &sp1)?;
-            sp1.commit()?;
+            assert_current_sum(6, &sp1);
+            sp1.commit().unwrap();
         }
-        assert_current_sum(6, &tx)?;
-        Ok(())
+        assert_current_sum(6, &tx);
     }
 
     #[test]
-    fn test_savepoint_names() -> Result<()> {
-        let mut db = checked_memory_handle()?;
+    fn test_savepoint_names() {
+        let mut db = checked_memory_handle();
 
         {
-            let mut sp1 = db.savepoint_with_name("my_sp")?;
-            insert(1, &sp1)?;
-            assert_current_sum(1, &sp1)?;
+            let mut sp1 = db.savepoint_with_name("my_sp").unwrap();
+            insert(1, &sp1);
+            assert_current_sum(1, &sp1);
             {
-                let mut sp2 = sp1.savepoint_with_name("my_sp")?;
+                let mut sp2 = sp1.savepoint_with_name("my_sp").unwrap();
                 sp2.set_drop_behavior(DropBehavior::Commit);
-                insert(2, &sp2)?;
-                assert_current_sum(3, &sp2)?;
-                sp2.rollback()?;
-                assert_current_sum(1, &sp2)?;
-                insert(4, &sp2)?;
+                insert(2, &sp2);
+                assert_current_sum(3, &sp2);
+                sp2.rollback().unwrap();
+                assert_current_sum(1, &sp2);
+                insert(4, &sp2);
             }
-            assert_current_sum(5, &sp1)?;
-            sp1.rollback()?;
+            assert_current_sum(5, &sp1);
+            sp1.rollback().unwrap();
             {
-                let mut sp2 = sp1.savepoint_with_name("my_sp")?;
+                let mut sp2 = sp1.savepoint_with_name("my_sp").unwrap();
                 sp2.set_drop_behavior(DropBehavior::Ignore);
-                insert(8, &sp2)?;
+                insert(8, &sp2);
             }
-            assert_current_sum(8, &sp1)?;
-            sp1.commit()?;
+            assert_current_sum(8, &sp1);
+            sp1.commit().unwrap();
         }
-        assert_current_sum(8, &db)?;
-        Ok(())
+        assert_current_sum(8, &db);
     }
 
     #[test]
-    fn test_rc() -> Result<()> {
+    fn test_rc() {
         use std::rc::Rc;
-        let mut conn = Connection::open_in_memory()?;
-        let rc_txn = Rc::new(conn.transaction()?);
+        let mut conn = Connection::open_in_memory().unwrap();
+        let rc_txn = Rc::new(conn.transaction().unwrap());
 
         // This will compile only if Transaction is Debug
         Rc::try_unwrap(rc_txn).unwrap();
-        Ok(())
     }
 
-    fn insert(x: i32, conn: &Connection) -> Result<usize> {
-        conn.execute("INSERT INTO foo VALUES(?)", [x])
+    fn insert(x: i32, conn: &Connection) {
+        conn.execute("INSERT INTO foo VALUES(?)", &[x]).unwrap();
     }
 
-    fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
-        let i = conn.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+    fn assert_current_sum(x: i32, conn: &Connection) {
+        let i = conn
+            .query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(x, i);
-        Ok(())
     }
 }
diff --git a/src/types/chrono.rs b/src/types/chrono.rs
index 6bfc2f4..3cba1e9 100644
--- a/src/types/chrono.rs
+++ b/src/types/chrono.rs
@@ -1,26 +1,26 @@
 //! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
 
-use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
+use std::borrow::Cow;
+
+use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
 
 use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
 use crate::Result;
 
 /// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
 impl ToSql for NaiveDate {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
-        let date_str = self.format("%F").to_string();
+        let date_str = self.format("%Y-%m-%d").to_string();
         Ok(ToSqlOutput::from(date_str))
     }
 }
 
 /// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
 impl FromSql for NaiveDate {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         value
             .as_str()
-            .and_then(|s| match NaiveDate::parse_from_str(s, "%F") {
+            .and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
                 Ok(dt) => Ok(dt),
                 Err(err) => Err(FromSqlError::Other(Box::new(err))),
             })
@@ -29,9 +29,8 @@
 
 /// ISO 8601 time without timezone => "HH:MM:SS.SSS"
 impl ToSql for NaiveTime {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
-        let date_str = self.format("%T%.f").to_string();
+        let date_str = self.format("%H:%M:%S%.f").to_string();
         Ok(ToSqlOutput::from(date_str))
     }
 }
@@ -42,8 +41,8 @@
         value.as_str().and_then(|s| {
             let fmt = match s.len() {
                 5 => "%H:%M",
-                8 => "%T",
-                _ => "%T%.f",
+                8 => "%H:%M:%S",
+                _ => "%H:%M:%S%.f",
             };
             match NaiveTime::parse_from_str(s, fmt) {
                 Ok(dt) => Ok(dt),
@@ -54,11 +53,10 @@
 }
 
 /// ISO 8601 combined date and time without timezone =>
-/// "YYYY-MM-DD HH:MM:SS.SSS"
+/// "YYYY-MM-DDTHH:MM:SS.SSS"
 impl ToSql for NaiveDateTime {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
-        let date_str = self.format("%F %T%.f").to_string();
+        let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
         Ok(ToSqlOutput::from(date_str))
     }
 }
@@ -70,9 +68,9 @@
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         value.as_str().and_then(|s| {
             let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
-                "%FT%T%.f"
+                "%Y-%m-%dT%H:%M:%S%.f"
             } else {
-                "%F %T%.f"
+                "%Y-%m-%d %H:%M:%S%.f"
             };
 
             match NaiveDateTime::parse_from_str(s, fmt) {
@@ -83,50 +81,34 @@
     }
 }
 
-/// UTC time => UTC RFC3339 timestamp
-/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
-impl ToSql for DateTime<Utc> {
-    #[inline]
+/// Date and time with time zone => UTC RFC3339 timestamp
+/// ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
+impl<Tz: TimeZone> ToSql for DateTime<Tz> {
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
-        let date_str = self.format("%F %T%.f%:z").to_string();
-        Ok(ToSqlOutput::from(date_str))
+        Ok(ToSqlOutput::from(self.with_timezone(&Utc).to_rfc3339()))
     }
 }
 
-/// Local time => UTC RFC3339 timestamp
-/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
-impl ToSql for DateTime<Local> {
-    #[inline]
-    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
-        let date_str = self.with_timezone(&Utc).format("%F %T%.f%:z").to_string();
-        Ok(ToSqlOutput::from(date_str))
-    }
-}
-
-/// Date and time with time zone => RFC3339 timestamp
-/// ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM").
-impl ToSql for DateTime<FixedOffset> {
-    #[inline]
-    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
-        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>`.
+/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`.
 impl FromSql for DateTime<Utc> {
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         {
             // Try to parse value as rfc3339 first.
             let s = value.as_str()?;
 
-            let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
-                "%FT%T%.f%#z"
+            // If timestamp looks space-separated, make a copy and replace it with 'T'.
+            let s = if s.len() >= 11 && s.as_bytes()[10] == b' ' {
+                let mut s = s.to_string();
+                unsafe {
+                    let sbytes = s.as_mut_vec();
+                    sbytes[10] = b'T';
+                }
+                Cow::Owned(s)
             } else {
-                "%F %T%.f%#z"
+                Cow::Borrowed(s)
             };
 
-            if let Ok(dt) = DateTime::parse_from_str(s, fmt) {
+            if let Ok(dt) = DateTime::parse_from_rfc3339(&s) {
                 return Ok(dt.with_timezone(&Utc));
             }
         }
@@ -136,188 +118,163 @@
     }
 }
 
-/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`.
+/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`.
 impl FromSql for DateTime<Local> {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         let utc_dt = DateTime::<Utc>::column_result(value)?;
         Ok(utc_dt.with_timezone(&Local))
     }
 }
 
-/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<FixedOffset>`.
-impl FromSql for DateTime<FixedOffset> {
-    #[inline]
-    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
-        let s = String::column_result(value)?;
-        Self::parse_from_rfc3339(s.as_str())
-            .or_else(|_| Self::parse_from_str(s.as_str(), "%F %T%.f%:z"))
-            .map_err(|e| FromSqlError::Other(Box::new(e)))
-    }
-}
-
 #[cfg(test)]
 mod test {
-    use crate::{
-        types::{FromSql, ValueRef},
-        Connection, Result,
-    };
-    use chrono::{
-        DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
-    };
+    use crate::{Connection, Result, NO_PARAMS};
+    use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
 
-    fn checked_memory_handle() -> Result<Connection> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)")?;
-        Ok(db)
+    fn checked_memory_handle() -> Connection {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)")
+            .unwrap();
+        db
     }
 
     #[test]
-    fn test_naive_date() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_naive_date() {
+        let db = checked_memory_handle();
         let date = NaiveDate::from_ymd(2016, 2, 23);
-        db.execute("INSERT INTO foo (t) VALUES (?)", [date])?;
+        db.execute("INSERT INTO foo (t) VALUES (?)", &[&date])
+            .unwrap();
 
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let s: String = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!("2016-02-23", s);
-        let t: NaiveDate = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let t: NaiveDate = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(date, t);
-        Ok(())
     }
 
     #[test]
-    fn test_naive_time() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_naive_time() {
+        let db = checked_memory_handle();
         let time = NaiveTime::from_hms(23, 56, 4);
-        db.execute("INSERT INTO foo (t) VALUES (?)", [time])?;
+        db.execute("INSERT INTO foo (t) VALUES (?)", &[&time])
+            .unwrap();
 
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let s: String = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!("23:56:04", s);
-        let v: NaiveTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let v: NaiveTime = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(time, v);
-        Ok(())
     }
 
     #[test]
-    fn test_naive_date_time() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_naive_date_time() {
+        let db = checked_memory_handle();
         let date = NaiveDate::from_ymd(2016, 2, 23);
         let time = NaiveTime::from_hms(23, 56, 4);
         let dt = NaiveDateTime::new(date, time);
 
-        db.execute("INSERT INTO foo (t) VALUES (?)", [dt])?;
+        db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt])
+            .unwrap();
 
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
-        assert_eq!("2016-02-23 23:56:04", s);
-        let v: NaiveDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let s: String = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
+        assert_eq!("2016-02-23T23:56:04", s);
+        let v: NaiveDateTime = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(dt, v);
 
-        db.execute("UPDATE foo set b = datetime(t)", [])?; // "YYYY-MM-DD HH:MM:SS"
-        let hms: NaiveDateTime = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
+        db.execute("UPDATE foo set b = datetime(t)", NO_PARAMS)
+            .unwrap(); // "YYYY-MM-DD HH:MM:SS"
+        let hms: NaiveDateTime = db
+            .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(dt, hms);
-        Ok(())
     }
 
     #[test]
-    fn test_date_time_utc() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_date_time_utc() {
+        let db = checked_memory_handle();
         let date = NaiveDate::from_ymd(2016, 2, 23);
         let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
         let dt = NaiveDateTime::new(date, time);
         let utc = Utc.from_utc_datetime(&dt);
 
-        db.execute("INSERT INTO foo (t) VALUES (?)", [utc])?;
+        db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc])
+            .unwrap();
 
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
-        assert_eq!("2016-02-23 23:56:04.789+00:00", s);
+        let s: String = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
+        assert_eq!("2016-02-23T23:56:04.789+00:00", s);
 
-        let v1: DateTime<Utc> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let v1: DateTime<Utc> = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(utc, v1);
 
-        let v2: DateTime<Utc> =
-            db.query_row("SELECT '2016-02-23 23:56:04.789'", [], |r| r.get(0))?;
+        let v2: DateTime<Utc> = db
+            .query_row("SELECT '2016-02-23 23:56:04.789'", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(utc, v2);
 
-        let v3: DateTime<Utc> = db.query_row("SELECT '2016-02-23 23:56:04'", [], |r| r.get(0))?;
+        let v3: DateTime<Utc> = db
+            .query_row("SELECT '2016-02-23 23:56:04'", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(utc - Duration::milliseconds(789), v3);
 
-        let v4: DateTime<Utc> =
-            db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", [], |r| r.get(0))?;
+        let v4: DateTime<Utc> = db
+            .query_row("SELECT '2016-02-23 23:56:04.789+00:00'", NO_PARAMS, |r| {
+                r.get(0)
+            })
+            .unwrap();
         assert_eq!(utc, v4);
-        Ok(())
     }
 
     #[test]
-    fn test_date_time_local() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_date_time_local() {
+        let db = checked_memory_handle();
         let date = NaiveDate::from_ymd(2016, 2, 23);
         let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
         let dt = NaiveDateTime::new(date, time);
         let local = Local.from_local_datetime(&dt).single().unwrap();
 
-        db.execute("INSERT INTO foo (t) VALUES (?)", [local])?;
+        db.execute("INSERT INTO foo (t) VALUES (?)", &[&local])
+            .unwrap();
 
         // Stored string should be in UTC
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let s: String = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert!(s.ends_with("+00:00"));
 
-        let v: DateTime<Local> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let v: DateTime<Local> = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(local, v);
-        Ok(())
     }
 
     #[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));
+    fn test_sqlite_functions() {
+        let db = checked_memory_handle();
+        let result: Result<NaiveTime> =
+            db.query_row("SELECT CURRENT_TIME", NO_PARAMS, |r| r.get(0));
         assert!(result.is_ok());
-        let result: Result<NaiveDate> = db.query_row("SELECT CURRENT_DATE", [], |r| r.get(0));
+        let result: Result<NaiveDate> =
+            db.query_row("SELECT CURRENT_DATE", NO_PARAMS, |r| r.get(0));
         assert!(result.is_ok());
         let result: Result<NaiveDateTime> =
-            db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
+            db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
         assert!(result.is_ok());
         let result: Result<DateTime<Utc>> =
-            db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
+            db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
         assert!(result.is_ok());
-        Ok(())
-    }
-
-    #[test]
-    fn test_naive_date_time_param() -> Result<()> {
-        let db = checked_memory_handle()?;
-        let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()], |r| r.get(0));
-        assert!(result.is_ok());
-        Ok(())
-    }
-
-    #[test]
-    fn test_date_time_param() -> Result<()> {
-        let db = checked_memory_handle()?;
-        let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()], |r| r.get(0));
-        assert!(result.is_ok());
-        Ok(())
-    }
-
-    #[test]
-    fn test_lenient_parse_timezone() {
-        assert!(DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00Z")).is_ok());
-        assert!(DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00+00")).is_ok());
     }
 }
diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs
index 88bdd14..3fe74b4 100644
--- a/src/types/from_sql.rs
+++ b/src/types/from_sql.rs
@@ -1,9 +1,8 @@
 use super::{Value, ValueRef};
-use std::convert::TryInto;
 use std::error::Error;
 use std::fmt;
 
-/// Enum listing possible errors from [`FromSql`] trait.
+/// Enum listing possible errors from `FromSql` trait.
 #[derive(Debug)]
 #[non_exhaustive]
 pub enum FromSqlError {
@@ -15,16 +14,18 @@
     /// requested type.
     OutOfRange(i64),
 
-    /// Error when the blob result returned by SQLite cannot be stored into the
-    /// requested type due to a size mismatch.
-    InvalidBlobSize {
-        /// The expected size of the blob.
-        expected_size: usize,
-        /// The actual size of the blob that was returned.
-        blob_size: usize,
-    },
+    /// `feature = "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),
 
-    /// An error case available for implementors of the [`FromSql`] trait.
+    /// `feature = "uuid"` Error returned when reading a `uuid` from a blob with
+    /// a size other than 16. Only available when the `uuid` feature is enabled.
+    #[cfg(feature = "uuid")]
+    InvalidUuidSize(usize),
+
+    /// An error case available for implementors of the `FromSql` trait.
     Other(Box<dyn Error + Send + Sync + 'static>),
 }
 
@@ -33,16 +34,10 @@
         match (self, other) {
             (FromSqlError::InvalidType, FromSqlError::InvalidType) => true,
             (FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
-            (
-                FromSqlError::InvalidBlobSize {
-                    expected_size: es1,
-                    blob_size: bs1,
-                },
-                FromSqlError::InvalidBlobSize {
-                    expected_size: es2,
-                    blob_size: bs2,
-                },
-            ) => es1 == es2 && bs1 == bs2,
+            #[cfg(feature = "i128_blob")]
+            (FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
+            #[cfg(feature = "uuid")]
+            (FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
             (..) => false,
         }
     }
@@ -53,15 +48,13 @@
         match *self {
             FromSqlError::InvalidType => write!(f, "Invalid type"),
             FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i),
-            FromSqlError::InvalidBlobSize {
-                expected_size,
-                blob_size,
-            } => {
-                write!(
-                    f,
-                    "Cannot read {} byte value out of {} byte blob",
-                    expected_size, blob_size
-                )
+            #[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::Other(ref err) => err.fmt(f),
         }
@@ -78,22 +71,48 @@
     }
 }
 
-/// Result type for implementors of the [`FromSql`] trait.
+/// Result type for implementors of the `FromSql` trait.
 pub type FromSqlResult<T> = Result<T, FromSqlError>;
 
 /// A trait for types that can be created from a SQLite value.
+///
+/// Note that `FromSql` and `ToSql` are defined for most integral types, but
+/// not `u64` or `usize`. This is intentional; SQLite returns integers as
+/// signed 64-bit values, which cannot fully represent the range of these
+/// types. Rusqlite would have to
+/// decide how to handle negative values: return an error or reinterpret as a
+/// very large postive numbers, neither of which
+/// is guaranteed to be correct for everyone. Callers can work around this by
+/// fetching values as i64 and then doing the interpretation themselves or by
+/// defining a newtype and implementing `FromSql`/`ToSql` for it.
 pub trait FromSql: Sized {
     /// Converts SQLite value into Rust value.
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
 }
 
+impl FromSql for isize {
+    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+        i64::column_result(value).and_then(|i| {
+            if i < isize::min_value() as i64 || i > isize::max_value() as i64 {
+                Err(FromSqlError::OutOfRange(i))
+            } else {
+                Ok(i as isize)
+            }
+        })
+    }
+}
+
 macro_rules! from_sql_integral(
     ($t:ident) => (
         impl FromSql for $t {
-            #[inline]
             fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
-                let i = i64::column_result(value)?;
-                i.try_into().map_err(|_| FromSqlError::OutOfRange(i))
+                i64::column_result(value).and_then(|i| {
+                    if i < i64::from($t::min_value()) || i > i64::from($t::max_value()) {
+                        Err(FromSqlError::OutOfRange(i))
+                    } else {
+                        Ok(i as $t)
+                    }
+                })
             }
         }
     )
@@ -102,34 +121,17 @@
 from_sql_integral!(i8);
 from_sql_integral!(i16);
 from_sql_integral!(i32);
-// from_sql_integral!(i64); // Not needed because the native type is i64.
-from_sql_integral!(isize);
 from_sql_integral!(u8);
 from_sql_integral!(u16);
 from_sql_integral!(u32);
-from_sql_integral!(u64);
-from_sql_integral!(usize);
 
 impl FromSql for i64 {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         value.as_i64()
     }
 }
 
-impl FromSql for f32 {
-    #[inline]
-    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
-        match value {
-            ValueRef::Integer(i) => Ok(i as f32),
-            ValueRef::Real(f) => Ok(f as f32),
-            _ => Err(FromSqlError::InvalidType),
-        }
-    }
-}
-
 impl FromSql for f64 {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         match value {
             ValueRef::Integer(i) => Ok(i as f64),
@@ -140,80 +142,70 @@
 }
 
 impl FromSql for bool {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
-        i64::column_result(value).map(|i| i != 0)
+        i64::column_result(value).map(|i| !matches!(i, 0))
     }
 }
 
 impl FromSql for String {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         value.as_str().map(ToString::to_string)
     }
 }
 
 impl FromSql for Box<str> {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         value.as_str().map(Into::into)
     }
 }
 
 impl FromSql for std::rc::Rc<str> {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         value.as_str().map(Into::into)
     }
 }
 
 impl FromSql for std::sync::Arc<str> {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         value.as_str().map(Into::into)
     }
 }
 
 impl FromSql for Vec<u8> {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
-        value.as_blob().map(<[u8]>::to_vec)
-    }
-}
-
-impl<const N: usize> FromSql for [u8; N] {
-    #[inline]
-    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
-        let slice = value.as_blob()?;
-        slice.try_into().map_err(|_| FromSqlError::InvalidBlobSize {
-            expected_size: N,
-            blob_size: slice.len(),
-        })
+        value.as_blob().map(|b| b.to_vec())
     }
 }
 
 #[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))
+        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()))
+            }
+        })
     }
 }
 
 #[cfg(feature = "uuid")]
-#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
 impl FromSql for uuid::Uuid {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
-        let bytes = <[u8; 16]>::column_result(value)?;
-        Ok(uuid::Uuid::from_u128(u128::from_be_bytes(bytes)))
+        value
+            .as_blob()
+            .and_then(|bytes| {
+                uuid::Builder::from_slice(bytes)
+                    .map_err(|_| FromSqlError::InvalidUuidSize(bytes.len()))
+            })
+            .map(|mut builder| builder.build())
     }
 }
 
 impl<T: FromSql> FromSql for Option<T> {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         match value {
             ValueRef::Null => Ok(None),
@@ -223,7 +215,6 @@
 }
 
 impl FromSql for Value {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         Ok(value.into())
     }
@@ -232,11 +223,15 @@
 #[cfg(test)]
 mod test {
     use super::FromSql;
-    use crate::{Connection, Error, Result};
+    use crate::{Connection, Error};
+
+    fn checked_memory_handle() -> Connection {
+        Connection::open_in_memory().unwrap()
+    }
 
     #[test]
-    fn test_integral_ranges() -> Result<()> {
-        let db = Connection::open_in_memory()?;
+    fn test_integral_ranges() {
+        let db = checked_memory_handle();
 
         fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64])
         where
@@ -271,6 +266,5 @@
         check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
         check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
         check_ranges::<u32>(&db, &[-2, -1, 4_294_967_296], &[0, 1, 4_294_967_295]);
-        Ok(())
     }
 }
diff --git a/src/types/mod.rs b/src/types/mod.rs
index 4e524b2..85d8ef2 100644
--- a/src/types/mod.rs
+++ b/src/types/mod.rs
@@ -1,39 +1,22 @@
 //! Traits dealing with SQLite data types.
 //!
 //! SQLite uses a [dynamic type system](https://www.sqlite.org/datatype3.html). Implementations of
-//! the [`ToSql`] and [`FromSql`] traits are provided for the basic types that
+//! the `ToSql` and `FromSql` traits are provided for the basic types that
 //! SQLite provides methods for:
 //!
+//! * Integers (`i32` and `i64`; SQLite uses `i64` internally, so getting an
+//! `i32` will truncate   if the value is too large or too small).
+//! * Reals (`f64`)
 //! * Strings (`String` and `&str`)
 //! * Blobs (`Vec<u8>` and `&[u8]`)
-//! * Numbers
 //!
-//! The number situation is a little complicated due to the fact that all
-//! numbers in SQLite are stored as `INTEGER` (`i64`) or `REAL` (`f64`).
-//!
-//! [`ToSql`] and [`FromSql`] are implemented for all primitive number types.
-//! [`FromSql`] has different behaviour depending on the SQL and Rust types, and
-//! the value.
-//!
-//! * `INTEGER` to integer: returns an
-//!   [`Error::IntegralValueOutOfRange`](crate::Error::IntegralValueOutOfRange)
-//!   error if the value does not fit in the Rust type.
-//! * `REAL` to integer: always returns an
-//!   [`Error::InvalidColumnType`](crate::Error::InvalidColumnType) error.
-//! * `INTEGER` to float: casts using `as` operator. Never fails.
-//! * `REAL` to float: casts using `as` operator. Never fails.
-//!
-//! [`ToSql`] always succeeds except when storing a `u64` or `usize` value that
-//! cannot fit in an `INTEGER` (`i64`). Also note that SQLite ignores column
-//! types, so if you store an `i64` in a column with type `REAL` it will be
-//! stored as an `INTEGER`, not a `REAL`.
-//!
-//! If the `time` feature is enabled, implementations are
+//! Additionally, if the `time` feature is enabled, implementations are
 //! provided for `time::OffsetDateTime` that use the RFC 3339 date/time format,
 //! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings.  These values
 //! can be parsed by SQLite's builtin
 //! [datetime](https://www.sqlite.org/lang_datefunc.html) functions.  If you
 //! want different storage for datetimes, you can use a newtype.
+//!
 #![cfg_attr(
     feature = "time",
     doc = r##"
@@ -41,33 +24,31 @@
 the Unix epoch:
 
 ```
-use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use rusqlite::types::{FromSql, 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).and_then(|as_i64| {
-            time::OffsetDateTime::from_unix_timestamp(as_i64)
-            .map(|odt| DateTimeSql(odt))
-            .map_err(|err| FromSqlError::Other(Box::new(err)))
+        i64::column_result(value).map(|as_i64| {
+            DateTimeSql(time::OffsetDateTime::from_unix_timestamp(as_i64))
         })
     }
 }
 
 impl ToSql for DateTimeSql {
     fn to_sql(&self) -> Result<ToSqlOutput> {
-        Ok(self.0.unix_timestamp().into())
+        Ok(self.0.timestamp().into())
     }
 }
 ```
 
 "##
 )]
-//! [`ToSql`] and [`FromSql`] are also implemented for `Option<T>` where `T`
-//! implements [`ToSql`] or [`FromSql`] for the cases where you want to know if
-//! a value was NULL (which gets translated to `None`).
+//! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T`
+//! implements `ToSql` or `FromSql` for the cases where you want to know if a
+//! value was NULL (which gets translated to `None`).
 
 pub use self::from_sql::{FromSql, FromSqlError, FromSqlResult};
 pub use self::to_sql::{ToSql, ToSqlOutput};
@@ -77,18 +58,14 @@
 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;
@@ -102,7 +79,7 @@
 /// # use rusqlite::types::{Null};
 ///
 /// fn insert_null(conn: &Connection) -> Result<usize> {
-///     conn.execute("INSERT INTO people (name) VALUES (?)", [Null])
+///     conn.execute("INSERT INTO people (name) VALUES (?)", &[Null])
 /// }
 /// ```
 #[derive(Copy, Clone)]
@@ -127,11 +104,11 @@
 impl fmt::Display for Type {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match *self {
-            Type::Null => f.pad("Null"),
-            Type::Integer => f.pad("Integer"),
-            Type::Real => f.pad("Real"),
-            Type::Text => f.pad("Text"),
-            Type::Blob => f.pad("Blob"),
+            Type::Null => write!(f, "Null"),
+            Type::Integer => write!(f, "Integer"),
+            Type::Real => write!(f, "Real"),
+            Type::Text => write!(f, "Text"),
+            Type::Blob => write!(f, "Blob"),
         }
     }
 }
@@ -139,92 +116,103 @@
 #[cfg(test)]
 mod test {
     use super::Value;
-    use crate::{params, Connection, Error, Result, Statement};
+    use crate::{Connection, Error, NO_PARAMS};
     use std::f64::EPSILON;
     use std::os::raw::{c_double, c_int};
 
-    fn checked_memory_handle() -> Result<Connection> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT, i INTEGER, f FLOAT, n)")?;
-        Ok(db)
+    fn checked_memory_handle() -> Connection {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT, i INTEGER, f FLOAT, n)")
+            .unwrap();
+        db
     }
 
     #[test]
-    fn test_blob() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_blob() {
+        let db = checked_memory_handle();
 
         let v1234 = vec![1u8, 2, 3, 4];
-        db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])?;
+        db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])
+            .unwrap();
 
-        let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
+        let v: Vec<u8> = db
+            .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(v, v1234);
-        Ok(())
     }
 
     #[test]
-    fn test_empty_blob() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_empty_blob() {
+        let db = checked_memory_handle();
 
         let empty = vec![];
-        db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])?;
+        db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])
+            .unwrap();
 
-        let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
+        let v: Vec<u8> = db
+            .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(v, empty);
-        Ok(())
     }
 
     #[test]
-    fn test_str() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_str() {
+        let db = checked_memory_handle();
 
         let s = "hello, world!";
-        db.execute("INSERT INTO foo(t) VALUES (?)", &[&s])?;
+        db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
 
-        let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let from: String = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(from, s);
-        Ok(())
     }
 
     #[test]
-    fn test_string() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_string() {
+        let db = checked_memory_handle();
 
         let s = "hello, world!";
-        db.execute("INSERT INTO foo(t) VALUES (?)", [s.to_owned()])?;
+        db.execute("INSERT INTO foo(t) VALUES (?)", &[s.to_owned()])
+            .unwrap();
 
-        let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let from: String = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(from, s);
-        Ok(())
     }
 
     #[test]
-    fn test_value() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_value() {
+        let db = checked_memory_handle();
 
-        db.execute("INSERT INTO foo(i) VALUES (?)", [Value::Integer(10)])?;
+        db.execute("INSERT INTO foo(i) VALUES (?)", &[Value::Integer(10)])
+            .unwrap();
 
         assert_eq!(
             10i64,
-            db.query_row::<i64, _, _>("SELECT i FROM foo", [], |r| r.get(0))?
+            db.query_row::<i64, _, _>("SELECT i FROM foo", NO_PARAMS, |r| r.get(0))
+                .unwrap()
         );
-        Ok(())
     }
 
     #[test]
-    fn test_option() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_option() {
+        let db = checked_memory_handle();
 
         let s = Some("hello, world!");
         let b = Some(vec![1u8, 2, 3, 4]);
 
-        db.execute("INSERT INTO foo(t) VALUES (?)", &[&s])?;
-        db.execute("INSERT INTO foo(b) VALUES (?)", &[&b])?;
+        db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
+        db.execute("INSERT INTO foo(b) VALUES (?)", &[&b]).unwrap();
 
-        let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")?;
-        let mut rows = stmt.query([])?;
+        let mut stmt = db
+            .prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")
+            .unwrap();
+        let mut rows = stmt.query(NO_PARAMS).unwrap();
 
         {
-            let row1 = rows.next()?.unwrap();
+            let row1 = rows.next().unwrap().unwrap();
             let s1: Option<String> = row1.get_unwrap(0);
             let b1: Option<Vec<u8>> = row1.get_unwrap(1);
             assert_eq!(s.unwrap(), s1.unwrap());
@@ -232,42 +220,42 @@
         }
 
         {
-            let row2 = rows.next()?.unwrap();
+            let row2 = rows.next().unwrap().unwrap();
             let s2: Option<String> = row2.get_unwrap(0);
             let b2: Option<Vec<u8>> = row2.get_unwrap(1);
             assert!(s2.is_none());
             assert_eq!(b, b2);
         }
-        Ok(())
     }
 
     #[test]
     #[allow(clippy::cognitive_complexity)]
-    fn test_mismatched_types() -> Result<()> {
+    fn test_mismatched_types() {
         fn is_invalid_column_type(err: Error) -> bool {
             matches!(err, Error::InvalidColumnType(..))
         }
 
-        let db = checked_memory_handle()?;
+        let db = checked_memory_handle();
 
         db.execute(
             "INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
-            [],
-        )?;
+            NO_PARAMS,
+        )
+        .unwrap();
 
-        let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
-        let mut rows = stmt.query([])?;
+        let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
+        let mut rows = stmt.query(NO_PARAMS).unwrap();
 
-        let row = rows.next()?.unwrap();
+        let row = rows.next().unwrap().unwrap();
 
         // check the correct types come back as expected
-        assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0)?);
-        assert_eq!("text", row.get::<_, String>(1)?);
-        assert_eq!(1, row.get::<_, c_int>(2)?);
-        assert!((1.5 - row.get::<_, c_double>(3)?).abs() < EPSILON);
-        assert_eq!(row.get::<_, Option<c_int>>(4)?, None);
-        assert_eq!(row.get::<_, Option<c_double>>(4)?, None);
-        assert_eq!(row.get::<_, Option<String>>(4)?, None);
+        assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0).unwrap());
+        assert_eq!("text", row.get::<_, String>(1).unwrap());
+        assert_eq!(1, row.get::<_, c_int>(2).unwrap());
+        assert!((1.5 - row.get::<_, c_double>(3).unwrap()).abs() < EPSILON);
+        assert!(row.get::<_, Option<c_int>>(4).unwrap().is_none());
+        assert!(row.get::<_, Option<c_double>>(4).unwrap().is_none());
+        assert!(row.get::<_, Option<String>>(4).unwrap().is_none());
 
         // check some invalid types
 
@@ -352,117 +340,33 @@
         assert!(is_invalid_column_type(
             row.get::<_, time::OffsetDateTime>(4).err().unwrap()
         ));
-        Ok(())
     }
 
     #[test]
-    fn test_dynamic_type() -> Result<()> {
+    fn test_dynamic_type() {
         use super::Value;
-        let db = checked_memory_handle()?;
+        let db = checked_memory_handle();
 
         db.execute(
             "INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
-            [],
-        )?;
+            NO_PARAMS,
+        )
+        .unwrap();
 
-        let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
-        let mut rows = stmt.query([])?;
+        let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
+        let mut rows = stmt.query(NO_PARAMS).unwrap();
 
-        let row = rows.next()?.unwrap();
-        assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0)?);
-        assert_eq!(Value::Text(String::from("text")), row.get::<_, Value>(1)?);
-        assert_eq!(Value::Integer(1), row.get::<_, Value>(2)?);
-        match row.get::<_, Value>(3)? {
+        let row = rows.next().unwrap().unwrap();
+        assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0).unwrap());
+        assert_eq!(
+            Value::Text(String::from("text")),
+            row.get::<_, Value>(1).unwrap()
+        );
+        assert_eq!(Value::Integer(1), row.get::<_, Value>(2).unwrap());
+        match row.get::<_, Value>(3).unwrap() {
             Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
             x => panic!("Invalid Value {:?}", x),
         }
-        assert_eq!(Value::Null, row.get::<_, Value>(4)?);
-        Ok(())
-    }
-
-    macro_rules! test_conversion {
-        ($db_etc:ident, $insert_value:expr, $get_type:ty,expect $expected_value:expr) => {
-            $db_etc.insert_statement.execute(params![$insert_value])?;
-            let res = $db_etc
-                .query_statement
-                .query_row([], |row| row.get::<_, $get_type>(0));
-            assert_eq!(res?, $expected_value);
-            $db_etc.delete_statement.execute([])?;
-        };
-        ($db_etc:ident, $insert_value:expr, $get_type:ty,expect_from_sql_error) => {
-            $db_etc.insert_statement.execute(params![$insert_value])?;
-            let res = $db_etc
-                .query_statement
-                .query_row([], |row| row.get::<_, $get_type>(0));
-            res.unwrap_err();
-            $db_etc.delete_statement.execute([])?;
-        };
-        ($db_etc:ident, $insert_value:expr, $get_type:ty,expect_to_sql_error) => {
-            $db_etc
-                .insert_statement
-                .execute(params![$insert_value])
-                .unwrap_err();
-        };
-    }
-
-    #[test]
-    fn test_numeric_conversions() -> Result<()> {
-        #![allow(clippy::float_cmp)]
-
-        // Test what happens when we store an f32 and retrieve an i32 etc.
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo (x)")?;
-
-        // SQLite actually ignores the column types, so we just need to test
-        // different numeric values.
-
-        struct DbEtc<'conn> {
-            insert_statement: Statement<'conn>,
-            query_statement: Statement<'conn>,
-            delete_statement: Statement<'conn>,
-        }
-
-        let mut db_etc = DbEtc {
-            insert_statement: db.prepare("INSERT INTO foo VALUES (?1)")?,
-            query_statement: db.prepare("SELECT x FROM foo")?,
-            delete_statement: db.prepare("DELETE FROM foo")?,
-        };
-
-        // Basic non-converting test.
-        test_conversion!(db_etc, 0u8, u8, expect 0u8);
-
-        // In-range integral conversions.
-        test_conversion!(db_etc, 100u8, i8, expect 100i8);
-        test_conversion!(db_etc, 200u8, u8, expect 200u8);
-        test_conversion!(db_etc, 100u16, i8, expect 100i8);
-        test_conversion!(db_etc, 200u16, u8, expect 200u8);
-        test_conversion!(db_etc, u32::MAX, u64, expect u32::MAX as u64);
-        test_conversion!(db_etc, i64::MIN, i64, expect i64::MIN);
-        test_conversion!(db_etc, i64::MAX, i64, expect i64::MAX);
-        test_conversion!(db_etc, i64::MAX, u64, expect i64::MAX as u64);
-        test_conversion!(db_etc, 100usize, usize, expect 100usize);
-        test_conversion!(db_etc, 100u64, u64, expect 100u64);
-        test_conversion!(db_etc, i64::MAX as u64, u64, expect i64::MAX as u64);
-
-        // Out-of-range integral conversions.
-        test_conversion!(db_etc, 200u8, i8, expect_from_sql_error);
-        test_conversion!(db_etc, 400u16, i8, expect_from_sql_error);
-        test_conversion!(db_etc, 400u16, u8, expect_from_sql_error);
-        test_conversion!(db_etc, -1i8, u8, expect_from_sql_error);
-        test_conversion!(db_etc, i64::MIN, u64, expect_from_sql_error);
-        test_conversion!(db_etc, u64::MAX, i64, expect_to_sql_error);
-        test_conversion!(db_etc, u64::MAX, u64, expect_to_sql_error);
-        test_conversion!(db_etc, i64::MAX as u64 + 1, u64, expect_to_sql_error);
-
-        // FromSql integer to float, always works.
-        test_conversion!(db_etc, i64::MIN, f32, expect i64::MIN as f32);
-        test_conversion!(db_etc, i64::MAX, f32, expect i64::MAX as f32);
-        test_conversion!(db_etc, i64::MIN, f64, expect i64::MIN as f64);
-        test_conversion!(db_etc, i64::MAX, f64, expect i64::MAX as f64);
-
-        // FromSql float to int conversion, never works even if the actual value
-        // is an integer.
-        test_conversion!(db_etc, 0f64, i64, expect_from_sql_error);
-        Ok(())
+        assert_eq!(Value::Null, row.get::<_, Value>(4).unwrap());
     }
 }
diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs
index a9761bd..abaecda 100644
--- a/src/types/serde_json.rs
+++ b/src/types/serde_json.rs
@@ -1,4 +1,4 @@
-//! [`ToSql`] and [`FromSql`] implementation for JSON `Value`.
+//! `ToSql` and `FromSql` implementation for JSON `Value`.
 
 use serde_json::Value;
 
@@ -7,7 +7,6 @@
 
 /// Serialize JSON `Value` to text.
 impl ToSql for Value {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
     }
@@ -15,39 +14,47 @@
 
 /// Deserialize text/blob to JSON `Value`.
 impl FromSql for Value {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
-        let bytes = value.as_bytes()?;
-        serde_json::from_slice(bytes).map_err(|err| FromSqlError::Other(Box::new(err)))
+        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)))
     }
 }
 
 #[cfg(test)]
 mod test {
     use crate::types::ToSql;
-    use crate::{Connection, Result};
+    use crate::{Connection, NO_PARAMS};
 
-    fn checked_memory_handle() -> Result<Connection> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo (t TEXT, b BLOB)")?;
-        Ok(db)
+    fn checked_memory_handle() -> Connection {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo (t TEXT, b BLOB)")
+            .unwrap();
+        db
     }
 
     #[test]
-    fn test_json_value() -> Result<()> {
-        let db = checked_memory_handle()?;
+    fn test_json_value() {
+        let db = checked_memory_handle();
 
         let json = r#"{"foo": 13, "bar": "baz"}"#;
         let data: serde_json::Value = serde_json::from_str(json).unwrap();
         db.execute(
             "INSERT INTO foo (t, b) VALUES (?, ?)",
             &[&data as &dyn ToSql, &json.as_bytes()],
-        )?;
+        )
+        .unwrap();
 
-        let t: serde_json::Value = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let t: serde_json::Value = db
+            .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(data, t);
-        let b: serde_json::Value = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
+        let b: serde_json::Value = db
+            .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
+            .unwrap();
         assert_eq!(data, b);
-        Ok(())
     }
 }
diff --git a/src/types/time.rs b/src/types/time.rs
index 4e2811e..8589167 100644
--- a/src/types/time.rs
+++ b/src/types/time.rs
@@ -1,35 +1,15 @@
-//! [`ToSql`] and [`FromSql`] implementation for [`time::OffsetDateTime`].
+//! `ToSql` and `FromSql` implementation for [`time::OffsetDateTime`].
 use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
-use crate::{Error, Result};
-use time::format_description::well_known::Rfc3339;
-use time::format_description::FormatItem;
-use time::macros::format_description;
+use crate::Result;
 use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
 
-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]"
-);
+const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S";
+const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%NZ";
+const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%N %z";
 
 impl ToSql for OffsetDateTime {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
-        // 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()))?;
+        let time_string = self.to_offset(UtcOffset::UTC).format(SQLITE_DATETIME_FMT);
         Ok(ToSqlOutput::from(time_string))
     }
 }
@@ -37,29 +17,13 @@
 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() {
-                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)
+                19 => PrimitiveDateTime::parse(s, CURRENT_TIMESTAMP_FMT).map(|d| d.assume_utc()),
+                _ => PrimitiveDateTime::parse(s, SQLITE_DATETIME_FMT)
+                    .map(|d| d.assume_utc())
                     .or_else(|err| {
-                        PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
-                            .map(PrimitiveDateTime::assume_utc)
-                            .map_err(|_| err)
+                        OffsetDateTime::parse(s, SQLITE_DATETIME_FMT_LEGACY).map_err(|_| err)
                     }),
-                _ => OffsetDateTime::parse(s, &OFFSET_SHORT_DATE_TIME_FORMAT),
             }
             .map_err(|err| FromSqlError::Other(Box::new(err)))
         })
@@ -68,20 +32,25 @@
 
 #[cfg(test)]
 mod test {
-    use crate::{Connection, Result};
-    use time::format_description::well_known::Rfc3339;
+    use crate::{Connection, Result, NO_PARAMS};
+    use std::time::Duration;
     use time::OffsetDateTime;
 
+    fn checked_memory_handle() -> Connection {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")
+            .unwrap();
+        db
+    }
+
     #[test]
-    fn test_offset_date_time() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
+    fn test_offset_date_time() {
+        let db = checked_memory_handle();
 
         let mut ts_vec = vec![];
 
-        let make_datetime = |secs: i128, nanos: i128| {
-            OffsetDateTime::from_unix_timestamp_nanos(1_000_000_000 * secs + nanos).unwrap()
-        };
+        let make_datetime =
+            |secs, nanos| OffsetDateTime::from_unix_timestamp(secs) + Duration::from_nanos(nanos);
 
         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)
@@ -91,78 +60,23 @@
         ts_vec.push(make_datetime(10_000_000_000, 0)); //November 20, 2286
 
         for ts in ts_vec {
-            db.execute("INSERT INTO foo(t) VALUES (?)", [ts])?;
+            db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
 
-            let from: OffsetDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+            let from: OffsetDateTime = db
+                .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+                .unwrap();
 
-            db.execute("DELETE FROM foo", [])?;
+            db.execute("DELETE FROM foo", NO_PARAMS).unwrap();
 
             assert_eq!(from, ts);
         }
-        Ok(())
     }
 
     #[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 = Connection::open_in_memory()?;
+    fn test_sqlite_functions() {
+        let db = checked_memory_handle();
         let result: Result<OffsetDateTime> =
-            db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
+            db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
         assert!(result.is_ok());
-        Ok(())
-    }
-
-    #[test]
-    fn test_param() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [OffsetDateTime::now_utc()], |r| r.get(0));
-        assert!(result.is_ok());
-        Ok(())
     }
 }
diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs
index 2445339..937c0f8 100644
--- a/src/types/to_sql.rs
+++ b/src/types/to_sql.rs
@@ -1,12 +1,11 @@
 use super::{Null, Value, ValueRef};
 #[cfg(feature = "array")]
 use crate::vtab::array::Array;
-use crate::{Error, Result};
+use crate::Result;
 use std::borrow::Cow;
-use std::convert::TryFrom;
 
-/// `ToSqlOutput` represents the possible output types for implementers of the
-/// [`ToSql`] trait.
+/// `ToSqlOutput` represents the possible output types for implementors of the
+/// `ToSql` trait.
 #[derive(Clone, Debug, PartialEq)]
 #[non_exhaustive]
 pub enum ToSqlOutput<'a> {
@@ -16,15 +15,13 @@
     /// An owned SQLite-representable value.
     Owned(Value),
 
-    /// A BLOB of the given length that is filled with
+    /// `feature = "blob"` 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),
 }
 
@@ -34,7 +31,6 @@
 where
     &'a T: Into<ValueRef<'a>>,
 {
-    #[inline]
     fn from(t: &'a T) -> Self {
         ToSqlOutput::Borrowed(t.into())
     }
@@ -48,7 +44,6 @@
 macro_rules! from_value(
     ($t:ty) => (
         impl From<$t> for ToSqlOutput<'_> {
-            #[inline]
             fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
         }
     )
@@ -64,7 +59,6 @@
 from_value!(u8);
 from_value!(u16);
 from_value!(u32);
-from_value!(f32);
 from_value!(f64);
 from_value!(Vec<u8>);
 
@@ -72,15 +66,12 @@
 // `i128` needs in `Into<Value>`, but it's probably fine for the moment, and not
 // worth adding another case to Value.
 #[cfg(feature = "i128_blob")]
-#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
 from_value!(i128);
 
 #[cfg(feature = "uuid")]
-#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
 from_value!(uuid::Uuid);
 
 impl ToSql for ToSqlOutput<'_> {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         Ok(match *self {
             ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
@@ -94,36 +85,31 @@
     }
 }
 
-/// A trait for types that can be converted into SQLite values. Returns
-/// [`Error::ToSqlConversionFailure`] if the conversion fails.
+/// A trait for types that can be converted into SQLite values.
 pub trait ToSql {
     /// Converts Rust value to SQLite value
     fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
 }
 
 impl<T: ToSql + ToOwned + ?Sized> ToSql for Cow<'_, T> {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         self.as_ref().to_sql()
     }
 }
 
 impl<T: ToSql + ?Sized> ToSql for Box<T> {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         self.as_ref().to_sql()
     }
 }
 
 impl<T: ToSql + ?Sized> ToSql for std::rc::Rc<T> {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         self.as_ref().to_sql()
     }
 }
 
 impl<T: ToSql + ?Sized> ToSql for std::sync::Arc<T> {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         self.as_ref().to_sql()
     }
@@ -144,7 +130,6 @@
 macro_rules! to_sql_self(
     ($t:ty) => (
         impl ToSql for $t {
-            #[inline]
             fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
                 Ok(ToSqlOutput::from(*self))
             }
@@ -162,91 +147,54 @@
 to_sql_self!(u8);
 to_sql_self!(u16);
 to_sql_self!(u32);
-to_sql_self!(f32);
 to_sql_self!(f64);
 
 #[cfg(feature = "i128_blob")]
-#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
 to_sql_self!(i128);
 
 #[cfg(feature = "uuid")]
-#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
 to_sql_self!(uuid::Uuid);
 
-macro_rules! to_sql_self_fallible(
-    ($t:ty) => (
-        impl ToSql for $t {
-            #[inline]
-            fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
-                Ok(ToSqlOutput::Owned(Value::Integer(
-                    i64::try_from(*self).map_err(
-                        // TODO: Include the values in the error message.
-                        |err| Error::ToSqlConversionFailure(err.into())
-                    )?
-                )))
-            }
-        }
-    )
-);
-
-// Special implementations for usize and u64 because these conversions can fail.
-to_sql_self_fallible!(u64);
-to_sql_self_fallible!(usize);
-
 impl<T: ?Sized> ToSql for &'_ T
 where
     T: ToSql,
 {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         (*self).to_sql()
     }
 }
 
 impl ToSql for String {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         Ok(ToSqlOutput::from(self.as_str()))
     }
 }
 
 impl ToSql for str {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         Ok(ToSqlOutput::from(self))
     }
 }
 
 impl ToSql for Vec<u8> {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         Ok(ToSqlOutput::from(self.as_slice()))
     }
 }
 
-impl<const N: usize> ToSql for [u8; N] {
-    #[inline]
-    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
-        Ok(ToSqlOutput::from(&self[..]))
-    }
-}
-
 impl ToSql for [u8] {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         Ok(ToSqlOutput::from(self))
     }
 }
 
 impl ToSql for Value {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         Ok(ToSqlOutput::from(self))
     }
 }
 
 impl<T: ToSql> ToSql for Option<T> {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         match *self {
             None => Ok(ToSqlOutput::from(Null)),
@@ -273,15 +221,6 @@
     }
 
     #[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";
@@ -361,11 +300,12 @@
 
     #[cfg(feature = "i128_blob")]
     #[test]
-    fn test_i128() -> crate::Result<()> {
-        use crate::Connection;
+    fn test_i128() {
+        use crate::{Connection, NO_PARAMS};
         use std::i128;
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")?;
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")
+            .unwrap();
         db.execute(
             "
             INSERT INTO foo(i128, desc) VALUES
@@ -373,16 +313,21 @@
                 (?, 'neg one'), (?, 'neg two'),
                 (?, 'pos one'), (?, 'pos two'),
                 (?, 'min'), (?, 'max')",
-            [0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
-        )?;
+            &[0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
+        )
+        .unwrap();
 
-        let mut stmt = db.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")?;
+        let mut stmt = db
+            .prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")
+            .unwrap();
 
         let res = stmt
-            .query_map([], |row| {
+            .query_map(NO_PARAMS, |row| {
                 Ok((row.get::<_, i128>(0)?, row.get::<_, String>(1)?))
-            })?
-            .collect::<Result<Vec<_>, _>>()?;
+            })
+            .unwrap()
+            .collect::<Result<Vec<_>, _>>()
+            .unwrap();
 
         assert_eq!(
             res,
@@ -396,35 +341,37 @@
                 (i128::MAX, "max".to_owned()),
             ]
         );
-        Ok(())
     }
 
     #[cfg(feature = "uuid")]
     #[test]
-    fn test_uuid() -> crate::Result<()> {
+    fn test_uuid() {
         use crate::{params, Connection};
         use uuid::Uuid;
 
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);")?;
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);")
+            .unwrap();
 
         let id = Uuid::new_v4();
 
         db.execute(
             "INSERT INTO foo (id, label) VALUES (?, ?)",
             params![id, "target"],
-        )?;
+        )
+        .unwrap();
 
-        let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?")?;
+        let mut stmt = db
+            .prepare("SELECT id, label FROM foo WHERE id = ?")
+            .unwrap();
 
-        let mut rows = stmt.query(params![id])?;
-        let row = rows.next()?.unwrap();
+        let mut rows = stmt.query(params![id]).unwrap();
+        let row = rows.next().unwrap().unwrap();
 
         let found_id: Uuid = row.get_unwrap(0);
         let found_label: String = row.get_unwrap(1);
 
         assert_eq!(found_id, id);
         assert_eq!(found_label, "target");
-        Ok(())
     }
 }
diff --git a/src/types/url.rs b/src/types/url.rs
index fea8500..1c9c63a 100644
--- a/src/types/url.rs
+++ b/src/types/url.rs
@@ -1,11 +1,10 @@
-//! [`ToSql`] and [`FromSql`] implementation for [`url::Url`].
+//! `ToSql` and `FromSql` implementation for [`url::Url`].
 use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
 use crate::Result;
 use url::Url;
 
 /// Serialize `Url` to text.
 impl ToSql for Url {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         Ok(ToSqlOutput::from(self.as_str()))
     }
@@ -13,7 +12,6 @@
 
 /// Deserialize text to `Url`.
 impl FromSql for Url {
-    #[inline]
     fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
         match value {
             ValueRef::Text(s) => {
@@ -30,19 +28,20 @@
     use crate::{params, Connection, Error, Result};
     use url::{ParseError, Url};
 
-    fn checked_memory_handle() -> Result<Connection> {
-        let db = Connection::open_in_memory()?;
-        db.execute_batch("CREATE TABLE urls (i INTEGER, v TEXT)")?;
-        Ok(db)
+    fn checked_memory_handle() -> Connection {
+        let db = Connection::open_in_memory().unwrap();
+        db.execute_batch("CREATE TABLE urls (i INTEGER, v TEXT)")
+            .unwrap();
+        db
     }
 
     fn get_url(db: &Connection, id: i64) -> Result<Url> {
-        db.query_row("SELECT v FROM urls WHERE i = ?", [id], |r| r.get(0))
+        db.query_row("SELECT v FROM urls WHERE i = ?", params![id], |r| r.get(0))
     }
 
     #[test]
-    fn test_sql_url() -> Result<()> {
-        let db = &checked_memory_handle()?;
+    fn test_sql_url() {
+        let db = &checked_memory_handle();
 
         let url0 = Url::parse("http://www.example1.com").unwrap();
         let url1 = Url::parse("http://www.example1.com/👌").unwrap();
@@ -53,15 +52,16 @@
             // also insert a non-hex encoded url (which might be present if it was
             // inserted separately)
             params![url0, url1, url2, "illegal"],
-        )?;
+        )
+        .unwrap();
 
-        assert_eq!(get_url(db, 0)?, url0);
+        assert_eq!(get_url(db, 0).unwrap(), url0);
 
-        assert_eq!(get_url(db, 1)?, url1);
+        assert_eq!(get_url(db, 1).unwrap(), url1);
 
         // Should successfully read it, even though it wasn't inserted as an
         // escaped url.
-        let out_url2: Url = get_url(db, 2)?;
+        let out_url2: Url = get_url(db, 2).unwrap();
         assert_eq!(out_url2, Url::parse(url2).unwrap());
 
         // Make sure the conversion error comes through correctly.
@@ -77,6 +77,5 @@
                 panic!("Expected conversion failure, got {}", e);
             }
         }
-        Ok(())
     }
 }
diff --git a/src/types/value.rs b/src/types/value.rs
index ca3ee9f..64dc203 100644
--- a/src/types/value.rs
+++ b/src/types/value.rs
@@ -3,8 +3,7 @@
 /// Owning [dynamic type value](http://sqlite.org/datatype3.html). Value's type is typically
 /// dictated by SQLite (not by the caller).
 ///
-/// See [`ValueRef`](crate::types::ValueRef) for a non-owning dynamic type
-/// value.
+/// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value.
 #[derive(Clone, Debug, PartialEq)]
 pub enum Value {
     /// The value is a `NULL` value.
@@ -20,41 +19,37 @@
 }
 
 impl From<Null> for Value {
-    #[inline]
     fn from(_: Null) -> Value {
         Value::Null
     }
 }
 
 impl From<bool> for Value {
-    #[inline]
     fn from(i: bool) -> Value {
         Value::Integer(i as i64)
     }
 }
 
 impl From<isize> for Value {
-    #[inline]
     fn from(i: isize) -> Value {
         Value::Integer(i as i64)
     }
 }
 
 #[cfg(feature = "i128_blob")]
-#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
 impl From<i128> for Value {
-    #[inline]
     fn from(i: i128) -> Value {
+        use byteorder::{BigEndian, ByteOrder};
+        let mut buf = vec![0u8; 16];
         // We store these biased (e.g. with the most significant bit flipped)
         // so that comparisons with negative numbers work properly.
-        Value::Blob(i128::to_be_bytes(i ^ (1_i128 << 127)).to_vec())
+        BigEndian::write_i128(&mut buf, i ^ (1i128 << 127));
+        Value::Blob(buf)
     }
 }
 
 #[cfg(feature = "uuid")]
-#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
 impl From<uuid::Uuid> for Value {
-    #[inline]
     fn from(id: uuid::Uuid) -> Value {
         Value::Blob(id.as_bytes().to_vec())
     }
@@ -63,7 +58,6 @@
 macro_rules! from_i64(
     ($t:ty) => (
         impl From<$t> for Value {
-            #[inline]
             fn from(i: $t) -> Value {
                 Value::Integer(i64::from(i))
             }
@@ -79,35 +73,24 @@
 from_i64!(u32);
 
 impl From<i64> for Value {
-    #[inline]
     fn from(i: i64) -> Value {
         Value::Integer(i)
     }
 }
 
-impl From<f32> for Value {
-    #[inline]
-    fn from(f: f32) -> Value {
-        Value::Real(f.into())
-    }
-}
-
 impl From<f64> for Value {
-    #[inline]
     fn from(f: f64) -> Value {
         Value::Real(f)
     }
 }
 
 impl From<String> for Value {
-    #[inline]
     fn from(s: String) -> Value {
         Value::Text(s)
     }
 }
 
 impl From<Vec<u8>> for Value {
-    #[inline]
     fn from(v: Vec<u8>) -> Value {
         Value::Blob(v)
     }
@@ -117,7 +100,6 @@
 where
     T: Into<Value>,
 {
-    #[inline]
     fn from(v: Option<T>) -> Value {
         match v {
             Some(x) => x.into(),
@@ -128,8 +110,6 @@
 
 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 c0d81ca..2f32434 100644
--- a/src/types/value_ref.rs
+++ b/src/types/value_ref.rs
@@ -4,7 +4,7 @@
 /// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the
 /// memory backing this value is owned by SQLite.
 ///
-/// See [`Value`](Value) for an owning dynamic type value.
+/// See [`Value`](enum.Value.html) for an owning dynamic type value.
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum ValueRef<'a> {
     /// The value is a `NULL` value.
@@ -21,8 +21,6 @@
 
 impl ValueRef<'_> {
     /// Returns SQLite fundamental datatype.
-    #[inline]
-    #[must_use]
     pub fn data_type(&self) -> Type {
         match *self {
             ValueRef::Null => Type::Null,
@@ -36,9 +34,7 @@
 
 impl<'a> ValueRef<'a> {
     /// If `self` is case `Integer`, returns the integral value. Otherwise,
-    /// returns [`Err(Error::InvalidColumnType)`](crate::Error::
-    /// InvalidColumnType).
-    #[inline]
+    /// returns `Err(Error::InvalidColumnType)`.
     pub fn as_i64(&self) -> FromSqlResult<i64> {
         match *self {
             ValueRef::Integer(i) => Ok(i),
@@ -46,23 +42,8 @@
         }
     }
 
-    /// 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).
-    #[inline]
+    /// returns `Err(Error::InvalidColumnType)`.
     pub fn as_f64(&self) -> FromSqlResult<f64> {
         match *self {
             ValueRef::Real(f) => Ok(f),
@@ -70,22 +51,8 @@
         }
     }
 
-    /// 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]
+    /// `Err(Error::InvalidColumnType)`.
     pub fn as_str(&self) -> FromSqlResult<&'a str> {
         match *self {
             ValueRef::Text(t) => {
@@ -95,69 +62,17 @@
         }
     }
 
-    /// 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]
+    /// `Err(Error::InvalidColumnType)`.
     pub fn as_blob(&self) -> FromSqlResult<&'a [u8]> {
         match *self {
             ValueRef::Blob(b) => Ok(b),
             _ => Err(FromSqlError::InvalidType),
         }
     }
-
-    /// If `self` is case `Null` returns None.
-    /// If `self` is case `Blob`, returns the byte slice.
-    /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
-    /// InvalidColumnType).
-    #[inline]
-    pub fn as_blob_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
-        match *self {
-            ValueRef::Null => Ok(None),
-            ValueRef::Blob(b) => Ok(Some(b)),
-            _ => Err(FromSqlError::InvalidType),
-        }
-    }
-
-    /// Returns the byte slice that makes up this ValueRef if it's either
-    /// [`ValueRef::Blob`] or [`ValueRef::Text`].
-    #[inline]
-    pub fn as_bytes(&self) -> FromSqlResult<&'a [u8]> {
-        match self {
-            ValueRef::Text(s) | ValueRef::Blob(s) => Ok(s),
-            _ => Err(FromSqlError::InvalidType),
-        }
-    }
-
-    /// If `self` is case `Null` returns None.
-    /// If `self` is [`ValueRef::Blob`] or [`ValueRef::Text`] returns the byte
-    /// slice that makes up this value
-    #[inline]
-    pub fn as_bytes_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
-        match *self {
-            ValueRef::Null => Ok(None),
-            ValueRef::Text(s) | ValueRef::Blob(s) => Ok(Some(s)),
-            _ => Err(FromSqlError::InvalidType),
-        }
-    }
 }
 
 impl From<ValueRef<'_>> for Value {
-    #[inline]
     fn from(borrowed: ValueRef<'_>) -> Value {
         match borrowed {
             ValueRef::Null => Value::Null,
@@ -173,21 +88,18 @@
 }
 
 impl<'a> From<&'a str> for ValueRef<'a> {
-    #[inline]
     fn from(s: &str) -> ValueRef<'_> {
         ValueRef::Text(s.as_bytes())
     }
 }
 
 impl<'a> From<&'a [u8]> for ValueRef<'a> {
-    #[inline]
     fn from(s: &[u8]) -> ValueRef<'_> {
         ValueRef::Blob(s)
     }
 }
 
 impl<'a> From<&'a Value> for ValueRef<'a> {
-    #[inline]
     fn from(value: &'a Value) -> ValueRef<'a> {
         match *value {
             Value::Null => ValueRef::Null,
@@ -203,7 +115,6 @@
 where
     T: Into<ValueRef<'a>>,
 {
-    #[inline]
     fn from(s: Option<T>) -> ValueRef<'a> {
         match s {
             Some(x) => x.into(),
@@ -229,7 +140,7 @@
                     !text.is_null(),
                     "unexpected SQLITE_TEXT value type with NULL data"
                 );
-                let s = from_raw_parts(text.cast::<u8>(), len as usize);
+                let s = from_raw_parts(text as *const u8, len as usize);
                 ValueRef::Text(s)
             }
             ffi::SQLITE_BLOB => {
@@ -247,7 +158,7 @@
                         !blob.is_null(),
                         "unexpected SQLITE_BLOB value type with NULL data"
                     );
-                    ValueRef::Blob(from_raw_parts(blob.cast::<u8>(), len as usize))
+                    ValueRef::Blob(from_raw_parts(blob as *const 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 8fba6b3..af2a06c 100644
--- a/src/unlock_notify.rs
+++ b/src/unlock_notify.rs
@@ -1,17 +1,22 @@
 //! [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 {
@@ -22,33 +27,30 @@
     }
 
     fn fired(&self) {
-        let mut flag = unpoison(self.mutex.lock());
+        let mut flag = self.mutex.lock().unwrap();
         *flag = true;
         self.cond.notify_one();
     }
 
     fn wait(&self) {
-        let mut fired = unpoison(self.mutex.lock());
+        let mut fired = self.mutex.lock().unwrap();
         while !*fired {
-            fired = unpoison(self.cond.wait(fired));
+            fired = self.cond.wait(fired).unwrap();
         }
     }
 }
 
-#[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 {
-        drop(catch_unwind(std::panic::AssertUnwindSafe(|| un.fired())));
+        let _ = 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
@@ -85,19 +87,30 @@
     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};
+    use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior, NO_PARAMS};
     use std::sync::mpsc::sync_channel;
     use std::thread;
     use std::time;
 
     #[test]
-    fn test_unlock_notify() -> Result<()> {
+    fn test_unlock_notify() {
         let url = "file::memory:?cache=shared";
         let flags = OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_URI;
-        let db1 = Connection::open_with_flags(url, flags)?;
-        db1.execute_batch("CREATE TABLE foo (x)")?;
+        let db1 = Connection::open_with_flags(url, flags).unwrap();
+        db1.execute_batch("CREATE TABLE foo (x)").unwrap();
         let (rx, tx) = sync_channel(0);
         let child = thread::spawn(move || {
             let mut db2 = Connection::open_with_flags(url, flags).unwrap();
@@ -109,9 +122,8 @@
             tx2.commit().unwrap();
         });
         assert_eq!(tx.recv().unwrap(), 1);
-        let the_answer: Result<i64> = db1.query_row("SELECT x FROM foo", [], |r| r.get(0));
-        assert_eq!(42i64, the_answer?);
+        let the_answer: Result<i64> = db1.query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0));
+        assert_eq!(42i64, the_answer.unwrap());
         child.join().unwrap();
-        Ok(())
     }
 }
diff --git a/src/util/small_cstr.rs b/src/util/small_cstr.rs
index 4543c62..bc4d9cb 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(&0_u8) {
+        if s.as_bytes().contains(&0u8) {
             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();
@@ -100,7 +100,6 @@
 
 impl std::ops::Deref for SmallCString {
     type Target = CStr;
-
     #[inline]
     fn deref(&self) -> &CStr {
         self.as_cstr()
diff --git a/src/util/sqlite_string.rs b/src/util/sqlite_string.rs
index da261ba..18d462e 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_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.
+    /// 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.
     ///
     /// 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).cast::<c_char>())
+                    NonNull::new(ffi::sqlite3_malloc(len_to_alloc) as *mut c_char)
                 })
                 .unwrap_or_else(|| {
                     use std::alloc::{handle_alloc_error, Layout};
@@ -130,15 +130,14 @@
                     // This is safe:
                     // - `align` is never 0
                     // - `align` is always a power of 2.
-                    // - `size` needs no realignment because it's guaranteed to be aligned
-                    //   (everything is aligned to 1)
-                    // - `size` is also never zero, although this function doesn't actually require
-                    //   it now.
+                    // - `size` needs no realignment because it's guaranteed to be
+                    //   aligned (everything is aligned to 1)
+                    // - `size` is also never zero, although this function doesn't actually require it now.
                     let layout = Layout::from_size_align_unchecked(s.len().saturating_add(1), 1);
                     // Note: This call does not return.
                     handle_alloc_error(layout);
                 });
-            let buf: *mut c_char = res_ptr.as_ptr().cast::<c_char>();
+            let buf: *mut c_char = res_ptr.as_ptr() as *mut 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);
@@ -155,7 +154,6 @@
 }
 
 impl Drop for SqliteMallocString {
-    #[inline]
     fn drop(&mut self) {
         unsafe { ffi::sqlite3_free(self.ptr.as_ptr().cast()) };
     }
diff --git a/src/version.rs b/src/version.rs
index d70af7e..215900b 100644
--- a/src/version.rs
+++ b/src/version.rs
@@ -5,8 +5,6 @@
 /// 3.16.2.
 ///
 /// See [`sqlite3_libversion_number()`](https://www.sqlite.org/c3ref/libversion.html).
-#[inline]
-#[must_use]
 pub fn version_number() -> i32 {
     unsafe { ffi::sqlite3_libversion_number() }
 }
@@ -14,8 +12,6 @@
 /// Returns the SQLite version as a string; e.g., `"3.16.2"` for version 3.16.2.
 ///
 /// See [`sqlite3_libversion()`](https://www.sqlite.org/c3ref/libversion.html).
-#[inline]
-#[must_use]
 pub fn version() -> &'static str {
     let cstr = unsafe { CStr::from_ptr(ffi::sqlite3_libversion()) };
     cstr.to_str()
diff --git a/src/vtab/array.rs b/src/vtab/array.rs
index adfd9c9..644b468 100644
--- a/src/vtab/array.rs
+++ b/src/vtab/array.rs
@@ -1,10 +1,10 @@
-//! Array Virtual Table.
+//! `feature = "array"` Array Virtual Table.
 //!
 //! Note: `rarray`, not `carray` is the name of the table valued function we
 //! define.
 //!
 //! Port of [carray](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/carray.c)
-//! C extension: `https://www.sqlite.org/carray.html`
+//! C extension: https://www.sqlite.org/carray.html
 //!
 //! # Example
 //!
@@ -18,7 +18,7 @@
 //!     // Note: A `Rc<Vec<Value>>` must be used as the parameter.
 //!     let values = Rc::new(v.iter().copied().map(Value::from).collect::<Vec<Value>>());
 //!     let mut stmt = db.prepare("SELECT value from rarray(?);")?;
-//!     let rows = stmt.query_map([values], |row| row.get::<_, i64>(0))?;
+//!     let rows = stmt.query_map(params![values], |row| row.get::<_, i64>(0))?;
 //!     for value in rows {
 //!         println!("{}", value?);
 //!     }
@@ -41,23 +41,22 @@
 
 // http://sqlite.org/bindptr.html
 
-pub(crate) const ARRAY_TYPE: *const c_char = (b"rarray\0" as *const u8).cast::<c_char>();
+pub(crate) const ARRAY_TYPE: *const c_char = b"rarray\0" as *const u8 as *const c_char;
 
 pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
-    drop(Rc::from_raw(p as *const Vec<Value>));
+    let _: Array = Rc::from_raw(p as *const Vec<Value>);
 }
 
 /// Array parameter / pointer
 pub type Array = Rc<Vec<Value>>;
 
 impl ToSql for Array {
-    #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
         Ok(ToSqlOutput::Array(self.clone()))
     }
 }
 
-/// Register the "rarray" module.
+/// `feature = "array"` 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 +90,8 @@
 
     fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
         // Index of the pointer= constraint
-        let mut ptr_idx = false;
-        for (constraint, mut constraint_usage) in info.constraints_and_usages() {
+        let mut ptr_idx = None;
+        for (i, constraint) in info.constraints().enumerate() {
             if !constraint.is_usable() {
                 continue;
             }
@@ -100,17 +99,20 @@
                 continue;
             }
             if let CARRAY_COLUMN_POINTER = constraint.column() {
-                ptr_idx = true;
+                ptr_idx = Some(i);
+            }
+        }
+        if let Some(ptr_idx) = ptr_idx {
+            {
+                let mut constraint_usage = info.constraint_usage(ptr_idx);
                 constraint_usage.set_argv_index(1);
                 constraint_usage.set_omit(true);
             }
-        }
-        if ptr_idx {
-            info.set_estimated_cost(1_f64);
+            info.set_estimated_cost(1f64);
             info.set_estimated_rows(100);
             info.set_idx_num(1);
         } else {
-            info.set_estimated_cost(2_147_483_647_f64);
+            info.set_estimated_cost(2_147_483_647f64);
             info.set_estimated_rows(2_147_483_647);
             info.set_idx_num(0);
         }
@@ -154,7 +156,7 @@
 unsafe impl VTabCursor for ArrayTabCursor<'_> {
     fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> {
         if idx_num > 0 {
-            self.ptr = args.get_array(0);
+            self.ptr = args.get_array(0)?;
         } else {
             self.ptr = None;
         }
@@ -194,30 +196,29 @@
 mod test {
     use crate::types::Value;
     use crate::vtab::array;
-    use crate::{Connection, Result};
+    use crate::Connection;
     use std::rc::Rc;
 
     #[test]
-    fn test_array_module() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        array::load_module(&db)?;
+    fn test_array_module() {
+        let db = Connection::open_in_memory().unwrap();
+        array::load_module(&db).unwrap();
 
         let v = vec![1i64, 2, 3, 4];
         let values: Vec<Value> = v.into_iter().map(Value::from).collect();
         let ptr = Rc::new(values);
         {
-            let mut stmt = db.prepare("SELECT value from rarray(?);")?;
+            let mut stmt = db.prepare("SELECT value from rarray(?);").unwrap();
 
-            let rows = stmt.query_map(&[&ptr], |row| row.get::<_, i64>(0))?;
+            let rows = stmt.query_map(&[&ptr], |row| row.get::<_, i64>(0)).unwrap();
             assert_eq!(2, Rc::strong_count(&ptr));
             let mut count = 0;
             for (i, value) in rows.enumerate() {
-                assert_eq!(i as i64, value? - 1);
+                assert_eq!(i as i64, value.unwrap() - 1);
                 count += 1;
             }
             assert_eq!(4, count);
         }
         assert_eq!(1, Rc::strong_count(&ptr));
-        Ok(())
     }
 }
diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs
index df3529a..79ec5da 100644
--- a/src/vtab/csvtab.rs
+++ b/src/vtab/csvtab.rs
@@ -1,7 +1,7 @@
-//! CSV Virtual Table.
+//! `feature = "csvtab"` CSV Virtual Table.
 //!
 //! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C
-//! extension: `https://www.sqlite.org/csv.html`
+//! extension: https://www.sqlite.org/csv.html
 //!
 //! # Example
 //!
@@ -35,7 +35,7 @@
 };
 use crate::{Connection, Error, Result};
 
-/// Register the "csv" module.
+/// `feature = "csvtab"` Register the "csv" module.
 /// ```sql
 /// CREATE VIRTUAL TABLE vtab USING csv(
 ///   filename=FILENAME -- Name of file containing CSV content
@@ -48,12 +48,12 @@
 /// ```
 pub fn load_module(conn: &Connection) -> Result<()> {
     let aux: Option<()> = None;
-    conn.create_module("csv", read_only_module::<CsvTab>(), aux)
+    conn.create_module("csv", read_only_module::<CSVTab>(), aux)
 }
 
 /// An instance of the CSV virtual table
 #[repr(C)]
-struct CsvTab {
+struct CSVTab {
     /// Base class. Must be first
     base: ffi::sqlite3_vtab,
     /// Name of the CSV file
@@ -65,7 +65,7 @@
     offset_first_row: csv::Position,
 }
 
-impl CsvTab {
+impl CSVTab {
     fn reader(&self) -> Result<csv::Reader<File>, csv::Error> {
         csv::ReaderBuilder::new()
             .has_headers(self.has_headers)
@@ -96,20 +96,20 @@
     }
 }
 
-unsafe impl<'vtab> VTab<'vtab> for CsvTab {
+unsafe impl<'vtab> VTab<'vtab> for CSVTab {
     type Aux = ();
-    type Cursor = CsvTabCursor<'vtab>;
+    type Cursor = CSVTabCursor<'vtab>;
 
     fn connect(
         _: &mut VTabConnection,
         _aux: Option<&()>,
         args: &[&[u8]],
-    ) -> Result<(String, CsvTab)> {
+    ) -> Result<(String, CSVTab)> {
         if args.len() < 4 {
             return Err(Error::ModuleError("no CSV file specified".to_owned()));
         }
 
-        let mut vtab = CsvTab {
+        let mut vtab = CSVTab {
             base: ffi::sqlite3_vtab::default(),
             filename: "".to_owned(),
             has_headers: false,
@@ -122,7 +122,7 @@
 
         let args = &args[3..];
         for c_slice in args {
-            let (param, value) = CsvTab::parameter(c_slice)?;
+            let (param, value) = CSVTab::parameter(c_slice)?;
             match param {
                 "filename" => {
                     if !Path::new(value).exists() {
@@ -166,7 +166,7 @@
                     }
                 }
                 "delimiter" => {
-                    if let Some(b) = CsvTab::parse_byte(value) {
+                    if let Some(b) = CSVTab::parse_byte(value) {
                         vtab.delimiter = b;
                     } else {
                         return Err(Error::ModuleError(format!(
@@ -176,7 +176,7 @@
                     }
                 }
                 "quote" => {
-                    if let Some(b) = CsvTab::parse_byte(value) {
+                    if let Some(b) = CSVTab::parse_byte(value) {
                         if b == b'0' {
                             vtab.quote = 0;
                         } else {
@@ -212,7 +212,7 @@
                     if n_col.is_none() && schema.is_none() {
                         cols = headers
                             .into_iter()
-                            .map(|header| escape_double_quote(header).into_owned())
+                            .map(|header| escape_double_quote(&header).into_owned())
                             .collect();
                     }
                 }
@@ -259,16 +259,16 @@
         Ok(())
     }
 
-    fn open(&self) -> Result<CsvTabCursor<'_>> {
-        Ok(CsvTabCursor::new(self.reader()?))
+    fn open(&self) -> Result<CSVTabCursor<'_>> {
+        Ok(CSVTabCursor::new(self.reader()?))
     }
 }
 
-impl CreateVTab<'_> for CsvTab {}
+impl CreateVTab<'_> for CSVTab {}
 
 /// A cursor for the CSV virtual table
 #[repr(C)]
-struct CsvTabCursor<'vtab> {
+struct CSVTabCursor<'vtab> {
     /// Base class. Must be first
     base: ffi::sqlite3_vtab_cursor,
     /// The CSV reader object
@@ -278,12 +278,12 @@
     /// Values of the current row
     cols: csv::StringRecord,
     eof: bool,
-    phantom: PhantomData<&'vtab CsvTab>,
+    phantom: PhantomData<&'vtab CSVTab>,
 }
 
-impl CsvTabCursor<'_> {
-    fn new<'vtab>(reader: csv::Reader<File>) -> CsvTabCursor<'vtab> {
-        CsvTabCursor {
+impl CSVTabCursor<'_> {
+    fn new<'vtab>(reader: csv::Reader<File>) -> CSVTabCursor<'vtab> {
+        CSVTabCursor {
             base: ffi::sqlite3_vtab_cursor::default(),
             reader,
             row_number: 0,
@@ -294,12 +294,12 @@
     }
 
     /// Accessor to the associated virtual table.
-    fn vtab(&self) -> &CsvTab {
-        unsafe { &*(self.base.pVtab as *const CsvTab) }
+    fn vtab(&self) -> &CSVTab {
+        unsafe { &*(self.base.pVtab as *const CSVTab) }
     }
 }
 
-unsafe impl VTabCursor for CsvTabCursor<'_> {
+unsafe impl VTabCursor for CSVTabCursor<'_> {
     // Only a full table scan is supported.  So `filter` simply rewinds to
     // the beginning.
     fn filter(
@@ -354,7 +354,6 @@
 }
 
 impl From<csv::Error> for Error {
-    #[cold]
     fn from(err: csv::Error) -> Error {
         Error::ModuleError(err.to_string())
     }
@@ -363,45 +362,53 @@
 #[cfg(test)]
 mod test {
     use crate::vtab::csvtab;
-    use crate::{Connection, Result};
+    use crate::{Connection, Result, NO_PARAMS};
     use fallible_iterator::FallibleIterator;
 
     #[test]
-    fn test_csv_module() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        csvtab::load_module(&db)?;
-        db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")?;
+    fn test_csv_module() {
+        let db = Connection::open_in_memory().unwrap();
+        csvtab::load_module(&db).unwrap();
+        db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
+            .unwrap();
 
         {
-            let mut s = db.prepare("SELECT rowid, * FROM vtab")?;
+            let mut s = db.prepare("SELECT rowid, * FROM vtab").unwrap();
             {
                 let headers = s.column_names();
                 assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers);
             }
 
-            let ids: Result<Vec<i32>> = s.query([])?.map(|row| row.get::<_, i32>(0)).collect();
-            let sum = ids?.iter().sum::<i32>();
+            let ids: Result<Vec<i32>> = s
+                .query(NO_PARAMS)
+                .unwrap()
+                .map(|row| row.get::<_, i32>(0))
+                .collect();
+            let sum = ids.unwrap().iter().sum::<i32>();
             assert_eq!(sum, 15);
         }
-        db.execute_batch("DROP TABLE vtab")
+        db.execute_batch("DROP TABLE vtab").unwrap();
     }
 
     #[test]
-    fn test_csv_cursor() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        csvtab::load_module(&db)?;
-        db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")?;
+    fn test_csv_cursor() {
+        let db = Connection::open_in_memory().unwrap();
+        csvtab::load_module(&db).unwrap();
+        db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
+            .unwrap();
 
         {
-            let mut s = db.prepare(
-                "SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
+            let mut s = db
+                .prepare(
+                    "SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
                      v1.rowid < v2.rowid",
-            )?;
+                )
+                .unwrap();
 
-            let mut rows = s.query([])?;
-            let row = rows.next()?.unwrap();
+            let mut rows = s.query(NO_PARAMS).unwrap();
+            let row = rows.next().unwrap().unwrap();
             assert_eq!(row.get_unwrap::<_, i32>(0), 2);
         }
-        db.execute_batch("DROP TABLE vtab")
+        db.execute_batch("DROP TABLE vtab").unwrap();
     }
 }
diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs
index bdb6509..dc3bda6 100644
--- a/src/vtab/mod.rs
+++ b/src/vtab/mod.rs
@@ -1,10 +1,10 @@
-//! Create virtual tables.
+//! `feature = "vtab"` Create virtual tables.
 //!
 //! Follow these steps to create your own virtual table:
-//! 1. Write implementation of [`VTab`] and [`VTabCursor`] traits.
-//! 2. Create an instance of the [`Module`] structure specialized for [`VTab`]
-//! impl. from step 1.
-//! 3. Register your [`Module`] structure using [`Connection::create_module`].
+//! 1. Write implemenation of `VTab` and `VTabCursor` traits.
+//! 2. Create an instance of the `Module` structure specialized for `VTab` impl.
+//! from step 1.
+//! 3. Register your `Module` structure using `Connection.create_module`.
 //! 4. Run a `CREATE VIRTUAL TABLE` command that specifies the new module in the
 //! `USING` clause.
 //!
@@ -57,7 +57,7 @@
 // ffi::sqlite3_vtab => VTab
 // ffi::sqlite3_vtab_cursor => VTabCursor
 
-/// Virtual table module
+/// `feature = "vtab"` Virtual table module
 ///
 /// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
 #[repr(transparent)]
@@ -79,19 +79,17 @@
 // structs are allowed to be zeroed.
 const ZERO_MODULE: ffi::sqlite3_module = unsafe {
     ModuleZeroHack {
-        bytes: [0_u8; std::mem::size_of::<ffi::sqlite3_module>()],
+        bytes: [0u8; std::mem::size_of::<ffi::sqlite3_module>()],
     }
     .module
 };
 
-/// Create a read-only virtual table implementation.
+/// `feature = "vtab"` 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
@@ -124,15 +122,13 @@
     }
 }
 
-/// Create an eponymous only virtual table implementation.
+/// `feature = "vtab"` 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
@@ -191,25 +187,25 @@
     }
 }
 
-/// Virtual table instance trait.
+/// `feature = "vtab"` 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: rusqlite::vtab::sqlite3_vtab,
+///    base: ffi::sqlite3_vtab,
 ///    /* Virtual table implementations will typically add additional fields */
 /// }
 /// ```
 ///
 /// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
 pub unsafe trait VTab<'vtab>: Sized {
-    /// Client data passed to [`Connection::create_module`].
+    /// Client data passed to `Connection::create_module`.
     type Aux;
     /// Specific cursor implementation
     type Cursor: VTabCursor;
@@ -232,7 +228,7 @@
     fn open(&'vtab self) -> Result<Self::Cursor>;
 }
 
-/// Non-eponymous virtual table instance trait.
+/// `feature = "vtab"` Non-eponymous virtual table instance trait.
 ///
 /// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
 pub trait CreateVTab<'vtab>: VTab<'vtab> {
@@ -241,7 +237,7 @@
     /// database connection that is executing the CREATE VIRTUAL TABLE
     /// statement.
     ///
-    /// Call [`connect`](VTab::connect) by default.
+    /// Call `connect` by default.
     /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xcreate_method))
     fn create(
         db: &mut VTabConnection,
@@ -252,7 +248,7 @@
     }
 
     /// Destroy the underlying table implementation. This method undoes the work
-    /// of [`create`](CreateVTab::create).
+    /// of `create`.
     ///
     /// Do nothing by default.
     /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xdestroy_method))
@@ -261,11 +257,11 @@
     }
 }
 
-/// Index constraint operator.
+/// `feature = "vtab"` 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)]
-#[allow(clippy::upper_case_acronyms)]
+#[non_exhaustive]
 pub enum IndexConstraintOp {
     SQLITE_INDEX_CONSTRAINT_EQ,
     SQLITE_INDEX_CONSTRAINT_GT,
@@ -281,8 +277,6 @@
     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
 }
 
@@ -303,36 +297,19 @@
             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),
         }
     }
 }
 
-/// Pass information into and receive the reply from the
-/// [`VTab::best_index`] method.
+/// `feature = "vtab"` 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) };
@@ -342,8 +319,6 @@
     }
 
     /// 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) };
@@ -353,14 +328,11 @@
     }
 
     /// Number of terms in the ORDER BY clause
-    #[inline]
-    #[must_use]
     pub fn num_of_order_by(&self) -> usize {
         unsafe { (*self.0).nOrderBy as usize }
     }
 
-    /// Information about what parameters to pass to [`VTabCursor::filter`].
-    #[inline]
+    /// Information about what parameters to pass to `VTabCursor.filter`.
     pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage<'_> {
         let constraint_usages = unsafe {
             slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
@@ -369,7 +341,6 @@
     }
 
     /// Number used to identify the index
-    #[inline]
     pub fn set_idx_num(&mut self, idx_num: c_int) {
         unsafe {
             (*self.0).idxNum = idx_num;
@@ -377,7 +348,6 @@
     }
 
     /// True if output is already ordered
-    #[inline]
     pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
         unsafe {
             (*self.0).orderByConsumed = if order_by_consumed { 1 } else { 0 };
@@ -385,7 +355,6 @@
     }
 
     /// Estimated cost of using this index
-    #[inline]
     pub fn set_estimated_cost(&mut self, estimated_ost: f64) {
         unsafe {
             (*self.0).estimatedCost = estimated_ost;
@@ -394,8 +363,6 @@
 
     /// Estimated number of rows returned.
     #[cfg(feature = "modern_sqlite")] // SQLite >= 3.8.2
-    #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
-    #[inline]
     pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
         unsafe {
             (*self.0).estimatedRows = estimated_rows;
@@ -408,30 +375,6 @@
     // 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>,
@@ -440,57 +383,46 @@
 impl<'a> Iterator for IndexConstraintIter<'a> {
     type Item = IndexConstraint<'a>;
 
-    #[inline]
     fn next(&mut self) -> Option<IndexConstraint<'a>> {
-        self.iter.next().map(IndexConstraint)
+        self.iter.next().map(|raw| IndexConstraint(raw))
     }
 
-    #[inline]
     fn size_hint(&self) -> (usize, Option<usize>) {
         self.iter.size_hint()
     }
 }
 
-/// WHERE clause constraint.
+/// `feature = "vtab"` 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
     }
 }
 
-/// Information about what parameters to pass to
-/// [`VTabCursor::filter`].
+/// `feature = "vtab"` Information about what parameters to pass to
+/// `VTabCursor.filter`.
 pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
 
 impl IndexConstraintUsage<'_> {
-    /// if `argv_index` > 0, constraint is part of argv to
-    /// [`VTabCursor::filter`]
-    #[inline]
+    /// if `argv_index` > 0, constraint is part of argv to `VTabCursor.filter`
     pub fn set_argv_index(&mut self, argv_index: c_int) {
         self.0.argvIndex = argv_index;
     }
 
     /// if `omit`, do not code a test for this constraint
-    #[inline]
     pub fn set_omit(&mut self, omit: bool) {
         self.0.omit = if omit { 1 } else { 0 };
     }
@@ -504,46 +436,38 @@
 impl<'a> Iterator for OrderByIter<'a> {
     type Item = OrderBy<'a>;
 
-    #[inline]
     fn next(&mut self) -> Option<OrderBy<'a>> {
-        self.iter.next().map(OrderBy)
+        self.iter.next().map(|raw| OrderBy(raw))
     }
 
-    #[inline]
     fn size_hint(&self) -> (usize, Option<usize>) {
         self.iter.size_hint()
     }
 }
 
-/// A column of the ORDER BY clause.
+/// `feature = "vtab"` 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
     }
 }
 
-/// Virtual table cursor trait.
-///
-/// # Safety
+/// `feature = "vtab"` Virtual table cursor trait.
 ///
 /// Implementations must be like:
 /// ```rust,ignore
 /// #[repr(C)]
 /// struct MyTabCursor {
 ///    /// Base class. Must be first
-///    base: rusqlite::vtab::sqlite3_vtab_cursor,
+///    base: ffi::sqlite3_vtab_cursor,
 ///    /* Virtual table implementations will typically add additional fields */
 /// }
 /// ```
@@ -553,8 +477,8 @@
     /// Begin a search of a virtual table.
     /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xfilter_method))
     fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Values<'_>) -> Result<()>;
-    /// Advance cursor to the next row of a result set initiated by
-    /// [`filter`](VTabCursor::filter). (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method))
+    /// Advance cursor to the next row of a result set initiated by `filter`.
+    /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method))
     fn next(&mut self) -> Result<()>;
     /// Must return `false` if the cursor currently points to a valid row of
     /// data, or `true` otherwise.
@@ -570,13 +494,12 @@
     fn rowid(&self) -> Result<i64>;
 }
 
-/// Context is used by [`VTabCursor::column`] to specify the
+/// `feature = "vtab"` Context is used by `VTabCursor.column` to specify the
 /// cell value.
 pub struct Context(*mut ffi::sqlite3_context);
 
 impl Context {
     /// Set current cell value
-    #[inline]
     pub fn set_result<T: ToSql>(&mut self, value: &T) -> Result<()> {
         let t = value.to_sql()?;
         unsafe { set_result(self.0, &t) };
@@ -586,23 +509,19 @@
     // TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html)
 }
 
-/// Wrapper to [`VTabCursor::filter`] arguments, the values
-/// requested by [`VTab::best_index`].
+/// `feature = "vtab"` Wrapper to `VTabCursor.filter` arguments, the values
+/// requested by `VTab.best_index`.
 pub struct Values<'a> {
     args: &'a [*mut ffi::sqlite3_value],
 }
 
 impl Values<'_> {
     /// Returns the number of values.
-    #[inline]
-    #[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()
     }
@@ -616,36 +535,38 @@
             FromSqlError::Other(err) => {
                 Error::FromSqlConversionFailure(idx, value.data_type(), err)
             }
-            FromSqlError::InvalidBlobSize { .. } => {
+            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(_) => {
                 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> {
+    fn get_array(&self, idx: usize) -> Result<Option<array::Array>> {
         use crate::types::Value;
         let arg = self.args[idx];
         let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) };
         if ptr.is_null() {
-            None
+            Ok(None)
         } else {
-            Some(unsafe {
+            Ok(Some(unsafe {
                 let rc = array::Array::from_raw(ptr as *const Vec<Value>);
                 let array = rc.clone();
                 array::Array::into_raw(rc); // don't consume it
                 array
-            })
+            }))
         }
     }
 
     /// Turns `Values` into an iterator.
-    #[inline]
-    #[must_use]
     pub fn iter(&self) -> ValueIter<'_> {
         ValueIter {
             iter: self.args.iter(),
@@ -657,13 +578,12 @@
     type IntoIter = ValueIter<'a>;
     type Item = ValueRef<'a>;
 
-    #[inline]
     fn into_iter(self) -> ValueIter<'a> {
         self.iter()
     }
 }
 
-/// [`Values`] iterator.
+/// `Values` iterator.
 pub struct ValueIter<'a> {
     iter: slice::Iter<'a, *mut ffi::sqlite3_value>,
 }
@@ -671,25 +591,22 @@
 impl<'a> Iterator for ValueIter<'a> {
     type Item = ValueRef<'a>;
 
-    #[inline]
     fn next(&mut self) -> Option<ValueRef<'a>> {
         self.iter
             .next()
             .map(|&raw| unsafe { ValueRef::from_value(raw) })
     }
 
-    #[inline]
     fn size_hint(&self) -> (usize, Option<usize>) {
         self.iter.size_hint()
     }
 }
 
 impl Connection {
-    /// Register a virtual table implementation.
+    /// `feature = "vtab"` Register a virtual table implementation.
     ///
     /// Step 3 of [Creating New Virtual Table
     /// Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
-    #[inline]
     pub fn create_module<'vtab, T: VTab<'vtab>>(
         &self,
         module_name: &str,
@@ -716,7 +633,7 @@
                         self.db(),
                         c_name.as_ptr(),
                         &module.base,
-                        boxed_aux.cast::<c_void>(),
+                        boxed_aux as *mut c_void,
                         Some(free_boxed_value::<T::Aux>),
                     )
                 }
@@ -735,19 +652,17 @@
     }
 }
 
-/// Escape double-quote (`"`) character occurrences by
+/// `feature = "vtab"` Escape double-quote (`"`) character occurences 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)
     }
 }
-/// Dequote string
-#[must_use]
+/// `feature = "vtab"` Dequote string
 pub fn dequote(s: &str) -> &str {
     if s.len() < 2 {
         return s;
@@ -760,12 +675,11 @@
         _ => s,
     }
 }
-/// The boolean can be one of:
+/// `feature = "vtab"` 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")
@@ -786,7 +700,7 @@
 
 // FIXME copy/paste from function.rs
 unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
-    drop(Box::from_raw(p.cast::<T>()));
+    let _: Box<T> = Box::from_raw(p as *mut T);
 }
 
 unsafe extern "C" fn rust_create<'vtab, T>(
@@ -803,7 +717,7 @@
     use std::ffi::CStr;
 
     let mut conn = VTabConnection(db);
-    let aux = aux.cast::<T::Aux>();
+    let aux = aux as *mut T::Aux;
     let args = slice::from_raw_parts(argv, argc as usize);
     let vec = args
         .iter()
@@ -815,7 +729,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.cast::<ffi::sqlite3_vtab>();
+                    *pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab;
                     ffi::SQLITE_OK
                 } else {
                     let err = error_from_sqlite_code(rc, None);
@@ -855,7 +769,7 @@
     use std::ffi::CStr;
 
     let mut conn = VTabConnection(db);
-    let aux = aux.cast::<T::Aux>();
+    let aux = aux as *mut T::Aux;
     let args = slice::from_raw_parts(argv, argc as usize);
     let vec = args
         .iter()
@@ -867,7 +781,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.cast::<ffi::sqlite3_vtab>();
+                    *pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab;
                     ffi::SQLITE_OK
                 } else {
                     let err = error_from_sqlite_code(rc, None);
@@ -900,7 +814,7 @@
 where
     T: VTab<'vtab>,
 {
-    let vt = vtab.cast::<T>();
+    let vt = vtab as *mut T;
     let mut idx_info = IndexInfo(info);
     match (*vt).best_index(&mut idx_info) {
         Ok(_) => ffi::SQLITE_OK,
@@ -924,8 +838,8 @@
     if vtab.is_null() {
         return ffi::SQLITE_OK;
     }
-    let vtab = vtab.cast::<T>();
-    drop(Box::from_raw(vtab));
+    let vtab = vtab as *mut T;
+    let _: Box<T> = Box::from_raw(vtab);
     ffi::SQLITE_OK
 }
 
@@ -936,10 +850,10 @@
     if vtab.is_null() {
         return ffi::SQLITE_OK;
     }
-    let vt = vtab.cast::<T>();
+    let vt = vtab as *mut T;
     match (*vt).destroy() {
         Ok(_) => {
-            drop(Box::from_raw(vt));
+            let _: Box<T> = Box::from_raw(vt);
             ffi::SQLITE_OK
         }
         Err(Error::SqliteFailure(err, s)) => {
@@ -962,11 +876,11 @@
 where
     T: VTab<'vtab>,
 {
-    let vt = vtab.cast::<T>();
+    let vt = vtab as *mut T;
     match (*vt).open() {
         Ok(cursor) => {
             let boxed_cursor: *mut T::Cursor = Box::into_raw(Box::new(cursor));
-            *pp_cursor = boxed_cursor.cast::<ffi::sqlite3_vtab_cursor>();
+            *pp_cursor = boxed_cursor as *mut ffi::sqlite3_vtab_cursor;
             ffi::SQLITE_OK
         }
         Err(Error::SqliteFailure(err, s)) => {
@@ -986,8 +900,8 @@
 where
     C: VTabCursor,
 {
-    let cr = cursor.cast::<C>();
-    drop(Box::from_raw(cr));
+    let cr = cursor as *mut C;
+    let _: Box<C> = Box::from_raw(cr);
     ffi::SQLITE_OK
 }
 
@@ -1027,7 +941,7 @@
 where
     C: VTabCursor,
 {
-    let cr = cursor.cast::<C>();
+    let cr = cursor as *mut C;
     (*cr).eof() as c_int
 }
 
@@ -1039,7 +953,7 @@
 where
     C: VTabCursor,
 {
-    let cr = cursor.cast::<C>();
+    let cr = cursor as *mut C;
     let mut ctxt = Context(ctx);
     result_error(ctx, (*cr).column(&mut ctxt, i))
 }
@@ -1051,7 +965,7 @@
 where
     C: VTabCursor,
 {
-    let cr = cursor.cast::<C>();
+    let cr = cursor as *mut C;
     match (*cr).rowid() {
         Ok(rowid) => {
             *p_rowid = rowid;
@@ -1063,7 +977,6 @@
 
 /// Virtual table cursors can set an error message by assigning a string to
 /// `zErrMsg`.
-#[cold]
 unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
     match result {
         Ok(_) => ffi::SQLITE_OK,
@@ -1082,17 +995,15 @@
 
 /// Virtual tables methods can set an error message by assigning a string to
 /// `zErrMsg`.
-#[cold]
 unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
     if !(*vtab).zErrMsg.is_null() {
-        ffi::sqlite3_free((*vtab).zErrMsg.cast::<c_void>());
+        ffi::sqlite3_free((*vtab).zErrMsg as *mut c_void);
     }
     (*vtab).zErrMsg = alloc(err_msg);
 }
 
 /// To raise an error, the `column` method should use this method to set the
 /// error message and return the error code.
-#[cold]
 unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int {
     match result {
         Ok(_) => ffi::SQLITE_OK,
@@ -1130,13 +1041,10 @@
 }
 
 #[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 f26212a..ed67f16 100644
--- a/src/vtab/series.rs
+++ b/src/vtab/series.rs
@@ -1,8 +1,8 @@
-//! Generate series virtual table.
+//! `feature = "series"` Generate series virtual table.
 //!
 //! Port of C [generate series
 //! "function"](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/series.c):
-//! `https://www.sqlite.org/series.html`
+//! https://www.sqlite.org/series.html
 use std::default::Default;
 use std::marker::PhantomData;
 use std::os::raw::c_int;
@@ -13,9 +13,9 @@
     eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConnection, VTabCursor,
     Values,
 };
-use crate::{Connection, Error, Result};
+use crate::{Connection, Result};
 
-/// Register the "generate_series" module.
+/// `feature = "series"` 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,8 +38,6 @@
         const STEP  = 4;
         // output in descending order
         const DESC  = 8;
-        // output in ascending order
-        const ASC  = 16;
         // Both start and stop
         const BOTH  = QueryPlanFlags::START.bits | QueryPlanFlags::STOP.bits;
     }
@@ -73,42 +71,54 @@
     fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
         // The query plan bitmask
         let mut idx_num: QueryPlanFlags = QueryPlanFlags::empty();
-        // Mask of unusable constraints
-        let mut unusable_mask: QueryPlanFlags = QueryPlanFlags::empty();
-        // Constraints on start, stop, and step
-        let mut a_idx: [Option<usize>; 3] = [None, None, None];
+        // Index of the start= constraint
+        let mut start_idx = None;
+        // Index of the stop= constraint
+        let mut stop_idx = None;
+        // Index of the step= constraint
+        let mut step_idx = None;
         for (i, constraint) in info.constraints().enumerate() {
-            if constraint.column() < SERIES_COLUMN_START {
+            if !constraint.is_usable() {
                 continue;
             }
-            let (i_col, i_mask) = match constraint.column() {
-                SERIES_COLUMN_START => (0, QueryPlanFlags::START),
-                SERIES_COLUMN_STOP => (1, QueryPlanFlags::STOP),
-                SERIES_COLUMN_STEP => (2, QueryPlanFlags::STEP),
-                _ => {
-                    unreachable!()
-                }
-            };
-            if !constraint.is_usable() {
-                unusable_mask |= i_mask;
-            } else if constraint.operator() == IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
-                idx_num |= i_mask;
-                a_idx[i_col] = Some(i);
+            if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
+                continue;
             }
+            match constraint.column() {
+                SERIES_COLUMN_START => {
+                    start_idx = Some(i);
+                    idx_num |= QueryPlanFlags::START;
+                }
+                SERIES_COLUMN_STOP => {
+                    stop_idx = Some(i);
+                    idx_num |= QueryPlanFlags::STOP;
+                }
+                SERIES_COLUMN_STEP => {
+                    step_idx = Some(i);
+                    idx_num |= QueryPlanFlags::STEP;
+                }
+                _ => {}
+            };
         }
-        // Number of arguments that SeriesTabCursor::filter expects
-        let mut n_arg = 0;
-        for j in a_idx.iter().flatten() {
-            n_arg += 1;
-            let mut constraint_usage = info.constraint_usage(*j);
-            constraint_usage.set_argv_index(n_arg);
+
+        let mut num_of_arg = 0;
+        if let Some(start_idx) = start_idx {
+            num_of_arg += 1;
+            let mut constraint_usage = info.constraint_usage(start_idx);
+            constraint_usage.set_argv_index(num_of_arg);
             constraint_usage.set_omit(true);
         }
-        if !(unusable_mask & !idx_num).is_empty() {
-            return Err(Error::SqliteFailure(
-                ffi::Error::new(ffi::SQLITE_CONSTRAINT),
-                None,
-            ));
+        if let Some(stop_idx) = stop_idx {
+            num_of_arg += 1;
+            let mut constraint_usage = info.constraint_usage(stop_idx);
+            constraint_usage.set_argv_index(num_of_arg);
+            constraint_usage.set_omit(true);
+        }
+        if let Some(step_idx) = step_idx {
+            num_of_arg += 1;
+            let mut constraint_usage = info.constraint_usage(step_idx);
+            constraint_usage.set_argv_index(num_of_arg);
+            constraint_usage.set_omit(true);
         }
         if idx_num.contains(QueryPlanFlags::BOTH) {
             // Both start= and stop= boundaries are available.
@@ -123,16 +133,10 @@
             let order_by_consumed = {
                 let mut order_bys = info.order_bys();
                 if let Some(order_by) = order_bys.next() {
-                    if order_by.column() == 0 {
-                        if order_by.is_order_by_desc() {
-                            idx_num |= QueryPlanFlags::DESC;
-                        } else {
-                            idx_num |= QueryPlanFlags::ASC;
-                        }
-                        true
-                    } else {
-                        false
+                    if order_by.is_order_by_desc() {
+                        idx_num |= QueryPlanFlags::DESC;
                     }
+                    true
                 } else {
                     false
                 }
@@ -141,9 +145,7 @@
                 info.set_order_by_consumed(true);
             }
         } else {
-            // If either boundary is missing, we have to generate a huge span
-            // of numbers.  Make this case very expensive so that the query
-            // planner will work hard to avoid it.
+            info.set_estimated_cost(2_147_483_647f64);
             info.set_estimated_rows(2_147_483_647);
         }
         info.set_idx_num(idx_num.bits());
@@ -166,7 +168,7 @@
     row_id: i64,
     /// Current value ("value")
     value: i64,
-    /// Minimum value ("start")
+    /// Mimimum value ("start")
     min_value: i64,
     /// Maximum value ("stop")
     max_value: i64,
@@ -189,10 +191,9 @@
         }
     }
 }
-#[allow(clippy::comparison_chain)]
 unsafe impl VTabCursor for SeriesTabCursor<'_> {
     fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> {
-        let mut idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
+        let idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
         let mut i = 0;
         if idx_num.contains(QueryPlanFlags::START) {
             self.min_value = args.get(i)?;
@@ -208,13 +209,8 @@
         }
         if idx_num.contains(QueryPlanFlags::STEP) {
             self.step = args.get(i)?;
-            if self.step == 0 {
+            if self.step < 1 {
                 self.step = 1;
-            } else if self.step < 0 {
-                self.step = -self.step;
-                if !idx_num.contains(QueryPlanFlags::ASC) {
-                    idx_num |= QueryPlanFlags::DESC;
-                }
             }
         } else {
             self.step = 1;
@@ -277,40 +273,26 @@
 mod test {
     use crate::ffi;
     use crate::vtab::series;
-    use crate::{Connection, Result};
-    use fallible_iterator::FallibleIterator;
+    use crate::{Connection, NO_PARAMS};
 
     #[test]
-    fn test_series_module() -> Result<()> {
+    fn test_series_module() {
         let version = unsafe { ffi::sqlite3_libversion_number() };
         if version < 3_008_012 {
-            return Ok(());
+            return;
         }
 
-        let db = Connection::open_in_memory()?;
-        series::load_module(&db)?;
+        let db = Connection::open_in_memory().unwrap();
+        series::load_module(&db).unwrap();
 
-        let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)")?;
+        let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)").unwrap();
 
-        let series = s.query_map([], |row| row.get::<_, i32>(0))?;
+        let series = s.query_map(NO_PARAMS, |row| row.get::<_, i32>(0)).unwrap();
 
         let mut expected = 0;
         for value in series {
-            assert_eq!(expected, value?);
+            assert_eq!(expected, value.unwrap());
             expected += 5;
         }
-
-        let mut s =
-            db.prepare("SELECT * FROM generate_series WHERE start=1 AND stop=9 AND step=2")?;
-        let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
-        assert_eq!(vec![1, 3, 5, 7, 9], series);
-        let mut s = db.prepare("SELECT * FROM generate_series LIMIT 5")?;
-        let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
-        assert_eq!(vec![0, 1, 2, 3, 4], series);
-        let mut s = db.prepare("SELECT * FROM generate_series(0,32,5) ORDER BY value DESC")?;
-        let series: Vec<i32> = s.query([])?.map(|r| r.get(0)).collect()?;
-        assert_eq!(vec![30, 25, 20, 15, 10, 5, 0], series);
-
-        Ok(())
     }
 }
diff --git a/tests/vtab.rs b/tests/vtab.rs
index fa26459..4b31574 100644
--- a/tests/vtab.rs
+++ b/tests/vtab.rs
@@ -2,7 +2,8 @@
 
 #[cfg(feature = "vtab")]
 #[test]
-fn test_dummy_module() -> rusqlite::Result<()> {
+fn test_dummy_module() {
+    use rusqlite::types::ToSql;
     use rusqlite::vtab::{
         eponymous_only_module, sqlite3_vtab, sqlite3_vtab_cursor, Context, IndexInfo, VTab,
         VTabConnection, VTabCursor, Values,
@@ -83,18 +84,20 @@
         }
     }
 
-    let db = Connection::open_in_memory()?;
+    let db = Connection::open_in_memory().unwrap();
 
-    db.create_module::<DummyTab>("dummy", module, None)?;
+    db.create_module::<DummyTab>("dummy", &module, None)
+        .unwrap();
 
     let version = version_number();
     if version < 3_008_012 {
-        return Ok(());
+        return;
     }
 
-    let mut s = db.prepare("SELECT * FROM dummy()")?;
+    let mut s = db.prepare("SELECT * FROM dummy()").unwrap();
 
-    let dummy = s.query_row([], |row| row.get::<_, i32>(0))?;
+    let dummy = s
+        .query_row(&[] as &[&dyn ToSql], |row| row.get::<_, i32>(0))
+        .unwrap();
     assert_eq!(1, dummy);
-    Ok(())
 }