diff options
author | Haibo Huang <hhb@google.com> | 2020-09-16 19:15:45 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-09-16 19:15:45 +0000 |
commit | b3882bc3670d9d26c6d731e04fa0ef879746c90d (patch) | |
tree | 7627b35b901cea37bfd0a90396c150eddd1e526b | |
parent | 6a763eeeeac193c60806ba755cb331bdbf6228da (diff) | |
parent | 0053da6db0d781003180e44ff8363fd1def947a1 (diff) | |
download | rusqlite-b3882bc3670d9d26c6d731e04fa0ef879746c90d.tar.gz |
Upgrade rust/crates/rusqlite to 0.24.0 am: b5372b7e69 am: 29fb7839dc am: 76138a0f92 am: 0053da6db0
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/rusqlite/+/1407248
Change-Id: Id8f48d7d90aaba1b94e5d1daa6114f5e1c2d4e97
38 files changed, 639 insertions, 216 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 2220414..8625996 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,5 +1,5 @@ { "git": { - "sha1": "65ef2224b6c36ee7d298331ee45349844a8bed80" + "sha1": "79ab6894f0f6f651515ab6eac6043685befe9bd8" } } diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d64ee25..5e93ceb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,6 +54,14 @@ jobs: command: test args: --features 'bundled-full session buildtime_bindgen' --all-targets --workspace + - name: "cargo test --doc --features 'bundled-full session buildtime_bindgen'" + # TODO: clang is installed on these -- but `bindgen` can't find it... + if: matrix.platform.os != 'windows-latest' + uses: actions-rs/cargo@v1 + with: + command: test + args: --features 'bundled-full session buildtime_bindgen' --doc --workspace + - name: "cargo test --features bundled-full" uses: actions-rs/cargo@v1 with: @@ -85,6 +93,11 @@ jobs: RUSTFLAGS: -Zsanitizer=address RUSTDOCFLAGS: -Zsanitizer=address ASAN_OPTIONS: 'detect_stack_use_after_return=1' + # Work around https://github.com/rust-lang/rust/issues/59125 by + # disabling backtraces. In an ideal world we'd probably suppress the + # leak sanitization, but we don't care about backtraces here, so long + # as the other tests have them. + RUST_BACKTRACE: '0' run: cargo -Z build-std test --features 'bundled-full session buildtime_bindgen with-asan' --target x86_64-unknown-linux-gnu # Ensure clippy doesn't complain. @@ -13,7 +13,7 @@ [package] edition = "2018" name = "rusqlite" -version = "0.23.1" +version = "0.24.0" authors = ["The rusqlite developers"] description = "Ergonomic wrapper for SQLite" documentation = "http://docs.rs/rusqlite/" @@ -25,7 +25,7 @@ repository = "https://github.com/rusqlite/rusqlite" [package.metadata.docs.rs] all-features = false default-target = "x86_64-unknown-linux-gnu" -features = ["backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "trace", "url", "vtab", "window", "modern_sqlite"] +features = ["backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "time", "trace", "url", "vtab", "window", "modern_sqlite", "column_decltype"] no-default-features = true [package.metadata.playground] @@ -48,6 +48,10 @@ name = "vtab" [[bench]] name = "cache" harness = false + +[[bench]] +name = "exec" +harness = false [dependencies.bitflags] version = "1.0" @@ -75,7 +79,7 @@ version = "1.0" optional = true [dependencies.libsqlite3-sys] -version = "0.18.0" +version = "0.20.0" [dependencies.lru-cache] version = "0.1" @@ -91,7 +95,8 @@ optional = true version = "1.3" [dependencies.time] -version = "0.1.0" +version = "0.2" +optional = true [dependencies.url] version = "2.0" @@ -128,9 +133,10 @@ backup = ["libsqlite3-sys/min_sqlite_version_3_6_23"] blob = ["libsqlite3-sys/min_sqlite_version_3_7_7"] buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"] bundled = ["libsqlite3-sys/bundled", "modern_sqlite"] -bundled-full = ["array", "backup", "blob", "bundled", "chrono", "collation", "csvtab", "extra_check", "functions", "hooks", "i128_blob", "limits", "load_extension", "serde_json", "series", "trace", "unlock_notify", "url", "uuid", "vtab", "window"] +bundled-full = ["array", "backup", "blob", "bundled", "chrono", "collation", "column_decltype", "csvtab", "extra_check", "functions", "hooks", "i128_blob", "limits", "load_extension", "serde_json", "series", "trace", "unlock_notify", "url", "uuid", "vtab", "window"] bundled-windows = ["libsqlite3-sys/bundled-windows"] collation = [] +column_decltype = [] csvtab = ["csv", "vtab"] extra_check = [] functions = ["libsqlite3-sys/min_sqlite_version_3_7_7"] @@ -146,6 +152,7 @@ sqlcipher = ["libsqlite3-sys/sqlcipher"] trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"] unlock_notify = ["libsqlite3-sys/unlock_notify"] vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"] +wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"] window = ["functions"] with-asan = ["libsqlite3-sys/with-asan"] [badges.appveyor] diff --git a/Cargo.toml.orig b/Cargo.toml.orig index eef2a27..6394b09 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "rusqlite" -version = "0.23.1" +version = "0.24.0" authors = ["The rusqlite developers"] edition = "2018" description = "Ergonomic wrapper for SQLite" @@ -59,6 +59,8 @@ in_gecko = ["modern_sqlite", "libsqlite3-sys/in_gecko"] bundled-windows = ["libsqlite3-sys/bundled-windows"] # Build bundled sqlite with -fsanitize=address with-asan = ["libsqlite3-sys/with-asan"] +column_decltype = [] +wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"] # Helper feature for enabling both `bundled` and most non-build-related optional # features or dependencies. This is useful for running tests / clippy / etc. New @@ -71,6 +73,7 @@ bundled-full = [ "bundled", "chrono", "collation", + "column_decltype", "csvtab", "extra_check", "functions", @@ -80,6 +83,9 @@ bundled-full = [ "load_extension", "serde_json", "series", + # time v0.2 does not work with tarpaulin v0.14.0. See time-rs/time#265. + # Re-enable when time v0.3 is released with the fix. + # "time", "trace", "unlock_notify", "url", @@ -89,7 +95,7 @@ bundled-full = [ ] [dependencies] -time = "0.1.0" +time = { version = "0.2", optional = true } bitflags = "1.0" lru-cache = "0.1" chrono = { version = "0.4", optional = true } @@ -117,7 +123,7 @@ bencher = "0.1" [dependencies.libsqlite3-sys] path = "libsqlite3-sys" -version = "0.18.0" +version = "0.20.0" [[test]] name = "config_log" @@ -133,8 +139,12 @@ name = "vtab" name = "cache" harness = false +[[bench]] +name = "exec" +harness = false + [package.metadata.docs.rs] -features = [ "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "trace", "url", "vtab", "window", "modern_sqlite" ] +features = [ "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "time", "trace", "url", "vtab", "window", "modern_sqlite", "column_decltype" ] all-features = false no-default-features = true default-target = "x86_64-unknown-linux-gnu" @@ -9,11 +9,11 @@ third_party { type: ARCHIVE value: "https://static.crates.io/crates/rusqlite/rusqlite-0.23.1.crate" } - version: "0.23.1" + version: "0.24.0" license_type: NOTICE last_upgrade_date { year: 2020 - month: 6 - day: 11 + month: 8 + day: 22 } } @@ -5,6 +5,7 @@ [![Build Status](https://github.com/rusqlite/rusqlite/workflows/CI/badge.svg)](https://github.com/rusqlite/rusqlite/actions) [![dependency status](https://deps.rs/repo/github/rusqlite/rusqlite/status.svg)](https://deps.rs/repo/github/rusqlite/rusqlite) [![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite) +[![Gitter](https://badges.gitter.im/rusqlite.svg)](https://gitter.im/rusqlite/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Docs](https://docs.rs/rusqlite/badge.svg)](https://docs.rs/rusqlite) [![codecov](https://codecov.io/gh/rusqlite/rusqlite/branch/master/graph/badge.svg)](https://codecov.io/gh/rusqlite/rusqlite) @@ -90,6 +91,9 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s * `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the `Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json). +* `time` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) + and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the + `time::OffsetDateTime` type from the [`time` crate](https://crates.io/crates/time). * `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the `Url` type from the [`url` crate](https://crates.io/crates/url). @@ -97,7 +101,7 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s * `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature is mutually exclusive with `bundled`. * `hooks` for [Commit, Rollback](http://sqlite.org/c3ref/commit_hook.html) and [Data Change](http://sqlite.org/c3ref/update_hook.html) notification callbacks. * `unlock_notify` for [Unlock](https://sqlite.org/unlock_notify.html) notification. -* `vtab` for [virtual table](https://sqlite.org/vtab.html) support (allows you to write virtual table implemntations in Rust). Currently, only read-only virtual tables are supported. +* `vtab` for [virtual table](https://sqlite.org/vtab.html) support (allows you to write virtual table implementations in Rust). Currently, only read-only virtual tables are supported. * [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust. * [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function. * `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected. @@ -108,18 +112,18 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s `libsqlite3-sys` is a separate crate from `rusqlite` that provides the Rust declarations for SQLite's C API. By default, `libsqlite3-sys` attempts to find a SQLite library that already exists on your system using pkg-config, or a -[Vcpkg](https://github.com/Microsoft/vcpkg) installation for MSVC ABI builds. +[Vcpkg](https://github.com/Microsoft/vcpkg) installation for MSVC ABI builds. You can adjust this behavior in a number of ways: * If you use the `bundled` feature, `libsqlite3-sys` will use the [cc](https://crates.io/crates/cc) crate to compile SQLite from source and link against that. This source is embedded in the `libsqlite3-sys` crate and - is currently SQLite 3.30.1 (as of `rusqlite` 0.21.0 / `libsqlite3-sys` - 0.17.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file: + is currently SQLite 3.33.0 (as of `rusqlite` 0.24.0 / `libsqlite3-sys` + 0.20.0). This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file: ```toml [dependencies.rusqlite] - version = "0.21.0" + version = "0.24.0" features = ["bundled"] ``` * You can set the `SQLITE3_LIB_DIR` to point to directory containing the SQLite @@ -130,7 +134,7 @@ You can adjust this behavior in a number of ways: options. The default when using vcpkg is to dynamically link, which must be enabled by setting `VCPKGRS_DYNAMIC=1` environment variable before build. `vcpkg install sqlite3:x64-windows` will install the required library. - + ### Binding generation We use [bindgen](https://crates.io/crates/bindgen) to generate the Rust @@ -192,6 +196,10 @@ instead. Rusqlite is the product of hard work by a number of people. A list is available here: https://github.com/rusqlite/rusqlite/graphs/contributors +## Community + +Currently there's a gitter channel set up for rusqlite [here](https://gitter.im/rusqlite/community). + ## License Rusqlite is available under the MIT license. See the LICENSE file for more info. diff --git a/benches/exec.rs b/benches/exec.rs new file mode 100644 index 0000000..360a98b --- /dev/null +++ b/benches/exec.rs @@ -0,0 +1,17 @@ +use bencher::{benchmark_group, benchmark_main, Bencher}; +use rusqlite::{Connection, NO_PARAMS}; + +fn bench_execute(b: &mut Bencher) { + let db = Connection::open_in_memory().unwrap(); + let sql = "PRAGMA user_version=1"; + b.iter(|| db.execute(sql, NO_PARAMS).unwrap()); +} + +fn bench_execute_batch(b: &mut Bencher) { + let db = Connection::open_in_memory().unwrap(); + let sql = "PRAGMA user_version=1"; + b.iter(|| db.execute_batch(sql).unwrap()); +} + +benchmark_group!(exec_benches, bench_execute, bench_execute_batch); +benchmark_main!(exec_benches); diff --git a/codecov.yml b/codecov.yml index 9811dde..7a4789e 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,11 @@ ignore: - "libsqlite3-sys/bindgen-bindings" - "libsqlite3-sys/sqlite3" +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true diff --git a/src/backup.rs b/src/backup.rs index e095146..4654907 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -165,8 +165,8 @@ pub struct Progress { /// `feature = "backup"` A handle to an online backup. pub struct Backup<'a, 'b> { - phantom_from: PhantomData<&'a ()>, - phantom_to: PhantomData<&'b ()>, + phantom_from: PhantomData<&'a Connection>, + phantom_to: PhantomData<&'b Connection>, b: *mut ffi::sqlite3_backup, } diff --git a/src/busy.rs b/src/busy.rs index 223df78..b87504a 100644 --- a/src/busy.rs +++ b/src/busy.rs @@ -60,7 +60,7 @@ impl Connection { let mut c = self.db.borrow_mut(); let r = match callback { Some(f) => unsafe { - ffi::sqlite3_busy_handler(c.db(), Some(busy_handler_callback), mem::transmute(f)) + ffi::sqlite3_busy_handler(c.db(), Some(busy_handler_callback), f as *mut c_void) }, None => unsafe { ffi::sqlite3_busy_handler(c.db(), None, ptr::null_mut()) }, }; diff --git a/src/cache.rs b/src/cache.rs index f85c3ab..67ecd24 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -84,7 +84,7 @@ impl Drop for CachedStatement<'_> { #[allow(unused_must_use)] fn drop(&mut self) { if let Some(stmt) = self.stmt.take() { - self.cache.cache_stmt(stmt.into()); + self.cache.cache_stmt(unsafe { stmt.into_raw() }); } } } diff --git a/src/collation.rs b/src/collation.rs index 25b8458..1168b75 100644 --- a/src/collation.rs +++ b/src/collation.rs @@ -132,7 +132,7 @@ impl InnerConnection { let r = unsafe { ffi::sqlite3_collation_needed( self.db(), - mem::transmute(x_coll_needed), + x_coll_needed as *mut c_void, Some(collation_needed_callback), ) }; diff --git a/src/column.rs b/src/column.rs index 36ba9c8..4f6daac 100644 --- a/src/column.rs +++ b/src/column.rs @@ -39,7 +39,7 @@ impl Statement<'_> { self.stmt.column_count() } - pub(crate) fn column_name_unwrap(&self, col: usize) -> &str { + pub(super) fn column_name_unwrap(&self, col: usize) -> &str { // Just panic if the bounds are wrong for now, we never call this // without checking first. self.column_name(col).expect("Column out of bounds") @@ -86,6 +86,7 @@ impl Statement<'_> { } /// Returns a slice describing the columns of the result of the query. + #[cfg(feature = "column_decltype")] pub fn columns(&self) -> Vec<Column> { let n = self.column_count(); let mut cols = Vec::with_capacity(n as usize); @@ -123,6 +124,7 @@ impl<'stmt> Rows<'stmt> { } /// Returns a slice describing the columns of the Rows. + #[cfg(feature = "column_decltype")] pub fn columns(&self) -> Option<Vec<Column>> { self.stmt.map(Statement::columns) } @@ -150,6 +152,7 @@ impl<'stmt> Row<'stmt> { } /// Returns a slice describing the columns of the Row. + #[cfg(feature = "column_decltype")] pub fn columns(&self) -> Vec<Column> { self.stmt.columns() } @@ -157,11 +160,13 @@ impl<'stmt> Row<'stmt> { #[cfg(test)] mod test { - use super::Column; use crate::Connection; #[test] + #[cfg(feature = "column_decltype")] fn test_columns() { + use super::Column; + let db = Connection::open_in_memory().unwrap(); let query = db.prepare("SELECT * FROM sqlite_master").unwrap(); let columns = query.columns(); diff --git a/src/config.rs b/src/config.rs index 074fed0..797069e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,33 +6,55 @@ use crate::ffi; use crate::{Connection, Result}; /// Database Connection Configuration Options +/// See [Database Connection Configuration Options](https://sqlite.org/c3ref/c_dbconfig_enable_fkey.html) for details. #[repr(i32)] #[allow(non_snake_case, non_camel_case_types)] #[non_exhaustive] pub enum DbConfig { //SQLITE_DBCONFIG_MAINDBNAME = 1000, /* const char* */ //SQLITE_DBCONFIG_LOOKASIDE = 1001, /* void* int int */ + /// Enable or disable the enforcement of foreign key constraints. SQLITE_DBCONFIG_ENABLE_FKEY = 1002, + /// Enable or disable triggers. SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003, + /// Enable or disable the fts3_tokenizer() function which is part of the + /// FTS3 full-text search engine extension. SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // 3.12.0 //SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005, + /// In WAL mode, enable or disable the checkpoint operation before closing + /// the connection. SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006, // 3.16.2 - SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0 - SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0 + /// Activates or deactivates the query planner stability guarantee (QPSG). + SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // 3.20.0 + /// Includes or excludes output for any operations performed by trigger + /// programs from the output of EXPLAIN QUERY PLAN commands. + SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0 //SQLITE_DBCONFIG_RESET_DATABASE = 1009, + /// Activates or deactivates the "defensive" flag for a database connection. SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0 + /// Activates or deactivates the "writable_schema" flag. #[cfg(feature = "modern_sqlite")] SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011, // 3.28.0 + /// Activates or deactivates the legacy behavior of the ALTER TABLE RENAME + /// command. #[cfg(feature = "modern_sqlite")] SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012, // 3.29 + /// Activates or deactivates the legacy double-quoted string literal + /// misfeature for DML statements only. #[cfg(feature = "modern_sqlite")] SQLITE_DBCONFIG_DQS_DML = 1013, // 3.29.0 + /// Activates or deactivates the legacy double-quoted string literal + /// misfeature for DDL statements. #[cfg(feature = "modern_sqlite")] SQLITE_DBCONFIG_DQS_DDL = 1014, // 3.29.0 + /// Enable or disable views. #[cfg(feature = "modern_sqlite")] SQLITE_DBCONFIG_ENABLE_VIEW = 1015, // 3.30.0 + /// Activates or deactivates the legacy file format flag. #[cfg(feature = "modern_sqlite")] SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016, // 3.31.0 + /// Tells SQLite to assume that database schemas (the contents of the + /// sqlite_master tables) are untainted by malicious content. #[cfg(feature = "modern_sqlite")] SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017, // 3.31.0 } diff --git a/src/context.rs b/src/context.rs index ad0a3ad..b7e8bc8 100644 --- a/src/context.rs +++ b/src/context.rs @@ -12,7 +12,7 @@ use crate::types::{ToSqlOutput, ValueRef}; #[cfg(feature = "array")] use crate::vtab::array::{free_array, ARRAY_TYPE}; -pub(crate) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<'_>) { +pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<'_>) { let value = match *result { ToSqlOutput::Borrowed(v) => v, ToSqlOutput::Owned(ref v) => ValueRef::from(v), diff --git a/src/error.rs b/src/error.rs index 35efaf1..c05f8cc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -94,6 +94,7 @@ pub enum Error { #[allow(dead_code)] ModuleError(String), + /// An unwinding panic occurs in an UDF (user-defined function). #[cfg(feature = "functions")] UnwindingPanic, diff --git a/src/functions.rs b/src/functions.rs index b364f21..3531391 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -267,17 +267,26 @@ where } bitflags::bitflags! { - #[doc = "Function Flags."] - #[doc = "See [sqlite3_create_function](https://sqlite.org/c3ref/create_function.html) for details."] + /// Function Flags. + /// See [sqlite3_create_function](https://sqlite.org/c3ref/create_function.html) + /// and [Function Flags](https://sqlite.org/c3ref/c_deterministic.html) for details. #[repr(C)] pub struct FunctionFlags: ::std::os::raw::c_int { + /// Specifies UTF-8 as the text encoding this SQL function prefers for its parameters. const SQLITE_UTF8 = ffi::SQLITE_UTF8; + /// Specifies UTF-16 using little-endian byte order as the text encoding this SQL function prefers for its parameters. const SQLITE_UTF16LE = ffi::SQLITE_UTF16LE; + /// Specifies UTF-16 using big-endian byte order as the text encoding this SQL function prefers for its parameters. const SQLITE_UTF16BE = ffi::SQLITE_UTF16BE; + /// 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; + /// 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. const SQLITE_SUBTYPE = 0x0000_0010_0000; // 3.30.0 + /// Means that the function is unlikely to cause problems even if misused. const SQLITE_INNOCUOUS = 0x0000_0020_0000; // 3.31.0 } } diff --git a/src/hooks.rs b/src/hooks.rs index ed556f5..53dc041 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -14,9 +14,13 @@ use crate::{Connection, InnerConnection}; #[repr(i32)] #[non_exhaustive] pub enum Action { + /// Unsupported / unexpected action UNKNOWN = -1, + /// DELETE command SQLITE_DELETE = ffi::SQLITE_DELETE, + /// INSERT command SQLITE_INSERT = ffi::SQLITE_INSERT, + /// UPDATE command SQLITE_UPDATE = ffi::SQLITE_UPDATE, } diff --git a/src/inner_connection.rs b/src/inner_connection.rs index 95b2d82..dd786fe 100644 --- a/src/inner_connection.rs +++ b/src/inner_connection.rs @@ -106,6 +106,10 @@ impl InnerConnection { return Err(e); } + + // attempt to turn on extended results code; don't fail if we can't. + ffi::sqlite3_extended_result_codes(db, 1); + let r = ffi::sqlite3_busy_timeout(db, 5000); if r != ffi::SQLITE_OK { let e = error_from_handle(db, r); @@ -113,9 +117,6 @@ impl InnerConnection { return Err(e); } - // attempt to turn on extended results code; don't fail if we can't. - ffi::sqlite3_extended_result_codes(db, 1); - Ok(InnerConnection::new(db, true)) } } @@ -170,21 +171,6 @@ impl InnerConnection { } } - pub fn execute_batch(&mut self, sql: &str) -> Result<()> { - // use CString instead of SmallCString because it's probably big. - let c_sql = std::ffi::CString::new(sql)?; - unsafe { - let r = ffi::sqlite3_exec( - self.db(), - c_sql.as_ptr(), - None, - ptr::null_mut(), - ptr::null_mut(), - ); - self.decode_result(r) - } - } - #[cfg(feature = "load_extension")] pub fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> { let r = unsafe { ffi::sqlite3_enable_load_extension(self.db, onoff) }; @@ -261,8 +247,17 @@ impl InnerConnection { // comment) then *ppStmt is set to NULL. let c_stmt: *mut ffi::sqlite3_stmt = c_stmt; let c_tail: *const c_char = c_tail; - // TODO ignore spaces, comments, ... at the end - let tail = !c_tail.is_null() && unsafe { c_tail != c_sql.offset(len as isize) }; + let tail = if c_tail.is_null() { + 0 + } else { + // TODO nightly feature ptr_offset_from #41079 + let n = (c_tail as isize) - (c_sql as isize); + if n <= 0 || n >= len as isize { + 0 + } else { + n as usize + } + }; Ok(Statement::new(conn, unsafe { RawStatement::new(c_stmt, tail) })) @@ -3,13 +3,11 @@ //! //! ```rust //! use rusqlite::{params, Connection, Result}; -//! use time::Timespec; //! //! #[derive(Debug)] //! struct Person { //! id: i32, //! name: String, -//! time_created: Timespec, //! data: Option<Vec<u8>>, //! } //! @@ -20,7 +18,6 @@ //! "CREATE TABLE person ( //! id INTEGER PRIMARY KEY, //! name TEXT NOT NULL, -//! time_created TEXT NOT NULL, //! data BLOB //! )", //! params![], @@ -28,22 +25,19 @@ //! let me = Person { //! id: 0, //! name: "Steven".to_string(), -//! time_created: time::get_time(), //! data: None, //! }; //! conn.execute( -//! "INSERT INTO person (name, time_created, data) -//! VALUES (?1, ?2, ?3)", -//! params![me.name, me.time_created, me.data], +//! "INSERT INTO person (name, data) VALUES (?1, ?2)", +//! params![me.name, me.data], //! )?; //! -//! let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person")?; +//! let mut stmt = conn.prepare("SELECT id, name, data FROM person")?; //! let person_iter = stmt.query_map(params![], |row| { //! Ok(Person { //! id: row.get(0)?, //! name: row.get(1)?, -//! time_created: row.get(2)?, -//! data: row.get(3)?, +//! data: row.get(2)?, //! }) //! })?; //! @@ -53,7 +47,7 @@ //! Ok(()) //! } //! ``` -#![allow(unknown_lints)] +#![warn(missing_docs)] pub use libsqlite3_sys as ffi; @@ -83,7 +77,7 @@ pub use crate::ffi::ErrorCode; pub use crate::hooks::Action; #[cfg(feature = "load_extension")] pub use crate::load_extension_guard::LoadExtensionGuard; -pub use crate::row::{AndThenRows, MappedRows, Row, RowIndex, Rows}; +pub use crate::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows}; pub use crate::statement::{Statement, StatementStatus}; pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior}; pub use crate::types::ToSql; @@ -435,8 +429,6 @@ impl Connection { /// Convenience method to run multiple SQL statements (that cannot take any /// parameters). /// - /// Uses [sqlite3_exec](http://www.sqlite.org/c3ref/exec.html) under the hood. - /// /// ## Example /// /// ```rust,no_run @@ -456,7 +448,20 @@ impl Connection { /// Will return `Err` if `sql` cannot be converted to a C-compatible string /// or if the underlying SQLite call fails. pub fn execute_batch(&self, sql: &str) -> Result<()> { - self.db.borrow_mut().execute_batch(sql) + let mut sql = sql; + while !sql.is_empty() { + let stmt = self.prepare(sql)?; + if !stmt.stmt.is_null() && stmt.step()? && cfg!(feature = "extra_check") { + // Some PRAGMA may return rows + return Err(Error::ExecuteReturnedResults); + } + let tail = stmt.stmt.tail(); + if tail == 0 || tail >= sql.len() { + break; + } + sql = &sql[tail..]; + } + Ok(()) } /// Convenience method to prepare and execute a single SQL statement. @@ -806,19 +811,32 @@ impl fmt::Debug for Connection { } bitflags::bitflags! { - #[doc = "Flags for opening SQLite database connections."] - #[doc = "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. #[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; + /// 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; + /// The database is created if it does not already exist const SQLITE_OPEN_CREATE = ffi::SQLITE_OPEN_CREATE; + /// The filename can be interpreted as a URI if this flag is set. const SQLITE_OPEN_URI = 0x0000_0040; + /// 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; + /// 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. const SQLITE_OPEN_NOFOLLOW = 0x0100_0000; } } diff --git a/src/pragma.rs b/src/pragma.rs index b115f8f..4855154 100644 --- a/src/pragma.rs +++ b/src/pragma.rs @@ -430,4 +430,15 @@ mod test { sql.push_string_literal("value'; --"); assert_eq!("'value''; --'", sql.as_str()); } + + #[test] + fn locking_mode() { + let db = Connection::open_in_memory().unwrap(); + let r = db.pragma_update(None, "locking_mode", &"exclusive"); + if cfg!(feature = "extra_check") { + r.unwrap_err(); + } else { + r.unwrap(); + } + } } diff --git a/src/raw_statement.rs b/src/raw_statement.rs index 893ac8b..c02dcd9 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -12,7 +12,7 @@ use std::sync::Arc; #[derive(Debug)] pub struct RawStatement { ptr: *mut ffi::sqlite3_stmt, - tail: bool, + tail: usize, // Cached indices of named parameters, computed on the fly. cache: crate::util::ParamIndexCache, // Cached SQL (trimmed) that we use as the key when we're in the statement @@ -29,7 +29,7 @@ pub struct RawStatement { } impl RawStatement { - pub unsafe fn new(stmt: *mut ffi::sqlite3_stmt, tail: bool) -> RawStatement { + pub unsafe fn new(stmt: *mut ffi::sqlite3_stmt, tail: usize) -> RawStatement { RawStatement { ptr: stmt, tail, @@ -63,6 +63,7 @@ impl RawStatement { unsafe { ffi::sqlite3_column_type(self.ptr, idx as c_int) } } + #[cfg(feature = "column_decltype")] pub fn column_decltype(&self, idx: usize) -> Option<&CStr> { unsafe { let decltype = ffi::sqlite3_column_decltype(self.ptr, idx as c_int); @@ -152,7 +153,7 @@ impl RawStatement { r } - #[cfg(feature = "modern_sqlite")] // 3.7.4 + #[cfg(all(feature = "extra_check", feature = "modern_sqlite"))] // 3.7.4 pub fn readonly(&self) -> bool { unsafe { ffi::sqlite3_stmt_readonly(self.ptr) != 0 } } @@ -167,7 +168,12 @@ impl RawStatement { unsafe { ffi::sqlite3_stmt_status(self.ptr, status as i32, reset as i32) } } + #[cfg(feature = "extra_check")] pub fn has_tail(&self) -> bool { + self.tail != 0 + } + + pub fn tail(&self) -> usize { self.tail } } @@ -35,6 +35,17 @@ impl<'stmt> Rows<'stmt> { Ok((*self).get()) } + /// Map over this `Rows`, converting it to a [`Map`], which + /// implements `FallibleIterator`. + /// ```rust,no_run + /// use fallible_iterator::FallibleIterator; + /// # use rusqlite::{Result, Statement, NO_PARAMS}; + /// fn query(stmt: &mut Statement) -> Result<Vec<i64>> { + /// let rows = stmt.query(NO_PARAMS)?; + /// rows.map(|r| r.get(0)).collect() + /// } + /// ``` + // FIXME Hide FallibleStreamingIterator::map pub fn map<F, B>(self, f: F) -> Map<'stmt, F> where F: FnMut(&Row<'_>) -> Result<B>, @@ -84,6 +95,7 @@ impl Drop for Rows<'_> { } } +/// `F` is used to tranform the _streaming_ iterator into a _fallible_ iterator. pub struct Map<'stmt, F> { rows: Rows<'stmt>, f: F, @@ -105,6 +117,8 @@ where } /// An iterator over the mapped resulting rows of a query. +/// +/// `F` is used to tranform the _streaming_ iterator into a _standard_ iterator. pub struct MappedRows<'stmt, F> { rows: Rows<'stmt>, map: F, @@ -166,6 +180,24 @@ where } } +/// `FallibleStreamingIterator` differs from the standard library's `Iterator` +/// in two ways: +/// * each call to `next` (sqlite3_step) can fail. +/// * returned `Row` is valid until `next` is called again or `Statement` is +/// reset or finalized. +/// +/// While these iterators cannot be used with Rust `for` loops, `while let` +/// loops offer a similar level of ergonomics: +/// ```rust,no_run +/// # use rusqlite::{Result, Statement, NO_PARAMS}; +/// fn query(stmt: &mut Statement) -> Result<()> { +/// let mut rows = stmt.query(NO_PARAMS)?; +/// while let Some(row) = rows.next()? { +/// // scan columns value +/// } +/// Ok(()) +/// } +/// ``` impl<'stmt> FallibleStreamingIterator for Rows<'stmt> { type Error = Error; type Item = Row<'stmt>; @@ -334,3 +366,201 @@ impl RowIndex for &'_ str { stmt.column_index(*self) } } + +macro_rules! tuple_try_from_row { + ($($field:ident),*) => { + impl<'a, $($field,)*> convert::TryFrom<&'a Row<'a>> for ($($field,)*) where $($field: FromSql,)* { + type Error = crate::Error; + + // we end with index += 1, which rustc warns about + // unused_variables and unused_mut are allowed for () + #[allow(unused_assignments, unused_variables, unused_mut)] + fn try_from(row: &'a Row<'a>) -> Result<Self> { + let mut index = 0; + $( + #[allow(non_snake_case)] + let $field = row.get::<_, $field>(index)?; + index += 1; + )* + Ok(($($field,)*)) + } + } + } +} + +macro_rules! tuples_try_from_row { + () => { + // not very useful, but maybe some other macro users will find this helpful + tuple_try_from_row!(); + }; + ($first:ident $(, $remaining:ident)*) => { + tuple_try_from_row!($first $(, $remaining)*); + tuples_try_from_row!($($remaining),*); + }; +} + +tuples_try_from_row!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); + +#[cfg(test)] +mod tests { + #![allow(clippy::redundant_closure)] // false positives due to lifetime issues; clippy issue #5594 + + #[test] + fn test_try_from_row_for_tuple_1() { + use crate::{Connection, ToSql}; + use std::convert::TryFrom; + + let conn = Connection::open_in_memory().expect("failed to create in-memoory database"); + conn.execute( + "CREATE TABLE test (a INTEGER)", + std::iter::empty::<&dyn ToSql>(), + ) + .expect("failed to create table"); + conn.execute( + "INSERT INTO test VALUES (42)", + std::iter::empty::<&dyn ToSql>(), + ) + .expect("failed to insert value"); + let val = conn + .query_row( + "SELECT a FROM test", + std::iter::empty::<&dyn ToSql>(), + |row| <(u32,)>::try_from(row), + ) + .expect("failed to query row"); + assert_eq!(val, (42,)); + let fail = conn.query_row( + "SELECT a FROM test", + std::iter::empty::<&dyn ToSql>(), + |row| <(u32, u32)>::try_from(row), + ); + assert!(fail.is_err()); + } + + #[test] + fn test_try_from_row_for_tuple_2() { + use crate::{Connection, ToSql}; + use std::convert::TryFrom; + + let conn = Connection::open_in_memory().expect("failed to create in-memoory database"); + conn.execute( + "CREATE TABLE test (a INTEGER, b INTEGER)", + std::iter::empty::<&dyn ToSql>(), + ) + .expect("failed to create table"); + conn.execute( + "INSERT INTO test VALUES (42, 47)", + std::iter::empty::<&dyn ToSql>(), + ) + .expect("failed to insert value"); + let val = conn + .query_row( + "SELECT a, b FROM test", + std::iter::empty::<&dyn ToSql>(), + |row| <(u32, u32)>::try_from(row), + ) + .expect("failed to query row"); + assert_eq!(val, (42, 47)); + let fail = conn.query_row( + "SELECT a, b FROM test", + std::iter::empty::<&dyn ToSql>(), + |row| <(u32, u32, u32)>::try_from(row), + ); + assert!(fail.is_err()); + } + + #[test] + fn test_try_from_row_for_tuple_16() { + use crate::{Connection, ToSql}; + use std::convert::TryFrom; + + let create_table = "CREATE TABLE test ( + a INTEGER, + b INTEGER, + c INTEGER, + d INTEGER, + e INTEGER, + f INTEGER, + g INTEGER, + h INTEGER, + i INTEGER, + j INTEGER, + k INTEGER, + l INTEGER, + m INTEGER, + n INTEGER, + o INTEGER, + p INTEGER + )"; + + let insert_values = "INSERT INTO test VALUES ( + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + )"; + + type BigTuple = ( + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + u32, + ); + + let conn = Connection::open_in_memory().expect("failed to create in-memoory database"); + conn.execute(create_table, std::iter::empty::<&dyn ToSql>()) + .expect("failed to create table"); + conn.execute(insert_values, std::iter::empty::<&dyn ToSql>()) + .expect("failed to insert value"); + let val = conn + .query_row( + "SELECT * FROM test", + std::iter::empty::<&dyn ToSql>(), + |row| BigTuple::try_from(row), + ) + .expect("failed to query row"); + // Debug is not implemented for tuples of 16 + assert_eq!(val.0, 0); + assert_eq!(val.1, 1); + assert_eq!(val.2, 2); + assert_eq!(val.3, 3); + assert_eq!(val.4, 4); + assert_eq!(val.5, 5); + assert_eq!(val.6, 6); + assert_eq!(val.7, 7); + assert_eq!(val.8, 8); + assert_eq!(val.9, 9); + assert_eq!(val.10, 10); + assert_eq!(val.11, 11); + assert_eq!(val.12, 12); + assert_eq!(val.13, 13); + assert_eq!(val.14, 14); + assert_eq!(val.15, 15); + + // We don't test one bigger because it's unimplemented + } +} diff --git a/src/session.rs b/src/session.rs index b4782b2..97ae3a5 100644 --- a/src/session.rs +++ b/src/session.rs @@ -22,7 +22,7 @@ use crate::{errmsg_to_string, str_to_cstring, Connection, DatabaseName, Result}; /// `feature = "session"` An instance of this object is a session that can be /// used to record changes to a database. pub struct Session<'conn> { - phantom: PhantomData<&'conn ()>, + phantom: PhantomData<&'conn Connection>, s: *mut ffi::sqlite3_session, filter: Option<Box<dyn Fn(&str) -> bool>>, } @@ -299,7 +299,7 @@ impl Drop for Changeset { /// `feature = "session"` Cursor for iterating over the elements of a changeset /// or patchset. pub struct ChangesetIter<'changeset> { - phantom: PhantomData<&'changeset ()>, + phantom: PhantomData<&'changeset Changeset>, it: *mut ffi::sqlite3_changeset_iter, item: Option<ChangesetItem>, } @@ -356,18 +356,22 @@ pub struct Operation<'item> { } impl Operation<'_> { + /// Returns the table name. pub fn table_name(&self) -> &str { self.table_name } + /// Returns the number of columns in table pub fn number_of_columns(&self) -> i32 { self.number_of_columns } + /// Returns the action code. pub fn code(&self) -> Action { self.code } + /// Returns `true` for an 'indirect' change. pub fn indirect(&self) -> bool { self.indirect } @@ -488,6 +492,7 @@ pub struct Changegroup { } impl Changegroup { + /// Create a new change group. pub fn new() -> Result<Self> { let mut cg = ptr::null_mut(); check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) }); @@ -620,6 +625,8 @@ impl Connection { } /// `feature = "session"` Constants passed to the conflict handler +/// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_CONFLICT) for details. +#[allow(missing_docs)] #[repr(i32)] #[derive(Debug, PartialEq)] #[non_exhaustive] @@ -645,6 +652,8 @@ impl From<i32> for ConflictType { } /// `feature = "session"` Constants returned by the conflict handler +/// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_ABORT) for details. +#[allow(missing_docs)] #[repr(i32)] #[derive(Debug, PartialEq)] #[non_exhaustive] diff --git a/src/statement.rs b/src/statement.rs index cd05ef6..648a9b7 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -219,6 +219,8 @@ impl Statement<'_> { /// Ok(names) /// } /// ``` + /// `f` is used to tranform the _streaming_ iterator into a _standard_ + /// iterator. /// /// ## Failure /// @@ -256,6 +258,8 @@ impl Statement<'_> { /// Ok(names) /// } /// ``` + /// `f` is used to tranform the _streaming_ iterator into a _standard_ + /// iterator. /// /// ## Failure /// @@ -450,7 +454,7 @@ impl Statement<'_> { self.bind_parameter(&p, index)?; } if index != expected { - Err(Error::InvalidParameterCount(expected, index)) + Err(Error::InvalidParameterCount(index, expected)) } else { Ok(()) } @@ -618,7 +622,7 @@ impl Statement<'_> { } fn finalize_(&mut self) -> Result<()> { - let mut stmt = unsafe { RawStatement::new(ptr::null_mut(), false) }; + let mut stmt = unsafe { RawStatement::new(ptr::null_mut(), 0) }; mem::swap(&mut stmt, &mut self.stmt); self.conn.decode_result(stmt.finalize()) } @@ -642,7 +646,7 @@ impl Statement<'_> { #[inline] fn check_update(&self) -> Result<()> { // sqlite3_column_count works for DML but not for DDL (ie ALTER) - if self.column_count() > 0 || self.stmt.readonly() { + if self.column_count() > 0 && self.stmt.readonly() { return Err(Error::ExecuteReturnedResults); } Ok(()) @@ -698,11 +702,12 @@ impl Statement<'_> { pub(crate) fn check_no_tail(&self) -> Result<()> { Ok(()) } -} -impl Into<RawStatement> for Statement<'_> { - fn into(mut self) -> RawStatement { - let mut stmt = unsafe { RawStatement::new(ptr::null_mut(), false) }; + /// Safety: This is unsafe, because using `sqlite3_stmt` after the + /// connection has closed is illegal, but `RawStatement` does not enforce + /// this, as it loses our protective `'conn` lifetime bound. + pub(crate) unsafe fn into_raw(mut self) -> RawStatement { + let mut stmt = RawStatement::new(ptr::null_mut(), 0); mem::swap(&mut stmt, &mut self.stmt); stmt } @@ -731,11 +736,11 @@ impl Drop for Statement<'_> { } impl Statement<'_> { - pub(crate) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> { + pub(super) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> { Statement { conn, stmt } } - pub(crate) fn value_ref(&self, col: usize) -> ValueRef<'_> { + pub(super) fn value_ref(&self, col: usize) -> ValueRef<'_> { let raw = unsafe { self.stmt.ptr() }; match self.stmt.column_type(col) { @@ -791,7 +796,7 @@ impl Statement<'_> { } } - pub(crate) fn step(&self) -> Result<bool> { + pub(super) fn step(&self) -> Result<bool> { match self.stmt.step() { ffi::SQLITE_ROW => Ok(true), ffi::SQLITE_DONE => Ok(false), @@ -799,7 +804,7 @@ impl Statement<'_> { } } - pub(crate) fn reset(&self) -> c_int { + pub(super) fn reset(&self) -> c_int { self.stmt.reset() } } diff --git a/src/trace.rs b/src/trace.rs index 39ef69f..76e0969 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -35,14 +35,11 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> { } let rc = match callback { - Some(f) => { - let p_arg: *mut c_void = mem::transmute(f); - ffi::sqlite3_config( - ffi::SQLITE_CONFIG_LOG, - log_callback as extern "C" fn(_, _, _), - p_arg, - ) - } + Some(f) => ffi::sqlite3_config( + ffi::SQLITE_CONFIG_LOG, + log_callback as extern "C" fn(_, _, _), + f as *mut c_void, + ), None => { let nullptr: *mut c_void = ptr::null_mut(); ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr) @@ -83,7 +80,7 @@ impl Connection { let c = self.db.borrow_mut(); match trace_fn { Some(f) => unsafe { - ffi::sqlite3_trace(c.db(), Some(trace_callback), mem::transmute(f)); + ffi::sqlite3_trace(c.db(), Some(trace_callback), f as *mut c_void); }, None => unsafe { ffi::sqlite3_trace(c.db(), None, ptr::null_mut()); @@ -117,7 +114,7 @@ impl Connection { let c = self.db.borrow_mut(); match profile_fn { Some(f) => unsafe { - ffi::sqlite3_profile(c.db(), Some(profile_callback), mem::transmute(f)) + ffi::sqlite3_profile(c.db(), Some(profile_callback), f as *mut c_void) }, None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) }, }; diff --git a/src/transaction.rs b/src/transaction.rs index e1b806e..5e649b7 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -6,8 +6,14 @@ use std::ops::Deref; #[derive(Copy, Clone)] #[non_exhaustive] pub enum TransactionBehavior { + /// DEFERRED means that the transaction does not actually start until the + /// database is first accessed. Deferred, + /// IMMEDIATE cause the database connection to start a new write + /// immediately, without waiting for a writes statement. Immediate, + /// EXCLUSIVE prevents other database connections from reading the database + /// while the transaction is underway. Exclusive, } diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs index e8eadc5..3fe74b4 100644 --- a/src/types/from_sql.rs +++ b/src/types/from_sql.rs @@ -86,6 +86,7 @@ pub type FromSqlResult<T> = Result<T, FromSqlError>; /// fetching values as i64 and then doing the interpretation themselves or by /// defining a newtype and implementing `FromSql`/`ToSql` for it. pub trait FromSql: Sized { + /// Converts SQLite value into Rust value. fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>; } @@ -142,10 +143,7 @@ impl FromSql for f64 { impl FromSql for bool { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { - i64::column_result(value).map(|i| match i { - 0 => false, - _ => true, - }) + i64::column_result(value).map(|i| !matches!(i, 0)) } } diff --git a/src/types/mod.rs b/src/types/mod.rs index d79ff82..2d163cf 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -10,40 +10,39 @@ //! * Strings (`String` and `&str`) //! * Blobs (`Vec<u8>` and `&[u8]`) //! -//! Additionally, because it is such a common data type, implementations are -//! provided for `time::Timespec` that use the RFC 3339 date/time format, +//! Additionally, if the `time` feature is enabled, implementations are +//! provided for `time::OffsetDateTime` that use the RFC 3339 date/time format, //! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values //! can be parsed by SQLite's builtin //! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you -//! want different storage for timespecs, you can use a newtype. For example, to -//! store timespecs as `f64`s: -//! -//! ```rust -//! use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; -//! use rusqlite::Result; -//! -//! pub struct TimespecSql(pub time::Timespec); -//! -//! impl FromSql for TimespecSql { -//! fn column_result(value: ValueRef) -> FromSqlResult<Self> { -//! f64::column_result(value).map(|as_f64| { -//! TimespecSql(time::Timespec { -//! sec: as_f64.trunc() as i64, -//! nsec: (as_f64.fract() * 1.0e9) as i32, -//! }) -//! }) -//! } -//! } -//! -//! impl ToSql for TimespecSql { -//! fn to_sql(&self) -> Result<ToSqlOutput> { -//! let TimespecSql(ts) = *self; -//! let as_f64 = ts.sec as f64 + (ts.nsec as f64) / 1.0e9; -//! Ok(as_f64.into()) -//! } -//! } -//! ``` +//! want different storage for datetimes, you can use a newtype. //! +#![cfg_attr(feature = "time", doc = r##" +For example, to store datetimes as `i64`s counting the number of seconds since +the Unix epoch: + +``` +use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; +use rusqlite::Result; + +pub struct DateTimeSql(pub time::OffsetDateTime); + +impl FromSql for DateTimeSql { + fn column_result(value: ValueRef) -> FromSqlResult<Self> { + i64::column_result(value).map(|as_i64| { + DateTimeSql(time::OffsetDateTime::from_unix_timestamp(as_i64)) + }) + } +} + +impl ToSql for DateTimeSql { + fn to_sql(&self) -> Result<ToSqlOutput> { + Ok(self.0.timestamp().into()) + } +} +``` + +"##)] //! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T` //! implements `ToSql` or `FromSql` for the cases where you want to know if a //! value was NULL (which gets translated to `None`). @@ -60,6 +59,7 @@ mod chrono; mod from_sql; #[cfg(feature = "serde_json")] mod serde_json; +#[cfg(feature = "time")] mod time; mod to_sql; #[cfg(feature = "url")] @@ -82,12 +82,19 @@ mod value_ref; #[derive(Copy, Clone)] pub struct Null; +/// SQLite data types. +/// See [Fundamental Datatypes](https://sqlite.org/c3ref/c_blob.html). #[derive(Clone, Debug, PartialEq)] pub enum Type { + /// NULL Null, + /// 64-bit signed integer Integer, + /// 64-bit IEEE floating point number Real, + /// String Text, + /// BLOB Blob, } @@ -266,8 +273,9 @@ mod test { assert!(is_invalid_column_type( row.get::<_, String>(0).err().unwrap() )); + #[cfg(feature = "time")] assert!(is_invalid_column_type( - row.get::<_, time::Timespec>(0).err().unwrap() + row.get::<_, time::OffsetDateTime>(0).err().unwrap() )); assert!(is_invalid_column_type( row.get::<_, Option<c_int>>(0).err().unwrap() @@ -328,8 +336,9 @@ mod test { assert!(is_invalid_column_type( row.get::<_, Vec<u8>>(4).err().unwrap() )); + #[cfg(feature = "time")] assert!(is_invalid_column_type( - row.get::<_, time::Timespec>(4).err().unwrap() + row.get::<_, time::OffsetDateTime>(4).err().unwrap() )); } diff --git a/src/types/time.rs b/src/types/time.rs index 097b22a..8589167 100644 --- a/src/types/time.rs +++ b/src/types/time.rs @@ -1,40 +1,40 @@ +//! `ToSql` and `FromSql` implementation for [`time::OffsetDateTime`]. use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use crate::Result; +use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S"; -const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ"; -const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z"; +const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%NZ"; +const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%N %z"; -impl ToSql for time::Timespec { +impl ToSql for OffsetDateTime { fn to_sql(&self) -> Result<ToSqlOutput<'_>> { - let time_string = time::at_utc(*self) - .strftime(SQLITE_DATETIME_FMT) - .unwrap() - .to_string(); + let time_string = self.to_offset(UtcOffset::UTC).format(SQLITE_DATETIME_FMT); Ok(ToSqlOutput::from(time_string)) } } -impl FromSql for time::Timespec { +impl FromSql for OffsetDateTime { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { - value - .as_str() - .and_then(|s| { - match s.len() { - 19 => time::strptime(s, CURRENT_TIMESTAMP_FMT), - _ => time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| { - time::strptime(s, SQLITE_DATETIME_FMT_LEGACY).or_else(|_| Err(err)) + value.as_str().and_then(|s| { + match s.len() { + 19 => PrimitiveDateTime::parse(s, CURRENT_TIMESTAMP_FMT).map(|d| d.assume_utc()), + _ => PrimitiveDateTime::parse(s, SQLITE_DATETIME_FMT) + .map(|d| d.assume_utc()) + .or_else(|err| { + OffsetDateTime::parse(s, SQLITE_DATETIME_FMT_LEGACY).map_err(|_| err) }), - } - .or_else(|err| Err(FromSqlError::Other(Box::new(err)))) - }) - .map(|tm| tm.to_timespec()) + } + .map_err(|err| FromSqlError::Other(Box::new(err))) + }) } } #[cfg(test)] mod test { use crate::{Connection, Result, NO_PARAMS}; + use std::time::Duration; + use time::OffsetDateTime; fn checked_memory_handle() -> Connection { let db = Connection::open_in_memory().unwrap(); @@ -44,22 +44,25 @@ mod test { } #[test] - fn test_timespec() { + fn test_offset_date_time() { let db = checked_memory_handle(); let mut ts_vec = vec![]; - ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM - ts_vec.push(time::Timespec::new(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond) - ts_vec.push(time::Timespec::new(1_500_391_124, 1_000_000)); //July 18, 2017 - ts_vec.push(time::Timespec::new(2_000_000_000, 2_000_000)); //May 18, 2033 - ts_vec.push(time::Timespec::new(3_000_000_000, 999_999_999)); //January 24, 2065 - ts_vec.push(time::Timespec::new(10_000_000_000, 0)); //November 20, 2286 + let make_datetime = + |secs, nanos| OffsetDateTime::from_unix_timestamp(secs) + Duration::from_nanos(nanos); + + ts_vec.push(make_datetime(10_000, 0)); //January 1, 1970 2:46:40 AM + ts_vec.push(make_datetime(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond) + ts_vec.push(make_datetime(1_500_391_124, 1_000_000)); //July 18, 2017 + ts_vec.push(make_datetime(2_000_000_000, 2_000_000)); //May 18, 2033 + ts_vec.push(make_datetime(3_000_000_000, 999_999_999)); //January 24, 2065 + ts_vec.push(make_datetime(10_000_000_000, 0)); //November 20, 2286 for ts in ts_vec { db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap(); - let from: time::Timespec = db + let from: OffsetDateTime = db .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0)) .unwrap(); @@ -72,7 +75,7 @@ mod test { #[test] fn test_sqlite_functions() { let db = checked_memory_handle(); - let result: Result<time::Timespec> = + let result: Result<OffsetDateTime> = db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0)); assert!(result.is_ok()); } diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs index bec53f8..937c0f8 100644 --- a/src/types/to_sql.rs +++ b/src/types/to_sql.rs @@ -87,6 +87,7 @@ impl ToSql for ToSqlOutput<'_> { /// A trait for types that can be converted into SQLite values. pub trait ToSql { + /// Converts Rust value to SQLite value fn to_sql(&self) -> Result<ToSqlOutput<'_>>; } diff --git a/src/types/value.rs b/src/types/value.rs index 332d78b..64dc203 100644 --- a/src/types/value.rs +++ b/src/types/value.rs @@ -109,6 +109,7 @@ where } impl Value { + /// Returns SQLite fundamental datatype. pub fn data_type(&self) -> Type { match *self { Value::Null => Type::Null, diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs index 80d2457..2f32434 100644 --- a/src/types/value_ref.rs +++ b/src/types/value_ref.rs @@ -20,6 +20,7 @@ pub enum ValueRef<'a> { } impl ValueRef<'_> { + /// Returns SQLite fundamental datatype. pub fn data_type(&self) -> Type { match *self { ValueRef::Null => Type::Null, diff --git a/src/vtab/array.rs b/src/vtab/array.rs index 1ade815..644b468 100644 --- a/src/vtab/array.rs +++ b/src/vtab/array.rs @@ -27,6 +27,7 @@ //! ``` use std::default::Default; +use std::marker::PhantomData; use std::os::raw::{c_char, c_int, c_void}; use std::rc::Rc; @@ -46,6 +47,7 @@ pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) { let _: Array = Rc::from_raw(p as *const Vec<Value>); } +/// Array parameter / pointer pub type Array = Rc<Vec<Value>>; impl ToSql for Array { @@ -71,9 +73,9 @@ struct ArrayTab { base: ffi::sqlite3_vtab, } -unsafe impl VTab for ArrayTab { +unsafe impl<'vtab> VTab<'vtab> for ArrayTab { type Aux = (); - type Cursor = ArrayTabCursor; + type Cursor = ArrayTabCursor<'vtab>; fn connect( _: &mut VTabConnection, @@ -117,28 +119,30 @@ unsafe impl VTab for ArrayTab { Ok(()) } - fn open(&self) -> Result<ArrayTabCursor> { + fn open(&self) -> Result<ArrayTabCursor<'_>> { Ok(ArrayTabCursor::new()) } } /// A cursor for the Array virtual table #[repr(C)] -struct ArrayTabCursor { +struct ArrayTabCursor<'vtab> { /// Base class. Must be first base: ffi::sqlite3_vtab_cursor, /// The rowid row_id: i64, /// Pointer to the array of values ("pointer") ptr: Option<Array>, + phantom: PhantomData<&'vtab ArrayTab>, } -impl ArrayTabCursor { - fn new() -> ArrayTabCursor { +impl ArrayTabCursor<'_> { + fn new<'vtab>() -> ArrayTabCursor<'vtab> { ArrayTabCursor { base: ffi::sqlite3_vtab_cursor::default(), row_id: 0, ptr: None, + phantom: PhantomData, } } @@ -149,7 +153,7 @@ impl ArrayTabCursor { } } } -unsafe impl VTabCursor for ArrayTabCursor { +unsafe impl VTabCursor for ArrayTabCursor<'_> { fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> { if idx_num > 0 { self.ptr = args.get_array(0)?; diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs index 0d8a4c5..79ec5da 100644 --- a/src/vtab/csvtab.rs +++ b/src/vtab/csvtab.rs @@ -22,6 +22,7 @@ //! } //! ``` use std::fs::File; +use std::marker::PhantomData; use std::os::raw::c_int; use std::path::Path; use std::str; @@ -95,9 +96,9 @@ impl CSVTab { } } -unsafe impl VTab for CSVTab { +unsafe impl<'vtab> VTab<'vtab> for CSVTab { type Aux = (); - type Cursor = CSVTabCursor; + type Cursor = CSVTabCursor<'vtab>; fn connect( _: &mut VTabConnection, @@ -258,16 +259,16 @@ unsafe impl VTab for CSVTab { Ok(()) } - fn open(&self) -> Result<CSVTabCursor> { + fn open(&self) -> Result<CSVTabCursor<'_>> { Ok(CSVTabCursor::new(self.reader()?)) } } -impl CreateVTab for CSVTab {} +impl CreateVTab<'_> for CSVTab {} /// A cursor for the CSV virtual table #[repr(C)] -struct CSVTabCursor { +struct CSVTabCursor<'vtab> { /// Base class. Must be first base: ffi::sqlite3_vtab_cursor, /// The CSV reader object @@ -277,16 +278,18 @@ struct CSVTabCursor { /// Values of the current row cols: csv::StringRecord, eof: bool, + phantom: PhantomData<&'vtab CSVTab>, } -impl CSVTabCursor { - fn new(reader: csv::Reader<File>) -> CSVTabCursor { +impl CSVTabCursor<'_> { + fn new<'vtab>(reader: csv::Reader<File>) -> CSVTabCursor<'vtab> { CSVTabCursor { base: ffi::sqlite3_vtab_cursor::default(), reader, row_number: 0, cols: csv::StringRecord::new(), eof: false, + phantom: PhantomData, } } @@ -296,7 +299,7 @@ impl CSVTabCursor { } } -unsafe impl VTabCursor for CSVTabCursor { +unsafe impl VTabCursor for CSVTabCursor<'_> { // Only a full table scan is supported. So `filter` simply rewinds to // the beginning. fn filter( diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs index 5b47c65..dc3bda6 100644 --- a/src/vtab/mod.rs +++ b/src/vtab/mod.rs @@ -61,13 +61,13 @@ use crate::{str_to_cstring, Connection, Error, InnerConnection, Result}; /// /// (See [SQLite doc](https://sqlite.org/c3ref/module.html)) #[repr(transparent)] -pub struct Module<T: VTab> { +pub struct Module<'vtab, T: VTab<'vtab>> { base: ffi::sqlite3_module, - phantom: PhantomData<T>, + phantom: PhantomData<&'vtab T>, } -unsafe impl<T: VTab> Send for Module<T> {} -unsafe impl<T: VTab> Sync for Module<T> {} +unsafe impl<'vtab, T: VTab<'vtab>> Send for Module<'vtab, T> {} +unsafe impl<'vtab, T: VTab<'vtab>> Sync for Module<'vtab, T> {} union ModuleZeroHack { bytes: [u8; std::mem::size_of::<ffi::sqlite3_module>()], @@ -87,7 +87,7 @@ const ZERO_MODULE: ffi::sqlite3_module = unsafe { /// `feature = "vtab"` Create a read-only virtual table implementation. /// /// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations). -pub fn read_only_module<T: CreateVTab>() -> &'static Module<T> { +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. &Module { @@ -118,14 +118,14 @@ pub fn read_only_module<T: CreateVTab>() -> &'static Module<T> { xRollbackTo: None, ..ZERO_MODULE }, - phantom: PhantomData::<T>, + phantom: PhantomData::<&'vtab T>, } } /// `feature = "vtab"` Create an eponymous only virtual table implementation. /// /// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations). -pub fn eponymous_only_module<T: VTab>() -> &'static Module<T> { +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 @@ -157,7 +157,7 @@ pub fn eponymous_only_module<T: VTab>() -> &'static Module<T> { xRollbackTo: None, ..ZERO_MODULE }, - phantom: PhantomData::<T>, + phantom: PhantomData::<&'vtab T>, } } @@ -204,8 +204,10 @@ impl VTabConnection { /// ``` /// /// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html)) -pub unsafe trait VTab: Sized { +pub unsafe trait VTab<'vtab>: Sized { + /// Client data passed to `Connection::create_module`. type Aux; + /// Specific cursor implementation type Cursor: VTabCursor; /// Establish a new connection to an existing virtual table. @@ -223,13 +225,13 @@ pub unsafe trait 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(&self) -> Result<Self::Cursor>; + fn open(&'vtab self) -> Result<Self::Cursor>; } /// `feature = "vtab"` Non-eponymous virtual table instance trait. /// /// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html)) -pub trait CreateVTab: VTab { +pub trait CreateVTab<'vtab>: VTab<'vtab> { /// 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 @@ -256,8 +258,9 @@ pub trait CreateVTab: VTab { } /// `feature = "vtab"` Index constraint operator. +/// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details. #[derive(Debug, PartialEq)] -#[allow(non_snake_case, non_camel_case_types)] +#[allow(non_snake_case, non_camel_case_types, missing_docs)] #[non_exhaustive] pub enum IndexConstraintOp { SQLITE_INDEX_CONSTRAINT_EQ, @@ -329,6 +332,7 @@ impl IndexInfo { unsafe { (*self.0).nOrderBy as usize } } + /// Information about what parameters to pass to `VTabCursor.filter`. pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage<'_> { let constraint_usages = unsafe { slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize) @@ -495,6 +499,7 @@ pub unsafe trait VTabCursor: Sized { pub struct Context(*mut ffi::sqlite3_context); impl Context { + /// Set current cell value pub fn set_result<T: ToSql>(&mut self, value: &T) -> Result<()> { let t = value.to_sql()?; unsafe { set_result(self.0, &t) }; @@ -511,14 +516,17 @@ pub struct Values<'a> { } impl Values<'_> { + /// Returns the number of values. pub fn len(&self) -> usize { self.args.len() } + /// Returns `true` if there is no value. pub fn is_empty(&self) -> bool { self.args.is_empty() } + /// Returns value at `idx` pub fn get<T: FromSql>(&self, idx: usize) -> Result<T> { let arg = self.args[idx]; let value = unsafe { ValueRef::from_value(arg) }; @@ -542,7 +550,7 @@ impl Values<'_> { // `sqlite3_value_type` returns `SQLITE_NULL` for pointer. // So it seems not possible to enhance `ValueRef::from_value`. #[cfg(feature = "array")] - pub(crate) fn get_array(&self, idx: usize) -> Result<Option<array::Array>> { + fn get_array(&self, idx: usize) -> Result<Option<array::Array>> { use crate::types::Value; let arg = self.args[idx]; let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) }; @@ -558,6 +566,7 @@ impl Values<'_> { } } + /// Turns `Values` into an iterator. pub fn iter(&self) -> ValueIter<'_> { ValueIter { iter: self.args.iter(), @@ -574,6 +583,7 @@ impl<'a> IntoIterator for &'a Values<'a> { } } +/// `Values` iterator. pub struct ValueIter<'a> { iter: slice::Iter<'a, *mut ffi::sqlite3_value>, } @@ -597,10 +607,10 @@ impl Connection { /// /// Step 3 of [Creating New Virtual Table /// Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations). - pub fn create_module<T: VTab>( + pub fn create_module<'vtab, T: VTab<'vtab>>( &self, module_name: &str, - module: &'static Module<T>, + module: &'static Module<'vtab, T>, aux: Option<T::Aux>, ) -> Result<()> { self.db.borrow_mut().create_module(module_name, module, aux) @@ -608,10 +618,10 @@ impl Connection { } impl InnerConnection { - fn create_module<T: VTab>( + fn create_module<'vtab, T: VTab<'vtab>>( &mut self, module_name: &str, - module: &'static Module<T>, + module: &'static Module<'vtab, T>, aux: Option<T::Aux>, ) -> Result<()> { let c_name = str_to_cstring(module_name)?; @@ -693,7 +703,7 @@ unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) { let _: Box<T> = Box::from_raw(p as *mut T); } -unsafe extern "C" fn rust_create<T>( +unsafe extern "C" fn rust_create<'vtab, T>( db: *mut ffi::sqlite3, aux: *mut c_void, argc: c_int, @@ -702,7 +712,7 @@ unsafe extern "C" fn rust_create<T>( err_msg: *mut *mut c_char, ) -> c_int where - T: CreateVTab, + T: CreateVTab<'vtab>, { use std::ffi::CStr; @@ -745,7 +755,7 @@ where } } -unsafe extern "C" fn rust_connect<T>( +unsafe extern "C" fn rust_connect<'vtab, T>( db: *mut ffi::sqlite3, aux: *mut c_void, argc: c_int, @@ -754,7 +764,7 @@ unsafe extern "C" fn rust_connect<T>( err_msg: *mut *mut c_char, ) -> c_int where - T: VTab, + T: VTab<'vtab>, { use std::ffi::CStr; @@ -797,12 +807,12 @@ where } } -unsafe extern "C" fn rust_best_index<T>( +unsafe extern "C" fn rust_best_index<'vtab, T>( vtab: *mut ffi::sqlite3_vtab, info: *mut ffi::sqlite3_index_info, ) -> c_int where - T: VTab, + T: VTab<'vtab>, { let vt = vtab as *mut T; let mut idx_info = IndexInfo(info); @@ -821,9 +831,9 @@ where } } -unsafe extern "C" fn rust_disconnect<T>(vtab: *mut ffi::sqlite3_vtab) -> c_int +unsafe extern "C" fn rust_disconnect<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int where - T: VTab, + T: VTab<'vtab>, { if vtab.is_null() { return ffi::SQLITE_OK; @@ -833,9 +843,9 @@ where ffi::SQLITE_OK } -unsafe extern "C" fn rust_destroy<T>(vtab: *mut ffi::sqlite3_vtab) -> c_int +unsafe extern "C" fn rust_destroy<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int where - T: CreateVTab, + T: CreateVTab<'vtab>, { if vtab.is_null() { return ffi::SQLITE_OK; @@ -859,12 +869,12 @@ where } } -unsafe extern "C" fn rust_open<T>( +unsafe extern "C" fn rust_open<'vtab, T: 'vtab>( vtab: *mut ffi::sqlite3_vtab, pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor, ) -> c_int where - T: VTab, + T: VTab<'vtab>, { let vt = vtab as *mut T; match (*vt).open() { diff --git a/src/vtab/series.rs b/src/vtab/series.rs index dfc8e69..ed67f16 100644 --- a/src/vtab/series.rs +++ b/src/vtab/series.rs @@ -4,6 +4,7 @@ //! "function"](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/series.c): //! https://www.sqlite.org/series.html use std::default::Default; +use std::marker::PhantomData; use std::os::raw::c_int; use crate::ffi; @@ -49,9 +50,9 @@ struct SeriesTab { base: ffi::sqlite3_vtab, } -unsafe impl VTab for SeriesTab { +unsafe impl<'vtab> VTab<'vtab> for SeriesTab { type Aux = (); - type Cursor = SeriesTabCursor; + type Cursor = SeriesTabCursor<'vtab>; fn connect( _: &mut VTabConnection, @@ -151,15 +152,14 @@ unsafe impl VTab for SeriesTab { Ok(()) } - fn open(&self) -> Result<SeriesTabCursor> { + fn open(&self) -> Result<SeriesTabCursor<'_>> { Ok(SeriesTabCursor::new()) } } /// A cursor for the Series virtual table -#[derive(Default)] #[repr(C)] -struct SeriesTabCursor { +struct SeriesTabCursor<'vtab> { /// Base class. Must be first base: ffi::sqlite3_vtab_cursor, /// True to count down rather than up @@ -174,14 +174,24 @@ struct SeriesTabCursor { max_value: i64, /// Increment ("step") step: i64, + phantom: PhantomData<&'vtab SeriesTab>, } -impl SeriesTabCursor { - fn new() -> SeriesTabCursor { - SeriesTabCursor::default() +impl SeriesTabCursor<'_> { + fn new<'vtab>() -> SeriesTabCursor<'vtab> { + SeriesTabCursor { + base: ffi::sqlite3_vtab_cursor::default(), + is_desc: false, + row_id: 0, + value: 0, + min_value: 0, + max_value: 0, + step: 0, + phantom: PhantomData, + } } } -unsafe impl VTabCursor for SeriesTabCursor { +unsafe impl VTabCursor for SeriesTabCursor<'_> { fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values<'_>) -> Result<()> { let idx_num = QueryPlanFlags::from_bits_truncate(idx_num); let mut i = 0; diff --git a/tests/vtab.rs b/tests/vtab.rs index d42bac6..4b31574 100644 --- a/tests/vtab.rs +++ b/tests/vtab.rs @@ -9,6 +9,7 @@ fn test_dummy_module() { VTabConnection, VTabCursor, Values, }; use rusqlite::{version_number, Connection, Result}; + use std::marker::PhantomData; use std::os::raw::c_int; let module = eponymous_only_module::<DummyTab>(); @@ -19,9 +20,9 @@ fn test_dummy_module() { base: sqlite3_vtab, } - unsafe impl VTab for DummyTab { + unsafe impl<'vtab> VTab<'vtab> for DummyTab { type Aux = (); - type Cursor = DummyTabCursor; + type Cursor = DummyTabCursor<'vtab>; fn connect( _: &mut VTabConnection, @@ -39,21 +40,22 @@ fn test_dummy_module() { Ok(()) } - fn open(&self) -> Result<DummyTabCursor> { + fn open(&'vtab self) -> Result<DummyTabCursor<'vtab>> { Ok(DummyTabCursor::default()) } } #[derive(Default)] #[repr(C)] - struct DummyTabCursor { + struct DummyTabCursor<'vtab> { /// Base class. Must be first base: sqlite3_vtab_cursor, /// The rowid row_id: i64, + phantom: PhantomData<&'vtab DummyTab>, } - unsafe impl VTabCursor for DummyTabCursor { + unsafe impl VTabCursor for DummyTabCursor<'_> { fn filter( &mut self, _idx_num: c_int, |