Update to 0.29.0.

Test: m librusqlite
Change-Id: I8bbc5813fc260ccc7bb9b119b0622990944e2d07
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index 96ed8b8..02fc5c1 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
 {
   "git": {
-    "sha1": "26293a11f595574897e7e5a5b639d1587255c6b9"
+    "sha1": "a1ef4b5b6d647d907810b7e15db34f460abd8ff7"
   },
   "path_in_vcs": ""
 }
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index ca704b4..a1cdbea 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,7 +23,7 @@
     host_supported: true,
     crate_name: "rusqlite",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.28.0",
+    cargo_pkg_version: "0.29.0",
     srcs: ["src/lib.rs"],
     edition: "2018",
     features: [
@@ -31,7 +31,7 @@
         "trace",
     ],
     rustlibs: [
-        "libbitflags-1.3.2",
+        "libbitflags",
         "libfallible_iterator",
         "libfallible_streaming_iterator",
         "libhashlink",
diff --git a/Cargo.toml b/Cargo.toml
index 08b3bc0..28b8336 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@
 [package]
 edition = "2018"
 name = "rusqlite"
-version = "0.28.0"
+version = "0.29.0"
 authors = ["The rusqlite developers"]
 exclude = [
     "/.github/*",
@@ -35,18 +35,18 @@
 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"
+features = ["modern-full"]
+no-default-features = true
 rustdoc-args = [
     "--cfg",
     "docsrs",
 ]
 
 [package.metadata.playground]
-features = ["bundled-full"]
 all-features = false
+features = ["bundled-full"]
 
 [lib]
 name = "rusqlite"
@@ -70,7 +70,7 @@
 harness = false
 
 [dependencies.bitflags]
-version = "1.2"
+version = "2.0"
 
 [dependencies.chrono]
 version = "0.4"
@@ -91,12 +91,8 @@
 [dependencies.hashlink]
 version = "0.8"
 
-[dependencies.lazy_static]
-version = "1.4"
-optional = true
-
 [dependencies.libsqlite3-sys]
-version = "0.25.0"
+version = "0.26.0"
 
 [dependencies.serde_json]
 version = "1.0"
@@ -146,8 +142,8 @@
 
 [features]
 array = ["vtab"]
-backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
-blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
+backup = []
+blob = []
 buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
 bundled = [
     "libsqlite3-sys/bundled",
@@ -173,7 +169,7 @@
     "vtab",
 ]
 extra_check = []
-functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
+functions = []
 hooks = []
 i128_blob = []
 in_gecko = [
@@ -208,16 +204,16 @@
     "window",
 ]
 modern_sqlite = ["libsqlite3-sys/bundled_bindings"]
-release_memory = ["libsqlite3-sys/min_sqlite_version_3_7_16"]
+release_memory = []
 series = ["vtab"]
 session = [
     "libsqlite3-sys/session",
     "hooks",
 ]
 sqlcipher = ["libsqlite3-sys/sqlcipher"]
-trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
+trace = []
 unlock_notify = ["libsqlite3-sys/unlock_notify"]
-vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
+vtab = []
 wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
 window = ["functions"]
 winsqlite3 = ["libsqlite3-sys/winsqlite3"]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index bd81d44..7d79d7b 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,7 +1,7 @@
 [package]
 name = "rusqlite"
 # Note: Update version in README.md when you change this.
-version = "0.28.0"
+version = "0.29.0"
 authors = ["The rusqlite developers"]
 edition = "2018"
 description = "Ergonomic wrapper for SQLite"
@@ -35,16 +35,16 @@
 [features]
 load_extension = []
 # hot-backup interface: 3.6.11 (2009-02-18)
-backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
+backup = []
 # sqlite3_blob_reopen: 3.7.4
-blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
+blob = []
 collation = []
 # sqlite3_create_function_v2: 3.7.3 (2010-10-08)
-functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
+functions = []
 # sqlite3_log: 3.6.23 (2010-03-09)
-trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
+trace = []
 # sqlite3_db_release_memory: 3.7.10 (2012-01-16)
-release_memory = ["libsqlite3-sys/min_sqlite_version_3_7_16"]
+release_memory = []
 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"]
@@ -55,7 +55,7 @@
 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 = []
 csvtab = ["csv", "vtab"]
 # pointer passing interfaces: 3.20.0
 array = ["vtab"]
@@ -67,6 +67,7 @@
 series = ["vtab"]
 # check for invalid query.
 extra_check = []
+# ]3.14.0, last]
 modern_sqlite = ["libsqlite3-sys/bundled_bindings"]
 in_gecko = ["modern_sqlite", "libsqlite3-sys/in_gecko"]
 bundled-windows = ["libsqlite3-sys/bundled-windows"]
@@ -111,13 +112,12 @@
 
 [dependencies]
 time = { version = "0.3.0", features = ["formatting", "macros", "parsing"], optional = true }
-bitflags = "1.2"
+bitflags = "2.0"
 hashlink = "0.8"
 chrono = { version = "0.4", optional = true, default-features = false, features = ["clock"] }
 serde_json = { version = "1.0", optional = true }
 csv = { version = "1.1", optional = true }
 url = { version = "2.1", optional = true }
-lazy_static = { version = "1.4", optional = true }
 fallible-iterator = "0.2"
 fallible-streaming-iterator = "0.1"
 uuid = { version = "1.0", optional = true }
@@ -136,7 +136,7 @@
 
 [dependencies.libsqlite3-sys]
 path = "libsqlite3-sys"
-version = "0.25.0"
+version = "0.26.0"
 
 [[test]]
 name = "config_log"
diff --git a/METADATA b/METADATA
index ea56c4e..17cfd9b 100644
--- a/METADATA
+++ b/METADATA
@@ -11,13 +11,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://static.crates.io/crates/rusqlite/rusqlite-0.28.0.crate"
+    value: "https://static.crates.io/crates/rusqlite/rusqlite-0.29.0.crate"
   }
-  version: "0.28.0"
+  version: "0.29.0"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2022
-    month: 12
-    day: 13
+    year: 2023
+    month: 5
+    day: 25
   }
 }
diff --git a/README.md b/README.md
index fdc2381..76013b8 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@
 # That said, it's not ideal for all scenarios and in particular, generic
 # libraries built around `rusqlite` should probably not enable it, which
 # is why it is not a default feature -- it could become hard to disable.
