aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 05:14:17 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 05:14:17 +0000
commit6660523b24739b1c39571e059d8e1be80551b6bf (patch)
tree551d464c08f582e18853edf08239fe3b74bbfbc3
parent04570843781b79ee65394e794e0ee03b85082da6 (diff)
parent048f509b989d65e59578f2e70bf3b55470c43632 (diff)
downloadrusqlite-android14-mainline-sdkext-release.tar.gz
Snap for 10453563 from 048f509b989d65e59578f2e70bf3b55470c43632 to mainline-sdkext-releaseaml_sdk_341510000aml_sdk_341410000aml_sdk_341110080aml_sdk_341110000aml_sdk_341010000aml_sdk_340912010android14-mainline-sdkext-release
Change-Id: I4930260030d96cd09b39855f5a6f41af2b11e17f
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--Android.bp9
-rw-r--r--Cargo.toml16
-rw-r--r--Cargo.toml.orig17
-rw-r--r--METADATA12
-rw-r--r--README.md48
-rw-r--r--TEST_MAPPING14
-rw-r--r--patches/Android.bp.diff13
-rw-r--r--src/busy.rs12
-rw-r--r--src/config.rs2
-rw-r--r--src/context.rs7
-rw-r--r--src/error.rs96
-rw-r--r--src/functions.rs31
-rw-r--r--src/hooks.rs14
-rw-r--r--src/inner_connection.rs78
-rw-r--r--src/lib.rs227
-rw-r--r--src/params.rs128
-rw-r--r--src/pragma.rs22
-rw-r--r--src/raw_statement.rs10
-rw-r--r--src/row.rs2
-rw-r--r--src/session.rs2
-rw-r--r--src/statement.rs146
-rw-r--r--src/trace.rs2
-rw-r--r--src/transaction.rs48
-rw-r--r--src/types/from_sql.rs2
-rw-r--r--src/types/mod.rs69
-rw-r--r--src/types/to_sql.rs1
-rw-r--r--src/types/value_ref.rs3
-rw-r--r--src/util/small_cstr.rs2
-rw-r--r--src/vtab/array.rs2
-rw-r--r--src/vtab/csvtab.rs31
-rw-r--r--src/vtab/mod.rs336
-rw-r--r--src/vtab/series.rs11
-rw-r--r--src/vtab/vtablog.rs300
-rw-r--r--tests/deny_single_threaded_sqlite_config.rs11
-rw-r--r--tests/vtab.rs4
36 files changed, 1396 insertions, 334 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index de52e1b..96ed8b8 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
{
"git": {
- "sha1": "8141b5e085bd3a02951588413e5569f1ddf17a8c"
+ "sha1": "26293a11f595574897e7e5a5b639d1587255c6b9"
},
"path_in_vcs": ""
} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
index d5c31aa..3e4e03c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,7 +23,7 @@ rust_library {
host_supported: true,
crate_name: "rusqlite",
cargo_env_compat: true,
- cargo_pkg_version: "0.27.0",
+ cargo_pkg_version: "0.28.0",
srcs: ["src/lib.rs"],
edition: "2018",
features: [
@@ -31,14 +31,17 @@ rust_library {
"trace",
],
rustlibs: [
- "libbitflags",
+ "libbitflags-1.3.2",
"libfallible_iterator",
"libfallible_streaming_iterator",
"libhashlink",
"liblibsqlite3_sys",
- "libmemchr",
"libsmallvec",
],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
}
rust_library {
diff --git a/Cargo.toml b/Cargo.toml
index f8b8dda..08b3bc0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@
[package]
edition = "2018"
name = "rusqlite"
-version = "0.27.0"
+version = "0.28.0"
authors = ["The rusqlite developers"]
exclude = [
"/.github/*",
@@ -89,17 +89,14 @@ version = "0.2"
version = "0.1"
[dependencies.hashlink]
-version = "0.7"
+version = "0.8"
[dependencies.lazy_static]
version = "1.4"
optional = true
[dependencies.libsqlite3-sys]
-version = "0.24.0"
-
-[dependencies.memchr]
-version = "2.3"
+version = "0.25.0"
[dependencies.serde_json]
version = "1.0"
@@ -122,7 +119,7 @@ version = "2.1"
optional = true
[dependencies.uuid]
-version = "0.8"
+version = "1.0"
optional = true
[dev-dependencies.bencher]
@@ -135,7 +132,7 @@ version = "0.3"
version = "1.4"
[dev-dependencies.regex]
-version = "1.3"
+version = "1.5.5"
[dev-dependencies.tempfile]
version = "3.1.0"
@@ -144,7 +141,7 @@ version = "3.1.0"
version = "2.6.0"
[dev-dependencies.uuid]
-version = "0.8"
+version = "1.0"
features = ["v4"]
[features]
@@ -211,6 +208,7 @@ modern-full = [
"window",
]
modern_sqlite = ["libsqlite3-sys/bundled_bindings"]
+release_memory = ["libsqlite3-sys/min_sqlite_version_3_7_16"]
series = ["vtab"]
session = [
"libsqlite3-sys/session",
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 6ab1a7e..bd81d44 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,7 @@
[package]
name = "rusqlite"
-version = "0.27.0"
+# Note: Update version in README.md when you change this.
+version = "0.28.0"
authors = ["The rusqlite developers"]
edition = "2018"
description = "Ergonomic wrapper for SQLite"
@@ -42,6 +43,8 @@ collation = []
functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
# sqlite3_log: 3.6.23 (2010-03-09)
trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
+# sqlite3_db_release_memory: 3.7.10 (2012-01-16)
+release_memory = ["libsqlite3-sys/min_sqlite_version_3_7_16"]
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"]
@@ -71,6 +74,7 @@ bundled-windows = ["libsqlite3-sys/bundled-windows"]
with-asan = ["libsqlite3-sys/with-asan"]
column_decltype = []
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
+# Note: doesn't support 32-bit.
winsqlite3 = ["libsqlite3-sys/winsqlite3"]
# Helper feature for enabling most non-build-related optional features
@@ -108,7 +112,7 @@ bundled-full = ["modern-full", "bundled"]
[dependencies]
time = { version = "0.3.0", features = ["formatting", "macros", "parsing"], optional = true }
bitflags = "1.2"
-hashlink = "0.7"
+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 }
@@ -116,16 +120,15 @@ url = { version = "2.1", optional = true }
lazy_static = { version = "1.4", optional = true }
fallible-iterator = "0.2"
fallible-streaming-iterator = "0.1"
-memchr = "2.3"
-uuid = { version = "0.8", optional = true }
+uuid = { version = "1.0", optional = true }
smallvec = "1.6.1"
[dev-dependencies]
doc-comment = "0.3"
tempfile = "3.1.0"
lazy_static = "1.4"
-regex = "1.3"
-uuid = { version = "0.8", features = ["v4"] }
+regex = "1.5.5"
+uuid = { version = "1.0", features = ["v4"] }
unicase = "2.6.0"
# Use `bencher` over criterion because it builds much faster and we don't have
# many benchmarks
@@ -133,7 +136,7 @@ bencher = "0.1"
[dependencies.libsqlite3-sys]
path = "libsqlite3-sys"
-version = "0.24.0"
+version = "0.25.0"
[[test]]
name = "config_log"
diff --git a/METADATA b/METADATA
index 77fbcb9..ea56c4e 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,7 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update rust/crates/rusqlite
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+
name: "rusqlite"
description: "Ergonomic wrapper for SQLite"
third_party {
@@ -7,13 +11,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/rusqlite/rusqlite-0.27.0.crate"
+ value: "https://static.crates.io/crates/rusqlite/rusqlite-0.28.0.crate"
}
- version: "0.27.0"
+ version: "0.28.0"
license_type: NOTICE
last_upgrade_date {
year: 2022
- month: 3
- day: 1
+ month: 12
+ day: 13
}
}
diff --git a/README.md b/README.md
index 073c464..fdc2381 100644
--- a/README.md
+++ b/README.md
@@ -8,11 +8,32 @@
[![Dependency Status](https://deps.rs/repo/github/rusqlite/rusqlite/status.svg)](https://deps.rs/repo/github/rusqlite/rusqlite)
[![Discord Chat](https://img.shields.io/discord/927966344266256434.svg?logo=discord)](https://discord.gg/nFYfGPB8g4)
-Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
-an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
+Rusqlite is an ergonomic wrapper for using SQLite from Rust.
+
+Historically, the API was based on the one from [`rust-postgres`](https://github.com/sfackler/rust-postgres). However, the two have diverged in many ways, and no compatibility between the two is intended.
+
+## Usage
+
+In your Cargo.toml:
+
+```toml
+[dependencies]
+# `bundled` causes us to automatically compile and link in an up to date
+# version of SQLite for you. This avoids many common build issues, and
+# avoids depending on the version of SQLite on the users system (or your
+# system), which may be old or missing. It's the right choice for most
+# programs that control their own SQLite databases.
+#
+# 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"] }
+```
+
+Simple example usage:
```rust
-use rusqlite::{params, Connection, Result};
+use rusqlite::{Connection, Result};
#[derive(Debug)]
struct Person {
@@ -26,11 +47,11 @@ fn main() -> Result<()> {
conn.execute(
"CREATE TABLE person (
- id INTEGER PRIMARY KEY,
- name TEXT NOT NULL,
- data BLOB
- )",
- [],
+ id INTEGER PRIMARY KEY,
+ name TEXT NOT NULL,
+ data BLOB
+ )",
+ (), // empty list of parameters.
)?;
let me = Person {
id: 0,
@@ -39,7 +60,7 @@ fn main() -> Result<()> {
};
conn.execute(
"INSERT INTO person (name, data) VALUES (?1, ?2)",
- params![me.name, me.data],
+ (&me.name, &me.data),
)?;
let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
@@ -115,6 +136,7 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
* `extra_check` fail when a query passed to execute is readonly or has a column count > 0.
* `column_decltype` provides `columns()` method for Statements and Rows; omit if linking to a version of SQLite/SQLCipher compiled with `-DSQLITE_OMIT_DECLTYPE`.
* `collation` exposes [`sqlite3_create_collation_v2`](https://sqlite.org/c3ref/create_collation.html).
+* `winsqlite3` allows linking against the SQLite present in newer versions of Windows
## Notes on building rusqlite and libsqlite3-sys
@@ -127,14 +149,14 @@ You can adjust this behavior in a number of ways:
* If you use the `bundled`, `bundled-sqlcipher`, or `bundled-sqlcipher-vendored-openssl` features, `libsqlite3-sys` will use the
[cc](https://crates.io/crates/cc) crate to compile SQLite or SQLCipher from source and
link against that. This source is embedded in the `libsqlite3-sys` crate and
- is currently SQLite 3.38.0 (as of `rusqlite` 0.27.0 / `libsqlite3-sys`
- 0.24.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
+ is currently SQLite 3.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:
```toml
[dependencies.rusqlite]
- version = "0.27.0"
+ version = "0.28.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 `LIBSQLITE_FLAGS` variable, which can have a format like `"-USQLITE_ALPHA -DSQLITE_BETA SQLITE_GAMMA ..."`. That would disable the `SQLITE_ALPHA` flag, and set the `SQLITE_BETA` and `SQLITE_GAMMA` flags. (The initial `-D` can be omitted, as on the last one.)
+* When using 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.)
* When using `bundled-sqlcipher` (and not also using `bundled-sqlcipher-vendored-openssl`), `libsqlite3-sys` will need to
link against crypto libraries on the system. If the build script can find a `libcrypto` from OpenSSL or LibreSSL (it will consult `OPENSSL_LIB_DIR`/`OPENSSL_INCLUDE_DIR` and `OPENSSL_DIR` environment variables), it will use that. If building on and for Macs, and none of those variables are set, it will use the system's SecurityFramework instead.
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 91f37bf..5953440 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,19 +1,11 @@
// Generated by update_crate_tests.py for tests that depend on this crate.
{
- "presubmit": [
+ "imports": [
{
- "name": "keystore2_test"
+ "path": "system/security/keystore2"
},
{
- "name": "legacykeystore_test"
- }
- ],
- "presubmit-rust": [
- {
- "name": "keystore2_test"
- },
- {
- "name": "legacykeystore_test"
+ "path": "system/security/keystore2/legacykeystore"
}
]
}
diff --git a/patches/Android.bp.diff b/patches/Android.bp.diff
new file mode 100644
index 0000000..34d5397
--- /dev/null
+++ b/patches/Android.bp.diff
@@ -0,0 +1,13 @@
+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/busy.rs b/src/busy.rs
index b394d01..7297f20 100644
--- a/src/busy.rs
+++ b/src/busy.rs
@@ -90,7 +90,7 @@ mod test {
use std::thread;
use std::time::Duration;
- use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior};
+ use crate::{Connection, ErrorCode, Result, TransactionBehavior};
#[test]
fn test_default_busy() -> Result<()> {
@@ -101,12 +101,10 @@ mod test {
let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?;
let db2 = Connection::open(&path)?;
let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!());
- match r.unwrap_err() {
- Error::SqliteFailure(err, _) => {
- assert_eq!(err.code, ErrorCode::DatabaseBusy);
- }
- err => panic!("Unexpected error {}", err),
- }
+ assert_eq!(
+ r.unwrap_err().sqlite_error_code(),
+ Some(ErrorCode::DatabaseBusy)
+ );
tx1.rollback()
}
diff --git a/src/config.rs b/src/config.rs
index b59e5ef..b295d97 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -33,7 +33,7 @@ pub enum DbConfig {
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
/// Activates or deactivates the "reset" flag for a database connection.
/// Run VACUUM with this flag set to reset the database.
- SQLITE_DBCONFIG_RESET_DATABASE = 1009,
+ SQLITE_DBCONFIG_RESET_DATABASE = 1009, // 3.24.0
/// Activates or deactivates the "defensive" flag for a database connection.
SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0
/// Activates or deactivates the "writable_schema" flag.
diff --git a/src/context.rs b/src/context.rs
index 5f935fa..bcaefc9 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -23,6 +23,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
#[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => {
+ // TODO sqlite3_result_zeroblob64 // 3.8.11
return ffi::sqlite3_result_zeroblob(ctx, len);
}
#[cfg(feature = "array")]
@@ -42,7 +43,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r),
ValueRef::Text(s) => {
let length = s.len();
- if length > c_int::max_value() as usize {
+ if length > c_int::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else {
let (c_str, len, destructor) = match str_for_sqlite(s) {
@@ -50,16 +51,18 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
// TODO sqlite3_result_error
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
};
+ // TODO sqlite3_result_text64 // 3.8.7
ffi::sqlite3_result_text(ctx, c_str, len, destructor);
}
}
ValueRef::Blob(b) => {
let length = b.len();
- if length > c_int::max_value() as usize {
+ if length > c_int::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else if length == 0 {
ffi::sqlite3_result_zeroblob(ctx, 0);
} else {
+ // TODO sqlite3_result_blob64 // 3.8.7
ffi::sqlite3_result_blob(
ctx,
b.as_ptr().cast::<c_void>(),
diff --git a/src/error.rs b/src/error.rs
index 129f697..3c264d3 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -34,7 +34,7 @@ pub enum Error {
/// Error converting a string to a C-compatible string because it contained
/// an embedded nul.
- NulError(::std::ffi::NulError),
+ NulError(std::ffi::NulError),
/// Error when using SQL named parameters and passing a parameter name not
/// present in the SQL.
@@ -128,6 +128,19 @@ pub enum Error {
#[cfg(feature = "blob")]
#[cfg_attr(docsrs, doc(cfg(feature = "blob")))]
BlobSizeError,
+ /// Error referencing a specific token in the input SQL
+ #[cfg(feature = "modern_sqlite")] // 3.38.0
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+ SqlInputError {
+ /// error code
+ error: ffi::Error,
+ /// error message
+ msg: String,
+ /// SQL input
+ sql: String,
+ /// byte offset of the start of invalid token
+ offset: c_int,
+ },
}
impl PartialEq for Error {
@@ -172,6 +185,21 @@ impl PartialEq for Error {
}
#[cfg(feature = "blob")]
(Error::BlobSizeError, Error::BlobSizeError) => true,
+ #[cfg(feature = "modern_sqlite")]
+ (
+ Error::SqlInputError {
+ error: e1,
+ msg: m1,
+ sql: s1,
+ offset: o1,
+ },
+ Error::SqlInputError {
+ error: e2,
+ msg: m2,
+ sql: s2,
+ offset: o2,
+ },
+ ) => e1 == e2 && m1 == m2 && s1 == s2 && o1 == o2,
(..) => false,
}
}
@@ -184,14 +212,14 @@ impl From<str::Utf8Error> for Error {
}
}
-impl From<::std::ffi::NulError> for Error {
+impl From<std::ffi::NulError> for Error {
#[cold]
- fn from(err: ::std::ffi::NulError) -> Error {
+ fn from(err: std::ffi::NulError) -> Error {
Error::NulError(err)
}
}
-const UNKNOWN_COLUMN: usize = std::usize::MAX;
+const UNKNOWN_COLUMN: usize = usize::MAX;
/// The conversion isn't precise, but it's convenient to have it
/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
@@ -281,9 +309,15 @@ impl fmt::Display for Error {
#[cfg(feature = "functions")]
Error::GetAuxWrongType => write!(f, "get_aux called with wrong type"),
Error::MultipleStatement => write!(f, "Multiple statements provided"),
-
#[cfg(feature = "blob")]
Error::BlobSizeError => "Blob size is insufficient".fmt(f),
+ #[cfg(feature = "modern_sqlite")]
+ Error::SqlInputError {
+ ref msg,
+ offset,
+ ref sql,
+ ..
+ } => write!(f, "{} in {} at offset {}", msg, sql, offset),
}
}
}
@@ -331,14 +365,35 @@ impl error::Error for Error {
#[cfg(feature = "blob")]
Error::BlobSizeError => None,
+ #[cfg(feature = "modern_sqlite")]
+ Error::SqlInputError { ref error, .. } => Some(error),
}
}
}
+impl Error {
+ /// Returns the underlying SQLite error if this is [`Error::SqliteFailure`].
+ #[inline]
+ pub fn sqlite_error(&self) -> Option<&ffi::Error> {
+ match self {
+ Self::SqliteFailure(error, _) => Some(error),
+ _ => None,
+ }
+ }
+
+ /// Returns the underlying SQLite error code if this is
+ /// [`Error::SqliteFailure`].
+ #[inline]
+ pub fn sqlite_error_code(&self) -> Option<ffi::ErrorCode> {
+ self.sqlite_error().map(|error| error.code)
+ }
+}
+
// These are public but not re-exported by lib.rs, so only visible within crate.
#[cold]
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
+ // TODO sqlite3_error_offset // 3.38.0, #1130
Error::SqliteFailure(ffi::Error::new(code), message)
}
@@ -352,9 +407,38 @@ pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error {
error_from_sqlite_code(code, message)
}
+#[cold]
+#[cfg(not(all(feature = "modern_sqlite", not(feature = "bundled-sqlcipher"))))] // 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
+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)
+ } else {
+ let error = ffi::Error::new(code);
+ let msg = errmsg_to_string(ffi::sqlite3_errmsg(db));
+ if ffi::ErrorCode::Unknown == error.code {
+ let offset = ffi::sqlite3_error_offset(db);
+ if offset >= 0 {
+ return Error::SqlInputError {
+ error,
+ msg,
+ sql: sql.to_owned(),
+ offset,
+ };
+ }
+ }
+ Error::SqliteFailure(error, Some(msg))
+ }
+}
+
pub fn check(code: c_int) -> Result<()> {
if code != crate::ffi::SQLITE_OK {
- Err(crate::error::error_from_sqlite_code(code, None))
+ Err(error_from_sqlite_code(code, None))
} else {
Ok(())
}
diff --git a/src/functions.rs b/src/functions.rs
index e613182..138baac 100644
--- a/src/functions.rs
+++ b/src/functions.rs
@@ -162,6 +162,19 @@ impl Context<'_> {
unsafe { ValueRef::from_value(arg) }
}
+ /// Returns the subtype of `idx`th argument.
+ ///
+ /// # Failure
+ ///
+ /// 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) }
+ }
+
/// Fetch or insert the auxiliary data associated with a particular
/// parameter. This is intended to be an easier-to-use way of fetching it
/// compared to calling [`get_aux`](Context::get_aux) and
@@ -234,6 +247,13 @@ impl Context<'_> {
phantom: PhantomData,
})
}
+
+ /// 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) };
+ }
}
/// A reference to a connection handle with a lifetime bound to something.
@@ -319,7 +339,7 @@ bitflags::bitflags! {
/// Specifies UTF-16 using native byte order as the text encoding this SQL function prefers for its parameters.
const SQLITE_UTF16 = ffi::SQLITE_UTF16;
/// Means that the function always gives the same output when the input parameters are the same.
- const SQLITE_DETERMINISTIC = ffi::SQLITE_DETERMINISTIC;
+ const SQLITE_DETERMINISTIC = ffi::SQLITE_DETERMINISTIC; // 3.8.3
/// Means that the function may only be invoked from top-level SQL.
const SQLITE_DIRECTONLY = 0x0000_0008_0000; // 3.30.0
/// Indicates to SQLite that a function may call `sqlite3_value_subtype()` to inspect the sub-types of its arguments.
@@ -617,7 +637,7 @@ unsafe extern "C" fn call_boxed_step<A, D, T>(
D: Aggregate<A, T>,
T: ToSql,
{
- let pac = if let Some(pac) = aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
+ let pac = if let Some(pac) = aggregate_context(ctx, std::mem::size_of::<*mut A>()) {
pac
} else {
ffi::sqlite3_result_error_nomem(ctx);
@@ -664,7 +684,7 @@ unsafe extern "C" fn call_boxed_inverse<A, W, T>(
W: WindowAggregate<A, T>,
T: ToSql,
{
- let pac = if let Some(pac) = aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
+ let pac = if let Some(pac) = aggregate_context(ctx, std::mem::size_of::<*mut A>()) {
pac
} else {
ffi::sqlite3_result_error_nomem(ctx);
@@ -787,7 +807,6 @@ where
#[cfg(test)]
mod test {
use regex::Regex;
- use std::f64::EPSILON;
use std::os::raw::c_double;
#[cfg(feature = "window")]
@@ -812,7 +831,7 @@ mod test {
)?;
let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
- assert!((3f64 - result?).abs() < EPSILON);
+ assert!((3f64 - result?).abs() < f64::EPSILON);
Ok(())
}
@@ -826,7 +845,7 @@ mod test {
half,
)?;
let result: Result<f64> = db.query_row("SELECT half(6)", [], |r| r.get(0));
- assert!((3f64 - result?).abs() < EPSILON);
+ 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));
diff --git a/src/hooks.rs b/src/hooks.rs
index f0ae1f3..5058a0c 100644
--- a/src/hooks.rs
+++ b/src/hooks.rs
@@ -10,7 +10,7 @@ use crate::ffi;
use crate::{Connection, InnerConnection};
/// Action Codes
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(i32)]
#[non_exhaustive]
#[allow(clippy::upper_case_acronyms)]
@@ -37,10 +37,10 @@ impl From<i32> for Action {
}
}
-/// The context recieved by an authorizer hook.
+/// The context received by an authorizer hook.
///
/// See <https://sqlite.org/c3ref/set_authorizer.html> for more info.
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AuthContext<'c> {
/// The action to be authorized.
pub action: AuthAction<'c>,
@@ -57,7 +57,7 @@ pub struct AuthContext<'c> {
/// preparation.
///
/// See <https://sqlite.org/c3ref/c_alter_table.html> for more info.
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum AuthAction<'c> {
@@ -285,7 +285,7 @@ impl<'c> AuthAction<'c> {
operation: TransactionOperation::from_str(operation_str),
savepoint_name,
},
- #[cfg(feature = "modern_sqlite")]
+ #[cfg(feature = "modern_sqlite")] // 3.8.3
(ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
(code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
}
@@ -296,7 +296,7 @@ pub(crate) type BoxedAuthorizer =
Box<dyn for<'c> FnMut(AuthContext<'c>) -> Authorization + Send + 'static>;
/// A transaction operation.
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum TransactionOperation {
@@ -318,7 +318,7 @@ impl TransactionOperation {
}
/// [`authorizer`](Connection::authorizer) return code
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Authorization {
/// Authorize the action.
diff --git a/src/inner_connection.rs b/src/inner_connection.rs
index 0ea630e..e5bc3f1 100644
--- a/src/inner_connection.rs
+++ b/src/inner_connection.rs
@@ -10,7 +10,7 @@ use std::sync::{Arc, Mutex};
use super::ffi;
use super::str_for_sqlite;
use super::{Connection, InterruptHandle, OpenFlags, Result};
-use crate::error::{error_from_handle, error_from_sqlite_code, Error};
+use crate::error::{error_from_handle, error_from_sqlite_code, error_with_offset, Error};
use crate::raw_statement::RawStatement;
use crate::statement::Statement;
use crate::version::version_number;
@@ -25,11 +25,11 @@ pub struct InnerConnection {
// interrupt would only acquire the lock after the query's completion.
interrupt_lock: Arc<Mutex<*mut ffi::sqlite3>>,
#[cfg(feature = "hooks")]
- pub free_commit_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>,
+ pub free_commit_hook: Option<unsafe fn(*mut std::os::raw::c_void)>,
#[cfg(feature = "hooks")]
- pub free_rollback_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>,
+ pub free_rollback_hook: Option<unsafe fn(*mut std::os::raw::c_void)>,
#[cfg(feature = "hooks")]
- pub free_update_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>,
+ pub free_update_hook: Option<unsafe fn(*mut std::os::raw::c_void)>,
#[cfg(feature = "hooks")]
pub progress_handler: Option<Box<dyn FnMut() -> bool + Send>>,
#[cfg(feature = "hooks")]
@@ -208,7 +208,7 @@ impl InnerConnection {
Ok(())
} else {
let message = super::errmsg_to_string(errmsg);
- ffi::sqlite3_free(errmsg.cast::<::std::os::raw::c_void>());
+ ffi::sqlite3_free(errmsg.cast::<std::os::raw::c_void>());
Err(error_from_sqlite_code(r, Some(message)))
}
}
@@ -222,6 +222,7 @@ impl InnerConnection {
let mut c_stmt = ptr::null_mut();
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
let mut c_tail = ptr::null();
+ // TODO sqlite3_prepare_v3 (https://sqlite.org/c3ref/c_prepare_normalize.html) // 3.20.0, #728
#[cfg(not(feature = "unlock_notify"))]
let r = unsafe {
ffi::sqlite3_prepare_v2(
@@ -255,7 +256,9 @@ impl InnerConnection {
rc
};
// If there is an error, *ppStmt is set to NULL.
- self.decode_result(r)?;
+ if r != ffi::SQLITE_OK {
+ return Err(unsafe { error_with_offset(self.db, r, sql) });
+ }
// If the input text contains no SQL (if the input is an empty string or a
// comment) then *ppStmt is set to NULL.
let c_stmt: *mut ffi::sqlite3_stmt = c_stmt;
@@ -276,8 +279,15 @@ impl InnerConnection {
}
#[inline]
- pub fn changes(&self) -> usize {
- unsafe { ffi::sqlite3_changes(self.db()) as usize }
+ pub fn changes(&self) -> u64 {
+ #[cfg(not(feature = "modern_sqlite"))]
+ unsafe {
+ ffi::sqlite3_changes(self.db()) as u64
+ }
+ #[cfg(feature = "modern_sqlite")] // 3.37.0
+ unsafe {
+ ffi::sqlite3_changes64(self.db()) as u64
+ }
}
#[inline]
@@ -308,6 +318,56 @@ impl InnerConnection {
#[cfg(not(feature = "hooks"))]
#[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()) };
+ match r {
+ 0 => Ok(false),
+ 1 => Ok(true),
+ -1 => Err(Error::SqliteFailure(
+ ffi::Error::new(ffi::SQLITE_MISUSE),
+ Some(format!("{:?} is not the name of a database", db_name)),
+ )),
+ _ => Err(error_from_sqlite_code(
+ r,
+ Some("Unexpected result".to_owned()),
+ )),
+ }
+ }
+
+ #[cfg(feature = "modern_sqlite")] // 3.37.0
+ pub fn txn_state(
+ &self,
+ db_name: Option<super::DatabaseName<'_>>,
+ ) -> Result<super::transaction::TransactionState> {
+ let r = if let Some(ref name) = db_name {
+ let name = name.as_cstring()?;
+ unsafe { ffi::sqlite3_txn_state(self.db, name.as_ptr()) }
+ } else {
+ unsafe { ffi::sqlite3_txn_state(self.db, ptr::null()) }
+ };
+ match r {
+ 0 => Ok(super::transaction::TransactionState::None),
+ 1 => Ok(super::transaction::TransactionState::Read),
+ 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)),
+ )),
+ _ => Err(error_from_sqlite_code(
+ r,
+ Some("Unexpected result".to_owned()),
+ )),
+ }
+ }
+
+ #[inline]
+ #[cfg(feature = "release_memory")]
+ pub fn release_memory(&self) -> Result<()> {
+ self.decode_result(unsafe { ffi::sqlite3_db_release_memory(self.db) })
+ }
}
impl Drop for InnerConnection {
@@ -340,7 +400,7 @@ fn ensure_safe_sqlite_threading_mode() -> Result<()> {
#[cfg(not(any(target_arch = "wasm32")))]
fn ensure_safe_sqlite_threading_mode() -> Result<()> {
- // Ensure SQLite was compiled in thredsafe mode.
+ // Ensure SQLite was compiled in threadsafe mode.
if unsafe { ffi::sqlite3_threadsafe() == 0 } {
return Err(Error::SqliteSingleThreadedMode);
}
diff --git a/src/lib.rs b/src/lib.rs
index 1fa7a33..89f133e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,9 @@
-//! Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to
-//! expose an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
+//! Rusqlite is an ergonomic wrapper for using SQLite from Rust.
+//!
+//! Historically, the API was based on the one from
+//! [`rust-postgres`](https://github.com/sfackler/rust-postgres). However, the
+//! two have diverged in many ways, and no compatibility between the two is
+//! intended.
//!
//! ```rust
//! use rusqlite::{params, Connection, Result};
@@ -16,11 +20,11 @@
//!
//! conn.execute(
//! "CREATE TABLE person (
-//! id INTEGER PRIMARY KEY,
-//! name TEXT NOT NULL,
-//! data BLOB
-//! )",
-//! [],
+//! id INTEGER PRIMARY KEY,
+//! name TEXT NOT NULL,
+//! data BLOB
+//! )",
+//! (), // empty list of parameters.
//! )?;
//! let me = Person {
//! id: 0,
@@ -29,7 +33,7 @@
//! };
//! conn.execute(
//! "INSERT INTO person (name, data) VALUES (?1, ?2)",
-//! params![me.name, me.data],
+//! (&me.name, &me.data),
//! )?;
//!
//! let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
@@ -53,7 +57,6 @@
pub use libsqlite3_sys as ffi;
use std::cell::RefCell;
-use std::convert;
use std::default::Default;
use std::ffi::{CStr, CString};
use std::fmt;
@@ -145,7 +148,7 @@ const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
#[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 pass heterogeneous or long lists of
+/// A macro making it more convenient to longer lists of
/// parameters as a `&[&dyn ToSql]`.
///
/// # Example
@@ -161,8 +164,7 @@ pub const NO_PARAMS: &[&dyn ToSql] = &[];
///
/// fn add_person(conn: &Connection, person: &Person) -> Result<()> {
/// conn.execute(
-/// "INSERT INTO person (name, age_in_years, data)
-/// VALUES (?1, ?2, ?3)",
+/// "INSERT INTO person(name, age_in_years, data) VALUES (?1, ?2, ?3)",
/// params![person.name, person.age_in_years, person.data],
/// )?;
/// Ok(())
@@ -269,7 +271,7 @@ fn str_for_sqlite(s: &[u8]) -> Result<(*const c_char, c_int, ffi::sqlite3_destru
// Helper to cast to c_int safely, returning the correct error type if the cast
// failed.
fn len_as_c_int(len: usize) -> Result<c_int> {
- if len >= (c_int::max_value() as usize) {
+ if len >= (c_int::MAX as usize) {
Err(Error::SqliteFailure(
ffi::Error::new(ffi::SQLITE_TOOBIG),
None,
@@ -320,7 +322,7 @@ pub const TEMP_DB: DatabaseName<'static> = DatabaseName::Temp;
))]
impl DatabaseName<'_> {
#[inline]
- fn as_cstring(&self) -> Result<util::SmallCString> {
+ fn as_cstring(&self) -> Result<SmallCString> {
use self::DatabaseName::{Attached, Main, Temp};
match *self {
Main => str_to_cstring("main"),
@@ -347,27 +349,58 @@ impl Drop for Connection {
}
impl Connection {
- /// Open a new connection to a SQLite database.
- ///
- /// `Connection::open(path)` is equivalent to
- /// `Connection::open_with_flags(path,
- /// OpenFlags::SQLITE_OPEN_READ_WRITE |
- /// OpenFlags::SQLITE_OPEN_CREATE)`.
+ /// Open a new connection to a SQLite database. If a database does not exist
+ /// at the path, one is created.
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn open_my_db() -> Result<()> {
/// let path = "./my_db.db3";
- /// let db = Connection::open(&path)?;
+ /// let db = Connection::open(path)?;
+ /// // Use the database somehow...
/// println!("{}", db.is_autocommit());
/// Ok(())
/// }
/// ```
///
+ /// # Flags
+ ///
+ /// `Connection::open(path)` is equivalent to using
+ /// [`Connection::open_with_flags`] with the default [`OpenFlags`]. That is,
+ /// it's equivalent to:
+ ///
+ /// ```ignore
+ /// Connection::open_with_flags(
+ /// path,
+ /// OpenFlags::SQLITE_OPEN_READ_WRITE
+ /// | OpenFlags::SQLITE_OPEN_CREATE
+ /// | OpenFlags::SQLITE_OPEN_URI
+ /// | OpenFlags::SQLITE_OPEN_NO_MUTEX,
+ /// )
+ /// ```
+ ///
+ /// These flags have the following effects:
+ ///
+ /// - Open the database for both reading or writing.
+ /// - Create the database if one does not exist at the path.
+ /// - Allow the filename to be interpreted as a URI (see <https://www.sqlite.org/uri.html#uri_filenames_in_sqlite>
+ /// for details).
+ /// - Disables the use of a per-connection mutex.
+ ///
+ /// Rusqlite enforces thread-safety at compile time, so additional
+ /// locking is not needed and provides no benefit. (See the
+ /// documentation on [`OpenFlags::SQLITE_OPEN_FULL_MUTEX`] for some
+ /// additional discussion about this).
+ ///
+ /// Most of these are also the default settings for the C API, although
+ /// technically the default locking behavior is controlled by the flags used
+ /// when compiling SQLite -- rather than let it vary, we choose `NO_MUTEX`
+ /// because it's a fairly clearly the best choice for users of this library.
+ ///
/// # Failure
///
- /// Will return `Err` if `path` cannot be converted to a C-compatible
- /// string or if the underlying SQLite open call fails.
+ /// Will return `Err` if `path` cannot be converted to a C-compatible string
+ /// or if the underlying SQLite open call fails.
#[inline]
pub fn open<P: AsRef<Path>>(path: P) -> Result<Connection> {
let flags = OpenFlags::default();
@@ -561,6 +594,16 @@ impl Connection {
self.path.as_deref()
}
+ /// Attempts to free as much heap memory as possible from the database
+ /// connection.
+ ///
+ /// This calls [`sqlite3_db_release_memory`](https://www.sqlite.org/c3ref/db_release_memory.html).
+ #[inline]
+ #[cfg(feature = "release_memory")]
+ pub fn release_memory(&self) -> Result<()> {
+ self.db.borrow_mut().release_memory()
+ }
+
/// Convenience method to prepare and execute a single SQL statement with
/// named parameter(s).
///
@@ -680,7 +723,7 @@ impl Connection {
where
P: Params,
F: FnOnce(&Row<'_>) -> Result<T, E>,
- E: convert::From<Error>,
+ E: From<Error>,
{
let mut stmt = self.prepare(sql)?;
stmt.check_no_tail()?;
@@ -902,8 +945,10 @@ impl Connection {
/// Return the number of rows modified, inserted or deleted by the most
/// recently completed INSERT, UPDATE or DELETE statement on the database
/// connection.
+ ///
+ /// See <https://www.sqlite.org/c3ref/changes.html>
#[inline]
- fn changes(&self) -> usize {
+ pub fn changes(&self) -> u64 {
self.db.borrow().changes()
}
@@ -928,6 +973,13 @@ impl Connection {
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)
+ }
}
impl fmt::Debug for Connection {
@@ -1000,40 +1052,81 @@ impl<'conn> Iterator for Batch<'conn, '_> {
}
bitflags::bitflags! {
- /// Flags for opening SQLite database connections.
- /// See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
+ /// Flags for opening SQLite database connections. See
+ /// [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
+ ///
+ /// 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.
#[repr(C)]
pub struct OpenFlags: ::std::os::raw::c_int {
/// The database is opened in read-only mode.
/// If the database does not already exist, an error is returned.
- const SQLITE_OPEN_READ_ONLY = ffi::SQLITE_OPEN_READONLY;
+ const SQLITE_OPEN_READ_ONLY = ffi::SQLITE_OPEN_READONLY;
/// The database is opened for reading and writing if possible,
/// or reading only if the file is write protected by the operating system.
/// In either case the database must already exist, otherwise an error is returned.
- const SQLITE_OPEN_READ_WRITE = ffi::SQLITE_OPEN_READWRITE;
+ const SQLITE_OPEN_READ_WRITE = ffi::SQLITE_OPEN_READWRITE;
/// The database is created if it does not already exist
- const SQLITE_OPEN_CREATE = ffi::SQLITE_OPEN_CREATE;
+ 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 = 0x0000_0040;
/// The database will be opened as an in-memory database.
- const SQLITE_OPEN_MEMORY = 0x0000_0080;
- /// The new database connection will use the "multi-thread" threading mode.
- const SQLITE_OPEN_NO_MUTEX = ffi::SQLITE_OPEN_NOMUTEX;
- /// The new database connection will use the "serialized" threading mode.
- const SQLITE_OPEN_FULL_MUTEX = ffi::SQLITE_OPEN_FULLMUTEX;
- /// The database is opened shared cache enabled.
- const SQLITE_OPEN_SHARED_CACHE = 0x0002_0000;
+ const SQLITE_OPEN_MEMORY = 0x0000_0080;
+ /// The new database connection will not use a per-connection mutex (the
+ /// connection will use the "multi-thread" threading mode, in SQLite
+ /// parlance).
+ ///
+ /// This is used by default, as proper `Send`/`Sync` usage (in
+ /// particular, the fact that [`Connection`] does not implement `Sync`)
+ /// ensures thread-safety without the need to perform locking around all
+ /// calls.
+ const SQLITE_OPEN_NO_MUTEX = ffi::SQLITE_OPEN_NOMUTEX;
+ /// The new database connection will use a per-connection mutex -- the
+ /// "serialized" threading mode, in SQLite parlance.
+ ///
+ /// # Caveats
+ ///
+ /// This flag should probably never be used with `rusqlite`, as we
+ /// ensure thread-safety statically (we implement [`Send`] and not
+ /// [`Sync`]). That said
+ ///
+ /// Critically, even if this flag is used, the [`Connection`] is not
+ /// safe to use across multiple threads simultaneously. To access a
+ /// database from multiple threads, you should either create multiple
+ /// connections, one for each thread (if you have very many threads,
+ /// wrapping the `rusqlite::Connection` in a mutex is also reasonable).
+ ///
+ /// This is both because of the additional per-connection state stored
+ /// by `rusqlite` (for example, the prepared statement cache), and
+ /// because not all of SQLites functions are fully thread safe, even in
+ /// serialized/`SQLITE_OPEN_FULLMUTEX` mode.
+ ///
+ /// All that said, it's fairly harmless to enable this flag with
+ /// `rusqlite`, it will just slow things down while providing no
+ /// benefit.
+ const SQLITE_OPEN_FULL_MUTEX = ffi::SQLITE_OPEN_FULLMUTEX;
+ /// The database is opened with shared cache enabled.
+ ///
+ /// This is frequently useful for in-memory connections, but note that
+ /// broadly speaking it's discouraged by SQLite itself, which states
+ /// "Any use of shared cache is discouraged" in the official
+ /// [documentation](https://www.sqlite.org/c3ref/enable_shared_cache.html).
+ const SQLITE_OPEN_SHARED_CACHE = 0x0002_0000;
/// The database is opened shared cache disabled.
const SQLITE_OPEN_PRIVATE_CACHE = 0x0004_0000;
- /// The database filename is not allowed to be a symbolic link.
+ /// The database filename is not allowed to be a symbolic link. (3.31.0)
const SQLITE_OPEN_NOFOLLOW = 0x0100_0000;
- /// Extended result codes.
+ /// Extended result codes. (3.37.0)
const SQLITE_OPEN_EXRESCODE = 0x0200_0000;
}
}
impl Default for OpenFlags {
+ #[inline]
fn default() -> OpenFlags {
+ // Note: update the `Connection::open` and top-level `OpenFlags` docs if
+ // you change these.
OpenFlags::SQLITE_OPEN_READ_WRITE
| OpenFlags::SQLITE_OPEN_CREATE
| OpenFlags::SQLITE_OPEN_NO_MUTEX
@@ -1204,7 +1297,7 @@ mod test {
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.err().unwrap();
+ let err = result.unwrap_err();
if let Error::SqliteFailure(e, Some(msg)) = err {
assert_eq!(ErrorCode::CannotOpen, e.code);
assert_eq!(ffi::SQLITE_CANTOPEN, e.extended_code);
@@ -1340,8 +1433,9 @@ mod test {
fn test_execute_select() {
let db = checked_memory_handle();
let err = db.execute("SELECT 1 WHERE 1 < ?", [1i32]).unwrap_err();
- assert!(
- err == Error::ExecuteReturnedResults,
+ assert_eq!(
+ err,
+ Error::ExecuteReturnedResults,
"Unexpected error: {}",
err
);
@@ -1509,15 +1603,28 @@ mod test {
#[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))?
);
- assert_eq!(
- "off",
- db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?
- );
+ let mode = db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?;
+ if cfg!(features = "bundled") {
+ assert_eq!(mode, "off");
+ } else {
+ // Note: system SQLite on macOS defaults to "off" rather than
+ // "memory" for the journal mode (which cannot be changed for
+ // in-memory connections). This seems like it's *probably* legal
+ // according to the docs below, so we relax this test when not
+ // bundling:
+ //
+ // From https://www.sqlite.org/pragma.html#pragma_journal_mode
+ // > Note that the journal_mode for an in-memory database is either
+ // > MEMORY or OFF and can not be changed to a different value. An
+ // > attempt to change the journal_mode of an in-memory database to
+ // > any setting other than MEMORY or OFF is ignored.
+ assert!(mode == "memory" || mode == "off", "Got mode {:?}", mode);
+ }
+
Ok(())
}
@@ -1632,7 +1739,7 @@ mod test {
db.create_scalar_function(
"interrupt",
0,
- crate::functions::FunctionFlags::default(),
+ functions::FunctionFlags::default(),
move |_| {
interrupt_handle.interrupt();
Ok(0)
@@ -1644,14 +1751,10 @@ mod test {
let result: Result<Vec<i32>> = stmt.query([])?.map(|r| r.get(0)).collect();
- match result.unwrap_err() {
- Error::SqliteFailure(err, _) => {
- assert_eq!(err.code, ErrorCode::OperationInterrupted);
- }
- err => {
- panic!("Unexpected error {}", err);
- }
- }
+ assert_eq!(
+ result.unwrap_err().sqlite_error_code(),
+ Some(ErrorCode::OperationInterrupted)
+ );
Ok(())
}
@@ -1995,7 +2098,7 @@ mod test {
}
#[test]
- #[cfg(all(feature = "bundled", not(feature = "bundled-sqlcipher")))] // SQLite >= 3.35.0
+ #[cfg(feature = "modern_sqlite")]
fn test_returning() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
@@ -2013,4 +2116,12 @@ mod test {
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)?);
+ Ok(())
+ }
}
diff --git a/src/params.rs b/src/params.rs
index 54aa571..6ab6b5f 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -31,12 +31,19 @@ use sealed::Sealed;
/// parameters is known at compile time, this can be done in one of the
/// following ways:
///
+/// - For small lists of parameters up to 16 items, they may alternatively be
+/// passed as a tuple, as in `thing.query((1, "foo"))`.
+///
+/// This is somewhat inconvenient for a single item, since you need a
+/// weird-looking trailing comma: `thing.query(("example",))`. That case is
+/// perhaps more cleanly expressed as `thing.query(["example"])`.
+///
/// - Using the [`rusqlite::params!`](crate::params!) macro, e.g.
/// `thing.query(rusqlite::params![1, "foo", bar])`. This is mostly useful for
-/// heterogeneous lists of parameters, or lists where the number of parameters
-/// exceeds 32.
+/// heterogeneous lists where the number of parameters greater than 16, or
+/// homogenous lists of parameters where the number of parameters exceeds 32.
///
-/// - For small heterogeneous lists of parameters, they can either be passed as:
+/// - For small homogeneous lists of parameters, they can either be passed as:
///
/// - an array, as in `thing.query([1i32, 2, 3, 4])` or `thing.query(["foo",
/// "bar", "baz"])`.
@@ -65,6 +72,9 @@ use sealed::Sealed;
/// fn update_rows(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?, ?)")?;
///
+/// // Using a tuple:
+/// stmt.execute((0, "foobar"))?;
+///
/// // Using `rusqlite::params!`:
/// stmt.execute(params![1i32, "blah"])?;
///
@@ -127,12 +137,26 @@ use sealed::Sealed;
///
/// ## No parameters
///
-/// You can just use an empty array literal for no params. The
-/// `rusqlite::NO_PARAMS` constant which was so common in previous versions of
-/// this library is no longer needed (and is now deprecated).
+/// 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).
///
/// ### Example (no parameters)
///
+/// The empty tuple:
+///
+/// ```rust,no_run
+/// # use rusqlite::{Connection, Result, params};
+/// fn delete_all_users(conn: &Connection) -> Result<()> {
+/// // You may also use `()`.
+/// conn.execute("DELETE FROM users", ())?;
+/// Ok(())
+/// }
+/// ```
+///
+/// The empty array:
+///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result, params};
/// fn delete_all_users(conn: &Connection) -> Result<()> {
@@ -147,10 +171,11 @@ use sealed::Sealed;
/// If you have a number of parameters which is unknown at compile time (for
/// example, building a dynamic query at runtime), you have two choices:
///
-/// - Use a `&[&dyn ToSql]`, which is nice if you have one otherwise might be
-/// annoying.
+/// - Use a `&[&dyn ToSql]`. This is often annoying to construct if you don't
+/// already have this type on-hand.
/// - Use the [`ParamsFromIter`] type. This essentially lets you wrap an
-/// iterator some `T: ToSql` with something that implements `Params`.
+/// iterator some `T: ToSql` with something that implements `Params`. The
+/// usage of this looks like `rusqlite::params_from_iter(something)`.
///
/// A lot of the considerations here are similar either way, so you should see
/// the [`ParamsFromIter`] documentation for more info / examples.
@@ -169,14 +194,23 @@ pub trait Params: Sealed {
// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
// unambiguous, this must be the *only* implementation for an empty array. This
// avoids `NO_PARAMS` being a necessary part of the API.
+//
+// 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
+// homogenous lists of parameters. This is not that big of a deal, but is
+// unfortunate, especially because I mostly did it because I wanted a simple
+// syntax for no-params that didnt require importing -- the empty tuple fits
+// that nicely, but I didn't think of it until much later.
+//
+// Admittedly, if we did have the generic impl, then we *wouldn't* support the
+// empty array literal as a parameter, since the `T` there would fail to be
+// inferred. The error message here would probably be quite bad, and so on
+// further thought, probably would end up causing *more* surprises, not less.
impl Sealed for [&(dyn ToSql + Send + Sync); 0] {}
impl Params for [&(dyn ToSql + Send + Sync); 0] {
#[inline]
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
- // Note: Can't just return `Ok(())` — `Statement::bind_parameters`
- // checks that the right number of params were passed too.
- // TODO: we should have tests for `Error::InvalidParameterCount`...
- stmt.bind_parameters(&[] as &[&dyn ToSql])
+ stmt.ensure_parameter_count(0)
}
}
@@ -196,6 +230,69 @@ impl Params for &[(&str, &dyn ToSql)] {
}
}
+// Manual impls for the empty and singleton tuple, although the rest are covered
+// by macros.
+impl Sealed for () {}
+impl Params for () {
+ #[inline]
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
+ stmt.ensure_parameter_count(0)
+ }
+}
+
+// I'm pretty sure you could tweak the `single_tuple_impl` to accept this.
+impl<T: ToSql> Sealed for (T,) {}
+impl<T: ToSql> Params for (T,) {
+ #[inline]
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
+ stmt.ensure_parameter_count(1)?;
+ stmt.raw_bind_parameter(1, self.0)?;
+ Ok(())
+ }
+}
+
+macro_rules! single_tuple_impl {
+ ($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => {
+ impl<$($ftype,)*> Sealed for ($($ftype,)*) where $($ftype: ToSql,)* {}
+ impl<$($ftype,)*> Params for ($($ftype,)*) where $($ftype: ToSql,)* {
+ fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
+ stmt.ensure_parameter_count($count)?;
+ $({
+ debug_assert!($field < $count);
+ stmt.raw_bind_parameter($field + 1, self.$field)?;
+ })+
+ Ok(())
+ }
+ }
+ }
+}
+
+// We use a the macro for the rest, but don't bother with trying to implement it
+// in a single invocation (it's possible to do, but my attempts were almost the
+// same amount of code as just writing it out this way, and much more dense --
+// it is a more complicated case than the TryFrom macro we have for row->tuple).
+//
+// Note that going up to 16 (rather than the 12 that the impls in the stdlib
+// usually support) is just because we did the same in the `TryFrom<Row>` impl.
+// I didn't catch that then, but there's no reason to remove it, and it seems
+// nice to be consistent here; this way putting data in the database and getting
+// data out of the database are more symmetric in a (mostly superficial) sense.
+single_tuple_impl!(2: (0 A), (1 B));
+single_tuple_impl!(3: (0 A), (1 B), (2 C));
+single_tuple_impl!(4: (0 A), (1 B), (2 C), (3 D));
+single_tuple_impl!(5: (0 A), (1 B), (2 C), (3 D), (4 E));
+single_tuple_impl!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F));
+single_tuple_impl!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G));
+single_tuple_impl!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H));
+single_tuple_impl!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I));
+single_tuple_impl!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J));
+single_tuple_impl!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K));
+single_tuple_impl!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L));
+single_tuple_impl!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M));
+single_tuple_impl!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N));
+single_tuple_impl!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O));
+single_tuple_impl!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P));
+
macro_rules! impl_for_array_ref {
($($N:literal)+) => {$(
// These are already generic, and there's a shedload of them, so lets
@@ -225,9 +322,12 @@ macro_rules! impl_for_array_ref {
// Following libstd/libcore's (old) lead, implement this for arrays up to `[_;
// 32]`. Note `[_; 0]` is intentionally omitted for coherence reasons, see the
// note above the impl of `[&dyn ToSql; 0]` for more information.
+//
+// Note that this unfortunately means we can't use const generics here, but I
+// don't really think it matters -- users who hit that can use `params!` anyway.
impl_for_array_ref!(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
- 18 19 20 21 22 23 24 25 26 27 29 30 31 32
+ 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
);
/// Adapter type which allows any iterator over [`ToSql`] values to implement
diff --git a/src/pragma.rs b/src/pragma.rs
index 1c81c95..673478a 100644
--- a/src/pragma.rs
+++ b/src/pragma.rs
@@ -406,18 +406,20 @@ mod test {
let db = Connection::open_in_memory()?;
let journal_mode: String =
db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get(0))?;
- assert_eq!("off", &journal_mode);
- // Sanity checks to ensure the move to a generic `ToSql` wasn't breaking
- assert_eq!(
- "off",
- db.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row
- .get::<_, String>(0))?,
+ assert!(
+ journal_mode == "off" || journal_mode == "memory",
+ "mode: {:?}",
+ 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))?;
+ assert!(mode == "off" || mode == "memory", "mode: {:?}", mode);
+
let param: &dyn crate::ToSql = &"OFF";
- assert_eq!(
- "off",
- db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?,
- );
+ let mode =
+ db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?;
+ assert!(mode == "off" || mode == "memory", "mode: {:?}", mode);
Ok(())
}
diff --git a/src/raw_statement.rs b/src/raw_statement.rs
index 8e624dc..f057761 100644
--- a/src/raw_statement.rs
+++ b/src/raw_statement.rs
@@ -110,7 +110,7 @@ impl RawStatement {
#[cfg(feature = "unlock_notify")]
pub fn step(&self) -> c_int {
use crate::unlock_notify;
- let mut db = core::ptr::null_mut::<ffi::sqlite3>();
+ let mut db = ptr::null_mut::<ffi::sqlite3>();
loop {
unsafe {
let mut rc = ffi::sqlite3_step(self.ptr);
@@ -224,6 +224,14 @@ impl RawStatement {
pub fn tail(&self) -> usize {
self.tail
}
+
+ #[inline]
+ #[cfg(feature = "modern_sqlite")] // 3.28.0
+ pub fn is_explain(&self) -> i32 {
+ unsafe { ffi::sqlite3_stmt_isexplain(self.ptr) }
+ }
+
+ // TODO sqlite3_normalized_sql (https://sqlite.org/c3ref/expanded_sql.html) // 3.27.0 + SQLITE_ENABLE_NORMALIZE
}
impl Drop for RawStatement {
diff --git a/src/row.rs b/src/row.rs
index c766e50..221905a 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -171,7 +171,7 @@ pub struct AndThenRows<'stmt, F> {
impl<T, E, F> Iterator for AndThenRows<'_, F>
where
- E: convert::From<Error>,
+ E: From<Error>,
F: FnMut(&Row<'_>) -> Result<T, E>,
{
type Item = Result<T, E>;
diff --git a/src/session.rs b/src/session.rs
index b02d306..f8aa764 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -168,7 +168,7 @@ impl Session<'_> {
if r != ffi::SQLITE_OK {
let errmsg: *mut c_char = errmsg;
let message = errmsg_to_string(&*errmsg);
- ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
+ ffi::sqlite3_free(errmsg as *mut c_void);
return Err(error_from_sqlite_code(r, Some(message)));
}
}
diff --git a/src/statement.rs b/src/statement.rs
index 60abd90..ee5e220 100644
--- a/src/statement.rs
+++ b/src/statement.rs
@@ -3,7 +3,7 @@ use std::os::raw::{c_int, c_void};
#[cfg(feature = "array")]
use std::rc::Rc;
use std::slice::from_raw_parts;
-use std::{convert, fmt, mem, ptr, str};
+use std::{fmt, mem, ptr, str};
use super::ffi;
use super::{len_as_c_int, str_for_sqlite};
@@ -33,17 +33,45 @@ impl Statement<'_> {
/// ```rust,no_run
/// # use rusqlite::{Connection, Result, params};
/// fn update_rows(conn: &Connection) -> Result<()> {
- /// let mut stmt = conn.prepare("UPDATE foo SET bar = 'baz' WHERE qux = ?")?;
+ /// let mut stmt = conn.prepare("UPDATE foo SET bar = ? WHERE qux = ?")?;
+ /// // For a single parameter, or a parameter where all the values have
+ /// // the same type, just passing an array is simplest.
+ /// stmt.execute([2i32])?;
/// // The `rusqlite::params!` macro is mostly useful when the parameters do not
/// // all have the same type, or if there are more than 32 parameters
- /// // at once.
+ /// // at once, but it can be used in other cases.
/// stmt.execute(params![1i32])?;
/// // However, it's not required, many cases are fine as:
/// stmt.execute(&[&2i32])?;
/// // Or even:
/// stmt.execute([2i32])?;
+ /// // If you really want to, this is an option as well.
+ /// stmt.execute((2i32,))?;
+ /// Ok(())
+ /// }
+ /// ```
+ ///
+ /// #### Heterogeneous positional parameters
+ ///
+ /// ```
+ /// use rusqlite::{Connection, Result};
+ /// 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 mut stmt = conn.prepare_cached(query)?;
+ /// let hash: [u8; 32] = sha256(data);
+ /// // The easiest way to pass positional parameters of have several
+ /// // different types is by using a tuple.
+ /// stmt.execute((path, hash, data))?;
+ /// // Using the `params!` macro also works, and supports longer parameter lists:
+ /// stmt.execute(rusqlite::params![path, hash, data])?;
/// Ok(())
/// }
+ /// # let c = Connection::open_in_memory().unwrap();
+ /// # c.execute_batch("CREATE TABLE files(path TEXT PRIMARY KEY, hash BLOB, data BLOB)").unwrap();
+ /// # store_file(&c, "foo/bar.txt", b"bibble").unwrap();
+ /// # store_file(&c, "foo/baz.txt", b"bobble").unwrap();
/// ```
///
/// ### Use with named parameters
@@ -104,6 +132,7 @@ impl Statement<'_> {
/// 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> {
@@ -239,6 +268,7 @@ impl Statement<'_> {
/// # 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)
@@ -316,6 +346,7 @@ impl Statement<'_> {
/// ## 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,
@@ -386,7 +417,7 @@ impl Statement<'_> {
pub fn query_and_then<T, E, P, F>(&mut self, params: P, f: F) -> Result<AndThenRows<'_, F>>
where
P: Params,
- E: convert::From<Error>,
+ E: From<Error>,
F: FnMut(&Row<'_>) -> Result<T, E>,
{
self.query(params).map(|rows| rows.and_then(f))
@@ -408,6 +439,7 @@ impl Statement<'_> {
/// ## 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,
@@ -415,7 +447,7 @@ impl Statement<'_> {
f: F,
) -> Result<AndThenRows<'_, F>>
where
- E: convert::From<Error>,
+ E: From<Error>,
F: FnMut(&Row<'_>) -> Result<T, E>,
{
self.query_and_then(params, f)
@@ -475,6 +507,7 @@ impl Statement<'_> {
///
/// 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
@@ -567,6 +600,16 @@ impl Statement<'_> {
}
#[inline]
+ pub(crate) fn ensure_parameter_count(&self, n: usize) -> Result<()> {
+ let count = self.parameter_count();
+ if count != n {
+ Err(Error::InvalidParameterCount(n, count))
+ } else {
+ Ok(())
+ }
+ }
+
+ #[inline]
pub(crate) fn bind_parameters_named<T: ?Sized + ToSql>(
&mut self,
params: &[(&str, &T)],
@@ -606,9 +649,14 @@ impl Statement<'_> {
/// - binding named and positional parameters in the same query.
/// - separating parameter binding from query execution.
///
- /// Statements that have had their parameters bound this way should be
- /// queried or executed by [`Statement::raw_query`] or
- /// [`Statement::raw_execute`]. Other functions are not guaranteed to work.
+ /// In general, statements that have had *any* parameters bound this way
+ /// should have *all* parameters bound this way, and be queried or executed
+ /// by [`Statement::raw_query`] or [`Statement::raw_execute`], other usage
+ /// is unsupported and will likely, probably in surprising ways.
+ ///
+ /// That is: Do not mix the "raw" statement functions with the rest of the
+ /// API, or the results may be surprising, and may even change in future
+ /// versions without comment.
///
/// # Example
///
@@ -684,6 +732,7 @@ impl Statement<'_> {
#[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => {
+ // TODO sqlite3_bind_zeroblob64 // 3.8.11
return self
.conn
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, len) });
@@ -707,6 +756,7 @@ impl Statement<'_> {
ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
ValueRef::Text(s) => unsafe {
let (c_str, len, destructor) = str_for_sqlite(s)?;
+ // TODO sqlite3_bind_text64 // 3.8.7
ffi::sqlite3_bind_text(ptr, col as c_int, c_str, len, destructor)
},
ValueRef::Blob(b) => unsafe {
@@ -714,6 +764,7 @@ impl Statement<'_> {
if length == 0 {
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
} else {
+ // TODO sqlite3_bind_blob64 // 3.8.7
ffi::sqlite3_bind_blob(
ptr,
col as c_int,
@@ -732,7 +783,7 @@ impl Statement<'_> {
let r = self.stmt.step();
self.stmt.reset();
match r {
- ffi::SQLITE_DONE => Ok(self.conn.changes()),
+ ffi::SQLITE_DONE => Ok(self.conn.changes() as usize),
ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults),
_ => Err(self.conn.decode_result(r).unwrap_err()),
}
@@ -795,6 +846,16 @@ impl Statement<'_> {
self.stmt.get_status(status, true)
}
+ /// Returns 1 if the prepared statement is an EXPLAIN statement,
+ /// or 2 if the statement is an EXPLAIN QUERY PLAN,
+ /// or 0 if it is an ordinary statement or a NULL pointer.
+ #[inline]
+ #[cfg(feature = "modern_sqlite")] // 3.28.0
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+ pub fn is_explain(&self) -> i32 {
+ self.stmt.is_explain()
+ }
+
#[cfg(feature = "extra_check")]
#[inline]
pub(crate) fn check_no_tail(&self) -> Result<()> {
@@ -942,15 +1003,15 @@ pub enum StatementStatus {
AutoIndex = 3,
/// Equivalent to SQLITE_STMTSTATUS_VM_STEP
VmStep = 4,
- /// Equivalent to SQLITE_STMTSTATUS_REPREPARE
+ /// Equivalent to SQLITE_STMTSTATUS_REPREPARE (3.20.0)
RePrepare = 5,
- /// Equivalent to SQLITE_STMTSTATUS_RUN
+ /// Equivalent to SQLITE_STMTSTATUS_RUN (3.20.0)
Run = 6,
/// Equivalent to SQLITE_STMTSTATUS_FILTER_MISS
FilterMiss = 7,
/// Equivalent to SQLITE_STMTSTATUS_FILTER_HIT
FilterHit = 8,
- /// Equivalent to SQLITE_STMTSTATUS_MEMUSED
+ /// Equivalent to SQLITE_STMTSTATUS_MEMUSED (3.20.0)
MemUsed = 99,
}
@@ -1266,6 +1327,41 @@ mod test {
assert!(!stmt.exists([0i32])?);
Ok(())
}
+ #[test]
+ fn test_tuple_params() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ let s = db.query_row("SELECT printf('[%s]', ?)", ("abc",), |r| {
+ r.get::<_, String>(0)
+ })?;
+ assert_eq!(s, "[abc]");
+ let s = db.query_row(
+ "SELECT printf('%d %s %d', ?, ?, ?)",
+ (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', ?, ?, ?, ?)",
+ (1, "abc", 2i32, 4i64),
+ |r| r.get::<_, String>(0),
+ )?;
+ assert_eq!(s, "1 abc 2 4");
+ #[rustfmt::skip]
+ let bigtup = (
+ 0, "a", 1, "b", 2, "c", 3, "d",
+ 4, "e", 5, "f", 6, "g", 7, "h",
+ );
+ let query = "SELECT printf(
+ '%d %s | %d %s | %d %s | %d %s || %d %s | %d %s | %d %s | %d %s',
+ ?, ?, ?, ?,
+ ?, ?, ?, ?,
+ ?, ?, ?, ?,
+ ?, ?, ?, ?
+ )";
+ 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");
+ Ok(())
+ }
#[test]
fn test_query_row() -> Result<()> {
@@ -1430,4 +1526,30 @@ mod test {
assert_eq!(expected, actual);
Ok(())
}
+
+ #[test]
+ #[cfg(feature = "modern_sqlite")]
+ fn is_explain() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ let stmt = db.prepare("SELECT 1;")?;
+ assert_eq!(0, stmt.is_explain());
+ Ok(())
+ }
+
+ #[test]
+ #[cfg(all(feature = "modern_sqlite", not(feature = "bundled-sqlcipher")))] // 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);
+ assert_eq!(offset, 7);
+ }
+ err => panic!("Unexpected error {}", err),
+ }
+ Ok(())
+ }
}
diff --git a/src/trace.rs b/src/trace.rs
index 3932976..7fc9090 100644
--- a/src/trace.rs
+++ b/src/trace.rs
@@ -119,6 +119,8 @@ impl Connection {
None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) },
};
}
+
+ // TODO sqlite3_trace_v2 (https://sqlite.org/c3ref/trace_v2.html) // 3.14.0, #977
}
#[cfg(test)]
diff --git a/src/transaction.rs b/src/transaction.rs
index 296b2aa..2c4c6c0 100644
--- a/src/transaction.rs
+++ b/src/transaction.rs
@@ -87,6 +87,7 @@ pub struct Transaction<'conn> {
/// sp.commit()
/// }
/// ```
+#[derive(Debug)]
pub struct Savepoint<'conn> {
conn: &'conn Connection,
name: String,
@@ -375,6 +376,20 @@ impl Drop for Savepoint<'_> {
}
}
+/// Transaction state of a database
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[non_exhaustive]
+#[cfg(feature = "modern_sqlite")] // 3.37.0
+#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+pub enum TransactionState {
+ /// Equivalent to SQLITE_TXN_NONE
+ None,
+ /// Equivalent to SQLITE_TXN_READ
+ Read,
+ /// Equivalent to SQLITE_TXN_WRITE
+ Write,
+}
+
impl Connection {
/// Begin a new transaction with the default behavior (DEFERRED).
///
@@ -499,6 +514,16 @@ impl Connection {
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_name(self, name)
}
+
+ /// Determine the transaction state of a database
+ #[cfg(feature = "modern_sqlite")] // 3.37.0
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+ pub fn transaction_state(
+ &self,
+ db_name: Option<crate::DatabaseName<'_>>,
+ ) -> Result<TransactionState> {
+ self.db.borrow().txn_state(db_name)
+ }
}
#[cfg(test)]
@@ -534,7 +559,7 @@ mod test {
}
Ok(())
}
- fn assert_nested_tx_error(e: crate::Error) {
+ fn assert_nested_tx_error(e: Error) {
if let Error::SqliteFailure(e, Some(m)) = &e {
assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR);
// FIXME: Not ideal...
@@ -710,4 +735,25 @@ mod test {
assert_eq!(x, i);
Ok(())
}
+
+ #[test]
+ #[cfg(feature = "modern_sqlite")]
+ fn txn_state() -> Result<()> {
+ use super::TransactionState;
+ use crate::DatabaseName;
+ let db = Connection::open_in_memory()?;
+ assert_eq!(
+ TransactionState::None,
+ db.transaction_state(Some(DatabaseName::Main))?
+ );
+ assert_eq!(TransactionState::None, db.transaction_state(None)?);
+ db.execute_batch("BEGIN")?;
+ assert_eq!(TransactionState::None, db.transaction_state(None)?);
+ let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
+ assert_eq!(TransactionState::Read, db.transaction_state(None)?);
+ db.pragma_update(None, "user_version", 1)?;
+ assert_eq!(TransactionState::Write, db.transaction_state(None)?);
+ db.execute_batch("ROLLBACK")?;
+ Ok(())
+ }
}
diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs
index 88bdd14..b95a378 100644
--- a/src/types/from_sql.rs
+++ b/src/types/from_sql.rs
@@ -240,7 +240,7 @@ mod test {
fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64])
where
- T: Into<i64> + FromSql + ::std::fmt::Debug,
+ T: Into<i64> + FromSql + std::fmt::Debug,
{
for n in out_of_range {
let err = db
diff --git a/src/types/mod.rs b/src/types/mod.rs
index 4e524b2..4000ae2 100644
--- a/src/types/mod.rs
+++ b/src/types/mod.rs
@@ -110,7 +110,7 @@ pub struct Null;
/// SQLite data types.
/// See [Fundamental Datatypes](https://sqlite.org/c3ref/c_blob.html).
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Type {
/// NULL
Null,
@@ -140,7 +140,6 @@ impl fmt::Display for Type {
mod test {
use super::Value;
use crate::{params, Connection, Error, Result, Statement};
- use std::f64::EPSILON;
use std::os::raw::{c_double, c_int};
fn checked_memory_handle() -> Result<Connection> {
@@ -264,7 +263,7 @@ mod test {
assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0)?);
assert_eq!("text", row.get::<_, String>(1)?);
assert_eq!(1, row.get::<_, c_int>(2)?);
- assert!((1.5 - row.get::<_, c_double>(3)?).abs() < EPSILON);
+ assert!((1.5 - row.get::<_, c_double>(3)?).abs() < f64::EPSILON);
assert_eq!(row.get::<_, Option<c_int>>(4)?, None);
assert_eq!(row.get::<_, Option<c_double>>(4)?, None);
assert_eq!(row.get::<_, Option<String>>(4)?, None);
@@ -272,85 +271,67 @@ mod test {
// check some invalid types
// 0 is actually a blob (Vec<u8>)
- assert!(is_invalid_column_type(
- row.get::<_, c_int>(0).err().unwrap()
- ));
- assert!(is_invalid_column_type(
- row.get::<_, c_int>(0).err().unwrap()
- ));
+ assert!(is_invalid_column_type(row.get::<_, c_int>(0).unwrap_err()));
+ assert!(is_invalid_column_type(row.get::<_, c_int>(0).unwrap_err()));
assert!(is_invalid_column_type(row.get::<_, i64>(0).err().unwrap()));
assert!(is_invalid_column_type(
- row.get::<_, c_double>(0).err().unwrap()
- ));
- assert!(is_invalid_column_type(
- row.get::<_, String>(0).err().unwrap()
+ row.get::<_, c_double>(0).unwrap_err()
));
+ assert!(is_invalid_column_type(row.get::<_, String>(0).unwrap_err()));
#[cfg(feature = "time")]
assert!(is_invalid_column_type(
- row.get::<_, time::OffsetDateTime>(0).err().unwrap()
+ row.get::<_, time::OffsetDateTime>(0).unwrap_err()
));
assert!(is_invalid_column_type(
- row.get::<_, Option<c_int>>(0).err().unwrap()
+ row.get::<_, Option<c_int>>(0).unwrap_err()
));
// 1 is actually a text (String)
- assert!(is_invalid_column_type(
- row.get::<_, c_int>(1).err().unwrap()
- ));
+ assert!(is_invalid_column_type(row.get::<_, c_int>(1).unwrap_err()));
assert!(is_invalid_column_type(row.get::<_, i64>(1).err().unwrap()));
assert!(is_invalid_column_type(
- row.get::<_, c_double>(1).err().unwrap()
+ row.get::<_, c_double>(1).unwrap_err()
));
assert!(is_invalid_column_type(
- row.get::<_, Vec<u8>>(1).err().unwrap()
+ row.get::<_, Vec<u8>>(1).unwrap_err()
));
assert!(is_invalid_column_type(
- row.get::<_, Option<c_int>>(1).err().unwrap()
+ row.get::<_, Option<c_int>>(1).unwrap_err()
));
// 2 is actually an integer
+ assert!(is_invalid_column_type(row.get::<_, String>(2).unwrap_err()));
assert!(is_invalid_column_type(
- row.get::<_, String>(2).err().unwrap()
- ));
- assert!(is_invalid_column_type(
- row.get::<_, Vec<u8>>(2).err().unwrap()
+ row.get::<_, Vec<u8>>(2).unwrap_err()
));
assert!(is_invalid_column_type(
- row.get::<_, Option<String>>(2).err().unwrap()
+ row.get::<_, Option<String>>(2).unwrap_err()
));
// 3 is actually a float (c_double)
- assert!(is_invalid_column_type(
- row.get::<_, c_int>(3).err().unwrap()
- ));
+ assert!(is_invalid_column_type(row.get::<_, c_int>(3).unwrap_err()));
assert!(is_invalid_column_type(row.get::<_, i64>(3).err().unwrap()));
+ assert!(is_invalid_column_type(row.get::<_, String>(3).unwrap_err()));
assert!(is_invalid_column_type(
- row.get::<_, String>(3).err().unwrap()
- ));
- assert!(is_invalid_column_type(
- row.get::<_, Vec<u8>>(3).err().unwrap()
+ row.get::<_, Vec<u8>>(3).unwrap_err()
));
assert!(is_invalid_column_type(
- row.get::<_, Option<c_int>>(3).err().unwrap()
+ row.get::<_, Option<c_int>>(3).unwrap_err()
));
// 4 is actually NULL
- assert!(is_invalid_column_type(
- row.get::<_, c_int>(4).err().unwrap()
- ));
+ assert!(is_invalid_column_type(row.get::<_, c_int>(4).unwrap_err()));
assert!(is_invalid_column_type(row.get::<_, i64>(4).err().unwrap()));
assert!(is_invalid_column_type(
- row.get::<_, c_double>(4).err().unwrap()
- ));
- assert!(is_invalid_column_type(
- row.get::<_, String>(4).err().unwrap()
+ row.get::<_, c_double>(4).unwrap_err()
));
+ assert!(is_invalid_column_type(row.get::<_, String>(4).unwrap_err()));
assert!(is_invalid_column_type(
- row.get::<_, Vec<u8>>(4).err().unwrap()
+ row.get::<_, Vec<u8>>(4).unwrap_err()
));
#[cfg(feature = "time")]
assert!(is_invalid_column_type(
- row.get::<_, time::OffsetDateTime>(4).err().unwrap()
+ row.get::<_, time::OffsetDateTime>(4).unwrap_err()
));
Ok(())
}
@@ -373,7 +354,7 @@ mod test {
assert_eq!(Value::Text(String::from("text")), row.get::<_, Value>(1)?);
assert_eq!(Value::Integer(1), row.get::<_, Value>(2)?);
match row.get::<_, Value>(3)? {
- Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
+ Value::Real(val) => assert!((1.5 - val).abs() < f64::EPSILON),
x => panic!("Invalid Value {:?}", x),
}
assert_eq!(Value::Null, row.get::<_, Value>(4)?);
diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs
index 2445339..4e0d882 100644
--- a/src/types/to_sql.rs
+++ b/src/types/to_sql.rs
@@ -363,7 +363,6 @@ mod test {
#[test]
fn test_i128() -> crate::Result<()> {
use crate::Connection;
- use std::i128;
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")?;
db.execute(
diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs
index c0d81ca..12806f8 100644
--- a/src/types/value_ref.rs
+++ b/src/types/value_ref.rs
@@ -257,4 +257,7 @@ impl<'a> ValueRef<'a> {
_ => unreachable!("sqlite3_value_type returned invalid value"),
}
}
+
+ // TODO sqlite3_value_nochange // 3.22.0 & VTab xUpdate
+ // TODO sqlite3_value_frombind // 3.28.0
}
diff --git a/src/util/small_cstr.rs b/src/util/small_cstr.rs
index 4543c62..78e43bd 100644
--- a/src/util/small_cstr.rs
+++ b/src/util/small_cstr.rs
@@ -5,7 +5,7 @@ use std::ffi::{CStr, CString, NulError};
/// small enough. Also guarantees it's input is UTF-8 -- used for cases where we
/// need to pass a NUL-terminated string to SQLite, and we have a `&str`.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
-pub(crate) struct SmallCString(smallvec::SmallVec<[u8; 16]>);
+pub(crate) struct SmallCString(SmallVec<[u8; 16]>);
impl SmallCString {
#[inline]
diff --git a/src/vtab/array.rs b/src/vtab/array.rs
index adfd9c9..f09ac1a 100644
--- a/src/vtab/array.rs
+++ b/src/vtab/array.rs
@@ -117,7 +117,7 @@ unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
Ok(())
}
- fn open(&self) -> Result<ArrayTabCursor<'_>> {
+ fn open(&mut self) -> Result<ArrayTabCursor<'_>> {
Ok(ArrayTabCursor::new())
}
}
diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs
index df3529a..a65db05 100644
--- a/src/vtab/csvtab.rs
+++ b/src/vtab/csvtab.rs
@@ -11,7 +11,7 @@
//! // Note: This should be done once (usually when opening the DB).
//! let db = Connection::open_in_memory()?;
//! rusqlite::vtab::csvtab::load_module(&db)?;
-//! // Assum3e my_csv.csv
+//! // Assume my_csv.csv
//! let schema = "
//! CREATE VIRTUAL TABLE my_csv_data
//! USING csv(filename = 'my_csv.csv')
@@ -30,8 +30,8 @@ use std::str;
use crate::ffi;
use crate::types::Null;
use crate::vtab::{
- dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
- VTab, VTabConnection, VTabCursor, Values,
+ escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo, VTab,
+ VTabConfig, VTabConnection, VTabCursor, VTabKind, Values,
};
use crate::{Connection, Error, Result};
@@ -74,19 +74,6 @@ impl CsvTab {
.from_path(&self.filename)
}
- fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
- let arg = str::from_utf8(c_slice)?.trim();
- let mut split = arg.split('=');
- if let Some(key) = split.next() {
- if let Some(value) = split.next() {
- let param = key.trim();
- let value = dequote(value);
- return Ok((param, value));
- }
- }
- Err(Error::ModuleError(format!("illegal argument: '{}'", arg)))
- }
-
fn parse_byte(arg: &str) -> Option<u8> {
if arg.len() == 1 {
arg.bytes().next()
@@ -101,7 +88,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
type Cursor = CsvTabCursor<'vtab>;
fn connect(
- _: &mut VTabConnection,
+ db: &mut VTabConnection,
_aux: Option<&()>,
args: &[&[u8]],
) -> Result<(String, CsvTab)> {
@@ -122,7 +109,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
let args = &args[3..];
for c_slice in args {
- let (param, value) = CsvTab::parameter(c_slice)?;
+ let (param, value) = super::parameter(c_slice)?;
match param {
"filename" => {
if !Path::new(value).exists() {
@@ -249,7 +236,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
}
schema = Some(sql);
}
-
+ db.config(VTabConfig::DirectOnly)?;
Ok((schema.unwrap(), vtab))
}
@@ -259,12 +246,14 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
Ok(())
}
- fn open(&self) -> Result<CsvTabCursor<'_>> {
+ fn open(&mut self) -> Result<CsvTabCursor<'_>> {
Ok(CsvTabCursor::new(self.reader()?))
}
}
-impl CreateVTab<'_> for CsvTab {}
+impl CreateVTab<'_> for CsvTab {
+ const KIND: VTabKind = VTabKind::Default;
+}
/// A cursor for the CSV virtual table
#[repr(C)]
diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs
index bdb6509..07008f3 100644
--- a/src/vtab/mod.rs
+++ b/src/vtab/mod.rs
@@ -57,6 +57,23 @@ use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
// ffi::sqlite3_vtab => VTab
// ffi::sqlite3_vtab_cursor => VTabCursor
+/// Virtual table kind
+pub enum VTabKind {
+ /// Non-eponymous
+ Default,
+ /// [`create`](CreateVTab::create) == [`connect`](VTab::connect)
+ ///
+ /// See [SQLite doc](https://sqlite.org/vtab.html#eponymous_virtual_tables)
+ Eponymous,
+ /// No [`create`](CreateVTab::create) / [`destroy`](CreateVTab::destroy) or
+ /// not used
+ ///
+ /// SQLite >= 3.9.0
+ ///
+ /// See [SQLite doc](https://sqlite.org/vtab.html#eponymous_only_virtual_tables)
+ EponymousOnly,
+}
+
/// Virtual table module
///
/// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
@@ -84,31 +101,26 @@ const ZERO_MODULE: ffi::sqlite3_module = unsafe {
.module
};
-/// Create a read-only virtual table implementation.
-///
-/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
-#[must_use]
-pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T> {
- // The xConnect and xCreate methods do the same thing, but they must be
- // different so that the virtual table is not an eponymous virtual table.
+macro_rules! module {
+ ($lt:lifetime, $vt:ty, $ct:ty, $xc:expr, $xd:expr, $xu:expr) => {
#[allow(clippy::needless_update)]
&Module {
base: ffi::sqlite3_module {
// We don't use V3
- iVersion: 2, // We don't use V2 or V3 features in read_only_module types
- xCreate: Some(rust_create::<T>),
- xConnect: Some(rust_connect::<T>),
- xBestIndex: Some(rust_best_index::<T>),
- xDisconnect: Some(rust_disconnect::<T>),
- xDestroy: Some(rust_destroy::<T>),
- xOpen: Some(rust_open::<T>),
- xClose: Some(rust_close::<T::Cursor>),
- xFilter: Some(rust_filter::<T::Cursor>),
- xNext: Some(rust_next::<T::Cursor>),
- xEof: Some(rust_eof::<T::Cursor>),
- xColumn: Some(rust_column::<T::Cursor>),
- xRowid: Some(rust_rowid::<T::Cursor>),
- xUpdate: None,
+ iVersion: 2,
+ xCreate: $xc,
+ xConnect: Some(rust_connect::<$vt>),
+ xBestIndex: Some(rust_best_index::<$vt>),
+ xDisconnect: Some(rust_disconnect::<$vt>),
+ xDestroy: $xd,
+ xOpen: Some(rust_open::<$vt>),
+ xClose: Some(rust_close::<$ct>),
+ xFilter: Some(rust_filter::<$ct>),
+ xNext: Some(rust_next::<$ct>),
+ xEof: Some(rust_eof::<$ct>),
+ xColumn: Some(rust_column::<$ct>),
+ xRowid: Some(rust_rowid::<$ct>), // FIXME optional
+ xUpdate: $xu,
xBegin: None,
xSync: None,
xCommit: None,
@@ -120,7 +132,46 @@ pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab,
xRollbackTo: None,
..ZERO_MODULE
},
- phantom: PhantomData::<&'vtab T>,
+ phantom: PhantomData::<&$lt $vt>,
+ }
+ };
+}
+
+/// Create an modifiable virtual table implementation.
+///
+/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
+#[must_use]
+pub fn update_module<'vtab, T: UpdateVTab<'vtab>>() -> &'static Module<'vtab, T> {
+ match T::KIND {
+ VTabKind::EponymousOnly => {
+ module!('vtab, T, T::Cursor, None, None, Some(rust_update::<T>))
+ }
+ VTabKind::Eponymous => {
+ module!('vtab, T, T::Cursor, Some(rust_connect::<T>), Some(rust_disconnect::<T>), Some(rust_update::<T>))
+ }
+ _ => {
+ module!('vtab, T, T::Cursor, Some(rust_create::<T>), Some(rust_destroy::<T>), Some(rust_update::<T>))
+ }
+ }
+}
+
+/// Create a read-only virtual table implementation.
+///
+/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
+#[must_use]
+pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T> {
+ match T::KIND {
+ VTabKind::EponymousOnly => eponymous_only_module(),
+ VTabKind::Eponymous => {
+ // A virtual table is eponymous if its xCreate method is the exact same function
+ // as the xConnect method
+ module!('vtab, T, T::Cursor, Some(rust_connect::<T>), Some(rust_disconnect::<T>), None)
+ }
+ _ => {
+ // The xConnect and xCreate methods may do the same thing, but they must be
+ // different so that the virtual table is not an eponymous virtual table.
+ module!('vtab, T, T::Cursor, Some(rust_create::<T>), Some(rust_destroy::<T>), None)
+ }
}
}
@@ -129,49 +180,36 @@ pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab,
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
#[must_use]
pub fn eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, T> {
- // A virtual table is eponymous if its xCreate method is the exact same function
- // as the xConnect method For eponymous-only virtual tables, the xCreate
- // method is NULL
- #[allow(clippy::needless_update)]
- &Module {
- base: ffi::sqlite3_module {
- // We don't use V3
- iVersion: 2,
- xCreate: None,
- xConnect: Some(rust_connect::<T>),
- xBestIndex: Some(rust_best_index::<T>),
- xDisconnect: Some(rust_disconnect::<T>),
- xDestroy: None,
- xOpen: Some(rust_open::<T>),
- xClose: Some(rust_close::<T::Cursor>),
- xFilter: Some(rust_filter::<T::Cursor>),
- xNext: Some(rust_next::<T::Cursor>),
- xEof: Some(rust_eof::<T::Cursor>),
- xColumn: Some(rust_column::<T::Cursor>),
- xRowid: Some(rust_rowid::<T::Cursor>),
- xUpdate: None,
- xBegin: None,
- xSync: None,
- xCommit: None,
- xRollback: None,
- xFindFunction: None,
- xRename: None,
- xSavepoint: None,
- xRelease: None,
- xRollbackTo: None,
- ..ZERO_MODULE
- },
- phantom: PhantomData::<&'vtab T>,
- }
+ // For eponymous-only virtual tables, the xCreate method is NULL
+ module!('vtab, T, T::Cursor, None, None, None)
+}
+
+/// 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
+ ConstraintSupport = 1,
+ /// Equivalent to SQLITE_VTAB_INNOCUOUS
+ Innocuous = 2,
+ /// Equivalent to SQLITE_VTAB_DIRECTONLY
+ DirectOnly = 3,
}
/// `feature = "vtab"`
pub struct VTabConnection(*mut ffi::sqlite3);
impl VTabConnection {
- // TODO sqlite3_vtab_config (http://sqlite.org/c3ref/vtab_config.html)
+ /// 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) })
+ }
- // TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html)
+ // TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html) & xUpdate
/// Get access to the underlying SQLite database connection handle.
///
@@ -191,7 +229,7 @@ impl VTabConnection {
}
}
-/// Virtual table instance trait.
+/// Eponymous-only virtual table instance trait.
///
/// # Safety
///
@@ -229,13 +267,17 @@ pub unsafe trait VTab<'vtab>: Sized {
/// Create a new cursor used for accessing a virtual table.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xopen_method))
- fn open(&'vtab self) -> Result<Self::Cursor>;
+ fn open(&'vtab mut self) -> Result<Self::Cursor>;
}
-/// Non-eponymous virtual table instance trait.
+/// Read-only virtual table instance trait.
///
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
pub trait CreateVTab<'vtab>: VTab<'vtab> {
+ /// For [`EponymousOnly`](VTabKind::EponymousOnly),
+ /// [`create`](CreateVTab::create) and [`destroy`](CreateVTab::destroy) are
+ /// not called
+ const KIND: VTabKind;
/// Create a new instance of a virtual table in response to a CREATE VIRTUAL
/// TABLE statement. The `db` parameter is a pointer to the SQLite
/// database connection that is executing the CREATE VIRTUAL TABLE
@@ -261,9 +303,26 @@ pub trait CreateVTab<'vtab>: VTab<'vtab> {
}
}
+/// Writable virtual table instance trait.
+///
+/// (See [SQLite doc](https://sqlite.org/vtab.html#xupdate))
+pub trait UpdateVTab<'vtab>: CreateVTab<'vtab> {
+ /// Delete rowid or PK
+ fn delete(&mut self, arg: ValueRef<'_>) -> Result<()>;
+ /// Insert: `args[0] == NULL: old rowid or PK, args[1]: new rowid or PK,
+ /// args[2]: ...`
+ ///
+ /// Return the new rowid.
+ // TODO Make the distinction between argv[1] == NULL and argv[1] != NULL ?
+ fn insert(&mut self, args: &Values<'_>) -> Result<i64>;
+ /// Update: `args[0] != NULL: old rowid or PK, args[1]: new row id or PK,
+ /// args[2]: ...`
+ fn update(&mut self, args: &Values<'_>) -> Result<()>;
+}
+
/// Index constraint operator.
/// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details.
-#[derive(Debug, PartialEq)]
+#[derive(Debug, Eq, PartialEq)]
#[allow(non_snake_case, non_camel_case_types, missing_docs)]
#[allow(clippy::upper_case_acronyms)]
pub enum IndexConstraintOp {
@@ -310,10 +369,24 @@ impl From<u8> for IndexConstraintOp {
}
}
+#[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.
+ #[repr(C)]
+ pub struct IndexFlags: ::std::os::raw::c_int {
+ /// Default
+ const NONE = 0;
+ /// Scan visits at most 1 row.
+ const SQLITE_INDEX_SCAN_UNIQUE = ffi::SQLITE_INDEX_SCAN_UNIQUE;
+ }
+}
+
/// Pass information into and receive the reply from the
/// [`VTab::best_index`] method.
///
/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
+#[derive(Debug)]
pub struct IndexInfo(*mut ffi::sqlite3_index_info);
impl IndexInfo {
@@ -376,6 +449,14 @@ impl IndexInfo {
}
}
+ /// String used to identify the index
+ pub fn set_idx_str(&mut self, idx_str: &str) {
+ unsafe {
+ (*self.0).idxStr = alloc(idx_str);
+ (*self.0).needToFreeIdxStr = 1;
+ }
+ }
+
/// True if output is already ordered
#[inline]
pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
@@ -402,10 +483,60 @@ impl IndexInfo {
}
}
- // TODO idxFlags
- // TODO colUsed
+ /// 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() };
+ }
- // TODO sqlite3_vtab_collation (http://sqlite.org/c3ref/vtab_collation.html)
+ /// 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 }
+ }
+
+ /// Determine the collation for a virtual table constraint
+ #[cfg(feature = "modern_sqlite")] // SQLite >= 3.22.0
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+ pub fn collation(&self, constraint_idx: usize) -> Result<&str> {
+ use std::ffi::CStr;
+ let idx = constraint_idx as c_int;
+ let collation = unsafe { ffi::sqlite3_vtab_collation(self.0, idx) };
+ if collation.is_null() {
+ return Err(Error::SqliteFailure(
+ ffi::Error::new(ffi::SQLITE_MISUSE),
+ Some(format!("{} is out of range", constraint_idx)),
+ ));
+ }
+ Ok(unsafe { CStr::from_ptr(collation) }.to_str()?)
+ }
+
+ /*/// Determine if a virtual table query is DISTINCT
+ #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+ pub fn distinct(&self) -> c_int {
+ unsafe { ffi::sqlite3_vtab_distinct(self.0) }
+ }
+
+ /// Constraint values
+ #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+ pub fn set_rhs_value(&mut self, constraint_idx: c_int, value: ValueRef) -> Result<()> {
+ // TODO ValueRef to sqlite3_value
+ crate::error::check(unsafe { ffi::sqlite3_vtab_rhs_value(self.O, constraint_idx, value) })
+ }
+
+ /// Identify and handle IN constraints
+ #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+ pub fn set_in_constraint(&mut self, constraint_idx: c_int, b_handle: c_int) -> bool {
+ unsafe { ffi::sqlite3_vtab_in(self.0, constraint_idx, b_handle) != 0 }
+ } // TODO sqlite3_vtab_in_first / sqlite3_vtab_in_next https://sqlite.org/c3ref/vtab_in_first.html
+ */
}
/// Iterate on index constraint and its associated usage.
@@ -583,7 +714,7 @@ impl Context {
Ok(())
}
- // TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html)
+ // TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html) // 3.22.0 & xColumn
}
/// Wrapper to [`VTabCursor::filter`] arguments, the values
@@ -651,6 +782,7 @@ impl Values<'_> {
iter: self.args.iter(),
}
}
+ // TODO sqlite3_vtab_in_first / sqlite3_vtab_in_next https://sqlite.org/c3ref/vtab_in_first.html & 3.38.0
}
impl<'a> IntoIterator for &'a Values<'a> {
@@ -707,6 +839,13 @@ impl InnerConnection {
module: &'static Module<'vtab, T>,
aux: Option<T::Aux>,
) -> Result<()> {
+ use crate::version;
+ if version::version_number() < 3_009_000 && module.base.xCreate.is_none() {
+ return Err(Error::ModuleError(format!(
+ "Eponymous-only virtual table not supported by SQLite version {}",
+ version::version()
+ )));
+ }
let c_name = str_to_cstring(module_name)?;
let r = match aux {
Some(aux) => {
@@ -754,7 +893,7 @@ pub fn dequote(s: &str) -> &str {
}
match s.bytes().next() {
Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() {
- Some(e) if e == b => &s[1..s.len() - 1],
+ Some(e) if e == b => &s[1..s.len() - 1], // FIXME handle inner escaped quote(s)
_ => s,
},
_ => s,
@@ -784,6 +923,20 @@ pub fn parse_boolean(s: &str) -> Option<bool> {
}
}
+/// `<param_name>=['"]?<param_value>['"]?` => `(<param_name>, <param_value>)`
+pub fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
+ let arg = std::str::from_utf8(c_slice)?.trim();
+ let mut split = arg.split('=');
+ if let Some(key) = split.next() {
+ if let Some(value) = split.next() {
+ let param = key.trim();
+ let value = dequote(value);
+ return Ok((param, value));
+ }
+ }
+ Err(Error::ModuleError(format!("illegal argument: '{}'", arg)))
+}
+
// FIXME copy/paste from function.rs
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
drop(Box::from_raw(p.cast::<T>()));
@@ -810,7 +963,7 @@ where
.map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error>
.collect::<Vec<_>>();
match T::create(&mut conn, aux.as_ref(), &vec[..]) {
- Ok((sql, vtab)) => match ::std::ffi::CString::new(sql) {
+ Ok((sql, vtab)) => match std::ffi::CString::new(sql) {
Ok(c_sql) => {
let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
if rc == ffi::SQLITE_OK {
@@ -862,7 +1015,7 @@ where
.map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error>
.collect::<Vec<_>>();
match T::connect(&mut conn, aux.as_ref(), &vec[..]) {
- Ok((sql, vtab)) => match ::std::ffi::CString::new(sql) {
+ Ok((sql, vtab)) => match std::ffi::CString::new(sql) {
Ok(c_sql) => {
let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
if rc == ffi::SQLITE_OK {
@@ -1061,6 +1214,49 @@ where
}
}
+unsafe extern "C" fn rust_update<'vtab, T: 'vtab>(
+ vtab: *mut ffi::sqlite3_vtab,
+ argc: c_int,
+ argv: *mut *mut ffi::sqlite3_value,
+ p_rowid: *mut ffi::sqlite3_int64,
+) -> c_int
+where
+ T: UpdateVTab<'vtab>,
+{
+ assert!(argc >= 1);
+ let args = slice::from_raw_parts_mut(argv, argc as usize);
+ let vt = vtab.cast::<T>();
+ let r = if args.len() == 1 {
+ (*vt).delete(ValueRef::from_value(args[0]))
+ } else if ffi::sqlite3_value_type(args[0]) == ffi::SQLITE_NULL {
+ // TODO Make the distinction between argv[1] == NULL and argv[1] != NULL ?
+ let values = Values { args };
+ match (*vt).insert(&values) {
+ Ok(rowid) => {
+ *p_rowid = rowid;
+ Ok(())
+ }
+ Err(e) => Err(e),
+ }
+ } else {
+ let values = Values { args };
+ (*vt).update(&values)
+ };
+ match r {
+ Ok(_) => ffi::SQLITE_OK,
+ Err(Error::SqliteFailure(err, s)) => {
+ if let Some(err_msg) = s {
+ set_err_msg(vtab, &err_msg);
+ }
+ err.extended_code
+ }
+ Err(err) => {
+ set_err_msg(vtab, &err.to_string());
+ ffi::SQLITE_ERROR
+ }
+ }
+}
+
/// Virtual table cursors can set an error message by assigning a string to
/// `zErrMsg`.
#[cold]
@@ -1138,6 +1334,8 @@ pub mod csvtab;
#[cfg(feature = "series")]
#[cfg_attr(docsrs, doc(cfg(feature = "series")))]
pub mod series; // SQLite >= 3.9.0
+#[cfg(test)]
+mod vtablog;
#[cfg(test)]
mod test {
diff --git a/src/vtab/series.rs b/src/vtab/series.rs
index f26212a..fffbd4d 100644
--- a/src/vtab/series.rs
+++ b/src/vtab/series.rs
@@ -10,8 +10,8 @@ use std::os::raw::c_int;
use crate::ffi;
use crate::types::Type;
use crate::vtab::{
- eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConnection, VTabCursor,
- Values,
+ eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConfig, VTabConnection,
+ VTabCursor, Values,
};
use crate::{Connection, Error, Result};
@@ -57,13 +57,14 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
type Cursor = SeriesTabCursor<'vtab>;
fn connect(
- _: &mut VTabConnection,
+ db: &mut VTabConnection,
_aux: Option<&()>,
_args: &[&[u8]],
) -> Result<(String, SeriesTab)> {
let vtab = SeriesTab {
base: ffi::sqlite3_vtab::default(),
};
+ db.config(VTabConfig::Innocuous)?;
Ok((
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(),
vtab,
@@ -103,6 +104,8 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
let mut constraint_usage = info.constraint_usage(*j);
constraint_usage.set_argv_index(n_arg);
constraint_usage.set_omit(true);
+ #[cfg(all(test, feature = "modern_sqlite"))]
+ debug_assert_eq!(Ok("BINARY"), info.collation(*j));
}
if !(unusable_mask & !idx_num).is_empty() {
return Err(Error::SqliteFailure(
@@ -150,7 +153,7 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
Ok(())
}
- fn open(&self) -> Result<SeriesTabCursor<'_>> {
+ fn open(&mut self) -> Result<SeriesTabCursor<'_>> {
Ok(SeriesTabCursor::new())
}
}
diff --git a/src/vtab/vtablog.rs b/src/vtab/vtablog.rs
new file mode 100644
index 0000000..bc2e01f
--- /dev/null
+++ b/src/vtab/vtablog.rs
@@ -0,0 +1,300 @@
+///! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c)
+use std::default::Default;
+use std::marker::PhantomData;
+use std::os::raw::c_int;
+use std::str::FromStr;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+use crate::vtab::{
+ update_module, Context, CreateVTab, IndexInfo, UpdateVTab, VTab, VTabConnection, VTabCursor,
+ VTabKind, Values,
+};
+use crate::{ffi, ValueRef};
+use crate::{Connection, Error, Result};
+
+/// Register the "vtablog" module.
+pub fn load_module(conn: &Connection) -> Result<()> {
+ let aux: Option<()> = None;
+ conn.create_module("vtablog", update_module::<VTabLog>(), aux)
+}
+
+/// An instance of the vtablog virtual table
+#[repr(C)]
+struct VTabLog {
+ /// Base class. Must be first
+ base: ffi::sqlite3_vtab,
+ /// Number of rows in the table
+ n_row: i64,
+ /// Instance number for this vtablog table
+ i_inst: usize,
+ /// Number of cursors created
+ n_cursor: usize,
+}
+
+impl VTabLog {
+ fn connect_create(
+ _: &mut VTabConnection,
+ _: Option<&()>,
+ args: &[&[u8]],
+ is_create: bool,
+ ) -> Result<(String, VTabLog)> {
+ static N_INST: AtomicUsize = AtomicUsize::new(1);
+ let i_inst = N_INST.fetch_add(1, Ordering::SeqCst);
+ println!(
+ "VTabLog::{}(tab={}, args={:?}):",
+ if is_create { "create" } else { "connect" },
+ i_inst,
+ args,
+ );
+ let mut schema = None;
+ let mut n_row = None;
+
+ let args = &args[3..];
+ for c_slice in args {
+ let (param, value) = super::parameter(c_slice)?;
+ match param {
+ "schema" => {
+ if schema.is_some() {
+ return Err(Error::ModuleError(format!(
+ "more than one '{}' parameter",
+ param
+ )));
+ }
+ schema = Some(value.to_owned())
+ }
+ "rows" => {
+ if n_row.is_some() {
+ return Err(Error::ModuleError(format!(
+ "more than one '{}' parameter",
+ param
+ )));
+ }
+ if let Ok(n) = i64::from_str(value) {
+ n_row = Some(n)
+ }
+ }
+ _ => {
+ return Err(Error::ModuleError(format!(
+ "unrecognized parameter '{}'",
+ param
+ )));
+ }
+ }
+ }
+ if schema.is_none() {
+ return Err(Error::ModuleError("no schema defined".to_owned()));
+ }
+ let vtab = VTabLog {
+ base: ffi::sqlite3_vtab::default(),
+ n_row: n_row.unwrap_or(10),
+ i_inst,
+ n_cursor: 0,
+ };
+ Ok((schema.unwrap(), vtab))
+ }
+}
+
+impl Drop for VTabLog {
+ fn drop(&mut self) {
+ println!("VTabLog::drop({})", self.i_inst);
+ }
+}
+
+unsafe impl<'vtab> VTab<'vtab> for VTabLog {
+ type Aux = ();
+ type Cursor = VTabLogCursor<'vtab>;
+
+ fn connect(
+ db: &mut VTabConnection,
+ aux: Option<&Self::Aux>,
+ args: &[&[u8]],
+ ) -> Result<(String, Self)> {
+ VTabLog::connect_create(db, aux, args, false)
+ }
+
+ fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
+ println!("VTabLog::best_index({})", self.i_inst);
+ info.set_estimated_cost(500.);
+ info.set_estimated_rows(500);
+ Ok(())
+ }
+
+ fn open(&'vtab mut self) -> Result<Self::Cursor> {
+ self.n_cursor += 1;
+ println!(
+ "VTabLog::open(tab={}, cursor={})",
+ self.i_inst, self.n_cursor
+ );
+ Ok(VTabLogCursor {
+ base: ffi::sqlite3_vtab_cursor::default(),
+ i_cursor: self.n_cursor,
+ row_id: 0,
+ phantom: PhantomData,
+ })
+ }
+}
+
+impl<'vtab> CreateVTab<'vtab> for VTabLog {
+ const KIND: VTabKind = VTabKind::Default;
+
+ fn create(
+ db: &mut VTabConnection,
+ aux: Option<&Self::Aux>,
+ args: &[&[u8]],
+ ) -> Result<(String, Self)> {
+ VTabLog::connect_create(db, aux, args, true)
+ }
+
+ fn destroy(&self) -> Result<()> {
+ println!("VTabLog::destroy({})", self.i_inst);
+ Ok(())
+ }
+}
+
+impl<'vtab> UpdateVTab<'vtab> for VTabLog {
+ fn delete(&mut self, arg: ValueRef<'_>) -> Result<()> {
+ println!("VTabLog::delete({}, {:?})", self.i_inst, arg);
+ Ok(())
+ }
+
+ fn insert(&mut self, args: &Values<'_>) -> Result<i64> {
+ println!(
+ "VTabLog::insert({}, {:?})",
+ self.i_inst,
+ args.iter().collect::<Vec<ValueRef<'_>>>()
+ );
+ Ok(self.n_row as i64)
+ }
+
+ fn update(&mut self, args: &Values<'_>) -> Result<()> {
+ println!(
+ "VTabLog::update({}, {:?})",
+ self.i_inst,
+ args.iter().collect::<Vec<ValueRef<'_>>>()
+ );
+ Ok(())
+ }
+}
+
+/// A cursor for the Series virtual table
+#[repr(C)]
+struct VTabLogCursor<'vtab> {
+ /// Base class. Must be first
+ base: ffi::sqlite3_vtab_cursor,
+ /// Cursor number
+ i_cursor: usize,
+ /// The rowid
+ row_id: i64,
+ phantom: PhantomData<&'vtab VTabLog>,
+}
+
+impl VTabLogCursor<'_> {
+ fn vtab(&self) -> &VTabLog {
+ unsafe { &*(self.base.pVtab as *const VTabLog) }
+ }
+}
+
+impl Drop for VTabLogCursor<'_> {
+ fn drop(&mut self) {
+ println!(
+ "VTabLogCursor::drop(tab={}, cursor={})",
+ self.vtab().i_inst,
+ self.i_cursor
+ );
+ }
+}
+
+unsafe impl VTabCursor for VTabLogCursor<'_> {
+ fn filter(&mut self, _: c_int, _: Option<&str>, _: &Values<'_>) -> Result<()> {
+ println!(
+ "VTabLogCursor::filter(tab={}, cursor={})",
+ self.vtab().i_inst,
+ self.i_cursor
+ );
+ self.row_id = 0;
+ Ok(())
+ }
+
+ fn next(&mut self) -> Result<()> {
+ println!(
+ "VTabLogCursor::next(tab={}, cursor={}): rowid {} -> {}",
+ self.vtab().i_inst,
+ self.i_cursor,
+ self.row_id,
+ self.row_id + 1
+ );
+ self.row_id += 1;
+ Ok(())
+ }
+
+ fn eof(&self) -> bool {
+ let eof = self.row_id >= self.vtab().n_row;
+ println!(
+ "VTabLogCursor::eof(tab={}, cursor={}): {}",
+ self.vtab().i_inst,
+ self.i_cursor,
+ eof,
+ );
+ eof
+ }
+
+ fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
+ let value = if i < 26 {
+ format!(
+ "{}{}",
+ "abcdefghijklmnopqrstuvwyz".chars().nth(i as usize).unwrap(),
+ self.row_id
+ )
+ } else {
+ format!("{}{}", i, self.row_id)
+ };
+ println!(
+ "VTabLogCursor::column(tab={}, cursor={}, i={}): {}",
+ self.vtab().i_inst,
+ self.i_cursor,
+ i,
+ value,
+ );
+ ctx.set_result(&value)
+ }
+
+ fn rowid(&self) -> Result<i64> {
+ println!(
+ "VTabLogCursor::rowid(tab={}, cursor={}): {}",
+ self.vtab().i_inst,
+ self.i_cursor,
+ self.row_id,
+ );
+ Ok(self.row_id)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{Connection, Result};
+ #[test]
+ fn test_module() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ super::load_module(&db)?;
+
+ db.execute_batch(
+ "CREATE VIRTUAL TABLE temp.log USING vtablog(
+ schema='CREATE TABLE x(a,b,c)',
+ rows=25
+ );",
+ )?;
+ 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(
+ "INSERT INTO log (a, b, c) VALUES (?, ?, ?)",
+ ["a", "b", "c"],
+ )?;
+ db.execute(
+ "UPDATE log SET b = ?, c = ? WHERE a = ?",
+ ["bn", "cn", "a1"],
+ )?;
+ Ok(())
+ }
+}
diff --git a/tests/deny_single_threaded_sqlite_config.rs b/tests/deny_single_threaded_sqlite_config.rs
index f6afdd5..adfc8e5 100644
--- a/tests/deny_single_threaded_sqlite_config.rs
+++ b/tests/deny_single_threaded_sqlite_config.rs
@@ -5,17 +5,16 @@ use rusqlite::ffi;
use rusqlite::Connection;
#[test]
-#[should_panic]
fn test_error_when_singlethread_mode() {
// put SQLite into single-threaded mode
unsafe {
+ // Note: macOS system SQLite seems to return an error if you attempt to
+ // reconfigure to single-threaded mode.
if ffi::sqlite3_config(ffi::SQLITE_CONFIG_SINGLETHREAD) != ffi::SQLITE_OK {
return;
}
- if ffi::sqlite3_initialize() != ffi::SQLITE_OK {
- return;
- }
+ assert_eq!(ffi::sqlite3_initialize(), ffi::SQLITE_OK);
}
-
- let _ = Connection::open_in_memory().unwrap();
+ let res = Connection::open_in_memory();
+ assert!(res.is_err());
}
diff --git a/tests/vtab.rs b/tests/vtab.rs
index fa26459..6558206 100644
--- a/tests/vtab.rs
+++ b/tests/vtab.rs
@@ -39,7 +39,7 @@ fn test_dummy_module() -> rusqlite::Result<()> {
Ok(())
}
- fn open(&'vtab self) -> Result<DummyTabCursor<'vtab>> {
+ fn open(&'vtab mut self) -> Result<DummyTabCursor<'vtab>> {
Ok(DummyTabCursor::default())
}
}
@@ -88,7 +88,7 @@ fn test_dummy_module() -> rusqlite::Result<()> {
db.create_module::<DummyTab>("dummy", module, None)?;
let version = version_number();
- if version < 3_008_012 {
+ if version < 3_009_000 {
return Ok(());
}