Merge remote-tracking branch 'origin/master' into loadable_extension

This commit is contained in:
gwenn 2023-10-16 18:56:03 +02:00
commit 74c867d708
28 changed files with 6839 additions and 2913 deletions

View File

@ -49,7 +49,7 @@ jobs:
# The `{ sharedKey: ... }` allows different actions to share the cache. # The `{ sharedKey: ... }` allows different actions to share the cache.
# We're using a `fullBuild` key mostly as a "this needs to do the # We're using a `fullBuild` key mostly as a "this needs to do the
# complete" that needs to do the complete build (that is, including # complete" that needs to do the complete build (that is, including
# `--features 'bundled-full session buildtime_bindgen`), which is very # `--features 'bundled-full session buildtime_bindgen'`), which is very
# slow, and has several deps. # slow, and has several deps.
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: { sharedKey: fullBuild } with: { sharedKey: fullBuild }

View File

@ -78,6 +78,8 @@ column_decltype = []
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"] wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
# Note: doesn't support 32-bit. # Note: doesn't support 32-bit.
winsqlite3 = ["libsqlite3-sys/winsqlite3"] winsqlite3 = ["libsqlite3-sys/winsqlite3"]
# 3.23.0
serialize = ["modern_sqlite"]
# Helper feature for enabling most non-build-related optional features # Helper feature for enabling most non-build-related optional features
# or dependencies (except `session`). This is useful for running tests / clippy # or dependencies (except `session`). This is useful for running tests / clippy
@ -123,6 +125,7 @@ fallible-iterator = "0.3"
fallible-streaming-iterator = "0.1" fallible-streaming-iterator = "0.1"
uuid = { version = "1.0", optional = true } uuid = { version = "1.0", optional = true }
smallvec = "1.6.1" smallvec = "1.6.1"
rusqlite-macros = { path = "rusqlite-macros", version = "0.1.0", optional = true }
[dev-dependencies] [dev-dependencies]
doc-comment = "0.3" doc-comment = "0.3"
@ -167,7 +170,7 @@ name = "load_extension"
required-features = ["load_extension", "bundled", "functions", "vtab", "trace"] required-features = ["load_extension", "bundled", "functions", "vtab", "trace"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["modern-full"] features = ["modern-full", "rusqlite-macros"]
all-features = false all-features = false
no-default-features = true no-default-features = true
default-target = "x86_64-unknown-linux-gnu" default-target = "x86_64-unknown-linux-gnu"

View File

@ -15,7 +15,7 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
* Add DropBehavior::Panic to enforce intentional commit or rollback. * Add DropBehavior::Panic to enforce intentional commit or rollback.
* Implement `sqlite3_update_hook` (#260, #328), `sqlite3_commit_hook` and `sqlite3_rollback_hook`. * Implement `sqlite3_update_hook` (#260, #328), `sqlite3_commit_hook` and `sqlite3_rollback_hook`.
* Add support to unlock notification behind `unlock_notify` feature (#294, #331). * Add support to unlock notification behind `unlock_notify` feature (#294, #331).
* Make `Statement::column_index` case insensitive (#330). * Make `Statement::column_index` case-insensitive (#330).
* Add comment to justify `&mut Connection` in `Transaction`. * Add comment to justify `&mut Connection` in `Transaction`.
* Fix `tyvar_behind_raw_pointer` warnings. * Fix `tyvar_behind_raw_pointer` warnings.
* Fix handful of clippy warnings. * Fix handful of clippy warnings.
@ -29,7 +29,7 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
# Version 0.13.0 (2017-11-13) # Version 0.13.0 (2017-11-13)
* Added ToSqlConversionFailure case to Error enum. * Added ToSqlConversionFailure case to Error enum.
* Now depends on chrono 0.4, bitflats 1.0, and (optionally) cc 1.0 / bindgen 0.31. * Now depends on chrono 0.4, bitflags 1.0, and (optionally) cc 1.0 / bindgen 0.31.
* The ToSql/FromSql implementations for time::Timespec now include * The ToSql/FromSql implementations for time::Timespec now include
and expect fractional seconds and timezone in the serialized string. and expect fractional seconds and timezone in the serialized string.
* The RowIndex type used in Row::get is now publicly exported. * The RowIndex type used in Row::get is now publicly exported.
@ -61,18 +61,18 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
* Adds `version()` and `version_number()` functions for querying the version of SQLite in use. * Adds `version()` and `version_number()` functions for querying the version of SQLite in use.
* Adds the `limits` feature, exposing `limit()` and `set_limit()` methods on `Connection`. * Adds the `limits` feature, exposing `limit()` and `set_limit()` methods on `Connection`.
* Updates to `libsqlite3-sys` 0.7.0, which runs rust-bindgen at build-time instead of assuming the * Updates to `libsqlite3-sys` 0.7.0, which runs rust-bindgen at build-time instead of assuming the
precense of all expected SQLite constants and functions. presence of all expected SQLite constants and functions.
* Clarifies supported SQLite versions. Running with SQLite older than 3.6.8 now panics, and * Clarifies supported SQLite versions. Running with SQLite older than 3.6.8 now panics, and
some features will not compile unless a sufficiently-recent SQLite version is used. See some features will not compile unless a sufficiently-recent SQLite version is used. See
the README for requirements of particular features. the README for requirements of particular features.
* When running with SQLite 3.6.x, rusqlite attempts to perform SQLite initialization. If it fails, * When running with SQLite 3.6.x, rusqlite attempts to perform SQLite initialization. If it fails,
rusqlite will panic since it cannot ensure the threading mode for SQLite. This check can by rusqlite will panic since it cannot ensure the threading mode for SQLite. This check can be
skipped by calling the unsafe function `rusqlite::bypass_sqlite_initialization()`. This is skipped by calling the unsafe function `rusqlite::bypass_sqlite_initialization()`. This is
technically a breaking change but is unlikely to affect anyone in practice, since prior to this technically a breaking change but is unlikely to affect anyone in practice, since prior to this
version the check that rusqlite was using would cause a segfault if linked against a SQLite version the check that rusqlite was using would cause a segfault if linked against a SQLite
older than 3.7.0. older than 3.7.0.
* rusqlite now performs a one-time check (prior to the first connection attempt) that the runtime * rusqlite now performs a one-time check (prior to the first connection attempt) that the runtime
SQLite version is at least as new as the SQLite version found at buildtime. This check can by SQLite version is at least as new as the SQLite version found at buildtime. This check can be
skipped by calling the unsafe function `rusqlite::bypass_sqlite_version_check()`. skipped by calling the unsafe function `rusqlite::bypass_sqlite_version_check()`.
* Removes the `libc` dependency in favor of using `std::os::raw` * Removes the `libc` dependency in favor of using `std::os::raw`
@ -137,7 +137,7 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
This behavior is more correct. Previously there were runtime checks to prevent misuse, but This behavior is more correct. Previously there were runtime checks to prevent misuse, but
other changes in this release to reset statements as soon as possible introduced yet another other changes in this release to reset statements as soon as possible introduced yet another
hazard related to the lack of these lifetime connections. We were already recommending the hazard related to the lack of these lifetime connections. We were already recommending the
use of `query_map` and `query_and_then` over raw `query`; both of theose still return handles use of `query_map` and `query_and_then` over raw `query`; both of those still return handles
that implement `Iterator`. that implement `Iterator`.
* BREAKING CHANGE: `Transaction::savepoint()` now returns a `Savepoint` instead of another * BREAKING CHANGE: `Transaction::savepoint()` now returns a `Savepoint` instead of another
`Transaction`. Unlike `Transaction`, `Savepoint`s can be rolled back while keeping the current `Transaction`. Unlike `Transaction`, `Savepoint`s can be rolled back while keeping the current
@ -239,7 +239,7 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
* Add `column_names()` to `SqliteStatement`. * Add `column_names()` to `SqliteStatement`.
* By default, include `SQLITE_OPEN_NO_MUTEX` and `SQLITE_OPEN_URI` flags when opening a * By default, include `SQLITE_OPEN_NO_MUTEX` and `SQLITE_OPEN_URI` flags when opening a
new conneciton. new connection.
* Fix generated bindings (e.g., `sqlite3_exec` was wrong). * Fix generated bindings (e.g., `sqlite3_exec` was wrong).
* Use now-generated `sqlite3_destructor_type` to define `SQLITE_STATIC` and `SQLITE_TRANSIENT`. * Use now-generated `sqlite3_destructor_type` to define `SQLITE_STATIC` and `SQLITE_TRANSIENT`.

View File

@ -113,8 +113,8 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the 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). `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) * `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 and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for various
`time::OffsetDateTime` type from the [`time` crate](https://crates.io/crates/time). types from the [`time` crate](https://crates.io/crates/time).
* `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) * `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 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). `Url` type from the [`url` crate](https://crates.io/crates/url).

View File

@ -46,7 +46,7 @@ openssl-sys = { version = "0.9", optional = true }
atomic = { version = "0.5", optional = true } atomic = { version = "0.5", optional = true }
[build-dependencies] [build-dependencies]
bindgen = { version = "0.66", optional = true, default-features = false, features = ["runtime"] } bindgen = { version = "0.68", optional = true, default-features = false, features = ["runtime"] }
pkg-config = { version = "0.3.19", optional = true } pkg-config = { version = "0.3.19", optional = true }
cc = { version = "1.0", optional = true } cc = { version = "1.0", optional = true }
vcpkg = { version = "0.2", optional = true } vcpkg = { version = "0.2", optional = true }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()]. ** [sqlite_version()] and [sqlite_source_id()].
*/ */
#define SQLITE_VERSION "3.42.0" #define SQLITE_VERSION "3.43.2"
#define SQLITE_VERSION_NUMBER 3042000 #define SQLITE_VERSION_NUMBER 3043002
#define SQLITE_SOURCE_ID "2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf350b0" #define SQLITE_SOURCE_ID "2023-10-10 12:14:04 4310099cce5a487035fa535dd3002c59ac7f1d1bec68d7cf317fd3e769484790"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** CAPI3REF: Run-Time Library Version Numbers
@ -528,6 +528,7 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8))
#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8))
#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8))
#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
@ -1190,7 +1191,7 @@ struct sqlite3_io_methods {
** by clients within the current process, only within other processes. ** by clients within the current process, only within other processes.
** **
** <li>[[SQLITE_FCNTL_CKSM_FILE]] ** <li>[[SQLITE_FCNTL_CKSM_FILE]]
** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use interally by the ** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use internally by the
** [checksum VFS shim] only. ** [checksum VFS shim] only.
** **
** <li>[[SQLITE_FCNTL_RESET_CACHE]] ** <li>[[SQLITE_FCNTL_RESET_CACHE]]
@ -2454,7 +2455,7 @@ struct sqlite3_mem_methods {
** the [VACUUM] command will fail with an obscure error when attempting to ** the [VACUUM] command will fail with an obscure error when attempting to
** process a table with generated columns and a descending index. This is ** process a table with generated columns and a descending index. This is
** not considered a bug since SQLite versions 3.3.0 and earlier do not support ** not considered a bug since SQLite versions 3.3.0 and earlier do not support
** either generated columns or decending indexes. ** either generated columns or descending indexes.
** </dd> ** </dd>
** **
** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] ** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]]
@ -2735,6 +2736,7 @@ SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*);
** **
** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether ** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether
** or not an interrupt is currently in effect for [database connection] D. ** or not an interrupt is currently in effect for [database connection] D.
** It returns 1 if an interrupt is currently in effect, or 0 otherwise.
*/ */
SQLITE_API void sqlite3_interrupt(sqlite3*); SQLITE_API void sqlite3_interrupt(sqlite3*);
SQLITE_API int sqlite3_is_interrupted(sqlite3*); SQLITE_API int sqlite3_is_interrupted(sqlite3*);
@ -3388,8 +3390,10 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
** M argument should be the bitwise OR-ed combination of ** M argument should be the bitwise OR-ed combination of
** zero or more [SQLITE_TRACE] constants. ** zero or more [SQLITE_TRACE] constants.
** **
** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides ** ^Each call to either sqlite3_trace(D,X,P) or sqlite3_trace_v2(D,M,X,P)
** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2(). ** overrides (cancels) all prior calls to sqlite3_trace(D,X,P) or
** sqlite3_trace_v2(D,M,X,P) for the [database connection] D. Each
** database connection may have at most one trace callback.
** **
** ^The X callback is invoked whenever any of the events identified by ** ^The X callback is invoked whenever any of the events identified by
** mask M occur. ^The integer return value from the callback is currently ** mask M occur. ^The integer return value from the callback is currently
@ -3758,7 +3762,7 @@ SQLITE_API int sqlite3_open_v2(
** as F) must be one of: ** as F) must be one of:
** <ul> ** <ul>
** <li> A database filename pointer created by the SQLite core and ** <li> A database filename pointer created by the SQLite core and
** passed into the xOpen() method of a VFS implemention, or ** passed into the xOpen() method of a VFS implementation, or
** <li> A filename obtained from [sqlite3_db_filename()], or ** <li> A filename obtained from [sqlite3_db_filename()], or
** <li> A new filename constructed using [sqlite3_create_filename()]. ** <li> A new filename constructed using [sqlite3_create_filename()].
** </ul> ** </ul>
@ -3871,7 +3875,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
/* /*
** CAPI3REF: Create and Destroy VFS Filenames ** CAPI3REF: Create and Destroy VFS Filenames
** **
** These interfces are provided for use by [VFS shim] implementations and ** These interfaces are provided for use by [VFS shim] implementations and
** are not useful outside of that context. ** are not useful outside of that context.
** **
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of ** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
@ -4418,6 +4422,41 @@ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
*/ */
SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt); SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt);
/*
** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement
** METHOD: sqlite3_stmt
**
** The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN
** setting for [prepared statement] S. If E is zero, then S becomes
** a normal prepared statement. If E is 1, then S behaves as if
** its SQL text began with "[EXPLAIN]". If E is 2, then S behaves as if
** its SQL text began with "[EXPLAIN QUERY PLAN]".
**
** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared.
** SQLite tries to avoid a reprepare, but a reprepare might be necessary
** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode.
**
** Because of the potential need to reprepare, a call to
** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be
** reprepared because it was created using [sqlite3_prepare()] instead of
** the newer [sqlite3_prepare_v2()] or [sqlite3_prepare_v3()] interfaces and
** hence has no saved SQL text with which to reprepare.
**
** Changing the explain setting for a prepared statement does not change
** the original SQL text for the statement. Hence, if the SQL text originally
** began with EXPLAIN or EXPLAIN QUERY PLAN, but sqlite3_stmt_explain(S,0)
** is called to convert the statement into an ordinary statement, the EXPLAIN
** or EXPLAIN QUERY PLAN keywords will still appear in the sqlite3_sql(S)
** output, even though the statement now acts like a normal SQL statement.
**
** This routine returns SQLITE_OK if the explain mode is successfully
** changed, or an error code if the explain mode could not be changed.
** The explain mode cannot be changed while a statement is active.
** Hence, it is good practice to call [sqlite3_reset(S)]
** immediately prior to calling sqlite3_stmt_explain(S,E).
*/
SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode);
/* /*
** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** CAPI3REF: Determine If A Prepared Statement Has Been Reset
** METHOD: sqlite3_stmt ** METHOD: sqlite3_stmt
@ -4581,7 +4620,7 @@ typedef struct sqlite3_context sqlite3_context;
** with it may be passed. ^It is called to dispose of the BLOB or string even ** with it may be passed. ^It is called to dispose of the BLOB or string even
** if the call to the bind API fails, except the destructor is not called if ** if the call to the bind API fails, except the destructor is not called if
** the third parameter is a NULL pointer or the fourth parameter is negative. ** the third parameter is a NULL pointer or the fourth parameter is negative.
** ^ (2) The special constant, [SQLITE_STATIC], may be passsed to indicate that ** ^ (2) The special constant, [SQLITE_STATIC], may be passed to indicate that
** the application remains responsible for disposing of the object. ^In this ** the application remains responsible for disposing of the object. ^In this
** case, the object and the provided pointer to it must remain valid until ** case, the object and the provided pointer to it must remain valid until
** either the prepared statement is finalized or the same SQL parameter is ** either the prepared statement is finalized or the same SQL parameter is
@ -5260,14 +5299,26 @@ SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt);
** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S ** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S
** back to the beginning of its program. ** back to the beginning of its program.
** **
** ^If the most recent call to [sqlite3_step(S)] for the ** ^The return code from [sqlite3_reset(S)] indicates whether or not
** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE], ** the previous evaluation of prepared statement S completed successfully.
** or if [sqlite3_step(S)] has never before been called on S, ** ^If [sqlite3_step(S)] has never before been called on S or if
** then [sqlite3_reset(S)] returns [SQLITE_OK]. ** [sqlite3_step(S)] has not been called since the previous call
** to [sqlite3_reset(S)], then [sqlite3_reset(S)] will return
** [SQLITE_OK].
** **
** ^If the most recent call to [sqlite3_step(S)] for the ** ^If the most recent call to [sqlite3_step(S)] for the
** [prepared statement] S indicated an error, then ** [prepared statement] S indicated an error, then
** [sqlite3_reset(S)] returns an appropriate [error code]. ** [sqlite3_reset(S)] returns an appropriate [error code].
** ^The [sqlite3_reset(S)] interface might also return an [error code]
** if there were no prior errors but the process of resetting
** the prepared statement caused a new error. ^For example, if an
** [INSERT] statement with a [RETURNING] clause is only stepped one time,
** that one call to [sqlite3_step(S)] might return SQLITE_ROW but
** the overall statement might still fail and the [sqlite3_reset(S)] call
** might return SQLITE_BUSY if locking constraints prevent the
** database change from committing. Therefore, it is important that
** applications check the return code from [sqlite3_reset(S)] even if
** no prior call to [sqlite3_step(S)] indicated a problem.
** **
** ^The [sqlite3_reset(S)] interface does not change the values ** ^The [sqlite3_reset(S)] interface does not change the values
** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. ** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S.
@ -5484,7 +5535,7 @@ SQLITE_API int sqlite3_create_window_function(
** [application-defined SQL function] ** [application-defined SQL function]
** that has side-effects or that could potentially leak sensitive information. ** that has side-effects or that could potentially leak sensitive information.
** This will prevent attacks in which an application is tricked ** This will prevent attacks in which an application is tricked
** into using a database file that has had its schema surreptiously ** into using a database file that has had its schema surreptitiously
** modified to invoke the application-defined function in ways that are ** modified to invoke the application-defined function in ways that are
** harmful. ** harmful.
** <p> ** <p>
@ -8161,7 +8212,8 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_TRACEFLAGS 31 #define SQLITE_TESTCTRL_TRACEFLAGS 31
#define SQLITE_TESTCTRL_TUNE 32 #define SQLITE_TESTCTRL_TUNE 32
#define SQLITE_TESTCTRL_LOGEST 33 #define SQLITE_TESTCTRL_LOGEST 33
#define SQLITE_TESTCTRL_LAST 33 /* Largest TESTCTRL */ #define SQLITE_TESTCTRL_USELONGDOUBLE 34
#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */
/* /*
** CAPI3REF: SQL Keyword Checking ** CAPI3REF: SQL Keyword Checking
@ -9193,8 +9245,8 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
** blocked connection already has a registered unlock-notify callback, ** blocked connection already has a registered unlock-notify callback,
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
** called with a NULL pointer as its second argument, then any existing ** called with a NULL pointer as its second argument, then any existing
** unlock-notify callback is canceled. ^The blocked connections ** unlock-notify callback is cancelled. ^The blocked connections
** unlock-notify callback may also be canceled by closing the blocked ** unlock-notify callback may also be cancelled by closing the blocked
** connection using [sqlite3_close()]. ** connection using [sqlite3_close()].
** **
** The unlock-notify callback is not reentrant. If an application invokes ** The unlock-notify callback is not reentrant. If an application invokes
@ -9617,7 +9669,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
** [[SQLITE_VTAB_DIRECTONLY]]<dt>SQLITE_VTAB_DIRECTONLY</dt> ** [[SQLITE_VTAB_DIRECTONLY]]<dt>SQLITE_VTAB_DIRECTONLY</dt>
** <dd>Calls of the form ** <dd>Calls of the form
** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the ** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the
** the [xConnect] or [xCreate] methods of a [virtual table] implmentation ** the [xConnect] or [xCreate] methods of a [virtual table] implementation
** prohibits that virtual table from being used from within triggers and ** prohibits that virtual table from being used from within triggers and
** views. ** views.
** </dd> ** </dd>
@ -9807,7 +9859,7 @@ SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*);
** communicated to the xBestIndex method as a ** communicated to the xBestIndex method as a
** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use ** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use
** this constraint, it must set the corresponding ** this constraint, it must set the corresponding
** aConstraintUsage[].argvIndex to a postive integer. ^(Then, under ** aConstraintUsage[].argvIndex to a positive integer. ^(Then, under
** the usual mode of handling IN operators, SQLite generates [bytecode] ** the usual mode of handling IN operators, SQLite generates [bytecode]
** that invokes the [xFilter|xFilter() method] once for each value ** that invokes the [xFilter|xFilter() method] once for each value
** on the right-hand side of the IN operator.)^ Thus the virtual table ** on the right-hand side of the IN operator.)^ Thus the virtual table
@ -10236,7 +10288,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** When the [sqlite3_blob_write()] API is used to update a blob column, ** When the [sqlite3_blob_write()] API is used to update a blob column,
** the pre-update hook is invoked with SQLITE_DELETE. This is because the ** the pre-update hook is invoked with SQLITE_DELETE. This is because the
** in this case the new values are not available. In this case, when a ** in this case the new values are not available. In this case, when a
** callback made with op==SQLITE_DELETE is actuall a write using the ** callback made with op==SQLITE_DELETE is actually a write using the
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns ** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
** the index of the column being written. In other cases, where the ** the index of the column being written. In other cases, where the
** pre-update hook is being invoked for some other reason, including a ** pre-update hook is being invoked for some other reason, including a
@ -12754,7 +12806,7 @@ struct Fts5PhraseIter {
** See xPhraseFirstColumn above. ** See xPhraseFirstColumn above.
*/ */
struct Fts5ExtensionApi { struct Fts5ExtensionApi {
int iVersion; /* Currently always set to 3 */ int iVersion; /* Currently always set to 2 */
void *(*xUserData)(Fts5Context*); void *(*xUserData)(Fts5Context*);
@ -12983,8 +13035,8 @@ struct Fts5ExtensionApi {
** as separate queries of the FTS index are required for each synonym. ** as separate queries of the FTS index are required for each synonym.
** **
** When using methods (2) or (3), it is important that the tokenizer only ** When using methods (2) or (3), it is important that the tokenizer only
** provide synonyms when tokenizing document text (method (2)) or query ** provide synonyms when tokenizing document text (method (3)) or query
** text (method (3)), not both. Doing so will not cause any errors, but is ** text (method (2)), not both. Doing so will not cause any errors, but is
** inefficient. ** inefficient.
*/ */
typedef struct Fts5Tokenizer Fts5Tokenizer; typedef struct Fts5Tokenizer Fts5Tokenizer;
@ -13032,7 +13084,7 @@ struct fts5_api {
int (*xCreateTokenizer)( int (*xCreateTokenizer)(
fts5_api *pApi, fts5_api *pApi,
const char *zName, const char *zName,
void *pContext, void *pUserData,
fts5_tokenizer *pTokenizer, fts5_tokenizer *pTokenizer,
void (*xDestroy)(void*) void (*xDestroy)(void*)
); );
@ -13041,7 +13093,7 @@ struct fts5_api {
int (*xFindTokenizer)( int (*xFindTokenizer)(
fts5_api *pApi, fts5_api *pApi,
const char *zName, const char *zName,
void **ppContext, void **ppUserData,
fts5_tokenizer *pTokenizer fts5_tokenizer *pTokenizer
); );
@ -13049,7 +13101,7 @@ struct fts5_api {
int (*xCreateFunction)( int (*xCreateFunction)(
fts5_api *pApi, fts5_api *pApi,
const char *zName, const char *zName,
void *pContext, void *pUserData,
fts5_extension_function xFunction, fts5_extension_function xFunction,
void (*xDestroy)(void*) void (*xDestroy)(void*)
); );

View File

@ -361,6 +361,8 @@ struct sqlite3_api_routines {
int (*value_encoding)(sqlite3_value*); int (*value_encoding)(sqlite3_value*);
/* Version 3.41.0 and later */ /* Version 3.41.0 and later */
int (*is_interrupted)(sqlite3*); int (*is_interrupted)(sqlite3*);
/* Version 3.43.0 and later */
int (*stmt_explain)(sqlite3_stmt*,int);
}; };
/* /*
@ -689,6 +691,8 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_value_encoding sqlite3_api->value_encoding #define sqlite3_value_encoding sqlite3_api->value_encoding
/* Version 3.41.0 and later */ /* Version 3.41.0 and later */
#define sqlite3_is_interrupted sqlite3_api->is_interrupted #define sqlite3_is_interrupted sqlite3_api->is_interrupted
/* Version 3.43.0 and later */
#define sqlite3_stmt_explain sqlite3_api->stmt_explain
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

View File

@ -9,7 +9,7 @@ export SQLITE3_LIB_DIR="$SCRIPT_DIR/sqlite3"
mkdir -p "$TARGET_DIR" "$SQLITE3_LIB_DIR" mkdir -p "$TARGET_DIR" "$SQLITE3_LIB_DIR"
# Download and extract amalgamation # Download and extract amalgamation
SQLITE=sqlite-amalgamation-3420000 SQLITE=sqlite-amalgamation-3430200
curl -O https://sqlite.org/2023/$SQLITE.zip curl -O https://sqlite.org/2023/$SQLITE.zip
unzip -p "$SQLITE.zip" "$SQLITE/sqlite3.c" > "$SQLITE3_LIB_DIR/sqlite3.c" unzip -p "$SQLITE.zip" "$SQLITE/sqlite3.c" > "$SQLITE3_LIB_DIR/sqlite3.c"
unzip -p "$SQLITE.zip" "$SQLITE/sqlite3.h" > "$SQLITE3_LIB_DIR/sqlite3.h" unzip -p "$SQLITE.zip" "$SQLITE/sqlite3.h" > "$SQLITE3_LIB_DIR/sqlite3.h"

View File

@ -0,0 +1,16 @@
[package]
name = "rusqlite-macros"
version = "0.1.0"
authors = ["The rusqlite developers"]
edition = "2021"
description = "Private implementation detail of rusqlite crate"
repository = "https://github.com/rusqlite/rusqlite"
license = "MIT"
categories = ["database"]
[lib]
proc-macro = true
[dependencies]
sqlite3-parser = { version = "0.11", default-features = false, features = ["YYNOERRORRECOVERY"] }
fallible-iterator = "0.3"

115
rusqlite-macros/src/lib.rs Normal file
View File

@ -0,0 +1,115 @@
//! Private implementation details of `rusqlite`.
use proc_macro::{Delimiter, Group, Literal, Span, TokenStream, TokenTree};
use fallible_iterator::FallibleIterator;
use sqlite3_parser::ast::{ParameterInfo, ToTokens};
use sqlite3_parser::lexer::sql::Parser;
// https://internals.rust-lang.org/t/custom-error-diagnostics-with-procedural-macros-on-almost-stable-rust/8113
#[doc(hidden)]
#[proc_macro]
pub fn __bind(input: TokenStream) -> TokenStream {
try_bind(input).unwrap_or_else(|msg| parse_ts(&format!("compile_error!({:?})", msg)))
}
type Result<T> = std::result::Result<T, String>;
fn try_bind(input: TokenStream) -> Result<TokenStream> {
let (stmt, literal) = {
let mut iter = input.clone().into_iter();
let stmt = iter.next().unwrap();
let literal = iter.next().unwrap();
assert!(iter.next().is_none());
(stmt, literal)
};
let literal = match into_literal(&literal) {
Some(it) => it,
None => return Err("expected a plain string literal".to_string()),
};
let sql = literal.to_string();
if !sql.starts_with('"') {
return Err("expected a plain string literal".to_string());
}
let sql = strip_matches(&sql, "\"");
let mut parser = Parser::new(sql.as_bytes());
let ast = match parser.next() {
Ok(None) => return Err("Invalid input".to_owned()),
Err(err) => {
return Err(err.to_string());
}
Ok(Some(ast)) => ast,
};
let mut info = ParameterInfo::default();
if let Err(err) = ast.to_tokens(&mut info) {
return Err(err.to_string());
}
if info.count == 0 {
return Ok(input);
}
if info.count as usize != info.names.len() {
return Err("Mixing named and numbered parameters is not supported.".to_string());
}
let call_site = literal.span();
let mut res = TokenStream::new();
for (i, name) in info.names.iter().enumerate() {
res.extend(Some(stmt.clone()));
res.extend(respan(
parse_ts(&format!(
".raw_bind_parameter({}, &{})?;",
i + 1,
&name[1..]
)),
call_site,
));
}
Ok(res)
}
fn into_literal(ts: &TokenTree) -> Option<Literal> {
match ts {
TokenTree::Literal(l) => Some(l.clone()),
TokenTree::Group(g) => match g.delimiter() {
Delimiter::None => match g.stream().into_iter().collect::<Vec<_>>().as_slice() {
[TokenTree::Literal(l)] => Some(l.clone()),
_ => None,
},
Delimiter::Parenthesis | Delimiter::Brace | Delimiter::Bracket => None,
},
_ => None,
}
}
fn strip_matches<'a>(s: &'a str, pattern: &str) -> &'a str {
s.strip_prefix(pattern)
.unwrap_or(s)
.strip_suffix(pattern)
.unwrap_or(s)
}
fn respan(ts: TokenStream, span: Span) -> TokenStream {
let mut res = TokenStream::new();
for tt in ts {
let tt = match tt {
TokenTree::Ident(mut ident) => {
ident.set_span(ident.span().resolved_at(span).located_at(span));
TokenTree::Ident(ident)
}
TokenTree::Group(group) => {
TokenTree::Group(Group::new(group.delimiter(), respan(group.stream(), span)))
}
_ => tt,
};
res.extend(Some(tt))
}
res
}
fn parse_ts(s: &str) -> TokenStream {
s.parse().unwrap()
}

View File

@ -0,0 +1,36 @@
use rusqlite_macros::__bind;
type Result = std::result::Result<(), String>;
struct Stmt;
impl Stmt {
pub fn raw_bind_parameter(&mut self, one_based_col_index: usize, param: &str) -> Result {
let (..) = (one_based_col_index, param);
Ok(())
}
}
#[test]
fn test_literal() -> Result {
let first_name = "El";
let last_name = "Barto";
let mut stmt = Stmt;
__bind!(stmt "SELECT $first_name, $last_name");
Ok(())
}
/* FIXME
#[test]
fn test_raw_string() {
let stmt = ();
__bind!(stmt r#"SELECT 1"#);
}
#[test]
fn test_const() {
const SQL: &str = "SELECT 1";
let stmt = ();
__bind!(stmt SQL);
}
*/

View File

@ -3,12 +3,16 @@ use std::str;
use crate::{Error, Result, Statement}; use crate::{Error, Result, Statement};
/// Information about a column of a SQLite query. /// Information about a column of a SQLite query.
#[cfg(feature = "column_decltype")]
#[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
#[derive(Debug)] #[derive(Debug)]
pub struct Column<'stmt> { pub struct Column<'stmt> {
name: &'stmt str, name: &'stmt str,
decl_type: Option<&'stmt str>, decl_type: Option<&'stmt str>,
} }
#[cfg(feature = "column_decltype")]
#[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
impl Column<'_> { impl Column<'_> {
/// Returns the name of the column. /// Returns the name of the column.
#[inline] #[inline]
@ -90,6 +94,8 @@ impl Statement<'_> {
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
/// column range for this row. /// column range for this row.
/// ///
/// # Panics
///
/// Panics when column name is not valid UTF-8. /// Panics when column name is not valid UTF-8.
#[inline] #[inline]
pub fn column_name(&self, col: usize) -> Result<&str> { pub fn column_name(&self, col: usize) -> Result<&str> {

View File

@ -385,6 +385,7 @@ impl error::Error for Error {
impl Error { impl Error {
/// Returns the underlying SQLite error if this is [`Error::SqliteFailure`]. /// Returns the underlying SQLite error if this is [`Error::SqliteFailure`].
#[inline] #[inline]
#[must_use]
pub fn sqlite_error(&self) -> Option<&ffi::Error> { pub fn sqlite_error(&self) -> Option<&ffi::Error> {
match self { match self {
Self::SqliteFailure(error, _) => Some(error), Self::SqliteFailure(error, _) => Some(error),
@ -395,6 +396,7 @@ impl Error {
/// Returns the underlying SQLite error code if this is /// Returns the underlying SQLite error code if this is
/// [`Error::SqliteFailure`]. /// [`Error::SqliteFailure`].
#[inline] #[inline]
#[must_use]
pub fn sqlite_error_code(&self) -> Option<ffi::ErrorCode> { pub fn sqlite_error_code(&self) -> Option<ffi::ErrorCode> {
self.sqlite_error().map(|error| error.code) self.sqlite_error().map(|error| error.code)
} }
@ -404,7 +406,6 @@ impl Error {
#[cold] #[cold]
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error { 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) Error::SqliteFailure(ffi::Error::new(code), message)
} }
@ -458,10 +459,7 @@ pub fn check(code: c_int) -> Result<()> {
/// Transform Rust error to SQLite error (message and code). /// Transform Rust error to SQLite error (message and code).
/// # Safety /// # Safety
/// This function is unsafe because it uses raw pointer /// This function is unsafe because it uses raw pointer
pub unsafe fn to_sqlite_error( pub unsafe fn to_sqlite_error(e: &Error, err_msg: *mut *mut std::os::raw::c_char) -> c_int {
e: &Error,
err_msg: *mut *mut std::os::raw::c_char,
) -> std::os::raw::c_int {
use crate::util::alloc; use crate::util::alloc;
match e { match e {
Error::SqliteFailure(err, s) => { Error::SqliteFailure(err, s) => {

View File

@ -272,11 +272,11 @@ where
/// call to [`step()`](Aggregate::step) to set up the context for an /// call to [`step()`](Aggregate::step) to set up the context for an
/// invocation of the function. (Note: `init()` will not be called if /// invocation of the function. (Note: `init()` will not be called if
/// there are no rows.) /// there are no rows.)
fn init(&self, _: &mut Context<'_>) -> Result<A>; fn init(&self, ctx: &mut Context<'_>) -> Result<A>;
/// "step" function called once for each row in an aggregate group. May be /// "step" function called once for each row in an aggregate group. May be
/// called 0 times if there are no rows. /// called 0 times if there are no rows.
fn step(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>; fn step(&self, ctx: &mut Context<'_>, acc: &mut A) -> Result<()>;
/// Computes and returns the final result. Will be called exactly once for /// Computes and returns the final result. Will be called exactly once for
/// each invocation of the function. If [`step()`](Aggregate::step) was /// each invocation of the function. If [`step()`](Aggregate::step) was
@ -287,7 +287,7 @@ where
/// given `None`. /// given `None`.
/// ///
/// The passed context will have no arguments. /// The passed context will have no arguments.
fn finalize(&self, _: &mut Context<'_>, _: Option<A>) -> Result<T>; fn finalize(&self, ctx: &mut Context<'_>, acc: Option<A>) -> Result<T>;
} }
/// `WindowAggregate` is the callback interface for /// `WindowAggregate` is the callback interface for
@ -301,10 +301,10 @@ where
{ {
/// Returns the current value of the aggregate. Unlike xFinal, the /// Returns the current value of the aggregate. Unlike xFinal, the
/// implementation should not delete any context. /// implementation should not delete any context.
fn value(&self, _: Option<&A>) -> Result<T>; fn value(&self, acc: Option<&A>) -> Result<T>;
/// Removes a row from the current window. /// Removes a row from the current window.
fn inverse(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>; fn inverse(&self, ctx: &mut Context<'_>, acc: &mut A) -> Result<()>;
} }
bitflags::bitflags! { bitflags::bitflags! {
@ -638,6 +638,7 @@ unsafe extern "C" fn call_boxed_step<A, D, T>(
args: slice::from_raw_parts(argv, argc as usize), args: slice::from_raw_parts(argv, argc as usize),
}; };
#[allow(clippy::unnecessary_cast)]
if (*pac as *mut A).is_null() { if (*pac as *mut A).is_null() {
*pac = Box::into_raw(Box::new((*boxed_aggr).init(&mut ctx)?)); *pac = Box::into_raw(Box::new((*boxed_aggr).init(&mut ctx)?));
} }
@ -708,7 +709,9 @@ where
// Within the xFinal callback, it is customary to set N=0 in calls to // Within the xFinal callback, it is customary to set N=0 in calls to
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur. // sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
let a: Option<A> = match aggregate_context(ctx, 0) { let a: Option<A> = match aggregate_context(ctx, 0) {
Some(pac) => { Some(pac) =>
{
#[allow(clippy::unnecessary_cast)]
if (*pac as *mut A).is_null() { if (*pac as *mut A).is_null() {
None None
} else { } else {
@ -753,7 +756,9 @@ where
// Within the xValue callback, it is customary to set N=0 in calls to // Within the xValue callback, it is customary to set N=0 in calls to
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur. // sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
let a: Option<&A> = match aggregate_context(ctx, 0) { let a: Option<&A> = match aggregate_context(ctx, 0) {
Some(pac) => { Some(pac) =>
{
#[allow(clippy::unnecessary_cast)]
if (*pac as *mut A).is_null() { if (*pac as *mut A).is_null() {
None None
} else { } else {
@ -839,7 +844,7 @@ mod test {
// This implementation of a regexp scalar function uses SQLite's auxiliary data // This implementation of a regexp scalar function uses SQLite's auxiliary data
// (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular // (https://www.sqlite.org/c3ref/get_auxdata.html) to avoid recompiling the regular
// expression multiple times within one query. // expression multiple times within one query.
fn regexp_with_auxilliary(ctx: &Context<'_>) -> Result<bool> { fn regexp_with_auxiliary(ctx: &Context<'_>) -> Result<bool> {
assert_eq!(ctx.len(), 2, "called with unexpected number of arguments"); assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>; type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
let regexp: std::sync::Arc<Regex> = ctx let regexp: std::sync::Arc<Regex> = ctx
@ -860,7 +865,7 @@ mod test {
} }
#[test] #[test]
fn test_function_regexp_with_auxilliary() -> Result<()> { fn test_function_regexp_with_auxiliary() -> Result<()> {
let db = Connection::open_in_memory()?; let db = Connection::open_in_memory()?;
db.execute_batch( db.execute_batch(
"BEGIN; "BEGIN;
@ -874,7 +879,7 @@ mod test {
"regexp", "regexp",
2, 2,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
regexp_with_auxilliary, regexp_with_auxiliary,
)?; )?;
let result: bool = db.one_column("SELECT regexp('l.s[aeiouy]', 'lisa')")?; let result: bool = db.one_column("SELECT regexp('l.s[aeiouy]', 'lisa')")?;

View File

@ -776,9 +776,10 @@ mod test {
.unwrap(); .unwrap();
let authorizer = move |ctx: AuthContext<'_>| match ctx.action { let authorizer = move |ctx: AuthContext<'_>| match ctx.action {
AuthAction::Read { column_name, .. } if column_name == "private" => { AuthAction::Read {
Authorization::Ignore column_name: "private",
} ..
} => Authorization::Ignore,
AuthAction::DropTable { .. } => Authorization::Deny, AuthAction::DropTable { .. } => Authorization::Deny,
AuthAction::Pragma { .. } => panic!("shouldn't be called"), AuthAction::Pragma { .. } => panic!("shouldn't be called"),
_ => Authorization::Allow, _ => Authorization::Allow,

View File

@ -40,7 +40,7 @@ pub struct InnerConnection {
unsafe impl Send for InnerConnection {} unsafe impl Send for InnerConnection {}
impl InnerConnection { impl InnerConnection {
#[allow(clippy::mutex_atomic)] #[allow(clippy::mutex_atomic, clippy::arc_with_non_send_sync)] // See unsafe impl Send / Sync for InterruptHandle
#[inline] #[inline]
pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection { pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
InnerConnection { InnerConnection {

View File

@ -74,6 +74,7 @@ use crate::raw_statement::RawStatement;
use crate::types::ValueRef; use crate::types::ValueRef;
pub use crate::cache::CachedStatement; pub use crate::cache::CachedStatement;
#[cfg(feature = "column_decltype")]
pub use crate::column::Column; pub use crate::column::Column;
pub use crate::error::{to_sqlite_error, Error}; pub use crate::error::{to_sqlite_error, Error};
pub use crate::ffi::ErrorCode; pub use crate::ffi::ErrorCode;
@ -82,9 +83,14 @@ pub use crate::load_extension_guard::LoadExtensionGuard;
pub use crate::params::{params_from_iter, Params, ParamsFromIter}; pub use crate::params::{params_from_iter, Params, ParamsFromIter};
pub use crate::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows}; pub use crate::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows};
pub use crate::statement::{Statement, StatementStatus}; pub use crate::statement::{Statement, StatementStatus};
#[cfg(feature = "modern_sqlite")]
pub use crate::transaction::TransactionState;
pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior}; pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
pub use crate::types::ToSql; pub use crate::types::ToSql;
pub use crate::version::*; pub use crate::version::*;
#[cfg(feature = "rusqlite-macros")]
#[doc(hidden)]
pub use rusqlite_macros::__bind;
mod error; mod error;
@ -119,6 +125,9 @@ mod params;
mod pragma; mod pragma;
mod raw_statement; mod raw_statement;
mod row; mod row;
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serialize;
#[cfg(feature = "session")] #[cfg(feature = "session")]
#[cfg_attr(docsrs, doc(cfg(feature = "session")))] #[cfg_attr(docsrs, doc(cfg(feature = "session")))]
pub mod session; pub mod session;
@ -212,6 +221,51 @@ macro_rules! named_params {
}; };
} }
/// Captured identifiers in SQL
///
/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not
/// work).
/// * `$x.y` expression does not work.
///
/// # Example
///
/// ```rust, no_run
/// # use rusqlite::{prepare_and_bind, Connection, Result, Statement};
///
/// fn misc(db: &Connection) -> Result<Statement> {
/// let name = "Lisa";
/// let age = 8;
/// let smart = true;
/// Ok(prepare_and_bind!(db, "SELECT $name, @age, :smart;"))
/// }
/// ```
#[cfg(feature = "rusqlite-macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite-macros")))]
#[macro_export]
macro_rules! prepare_and_bind {
($conn:expr, $sql:literal) => {{
let mut stmt = $conn.prepare($sql)?;
$crate::__bind!(stmt $sql);
stmt
}};
}
/// Captured identifiers in SQL
///
/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not
/// work).
/// * `$x.y` expression does not work.
#[cfg(feature = "rusqlite-macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite-macros")))]
#[macro_export]
macro_rules! prepare_cached_and_bind {
($conn:expr, $sql:literal) => {{
let mut stmt = $conn.prepare_cached($sql)?;
$crate::__bind!(stmt $sql);
stmt
}};
}
/// A typedef of the result returned by many methods. /// A typedef of the result returned by many methods.
pub type Result<T, E = Error> = result::Result<T, E>; pub type Result<T, E = Error> = result::Result<T, E>;
@ -565,7 +619,7 @@ impl Connection {
#[inline] #[inline]
pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize> { pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize> {
self.prepare(sql) self.prepare(sql)
.and_then(|mut stmt| stmt.check_no_tail().and_then(|_| stmt.execute(params))) .and_then(|mut stmt| stmt.check_no_tail().and_then(|()| stmt.execute(params)))
} }
/// Returns the path to the database file, if one exists and is known. /// Returns the path to the database file, if one exists and is known.
@ -648,7 +702,7 @@ impl Connection {
// https://sqlite.org/tclsqlite.html#onecolumn // https://sqlite.org/tclsqlite.html#onecolumn
#[cfg(test)] #[cfg(test)]
pub(crate) fn one_column<T: crate::types::FromSql>(&self, sql: &str) -> Result<T> { pub(crate) fn one_column<T: types::FromSql>(&self, sql: &str) -> Result<T> {
self.query_row(sql, [], |r| r.get(0)) self.query_row(sql, [], |r| r.get(0))
} }
@ -920,7 +974,7 @@ impl Connection {
/// This function is unsafe because improper use may impact the Connection. /// This function is unsafe because improper use may impact the Connection.
/// In particular, it should only be called on connections created /// In particular, it should only be called on connections created
/// and owned by the caller, e.g. as a result of calling /// and owned by the caller, e.g. as a result of calling
/// ffi::sqlite3_open(). /// `ffi::sqlite3_open`().
#[inline] #[inline]
pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result<Connection> { pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result<Connection> {
let db = InnerConnection::new(db, true); let db = InnerConnection::new(db, true);
@ -1047,7 +1101,7 @@ impl<'conn> Iterator for Batch<'conn, '_> {
bitflags::bitflags! { bitflags::bitflags! {
/// Flags for opening SQLite database connections. See /// Flags for opening SQLite database connections. See
/// [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details. /// [sqlite3_open_v2](https://www.sqlite.org/c3ref/open.html) for details.
/// ///
/// The default open flags are `SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE /// The default open flags are `SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE
/// | SQLITE_OPEN_URI | SQLITE_OPEN_NO_MUTEX`. See [`Connection::open`] for /// | SQLITE_OPEN_URI | SQLITE_OPEN_NO_MUTEX`. See [`Connection::open`] for
@ -2122,9 +2176,25 @@ mod test {
} }
#[test] #[test]
pub fn db_readonly() -> Result<()> { fn db_readonly() -> Result<()> {
let db = Connection::open_in_memory()?; let db = Connection::open_in_memory()?;
assert!(!db.is_readonly(MAIN_DB)?); assert!(!db.is_readonly(MAIN_DB)?);
Ok(()) Ok(())
} }
#[test]
#[cfg(feature = "rusqlite-macros")]
fn prepare_and_bind() -> Result<()> {
let db = Connection::open_in_memory()?;
let name = "Lisa";
let age = 8;
let mut stmt = prepare_and_bind!(db, "SELECT $name, $age;");
let (v1, v2) = stmt
.raw_query()
.next()
.and_then(|o| o.ok_or(Error::QueryReturnedNoRows))
.and_then(|r| Ok((r.get::<_, String>(0)?, r.get::<_, i64>(1)?)))?;
assert_eq!((v1.as_str(), v2), (name, age));
Ok(())
}
} }

View File

@ -384,7 +384,6 @@ mod test {
let mut rows = table_info.query(["sqlite_master"])?; let mut rows = table_info.query(["sqlite_master"])?;
while let Some(row) = rows.next()? { while let Some(row) = rows.next()? {
let row = row;
let column: String = row.get(1)?; let column: String = row.get(1)?;
columns.push(column); columns.push(column);
} }

View File

@ -29,8 +29,8 @@ impl<'stmt> Rows<'stmt> {
/// This interface is not compatible with Rust's `Iterator` trait, because /// This interface is not compatible with Rust's `Iterator` trait, because
/// the lifetime of the returned row is tied to the lifetime of `self`. /// the lifetime of the returned row is tied to the lifetime of `self`.
/// This is a fallible "streaming iterator". For a more natural interface, /// This is a fallible "streaming iterator". For a more natural interface,
/// consider using [`query_map`](crate::Statement::query_map) or /// consider using [`query_map`](Statement::query_map) or
/// [`query_and_then`](crate::Statement::query_and_then) instead, which /// [`query_and_then`](Statement::query_and_then) instead, which
/// return types that implement `Iterator`. /// return types that implement `Iterator`.
#[allow(clippy::should_implement_trait)] // cannot implement Iterator #[allow(clippy::should_implement_trait)] // cannot implement Iterator
#[inline] #[inline]
@ -247,7 +247,7 @@ pub struct Row<'stmt> {
impl<'stmt> Row<'stmt> { impl<'stmt> Row<'stmt> {
/// Get the value of a particular column of the result row. /// Get the value of a particular column of the result row.
/// ///
/// ## Failure /// # Panics
/// ///
/// Panics if calling [`row.get(idx)`](Row::get) would return an error, /// Panics if calling [`row.get(idx)`](Row::get) would return an error,
/// including: /// including:
@ -330,7 +330,7 @@ impl<'stmt> Row<'stmt> {
/// it can be difficult to use, and most callers will be better served by /// it can be difficult to use, and most callers will be better served by
/// [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap). /// [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap).
/// ///
/// ## Failure /// # Panics
/// ///
/// Panics if calling [`row.get_ref(idx)`](Row::get_ref) would return an /// Panics if calling [`row.get_ref(idx)`](Row::get_ref) would return an
/// error, including: /// error, including:

167
src/serialize.rs Normal file
View File

@ -0,0 +1,167 @@
//! Serialize a database.
use std::convert::TryInto;
use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr::NonNull;
use crate::error::error_from_handle;
use crate::ffi;
use crate::{Connection, DatabaseName, Result};
/// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database
pub struct SharedData<'conn> {
phantom: PhantomData<&'conn Connection>,
ptr: NonNull<u8>,
sz: usize,
}
/// Owned serialized database
pub struct OwnedData {
ptr: NonNull<u8>,
sz: usize,
}
impl OwnedData {
/// # Safety
///
/// Caller must be certain that `ptr` is allocated by `sqlite3_malloc`.
pub unsafe fn from_raw_nonnull(ptr: NonNull<u8>, sz: usize) -> Self {
Self { ptr, sz }
}
fn into_raw(self) -> (*mut u8, usize) {
let raw = (self.ptr.as_ptr(), self.sz);
std::mem::forget(self);
raw
}
}
impl Drop for OwnedData {
fn drop(&mut self) {
unsafe {
ffi::sqlite3_free(self.ptr.as_ptr().cast());
}
}
}
/// Serialized database
pub enum Data<'conn> {
/// Shared (SQLITE_SERIALIZE_NOCOPY) serialized database
Shared(SharedData<'conn>),
/// Owned serialized database
Owned(OwnedData),
}
impl<'conn> Deref for Data<'conn> {
type Target = [u8];
fn deref(&self) -> &[u8] {
let (ptr, sz) = match self {
Data::Owned(OwnedData { ptr, sz }) => (ptr.as_ptr(), *sz),
Data::Shared(SharedData { ptr, sz, .. }) => (ptr.as_ptr(), *sz),
};
unsafe { std::slice::from_raw_parts(ptr, sz) }
}
}
impl Connection {
/// Serialize a database.
pub fn serialize(&self, schema: DatabaseName) -> Result<Data> {
let schema = schema.as_cstring()?;
let mut sz = 0;
let mut ptr: *mut u8 = unsafe {
ffi::sqlite3_serialize(
self.handle(),
schema.as_ptr(),
&mut sz,
ffi::SQLITE_SERIALIZE_NOCOPY,
)
};
Ok(if ptr.is_null() {
ptr = unsafe { ffi::sqlite3_serialize(self.handle(), schema.as_ptr(), &mut sz, 0) };
if ptr.is_null() {
return Err(unsafe { error_from_handle(self.handle(), ffi::SQLITE_NOMEM) });
}
Data::Owned(OwnedData {
ptr: NonNull::new(ptr).unwrap(),
sz: sz.try_into().unwrap(),
})
} else {
// shared buffer
Data::Shared(SharedData {
ptr: NonNull::new(ptr).unwrap(),
sz: sz.try_into().unwrap(),
phantom: PhantomData,
})
})
}
/// Deserialize a database.
pub fn deserialize(
&mut self,
schema: DatabaseName<'_>,
data: OwnedData,
read_only: bool,
) -> Result<()> {
let schema = schema.as_cstring()?;
let (data, sz) = data.into_raw();
let sz = sz.try_into().unwrap();
let flags = if read_only {
ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_READONLY
} else {
ffi::SQLITE_DESERIALIZE_FREEONCLOSE | ffi::SQLITE_DESERIALIZE_RESIZEABLE
};
let rc = unsafe {
ffi::sqlite3_deserialize(self.handle(), schema.as_ptr(), data, sz, sz, flags)
};
if rc != ffi::SQLITE_OK {
// TODO sqlite3_free(data) ?
return Err(unsafe { error_from_handle(self.handle(), rc) });
}
/* TODO
if let Some(mxSize) = mxSize {
unsafe {
ffi::sqlite3_file_control(
self.handle(),
schema.as_ptr(),
ffi::SQLITE_FCNTL_SIZE_LIMIT,
&mut mxSize,
)
};
}*/
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{Connection, DatabaseName, Result};
#[test]
fn serialize() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
let data = db.serialize(DatabaseName::Main)?;
let Data::Owned(data) = data else {
panic!("expected OwnedData")
};
assert!(data.sz > 0);
Ok(())
}
#[test]
fn deserialize() -> Result<()> {
let src = Connection::open_in_memory()?;
src.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
let data = src.serialize(DatabaseName::Main)?;
let Data::Owned(data) = data else {
panic!("expected OwnedData")
};
let mut dst = Connection::open_in_memory()?;
dst.deserialize(DatabaseName::Main, data, false)?;
dst.execute("DELETE FROM x", [])?;
Ok(())
}
}

View File

@ -405,7 +405,7 @@ impl Drop for ChangesetIter<'_> {
} }
/// An item passed to a conflict-handler by /// An item passed to a conflict-handler by
/// [`Connection::apply`](crate::Connection::apply), or an item generated by /// [`Connection::apply`](Connection::apply), or an item generated by
/// [`ChangesetIter::next`](ChangesetIter::next). /// [`ChangesetIter::next`](ChangesetIter::next).
// TODO enum ? Delete, Insert, Update, ... // TODO enum ? Delete, Insert, Update, ...
pub struct ChangesetItem { pub struct ChangesetItem {

View File

@ -435,6 +435,10 @@ impl Statement<'_> {
/// ///
/// Will return `None` if the column index is out of bounds or if the /// Will return `None` if the column index is out of bounds or if the
/// parameter is positional. /// parameter is positional.
///
/// # Panics
///
/// Panics when parameter name is not valid UTF-8.
#[inline] #[inline]
pub fn parameter_name(&self, index: usize) -> Option<&'_ str> { pub fn parameter_name(&self, index: usize) -> Option<&'_ str> {
self.stmt.bind_parameter_name(index as i32).map(|name| { self.stmt.bind_parameter_name(index as i32).map(|name| {
@ -450,7 +454,7 @@ impl Statement<'_> {
{ {
let expected = self.stmt.bind_parameter_count(); let expected = self.stmt.bind_parameter_count();
let mut index = 0; let mut index = 0;
for p in params.into_iter() { for p in params {
index += 1; // The leftmost SQL parameter has an index of 1. index += 1; // The leftmost SQL parameter has an index of 1.
if index > expected { if index > expected {
break; break;
@ -744,7 +748,7 @@ impl Statement<'_> {
/// Reset all bindings /// Reset all bindings
pub fn clear_bindings(&mut self) { pub fn clear_bindings(&mut self) {
self.stmt.clear_bindings() self.stmt.clear_bindings();
} }
} }

View File

@ -122,7 +122,7 @@ impl Transaction<'_> {
TransactionBehavior::Immediate => "BEGIN IMMEDIATE", TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE", TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
}; };
conn.execute_batch(query).map(move |_| Transaction { conn.execute_batch(query).map(move |()| Transaction {
conn, conn,
drop_behavior: DropBehavior::Rollback, drop_behavior: DropBehavior::Rollback,
}) })
@ -251,7 +251,7 @@ impl Savepoint<'_> {
fn with_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>> { fn with_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>> {
let name = name.into(); let name = name.into();
conn.execute_batch(&format!("SAVEPOINT {name}")) conn.execute_batch(&format!("SAVEPOINT {name}"))
.map(|_| Savepoint { .map(|()| Savepoint {
conn, conn,
name, name,
drop_behavior: DropBehavior::Rollback, drop_behavior: DropBehavior::Rollback,
@ -346,8 +346,8 @@ impl Savepoint<'_> {
match self.drop_behavior() { match self.drop_behavior() {
DropBehavior::Commit => self DropBehavior::Commit => self
.commit_() .commit_()
.or_else(|_| self.rollback().and_then(|_| self.commit_())), .or_else(|_| self.rollback().and_then(|()| self.commit_())),
DropBehavior::Rollback => self.rollback().and_then(|_| self.commit_()), DropBehavior::Rollback => self.rollback().and_then(|()| self.commit_()),
DropBehavior::Ignore => Ok(()), DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."), DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
} }
@ -471,8 +471,7 @@ impl Connection {
/// ///
/// The savepoint defaults to rolling back when it is dropped. If you want /// The savepoint defaults to rolling back when it is dropped. If you want
/// the savepoint to commit, you must call [`commit`](Savepoint::commit) or /// the savepoint to commit, you must call [`commit`](Savepoint::commit) or
/// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint:: /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::set_drop_behavior).
/// set_drop_behavior).
/// ///
/// ## Example /// ## Example
/// ///

View File

@ -1,81 +1,174 @@
//! Convert formats 1-10 in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) to time types.
//! [`ToSql`] and [`FromSql`] implementation for [`time::OffsetDateTime`]. //! [`ToSql`] and [`FromSql`] implementation for [`time::OffsetDateTime`].
//! [`ToSql`] and [`FromSql`] implementation for [`time::PrimitiveDateTime`].
//! [`ToSql`] and [`FromSql`] implementation for [`time::Date`].
//! [`ToSql`] and [`FromSql`] implementation for [`time::Time`].
//! Time Strings in:
//! - Format 2: "YYYY-MM-DD HH:MM"
//! - Format 5: "YYYY-MM-DDTHH:MM"
//! - Format 8: "HH:MM"
//! without an explicit second value will assume 0 seconds.
//! Time String that contain an optional timezone without an explicit date are unsupported.
//! All other assumptions described in [Time Values](https://sqlite.org/lang_datefunc.html#time_values) section are unsupported.
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::{Error, Result}; use crate::{Error, Result};
use time::format_description::well_known::Rfc3339;
use time::format_description::FormatItem; use time::format_description::FormatItem;
use time::macros::format_description; use time::macros::format_description;
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
const PRIMITIVE_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] = const OFFSET_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); version = 2,
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
const PRIMITIVE_DATE_TIME_Z_FORMAT: &[FormatItem<'_>] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]Z");
const OFFSET_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
"[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]"
);
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour sign:mandatory]:[offset_minute]" "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour sign:mandatory]:[offset_minute]"
); );
const PRIMITIVE_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"
);
const TIME_ENCODING: &[FormatItem<'_>] =
format_description!(version = 2, "[hour]:[minute]:[second].[subsecond]");
const DATE_FORMAT: &[FormatItem<'_>] = format_description!(version = 2, "[year]-[month]-[day]");
const TIME_FORMAT: &[FormatItem<'_>] = format_description!(
version = 2,
"[hour]:[minute][optional [:[second][optional [.[subsecond]]]]]"
);
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]]"
);
const UTC_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]][optional [Z]]"
);
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day][first [ ][T]][hour]:[minute][optional [:[second][optional [.[subsecond]]]]][offset_hour sign:mandatory]:[offset_minute]"
);
const LEGACY_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!( const LEGACY_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
version = 2,
"[year]-[month]-[day] [hour]:[minute]:[second]:[subsecond] [offset_hour sign:mandatory]:[offset_minute]" "[year]-[month]-[day] [hour]:[minute]:[second]:[subsecond] [offset_hour sign:mandatory]:[offset_minute]"
); );
/// OffsetDatetime => RFC3339 format ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM")
impl ToSql for OffsetDateTime { impl ToSql for OffsetDateTime {
#[inline] #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
// FIXME keep original offset
let time_string = self let time_string = self
.to_offset(UtcOffset::UTC) .format(&OFFSET_DATE_TIME_ENCODING)
.format(&PRIMITIVE_DATE_TIME_Z_FORMAT)
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?; .map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(time_string)) Ok(ToSqlOutput::from(time_string))
} }
} }
// Supports parsing formats 2-7 from https://www.sqlite.org/lang_datefunc.html
// Formats 2-7 without a timezone assumes UTC
impl FromSql for OffsetDateTime { impl FromSql for OffsetDateTime {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| { value.as_str().and_then(|s| {
if s.len() > 10 && s.as_bytes()[10] == b'T' { if let Some(b' ') = s.as_bytes().get(23) {
// YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM // legacy
return OffsetDateTime::parse(s, &Rfc3339) return OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
.map_err(|err| FromSqlError::Other(Box::new(err))); .map_err(|err| FromSqlError::Other(Box::new(err)));
} }
let s = s.strip_suffix('Z').unwrap_or(s); if s[8..].contains('+') || s[8..].contains('-') {
match s.len() { // Formats 2-7 with timezone
len if len <= 19 => { return OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
// TODO YYYY-MM-DDTHH:MM:SS .map_err(|err| FromSqlError::Other(Box::new(err)));
PrimitiveDateTime::parse(s, &PRIMITIVE_SHORT_DATE_TIME_FORMAT)
.map(PrimitiveDateTime::assume_utc)
}
_ if s.as_bytes()[19] == b':' => {
// legacy
OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
}
_ if s.as_bytes()[19] == b'.' => OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
.or_else(|err| {
PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
.map(PrimitiveDateTime::assume_utc)
.map_err(|_| err)
}),
_ => OffsetDateTime::parse(s, &OFFSET_SHORT_DATE_TIME_FORMAT),
} }
// Formats 2-7 without timezone
PrimitiveDateTime::parse(s, &UTC_DATE_TIME_FORMAT)
.map(|p| p.assume_utc())
.map_err(|err| FromSqlError::Other(Box::new(err))) .map_err(|err| FromSqlError::Other(Box::new(err)))
}) })
} }
} }
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
impl ToSql for Date {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self
.format(&DATE_FORMAT)
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(date_str))
}
}
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
impl FromSql for Date {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
Date::parse(s, &DATE_FORMAT).map_err(|err| FromSqlError::Other(err.into()))
})
}
}
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
impl ToSql for Time {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let time_str = self
.format(&TIME_ENCODING)
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(time_str))
}
}
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
impl FromSql for Time {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
Time::parse(s, &TIME_FORMAT).map_err(|err| FromSqlError::Other(err.into()))
})
}
}
/// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS"
impl ToSql for PrimitiveDateTime {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_time_str = self
.format(&PRIMITIVE_DATE_TIME_ENCODING)
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(date_time_str))
}
}
/// YYYY-MM-DD HH:MM
/// YYYY-MM-DDTHH:MM
/// YYYY-MM-DD HH:MM:SS
/// YYYY-MM-DDTHH:MM:SS
/// YYYY-MM-DD HH:MM:SS.SSS
/// YYYY-MM-DDTHH:MM:SS.SSS
/// => ISO 8601 combined date and time with timezone
impl FromSql for PrimitiveDateTime {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
.map_err(|err| FromSqlError::Other(err.into()))
})
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{Connection, Result}; use crate::{Connection, Result};
use time::format_description::well_known::Rfc3339; use time::macros::{date, datetime, time};
use time::OffsetDateTime; use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
fn checked_memory_handle() -> Result<Connection> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)")?;
Ok(db)
}
#[test] #[test]
fn test_offset_date_time() -> Result<()> { fn test_offset_date_time() -> Result<()> {
let db = Connection::open_in_memory()?; let db = checked_memory_handle()?;
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
let mut ts_vec = vec![]; let mut ts_vec = vec![];
@ -103,47 +196,163 @@ mod test {
} }
#[test] #[test]
fn test_string_values() -> Result<()> { fn test_offset_date_time_parsing() -> Result<()> {
let db = Connection::open_in_memory()?; let db = checked_memory_handle()?;
for (s, t) in vec![ let tests = vec![
// Rfc3339
( (
"2013-10-07 08:23:19", "2013-10-07T08:23:19.123456789Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()), datetime!(2013-10-07 8:23:19.123456789 UTC),
), ),
( (
"2013-10-07 08:23:19Z", "2013-10-07 08:23:19.123456789Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()), datetime!(2013-10-07 8:23:19.123456789 UTC),
),
// Format 2
("2013-10-07 08:23", datetime!(2013-10-07 8:23 UTC)),
("2013-10-07 08:23Z", datetime!(2013-10-07 8:23 UTC)),
("2013-10-07 08:23+04:00", datetime!(2013-10-07 8:23 +4)),
// Format 3
("2013-10-07 08:23:19", datetime!(2013-10-07 8:23:19 UTC)),
("2013-10-07 08:23:19Z", datetime!(2013-10-07 8:23:19 UTC)),
(
"2013-10-07 08:23:19+04:00",
datetime!(2013-10-07 8:23:19 +4),
),
// Format 4
(
"2013-10-07 08:23:19.123",
datetime!(2013-10-07 8:23:19.123 UTC),
), ),
( (
"2013-10-07T08:23:19Z", "2013-10-07 08:23:19.123Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()), datetime!(2013-10-07 8:23:19.123 UTC),
), ),
( (
"2013-10-07 08:23:19.120", "2013-10-07 08:23:19.123+04:00",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()), datetime!(2013-10-07 8:23:19.123 +4),
),
// Format 5
("2013-10-07T08:23", datetime!(2013-10-07 8:23 UTC)),
("2013-10-07T08:23Z", datetime!(2013-10-07 8:23 UTC)),
("2013-10-07T08:23+04:00", datetime!(2013-10-07 8:23 +4)),
// Format 6
("2013-10-07T08:23:19", datetime!(2013-10-07 8:23:19 UTC)),
("2013-10-07T08:23:19Z", datetime!(2013-10-07 8:23:19 UTC)),
(
"2013-10-07T08:23:19+04:00",
datetime!(2013-10-07 8:23:19 +4),
),
// Format 7
(
"2013-10-07T08:23:19.123",
datetime!(2013-10-07 8:23:19.123 UTC),
), ),
( (
"2013-10-07 08:23:19.120Z", "2013-10-07T08:23:19.123Z",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()), datetime!(2013-10-07 8:23:19.123 UTC),
), ),
( (
"2013-10-07T08:23:19.120Z", "2013-10-07T08:23:19.123+04:00",
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()), datetime!(2013-10-07 8:23:19.123 +4),
), ),
// Legacy
( (
"2013-10-07 04:23:19-04:00", "2013-10-07 08:23:12:987 -07:00",
Ok(OffsetDateTime::parse("2013-10-07T04:23:19-04:00", &Rfc3339).unwrap()), datetime!(2013-10-07 8:23:12.987 -7),
), ),
( ];
"2013-10-07 04:23:19.120-04:00",
Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()), for (s, t) in tests {
), let result: OffsetDateTime = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
( assert_eq!(result, t);
"2013-10-07T04:23:19.120-04:00", }
Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()), Ok(())
), }
] {
let result: Result<OffsetDateTime> = db.query_row("SELECT ?1", [s], |r| r.get(0)); #[test]
fn test_date() -> Result<()> {
let db = checked_memory_handle()?;
let date = date!(2016 - 02 - 23);
db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert_eq!("2016-02-23", s);
let t: Date = db.one_column("SELECT t FROM foo")?;
assert_eq!(date, t);
Ok(())
}
#[test]
fn test_time() -> Result<()> {
let db = checked_memory_handle()?;
let time = time!(23:56:04.00001);
db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert_eq!("23:56:04.00001", s);
let v: Time = db.one_column("SELECT t FROM foo")?;
assert_eq!(time, v);
Ok(())
}
#[test]
fn test_primitive_date_time() -> Result<()> {
let db = checked_memory_handle()?;
let dt = date!(2016 - 02 - 23).with_time(time!(23:56:04));
db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
let s: String = db.one_column("SELECT t FROM foo")?;
assert_eq!("2016-02-23 23:56:04.0", s);
let v: PrimitiveDateTime = db.one_column("SELECT t FROM foo")?;
assert_eq!(dt, v);
db.execute("UPDATE foo set b = datetime(t)", [])?; // "YYYY-MM-DD HH:MM:SS"
let hms: PrimitiveDateTime = db.one_column("SELECT b FROM foo")?;
assert_eq!(dt, hms);
Ok(())
}
#[test]
fn test_date_parsing() -> Result<()> {
let db = checked_memory_handle()?;
let result: Date = db.query_row("SELECT ?1", ["2013-10-07"], |r| r.get(0))?;
assert_eq!(result, date!(2013 - 10 - 07));
Ok(())
}
#[test]
fn test_time_parsing() -> Result<()> {
let db = checked_memory_handle()?;
let tests = vec![
("08:23", time!(08:23)),
("08:23:19", time!(08:23:19)),
("08:23:19.111", time!(08:23:19.111)),
];
for (s, t) in tests {
let result: Time = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
assert_eq!(result, t);
}
Ok(())
}
#[test]
fn test_primitive_date_time_parsing() -> Result<()> {
let db = checked_memory_handle()?;
let tests = vec![
("2013-10-07T08:23", datetime!(2013-10-07 8:23)),
("2013-10-07T08:23:19", datetime!(2013-10-07 8:23:19)),
("2013-10-07T08:23:19.111", datetime!(2013-10-07 8:23:19.111)),
("2013-10-07 08:23", datetime!(2013-10-07 8:23)),
("2013-10-07 08:23:19", datetime!(2013-10-07 8:23:19)),
("2013-10-07 08:23:19.111", datetime!(2013-10-07 8:23:19.111)),
];
for (s, t) in tests {
let result: PrimitiveDateTime = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
assert_eq!(result, t); assert_eq!(result, t);
} }
Ok(()) Ok(())
@ -151,16 +360,66 @@ mod test {
#[test] #[test]
fn test_sqlite_functions() -> Result<()> { fn test_sqlite_functions() -> Result<()> {
let db = Connection::open_in_memory()?; let db = checked_memory_handle()?;
let result: Result<OffsetDateTime> = db.one_column("SELECT CURRENT_TIMESTAMP"); db.one_column::<Time>("SELECT CURRENT_TIME").unwrap();
db.one_column::<Date>("SELECT CURRENT_DATE").unwrap();
db.one_column::<PrimitiveDateTime>("SELECT CURRENT_TIMESTAMP")
.unwrap();
db.one_column::<OffsetDateTime>("SELECT CURRENT_TIMESTAMP")
.unwrap();
Ok(())
}
#[test]
fn test_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let now = OffsetDateTime::now_utc().time();
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN time('now', '-1 minute') AND time('now', '+1 minute')",
[now],
|r| r.get(0),
);
result.unwrap(); result.unwrap();
Ok(()) Ok(())
} }
#[test] #[test]
fn test_param() -> Result<()> { fn test_date_param() -> Result<()> {
let db = Connection::open_in_memory()?; let db = checked_memory_handle()?;
let result: Result<bool> = db.query_row("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [OffsetDateTime::now_utc()], |r| r.get(0)); let now = OffsetDateTime::now_utc().date();
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN date('now', '-1 day') AND date('now', '+1 day')",
[now],
|r| r.get(0),
);
result.unwrap();
Ok(())
}
#[test]
fn test_primitive_date_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let now = PrimitiveDateTime::new(
OffsetDateTime::now_utc().date(),
OffsetDateTime::now_utc().time(),
);
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
[now],
|r| r.get(0),
);
result.unwrap();
Ok(())
}
#[test]
fn test_offset_date_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let result: Result<bool> = db.query_row(
"SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')",
[OffsetDateTime::now_utc()],
|r| r.get(0),
);
result.unwrap(); result.unwrap();
Ok(()) Ok(())
} }

View File

@ -36,8 +36,7 @@ impl ValueRef<'_> {
impl<'a> ValueRef<'a> { impl<'a> ValueRef<'a> {
/// If `self` is case `Integer`, returns the integral value. Otherwise, /// If `self` is case `Integer`, returns the integral value. Otherwise,
/// returns [`Err(Error::InvalidColumnType)`](crate::Error:: /// returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
/// InvalidColumnType).
#[inline] #[inline]
pub fn as_i64(&self) -> FromSqlResult<i64> { pub fn as_i64(&self) -> FromSqlResult<i64> {
match *self { match *self {
@ -48,8 +47,7 @@ impl<'a> ValueRef<'a> {
/// If `self` is case `Null` returns None. /// If `self` is case `Null` returns None.
/// If `self` is case `Integer`, returns the integral value. /// If `self` is case `Integer`, returns the integral value.
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error:: /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
/// InvalidColumnType).
#[inline] #[inline]
pub fn as_i64_or_null(&self) -> FromSqlResult<Option<i64>> { pub fn as_i64_or_null(&self) -> FromSqlResult<Option<i64>> {
match *self { match *self {
@ -60,8 +58,7 @@ impl<'a> ValueRef<'a> {
} }
/// If `self` is case `Real`, returns the floating point value. Otherwise, /// If `self` is case `Real`, returns the floating point value. Otherwise,
/// returns [`Err(Error::InvalidColumnType)`](crate::Error:: /// returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
/// InvalidColumnType).
#[inline] #[inline]
pub fn as_f64(&self) -> FromSqlResult<f64> { pub fn as_f64(&self) -> FromSqlResult<f64> {
match *self { match *self {
@ -72,8 +69,7 @@ impl<'a> ValueRef<'a> {
/// If `self` is case `Null` returns None. /// If `self` is case `Null` returns None.
/// If `self` is case `Real`, returns the floating point value. /// If `self` is case `Real`, returns the floating point value.
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error:: /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
/// InvalidColumnType).
#[inline] #[inline]
pub fn as_f64_or_null(&self) -> FromSqlResult<Option<f64>> { pub fn as_f64_or_null(&self) -> FromSqlResult<Option<f64>> {
match *self { match *self {
@ -97,8 +93,7 @@ impl<'a> ValueRef<'a> {
/// If `self` is case `Null` returns None. /// If `self` is case `Null` returns None.
/// If `self` is case `Text`, returns the string value. /// If `self` is case `Text`, returns the string value.
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error:: /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
/// InvalidColumnType).
#[inline] #[inline]
pub fn as_str_or_null(&self) -> FromSqlResult<Option<&'a str>> { pub fn as_str_or_null(&self) -> FromSqlResult<Option<&'a str>> {
match *self { match *self {
@ -122,8 +117,7 @@ impl<'a> ValueRef<'a> {
/// If `self` is case `Null` returns None. /// If `self` is case `Null` returns None.
/// If `self` is case `Blob`, returns the byte slice. /// If `self` is case `Blob`, returns the byte slice.
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error:: /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
/// InvalidColumnType).
#[inline] #[inline]
pub fn as_blob_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> { pub fn as_blob_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
match *self { match *self {
@ -133,7 +127,7 @@ impl<'a> ValueRef<'a> {
} }
} }
/// Returns the byte slice that makes up this ValueRef if it's either /// Returns the byte slice that makes up this `ValueRef` if it's either
/// [`ValueRef::Blob`] or [`ValueRef::Text`]. /// [`ValueRef::Blob`] or [`ValueRef::Text`].
#[inline] #[inline]
pub fn as_bytes(&self) -> FromSqlResult<&'a [u8]> { pub fn as_bytes(&self) -> FromSqlResult<&'a [u8]> {

View File

@ -14,6 +14,10 @@ pub fn version_number() -> i32 {
/// Returns the SQLite version as a string; e.g., `"3.16.2"` for version 3.16.2. /// Returns the SQLite version as a string; e.g., `"3.16.2"` for version 3.16.2.
/// ///
/// See [`sqlite3_libversion()`](https://www.sqlite.org/c3ref/libversion.html). /// See [`sqlite3_libversion()`](https://www.sqlite.org/c3ref/libversion.html).
///
/// # Panics
///
/// Panics when version is not valid UTF-8.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn version() -> &'static str { pub fn version() -> &'static str {