-rusqlite = { version = "0.28.0", features = ["bundled"] }
+rusqlite = { version = "0.29.0", features = ["bundled"] }
 ```
 
 Simple example usage:
@@ -81,7 +81,7 @@
 
 ### Supported SQLite Versions
 
-The base `rusqlite` package supports SQLite version 3.6.8 or newer. If you need
+The base `rusqlite` package supports SQLite version 3.14.0 or newer. If you need
 support for older versions, please file an issue. Some cargo features require a
 newer SQLite version; see details below.
 
@@ -149,11 +149,11 @@
 * If you use the `bundled`, `bundled-sqlcipher`, or `bundled-sqlcipher-vendored-openssl` features, `libsqlite3-sys` will use the
   [cc](https://crates.io/crates/cc) crate to compile SQLite or SQLCipher from source and
   link against that. This source is embedded in the `libsqlite3-sys` crate and
-  is currently SQLite 3.39.0 (as of `rusqlite` 0.28.0 / `libsqlite3-sys`
-  0.25.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.41.2 (as of `rusqlite` 0.29.0 / `libsqlite3-sys`
+  0.26.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.28.0"
+  version = "0.29.0"
   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 `LIBSQLITE3_FLAGS` variable, which can have a format like `"-USQLITE_ALPHA -DSQLITE_BETA SQLITE_GAMMA ..."`. That would disable the `SQLITE_ALPHA` flag, and set the `SQLITE_BETA` and `SQLITE_GAMMA` flags. (The initial `-D` can be omitted, as on the last one.)
@@ -191,9 +191,7 @@
 `libsqlite3-sys` directly, you can use the same features to choose which
 pregenerated bindings are chosen:
 
-* `min_sqlite_version_3_6_8` - SQLite 3.6.8 bindings (this is the default)
-* `min_sqlite_version_3_6_23` - SQLite 3.6.23 bindings
-* `min_sqlite_version_3_7_7` - SQLite 3.7.7 bindings
+* `min_sqlite_version_3_14_0` - SQLite 3.14.0 bindings (this is the default)
 
 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
diff --git a/patches/Android.bp.diff b/patches/Android.bp.diff
deleted file mode 100644
index 34d5397..0000000
--- a/patches/Android.bp.diff
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/Android.bp b/Android.bp
-index 51830a3..ca704b4 100644
---- a/Android.bp
-+++ b/Android.bp
-@@ -31,7 +31,7 @@ rust_library {
-         "trace",
-     ],
-     rustlibs: [
--        "libbitflags",
-+        "libbitflags-1.3.2",
-         "libfallible_iterator",
-         "libfallible_streaming_iterator",
-         "libhashlink",
diff --git a/src/backup.rs b/src/backup.rs
index 6da01fd..f28ae9a 100644
--- a/src/backup.rs
+++ b/src/backup.rs
@@ -336,7 +336,7 @@
             backup.step(-1)?;
         }
 
-        let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
         assert_eq!(42, the_answer);
 
         src.execute_batch("INSERT INTO foo VALUES(43)")?;
@@ -346,7 +346,7 @@
             backup.run_to_completion(5, Duration::from_millis(250), None)?;
         }
 
-        let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
         assert_eq!(42 + 43, the_answer);
         Ok(())
     }
@@ -368,7 +368,7 @@
             backup.step(-1)?;
         }
 
-        let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
         assert_eq!(42, the_answer);
 
         src.execute_batch("INSERT INTO foo VALUES(43)")?;
@@ -379,7 +379,7 @@
             backup.run_to_completion(5, Duration::from_millis(250), None)?;
         }
 
-        let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
         assert_eq!(42 + 43, the_answer);
         Ok(())
     }
@@ -406,7 +406,7 @@
             backup.step(-1)?;
         }
 
-        let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
         assert_eq!(42, the_answer);
 
         src.execute_batch("INSERT INTO foo VALUES(43)")?;
@@ -421,7 +421,7 @@
             backup.run_to_completion(5, Duration::from_millis(250), None)?;
         }
 
-        let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+        let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
         assert_eq!(42 + 43, the_answer);
         Ok(())
     }
diff --git a/src/blob/mod.rs b/src/blob/mod.rs
index 81c6098..46e3ea8 100644
--- a/src/blob/mod.rs
+++ b/src/blob/mod.rs
@@ -134,7 +134,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 (?)",
+//!     "INSERT INTO test_table (content) VALUES (?1)",
 //!     [ZeroBlob(64)],
 //! )?;
 //!
@@ -175,7 +175,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 (?)",
+//!     "INSERT INTO test_table (content) VALUES (?1)",
 //!     [ZeroBlob(64)],
 //! )?;
 //!
@@ -235,7 +235,7 @@
                 table.as_ptr(),
                 column.as_ptr(),
                 row_id,
-                if read_only { 0 } else { 1 },
+                !read_only as std::os::raw::c_int,
                 &mut blob,
             )
         };
@@ -473,14 +473,14 @@
         assert_eq!(&bytes, b"Clob5");
 
         // should not be able to seek negative or past end
-        assert!(blob.seek(SeekFrom::Current(-20)).is_err());
-        assert!(blob.seek(SeekFrom::End(0)).is_ok());
-        assert!(blob.seek(SeekFrom::Current(1)).is_err());
+        blob.seek(SeekFrom::Current(-20)).unwrap_err();
+        blob.seek(SeekFrom::End(0)).unwrap();
+        blob.seek(SeekFrom::Current(1)).unwrap_err();
 
         // write_all should detect when we return Ok(0) because there is no space left,
         // and return a write error
         blob.reopen(rowid)?;
-        assert!(blob.write_all(b"0123456789x").is_err());
+        blob.write_all(b"0123456789x").unwrap_err();
         Ok(())
     }
 
@@ -519,7 +519,7 @@
             // trying to write too much and then flush should fail
             assert_eq!(8, writer.write(b"01234567").unwrap());
             assert_eq!(8, writer.write(b"01234567").unwrap());
-            assert!(writer.flush().is_err());
+            writer.flush().unwrap_err();
         }
 
         {
@@ -536,7 +536,7 @@
 
             // trying to write_all too much should fail
             writer.write_all(b"aaaaaaaaaabbbbb").unwrap();
-            assert!(writer.flush().is_err());
+            writer.flush().unwrap_err();
         }
 
         {
diff --git a/src/blob/pos_io.rs b/src/blob/pos_io.rs
index ecc7d65..d970ab7 100644
--- a/src/blob/pos_io.rs
+++ b/src/blob/pos_io.rs
@@ -214,7 +214,7 @@
         let mut s = [0u8; 10];
         blob.read_at_exact(&mut s, 0).unwrap();
         assert_eq!(&s, &one2ten, "write should go through");
-        assert!(blob.read_at_exact(&mut s, 1).is_err());
+        blob.read_at_exact(&mut s, 1).unwrap_err();
 
         blob.read_at_exact(&mut s, 0).unwrap();
         assert_eq!(&s, &one2ten, "should be unchanged");
@@ -225,13 +225,13 @@
 
         blob.read_at_exact(&mut fives, 5).unwrap();
         assert_eq!(&fives, &[6u8, 7, 8, 9, 10]);
-        assert!(blob.read_at_exact(&mut fives, 7).is_err());
-        assert!(blob.read_at_exact(&mut fives, 12).is_err());
-        assert!(blob.read_at_exact(&mut fives, 10).is_err());
-        assert!(blob.read_at_exact(&mut fives, i32::MAX as usize).is_err());
-        assert!(blob
-            .read_at_exact(&mut fives, i32::MAX as usize + 1)
-            .is_err());
+        blob.read_at_exact(&mut fives, 7).unwrap_err();
+        blob.read_at_exact(&mut fives, 12).unwrap_err();
+        blob.read_at_exact(&mut fives, 10).unwrap_err();
+        blob.read_at_exact(&mut fives, i32::MAX as usize)
+            .unwrap_err();
+        blob.read_at_exact(&mut fives, i32::MAX as usize + 1)
+            .unwrap_err();
 
         // zero length writes are fine if in bounds
         blob.read_at_exact(&mut [], 10).unwrap();
@@ -242,13 +242,11 @@
         blob.read_at_exact(&mut s, 0).unwrap();
         assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]);
 
-        assert!(blob.write_at(&[100, 99, 98, 97, 96], 6).is_err());
-        assert!(blob
-            .write_at(&[100, 99, 98, 97, 96], i32::MAX as usize)
-            .is_err());
-        assert!(blob
-            .write_at(&[100, 99, 98, 97, 96], i32::MAX as usize + 1)
-            .is_err());
+        blob.write_at(&[100, 99, 98, 97, 96], 6).unwrap_err();
+        blob.write_at(&[100, 99, 98, 97, 96], i32::MAX as usize)
+            .unwrap_err();
+        blob.write_at(&[100, 99, 98, 97, 96], i32::MAX as usize + 1)
+            .unwrap_err();
 
         blob.read_at_exact(&mut s, 0).unwrap();
         assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]);
@@ -265,9 +263,9 @@
             blob.raw_read_at_exact(&mut empty, 0).unwrap().as_ptr(),
             empty.as_ptr().cast(),
         ));
-        assert!(blob.raw_read_at_exact(&mut s2, 5).is_err());
+        blob.raw_read_at_exact(&mut s2, 5).unwrap_err();
 
-        let end_pos = blob.seek(std::io::SeekFrom::Current(0)).unwrap();
+        let end_pos = blob.stream_position().unwrap();
         assert_eq!(end_pos, 1);
         Ok(())
     }
diff --git a/src/busy.rs b/src/busy.rs
index 7297f20..b9a5d40 100644
--- a/src/busy.rs
+++ b/src/busy.rs
@@ -58,11 +58,7 @@
     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);
-            if let Ok(true) = catch_unwind(|| handler_fn(count)) {
-                1
-            } else {
-                0
-            }
+            c_int::from(catch_unwind(|| handler_fn(count)).unwrap_or_default())
         }
         let c = self.db.borrow_mut();
         let r = match callback {
diff --git a/src/cache.rs b/src/cache.rs
index c80a708..be15268 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -17,13 +17,13 @@
     /// # use rusqlite::{Connection, Result};
     /// fn insert_new_people(conn: &Connection) -> Result<()> {
     ///     {
-    ///         let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
+    ///         let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
     ///         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 (?)")?;
+    ///         let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
     ///         stmt.execute(["Bob Jones"])?;
     ///     }
     ///     Ok(())
diff --git a/src/column.rs b/src/column.rs
index aa1f5f7..4413a62 100644
--- a/src/column.rs
+++ b/src/column.rs
@@ -33,7 +33,7 @@
     /// calling this method.
     pub fn column_names(&self) -> Vec<&str> {
         let n = self.column_count();
-        let mut cols = Vec::with_capacity(n as usize);
+        let mut cols = Vec::with_capacity(n);
         for i in 0..n {
             let s = self.column_name_unwrap(i);
             cols.push(s);
@@ -95,6 +95,7 @@
     pub fn column_name(&self, col: usize) -> Result<&str> {
         self.stmt
             .column_name(col)
+            // clippy::or_fun_call (nightly) vs clippy::unnecessary-lazy-evaluations (stable)
             .ok_or(Error::InvalidColumnIndex(col))
             .map(|slice| {
                 str::from_utf8(slice.to_bytes()).expect("Invalid UTF-8 sequence in column name")
@@ -137,7 +138,7 @@
     #[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);
+        let mut cols = Vec::with_capacity(n);
         for i in 0..n {
             let name = self.column_name_unwrap(i);
             let slice = self.stmt.column_decltype(i);
diff --git a/src/config.rs b/src/config.rs
index b295d97..d0fa41a 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -16,12 +16,12 @@
     //SQLITE_DBCONFIG_MAINDBNAME = 1000, /* const char* */
     //SQLITE_DBCONFIG_LOOKASIDE = 1001,  /* void* int int */
     /// Enable or disable the enforcement of foreign key constraints.
-    SQLITE_DBCONFIG_ENABLE_FKEY = 1002,
+    SQLITE_DBCONFIG_ENABLE_FKEY = ffi::SQLITE_DBCONFIG_ENABLE_FKEY,
     /// Enable or disable triggers.
-    SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003,
+    SQLITE_DBCONFIG_ENABLE_TRIGGER = ffi::SQLITE_DBCONFIG_ENABLE_TRIGGER,
     /// Enable or disable the fts3_tokenizer() function which is part of the
     /// FTS3 full-text search engine extension.
-    SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // 3.12.0
+    SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = ffi::SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, // 3.12.0
     //SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005,
     /// In WAL mode, enable or disable the checkpoint operation before closing
     /// the connection.
@@ -115,7 +115,7 @@
             check(ffi::sqlite3_db_config(
                 c.db(),
                 config as c_int,
-                if new_val { 1 } else { 0 },
+                new_val as c_int,
                 &mut val,
             ))?;
             Ok(val != 0)
diff --git a/src/error.rs b/src/error.rs
index 3c264d3..797a216 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -245,7 +245,7 @@
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match *self {
             Error::SqliteFailure(ref err, None) => err.fmt(f),
-            Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
+            Error::SqliteFailure(_, Some(ref s)) => write!(f, "{s}"),
             Error::SqliteSingleThreadedMode => write!(
                 f,
                 "SQLite was compiled or configured for single-threaded use only"
@@ -263,21 +263,21 @@
             }
             Error::IntegralValueOutOfRange(col, val) => {
                 if col != UNKNOWN_COLUMN {
-                    write!(f, "Integer {} out of range at index {}", val, col)
+                    write!(f, "Integer {val} out of range at index {col}")
                 } else {
-                    write!(f, "Integer {} out of range", val)
+                    write!(f, "Integer {val} out of range")
                 }
             }
             Error::Utf8Error(ref err) => err.fmt(f),
             Error::NulError(ref err) => err.fmt(f),
-            Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {}", name),
+            Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {name}"),
             Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
             Error::ExecuteReturnedResults => {
                 write!(f, "Execute returned results - did you mean to call query?")
             }
             Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
-            Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
-            Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
+            Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
+            Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
             Error::InvalidColumnType(i, ref name, ref t) => write!(
                 f,
                 "Invalid column type {} at index: {}, name: {}",
@@ -288,22 +288,22 @@
                 "Wrong number of parameters passed to query. Got {}, needed {}",
                 i1, n1
             ),
-            Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
+            Error::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
 
             #[cfg(feature = "functions")]
             Error::InvalidFunctionParameterType(i, ref t) => {
-                write!(f, "Invalid function parameter type {} at index {}", t, i)
+                write!(f, "Invalid function parameter type {t} at index {i}")
             }
             #[cfg(feature = "vtab")]
             Error::InvalidFilterParameterType(i, ref t) => {
-                write!(f, "Invalid filter parameter type {} at index {}", t, i)
+                write!(f, "Invalid filter parameter type {t} at index {i}")
             }
             #[cfg(feature = "functions")]
             Error::UserFunctionError(ref err) => err.fmt(f),
             Error::ToSqlConversionFailure(ref err) => err.fmt(f),
             Error::InvalidQuery => write!(f, "Query is not read-only"),
             #[cfg(feature = "vtab")]
-            Error::ModuleError(ref desc) => write!(f, "{}", desc),
+            Error::ModuleError(ref desc) => write!(f, "{desc}"),
             #[cfg(feature = "functions")]
             Error::UnwindingPanic => write!(f, "unwinding panic"),
             #[cfg(feature = "functions")]
@@ -317,7 +317,7 @@
                 offset,
                 ref sql,
                 ..
-            } => write!(f, "{} in {} at offset {}", msg, sql, offset),
+            } => write!(f, "{msg} in {sql} at offset {offset}"),
         }
     }
 }
@@ -408,13 +408,13 @@
 }
 
 #[cold]
-#[cfg(not(all(feature = "modern_sqlite", not(feature = "bundled-sqlcipher"))))] // SQLite >= 3.38.0
+#[cfg(not(feature = "modern_sqlite"))] // SQLite >= 3.38.0
 pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, _sql: &str) -> Error {
     error_from_handle(db, code)
 }
 
 #[cold]
-#[cfg(all(feature = "modern_sqlite", not(feature = "bundled-sqlcipher")))] // SQLite >= 3.38.0
+#[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
 pub unsafe fn error_with_offset(db: *mut ffi::sqlite3, code: c_int, sql: &str) -> Error {
     if db.is_null() {
         error_from_sqlite_code(code, None)
diff --git a/src/functions.rs b/src/functions.rs
index 138baac..2ae29a8 100644
--- a/src/functions.rs
+++ b/src/functions.rs
@@ -75,14 +75,9 @@
     // an explicit feature check for that, and this doesn't really warrant one.
     // We'll use the extended code if we're on the bundled version (since it's
     // at least 3.17.0) and the normal constraint error code if not.
-    #[cfg(feature = "modern_sqlite")]
     fn constraint_error_code() -> i32 {
         ffi::SQLITE_CONSTRAINT_FUNCTION
     }
-    #[cfg(not(feature = "modern_sqlite"))]
-    fn constraint_error_code() -> i32 {
-        ffi::SQLITE_CONSTRAINT
-    }
 
     if let Error::SqliteFailure(ref err, ref s) = *err {
         ffi::sqlite3_result_error_code(ctx, err.extended_code);
@@ -168,8 +163,6 @@
     ///
     /// Will panic if `idx` is greater than or equal to
     /// [`self.len()`](Context::len).
-    #[cfg(feature = "modern_sqlite")] // 3.9.0
-    #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
     pub fn get_subtype(&self, idx: usize) -> std::os::raw::c_uint {
         let arg = self.args[idx];
         unsafe { ffi::sqlite3_value_subtype(arg) }
@@ -249,8 +242,6 @@
     }
 
     /// Set the Subtype of an SQL function
-    #[cfg(feature = "modern_sqlite")] // 3.9.0
-    #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
     pub fn set_result_subtype(&self, sub_type: std::os::raw::c_uint) {
         unsafe { ffi::sqlite3_result_subtype(self.ctx, sub_type) };
     }
@@ -829,9 +820,9 @@
             FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
             half,
         )?;
-        let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
+        let result: f64 = db.one_column("SELECT half(6)")?;
 
-        assert!((3f64 - result?).abs() < f64::EPSILON);
+        assert!((3f64 - result).abs() < f64::EPSILON);
         Ok(())
     }
 
@@ -844,12 +835,12 @@
             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() < f64::EPSILON);
+        let result: f64 = db.one_column("SELECT half(6)")?;
+        assert!((3f64 - result).abs() < f64::EPSILON);
 
         db.remove_function("half", 1)?;
-        let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
-        assert!(result.is_err());
+        let result: Result<f64> = db.one_column("SELECT half(6)");
+        result.unwrap_err();
         Ok(())
     }
 
@@ -894,18 +885,14 @@
             regexp_with_auxilliary,
         )?;
 
-        let result: Result<bool> =
-            db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", [], |r| r.get(0));
+        let result: bool = db.one_column("SELECT regexp('l.s[aeiouy]', 'lisa')")?;
 
-        assert!(result?);
+        assert!(result);
 
-        let result: Result<i64> = db.query_row(
-            "SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
-            [],
-            |r| r.get(0),
-        );
+        let result: i64 =
+            db.one_column("SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1")?;
 
-        assert_eq!(2, result?);
+        assert_eq!(2, result);
         Ok(())
     }
 
@@ -933,7 +920,7 @@
             ("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.one_column(query)?;
             assert_eq!(expected, result);
         }
         Ok(())
@@ -952,11 +939,8 @@
             Ok(true)
         })?;
 
-        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.one_column("SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)")?;
         // Doesn't actually matter, we'll assert in the function if there's a problem.
         assert!(res);
         Ok(())
@@ -1007,11 +991,11 @@
 
         // 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.one_column(no_result)?;
         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.one_column(single_sum)?;
         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 \
@@ -1033,11 +1017,11 @@
 
         // 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.one_column(no_result)?;
         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.one_column(single_sum)?;
         assert_eq!(2, result);
         Ok(())
     }
diff --git a/src/hooks.rs b/src/hooks.rs
index 5058a0c..7a87bc3 100644
--- a/src/hooks.rs
+++ b/src/hooks.rs
@@ -181,7 +181,6 @@
         operation: TransactionOperation,
         savepoint_name: &'c str,
     },
-    #[cfg(feature = "modern_sqlite")]
     Recursive,
 }
 
@@ -285,7 +284,6 @@
                 operation: TransactionOperation::from_str(operation_str),
                 savepoint_name,
             },
-            #[cfg(feature = "modern_sqlite")] // 3.8.3
             (ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
             (code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
         }
@@ -428,11 +426,7 @@
                 let boxed_hook: *mut F = p_arg.cast::<F>();
                 (*boxed_hook)()
             });
-            if let Ok(true) = r {
-                1
-            } else {
-                0
-            }
+            c_int::from(r.unwrap_or_default())
         }
 
         // unlike `sqlite3_create_function_v2`, we cannot specify a `xDestroy` with
@@ -570,11 +564,7 @@
                 let boxed_handler: *mut F = p_arg.cast::<F>();
                 (*boxed_handler)()
             });
-            if let Ok(true) = r {
-                1
-            } else {
-                0
-            }
+            c_int::from(r.unwrap_or_default())
         }
 
         if let Some(handler) = handler {
diff --git a/src/inner_connection.rs b/src/inner_connection.rs
index e5bc3f1..275e846 100644
--- a/src/inner_connection.rs
+++ b/src/inner_connection.rs
@@ -69,13 +69,13 @@
 
         // Replicate the check for sane open flags from SQLite, because the check in
         // SQLite itself wasn't added until version 3.7.3.
-        debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits, 0x02);
-        debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits, 0x04);
+        debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits(), 0x02);
+        debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits(), 0x04);
         debug_assert_eq!(
-            1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits,
+            1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits(),
             0x40
         );
-        if (1 << (flags.bits & 0x7)) & 0x46 == 0 {
+        if (1 << (flags.bits() & 0x7)) & 0x46 == 0 {
             return Err(Error::SqliteFailure(
                 ffi::Error::new(ffi::SQLITE_MISUSE),
                 None,
@@ -105,7 +105,7 @@
                     {
                         e = Error::SqliteFailure(
                             ffi::Error::new(r),
-                            Some(format!("{}: {}", msg, c_path.to_string_lossy())),
+                            Some(format!("{msg}: {}", c_path.to_string_lossy())),
                         );
                     }
                     ffi::sqlite3_close(db);
@@ -295,7 +295,6 @@
         unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 }
     }
 
-    #[cfg(feature = "modern_sqlite")] // 3.8.6
     pub fn is_busy(&self) -> bool {
         let db = self.db();
         unsafe {
@@ -310,7 +309,6 @@
         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()) })
     }
@@ -319,7 +317,6 @@
     #[inline]
     fn remove_hooks(&mut self) {}
 
-    #[cfg(feature = "modern_sqlite")] // 3.7.11
     pub fn db_readonly(&self, db_name: super::DatabaseName<'_>) -> Result<bool> {
         let name = db_name.as_cstring()?;
         let r = unsafe { ffi::sqlite3_db_readonly(self.db, name.as_ptr()) };
@@ -328,7 +325,7 @@
             1 => Ok(true),
             -1 => Err(Error::SqliteFailure(
                 ffi::Error::new(ffi::SQLITE_MISUSE),
-                Some(format!("{:?} is not the name of a database", db_name)),
+                Some(format!("{db_name:?} is not the name of a database")),
             )),
             _ => Err(error_from_sqlite_code(
                 r,
@@ -354,7 +351,7 @@
             2 => Ok(super::transaction::TransactionState::Write),
             -1 => Err(Error::SqliteFailure(
                 ffi::Error::new(ffi::SQLITE_MISUSE),
-                Some(format!("{:?} is not the name of a valid schema", db_name)),
+                Some(format!("{db_name:?} is not the name of a valid schema")),
             )),
             _ => Err(error_from_sqlite_code(
                 r,
@@ -374,15 +371,7 @@
     #[allow(unused_must_use)]
     #[inline]
     fn drop(&mut self) {
-        use std::thread::panicking;
-
-        if let Err(e) = self.close() {
-            if panicking() {
-                eprintln!("Error while closing SQLite connection: {:?}", e);
-            } else {
-                panic!("Error while closing SQLite connection: {:?}", e);
-            }
-        }
+        self.close();
     }
 }
 
diff --git a/src/lib.rs b/src/lib.rs
index 89f133e..1d982f6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -62,7 +62,7 @@
 use std::fmt;
 use std::os::raw::{c_char, c_int};
 
-use std::path::{Path, PathBuf};
+use std::path::Path;
 use std::result;
 use std::str;
 use std::sync::atomic::Ordering;
@@ -140,13 +140,6 @@
 
 // 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([])`"]
