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.
# 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 }

View File

@ -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"

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.
* 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`.

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
`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).

View File

@ -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 }

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()],
** [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*)
);

View File

@ -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)

View File

@ -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"

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};
/// 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> {

View File

@ -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) => {

View File

@ -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')")?;

View File

@ -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,

View File

@ -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 {

View File

@ -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(())
}
}

View File

@ -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);
}

View File

@ -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
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
/// [`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 {

View File

@ -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();
}
}

View File

@ -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
///

View File

@ -1,67 +1,155 @@
//! 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)));
}
.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()))
})
}
}
@ -69,13 +157,18 @@ impl FromSql for OffsetDateTime {
#[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(())
}

View File

@ -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]> {

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.
///
/// 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 {