mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-25 02:21:37 +08:00
Merge remote-tracking branch 'origin/master' into loadable_extension
This commit is contained in:
commit
74c867d708
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
# The `{ sharedKey: ... }` allows different actions to share the cache.
|
||||
# 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
|
||||
# `--features 'bundled-full session buildtime_bindgen`), which is very
|
||||
# `--features 'bundled-full session buildtime_bindgen'`), which is very
|
||||
# slow, and has several deps.
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with: { sharedKey: fullBuild }
|
||||
|
@ -78,6 +78,8 @@ column_decltype = []
|
||||
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
|
||||
# Note: doesn't support 32-bit.
|
||||
winsqlite3 = ["libsqlite3-sys/winsqlite3"]
|
||||
# 3.23.0
|
||||
serialize = ["modern_sqlite"]
|
||||
|
||||
# Helper feature for enabling most non-build-related optional features
|
||||
# or dependencies (except `session`). This is useful for running tests / clippy
|
||||
@ -123,6 +125,7 @@ fallible-iterator = "0.3"
|
||||
fallible-streaming-iterator = "0.1"
|
||||
uuid = { version = "1.0", optional = true }
|
||||
smallvec = "1.6.1"
|
||||
rusqlite-macros = { path = "rusqlite-macros", version = "0.1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = "0.3"
|
||||
@ -167,7 +170,7 @@ name = "load_extension"
|
||||
required-features = ["load_extension", "bundled", "functions", "vtab", "trace"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["modern-full"]
|
||||
features = ["modern-full", "rusqlite-macros"]
|
||||
all-features = false
|
||||
no-default-features = true
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
|
14
Changelog.md
14
Changelog.md
@ -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.
|
||||
* Implement `sqlite3_update_hook` (#260, #328), `sqlite3_commit_hook` and `sqlite3_rollback_hook`.
|
||||
* Add support to unlock notification behind `unlock_notify` feature (#294, #331).
|
||||
* Make `Statement::column_index` case insensitive (#330).
|
||||
* Make `Statement::column_index` case-insensitive (#330).
|
||||
* Add comment to justify `&mut Connection` in `Transaction`.
|
||||
* Fix `tyvar_behind_raw_pointer` warnings.
|
||||
* Fix handful of clippy warnings.
|
||||
@ -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)
|
||||
|
||||
* 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
|
||||
and expect fractional seconds and timezone in the serialized string.
|
||||
* 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 the `limits` feature, exposing `limit()` and `set_limit()` methods on `Connection`.
|
||||
* Updates to `libsqlite3-sys` 0.7.0, which runs rust-bindgen at build-time instead of assuming the
|
||||
precense of all expected SQLite constants and functions.
|
||||
presence of all expected SQLite constants and functions.
|
||||
* Clarifies supported SQLite versions. Running with SQLite older than 3.6.8 now panics, and
|
||||
some features will not compile unless a sufficiently-recent SQLite version is used. See
|
||||
the README for requirements of particular features.
|
||||
* When running with SQLite 3.6.x, rusqlite attempts to perform SQLite initialization. If it fails,
|
||||
rusqlite will panic since it cannot ensure the threading mode for SQLite. This check can by
|
||||
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
|
||||
technically a breaking change but is unlikely to affect anyone in practice, since prior to this
|
||||
version the check that rusqlite was using would cause a segfault if linked against a SQLite
|
||||
older than 3.7.0.
|
||||
* rusqlite now performs a one-time check (prior to the first connection attempt) that the runtime
|
||||
SQLite version is at least as new as the SQLite version found at buildtime. This check can by
|
||||
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()`.
|
||||
* 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
|
||||
other changes in this release to reset statements as soon as possible introduced yet another
|
||||
hazard related to the lack of these lifetime connections. We were already recommending the
|
||||
use of `query_map` and `query_and_then` over raw `query`; both of theose still return handles
|
||||
use of `query_map` and `query_and_then` over raw `query`; both of those still return handles
|
||||
that implement `Iterator`.
|
||||
* BREAKING CHANGE: `Transaction::savepoint()` now returns a `Savepoint` instead of another
|
||||
`Transaction`. Unlike `Transaction`, `Savepoint`s can be rolled back while keeping the current
|
||||
@ -239,7 +239,7 @@ For version 0.15.0 and above, see [Releases](https://github.com/rusqlite/rusqlit
|
||||
|
||||
* Add `column_names()` to `SqliteStatement`.
|
||||
* 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).
|
||||
* Use now-generated `sqlite3_destructor_type` to define `SQLITE_STATIC` and `SQLITE_TRANSIENT`.
|
||||
|
||||
|
@ -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
|
||||
`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).
|
||||
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for various
|
||||
types 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).
|
||||
|
@ -46,7 +46,7 @@ openssl-sys = { version = "0.9", optional = true }
|
||||
atomic = { version = "0.5", optional = true }
|
||||
|
||||
[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 }
|
||||
cc = { version = "1.0", optional = true }
|
||||
vcpkg = { version = "0.2", optional = true }
|
||||
|
796
libsqlite3-sys/sqlite3/bindgen_bundled_version.rs
vendored
796
libsqlite3-sys/sqlite3/bindgen_bundled_version.rs
vendored
File diff suppressed because it is too large
Load Diff
6908
libsqlite3-sys/sqlite3/sqlite3.c
vendored
6908
libsqlite3-sys/sqlite3/sqlite3.c
vendored
File diff suppressed because it is too large
Load Diff
106
libsqlite3-sys/sqlite3/sqlite3.h
vendored
106
libsqlite3-sys/sqlite3/sqlite3.h
vendored
@ -146,9 +146,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.42.0"
|
||||
#define SQLITE_VERSION_NUMBER 3042000
|
||||
#define SQLITE_SOURCE_ID "2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf350b0"
|
||||
#define SQLITE_VERSION "3.43.2"
|
||||
#define SQLITE_VERSION_NUMBER 3043002
|
||||
#define SQLITE_SOURCE_ID "2023-10-10 12:14:04 4310099cce5a487035fa535dd3002c59ac7f1d1bec68d7cf317fd3e769484790"
|
||||
|
||||
/*
|
||||
** 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_DATA (SQLITE_IOERR | (32<<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_VTAB (SQLITE_LOCKED | (2<<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.
|
||||
**
|
||||
** <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.
|
||||
**
|
||||
** <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
|
||||
** 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
|
||||
** either generated columns or decending indexes.
|
||||
** either generated columns or descending indexes.
|
||||
** </dd>
|
||||
**
|
||||
** [[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
|
||||
** 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 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
|
||||
** zero or more [SQLITE_TRACE] constants.
|
||||
**
|
||||
** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides
|
||||
** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2().
|
||||
** ^Each call to either sqlite3_trace(D,X,P) or sqlite3_trace_v2(D,M,X,P)
|
||||
** 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
|
||||
** 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:
|
||||
** <ul>
|
||||
** <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 new filename constructed using [sqlite3_create_filename()].
|
||||
** </ul>
|
||||
@ -3871,7 +3875,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
|
||||
/*
|
||||
** 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.
|
||||
**
|
||||
** 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);
|
||||
|
||||
/*
|
||||
** 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
|
||||
** 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
|
||||
** 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.
|
||||
** ^ (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
|
||||
** 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
|
||||
@ -5260,14 +5299,26 @@ SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt);
|
||||
** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S
|
||||
** back to the beginning of its program.
|
||||
**
|
||||
** ^If the most recent call to [sqlite3_step(S)] for the
|
||||
** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE],
|
||||
** or if [sqlite3_step(S)] has never before been called on S,
|
||||
** then [sqlite3_reset(S)] returns [SQLITE_OK].
|
||||
** ^The return code from [sqlite3_reset(S)] indicates whether or not
|
||||
** the previous evaluation of prepared statement S completed successfully.
|
||||
** ^If [sqlite3_step(S)] has never before been called on S or if
|
||||
** [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
|
||||
** [prepared statement] S indicated an error, then
|
||||
** [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
|
||||
** 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]
|
||||
** that has side-effects or that could potentially leak sensitive information.
|
||||
** 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
|
||||
** harmful.
|
||||
** <p>
|
||||
@ -8161,7 +8212,8 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
||||
#define SQLITE_TESTCTRL_TRACEFLAGS 31
|
||||
#define SQLITE_TESTCTRL_TUNE 32
|
||||
#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
|
||||
@ -9193,8 +9245,8 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
||||
** blocked connection already has a registered unlock-notify callback,
|
||||
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
|
||||
** called with a NULL pointer as its second argument, then any existing
|
||||
** unlock-notify callback is canceled. ^The blocked connections
|
||||
** unlock-notify callback may also be canceled by closing the blocked
|
||||
** unlock-notify callback is cancelled. ^The blocked connections
|
||||
** unlock-notify callback may also be cancelled by closing the blocked
|
||||
** connection using [sqlite3_close()].
|
||||
**
|
||||
** 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>
|
||||
** <dd>Calls of the form
|
||||
** [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
|
||||
** views.
|
||||
** </dd>
|
||||
@ -9807,7 +9859,7 @@ SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*);
|
||||
** communicated to the xBestIndex method as a
|
||||
** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use
|
||||
** 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]
|
||||
** that invokes the [xFilter|xFilter() method] once for each value
|
||||
** 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,
|
||||
** 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
|
||||
** 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
|
||||
** the index of the column being written. In other cases, where the
|
||||
** pre-update hook is being invoked for some other reason, including a
|
||||
@ -12754,7 +12806,7 @@ struct Fts5PhraseIter {
|
||||
** See xPhraseFirstColumn above.
|
||||
*/
|
||||
struct Fts5ExtensionApi {
|
||||
int iVersion; /* Currently always set to 3 */
|
||||
int iVersion; /* Currently always set to 2 */
|
||||
|
||||
void *(*xUserData)(Fts5Context*);
|
||||
|
||||
@ -12983,8 +13035,8 @@ struct Fts5ExtensionApi {
|
||||
** 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
|
||||
** provide synonyms when tokenizing document text (method (2)) or query
|
||||
** text (method (3)), not both. Doing so will not cause any errors, but is
|
||||
** provide synonyms when tokenizing document text (method (3)) or query
|
||||
** text (method (2)), not both. Doing so will not cause any errors, but is
|
||||
** inefficient.
|
||||
*/
|
||||
typedef struct Fts5Tokenizer Fts5Tokenizer;
|
||||
@ -13032,7 +13084,7 @@ struct fts5_api {
|
||||
int (*xCreateTokenizer)(
|
||||
fts5_api *pApi,
|
||||
const char *zName,
|
||||
void *pContext,
|
||||
void *pUserData,
|
||||
fts5_tokenizer *pTokenizer,
|
||||
void (*xDestroy)(void*)
|
||||
);
|
||||
@ -13041,7 +13093,7 @@ struct fts5_api {
|
||||
int (*xFindTokenizer)(
|
||||
fts5_api *pApi,
|
||||
const char *zName,
|
||||
void **ppContext,
|
||||
void **ppUserData,
|
||||
fts5_tokenizer *pTokenizer
|
||||
);
|
||||
|
||||
@ -13049,7 +13101,7 @@ struct fts5_api {
|
||||
int (*xCreateFunction)(
|
||||
fts5_api *pApi,
|
||||
const char *zName,
|
||||
void *pContext,
|
||||
void *pUserData,
|
||||
fts5_extension_function xFunction,
|
||||
void (*xDestroy)(void*)
|
||||
);
|
||||
|
4
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
4
libsqlite3-sys/sqlite3/sqlite3ext.h
vendored
@ -361,6 +361,8 @@ struct sqlite3_api_routines {
|
||||
int (*value_encoding)(sqlite3_value*);
|
||||
/* Version 3.41.0 and later */
|
||||
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
|
||||
/* Version 3.41.0 and later */
|
||||
#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) */
|
||||
|
||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||
|
@ -9,7 +9,7 @@ export SQLITE3_LIB_DIR="$SCRIPT_DIR/sqlite3"
|
||||
mkdir -p "$TARGET_DIR" "$SQLITE3_LIB_DIR"
|
||||
|
||||
# Download and extract amalgamation
|
||||
SQLITE=sqlite-amalgamation-3420000
|
||||
SQLITE=sqlite-amalgamation-3430200
|
||||
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.h" > "$SQLITE3_LIB_DIR/sqlite3.h"
|
||||
|
16
rusqlite-macros/Cargo.toml
Normal file
16
rusqlite-macros/Cargo.toml
Normal 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
115
rusqlite-macros/src/lib.rs
Normal 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()
|
||||
}
|
36
rusqlite-macros/tests/test.rs
Normal file
36
rusqlite-macros/tests/test.rs
Normal 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);
|
||||
}
|
||||
*/
|
@ -3,12 +3,16 @@ use std::str;
|
||||
use crate::{Error, Result, Statement};
|
||||
|
||||
/// Information about a column of a SQLite query.
|
||||
#[cfg(feature = "column_decltype")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
|
||||
#[derive(Debug)]
|
||||
pub struct Column<'stmt> {
|
||||
name: &'stmt str,
|
||||
decl_type: Option<&'stmt str>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "column_decltype")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
|
||||
impl Column<'_> {
|
||||
/// Returns the name of the column.
|
||||
#[inline]
|
||||
@ -90,6 +94,8 @@ impl Statement<'_> {
|
||||
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
|
||||
/// column range for this row.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when column name is not valid UTF-8.
|
||||
#[inline]
|
||||
pub fn column_name(&self, col: usize) -> Result<&str> {
|
||||
|
@ -385,6 +385,7 @@ impl error::Error for Error {
|
||||
impl Error {
|
||||
/// Returns the underlying SQLite error if this is [`Error::SqliteFailure`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn sqlite_error(&self) -> Option<&ffi::Error> {
|
||||
match self {
|
||||
Self::SqliteFailure(error, _) => Some(error),
|
||||
@ -395,6 +396,7 @@ impl Error {
|
||||
/// Returns the underlying SQLite error code if this is
|
||||
/// [`Error::SqliteFailure`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn sqlite_error_code(&self) -> Option<ffi::ErrorCode> {
|
||||
self.sqlite_error().map(|error| error.code)
|
||||
}
|
||||
@ -404,7 +406,6 @@ impl Error {
|
||||
|
||||
#[cold]
|
||||
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
|
||||
// TODO sqlite3_error_offset // 3.38.0, #1130
|
||||
Error::SqliteFailure(ffi::Error::new(code), message)
|
||||
}
|
||||
|
||||
@ -458,10 +459,7 @@ pub fn check(code: c_int) -> Result<()> {
|
||||
/// Transform Rust error to SQLite error (message and code).
|
||||
/// # Safety
|
||||
/// This function is unsafe because it uses raw pointer
|
||||
pub unsafe fn to_sqlite_error(
|
||||
e: &Error,
|
||||
err_msg: *mut *mut std::os::raw::c_char,
|
||||
) -> std::os::raw::c_int {
|
||||
pub unsafe fn to_sqlite_error(e: &Error, err_msg: *mut *mut std::os::raw::c_char) -> c_int {
|
||||
use crate::util::alloc;
|
||||
match e {
|
||||
Error::SqliteFailure(err, s) => {
|
||||
|
@ -272,11 +272,11 @@ where
|
||||
/// call to [`step()`](Aggregate::step) to set up the context for an
|
||||
/// invocation of the function. (Note: `init()` will not be called if
|
||||
/// there are no rows.)
|
||||
fn init(&self, _: &mut Context<'_>) -> Result<A>;
|
||||
fn init(&self, ctx: &mut Context<'_>) -> Result<A>;
|
||||
|
||||
/// "step" function called once for each row in an aggregate group. May be
|
||||
/// called 0 times if there are no rows.
|
||||
fn step(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
|
||||
fn step(&self, ctx: &mut Context<'_>, acc: &mut A) -> Result<()>;
|
||||
|
||||
/// Computes and returns the final result. Will be called exactly once for
|
||||
/// each invocation of the function. If [`step()`](Aggregate::step) was
|
||||
@ -287,7 +287,7 @@ where
|
||||
/// given `None`.
|
||||
///
|
||||
/// 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
|
||||
@ -301,10 +301,10 @@ where
|
||||
{
|
||||
/// Returns the current value of the aggregate. Unlike xFinal, the
|
||||
/// 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.
|
||||
fn inverse(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
|
||||
fn inverse(&self, ctx: &mut Context<'_>, acc: &mut A) -> Result<()>;
|
||||
}
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
if (*pac as *mut A).is_null() {
|
||||
*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
|
||||
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
|
||||
let a: Option<A> = match aggregate_context(ctx, 0) {
|
||||
Some(pac) => {
|
||||
Some(pac) =>
|
||||
{
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
if (*pac as *mut A).is_null() {
|
||||
None
|
||||
} else {
|
||||
@ -753,7 +756,9 @@ where
|
||||
// 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.
|
||||
let a: Option<&A> = match aggregate_context(ctx, 0) {
|
||||
Some(pac) => {
|
||||
Some(pac) =>
|
||||
{
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
if (*pac as *mut A).is_null() {
|
||||
None
|
||||
} else {
|
||||
@ -839,7 +844,7 @@ mod test {
|
||||
// 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
|
||||
// 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");
|
||||
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||
let regexp: std::sync::Arc<Regex> = ctx
|
||||
@ -860,7 +865,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_regexp_with_auxilliary() -> Result<()> {
|
||||
fn test_function_regexp_with_auxiliary() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch(
|
||||
"BEGIN;
|
||||
@ -874,7 +879,7 @@ mod test {
|
||||
"regexp",
|
||||
2,
|
||||
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|
||||
regexp_with_auxilliary,
|
||||
regexp_with_auxiliary,
|
||||
)?;
|
||||
|
||||
let result: bool = db.one_column("SELECT regexp('l.s[aeiouy]', 'lisa')")?;
|
||||
|
@ -776,9 +776,10 @@ mod test {
|
||||
.unwrap();
|
||||
|
||||
let authorizer = move |ctx: AuthContext<'_>| match ctx.action {
|
||||
AuthAction::Read { column_name, .. } if column_name == "private" => {
|
||||
Authorization::Ignore
|
||||
}
|
||||
AuthAction::Read {
|
||||
column_name: "private",
|
||||
..
|
||||
} => Authorization::Ignore,
|
||||
AuthAction::DropTable { .. } => Authorization::Deny,
|
||||
AuthAction::Pragma { .. } => panic!("shouldn't be called"),
|
||||
_ => Authorization::Allow,
|
||||
|
@ -40,7 +40,7 @@ pub struct InnerConnection {
|
||||
unsafe impl Send for 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]
|
||||
pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
||||
InnerConnection {
|
||||
|
80
src/lib.rs
80
src/lib.rs
@ -74,6 +74,7 @@ use crate::raw_statement::RawStatement;
|
||||
use crate::types::ValueRef;
|
||||
|
||||
pub use crate::cache::CachedStatement;
|
||||
#[cfg(feature = "column_decltype")]
|
||||
pub use crate::column::Column;
|
||||
pub use crate::error::{to_sqlite_error, Error};
|
||||
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::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows};
|
||||
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::types::ToSql;
|
||||
pub use crate::version::*;
|
||||
#[cfg(feature = "rusqlite-macros")]
|
||||
#[doc(hidden)]
|
||||
pub use rusqlite_macros::__bind;
|
||||
|
||||
mod error;
|
||||
|
||||
@ -119,6 +125,9 @@ mod params;
|
||||
mod pragma;
|
||||
mod raw_statement;
|
||||
mod row;
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
|
||||
pub mod serialize;
|
||||
#[cfg(feature = "session")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "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.
|
||||
pub type Result<T, E = Error> = result::Result<T, E>;
|
||||
|
||||
@ -565,7 +619,7 @@ impl Connection {
|
||||
#[inline]
|
||||
pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize> {
|
||||
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.
|
||||
@ -648,7 +702,7 @@ impl Connection {
|
||||
|
||||
// https://sqlite.org/tclsqlite.html#onecolumn
|
||||
#[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))
|
||||
}
|
||||
|
||||
@ -920,7 +974,7 @@ impl Connection {
|
||||
/// This function is unsafe because improper use may impact the Connection.
|
||||
/// In particular, it should only be called on connections created
|
||||
/// and owned by the caller, e.g. as a result of calling
|
||||
/// ffi::sqlite3_open().
|
||||
/// `ffi::sqlite3_open`().
|
||||
#[inline]
|
||||
pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result<Connection> {
|
||||
let db = InnerConnection::new(db, true);
|
||||
@ -1047,7 +1101,7 @@ impl<'conn> Iterator for Batch<'conn, '_> {
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Flags for opening SQLite database connections. See
|
||||
/// [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
|
||||
/// [sqlite3_open_v2](https://www.sqlite.org/c3ref/open.html) for details.
|
||||
///
|
||||
/// The default open flags are `SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE
|
||||
/// | SQLITE_OPEN_URI | SQLITE_OPEN_NO_MUTEX`. See [`Connection::open`] for
|
||||
@ -2122,9 +2176,25 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn db_readonly() -> Result<()> {
|
||||
fn db_readonly() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
assert!(!db.is_readonly(MAIN_DB)?);
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -384,7 +384,6 @@ mod test {
|
||||
let mut rows = table_info.query(["sqlite_master"])?;
|
||||
|
||||
while let Some(row) = rows.next()? {
|
||||
let row = row;
|
||||
let column: String = row.get(1)?;
|
||||
columns.push(column);
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ impl<'stmt> Rows<'stmt> {
|
||||
/// This interface is not compatible with Rust's `Iterator` trait, because
|
||||
/// the lifetime of the returned row is tied to the lifetime of `self`.
|
||||
/// This is a fallible "streaming iterator". For a more natural interface,
|
||||
/// consider using [`query_map`](crate::Statement::query_map) or
|
||||
/// [`query_and_then`](crate::Statement::query_and_then) instead, which
|
||||
/// consider using [`query_map`](Statement::query_map) or
|
||||
/// [`query_and_then`](Statement::query_and_then) instead, which
|
||||
/// return types that implement `Iterator`.
|
||||
#[allow(clippy::should_implement_trait)] // cannot implement Iterator
|
||||
#[inline]
|
||||
@ -247,7 +247,7 @@ pub struct Row<'stmt> {
|
||||
impl<'stmt> Row<'stmt> {
|
||||
/// 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,
|
||||
/// including:
|
||||
@ -330,7 +330,7 @@ impl<'stmt> Row<'stmt> {
|
||||
/// it can be difficult to use, and most callers will be better served by
|
||||
/// [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap).
|
||||
///
|
||||
/// ## Failure
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if calling [`row.get_ref(idx)`](Row::get_ref) would return an
|
||||
/// error, including:
|
||||
|
167
src/serialize.rs
Normal file
167
src/serialize.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -405,7 +405,7 @@ impl Drop for ChangesetIter<'_> {
|
||||
}
|
||||
|
||||
/// 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).
|
||||
// TODO enum ? Delete, Insert, Update, ...
|
||||
pub struct ChangesetItem {
|
||||
|
@ -435,6 +435,10 @@ impl Statement<'_> {
|
||||
///
|
||||
/// Will return `None` if the column index is out of bounds or if the
|
||||
/// parameter is positional.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when parameter name is not valid UTF-8.
|
||||
#[inline]
|
||||
pub fn parameter_name(&self, index: usize) -> Option<&'_ str> {
|
||||
self.stmt.bind_parameter_name(index as i32).map(|name| {
|
||||
@ -450,7 +454,7 @@ impl Statement<'_> {
|
||||
{
|
||||
let expected = self.stmt.bind_parameter_count();
|
||||
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.
|
||||
if index > expected {
|
||||
break;
|
||||
@ -744,7 +748,7 @@ impl Statement<'_> {
|
||||
|
||||
/// Reset all bindings
|
||||
pub fn clear_bindings(&mut self) {
|
||||
self.stmt.clear_bindings()
|
||||
self.stmt.clear_bindings();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ impl Transaction<'_> {
|
||||
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
|
||||
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
|
||||
};
|
||||
conn.execute_batch(query).map(move |_| Transaction {
|
||||
conn.execute_batch(query).map(move |()| Transaction {
|
||||
conn,
|
||||
drop_behavior: DropBehavior::Rollback,
|
||||
})
|
||||
@ -251,7 +251,7 @@ impl Savepoint<'_> {
|
||||
fn with_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>> {
|
||||
let name = name.into();
|
||||
conn.execute_batch(&format!("SAVEPOINT {name}"))
|
||||
.map(|_| Savepoint {
|
||||
.map(|()| Savepoint {
|
||||
conn,
|
||||
name,
|
||||
drop_behavior: DropBehavior::Rollback,
|
||||
@ -346,8 +346,8 @@ impl Savepoint<'_> {
|
||||
match self.drop_behavior() {
|
||||
DropBehavior::Commit => self
|
||||
.commit_()
|
||||
.or_else(|_| self.rollback().and_then(|_| self.commit_())),
|
||||
DropBehavior::Rollback => self.rollback().and_then(|_| self.commit_()),
|
||||
.or_else(|_| self.rollback().and_then(|()| self.commit_())),
|
||||
DropBehavior::Rollback => self.rollback().and_then(|()| self.commit_()),
|
||||
DropBehavior::Ignore => Ok(()),
|
||||
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 to commit, you must call [`commit`](Savepoint::commit) or
|
||||
/// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::
|
||||
/// set_drop_behavior).
|
||||
/// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::set_drop_behavior).
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
|
@ -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::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::{Error, Result};
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::format_description::FormatItem;
|
||||
use time::macros::format_description;
|
||||
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
|
||||
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
||||
|
||||
const PRIMITIVE_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] =
|
||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
|
||||
const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] =
|
||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
|
||||
const PRIMITIVE_DATE_TIME_Z_FORMAT: &[FormatItem<'_>] =
|
||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]Z");
|
||||
const OFFSET_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]"
|
||||
);
|
||||
const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
|
||||
const OFFSET_DATE_TIME_ENCODING: &[FormatItem<'_>] = format_description!(
|
||||
version = 2,
|
||||
"[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!(
|
||||
version = 2,
|
||||
"[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 {
|
||||
#[inline]
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
// FIXME keep original offset
|
||||
let time_string = self
|
||||
.to_offset(UtcOffset::UTC)
|
||||
.format(&PRIMITIVE_DATE_TIME_Z_FORMAT)
|
||||
.format(&OFFSET_DATE_TIME_ENCODING)
|
||||
.map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
|
||||
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 {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().and_then(|s| {
|
||||
if s.len() > 10 && s.as_bytes()[10] == b'T' {
|
||||
// YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM
|
||||
return OffsetDateTime::parse(s, &Rfc3339)
|
||||
if let Some(b' ') = s.as_bytes().get(23) {
|
||||
// legacy
|
||||
return OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
|
||||
.map_err(|err| FromSqlError::Other(Box::new(err)));
|
||||
}
|
||||
let s = s.strip_suffix('Z').unwrap_or(s);
|
||||
match s.len() {
|
||||
len if len <= 19 => {
|
||||
// TODO YYYY-MM-DDTHH:MM:SS
|
||||
PrimitiveDateTime::parse(s, &PRIMITIVE_SHORT_DATE_TIME_FORMAT)
|
||||
.map(PrimitiveDateTime::assume_utc)
|
||||
}
|
||||
_ if s.as_bytes()[19] == b':' => {
|
||||
// legacy
|
||||
OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
|
||||
}
|
||||
_ if s.as_bytes()[19] == b'.' => OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
|
||||
.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),
|
||||
if s[8..].contains('+') || s[8..].contains('-') {
|
||||
// Formats 2-7 with timezone
|
||||
return OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
|
||||
.map_err(|err| FromSqlError::Other(Box::new(err)));
|
||||
}
|
||||
// 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)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
mod test {
|
||||
use crate::{Connection, Result};
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
use time::macros::{date, datetime, time};
|
||||
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]
|
||||
fn test_offset_date_time() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
|
||||
let db = checked_memory_handle()?;
|
||||
|
||||
let mut ts_vec = vec![];
|
||||
|
||||
@ -103,47 +196,163 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_values() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
for (s, t) in vec![
|
||||
fn test_offset_date_time_parsing() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
let tests = vec![
|
||||
// Rfc3339
|
||||
(
|
||||
"2013-10-07 08:23:19",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07T08:23:19.123456789Z",
|
||||
datetime!(2013-10-07 8:23:19.123456789 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07 08:23:19Z",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07 08:23:19.123456789Z",
|
||||
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",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07 08:23:19.123Z",
|
||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07 08:23:19.120",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07 08:23:19.123+04:00",
|
||||
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",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07T08:23:19.123Z",
|
||||
datetime!(2013-10-07 8:23:19.123 UTC),
|
||||
),
|
||||
(
|
||||
"2013-10-07T08:23:19.120Z",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
|
||||
"2013-10-07T08:23:19.123+04:00",
|
||||
datetime!(2013-10-07 8:23:19.123 +4),
|
||||
),
|
||||
// Legacy
|
||||
(
|
||||
"2013-10-07 04:23:19-04:00",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T04:23:19-04:00", &Rfc3339).unwrap()),
|
||||
"2013-10-07 08:23:12:987 -07:00",
|
||||
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()),
|
||||
),
|
||||
(
|
||||
"2013-10-07T04:23:19.120-04:00",
|
||||
Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
|
||||
),
|
||||
] {
|
||||
let result: Result<OffsetDateTime> = db.query_row("SELECT ?1", [s], |r| r.get(0));
|
||||
];
|
||||
|
||||
for (s, t) in tests {
|
||||
let result: OffsetDateTime = db.query_row("SELECT ?1", [s], |r| r.get(0))?;
|
||||
assert_eq!(result, t);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
Ok(())
|
||||
@ -151,16 +360,66 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_sqlite_functions() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let result: Result<OffsetDateTime> = db.one_column("SELECT CURRENT_TIMESTAMP");
|
||||
let db = checked_memory_handle()?;
|
||||
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();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
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));
|
||||
fn test_date_param() -> Result<()> {
|
||||
let db = checked_memory_handle()?;
|
||||
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();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -36,8 +36,7 @@ impl ValueRef<'_> {
|
||||
|
||||
impl<'a> ValueRef<'a> {
|
||||
/// If `self` is case `Integer`, returns the integral value. Otherwise,
|
||||
/// returns [`Err(Error::InvalidColumnType)`](crate::Error::
|
||||
/// InvalidColumnType).
|
||||
/// returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_i64(&self) -> FromSqlResult<i64> {
|
||||
match *self {
|
||||
@ -48,8 +47,7 @@ impl<'a> ValueRef<'a> {
|
||||
|
||||
/// If `self` is case `Null` returns None.
|
||||
/// If `self` is case `Integer`, returns the integral value.
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
|
||||
/// InvalidColumnType).
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_i64_or_null(&self) -> FromSqlResult<Option<i64>> {
|
||||
match *self {
|
||||
@ -60,8 +58,7 @@ impl<'a> ValueRef<'a> {
|
||||
}
|
||||
|
||||
/// If `self` is case `Real`, returns the floating point value. Otherwise,
|
||||
/// returns [`Err(Error::InvalidColumnType)`](crate::Error::
|
||||
/// InvalidColumnType).
|
||||
/// returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_f64(&self) -> FromSqlResult<f64> {
|
||||
match *self {
|
||||
@ -72,8 +69,7 @@ impl<'a> ValueRef<'a> {
|
||||
|
||||
/// If `self` is case `Null` returns None.
|
||||
/// If `self` is case `Real`, returns the floating point value.
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
|
||||
/// InvalidColumnType).
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_f64_or_null(&self) -> FromSqlResult<Option<f64>> {
|
||||
match *self {
|
||||
@ -97,8 +93,7 @@ impl<'a> ValueRef<'a> {
|
||||
|
||||
/// If `self` is case `Null` returns None.
|
||||
/// If `self` is case `Text`, returns the string value.
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
|
||||
/// InvalidColumnType).
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_str_or_null(&self) -> FromSqlResult<Option<&'a str>> {
|
||||
match *self {
|
||||
@ -122,8 +117,7 @@ impl<'a> ValueRef<'a> {
|
||||
|
||||
/// If `self` is case `Null` returns None.
|
||||
/// If `self` is case `Blob`, returns the byte slice.
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
|
||||
/// InvalidColumnType).
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_blob_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
|
||||
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`].
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> FromSqlResult<&'a [u8]> {
|
||||
|
@ -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.
|
||||
///
|
||||
/// See [`sqlite3_libversion()`](https://www.sqlite.org/c3ref/libversion.html).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when version is not valid UTF-8.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn version() -> &'static str {
|
||||
|
Loading…
Reference in New Issue
Block a user