-pub const NO_PARAMS: &[&dyn ToSql] = &[];
 
 /// A macro making it more convenient to longer lists of
 /// parameters as a `&[&dyn ToSql]`.
@@ -314,12 +307,6 @@
 
 // Currently DatabaseName is only used by the backup and blob mods, so hide
 // this (private) impl to avoid dead code warnings.
-#[cfg(any(
-    feature = "backup",
-    feature = "blob",
-    feature = "session",
-    feature = "modern_sqlite"
-))]
 impl DatabaseName<'_> {
     #[inline]
     fn as_cstring(&self) -> Result<SmallCString> {
@@ -336,7 +323,6 @@
 pub struct Connection {
     db: RefCell<InnerConnection>,
     cache: StatementCache,
-    path: Option<PathBuf>,
 }
 
 unsafe impl Send for Connection {}
@@ -433,7 +419,6 @@
         InnerConnection::open_with_flags(&c_path, flags, None).map(|db| Connection {
             db: RefCell::new(db),
             cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
-            path: Some(path.as_ref().to_path_buf()),
         })
     }
 
@@ -458,7 +443,6 @@
         InnerConnection::open_with_flags(&c_path, flags, Some(&c_vfs)).map(|db| Connection {
             db: RefCell::new(db),
             cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
-            path: Some(path.as_ref().to_path_buf()),
         })
     }
 
@@ -540,7 +524,7 @@
     /// ```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 = ?1", [1i32]) {
     ///         Ok(updated) => println!("{} rows were updated", updated),
     ///         Err(err) => println!("update failed: {}", err),
     ///     }
@@ -586,12 +570,23 @@
 
     /// Returns the path to the database file, if one exists and is known.
     ///
+    /// Returns `Some("")` for a temporary or in-memory database.
+    ///
     /// 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()
+    pub fn path(&self) -> Option<&str> {
+        unsafe {
+            let db = self.handle();
+            let db_name = DatabaseName::Main.as_cstring().unwrap();
+            let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
+            if db_filename.is_null() {
+                None
+            } else {
+                CStr::from_ptr(db_filename).to_str().ok()
+            }
+        }
     }
 
     /// Attempts to free as much heap memory as possible from the database
@@ -604,26 +599,6 @@
         self.db.borrow_mut().release_memory()
     }
 
-    /// 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`).
-    ///
-    /// # 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))
-        })
-    }
-
     /// Get the SQLite rowid of the most recent successful INSERT.
     ///
     /// Uses [sqlite3_last_insert_rowid](https://www.sqlite.org/c3ref/last_insert_rowid.html) under
@@ -671,26 +646,10 @@
         stmt.query_row(params, f)
     }
 
-    /// Convenience method to execute a query with named parameter(s) that is
-    /// expected to return a single row.
-    ///
-    /// If the query returns more than one row, all rows except the first are
-    /// ignored.
-    ///
-    /// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
-    /// query truly is optional, you can call `.optional()` on the result of
-    /// this to get a `Result<Option<T>>`.
-    ///
-    /// # 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>(&self, sql: &str, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
-    where
-        F: FnOnce(&Row<'_>) -> Result<T>,
-    {
-        self.query_row(sql, params, f)
+    // https://sqlite.org/tclsqlite.html#onecolumn
+    #[cfg(test)]
+    pub(crate) fn one_column<T: crate::types::FromSql>(&self, sql: &str) -> Result<T> {
+        self.query_row(sql, [], |r| r.get(0))
     }
 
     /// Convenience method to execute a query that is expected to return a
@@ -739,7 +698,7 @@
     /// ```rust,no_run
     /// # use rusqlite::{Connection, Result};
     /// fn insert_new_people(conn: &Connection) -> Result<()> {
-    ///     let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?)")?;
+    ///     let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?1)")?;
     ///     stmt.execute(["Joe Smith"])?;
     ///     stmt.execute(["Bob Jones"])?;
     ///     Ok(())
@@ -921,12 +880,30 @@
     /// 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);
         Ok(Connection {
             db: RefCell::new(db),
             cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
-            path: db_path,
+        })
+    }
+
+    /// Create a `Connection` from a raw owned handle.
+    ///
+    /// The returned connection will attempt to close the inner connection
+    /// when dropped/closed. This function should only be called on connections
+    /// owned by the caller.
+    ///
+    /// # Safety
+    ///
+    /// This function is unsafe because improper use may impact the Connection.
+    /// In particular, it should only be called on connections created
+    /// and owned by the caller, e.g. as a result of calling ffi::sqlite3_open().
+    #[inline]
+    pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result<Connection> {
+        let db = InnerConnection::new(db, true);
+        Ok(Connection {
+            db: RefCell::new(db),
+            cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
         })
     }
 
@@ -961,22 +938,16 @@
 
     /// 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()
     }
 
     /// Determine if a database is read-only
-    #[cfg(feature = "modern_sqlite")] // 3.7.11
-    #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
     pub fn is_readonly(&self, db_name: DatabaseName<'_>) -> Result<bool> {
         self.db.borrow().db_readonly(db_name)
     }
@@ -985,7 +956,7 @@
 impl fmt::Debug for Connection {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_struct("Connection")
-            .field("path", &self.path)
+            .field("path", &self.path())
             .finish()
     }
 }
@@ -1058,6 +1029,7 @@
     /// The default open flags are `SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE
     /// | SQLITE_OPEN_URI | SQLITE_OPEN_NO_MUTEX`. See [`Connection::open`] for
     /// some discussion about these flags.
+    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
     #[repr(C)]
     pub struct OpenFlags: ::std::os::raw::c_int {
         /// The database is opened in read-only mode.
@@ -1070,9 +1042,9 @@
         /// The database is created if it does not already exist
         const SQLITE_OPEN_CREATE = ffi::SQLITE_OPEN_CREATE;
         /// The filename can be interpreted as a URI if this flag is set.
-        const SQLITE_OPEN_URI = 0x0000_0040;
+        const SQLITE_OPEN_URI = ffi::SQLITE_OPEN_URI;
         /// The database will be opened as an in-memory database.
-        const SQLITE_OPEN_MEMORY = 0x0000_0080;
+        const SQLITE_OPEN_MEMORY = ffi::SQLITE_OPEN_MEMORY;
         /// The new database connection will not use a per-connection mutex (the
         /// connection will use the "multi-thread" threading mode, in SQLite
         /// parlance).
@@ -1176,21 +1148,6 @@
     }
 }
 
-#[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_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
-    if db_filename.is_null() {
-        None
-    } else {
-        CStr::from_ptr(db_filename).to_str().ok().map(PathBuf::from)
-    }
-}
-#[cfg(not(feature = "modern_sqlite"))]
-unsafe fn db_filename(_: *mut ffi::sqlite3) -> Option<PathBuf> {
-    None
-}
-
 #[cfg(doctest)]
 doc_comment::doctest!("../README.md");
 
@@ -1277,26 +1234,40 @@
         }
 
         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)?;
+        let the_answer: i64 = db.one_column("SELECT x FROM foo")?;
 
-        assert_eq!(42i64, the_answer?);
+        assert_eq!(42i64, the_answer);
         Ok(())
     }
 
     #[test]
     fn test_open() {
-        assert!(Connection::open_in_memory().is_ok());
+        Connection::open_in_memory().unwrap();
 
         let db = checked_memory_handle();
-        assert!(db.close().is_ok());
+        db.close().unwrap();
+    }
+
+    #[test]
+    fn test_path() -> Result<()> {
+        let tmp = tempfile::tempdir().unwrap();
+        let db = Connection::open("")?;
+        assert_eq!(Some(""), db.path());
+        let db = Connection::open_in_memory()?;
+        assert_eq!(Some(""), db.path());
+        let db = Connection::open("file:dummy.db?mode=memory&cache=shared")?;
+        assert_eq!(Some(""), db.path());
+        let path = tmp.path().join("file.db");
+        let db = Connection::open(path)?;
+        assert!(db.path().map(|p| p.ends_with("file.db")).unwrap_or(false));
+        Ok(())
     }
 
     #[test]
     fn test_open_failure() {
         let filename = "no_such_file.db";
         let result = Connection::open_with_flags(filename, OpenFlags::SQLITE_OPEN_READ_ONLY);
-        assert!(result.is_err());
         let err = result.unwrap_err();
         if let Error::SqliteFailure(e, Some(msg)) = err {
             assert_eq!(ErrorCode::CannotOpen, e.code);
@@ -1336,9 +1307,9 @@
         }
 
         let db = Connection::open(&db_path)?;
-        let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
+        let the_answer: i64 = db.one_column("SELECT x FROM foo")?;
 
-        assert_eq!(42i64, the_answer?);
+        assert_eq!(42i64, the_answer);
         Ok(())
     }
 
@@ -1391,7 +1362,7 @@
             OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_READ_WRITE,
             OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_CREATE,
         ] {
-            assert!(Connection::open_in_memory_with_flags(*bad_flags).is_err());
+            Connection::open_in_memory_with_flags(*bad_flags).unwrap_err();
         }
     }
 
@@ -1409,7 +1380,7 @@
 
         db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3")?;
 
-        assert!(db.execute_batch("INVALID SQL").is_err());
+        db.execute_batch("INVALID SQL").unwrap_err();
         Ok(())
     }
 
@@ -1418,13 +1389,10 @@
         let db = Connection::open_in_memory()?;
         db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
 
-        assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [1i32])?);
-        assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [2i32])?);
+        assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?1)", [1i32])?);
+        assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?1)", [2i32])?);
 
-        assert_eq!(
-            3i32,
-            db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
-        );
+        assert_eq!(3i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?);
         Ok(())
     }
 
@@ -1432,7 +1400,7 @@
     #[cfg(feature = "extra_check")]
     fn test_execute_select() {
         let db = checked_memory_handle();
-        let err = db.execute("SELECT 1 WHERE 1 < ?", [1i32]).unwrap_err();
+        let err = db.execute("SELECT 1 WHERE 1 < ?1", [1i32]).unwrap_err();
         assert_eq!(
             err,
             Error::ExecuteReturnedResults,
@@ -1477,7 +1445,7 @@
         let db = Connection::open_in_memory()?;
         db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
 
-        let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
+        let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?1)")?;
         assert_eq!(insert_stmt.execute([1i32])?, 1);
         assert_eq!(insert_stmt.execute([2i32])?, 1);
         assert_eq!(insert_stmt.execute([3i32])?, 1);
@@ -1486,7 +1454,7 @@
         assert_eq!(insert_stmt.execute(["goodbye"])?, 1);
         assert_eq!(insert_stmt.execute([types::Null])?, 1);
 
-        let mut update_stmt = db.prepare("UPDATE foo SET x=? WHERE x<?")?;
+        let mut update_stmt = db.prepare("UPDATE foo SET x=?1 WHERE x<?2")?;
         assert_eq!(update_stmt.execute([3i32, 3i32])?, 2);
         assert_eq!(update_stmt.execute([3i32, 3i32])?, 0);
         assert_eq!(update_stmt.execute([8i32, 8i32])?, 3);
@@ -1498,12 +1466,12 @@
         let db = Connection::open_in_memory()?;
         db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
 
-        let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
+        let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?1)")?;
         assert_eq!(insert_stmt.execute([1i32])?, 1);
         assert_eq!(insert_stmt.execute([2i32])?, 1);
         assert_eq!(insert_stmt.execute([3i32])?, 1);
 
-        let mut query = db.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC")?;
+        let mut query = db.prepare("SELECT x FROM foo WHERE x < ?1 ORDER BY x DESC")?;
         {
             let mut rows = query.query([4i32])?;
             let mut v = Vec::<i32>::new();
@@ -1559,12 +1527,9 @@
                    END;";
         db.execute_batch(sql)?;
 
-        assert_eq!(
-            10i64,
-            db.query_row::<i64, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
-        );
+        assert_eq!(10i64, db.one_column::<i64>("SELECT SUM(x) FROM foo")?);
 
-        let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", [], |r| r.get(0));
+        let result: Result<i64> = db.one_column("SELECT x FROM foo WHERE x > 5");
         match result.unwrap_err() {
             Error::QueryReturnedNoRows => (),
             err => panic!("Unexpected error {}", err),
@@ -1572,7 +1537,7 @@
 
         let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()));
 
-        assert!(bad_query_result.is_err());
+        bad_query_result.unwrap_err();
         Ok(())
     }
 
@@ -1580,34 +1545,31 @@
     fn test_optional() -> Result<()> {
         let db = Connection::open_in_memory()?;
 
-        let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 <> 0", [], |r| r.get(0));
+        let result: Result<i64> = db.one_column("SELECT 1 WHERE 0 <> 0");
         let result = result.optional();
         match result? {
             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.one_column("SELECT 1 WHERE 0 == 0");
         let result = result.optional();
         match result? {
             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.one_column("NOT A PROPER QUERY");
         let bad_query_result = bad_query_result.optional();
-        assert!(bad_query_result.is_err());
+        bad_query_result.unwrap_err();
         Ok(())
     }
 
     #[test]
     fn test_pragma_query_row() -> Result<()> {
         let db = Connection::open_in_memory()?;
-        assert_eq!(
-            "memory",
-            db.query_row::<String, _, _>("PRAGMA journal_mode", [], |r| r.get(0))?
-        );
-        let mode = db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?;
+        assert_eq!("memory", db.one_column::<String>("PRAGMA journal_mode")?);
+        let mode = db.one_column::<String>("PRAGMA journal_mode=off")?;
         if cfg!(features = "bundled") {
             assert_eq!(mode, "off");
         } else {
@@ -1634,7 +1596,7 @@
         db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
 
         let err = db.prepare("SELECT * FROM does_not_exist").unwrap_err();
-        assert!(format!("{}", err).contains("does_not_exist"));
+        assert!(format!("{err}").contains("does_not_exist"));
         Ok(())
     }
 
@@ -1665,7 +1627,6 @@
     }
 
     #[test]
-    #[cfg(feature = "modern_sqlite")]
     fn test_is_busy() -> Result<()> {
         let db = Connection::open_in_memory()?;
         assert!(!db.is_busy());
@@ -1688,7 +1649,7 @@
         let query = "SELECT 12345";
         let stmt = db.prepare(query)?;
 
-        assert!(format!("{:?}", stmt).contains(query));
+        assert!(format!("{stmt:?}").contains(query));
         Ok(())
     }
 
@@ -1696,18 +1657,14 @@
     fn test_notnull_constraint_error() -> Result<()> {
         // extended error codes for constraints were added in SQLite 3.7.16; if we're
         // running on our bundled version, we know the extended error code exists.
-        #[cfg(feature = "modern_sqlite")]
         fn check_extended_code(extended_code: c_int) {
             assert_eq!(extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL);
         }
-        #[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 result = db.execute("INSERT INTO foo (x) VALUES (NULL)", []);
-        assert!(result.is_err());
 
         match result.unwrap_err() {
             Error::SqliteFailure(err, _) => {
@@ -1726,7 +1683,7 @@
         let minor = (n % 1_000_000) / 1_000;
         let patch = n % 1_000;
 
-        assert!(version().contains(&format!("{}.{}.{}", major, minor, patch)));
+        assert!(version().contains(&format!("{major}.{minor}.{patch}")));
     }
 
     #[test]
@@ -1779,7 +1736,7 @@
         let db = Connection::open_in_memory()?;
         db.execute_batch("CREATE TABLE foo(i, x);")?;
         let vals = ["foobar", "1234", "qwerty"];
-        let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?, ?)")?;
+        let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?1, ?2)")?;
         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);
@@ -1819,6 +1776,16 @@
         Ok(())
     }
 
+    #[test]
+    fn test_from_handle_owned() -> Result<()> {
+        let mut handle: *mut ffi::sqlite3 = std::ptr::null_mut();
+        let r = unsafe { ffi::sqlite3_open(":memory:\0".as_ptr() as *const i8, &mut handle) };
+        assert_eq!(r, ffi::SQLITE_OK);
+        let db = unsafe { Connection::from_handle_owned(handle) }?;
+        db.execute_batch("PRAGMA VACUUM")?;
+        Ok(())
+    }
+
     mod query_and_then_tests {
 
         use super::*;
@@ -1833,7 +1800,7 @@
             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
                 match *self {
                     CustomError::SomeError => write!(f, "my custom error"),
-                    CustomError::Sqlite(ref se) => write!(f, "my custom error: {}", se),
+                    CustomError::Sqlite(ref se) => write!(f, "my custom error: {se}"),
                 }
             }
         }
@@ -2045,7 +2012,7 @@
         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.execute("INSERT INTO foo VALUES(?1)", [b])?;
         db.query_row("SELECT x FROM foo", [], |r| {
             assert_eq!(5, r.get_unwrap::<_, i32>(0));
             Ok(())
@@ -2057,10 +2024,10 @@
         let db = Connection::open_in_memory()?;
         db.query_row(
             "SELECT
-            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
-            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
-            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
-            ?, ?, ?, ?;",
+            ?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, ?28, ?29, ?30,
+            ?31, ?32, ?33, ?34;",
             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,
@@ -2102,23 +2069,18 @@
     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)
-            })?;
+        let row_id = db.one_column::<i64>("INSERT INTO foo DEFAULT VALUES RETURNING ROWID")?;
         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(feature = "modern_sqlite")]
     pub fn db_readonly() -> Result<()> {
         let db = Connection::open_in_memory()?;
         assert!(!db.is_readonly(MAIN_DB)?);
diff --git a/src/limits.rs b/src/limits.rs
index 93e0bb0..d0694e3 100644
--- a/src/limits.rs
+++ b/src/limits.rs
@@ -39,10 +39,10 @@
     /// 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,
+    SQLITE_LIMIT_TRIGGER_DEPTH = ffi::SQLITE_LIMIT_TRIGGER_DEPTH,
     /// The maximum number of auxiliary worker threads that a single prepared
     /// statement may start.
-    SQLITE_LIMIT_WORKER_THREADS = 11,
+    SQLITE_LIMIT_WORKER_THREADS = ffi::SQLITE_LIMIT_WORKER_THREADS,
 }
 
 impl Connection {
@@ -71,55 +71,49 @@
 
     #[test]
     fn test_limit_values() {
-        assert_eq!(
-            Limit::SQLITE_LIMIT_LENGTH as i32,
-            ffi::SQLITE_LIMIT_LENGTH as i32,
-        );
+        assert_eq!(Limit::SQLITE_LIMIT_LENGTH as i32, ffi::SQLITE_LIMIT_LENGTH,);
         assert_eq!(
             Limit::SQLITE_LIMIT_SQL_LENGTH as i32,
-            ffi::SQLITE_LIMIT_SQL_LENGTH as i32,
+            ffi::SQLITE_LIMIT_SQL_LENGTH,
         );
-        assert_eq!(
-            Limit::SQLITE_LIMIT_COLUMN as i32,
-            ffi::SQLITE_LIMIT_COLUMN as i32,
-        );
+        assert_eq!(Limit::SQLITE_LIMIT_COLUMN as i32, ffi::SQLITE_LIMIT_COLUMN,);
         assert_eq!(
             Limit::SQLITE_LIMIT_EXPR_DEPTH as i32,
-            ffi::SQLITE_LIMIT_EXPR_DEPTH as i32,
+            ffi::SQLITE_LIMIT_EXPR_DEPTH,
         );
         assert_eq!(
             Limit::SQLITE_LIMIT_COMPOUND_SELECT as i32,
-            ffi::SQLITE_LIMIT_COMPOUND_SELECT as i32,
+            ffi::SQLITE_LIMIT_COMPOUND_SELECT,
         );
         assert_eq!(
             Limit::SQLITE_LIMIT_VDBE_OP as i32,
-            ffi::SQLITE_LIMIT_VDBE_OP as i32,
+            ffi::SQLITE_LIMIT_VDBE_OP,
         );
         assert_eq!(
             Limit::SQLITE_LIMIT_FUNCTION_ARG as i32,
-            ffi::SQLITE_LIMIT_FUNCTION_ARG as i32,
+            ffi::SQLITE_LIMIT_FUNCTION_ARG,
         );
         assert_eq!(
             Limit::SQLITE_LIMIT_ATTACHED as i32,
-            ffi::SQLITE_LIMIT_ATTACHED as i32,
+            ffi::SQLITE_LIMIT_ATTACHED,
         );
         assert_eq!(
             Limit::SQLITE_LIMIT_LIKE_PATTERN_LENGTH as i32,
-            ffi::SQLITE_LIMIT_LIKE_PATTERN_LENGTH as i32,
+            ffi::SQLITE_LIMIT_LIKE_PATTERN_LENGTH,
         );
         assert_eq!(
             Limit::SQLITE_LIMIT_VARIABLE_NUMBER as i32,
-            ffi::SQLITE_LIMIT_VARIABLE_NUMBER as i32,
+            ffi::SQLITE_LIMIT_VARIABLE_NUMBER,
         );
         #[cfg(feature = "bundled")]
         assert_eq!(
             Limit::SQLITE_LIMIT_TRIGGER_DEPTH as i32,
-            ffi::SQLITE_LIMIT_TRIGGER_DEPTH as i32,
+            ffi::SQLITE_LIMIT_TRIGGER_DEPTH,
         );
         #[cfg(feature = "bundled")]
         assert_eq!(
             Limit::SQLITE_LIMIT_WORKER_THREADS as i32,
-            ffi::SQLITE_LIMIT_WORKER_THREADS as i32,
+            ffi::SQLITE_LIMIT_WORKER_THREADS,
         );
     }
 
diff --git a/src/params.rs b/src/params.rs
index 6ab6b5f..a4c5066 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -70,7 +70,7 @@
 /// ```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 (?, ?)")?;
+///     let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?1, ?2)")?;
 ///
 ///     // Using a tuple:
 ///     stmt.execute((0, "foobar"))?;
@@ -138,9 +138,7 @@
 /// ## No parameters
 ///
 /// You can just use an empty tuple or the empty array literal to run a query
-/// that accepts no parameters. (The `rusqlite::NO_PARAMS` constant which was
-/// common in previous versions of this library is no longer needed, and is now
-/// deprecated).
+/// that accepts no parameters.
 ///
 /// ### Example (no parameters)
 ///
@@ -192,8 +190,7 @@
 }
 
 // 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.
+// unambiguous, this must be the *only* implementation for an empty array.
 //
 // This sadly prevents `impl<T: ToSql, const N: usize> Params for [T; N]`, which
 // forces people to use `params![...]` or `rusqlite::params_from_iter` for long
@@ -357,7 +354,7 @@
 /// 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 mut stmt = conn.prepare("SELECT * FROM users WHERE id IN (?1, ?2, ?3)")?;
 ///     let _rows = stmt.query(params_from_iter(ids.iter()))?;
 ///
 ///     // use _rows...
diff --git a/src/pragma.rs b/src/pragma.rs
index 673478a..338be5f 100644
--- a/src/pragma.rs
+++ b/src/pragma.rs
@@ -37,7 +37,7 @@
         } else {
             Err(Error::SqliteFailure(
                 ffi::Error::new(ffi::SQLITE_MISUSE),
-                Some(format!("Invalid keyword \"{}\"", keyword)),
+                Some(format!("Invalid keyword \"{keyword}\"")),
             ))
         }
     }
@@ -67,14 +67,14 @@
             ToSqlOutput::ZeroBlob(_) => {
                 return Err(Error::SqliteFailure(
                     ffi::Error::new(ffi::SQLITE_MISUSE),
-                    Some(format!("Unsupported value \"{:?}\"", value)),
+                    Some(format!("Unsupported value \"{value:?}\"")),
                 ));
             }
             #[cfg(feature = "array")]
             ToSqlOutput::Array(_) => {
                 return Err(Error::SqliteFailure(
                     ffi::Error::new(ffi::SQLITE_MISUSE),
-                    Some(format!("Unsupported value \"{:?}\"", value)),
+                    Some(format!("Unsupported value \"{value:?}\"")),
                 ));
             }
         };
@@ -92,7 +92,7 @@
             _ => {
                 return Err(Error::SqliteFailure(
                     ffi::Error::new(ffi::SQLITE_MISUSE),
-                    Some(format!("Unsupported value \"{:?}\"", value)),
+                    Some(format!("Unsupported value \"{value:?}\"")),
                 ));
             }
         };
@@ -211,7 +211,7 @@
     /// (e.g. `integrity_check`).
     ///
     /// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
-    /// `SELECT * FROM pragma_table_info(?);`
+    /// `SELECT * FROM pragma_table_info(?1);`
     pub fn pragma<F, V>(
         &self,
         schema_name: Option<DatabaseName<'_>>,
@@ -303,15 +303,15 @@
 }
 
 fn is_identifier_start(c: char) -> bool {
-    ('A'..='Z').contains(&c) || c == '_' || ('a'..='z').contains(&c) || c > '\x7F'
+    c.is_ascii_uppercase() || c == '_' || c.is_ascii_lowercase() || c > '\x7F'
 }
 
 fn is_identifier_continue(c: char) -> bool {
     c == '$'
-        || ('0'..='9').contains(&c)
-        || ('A'..='Z').contains(&c)
+        || c.is_ascii_digit()
+        || c.is_ascii_uppercase()
         || c == '_'
-        || ('a'..='z').contains(&c)
+        || c.is_ascii_lowercase()
         || c > '\x7F'
 }
 
@@ -333,10 +333,7 @@
     #[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)
-            })?;
+        let user_version: i32 = db.one_column("SELECT user_version FROM pragma_user_version")?;
         assert_eq!(0, user_version);
         Ok(())
     }
@@ -369,7 +366,7 @@
     fn pragma() -> Result<()> {
         let db = Connection::open_in_memory()?;
         let mut columns = Vec::new();
-        db.pragma(None, "table_info", &"sqlite_master", |row| {
+        db.pragma(None, "table_info", "sqlite_master", |row| {
             let column: String = row.get(1)?;
             columns.push(column);
             Ok(())
@@ -382,7 +379,7 @@
     #[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(?)")?;
+        let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?1)")?;
         let mut columns = Vec::new();
         let mut rows = table_info.query(["sqlite_master"])?;
 
@@ -412,8 +409,8 @@
             journal_mode,
         );
         // Sanity checks to ensure the move to a generic `ToSql` wasn't breaking
-        let mode = db
-            .pragma_update_and_check(None, "journal_mode", &"OFF", |row| row.get::<_, String>(0))?;
+        let mode =
+            db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get::<_, String>(0))?;
         assert!(mode == "off" || mode == "memory", "mode: {:?}", mode);
 
         let param: &dyn crate::ToSql = &"OFF";
@@ -448,7 +445,7 @@
     #[test]
     fn locking_mode() -> Result<()> {
         let db = Connection::open_in_memory()?;
-        let r = db.pragma_update(None, "locking_mode", &"exclusive");
+        let r = db.pragma_update(None, "locking_mode", "exclusive");
         if cfg!(feature = "extra_check") {
             r.unwrap_err();
         } else {
diff --git a/src/raw_statement.rs b/src/raw_statement.rs
index f057761..1683c7b 100644
--- a/src/raw_statement.rs
+++ b/src/raw_statement.rs
@@ -1,7 +1,6 @@
 use super::ffi;
 use super::StatementStatus;
 use crate::util::ParamIndexCache;
-#[cfg(feature = "modern_sqlite")]
 use crate::util::SqliteMallocString;
 use std::ffi::CStr;
 use std::os::raw::c_int;
@@ -170,8 +169,10 @@
     }
 
     #[inline]
-    pub fn clear_bindings(&self) -> c_int {
-        unsafe { ffi::sqlite3_clear_bindings(self.ptr) }
+    pub fn clear_bindings(&self) {
+        unsafe {
+            ffi::sqlite3_clear_bindings(self.ptr);
+        } // rc is always SQLITE_OK
     }
 
     #[inline]
@@ -197,13 +198,11 @@
 
     // 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)) }
     }
diff --git a/src/row.rs b/src/row.rs
index 221905a..93cf824 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -338,20 +338,6 @@
     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)
-    }
 }
 
 impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
@@ -360,6 +346,46 @@
     }
 }
 
+/// Debug `Row` like an ordered `Map<Result<&str>, Result<(Type, ValueRef)>>`
+/// with column name as key except that for `Type::Blob` only its size is
+/// printed (not its content).
+impl<'stmt> std::fmt::Debug for Row<'stmt> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let mut dm = f.debug_map();
+        for c in 0..self.stmt.column_count() {
+            let name = self.stmt.column_name(c);
+            dm.key(&name);
+            let value = self.get_ref(c);
+            match value {
+                Ok(value) => {
+                    let dt = value.data_type();
+                    match value {
+                        ValueRef::Null => {
+                            dm.value(&(dt, ()));
+                        }
+                        ValueRef::Integer(i) => {
+                            dm.value(&(dt, i));
+                        }
+                        ValueRef::Real(f) => {
+                            dm.value(&(dt, f));
+                        }
+                        ValueRef::Text(s) => {
+                            dm.value(&(dt, String::from_utf8_lossy(s)));
+                        }
+                        ValueRef::Blob(b) => {
+                            dm.value(&(dt, b.len()));
+                        }
+                    }
+                }
+                Err(ref _err) => {
+                    dm.value(&value);
+                }
+            }
+        }
+        dm.finish()
+    }
+}
+
 mod sealed {
     /// This trait exists just to ensure that the only impls of `trait Params`
     /// that are allowed are ones in this crate.
@@ -391,7 +417,7 @@
 impl RowIndex for &'_ str {
     #[inline]
     fn idx(&self, stmt: &Statement<'_>) -> Result<usize> {
-        stmt.column_index(*self)
+        stmt.column_index(self)
     }
 }
 
@@ -448,7 +474,7 @@
         let val = conn.query_row("SELECT a FROM test", [], |row| <(u32,)>::try_from(row))?;
         assert_eq!(val, (42,));
         let fail = conn.query_row("SELECT a FROM test", [], |row| <(u32, u32)>::try_from(row));
-        assert!(fail.is_err());
+        fail.unwrap_err();
         Ok(())
     }
 
@@ -466,7 +492,7 @@
         let fail = conn.query_row("SELECT a, b FROM test", [], |row| {
             <(u32, u32, u32)>::try_from(row)
         });
-        assert!(fail.is_err());
+        fail.unwrap_err();
         Ok(())
     }
 
diff --git a/src/session.rs b/src/session.rs
index f8aa764..c42bbd6 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -19,12 +19,14 @@
 
 // https://sqlite.org/session.html
 
+type Filter = Option<Box<dyn Fn(&str) -> bool>>;
+
 /// 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>,
     s: *mut ffi::sqlite3_session,
-    filter: Option<Box<dyn Fn(&str) -> bool>>,
+    filter: Filter,
 }
 
 impl Session<'_> {
@@ -73,13 +75,10 @@
                 let c_slice = CStr::from_ptr(tbl_str).to_bytes();
                 str::from_utf8(c_slice)
             };
-            if let Ok(true) =
+            c_int::from(
                 catch_unwind(|| (*boxed_filter)(tbl_name.expect("non-utf8 table name")))
-            {
-                1
-            } else {
-                0
-            }
+                    .unwrap_or_default(),
+            )
         }
 
         match filter {
@@ -191,7 +190,7 @@
     #[inline]
     pub fn set_enabled(&mut self, enabled: bool) {
         unsafe {
-            ffi::sqlite3session_enable(self.s, if enabled { 1 } else { 0 });
+            ffi::sqlite3session_enable(self.s, c_int::from(enabled));
         }
     }
 
@@ -205,7 +204,7 @@
     #[inline]
     pub fn set_indirect(&mut self, indirect: bool) {
         unsafe {
-            ffi::sqlite3session_indirect(self.s, if indirect { 1 } else { 0 });
+            ffi::sqlite3session_indirect(self.s, c_int::from(indirect));
         }
     }
 }
@@ -656,7 +655,7 @@
 /// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_CONFLICT) for details.
 #[allow(missing_docs)]
 #[repr(i32)]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Eq)]
 #[non_exhaustive]
 #[allow(clippy::upper_case_acronyms)]
 pub enum ConflictType {
@@ -684,7 +683,7 @@
 /// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_ABORT) for details.
 #[allow(missing_docs)]
 #[repr(i32)]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Eq)]
 #[non_exhaustive]
 #[allow(clippy::upper_case_acronyms)]
 pub enum ConflictAction {
@@ -706,13 +705,9 @@
         str::from_utf8(c_slice)
     };
     match *tuple {
-        (Some(ref filter), _) => {
-            if let Ok(true) = catch_unwind(|| filter(tbl_name.expect("illegal table name"))) {
-                1
-            } else {
-                0
-            }
-        }
+        (Some(ref filter), _) => c_int::from(
+            catch_unwind(|| filter(tbl_name.expect("illegal table name"))).unwrap_or_default(),
+        ),
         _ => unimplemented!(),
     }
 }
@@ -784,7 +779,7 @@
         assert!(session.is_empty());
 
         session.attach(None)?;
-        db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
+        db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
 
         session.changeset()
     }
@@ -797,7 +792,7 @@
         assert!(session.is_empty());
 
         session.attach(None)?;
-        db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
+        db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
 
         let mut output = Vec::new();
         session.changeset_strm(&mut output)?;
@@ -857,7 +852,7 @@
         )?;
 
         assert!(!CALLED.load(Ordering::Relaxed));
-        let check = db.query_row("SELECT 1 FROM foo WHERE t = ?", ["bar"], |row| {
+        let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| {
             row.get::<_, i32>(0)
         })?;
         assert_eq!(1, check);
@@ -892,7 +887,7 @@
             |_conflict_type, _item| ConflictAction::SQLITE_CHANGESET_OMIT,
         )?;
 
-        let check = db.query_row("SELECT 1 FROM foo WHERE t = ?", ["bar"], |row| {
+        let check = db.query_row("SELECT 1 FROM foo WHERE t = ?1", ["bar"], |row| {
             row.get::<_, i32>(0)
         })?;
         assert_eq!(1, check);
@@ -908,7 +903,7 @@
         assert!(session.is_empty());
 
         session.attach(None)?;
-        db.execute("INSERT INTO foo (t) VALUES (?);", ["bar"])?;
+        db.execute("INSERT INTO foo (t) VALUES (?1);", ["bar"])?;
 
         assert!(!session.is_empty());
         Ok(())
diff --git a/src/statement.rs b/src/statement.rs
index ee5e220..982567a 100644
--- a/src/statement.rs
+++ b/src/statement.rs
@@ -33,7 +33,7 @@
     /// ```rust,no_run
     /// # use rusqlite::{Connection, Result, params};
     /// fn update_rows(conn: &Connection) -> Result<()> {
-    ///     let mut stmt = conn.prepare("UPDATE foo SET bar = ? WHERE qux = ?")?;
+    ///     let mut stmt = conn.prepare("UPDATE foo SET bar = ?1 WHERE qux = ?2")?;
     ///     // For a single parameter, or a parameter where all the values have
     ///     // the same type, just passing an array is simplest.
     ///     stmt.execute([2i32])?;
@@ -58,7 +58,7 @@
     /// fn store_file(conn: &Connection, path: &str, data: &[u8]) -> Result<()> {
     ///     # // no need to do it for real.
     ///     # fn sha256(_: &[u8]) -> [u8; 32] { [0; 32] }
-    ///     let query = "INSERT OR REPLACE INTO files(path, hash, data) VALUES (?, ?, ?)";
+    ///     let query = "INSERT OR REPLACE INTO files(path, hash, data) VALUES (?1, ?2, ?3)";
     ///     let mut stmt = conn.prepare_cached(query)?;
     ///     let hash: [u8; 32] = sha256(data);
     ///     // The easiest way to pass positional parameters of have several
@@ -114,31 +114,6 @@
         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.
-    ///
-    /// On success, returns the number of rows that were changed or inserted or
-    /// deleted (via `sqlite3_changes`).
-    ///
-    /// # Failure
-    ///
-    /// Will return `Err` if binding parameters fails, the executed statement
-    /// returns rows (in which case `query` should be used instead), or the
-    /// underlying SQLite call fails.
-    #[doc(hidden)]
-    #[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)
-    }
-
     /// Execute an INSERT and return the ROWID.
     ///
     /// # Note
@@ -193,7 +168,7 @@
     /// ```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 stmt = conn.prepare("SELECT * FROM test where name = ?1")?;
     ///     let mut rows = stmt.query(rusqlite::params![name])?;
     ///     while let Some(row) = rows.next()? {
     ///         // ...
@@ -202,12 +177,12 @@
     /// }
     /// ```
     ///
-    /// Or, equivalently (but without the [`params!`] macro).
+    /// Or, equivalently (but without the [`crate::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 stmt = conn.prepare("SELECT * FROM test where name = ?1")?;
     ///     let mut rows = stmt.query([name])?;
     ///     while let Some(row) = rows.next()? {
     ///         // ...
@@ -254,26 +229,6 @@
         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.
-    #[doc(hidden)]
-    #[deprecated = "You can use `query` with named params now."]
-    pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'_>> {
-        self.query(params)
-    }
-
     /// Executes the prepared statement and maps a function over the resulting
     /// rows, returning an iterator over the mapped function results.
     ///
@@ -328,37 +283,6 @@
         self.query(params).map(|rows| rows.mapped(f))
     }
 
-    /// Execute the prepared statement with named parameter(s), returning an
-    /// iterator over the result of calling the mapping function over the
-    /// query's rows.
-    ///
-    /// Note: This function is deprecated in favor of [`Statement::query_map`],
-    /// which can now take named parameters directly.
-    ///
-    /// If any parameters that were in the prepared statement
-    /// are not included in `params`, they will continue to use the
-    /// most-recently bound value from a previous call to `query_named`,
-    /// or `NULL` if they have never been bound.
-    ///
-    /// `f` is used to transform the _streaming_ iterator into a _standard_
-    /// iterator.
-    ///
-    /// ## Failure
-    ///
-    /// Will return `Err` if binding parameters fails.
-    #[doc(hidden)]
-    #[deprecated = "You can use `query_map` with named params now."]
-    pub fn query_map_named<T, F>(
-        &mut self,
-        params: &[(&str, &dyn ToSql)],
-        f: F,
-    ) -> Result<MappedRows<'_, F>>
-    where
-        F: FnMut(&Row<'_>) -> Result<T>,
-    {
-        self.query_map(params, f)
-    }
-
     /// Executes the prepared statement and maps a function over the resulting
     /// rows, where the function returns a `Result` with `Error` type
     /// implementing `std::convert::From<Error>` (so errors can be unified).
@@ -398,7 +322,7 @@
     /// ```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 mut stmt = conn.prepare("SELECT name FROM people WHERE id = ?1")?;
     ///     let rows = stmt.query_and_then(["one"], |row| row.get::<_, String>(0))?;
     ///
     ///     let mut persons = Vec::new();
@@ -423,36 +347,6 @@
         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.
-    #[doc(hidden)]
-    #[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)],
-        f: F,
-    ) -> Result<AndThenRows<'_, F>>
-    where
-        E: From<Error>,
-        F: FnMut(&Row<'_>) -> Result<T, E>,
-    {
-        self.query_and_then(params, f)
-    }
-
     /// Return `true` if a query in the SQL statement it executes returns one
     /// or more rows and `false` if the SQL returns an empty set.
     #[inline]
@@ -487,35 +381,6 @@
         rows.get_expected_row().and_then(f)
     }
 
-    /// Convenience method to execute a query with named parameter(s) that is
-    /// expected to return a single row.
-    ///
-    /// Note: This function is deprecated in favor of
-    /// [`Statement::query_and_then`], which can now take named parameters
-    /// directly.
-    ///
-    /// If the query returns more than one row, all rows except the first are
-    /// ignored.
-    ///
-    /// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
-    /// query truly is optional, you can call
-    /// [`.optional()`](crate::OptionalExtension::optional) on the result of
-    /// this to get a `Result<Option<T>>` (requires that the trait
-    /// `rusqlite::OptionalExtension` is imported).
-    ///
-    /// # Failure
-    ///
-    /// Will return `Err` if `sql` cannot be converted to a C-compatible string
-    /// or if the underlying SQLite call fails.
-    #[doc(hidden)]
-    #[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)
-    }
-
     /// Consumes the statement.
     ///
     /// Functionally equivalent to the `Drop` implementation, but allows
@@ -796,7 +661,7 @@
         self.conn.decode_result(stmt.finalize())
     }
 
-    #[cfg(all(feature = "modern_sqlite", feature = "extra_check"))]
+    #[cfg(feature = "extra_check")]
     #[inline]
     fn check_update(&self) -> Result<()> {
         // sqlite3_column_count works for DML but not for DDL (ie ALTER)
@@ -806,16 +671,6 @@
         Ok(())
     }
 
-    #[cfg(all(not(feature = "modern_sqlite"), feature = "extra_check"))]
-    #[inline]
-    fn check_update(&self) -> Result<()> {
-        // sqlite3_column_count works for DML but not for DDL (ie ALTER)
-        if self.column_count() > 0 {
-            return Err(Error::ExecuteReturnedResults);
-        }
-        Ok(())
-    }
-
     #[cfg(not(feature = "extra_check"))]
     #[inline]
     #[allow(clippy::unnecessary_wraps)]
@@ -825,8 +680,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()
@@ -856,6 +709,12 @@
         self.stmt.is_explain()
     }
 
+    /// Returns true if the statement is read only.
+    #[inline]
+    pub fn readonly(&self) -> bool {
+        self.stmt.readonly()
+    }
+
     #[cfg(feature = "extra_check")]
     #[inline]
     pub(crate) fn check_no_tail(&self) -> Result<()> {
@@ -882,6 +741,11 @@
         mem::swap(&mut stmt, &mut self.stmt);
         stmt
     }
+
+    /// Reset all bindings
+    pub fn clear_bindings(&mut self) {
+        self.stmt.clear_bindings()
+    }
 }
 
 impl fmt::Debug for Statement<'_> {
@@ -1021,13 +885,12 @@
     use crate::{params_from_iter, Connection, Error, Result};
 
     #[test]
-    #[allow(deprecated)]
     fn test_execute_named() -> Result<()> {
         let db = Connection::open_in_memory()?;
         db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
 
         assert_eq!(
-            db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])?,
+            db.execute("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)])?,
             1
         );
         assert_eq!(
@@ -1044,7 +907,7 @@
 
         assert_eq!(
             6i32,
-            db.query_row_named::<i32, _>(
+            db.query_row::<i32, _, _>(
                 "SELECT SUM(x) FROM foo WHERE x > :x",
                 &[(":x", &0i32)],
                 |r| r.get(0)
@@ -1062,7 +925,6 @@
     }
 
     #[test]
-    #[allow(deprecated)]
     fn test_stmt_execute_named() -> Result<()> {
         let db = Connection::open_in_memory()?;
         let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
@@ -1070,22 +932,17 @@
         db.execute_batch(sql)?;
 
         let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)")?;
-        stmt.execute_named(&[(":name", &"one")])?;
+        stmt.execute(&[(":name", &"one")])?;
 
         let mut stmt = db.prepare("SELECT COUNT(*) FROM test WHERE name = :name")?;
         assert_eq!(
             1i32,
-            stmt.query_row_named::<i32, _>(&[(":name", &"one")], |r| r.get(0))?
-        );
-        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()?;
         let sql = r#"
@@ -1095,24 +952,13 @@
         db.execute_batch(sql)?;
 
         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);
-        }
-
-        // plain api
-        {
-            let mut rows = stmt.query(&[(":name", "one")])?;
-            let id: Result<i32> = rows.next()?.unwrap().get(0);
-            assert_eq!(Ok(1), id);
-        }
+        let mut rows = stmt.query(&[(":name", "one")])?;
+        let id: Result<i32> = rows.next()?.unwrap().get(0);
+        assert_eq!(Ok(1), id);
         Ok(())
     }
 
     #[test]
-    #[allow(deprecated)]
     fn test_query_map_named() -> Result<()> {
         let db = Connection::open_in_memory()?;
         let sql = r#"
@@ -1122,61 +968,13 @@
         db.execute_batch(sql)?;
 
         let mut stmt = db.prepare("SELECT id FROM test where name = :name")?;
-        // legacy `_named` api
-        {
-            let mut rows = stmt.query_map_named(&[(":name", &"one")], |row| {
-                let id: Result<i32> = row.get(0);
-                id.map(|i| 2 * i)
-            })?;
-
-            let 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(())
-    }
-
-    #[test]
-    #[allow(deprecated)]
-    fn test_query_and_then_named() -> Result<()> {
-        let db = Connection::open_in_memory()?;
-        let sql = r#"
-        CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag INTEGER);
-        INSERT INTO test(id, name) VALUES (1, "one");
-        INSERT INTO test(id, name) VALUES (2, "one");
-        "#;
-        db.execute_batch(sql)?;
-
-        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 rows = stmt.query_map(&[(":name", "one")], |row| {
+            let id: Result<i32> = row.get(0);
+            id.map(|i| 2 * i)
         })?;
 
-        // 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"),
-        }
+        assert_eq!(2, doubled_id);
         Ok(())
     }
 
@@ -1215,17 +1013,15 @@
     }
 
     #[test]
-    #[allow(deprecated)]
     fn test_unbound_parameters_are_null() -> Result<()> {
         let db = Connection::open_in_memory()?;
         let sql = "CREATE TABLE test (x TEXT, y TEXT)";
         db.execute_batch(sql)?;
 
         let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)")?;
-        stmt.execute_named(&[(":x", &"one")])?;
+        stmt.execute(&[(":x", &"one")])?;
 
-        let result: Option<String> =
-            db.query_row("SELECT y FROM test WHERE x = 'one'", [], |row| row.get(0))?;
+        let result: Option<String> = db.one_column("SELECT y FROM test WHERE x = 'one'")?;
         assert!(result.is_none());
         Ok(())
     }
@@ -1271,8 +1067,7 @@
         stmt.execute(&[(":x", "one")])?;
         stmt.execute(&[(":y", "two")])?;
 
-        let result: String =
-            db.query_row("SELECT x FROM test WHERE y = 'two'", [], |row| row.get(0))?;
+        let result: String = db.one_column("SELECT x FROM test WHERE y = 'two'")?;
         assert_eq!(result, "one");
         Ok(())
     }
@@ -1281,7 +1076,7 @@
     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 (?)")?;
+        let mut stmt = db.prepare("INSERT OR IGNORE INTO foo (x) VALUES (?1)")?;
         assert_eq!(stmt.insert([1i32])?, 1);
         assert_eq!(stmt.insert([2i32])?, 2);
         match stmt.insert([1i32]).unwrap_err() {
@@ -1321,7 +1116,7 @@
                    INSERT INTO foo VALUES(2);
                    END;";
         db.execute_batch(sql)?;
-        let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?")?;
+        let mut stmt = db.prepare("SELECT 1 FROM foo WHERE x = ?1")?;
         assert!(stmt.exists([1i32])?);
         assert!(stmt.exists([2i32])?);
         assert!(!stmt.exists([0i32])?);
@@ -1330,18 +1125,18 @@
     #[test]
     fn test_tuple_params() -> Result<()> {
         let db = Connection::open_in_memory()?;
-        let s = db.query_row("SELECT printf('[%s]', ?)", ("abc",), |r| {
+        let s = db.query_row("SELECT printf('[%s]', ?1)", ("abc",), |r| {
             r.get::<_, String>(0)
         })?;
         assert_eq!(s, "[abc]");
         let s = db.query_row(
-            "SELECT printf('%d %s %d', ?, ?, ?)",
+            "SELECT printf('%d %s %d', ?1, ?2, ?3)",
             (1i32, "abc", 2i32),
             |r| r.get::<_, String>(0),
         )?;
         assert_eq!(s, "1 abc 2");
         let s = db.query_row(
-            "SELECT printf('%d %s %d %d', ?, ?, ?, ?)",
+            "SELECT printf('%d %s %d %d', ?1, ?2, ?3, ?4)",
             (1, "abc", 2i32, 4i64),
             |r| r.get::<_, String>(0),
         )?;
@@ -1353,10 +1148,10 @@
         );
         let query = "SELECT printf(
             '%d %s | %d %s | %d %s | %d %s || %d %s | %d %s | %d %s | %d %s',
-            ?, ?, ?, ?,
-            ?, ?, ?, ?,
-            ?, ?, ?, ?,
-            ?, ?, ?, ?
+            ?1, ?2, ?3, ?4,
+            ?5, ?6, ?7, ?8,
+            ?9, ?10, ?11, ?12,
+            ?13, ?14, ?15, ?16
         )";
         let s = db.query_row(query, bigtup, |r| r.get::<_, String>(0))?;
         assert_eq!(s, "0 a | 1 b | 2 c | 3 d || 4 e | 5 f | 6 g | 7 h");
@@ -1372,7 +1167,7 @@
                    INSERT INTO foo VALUES(2, 4);
                    END;";
         db.execute_batch(sql)?;
-        let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?")?;
+        let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?1")?;
         let y: Result<i64> = stmt.query_row([1i32], |r| r.get(0));
         assert_eq!(3i64, y?);
         Ok(())
@@ -1407,10 +1202,9 @@
     }
 
     #[test]
-    #[cfg(feature = "modern_sqlite")]
     fn test_expanded_sql() -> Result<()> {
         let db = Connection::open_in_memory()?;
-        let stmt = db.prepare("SELECT ?")?;
+        let stmt = db.prepare("SELECT ?1")?;
         stmt.bind_parameter(&1, 1)?;
         assert_eq!(Some("SELECT 1".to_owned()), stmt.expanded_sql());
         Ok(())
@@ -1422,7 +1216,7 @@
         // dynamic slice:
         db.query_row(
             "SELECT ?1, ?2, ?3",
-            &[&1u8 as &dyn ToSql, &"one", &Some("one")],
+            [&1u8 as &dyn ToSql, &"one", &Some("one")],
             |row| row.get::<_, u8>(0),
         )?;
         // existing collection:
@@ -1474,10 +1268,10 @@
         let conn = Connection::open_in_memory()?;
         let mut stmt = conn.prepare("")?;
         assert_eq!(0, stmt.column_count());
-        assert!(stmt.parameter_index("test").is_ok());
-        assert!(stmt.step().is_err());
+        stmt.parameter_index("test").unwrap();
+        stmt.step().unwrap_err();
         stmt.reset();
-        assert!(stmt.execute([]).is_err());
+        stmt.execute([]).unwrap_err();
         Ok(())
     }
 
@@ -1507,13 +1301,13 @@
     #[test]
     fn test_utf16_conversion() -> Result<()> {
         let db = Connection::open_in_memory()?;
-        db.pragma_update(None, "encoding", &"UTF-16le")?;
+        db.pragma_update(None, "encoding", "UTF-16le")?;
         let encoding: String = db.pragma_query_value(None, "encoding", |row| row.get(0))?;
         assert_eq!("UTF-16le", encoding);
         db.execute_batch("CREATE TABLE foo(x TEXT)")?;
         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 (?1)", [&expected])?;
+        let actual: String = db.one_column("SELECT x FROM foo")?;
         assert_eq!(expected, actual);
         Ok(())
     }
@@ -1522,7 +1316,7 @@
     fn test_nul_byte() -> Result<()> {
         let db = Connection::open_in_memory()?;
         let expected = "a\x00b";
-        let actual: String = db.query_row("SELECT ?", [expected], |row| row.get(0))?;
+        let actual: String = db.query_row("SELECT ?1", [expected], |row| row.get(0))?;
         assert_eq!(expected, actual);
         Ok(())
     }
@@ -1537,12 +1331,19 @@
     }
 
     #[test]
-    #[cfg(all(feature = "modern_sqlite", not(feature = "bundled-sqlcipher")))] // SQLite >= 3.38.0
+    fn readonly() -> Result<()> {
+        let db = Connection::open_in_memory()?;
+        let stmt = db.prepare("SELECT 1;")?;
+        assert!(stmt.readonly());
+        Ok(())
+    }
+
+    #[test]
+    #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
     fn test_error_offset() -> Result<()> {
         use crate::ffi::ErrorCode;
         let db = Connection::open_in_memory()?;
         let r = db.execute_batch("SELECT CURRENT_TIMESTANP;");
-        assert!(r.is_err());
         match r.unwrap_err() {
             Error::SqlInputError { error, offset, .. } => {
                 assert_eq!(error.code, ErrorCode::Unknown);
diff --git a/src/trace.rs b/src/trace.rs
index 7fc9090..ce4c80b 100644
--- a/src/trace.rs
+++ b/src/trace.rs
@@ -144,13 +144,13 @@
         let mut db = Connection::open_in_memory()?;
         db.trace(Some(tracer));
         {
-            let _ = db.query_row("SELECT ?", [1i32], |_| Ok(()));
-            let _ = db.query_row("SELECT ?", ["hello"], |_| Ok(()));
+            let _ = db.query_row("SELECT ?1", [1i32], |_| Ok(()));
+            let _ = db.query_row("SELECT ?1", ["hello"], |_| Ok(()));
         }
         db.trace(None);
         {
-            let _ = db.query_row("SELECT ?", [2i32], |_| Ok(()));
-            let _ = db.query_row("SELECT ?", ["goodbye"], |_| Ok(()));
+            let _ = db.query_row("SELECT ?1", [2i32], |_| Ok(()));
+            let _ = db.query_row("SELECT ?1", ["goodbye"], |_| Ok(()));
         }
 
         let traced_stmts = TRACED_STMTS.lock().unwrap();
diff --git a/src/transaction.rs b/src/transaction.rs
index 2c4c6c0..5cf26a4 100644
--- a/src/transaction.rs
+++ b/src/transaction.rs
@@ -255,7 +255,7 @@
         name: T,
     ) -> Result<Savepoint<'_>> {
         let name = name.into();
-        conn.execute_batch(&format!("SAVEPOINT {}", name))
+        conn.execute_batch(&format!("SAVEPOINT {name}"))
             .map(|_| Savepoint {
                 conn,
                 name,
@@ -267,7 +267,7 @@
 
     #[inline]
     fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> {
-        let name = format!("_rusqlite_sp_{}", depth);
+        let name = format!("_rusqlite_sp_{depth}");
         Savepoint::with_depth_and_name(conn, depth, name)
     }
 
@@ -552,10 +552,7 @@
         }
         {
             let tx = db.transaction()?;
-            assert_eq!(
-                2i32,
-                tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
-            );
+            assert_eq!(2i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
         }
         Ok(())
     }
@@ -591,10 +588,7 @@
             tx.commit()?;
         }
 
-        assert_eq!(
-            2i32,
-            db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
-        );
+        assert_eq!(2i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?);
         Ok(())
     }
 
@@ -619,10 +613,7 @@
         }
         {
             let tx = db.transaction()?;
-            assert_eq!(
-                6i32,
-                tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
-            );
+            assert_eq!(6i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
         }
         Ok(())
     }
@@ -727,11 +718,11 @@
     }
 
     fn insert(x: i32, conn: &Connection) -> Result<usize> {
-        conn.execute("INSERT INTO foo VALUES(?)", [x])
+        conn.execute("INSERT INTO foo VALUES(?1)", [x])
     }
 
     fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
-        let i = conn.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+        let i = conn.one_column::<i32>("SELECT SUM(x) FROM foo")?;
         assert_eq!(x, i);
         Ok(())
     }
diff --git a/src/types/chrono.rs b/src/types/chrono.rs
index 6bfc2f4..6b50e01 100644
--- a/src/types/chrono.rs
+++ b/src/types/chrono.rs
@@ -175,12 +175,12 @@
     #[test]
     fn test_naive_date() -> Result<()> {
         let db = checked_memory_handle()?;
-        let date = NaiveDate::from_ymd(2016, 2, 23);
-        db.execute("INSERT INTO foo (t) VALUES (?)", [date])?;
+        let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
+        db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
 
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let s: String = db.one_column("SELECT t FROM foo")?;
         assert_eq!("2016-02-23", s);
-        let t: NaiveDate = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let t: NaiveDate = db.one_column("SELECT t FROM foo")?;
         assert_eq!(date, t);
         Ok(())
     }
@@ -188,12 +188,12 @@
     #[test]
     fn test_naive_time() -> Result<()> {
         let db = checked_memory_handle()?;
-        let time = NaiveTime::from_hms(23, 56, 4);
-        db.execute("INSERT INTO foo (t) VALUES (?)", [time])?;
+        let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
+        db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
 
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let s: String = db.one_column("SELECT t FROM foo")?;
         assert_eq!("23:56:04", s);
-        let v: NaiveTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let v: NaiveTime = db.one_column("SELECT t FROM foo")?;
         assert_eq!(time, v);
         Ok(())
     }
@@ -201,19 +201,19 @@
     #[test]
     fn test_naive_date_time() -> Result<()> {
         let db = checked_memory_handle()?;
-        let date = NaiveDate::from_ymd(2016, 2, 23);
-        let time = NaiveTime::from_hms(23, 56, 4);
+        let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
+        let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
         let dt = NaiveDateTime::new(date, time);
 
-        db.execute("INSERT INTO foo (t) VALUES (?)", [dt])?;
+        db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
 
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let s: String = db.one_column("SELECT t FROM foo")?;
         assert_eq!("2016-02-23 23:56:04", s);
-        let v: NaiveDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let v: NaiveDateTime = db.one_column("SELECT t FROM foo")?;
         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))?;
+        let hms: NaiveDateTime = db.one_column("SELECT b FROM foo")?;
         assert_eq!(dt, hms);
         Ok(())
     }
@@ -221,28 +221,26 @@
     #[test]
     fn test_date_time_utc() -> Result<()> {
         let db = checked_memory_handle()?;
-        let date = NaiveDate::from_ymd(2016, 2, 23);
-        let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
+        let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
+        let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap();
         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 (?1)", [utc])?;
 
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let s: String = db.one_column("SELECT t FROM foo")?;
         assert_eq!("2016-02-23 23: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.one_column("SELECT t FROM foo")?;
         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.one_column("SELECT '2016-02-23 23:56:04.789'")?;
         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.one_column("SELECT '2016-02-23 23:56:04'")?;
         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.one_column("SELECT '2016-02-23 23:56:04.789+00:00'")?;
         assert_eq!(utc, v4);
         Ok(())
     }
@@ -250,18 +248,18 @@
     #[test]
     fn test_date_time_local() -> Result<()> {
         let db = checked_memory_handle()?;
-        let date = NaiveDate::from_ymd(2016, 2, 23);
-        let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
+        let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
+        let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap();
         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 (?1)", [local])?;
 
         // Stored string should be in UTC
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let s: String = db.one_column("SELECT t FROM foo")?;
         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.one_column("SELECT t FROM foo")?;
         assert_eq!(local, v);
         Ok(())
     }
@@ -271,13 +269,13 @@
         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])?;
+        db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
 
         // Stored string should preserve timezone offset
-        let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let s: String = db.one_column("SELECT t FROM foo")?;
         assert!(s.ends_with("+04:00"));
 
-        let v: DateTime<FixedOffset> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let v: DateTime<FixedOffset> = db.one_column("SELECT t FROM foo")?;
         assert_eq!(time.offset(), v.offset());
         assert_eq!(time, v);
         Ok(())
@@ -286,38 +284,36 @@
     #[test]
     fn test_sqlite_functions() -> Result<()> {
         let db = checked_memory_handle()?;
-        let result: Result<NaiveTime> = db.query_row("SELECT CURRENT_TIME", [], |r| r.get(0));
-        assert!(result.is_ok());
-        let result: Result<NaiveDate> = db.query_row("SELECT CURRENT_DATE", [], |r| r.get(0));
-        assert!(result.is_ok());
-        let result: Result<NaiveDateTime> =
-            db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
-        assert!(result.is_ok());
-        let result: Result<DateTime<Utc>> =
-            db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
-        assert!(result.is_ok());
+        let result: Result<NaiveTime> = db.one_column("SELECT CURRENT_TIME");
+        result.unwrap();
+        let result: Result<NaiveDate> = db.one_column("SELECT CURRENT_DATE");
+        result.unwrap();
+        let result: Result<NaiveDateTime> = db.one_column("SELECT CURRENT_TIMESTAMP");
+        result.unwrap();
+        let result: Result<DateTime<Utc>> = db.one_column("SELECT CURRENT_TIMESTAMP");
+        result.unwrap();
         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());
+        let result: Result<bool> = db.query_row("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()], |r| r.get(0));
+        result.unwrap();
         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());
+        let result: Result<bool> = db.query_row("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()], |r| r.get(0));
+        result.unwrap();
         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());
+        DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00Z")).unwrap();
+        DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00+00")).unwrap();
     }
 }
diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs
index b95a378..91eed09 100644
--- a/src/types/from_sql.rs
+++ b/src/types/from_sql.rs
@@ -52,7 +52,7 @@
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match *self {
             FromSqlError::InvalidType => write!(f, "Invalid type"),
-            FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i),
+            FromSqlError::OutOfRange(i) => write!(f, "Value {i} out of range"),
             FromSqlError::InvalidBlobSize {
                 expected_size,
                 blob_size,
@@ -244,7 +244,7 @@
         {
             for n in out_of_range {
                 let err = db
-                    .query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
+                    .query_row("SELECT ?1", [n], |r| r.get::<_, T>(0))
                     .unwrap_err();
                 match err {
                     Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
@@ -254,7 +254,7 @@
             for n in in_range {
                 assert_eq!(
                     *n,
-                    db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
+                    db.query_row("SELECT ?1", [n], |r| r.get::<_, T>(0))
                         .unwrap()
                         .into()
                 );
diff --git a/src/types/mod.rs b/src/types/mod.rs
index 4000ae2..eece984 100644
--- a/src/types/mod.rs
+++ b/src/types/mod.rs
@@ -102,7 +102,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 (?1)", [Null])
 /// }
 /// ```
 #[derive(Copy, Clone)]
@@ -153,9 +153,9 @@
         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 (?1)", [&v1234])?;
 
-        let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
+        let v: Vec<u8> = db.one_column("SELECT b FROM foo")?;
         assert_eq!(v, v1234);
         Ok(())
     }
@@ -165,9 +165,9 @@
         let db = checked_memory_handle()?;
 
         let empty = vec![];
-        db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])?;
+        db.execute("INSERT INTO foo(b) VALUES (?1)", [&empty])?;
 
-        let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
+        let v: Vec<u8> = db.one_column("SELECT b FROM foo")?;
         assert_eq!(v, empty);
         Ok(())
     }
@@ -177,9 +177,9 @@
         let db = checked_memory_handle()?;
 
         let s = "hello, world!";
-        db.execute("INSERT INTO foo(t) VALUES (?)", &[&s])?;
+        db.execute("INSERT INTO foo(t) VALUES (?1)", [&s])?;
 
-        let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let from: String = db.one_column("SELECT t FROM foo")?;
         assert_eq!(from, s);
         Ok(())
     }
@@ -189,9 +189,9 @@
         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 (?1)", [s.to_owned()])?;
 
-        let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let from: String = db.one_column("SELECT t FROM foo")?;
         assert_eq!(from, s);
         Ok(())
     }
@@ -200,12 +200,9 @@
     fn test_value() -> Result<()> {
         let db = checked_memory_handle()?;
 
-        db.execute("INSERT INTO foo(i) VALUES (?)", [Value::Integer(10)])?;
+        db.execute("INSERT INTO foo(i) VALUES (?1)", [Value::Integer(10)])?;
 
-        assert_eq!(
-            10i64,
-            db.query_row::<i64, _, _>("SELECT i FROM foo", [], |r| r.get(0))?
-        );
+        assert_eq!(10i64, db.one_column::<i64>("SELECT i FROM foo")?);
         Ok(())
     }
 
@@ -216,8 +213,8 @@
         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 (?1)", [&s])?;
+        db.execute("INSERT INTO foo(b) VALUES (?1)", [&b])?;
 
         let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")?;
         let mut rows = stmt.query([])?;
diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs
index a9761bd..6e38ba3 100644
--- a/src/types/serde_json.rs
+++ b/src/types/serde_json.rs
@@ -1,24 +1,62 @@
 //! [`ToSql`] and [`FromSql`] implementation for JSON `Value`.
 
-use serde_json::Value;
+use serde_json::{Number, Value};
 
 use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
-use crate::Result;
+use crate::{Error, Result};
 
-/// Serialize JSON `Value` to text.
+/// Serialize JSON `Value` to text:
+///
+///
+/// | JSON   | SQLite    |
+/// |----------|---------|
+/// | Null     | NULL    |
+/// | Bool     | 'true' / 'false' |
+/// | Number   | INT or REAL except u64 |
+/// | _ | TEXT |
 impl ToSql for Value {
     #[inline]
     fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
-        Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
+        match self {
+            Value::Null => Ok(ToSqlOutput::Borrowed(ValueRef::Null)),
+            Value::Number(n) if n.is_i64() => Ok(ToSqlOutput::from(n.as_i64().unwrap())),
+            Value::Number(n) if n.is_f64() => Ok(ToSqlOutput::from(n.as_f64().unwrap())),
+            _ => serde_json::to_string(self)
+                .map(ToSqlOutput::from)
+                .map_err(|err| Error::ToSqlConversionFailure(err.into())),
+        }
     }
 }
 
-/// Deserialize text/blob to JSON `Value`.
+/// Deserialize SQLite value to JSON `Value`:
+///
+/// | SQLite   | JSON    |
+/// |----------|---------|
+/// | NULL     | Null    |
+/// | 'null'   | Null    |
+/// | 'true'   | Bool    |
+/// | 1        | Number  |
+/// | 0.1      | Number  |
+/// | '"text"' | String  |
+/// | 'text'   | _Error_ |
+/// | '[0, 1]' | Array   |
+/// | '{"x": 1}' | Object  |
 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), // KO for b"text"
+            ValueRef::Blob(b) => serde_json::from_slice(b),
+            ValueRef::Integer(i) => Ok(Value::Number(Number::from(i))),
+            ValueRef::Real(f) => {
+                match Number::from_f64(f) {
+                    Some(n) => Ok(Value::Number(n)),
+                    _ => return Err(FromSqlError::InvalidType), // FIXME
+                }
+            }
+            ValueRef::Null => Ok(Value::Null),
+        }
+        .map_err(|err| FromSqlError::Other(Box::new(err)))
     }
 }
 
@@ -26,6 +64,7 @@
 mod test {
     use crate::types::ToSql;
     use crate::{Connection, Result};
+    use serde_json::{Number, Value};
 
     fn checked_memory_handle() -> Result<Connection> {
         let db = Connection::open_in_memory()?;
@@ -38,16 +77,59 @@
         let db = checked_memory_handle()?;
 
         let json = r#"{"foo": 13, "bar": "baz"}"#;
-        let data: serde_json::Value = serde_json::from_str(json).unwrap();
+        let data: Value = serde_json::from_str(json).unwrap();
         db.execute(
-            "INSERT INTO foo (t, b) VALUES (?, ?)",
-            &[&data as &dyn ToSql, &json.as_bytes()],
+            "INSERT INTO foo (t, b) VALUES (?1, ?2)",
+            [&data as &dyn ToSql, &json.as_bytes()],
         )?;
 
-        let t: serde_json::Value = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+        let t: Value = db.one_column("SELECT t FROM foo")?;
         assert_eq!(data, t);
-        let b: serde_json::Value = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
+        let b: Value = db.one_column("SELECT b FROM foo")?;
         assert_eq!(data, b);
         Ok(())
     }
+
+    #[test]
+    fn test_to_sql() -> Result<()> {
+        let db = Connection::open_in_memory()?;
+
+        let v: Option<String> = db.query_row("SELECT ?", [Value::Null], |r| r.get(0))?;
+        assert_eq!(None, v);
+        let v: String = db.query_row("SELECT ?", [Value::Bool(true)], |r| r.get(0))?;
+        assert_eq!("true", v);
+        let v: i64 = db.query_row("SELECT ?", [Value::Number(Number::from(1))], |r| r.get(0))?;
+        assert_eq!(1, v);
+        let v: f64 = db.query_row(
+            "SELECT ?",
+            [Value::Number(Number::from_f64(0.1).unwrap())],
+            |r| r.get(0),
+        )?;
+        assert_eq!(0.1, v);
+        let v: String =
+            db.query_row("SELECT ?", [Value::String("text".to_owned())], |r| r.get(0))?;
+        assert_eq!("\"text\"", v);
+        Ok(())
+    }
+
+    #[test]
+    fn test_from_sql() -> Result<()> {
+        let db = Connection::open_in_memory()?;
+
+        let v: Value = db.one_column("SELECT NULL")?;
+        assert_eq!(Value::Null, v);
+        let v: Value = db.one_column("SELECT 'null'")?;
+        assert_eq!(Value::Null, v);
+        let v: Value = db.one_column("SELECT 'true'")?;
+        assert_eq!(Value::Bool(true), v);
+        let v: Value = db.one_column("SELECT 1")?;
+        assert_eq!(Value::Number(Number::from(1)), v);
+        let v: Value = db.one_column("SELECT 0.1")?;
+        assert_eq!(Value::Number(Number::from_f64(0.1).unwrap()), v);
+        let v: Value = db.one_column("SELECT '\"text\"'")?;
+        assert_eq!(Value::String("text".to_owned()), v);
+        let v: Result<Value> = db.one_column("SELECT 'text'");
+        assert!(v.is_err());
+        Ok(())
+    }
 }
diff --git a/src/types/time.rs b/src/types/time.rs
index 4e2811e..03b2d61 100644
--- a/src/types/time.rs
+++ b/src/types/time.rs
@@ -91,9 +91,9 @@
         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 (?1)", [ts])?;
 
-            let from: OffsetDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+            let from: OffsetDateTime = db.one_column("SELECT t FROM foo")?;
 
             db.execute("DELETE FROM foo", [])?;
 
@@ -143,7 +143,7 @@
                 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));
+            let result: Result<OffsetDateTime> = db.query_row("SELECT ?1", [s], |r| r.get(0));
             assert_eq!(result, t);
         }
         Ok(())
@@ -152,17 +152,16 @@
     #[test]
     fn test_sqlite_functions() -> Result<()> {
         let db = Connection::open_in_memory()?;
-        let result: Result<OffsetDateTime> =
-            db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
-        assert!(result.is_ok());
+        let result: Result<OffsetDateTime> = db.one_column("SELECT CURRENT_TIMESTAMP");
+        result.unwrap();
         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());
+        let result: Result<bool> = db.query_row("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [OffsetDateTime::now_utc()], |r| r.get(0));
+        result.unwrap();
         Ok(())
     }
 }
diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs
index 4e0d882..c2bc007 100644
--- a/src/types/to_sql.rs
+++ b/src/types/to_sql.rs
@@ -278,7 +278,7 @@
         let _a: &[&dyn ToSql] = crate::params![a];
         let r = ToSql::to_sql(&a);
 
-        assert!(r.is_ok());
+        r.unwrap();
     }
 
     #[test]
@@ -287,10 +287,10 @@
         let s = "str";
         let cow: Cow<str> = Cow::Borrowed(s);
         let r = cow.to_sql();
-        assert!(r.is_ok());
+        r.unwrap();
         let cow: Cow<str> = Cow::Owned::<str>(String::from(s));
         let r = cow.to_sql();
-        assert!(r.is_ok());
+        r.unwrap();
         // Ensure this compiles.
         let _p: &[&dyn ToSql] = crate::params![cow];
     }
@@ -301,7 +301,7 @@
         let _s: &[&dyn ToSql] = crate::params![s];
         let r = ToSql::to_sql(&s);
 
-        assert!(r.is_ok());
+        r.unwrap();
     }
 
     #[test]
@@ -310,7 +310,7 @@
         let _s: &[&dyn ToSql] = crate::params![s];
         let r = s.to_sql();
 
-        assert!(r.is_ok());
+        r.unwrap();
     }
 
     #[test]
@@ -319,7 +319,7 @@
         let _s: &[&dyn ToSql] = crate::params![s];
         let r = ToSql::to_sql(&s);
 
-        assert!(r.is_ok());
+        r.unwrap();
     }
 
     #[test]
@@ -331,32 +331,32 @@
         let s: Rc<Box<str>> = Rc::new(source_str.clone());
         let _s: &[&dyn ToSql] = crate::params![s];
         let r = s.to_sql();
-        assert!(r.is_ok());
+        r.unwrap();
 
         let s: Arc<Box<str>> = Arc::new(source_str.clone());
         let _s: &[&dyn ToSql] = crate::params![s];
         let r = s.to_sql();
-        assert!(r.is_ok());
+        r.unwrap();
 
         let s: Arc<str> = Arc::from(&*source_str);
         let _s: &[&dyn ToSql] = crate::params![s];
         let r = s.to_sql();
-        assert!(r.is_ok());
+        r.unwrap();
 
         let s: Arc<dyn ToSql> = Arc::new(source_str.clone());
         let _s: &[&dyn ToSql] = crate::params![s];
         let r = s.to_sql();
-        assert!(r.is_ok());
+        r.unwrap();
 
         let s: Rc<str> = Rc::from(&*source_str);
         let _s: &[&dyn ToSql] = crate::params![s];
         let r = s.to_sql();
-        assert!(r.is_ok());
+        r.unwrap();
 
         let s: Rc<dyn ToSql> = Rc::new(source_str);
         let _s: &[&dyn ToSql] = crate::params![s];
         let r = s.to_sql();
-        assert!(r.is_ok());
+        r.unwrap();
     }
 
     #[cfg(feature = "i128_blob")]
@@ -368,10 +368,10 @@
         db.execute(
             "
             INSERT INTO foo(i128, desc) VALUES
-                (?, 'zero'),
-                (?, 'neg one'), (?, 'neg two'),
-                (?, 'pos one'), (?, 'pos two'),
-                (?, 'min'), (?, 'max')",
+                (?1, 'zero'),
+                (?2, 'neg one'), (?3, 'neg two'),
+                (?4, 'pos one'), (?5, 'pos two'),
+                (?6, 'min'), (?7, 'max')",
             [0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
         )?;
 
@@ -410,11 +410,11 @@
         let id = Uuid::new_v4();
 
         db.execute(
-            "INSERT INTO foo (id, label) VALUES (?, ?)",
+            "INSERT INTO foo (id, label) VALUES (?1, ?2)",
             params![id, "target"],
         )?;
 
-        let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?")?;
+        let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?1")?;
 
         let mut rows = stmt.query(params![id])?;
         let row = rows.next()?.unwrap();
diff --git a/src/types/url.rs b/src/types/url.rs
index fea8500..0ebb59b 100644
--- a/src/types/url.rs
+++ b/src/types/url.rs
@@ -49,7 +49,7 @@
         let url2 = "http://www.example2.com/👌";
 
         db.execute(
-            "INSERT INTO urls (i, v) VALUES (0, ?), (1, ?), (2, ?), (3, ?)",
+            "INSERT INTO urls (i, v) VALUES (0, ?1), (1, ?2), (2, ?3), (3, ?4)",
             // also insert a non-hex encoded url (which might be present if it was
             // inserted separately)
             params![url0, url1, url2, "illegal"],
diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs
index 8fba6b3..065c52d 100644
--- a/src/unlock_notify.rs
+++ b/src/unlock_notify.rs
@@ -109,8 +109,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: i64 = db1.one_column("SELECT x FROM foo")?;
+        assert_eq!(42i64, the_answer);
         child.join().unwrap();
         Ok(())
     }
diff --git a/src/util/mod.rs b/src/util/mod.rs
index 2b8dcfd..e81e3c0 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -5,7 +5,5 @@
 pub(crate) use small_cstr::SmallCString;
 
 // Doesn't use any modern features or vtab stuff, but is only used by them.
-#[cfg(any(feature = "modern_sqlite", feature = "vtab"))]
 mod sqlite_string;
-#[cfg(any(feature = "modern_sqlite", feature = "vtab"))]
 pub(crate) use sqlite_string::SqliteMallocString;
diff --git a/src/util/small_cstr.rs b/src/util/small_cstr.rs
index 78e43bd..1ec7374 100644
--- a/src/util/small_cstr.rs
+++ b/src/util/small_cstr.rs
@@ -163,8 +163,8 @@
         assert_eq!(SmallCString::new("").unwrap().0.as_slice(), b"\0");
         assert_eq!(SmallCString::new("").unwrap().as_bytes_without_nul(), b"");
 
-        assert!(SmallCString::new("\0").is_err());
-        assert!(SmallCString::new("\0abc").is_err());
-        assert!(SmallCString::new("abc\0").is_err());
+        SmallCString::new("\0").unwrap_err();
+        SmallCString::new("\0abc").unwrap_err();
+        SmallCString::new("abc\0").unwrap_err();
     }
 }
diff --git a/src/util/sqlite_string.rs b/src/util/sqlite_string.rs
index da261ba..ae24c28 100644
--- a/src/util/sqlite_string.rs
+++ b/src/util/sqlite_string.rs
@@ -1,10 +1,7 @@
 // This is used when either vtab or modern-sqlite is on. Different methods are
 // used in each feature. Avoid having to track this for each function. We will
 // still warn for anything that's not used by either, though.
-#![cfg_attr(
-    not(all(feature = "vtab", feature = "modern-sqlite")),
-    allow(dead_code)
-)]
+#![cfg_attr(not(feature = "vtab"), allow(dead_code))]
 use crate::ffi;
 use std::marker::PhantomData;
 use std::os::raw::{c_char, c_int};
@@ -134,7 +131,8 @@
                     //   (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);
+                    let len = s.len().saturating_add(1).min(isize::MAX as usize);
+                    let layout = Layout::from_size_align_unchecked(len, 1);
                     // Note: This call does not return.
                     handle_alloc_error(layout);
                 });
@@ -214,7 +212,7 @@
         let mut v = vec![];
         for i in 0..1000 {
             v.push(SqliteMallocString::from_str(&i.to_string()).into_raw());
-            v.push(SqliteMallocString::from_str(&format!("abc {} 😀", i)).into_raw());
+            v.push(SqliteMallocString::from_str(&format!("abc {i} 😀")).into_raw());
         }
         unsafe {
             for (i, s) in v.chunks_mut(2).enumerate() {
@@ -226,7 +224,7 @@
                 );
                 assert_eq!(
                     std::ffi::CStr::from_ptr(s1).to_str().unwrap(),
-                    &format!("abc {} 😀", i)
+                    &format!("abc {i} 😀")
                 );
                 let _ = SqliteMallocString::from_raw(s0).unwrap();
                 let _ = SqliteMallocString::from_raw(s1).unwrap();
diff --git a/src/vtab/array.rs b/src/vtab/array.rs
index f09ac1a..be19a3e 100644
--- a/src/vtab/array.rs
+++ b/src/vtab/array.rs
@@ -17,7 +17,7 @@
 //!     let v = [1i64, 2, 3, 4];
 //!     // 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 mut stmt = db.prepare("SELECT value from rarray(?1);")?;
 //!     let rows = stmt.query_map([values], |row| row.get::<_, i64>(0))?;
 //!     for value in rows {
 //!         println!("{}", value?);
@@ -206,9 +206,9 @@
         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(?1);")?;
 
-            let rows = stmt.query_map(&[&ptr], |row| row.get::<_, i64>(0))?;
+            let rows = stmt.query_map([&ptr], |row| row.get::<_, i64>(0))?;
             assert_eq!(2, Rc::strong_count(&ptr));
             let mut count = 0;
             for (i, value) in rows.enumerate() {
diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs
index a65db05..843363c 100644
--- a/src/vtab/csvtab.rs
+++ b/src/vtab/csvtab.rs
@@ -208,13 +208,13 @@
                 let mut record = csv::ByteRecord::new();
                 if reader.read_byte_record(&mut record)? {
                     for (i, _) in record.iter().enumerate() {
-                        cols.push(format!("c{}", i));
+                        cols.push(format!("c{i}"));
                     }
                 }
             }
         } else if let Some(n_col) = n_col {
             for i in 0..n_col {
-                cols.push(format!("c{}", i));
+                cols.push(format!("c{i}"));
             }
         }
 
diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs
index 07008f3..f070e3a 100644
--- a/src/vtab/mod.rs
+++ b/src/vtab/mod.rs
@@ -187,7 +187,6 @@
 /// Virtual table configuration options
 #[repr(i32)]
 #[non_exhaustive]
-#[cfg(feature = "modern_sqlite")] // 3.7.7
 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
 pub enum VTabConfig {
     /// Equivalent to SQLITE_VTAB_CONSTRAINT_SUPPORT
@@ -203,8 +202,6 @@
 
 impl VTabConnection {
     /// Configure various facets of the virtual table interface
-    #[cfg(feature = "modern_sqlite")] // 3.7.7
-    #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
     pub fn config(&mut self, config: VTabConfig) -> Result<()> {
         crate::error::check(unsafe { ffi::sqlite3_vtab_config(self.0, config as c_int) })
     }
@@ -369,7 +366,6 @@
     }
 }
 
-#[cfg(feature = "modern_sqlite")] // 3.9.0
 bitflags::bitflags! {
     /// Virtual table scan flags
     /// See [Function Flags](https://sqlite.org/c3ref/c_index_scan_unique.html) for details.
@@ -461,7 +457,7 @@
     #[inline]
     pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
         unsafe {
-            (*self.0).orderByConsumed = if order_by_consumed { 1 } else { 0 };
+            (*self.0).orderByConsumed = order_by_consumed as c_int;
         }
     }
 
@@ -474,8 +470,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 {
@@ -484,16 +478,12 @@
     }
 
     /// Mask of SQLITE_INDEX_SCAN_* flags.
-    #[cfg(feature = "modern_sqlite")] // SQLite >= 3.9.0
-    #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
     #[inline]
     pub fn set_idx_flags(&mut self, flags: IndexFlags) {
         unsafe { (*self.0).idxFlags = flags.bits() };
     }
 
     /// Mask of columns used by statement
-    #[cfg(feature = "modern_sqlite")] // SQLite >= 3.10.0
-    #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
     #[inline]
     pub fn col_used(&self) -> u64 {
         unsafe { (*self.0).colUsed }
@@ -509,7 +499,7 @@
         if collation.is_null() {
             return Err(Error::SqliteFailure(
                 ffi::Error::new(ffi::SQLITE_MISUSE),
-                Some(format!("{} is out of range", constraint_idx)),
+                Some(format!("{constraint_idx} is out of range")),
             ));
         }
         Ok(unsafe { CStr::from_ptr(collation) }.to_str()?)
@@ -623,13 +613,13 @@
     /// 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 };
+        self.0.omit = omit as std::os::raw::c_uchar;
     }
 }
 
 /// `feature = "vtab"`
 pub struct OrderByIter<'a> {
-    iter: slice::Iter<'a, ffi::sqlite3_index_info_sqlite3_index_orderby>,
+    iter: slice::Iter<'a, ffi::sqlite3_index_orderby>,
 }
 
 impl<'a> Iterator for OrderByIter<'a> {
@@ -647,7 +637,7 @@
 }
 
 /// A column of the ORDER BY clause.
-pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
+pub struct OrderBy<'a>(&'a ffi::sqlite3_index_orderby);
 
 impl OrderBy<'_> {
     /// Column number
@@ -934,7 +924,7 @@
             return Ok((param, value));
         }
     }
-    Err(Error::ModuleError(format!("illegal argument: '{}'", arg)))
+    Err(Error::ModuleError(format!("illegal argument: '{arg}'")))
 }
 
 // FIXME copy/paste from function.rs
@@ -1334,7 +1324,7 @@
 #[cfg(feature = "series")]
 #[cfg_attr(docsrs, doc(cfg(feature = "series")))]
 pub mod series; // SQLite >= 3.9.0
-#[cfg(test)]
+#[cfg(all(test, feature = "modern_sqlite"))]
 mod vtablog;
 
 #[cfg(test)]
diff --git a/src/vtab/series.rs b/src/vtab/series.rs
index fffbd4d..4b1993e 100644
--- a/src/vtab/series.rs
+++ b/src/vtab/series.rs
@@ -28,6 +28,7 @@
 const SERIES_COLUMN_STEP: c_int = 3;
 
 bitflags::bitflags! {
+    #[derive(Clone, Copy)]
     #[repr(C)]
     struct QueryPlanFlags: ::std::os::raw::c_int {
         // start = $value  -- constraint exists
@@ -41,7 +42,7 @@
         // output in ascending order
         const ASC  = 16;
         // Both start and stop
-        const BOTH  = QueryPlanFlags::START.bits | QueryPlanFlags::STOP.bits;
+        const BOTH  = QueryPlanFlags::START.bits() | QueryPlanFlags::STOP.bits();
     }
 }
 
@@ -115,6 +116,7 @@
         }
         if idx_num.contains(QueryPlanFlags::BOTH) {
             // Both start= and stop= boundaries are available.
+            #[allow(clippy::bool_to_int_with_if)]
             info.set_estimated_cost(f64::from(
                 2 - if idx_num.contains(QueryPlanFlags::STEP) {
                     1
diff --git a/src/vtab/vtablog.rs b/src/vtab/vtablog.rs
index bc2e01f..1b3e1b8 100644
--- a/src/vtab/vtablog.rs
+++ b/src/vtab/vtablog.rs
@@ -153,7 +153,7 @@
 
 impl<'vtab> UpdateVTab<'vtab> for VTabLog {
     fn delete(&mut self, arg: ValueRef<'_>) -> Result<()> {
-        println!("VTabLog::delete({}, {:?})", self.i_inst, arg);
+        println!("VTabLog::delete({}, {arg:?})", self.i_inst);
         Ok(())
     }
 
@@ -163,7 +163,7 @@
             self.i_inst,
             args.iter().collect::<Vec<ValueRef<'_>>>()
         );
-        Ok(self.n_row as i64)
+        Ok(self.n_row)
     }
 
     fn update(&mut self, args: &Values<'_>) -> Result<()> {
@@ -246,7 +246,7 @@
                 self.row_id
             )
         } else {
-            format!("{}{}", i, self.row_id)
+            format!("{i}{}", self.row_id)
         };
         println!(
             "VTabLogCursor::column(tab={}, cursor={}, i={}): {}",
@@ -286,13 +286,13 @@
         let mut stmt = db.prepare("SELECT * FROM log;")?;
         let mut rows = stmt.query([])?;
         while rows.next()?.is_some() {}
-        db.execute("DELETE FROM log WHERE a = ?", ["a1"])?;
+        db.execute("DELETE FROM log WHERE a = ?1", ["a1"])?;
         db.execute(
-            "INSERT INTO log (a, b, c) VALUES (?, ?, ?)",
+            "INSERT INTO log (a, b, c) VALUES (?1, ?2, ?3)",
             ["a", "b", "c"],
         )?;
         db.execute(
-            "UPDATE log SET b = ?, c = ? WHERE a = ?",
+            "UPDATE log SET b = ?1, c = ?2 WHERE a = ?3",
             ["bn", "cn", "a1"],
         )?;
         Ok(())
diff --git a/tests/deny_single_threaded_sqlite_config.rs b/tests/deny_single_threaded_sqlite_config.rs
index adfc8e5..7118dab 100644
--- a/tests/deny_single_threaded_sqlite_config.rs
+++ b/tests/deny_single_threaded_sqlite_config.rs
@@ -16,5 +16,5 @@
         assert_eq!(ffi::sqlite3_initialize(), ffi::SQLITE_OK);
     }
     let res = Connection::open_in_memory();
-    assert!(res.is_err());
+    res.unwrap_err();
 }