chore: sync current workspace changes
Desktop CI Build / Build macOS ARM64 DMG (push) Has been cancelled
Desktop CI Build / Build Windows x64 (push) Has been cancelled

This commit is contained in:
boos
2026-07-14 03:21:10 +08:00
parent 1512fa23ce
commit 557914fea8
745 changed files with 124917 additions and 11222 deletions
+1
View File
@@ -0,0 +1 @@
{"v":1}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "bab1b022bd56a64f9a08b46b36b97c5cff19d77e"
},
"path_in_vcs": "sqlx-sqlite"
}
+176
View File
@@ -0,0 +1,176 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "sqlx-sqlite"
version = "0.8.6"
authors = [
"Ryan Leckey <leckey.ryan@gmail.com>",
"Austin Bonander <austin.bonander@gmail.com>",
"Chloe Ross <orangesnowfox@gmail.com>",
"Daniel Akhterov <akhterovd@gmail.com>",
]
description = "SQLite driver implementation for SQLx. Not for direct use; see the `sqlx` crate for details."
documentation = "https://docs.rs/sqlx"
license = "MIT OR Apache-2.0"
repository = "https://github.com/launchbadge/sqlx"
[package.metadata.docs.rs]
features = [
"bundled",
"any",
"json",
"chrono",
"time",
"uuid",
]
[dependencies.atoi]
version = "2.0"
[dependencies.chrono]
version = "0.4.34"
features = [
"std",
"clock",
]
optional = true
default-features = false
[dependencies.flume]
version = "0.11.0"
features = ["async"]
default-features = false
[dependencies.futures-channel]
version = "0.3.19"
features = [
"sink",
"alloc",
"std",
]
default-features = false
[dependencies.futures-core]
version = "0.3.19"
default-features = false
[dependencies.futures-executor]
version = "0.3.19"
[dependencies.futures-intrusive]
version = "0.5.0"
[dependencies.futures-util]
version = "0.3.19"
features = [
"alloc",
"sink",
]
default-features = false
[dependencies.libsqlite3-sys]
version = "0.30.1"
features = [
"pkg-config",
"vcpkg",
"unlock_notify",
]
default-features = false
[dependencies.log]
version = "0.4.18"
[dependencies.percent-encoding]
version = "2.1.0"
[dependencies.regex]
version = "1.5.5"
optional = true
[dependencies.serde]
version = "1.0.145"
features = ["derive"]
optional = true
[dependencies.serde_urlencoded]
version = "0.7"
[dependencies.sqlx-core]
version = "=0.8.6"
[dependencies.thiserror]
version = "2.0.0"
[dependencies.time]
version = "0.3.36"
features = [
"formatting",
"parsing",
"macros",
]
optional = true
[dependencies.tracing]
version = "0.1.37"
features = ["log"]
[dependencies.url]
version = "2.2.2"
[dependencies.uuid]
version = "1.1.2"
optional = true
[dev-dependencies.sqlx]
version = "=0.8.6"
features = [
"macros",
"runtime-tokio",
"tls-none",
"sqlite",
]
default-features = false
[features]
any = ["sqlx-core/any"]
bundled = ["libsqlite3-sys/bundled"]
chrono = [
"dep:chrono",
"sqlx-core/chrono",
]
json = [
"sqlx-core/json",
"serde",
]
migrate = ["sqlx-core/migrate"]
offline = [
"sqlx-core/offline",
"serde",
]
preupdate-hook = ["libsqlite3-sys/preupdate_hook"]
regexp = ["dep:regex"]
time = [
"dep:time",
"sqlx-core/time",
]
unbundled = ["libsqlite3-sys/buildtime_bindgen"]
uuid = [
"dep:uuid",
"sqlx-core/uuid",
]
[lints.clippy]
cast_possible_truncation = "deny"
cast_possible_wrap = "deny"
cast_sign_loss = "deny"
disallowed_methods = "deny"
+77
View File
@@ -0,0 +1,77 @@
[package]
name = "sqlx-sqlite"
documentation = "https://docs.rs/sqlx"
description = "SQLite driver implementation for SQLx. Not for direct use; see the `sqlx` crate for details."
version.workspace = true
license.workspace = true
edition.workspace = true
authors.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
any = ["sqlx-core/any"]
json = ["sqlx-core/json", "serde"]
offline = ["sqlx-core/offline", "serde"]
migrate = ["sqlx-core/migrate"]
# Type integrations
chrono = ["dep:chrono", "sqlx-core/chrono"]
time = ["dep:time", "sqlx-core/time"]
uuid = ["dep:uuid", "sqlx-core/uuid"]
regexp = ["dep:regex"]
preupdate-hook = ["libsqlite3-sys/preupdate_hook"]
bundled = ["libsqlite3-sys/bundled"]
unbundled = ["libsqlite3-sys/buildtime_bindgen"]
[dependencies]
futures-core = { version = "0.3.19", default-features = false }
futures-channel = { version = "0.3.19", default-features = false, features = ["sink", "alloc", "std"] }
# used by the SQLite worker thread to block on the async mutex that locks the database handle
futures-executor = { version = "0.3.19" }
futures-intrusive = "0.5.0"
futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink"] }
chrono = { workspace = true, optional = true }
time = { workspace = true, optional = true }
uuid = { workspace = true, optional = true }
url = { version = "2.2.2" }
percent-encoding = "2.1.0"
serde_urlencoded = "0.7"
flume = { version = "0.11.0", default-features = false, features = ["async"] }
atoi = "2.0"
log = "0.4.18"
tracing = { version = "0.1.37", features = ["log"] }
thiserror = "2.0.0"
serde = { version = "1.0.145", features = ["derive"], optional = true }
regex = { version = "1.5.5", optional = true }
[dependencies.libsqlite3-sys]
version = "0.30.1"
default-features = false
features = [
"pkg-config",
"vcpkg",
"unlock_notify"
]
[dependencies.sqlx-core]
workspace = true
[dev-dependencies]
sqlx = { workspace = true, default-features = false, features = ["macros", "runtime-tokio", "tls-none", "sqlite"] }
[lints]
workspace = true
[package.metadata.docs.rs]
features = ["bundled", "any", "json", "chrono", "time", "uuid"]
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 LaunchBadge, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2020 LaunchBadge, LLC
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+237
View File
@@ -0,0 +1,237 @@
use std::borrow::Cow;
use crate::{
Either, Sqlite, SqliteArgumentValue, SqliteArguments, SqliteColumn, SqliteConnectOptions,
SqliteConnection, SqliteQueryResult, SqliteRow, SqliteTransactionManager, SqliteTypeInfo,
};
use futures_core::future::BoxFuture;
use futures_core::stream::BoxStream;
use futures_util::{StreamExt, TryFutureExt, TryStreamExt};
use sqlx_core::any::{
Any, AnyArguments, AnyColumn, AnyConnectOptions, AnyConnectionBackend, AnyQueryResult, AnyRow,
AnyStatement, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind,
};
use crate::type_info::DataType;
use sqlx_core::connection::{ConnectOptions, Connection};
use sqlx_core::database::Database;
use sqlx_core::describe::Describe;
use sqlx_core::executor::Executor;
use sqlx_core::transaction::TransactionManager;
use std::pin::pin;
sqlx_core::declare_driver_with_optional_migrate!(DRIVER = Sqlite);
impl AnyConnectionBackend for SqliteConnection {
fn name(&self) -> &str {
<Sqlite as Database>::NAME
}
fn close(self: Box<Self>) -> BoxFuture<'static, sqlx_core::Result<()>> {
Connection::close(*self)
}
fn close_hard(self: Box<Self>) -> BoxFuture<'static, sqlx_core::Result<()>> {
Connection::close_hard(*self)
}
fn ping(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> {
Connection::ping(self)
}
fn begin(
&mut self,
statement: Option<Cow<'static, str>>,
) -> BoxFuture<'_, sqlx_core::Result<()>> {
SqliteTransactionManager::begin(self, statement)
}
fn commit(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> {
SqliteTransactionManager::commit(self)
}
fn rollback(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> {
SqliteTransactionManager::rollback(self)
}
fn start_rollback(&mut self) {
SqliteTransactionManager::start_rollback(self)
}
fn get_transaction_depth(&self) -> usize {
SqliteTransactionManager::get_transaction_depth(self)
}
fn shrink_buffers(&mut self) {
// NO-OP.
}
fn flush(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> {
Connection::flush(self)
}
fn should_flush(&self) -> bool {
Connection::should_flush(self)
}
#[cfg(feature = "migrate")]
fn as_migrate(
&mut self,
) -> sqlx_core::Result<&mut (dyn sqlx_core::migrate::Migrate + Send + 'static)> {
Ok(self)
}
fn fetch_many<'q>(
&'q mut self,
query: &'q str,
persistent: bool,
arguments: Option<AnyArguments<'q>>,
) -> BoxStream<'q, sqlx_core::Result<Either<AnyQueryResult, AnyRow>>> {
let persistent = persistent && arguments.is_some();
let args = arguments.map(map_arguments);
Box::pin(
self.worker
.execute(query, args, self.row_channel_size, persistent, None)
.map_ok(flume::Receiver::into_stream)
.try_flatten_stream()
.map(
move |res: sqlx_core::Result<Either<SqliteQueryResult, SqliteRow>>| match res? {
Either::Left(result) => Ok(Either::Left(map_result(result))),
Either::Right(row) => Ok(Either::Right(AnyRow::try_from(&row)?)),
},
),
)
}
fn fetch_optional<'q>(
&'q mut self,
query: &'q str,
persistent: bool,
arguments: Option<AnyArguments<'q>>,
) -> BoxFuture<'q, sqlx_core::Result<Option<AnyRow>>> {
let persistent = persistent && arguments.is_some();
let args = arguments.map(map_arguments);
Box::pin(async move {
let mut stream = pin!(
self.worker
.execute(query, args, self.row_channel_size, persistent, Some(1))
.map_ok(flume::Receiver::into_stream)
.await?
);
if let Some(Either::Right(row)) = stream.try_next().await? {
return Ok(Some(AnyRow::try_from(&row)?));
}
Ok(None)
})
}
fn prepare_with<'c, 'q: 'c>(
&'c mut self,
sql: &'q str,
_parameters: &[AnyTypeInfo],
) -> BoxFuture<'c, sqlx_core::Result<AnyStatement<'q>>> {
Box::pin(async move {
let statement = Executor::prepare_with(self, sql, &[]).await?;
AnyStatement::try_from_statement(sql, &statement, statement.column_names.clone())
})
}
fn describe<'q>(&'q mut self, sql: &'q str) -> BoxFuture<'q, sqlx_core::Result<Describe<Any>>> {
Box::pin(async move { Executor::describe(self, sql).await?.try_into_any() })
}
}
impl<'a> TryFrom<&'a SqliteTypeInfo> for AnyTypeInfo {
type Error = sqlx_core::Error;
fn try_from(sqlite_type: &'a SqliteTypeInfo) -> Result<Self, Self::Error> {
Ok(AnyTypeInfo {
kind: match &sqlite_type.0 {
DataType::Null => AnyTypeInfoKind::Null,
DataType::Int4 => AnyTypeInfoKind::Integer,
DataType::Integer => AnyTypeInfoKind::BigInt,
DataType::Float => AnyTypeInfoKind::Double,
DataType::Blob => AnyTypeInfoKind::Blob,
DataType::Text => AnyTypeInfoKind::Text,
_ => {
return Err(sqlx_core::Error::AnyDriverError(
format!("Any driver does not support the SQLite type {sqlite_type:?}")
.into(),
))
}
},
})
}
}
impl<'a> TryFrom<&'a SqliteColumn> for AnyColumn {
type Error = sqlx_core::Error;
fn try_from(col: &'a SqliteColumn) -> Result<Self, Self::Error> {
let type_info =
AnyTypeInfo::try_from(&col.type_info).map_err(|e| sqlx_core::Error::ColumnDecode {
index: col.name.to_string(),
source: e.into(),
})?;
Ok(AnyColumn {
ordinal: col.ordinal,
name: col.name.clone(),
type_info,
})
}
}
impl<'a> TryFrom<&'a SqliteRow> for AnyRow {
type Error = sqlx_core::Error;
fn try_from(row: &'a SqliteRow) -> Result<Self, Self::Error> {
AnyRow::map_from(row, row.column_names.clone())
}
}
impl<'a> TryFrom<&'a AnyConnectOptions> for SqliteConnectOptions {
type Error = sqlx_core::Error;
fn try_from(opts: &'a AnyConnectOptions) -> Result<Self, Self::Error> {
let mut opts_out = SqliteConnectOptions::from_url(&opts.database_url)?;
opts_out.log_settings = opts.log_settings.clone();
Ok(opts_out)
}
}
/// Instead of `AnyArguments::convert_into()`, we can do a direct mapping and preserve the lifetime.
fn map_arguments(args: AnyArguments<'_>) -> SqliteArguments<'_> {
SqliteArguments {
values: args
.values
.0
.into_iter()
.map(|val| match val {
AnyValueKind::Null(_) => SqliteArgumentValue::Null,
AnyValueKind::Bool(b) => SqliteArgumentValue::Int(b as i32),
AnyValueKind::SmallInt(i) => SqliteArgumentValue::Int(i as i32),
AnyValueKind::Integer(i) => SqliteArgumentValue::Int(i),
AnyValueKind::BigInt(i) => SqliteArgumentValue::Int64(i),
AnyValueKind::Real(r) => SqliteArgumentValue::Double(r as f64),
AnyValueKind::Double(d) => SqliteArgumentValue::Double(d),
AnyValueKind::Text(t) => SqliteArgumentValue::Text(t),
AnyValueKind::Blob(b) => SqliteArgumentValue::Blob(b),
// AnyValueKind is `#[non_exhaustive]` but we should have covered everything
_ => unreachable!("BUG: missing mapping for {val:?}"),
})
.collect(),
}
}
fn map_result(res: SqliteQueryResult) -> AnyQueryResult {
AnyQueryResult {
rows_affected: res.rows_affected(),
last_insert_id: Some(res.last_insert_rowid()),
}
}
+155
View File
@@ -0,0 +1,155 @@
use crate::encode::{Encode, IsNull};
use crate::error::Error;
use crate::statement::StatementHandle;
use crate::Sqlite;
use atoi::atoi;
use libsqlite3_sys::SQLITE_OK;
use std::borrow::Cow;
pub(crate) use sqlx_core::arguments::*;
use sqlx_core::error::BoxDynError;
#[derive(Debug, Clone)]
pub enum SqliteArgumentValue<'q> {
Null,
Text(Cow<'q, str>),
Blob(Cow<'q, [u8]>),
Double(f64),
Int(i32),
Int64(i64),
}
#[derive(Default, Debug, Clone)]
pub struct SqliteArguments<'q> {
pub(crate) values: Vec<SqliteArgumentValue<'q>>,
}
impl<'q> SqliteArguments<'q> {
pub(crate) fn add<T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'q, Sqlite>,
{
let value_length_before_encoding = self.values.len();
match value.encode(&mut self.values) {
Ok(IsNull::Yes) => self.values.push(SqliteArgumentValue::Null),
Ok(IsNull::No) => {}
Err(error) => {
// reset the value buffer to its previous value if encoding failed so we don't leave a half-encoded value behind
self.values.truncate(value_length_before_encoding);
return Err(error);
}
};
Ok(())
}
pub(crate) fn into_static(self) -> SqliteArguments<'static> {
SqliteArguments {
values: self
.values
.into_iter()
.map(SqliteArgumentValue::into_static)
.collect(),
}
}
}
impl<'q> Arguments<'q> for SqliteArguments<'q> {
type Database = Sqlite;
fn reserve(&mut self, len: usize, _size_hint: usize) {
self.values.reserve(len);
}
fn add<T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'q, Self::Database>,
{
self.add(value)
}
fn len(&self) -> usize {
self.values.len()
}
}
impl SqliteArguments<'_> {
pub(super) fn bind(&self, handle: &mut StatementHandle, offset: usize) -> Result<usize, Error> {
let mut arg_i = offset;
// for handle in &statement.handles {
let cnt = handle.bind_parameter_count();
for param_i in 1..=cnt {
// figure out the index of this bind parameter into our argument tuple
let n: usize = if let Some(name) = handle.bind_parameter_name(param_i) {
if let Some(name) = name.strip_prefix('?') {
// parameter should have the form ?NNN
atoi(name.as_bytes()).expect("parameter of the form ?NNN")
} else if let Some(name) = name.strip_prefix('$') {
// parameter should have the form $NNN
atoi(name.as_bytes()).ok_or_else(|| {
err_protocol!(
"parameters with non-integer names are not currently supported: {}",
name
)
})?
} else {
return Err(err_protocol!("unsupported SQL parameter format: {}", name));
}
} else {
arg_i += 1;
arg_i
};
if n > self.values.len() {
// SQLite treats unbound variables as NULL
// we reproduce this here
// If you are reading this and think this should be an error, open an issue and we can
// discuss configuring this somehow
// Note that the query macros have a different way of enforcing
// argument arity
break;
}
self.values[n - 1].bind(handle, param_i)?;
}
Ok(arg_i - offset)
}
}
impl SqliteArgumentValue<'_> {
fn into_static(self) -> SqliteArgumentValue<'static> {
use SqliteArgumentValue::*;
match self {
Null => Null,
Text(text) => Text(text.into_owned().into()),
Blob(blob) => Blob(blob.into_owned().into()),
Int(v) => Int(v),
Int64(v) => Int64(v),
Double(v) => Double(v),
}
}
fn bind(&self, handle: &mut StatementHandle, i: usize) -> Result<(), Error> {
use SqliteArgumentValue::*;
let status = match self {
Text(v) => handle.bind_text(i, v),
Blob(v) => handle.bind_blob(i, v),
Int(v) => handle.bind_int(i, *v),
Int64(v) => handle.bind_int64(i, *v),
Double(v) => handle.bind_double(i, *v),
Null => handle.bind_null(i),
};
if status != SQLITE_OK {
return Err(handle.last_error().into());
}
Ok(())
}
}
+28
View File
@@ -0,0 +1,28 @@
use crate::ext::ustr::UStr;
use crate::{Sqlite, SqliteTypeInfo};
pub(crate) use sqlx_core::column::*;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))]
pub struct SqliteColumn {
pub(crate) name: UStr,
pub(crate) ordinal: usize,
pub(crate) type_info: SqliteTypeInfo,
}
impl Column for SqliteColumn {
type Database = Sqlite;
fn ordinal(&self) -> usize {
self.ordinal
}
fn name(&self) -> &str {
&self.name
}
fn type_info(&self) -> &SqliteTypeInfo {
&self.type_info
}
}
+155
View File
@@ -0,0 +1,155 @@
use std::cmp::Ordering;
use std::ffi::CString;
use std::fmt::{self, Debug, Formatter};
use std::os::raw::{c_int, c_void};
use std::slice;
use std::str::from_utf8_unchecked;
use std::sync::Arc;
use libsqlite3_sys::{sqlite3_create_collation_v2, SQLITE_OK, SQLITE_UTF8};
use crate::connection::handle::ConnectionHandle;
use crate::error::Error;
#[derive(Clone)]
pub struct Collation {
name: Arc<str>,
#[allow(clippy::type_complexity)]
collate: Arc<dyn Fn(&str, &str) -> Ordering + Send + Sync + 'static>,
// SAFETY: these must match the concrete type of `collate`
call: unsafe extern "C" fn(
arg1: *mut c_void,
arg2: c_int,
arg3: *const c_void,
arg4: c_int,
arg5: *const c_void,
) -> c_int,
free: unsafe extern "C" fn(*mut c_void),
}
impl Collation {
pub fn new<N, F>(name: N, collate: F) -> Self
where
N: Into<Arc<str>>,
F: Fn(&str, &str) -> Ordering + Send + Sync + 'static,
{
unsafe extern "C" fn drop_arc_value<T>(p: *mut c_void) {
drop(Arc::from_raw(p as *mut T));
}
Collation {
name: name.into(),
collate: Arc::new(collate),
call: call_boxed_closure::<F>,
free: drop_arc_value::<F>,
}
}
pub(crate) fn create(&self, handle: &mut ConnectionHandle) -> Result<(), Error> {
let raw_f = Arc::into_raw(Arc::clone(&self.collate));
let c_name = CString::new(&*self.name)
.map_err(|_| err_protocol!("invalid collation name: {:?}", self.name))?;
let flags = SQLITE_UTF8;
let r = unsafe {
sqlite3_create_collation_v2(
handle.as_ptr(),
c_name.as_ptr(),
flags,
raw_f as *mut c_void,
Some(self.call),
Some(self.free),
)
};
if r == SQLITE_OK {
Ok(())
} else {
// The xDestroy callback is not called if the sqlite3_create_collation_v2() function fails.
drop(unsafe { Arc::from_raw(raw_f) });
Err(handle.expect_error().into())
}
}
}
impl Debug for Collation {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Collation")
.field("name", &self.name)
.finish_non_exhaustive()
}
}
pub(crate) fn create_collation<F>(
handle: &mut ConnectionHandle,
name: &str,
compare: F,
) -> Result<(), Error>
where
F: Fn(&str, &str) -> Ordering + Send + Sync + 'static,
{
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
drop(Box::from_raw(p as *mut T));
}
let boxed_f: *mut F = Box::into_raw(Box::new(compare));
let c_name =
CString::new(name).map_err(|_| err_protocol!("invalid collation name: {}", name))?;
let flags = SQLITE_UTF8;
let r = unsafe {
sqlite3_create_collation_v2(
handle.as_ptr(),
c_name.as_ptr(),
flags,
boxed_f as *mut c_void,
Some(call_boxed_closure::<F>),
Some(free_boxed_value::<F>),
)
};
if r == SQLITE_OK {
Ok(())
} else {
// The xDestroy callback is not called if the sqlite3_create_collation_v2() function fails.
drop(unsafe { Box::from_raw(boxed_f) });
Err(handle.expect_error().into())
}
}
unsafe extern "C" fn call_boxed_closure<C>(
data: *mut c_void,
left_len: c_int,
left_ptr: *const c_void,
right_len: c_int,
right_ptr: *const c_void,
) -> c_int
where
C: Fn(&str, &str) -> Ordering,
{
let boxed_f: *mut C = data as *mut C;
// Note: unwinding is now caught at the FFI boundary:
// https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding
assert!(!boxed_f.is_null());
let left_len =
usize::try_from(left_len).unwrap_or_else(|_| panic!("left_len out of range: {left_len}"));
let right_len = usize::try_from(right_len)
.unwrap_or_else(|_| panic!("right_len out of range: {right_len}"));
let s1 = {
let c_slice = slice::from_raw_parts(left_ptr as *const u8, left_len);
from_utf8_unchecked(c_slice)
};
let s2 = {
let c_slice = slice::from_raw_parts(right_ptr as *const u8, right_len);
from_utf8_unchecked(c_slice)
};
let t = (*boxed_f)(s1, s2);
match t {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
}
}
+94
View File
@@ -0,0 +1,94 @@
use crate::connection::explain::explain;
use crate::connection::ConnectionState;
use crate::describe::Describe;
use crate::error::Error;
use crate::statement::VirtualStatement;
use crate::type_info::DataType;
use crate::{Sqlite, SqliteColumn};
use sqlx_core::Either;
use std::convert::identity;
pub(crate) fn describe(conn: &mut ConnectionState, query: &str) -> Result<Describe<Sqlite>, Error> {
// describing a statement from SQLite can be involved
// each SQLx statement is comprised of multiple SQL statements
let mut statement = VirtualStatement::new(query, false)?;
let mut columns = Vec::new();
let mut nullable = Vec::new();
let mut num_params = 0;
// we start by finding the first statement that *can* return results
while let Some(stmt) = statement.prepare_next(&mut conn.handle)? {
num_params += stmt.handle.bind_parameter_count();
let mut stepped = false;
let num = stmt.handle.column_count();
if num == 0 {
// no columns in this statement; skip
continue;
}
// next we try to use [column_decltype] to inspect the type of each column
columns.reserve(num);
// as a last resort, we explain the original query and attempt to
// infer what would the expression types be as a fallback
// to [column_decltype]
// if explain.. fails, ignore the failure and we'll have no fallback
let (fallback, fallback_nullable) = match explain(conn, stmt.handle.sql()) {
Ok(v) => v,
Err(error) => {
tracing::debug!(%error, "describe: explain introspection failed");
(vec![], vec![])
}
};
for col in 0..num {
let name = stmt.handle.column_name(col).to_owned();
let type_info = if let Some(ty) = stmt.handle.column_decltype(col) {
ty
} else {
// if that fails, we back up and attempt to step the statement
// once *if* its read-only and then use [column_type] as a
// fallback to [column_decltype]
if !stepped && stmt.handle.read_only() {
stepped = true;
let _ = stmt.handle.step();
}
let mut ty = stmt.handle.column_type_info(col);
if ty.0 == DataType::Null {
if let Some(fallback) = fallback.get(col).cloned() {
ty = fallback;
}
}
ty
};
// check explain
let col_nullable = stmt.handle.column_nullable(col)?;
let exp_nullable = fallback_nullable.get(col).copied().and_then(identity);
nullable.push(exp_nullable.or(col_nullable));
columns.push(SqliteColumn {
name: name.into(),
type_info,
ordinal: col,
});
}
}
Ok(Describe {
columns,
parameters: Some(Either::Right(num_params)),
nullable,
})
}
+303
View File
@@ -0,0 +1,303 @@
use crate::connection::handle::ConnectionHandle;
use crate::connection::LogSettings;
use crate::connection::{ConnectionState, Statements};
use crate::error::Error;
use crate::{SqliteConnectOptions, SqliteError};
use libsqlite3_sys::{
sqlite3, sqlite3_busy_timeout, sqlite3_db_config, sqlite3_extended_result_codes, sqlite3_free,
sqlite3_load_extension, sqlite3_open_v2, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_OK,
SQLITE_OPEN_CREATE, SQLITE_OPEN_FULLMUTEX, SQLITE_OPEN_MEMORY, SQLITE_OPEN_NOMUTEX,
SQLITE_OPEN_PRIVATECACHE, SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_SHAREDCACHE,
SQLITE_OPEN_URI,
};
use percent_encoding::NON_ALPHANUMERIC;
use sqlx_core::IndexMap;
use std::collections::BTreeMap;
use std::ffi::{c_void, CStr, CString};
use std::io;
use std::os::raw::c_int;
use std::ptr::{addr_of_mut, null, null_mut};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
// This was originally `AtomicU64` but that's not supported on MIPS (or PowerPC):
// https://github.com/launchbadge/sqlx/issues/2859
// https://doc.rust-lang.org/stable/std/sync/atomic/index.html#portability
static THREAD_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Copy, Clone)]
enum SqliteLoadExtensionMode {
/// Enables only the C-API, leaving the SQL function disabled.
Enable,
/// Disables both the C-API and the SQL function.
DisableAll,
}
impl SqliteLoadExtensionMode {
fn to_int(self) -> c_int {
match self {
SqliteLoadExtensionMode::Enable => 1,
SqliteLoadExtensionMode::DisableAll => 0,
}
}
}
pub struct EstablishParams {
filename: CString,
open_flags: i32,
busy_timeout: Duration,
statement_cache_capacity: usize,
log_settings: LogSettings,
extensions: IndexMap<CString, Option<CString>>,
pub(crate) thread_name: String,
pub(crate) command_channel_size: usize,
#[cfg(feature = "regexp")]
register_regexp_function: bool,
}
impl EstablishParams {
pub fn from_options(options: &SqliteConnectOptions) -> Result<Self, Error> {
let mut filename = options
.filename
.to_str()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"filename passed to SQLite must be valid UTF-8",
)
})?
.to_owned();
// Set common flags we expect to have in sqlite
let mut flags = SQLITE_OPEN_URI;
// By default, we connect to an in-memory database.
// [SQLITE_OPEN_NOMUTEX] will instruct [sqlite3_open_v2] to return an error if it
// cannot satisfy our wish for a thread-safe, lock-free connection object
flags |= if options.serialized {
SQLITE_OPEN_FULLMUTEX
} else {
SQLITE_OPEN_NOMUTEX
};
flags |= if options.read_only {
SQLITE_OPEN_READONLY
} else if options.create_if_missing {
SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE
} else {
SQLITE_OPEN_READWRITE
};
if options.in_memory {
flags |= SQLITE_OPEN_MEMORY;
}
flags |= if options.shared_cache {
SQLITE_OPEN_SHAREDCACHE
} else {
SQLITE_OPEN_PRIVATECACHE
};
let mut query_params = BTreeMap::new();
if options.immutable {
query_params.insert("immutable", "true");
}
if let Some(vfs) = options.vfs.as_deref() {
query_params.insert("vfs", vfs);
}
if !query_params.is_empty() {
filename = format!(
"file:{}?{}",
percent_encoding::percent_encode(filename.as_bytes(), NON_ALPHANUMERIC),
serde_urlencoded::to_string(&query_params).unwrap()
);
}
let filename = CString::new(filename).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"filename passed to SQLite must not contain nul bytes",
)
})?;
let extensions = options
.extensions
.iter()
.map(|(name, entry)| {
let entry = entry
.as_ref()
.map(|e| {
CString::new(e.as_bytes()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"extension entrypoint names passed to SQLite must not contain nul bytes"
)
})
})
.transpose()?;
Ok((
CString::new(name.as_bytes()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"extension names passed to SQLite must not contain nul bytes",
)
})?,
entry,
))
})
.collect::<Result<IndexMap<CString, Option<CString>>, io::Error>>()?;
let thread_id = THREAD_ID.fetch_add(1, Ordering::AcqRel);
Ok(Self {
filename,
open_flags: flags,
busy_timeout: options.busy_timeout,
statement_cache_capacity: options.statement_cache_capacity,
log_settings: options.log_settings.clone(),
extensions,
thread_name: (options.thread_name)(thread_id as u64),
command_channel_size: options.command_channel_size,
#[cfg(feature = "regexp")]
register_regexp_function: options.register_regexp_function,
})
}
// Enable extension loading via the db_config function, as recommended by the docs rather
// than the more obvious `sqlite3_enable_load_extension`
// https://www.sqlite.org/c3ref/db_config.html
// https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension
unsafe fn sqlite3_set_load_extension(
db: *mut sqlite3,
mode: SqliteLoadExtensionMode,
) -> Result<(), Error> {
let status = sqlite3_db_config(
db,
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
mode.to_int(),
null::<i32>(),
);
if status != SQLITE_OK {
return Err(Error::Database(Box::new(SqliteError::new(db))));
}
Ok(())
}
pub(crate) fn establish(&self) -> Result<ConnectionState, Error> {
let mut handle = null_mut();
// <https://www.sqlite.org/c3ref/open.html>
let mut status = unsafe {
sqlite3_open_v2(self.filename.as_ptr(), &mut handle, self.open_flags, null())
};
if handle.is_null() {
// Failed to allocate memory
return Err(Error::Io(io::Error::new(
io::ErrorKind::OutOfMemory,
"SQLite is unable to allocate memory to hold the sqlite3 object",
)));
}
// SAFE: tested for NULL just above
// This allows any returns below to close this handle with RAII
let mut handle = unsafe { ConnectionHandle::new(handle) };
if status != SQLITE_OK {
return Err(Error::Database(Box::new(handle.expect_error())));
}
// Enable extended result codes
// https://www.sqlite.org/c3ref/extended_result_codes.html
unsafe {
// NOTE: ignore the failure here
sqlite3_extended_result_codes(handle.as_ptr(), 1);
}
if !self.extensions.is_empty() {
// Enable loading extensions
unsafe {
Self::sqlite3_set_load_extension(handle.as_ptr(), SqliteLoadExtensionMode::Enable)?;
}
for ext in self.extensions.iter() {
// `sqlite3_load_extension` is unusual as it returns its errors via an out-pointer
// rather than by calling `sqlite3_errmsg`
let mut error_msg = null_mut();
status = unsafe {
sqlite3_load_extension(
handle.as_ptr(),
ext.0.as_ptr(),
ext.1.as_ref().map_or(null(), |e| e.as_ptr()),
addr_of_mut!(error_msg),
)
};
if status != SQLITE_OK {
let mut e = handle.expect_error();
// SAFETY: We become responsible for any memory allocation at `&error`, so test
// for null and take an RAII version for returns
if !error_msg.is_null() {
e = e.with_message(unsafe {
let msg = CStr::from_ptr(error_msg).to_string_lossy().into();
sqlite3_free(error_msg as *mut c_void);
msg
});
}
return Err(Error::Database(Box::new(e)));
}
} // Preempt any hypothetical security issues arising from leaving ENABLE_LOAD_EXTENSION
// on by disabling the flag again once we've loaded all the requested modules.
// Fail-fast (via `?`) if disabling the extension loader didn't work for some reason,
// avoids an unexpected state going undetected.
unsafe {
Self::sqlite3_set_load_extension(
handle.as_ptr(),
SqliteLoadExtensionMode::DisableAll,
)?;
}
}
#[cfg(feature = "regexp")]
if self.register_regexp_function {
// configure a `regexp` function for sqlite, it does not come with one by default
let status = crate::regexp::register(handle.as_ptr());
if status != SQLITE_OK {
return Err(Error::Database(Box::new(handle.expect_error())));
}
}
// Configure a busy timeout
// This causes SQLite to automatically sleep in increasing intervals until the time
// when there is something locked during [sqlite3_step].
//
// We also need to convert the u128 value to i32, checking we're not overflowing.
let ms = i32::try_from(self.busy_timeout.as_millis())
.expect("Given busy timeout value is too big.");
status = unsafe { sqlite3_busy_timeout(handle.as_ptr(), ms) };
if status != SQLITE_OK {
return Err(Error::Database(Box::new(handle.expect_error())));
}
Ok(ConnectionState {
handle,
statements: Statements::new(self.statement_cache_capacity),
log_settings: self.log_settings.clone(),
progress_handler_callback: None,
update_hook_callback: None,
#[cfg(feature = "preupdate-hook")]
preupdate_hook_callback: None,
commit_hook_callback: None,
rollback_hook_callback: None,
})
}
}
+130
View File
@@ -0,0 +1,130 @@
use crate::connection::{ConnectionHandle, ConnectionState};
use crate::error::Error;
use crate::logger::QueryLogger;
use crate::statement::{StatementHandle, VirtualStatement};
use crate::{SqliteArguments, SqliteQueryResult, SqliteRow};
use sqlx_core::Either;
pub struct ExecuteIter<'a> {
handle: &'a mut ConnectionHandle,
statement: &'a mut VirtualStatement,
logger: QueryLogger<'a>,
args: Option<SqliteArguments<'a>>,
/// since a `VirtualStatement` can encompass multiple actual statements,
/// this keeps track of the number of arguments so far
args_used: usize,
goto_next: bool,
}
pub(crate) fn iter<'a>(
conn: &'a mut ConnectionState,
query: &'a str,
args: Option<SqliteArguments<'a>>,
persistent: bool,
) -> Result<ExecuteIter<'a>, Error> {
// fetch the cached statement or allocate a new one
let statement = conn.statements.get(query, persistent)?;
let logger = QueryLogger::new(query, conn.log_settings.clone());
Ok(ExecuteIter {
handle: &mut conn.handle,
statement,
logger,
args,
args_used: 0,
goto_next: true,
})
}
fn bind(
statement: &mut StatementHandle,
arguments: &Option<SqliteArguments<'_>>,
offset: usize,
) -> Result<usize, Error> {
let mut n = 0;
if let Some(arguments) = arguments {
n = arguments.bind(statement, offset)?;
}
Ok(n)
}
impl ExecuteIter<'_> {
pub fn finish(&mut self) -> Result<(), Error> {
for res in self {
let _ = res?;
}
Ok(())
}
}
impl Iterator for ExecuteIter<'_> {
type Item = Result<Either<SqliteQueryResult, SqliteRow>, Error>;
fn next(&mut self) -> Option<Self::Item> {
let statement = if self.goto_next {
let statement = match self.statement.prepare_next(self.handle) {
Ok(Some(statement)) => statement,
Ok(None) => return None,
Err(e) => return Some(Err(e)),
};
self.goto_next = false;
// sanity check: ensure the VM is reset and the bindings are cleared
if let Err(e) = statement.handle.reset() {
return Some(Err(e.into()));
}
statement.handle.clear_bindings();
match bind(statement.handle, &self.args, self.args_used) {
Ok(args_used) => self.args_used += args_used,
Err(e) => return Some(Err(e)),
}
statement
} else {
self.statement.current()?
};
match statement.handle.step() {
Ok(true) => {
self.logger.increment_rows_returned();
Some(Ok(Either::Right(SqliteRow::current(
statement.handle,
statement.columns,
statement.column_names,
))))
}
Ok(false) => {
let last_insert_rowid = self.handle.last_insert_rowid();
let changes = statement.handle.changes();
self.logger.increase_rows_affected(changes);
let done = SqliteQueryResult {
changes,
last_insert_rowid,
};
self.goto_next = true;
Some(Ok(Either::Left(done)))
}
Err(e) => Some(Err(e.into())),
}
}
}
impl Drop for ExecuteIter<'_> {
fn drop(&mut self) {
self.statement.reset().ok();
}
}
+100
View File
@@ -0,0 +1,100 @@
use crate::{
Sqlite, SqliteConnection, SqliteQueryResult, SqliteRow, SqliteStatement, SqliteTypeInfo,
};
use futures_core::future::BoxFuture;
use futures_core::stream::BoxStream;
use futures_util::{stream, FutureExt, StreamExt, TryFutureExt, TryStreamExt};
use sqlx_core::describe::Describe;
use sqlx_core::error::Error;
use sqlx_core::executor::{Execute, Executor};
use sqlx_core::Either;
use std::{future, pin::pin};
impl<'c> Executor<'c> for &'c mut SqliteConnection {
type Database = Sqlite;
fn fetch_many<'e, 'q, E>(
self,
mut query: E,
) -> BoxStream<'e, Result<Either<SqliteQueryResult, SqliteRow>, Error>>
where
'c: 'e,
E: Execute<'q, Self::Database>,
'q: 'e,
E: 'q,
{
let sql = query.sql();
let arguments = match query.take_arguments().map_err(Error::Encode) {
Ok(arguments) => arguments,
Err(error) => return stream::once(future::ready(Err(error))).boxed(),
};
let persistent = query.persistent() && arguments.is_some();
Box::pin(
self.worker
.execute(sql, arguments, self.row_channel_size, persistent, None)
.map_ok(flume::Receiver::into_stream)
.try_flatten_stream(),
)
}
fn fetch_optional<'e, 'q, E>(
self,
mut query: E,
) -> BoxFuture<'e, Result<Option<SqliteRow>, Error>>
where
'c: 'e,
E: Execute<'q, Self::Database>,
'q: 'e,
E: 'q,
{
let sql = query.sql();
let arguments = match query.take_arguments().map_err(Error::Encode) {
Ok(arguments) => arguments,
Err(error) => return future::ready(Err(error)).boxed(),
};
let persistent = query.persistent() && arguments.is_some();
Box::pin(async move {
let mut stream = pin!(self
.worker
.execute(sql, arguments, self.row_channel_size, persistent, Some(1))
.map_ok(flume::Receiver::into_stream)
.try_flatten_stream());
while let Some(res) = stream.try_next().await? {
if let Either::Right(row) = res {
return Ok(Some(row));
}
}
Ok(None)
})
}
fn prepare_with<'e, 'q: 'e>(
self,
sql: &'q str,
_parameters: &[SqliteTypeInfo],
) -> BoxFuture<'e, Result<SqliteStatement<'q>, Error>>
where
'c: 'e,
{
Box::pin(async move {
let statement = self.worker.prepare(sql).await?;
Ok(SqliteStatement {
sql: sql.into(),
..statement
})
})
}
#[doc(hidden)]
fn describe<'e, 'q: 'e>(self, sql: &'q str) -> BoxFuture<'e, Result<Describe<Sqlite>, Error>>
where
'c: 'e,
{
Box::pin(self.worker.describe(sql))
}
}
File diff suppressed because it is too large Load Diff
+100
View File
@@ -0,0 +1,100 @@
use std::ffi::CString;
use std::ptr;
use std::ptr::NonNull;
use crate::error::Error;
use libsqlite3_sys::{
sqlite3, sqlite3_close, sqlite3_exec, sqlite3_last_insert_rowid, SQLITE_LOCKED_SHAREDCACHE,
SQLITE_OK,
};
use crate::{statement::unlock_notify, SqliteError};
/// Managed handle to the raw SQLite3 database handle.
/// The database handle will be closed when this is dropped and no `ConnectionHandleRef`s exist.
#[derive(Debug)]
pub(crate) struct ConnectionHandle(NonNull<sqlite3>);
// A SQLite3 handle is safe to send between threads, provided not more than
// one is accessing it at the same time. This is upheld as long as [SQLITE_CONFIG_MULTITHREAD] is
// enabled and [SQLITE_THREADSAFE] was enabled when sqlite was compiled. We refuse to work
// if these conditions are not upheld.
// <https://www.sqlite.org/c3ref/threadsafe.html>
// <https://www.sqlite.org/c3ref/c_config_covering_index_scan.html#sqliteconfigmultithread>
unsafe impl Send for ConnectionHandle {}
impl ConnectionHandle {
#[inline]
pub(super) unsafe fn new(ptr: *mut sqlite3) -> Self {
Self(NonNull::new_unchecked(ptr))
}
#[inline]
pub(crate) fn as_ptr(&self) -> *mut sqlite3 {
self.0.as_ptr()
}
pub(crate) fn as_non_null_ptr(&self) -> NonNull<sqlite3> {
self.0
}
pub(crate) fn last_insert_rowid(&mut self) -> i64 {
// SAFETY: we have exclusive access to the database handle
unsafe { sqlite3_last_insert_rowid(self.as_ptr()) }
}
pub(crate) fn last_error(&mut self) -> Option<SqliteError> {
// SAFETY: we have exclusive access to the database handle
unsafe { SqliteError::try_new(self.as_ptr()) }
}
#[track_caller]
pub(crate) fn expect_error(&mut self) -> SqliteError {
self.last_error()
.expect("expected error code to be set in current context")
}
pub(crate) fn exec(&mut self, query: impl Into<String>) -> Result<(), Error> {
let query = query.into();
let query = CString::new(query).map_err(|_| err_protocol!("query contains nul bytes"))?;
// SAFETY: we have exclusive access to the database handle
unsafe {
loop {
let status = sqlite3_exec(
self.as_ptr(),
query.as_ptr(),
// callback if we wanted result rows
None,
// callback data
ptr::null_mut(),
// out-pointer for the error message, we just use `SqliteError::new()`
ptr::null_mut(),
);
match status {
SQLITE_OK => return Ok(()),
SQLITE_LOCKED_SHAREDCACHE => unlock_notify::wait(self.as_ptr())?,
_ => return Err(SqliteError::new(self.as_ptr()).into()),
}
}
}
}
}
impl Drop for ConnectionHandle {
fn drop(&mut self) {
unsafe {
// https://sqlite.org/c3ref/close.html
let status = sqlite3_close(self.0.as_ptr());
if status != SQLITE_OK {
// this should *only* happen due to an internal bug in SQLite where we left
// SQLite handles open
panic!("{}", SqliteError::new(self.0.as_ptr()));
}
}
}
}
+179
View File
@@ -0,0 +1,179 @@
// Bad casts in this module SHOULD NOT result in a SQL injection
// https://github.com/launchbadge/sqlx/issues/3440
#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
use std::cmp::Ordering;
use std::{fmt::Debug, hash::Hash};
/// Simplistic map implementation built on a Vec of Options (index = key)
#[derive(Debug, Clone, Eq)]
pub(crate) struct IntMap<V>(Vec<Option<V>>);
impl<V> Default for IntMap<V> {
fn default() -> Self {
IntMap(Vec::new())
}
}
impl<V> IntMap<V> {
pub(crate) fn new() -> Self {
Self(Vec::new())
}
pub(crate) fn expand(&mut self, size: i64) -> usize {
let idx = size.try_into().expect("negative column index unsupported");
while self.0.len() <= idx {
self.0.push(None);
}
idx
}
pub(crate) fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
self.0.iter_mut().filter_map(Option::as_mut)
}
pub(crate) fn values(&self) -> impl Iterator<Item = &V> {
self.0.iter().filter_map(Option::as_ref)
}
pub(crate) fn get(&self, idx: &i64) -> Option<&V> {
let idx: usize = (*idx)
.try_into()
.expect("negative column index unsupported");
match self.0.get(idx) {
Some(Some(v)) => Some(v),
_ => None,
}
}
pub(crate) fn get_mut(&mut self, idx: &i64) -> Option<&mut V> {
let idx: usize = (*idx)
.try_into()
.expect("negative column index unsupported");
match self.0.get_mut(idx) {
Some(Some(v)) => Some(v),
_ => None,
}
}
pub(crate) fn insert(&mut self, idx: i64, value: V) -> Option<V> {
let idx: usize = self.expand(idx);
std::mem::replace(&mut self.0[idx], Some(value))
}
pub(crate) fn remove(&mut self, idx: &i64) -> Option<V> {
let idx: usize = (*idx)
.try_into()
.expect("negative column index unsupported");
let item = self.0.get_mut(idx);
match item {
Some(content) => content.take(),
None => None,
}
}
pub(crate) fn iter(&self) -> impl Iterator<Item = Option<&V>> {
self.0.iter().map(Option::as_ref)
}
pub(crate) fn iter_entries(&self) -> impl Iterator<Item = (i64, &V)> {
self.0
.iter()
.enumerate()
.filter_map(|(i, v)| v.as_ref().map(|v: &V| (i as i64, v)))
}
pub(crate) fn last_index(&self) -> Option<i64> {
self.0.iter().rposition(|v| v.is_some()).map(|i| i as i64)
}
}
impl<V: Default> IntMap<V> {
pub(crate) fn get_mut_or_default<'a>(&'a mut self, idx: &i64) -> &'a mut V {
let idx: usize = self.expand(*idx);
let item: &mut Option<V> = &mut self.0[idx];
if item.is_none() {
*item = Some(V::default());
}
return self.0[idx].as_mut().unwrap();
}
}
impl<V: Clone> IntMap<V> {
pub(crate) fn from_elem(elem: V, len: usize) -> Self {
Self(vec![Some(elem); len])
}
pub(crate) fn from_dense_record(record: &[V]) -> Self {
Self(record.iter().cloned().map(Some).collect())
}
}
impl<V: Eq> IntMap<V> {
/// get the additions to this intmap compared to the prev intmap
pub(crate) fn diff<'a, 'b, 'c>(
&'a self,
prev: &'b Self,
) -> impl Iterator<Item = (usize, Option<&'c V>)>
where
'a: 'c,
'b: 'c,
{
let self_pad = if prev.0.len() > self.0.len() {
prev.0.len() - self.0.len()
} else {
0
};
self.iter()
.chain(std::iter::repeat(None).take(self_pad))
.zip(prev.iter().chain(std::iter::repeat(None)))
.enumerate()
.filter(|(_i, (n, p))| n != p)
.map(|(i, (n, _p))| (i, n))
}
}
impl<V: Hash> Hash for IntMap<V> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
for value in self.values() {
value.hash(state);
}
}
}
impl<V: PartialEq> PartialEq for IntMap<V> {
fn eq(&self, other: &Self) -> bool {
match self.0.len().cmp(&other.0.len()) {
Ordering::Greater => {
self.0[..other.0.len()] == other.0
&& self.0[other.0.len()..].iter().all(Option::is_none)
}
Ordering::Less => {
other.0[..self.0.len()] == self.0
&& other.0[self.0.len()..].iter().all(Option::is_none)
}
Ordering::Equal => self.0 == other.0,
}
}
}
impl<V: Debug> FromIterator<(i64, V)> for IntMap<V> {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = (i64, V)>,
{
let mut result = Self(Vec::new());
for (idx, val) in iter {
let idx = result.expand(idx);
result.0[idx] = Some(val);
}
result
}
}
+611
View File
@@ -0,0 +1,611 @@
use std::borrow::Cow;
use std::cmp::Ordering;
use std::ffi::CStr;
use std::fmt::Write;
use std::fmt::{self, Debug, Formatter};
use std::os::raw::{c_char, c_int, c_void};
use std::panic::catch_unwind;
use std::ptr;
use std::ptr::NonNull;
use futures_core::future::BoxFuture;
use futures_intrusive::sync::MutexGuard;
use futures_util::future;
use libsqlite3_sys::{
sqlite3, sqlite3_commit_hook, sqlite3_get_autocommit, sqlite3_progress_handler,
sqlite3_rollback_hook, sqlite3_update_hook, SQLITE_DELETE, SQLITE_INSERT, SQLITE_UPDATE,
};
#[cfg(feature = "preupdate-hook")]
pub use preupdate_hook::*;
pub(crate) use handle::ConnectionHandle;
use sqlx_core::common::StatementCache;
pub(crate) use sqlx_core::connection::*;
use sqlx_core::error::Error;
use sqlx_core::executor::Executor;
use sqlx_core::transaction::Transaction;
use crate::connection::establish::EstablishParams;
use crate::connection::worker::ConnectionWorker;
use crate::options::OptimizeOnClose;
use crate::statement::VirtualStatement;
use crate::{Sqlite, SqliteConnectOptions, SqliteError};
pub(crate) mod collation;
pub(crate) mod describe;
pub(crate) mod establish;
pub(crate) mod execute;
mod executor;
mod explain;
mod handle;
pub(crate) mod intmap;
#[cfg(feature = "preupdate-hook")]
mod preupdate_hook;
pub(crate) mod serialize;
mod worker;
/// A connection to an open [Sqlite] database.
///
/// Because SQLite is an in-process database accessed by blocking API calls, SQLx uses a background
/// thread and communicates with it via channels to allow non-blocking access to the database.
///
/// Dropping this struct will signal the worker thread to quit and close the database, though
/// if an error occurs there is no way to pass it back to the user this way.
///
/// You can explicitly call [`.close()`][Self::close] to ensure the database is closed successfully
/// or get an error otherwise.
pub struct SqliteConnection {
optimize_on_close: OptimizeOnClose,
pub(crate) worker: ConnectionWorker,
pub(crate) row_channel_size: usize,
}
pub struct LockedSqliteHandle<'a> {
pub(crate) guard: MutexGuard<'a, ConnectionState>,
}
/// Represents a callback handler that will be shared with the underlying sqlite3 connection.
pub(crate) struct Handler(NonNull<dyn FnMut() -> bool + Send + 'static>);
unsafe impl Send for Handler {}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum SqliteOperation {
Insert,
Update,
Delete,
Unknown(i32),
}
impl From<i32> for SqliteOperation {
fn from(value: i32) -> Self {
match value {
SQLITE_INSERT => SqliteOperation::Insert,
SQLITE_UPDATE => SqliteOperation::Update,
SQLITE_DELETE => SqliteOperation::Delete,
code => SqliteOperation::Unknown(code),
}
}
}
pub struct UpdateHookResult<'a> {
pub operation: SqliteOperation,
pub database: &'a str,
pub table: &'a str,
pub rowid: i64,
}
pub(crate) struct UpdateHookHandler(NonNull<dyn FnMut(UpdateHookResult) + Send + 'static>);
unsafe impl Send for UpdateHookHandler {}
pub(crate) struct CommitHookHandler(NonNull<dyn FnMut() -> bool + Send + 'static>);
unsafe impl Send for CommitHookHandler {}
pub(crate) struct RollbackHookHandler(NonNull<dyn FnMut() + Send + 'static>);
unsafe impl Send for RollbackHookHandler {}
pub(crate) struct ConnectionState {
pub(crate) handle: ConnectionHandle,
pub(crate) statements: Statements,
log_settings: LogSettings,
/// Stores the progress handler set on the current connection. If the handler returns `false`,
/// the query is interrupted.
progress_handler_callback: Option<Handler>,
update_hook_callback: Option<UpdateHookHandler>,
#[cfg(feature = "preupdate-hook")]
preupdate_hook_callback: Option<preupdate_hook::PreupdateHookHandler>,
commit_hook_callback: Option<CommitHookHandler>,
rollback_hook_callback: Option<RollbackHookHandler>,
}
impl ConnectionState {
/// Drops the `progress_handler_callback` if it exists.
pub(crate) fn remove_progress_handler(&mut self) {
if let Some(mut handler) = self.progress_handler_callback.take() {
unsafe {
sqlite3_progress_handler(self.handle.as_ptr(), 0, None, ptr::null_mut());
let _ = { Box::from_raw(handler.0.as_mut()) };
}
}
}
pub(crate) fn remove_update_hook(&mut self) {
if let Some(mut handler) = self.update_hook_callback.take() {
unsafe {
sqlite3_update_hook(self.handle.as_ptr(), None, ptr::null_mut());
let _ = { Box::from_raw(handler.0.as_mut()) };
}
}
}
#[cfg(feature = "preupdate-hook")]
pub(crate) fn remove_preupdate_hook(&mut self) {
if let Some(mut handler) = self.preupdate_hook_callback.take() {
unsafe {
libsqlite3_sys::sqlite3_preupdate_hook(self.handle.as_ptr(), None, ptr::null_mut());
let _ = { Box::from_raw(handler.0.as_mut()) };
}
}
}
pub(crate) fn remove_commit_hook(&mut self) {
if let Some(mut handler) = self.commit_hook_callback.take() {
unsafe {
sqlite3_commit_hook(self.handle.as_ptr(), None, ptr::null_mut());
let _ = { Box::from_raw(handler.0.as_mut()) };
}
}
}
pub(crate) fn remove_rollback_hook(&mut self) {
if let Some(mut handler) = self.rollback_hook_callback.take() {
unsafe {
sqlite3_rollback_hook(self.handle.as_ptr(), None, ptr::null_mut());
let _ = { Box::from_raw(handler.0.as_mut()) };
}
}
}
}
pub(crate) struct Statements {
// cache of semi-persistent statements
cached: StatementCache<VirtualStatement>,
// most recent non-persistent statement
temp: Option<VirtualStatement>,
}
impl SqliteConnection {
pub(crate) async fn establish(options: &SqliteConnectOptions) -> Result<Self, Error> {
let params = EstablishParams::from_options(options)?;
let worker = ConnectionWorker::establish(params).await?;
Ok(Self {
optimize_on_close: options.optimize_on_close.clone(),
worker,
row_channel_size: options.row_channel_size,
})
}
/// Lock the SQLite database handle out from the worker thread so direct SQLite API calls can
/// be made safely.
///
/// Returns an error if the worker thread crashed.
pub async fn lock_handle(&mut self) -> Result<LockedSqliteHandle<'_>, Error> {
let guard = self.worker.unlock_db().await?;
Ok(LockedSqliteHandle { guard })
}
}
impl Debug for SqliteConnection {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("SqliteConnection")
.field("row_channel_size", &self.row_channel_size)
.field("cached_statements_size", &self.cached_statements_size())
.finish()
}
}
impl Connection for SqliteConnection {
type Database = Sqlite;
type Options = SqliteConnectOptions;
fn close(mut self) -> BoxFuture<'static, Result<(), Error>> {
Box::pin(async move {
if let OptimizeOnClose::Enabled { analysis_limit } = self.optimize_on_close {
let mut pragma_string = String::new();
if let Some(limit) = analysis_limit {
write!(pragma_string, "PRAGMA analysis_limit = {limit}; ").ok();
}
pragma_string.push_str("PRAGMA optimize;");
self.execute(&*pragma_string).await?;
}
let shutdown = self.worker.shutdown();
// Drop the statement worker, which should
// cover all references to the connection handle outside of the worker thread
drop(self);
// Ensure the worker thread has terminated
shutdown.await
})
}
fn close_hard(self) -> BoxFuture<'static, Result<(), Error>> {
Box::pin(async move {
drop(self);
Ok(())
})
}
/// Ensure the background worker thread is alive and accepting commands.
fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(self.worker.ping())
}
fn begin(&mut self) -> BoxFuture<'_, Result<Transaction<'_, Self::Database>, Error>>
where
Self: Sized,
{
Transaction::begin(self, None)
}
fn begin_with(
&mut self,
statement: impl Into<Cow<'static, str>>,
) -> BoxFuture<'_, Result<Transaction<'_, Self::Database>, Error>>
where
Self: Sized,
{
Transaction::begin(self, Some(statement.into()))
}
fn cached_statements_size(&self) -> usize {
self.worker.shared.get_cached_statements_size()
}
fn clear_cached_statements(&mut self) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(async move {
self.worker.clear_cache().await?;
Ok(())
})
}
#[inline]
fn shrink_buffers(&mut self) {
// No-op.
}
#[doc(hidden)]
fn flush(&mut self) -> BoxFuture<'_, Result<(), Error>> {
// For SQLite, FLUSH does effectively nothing...
// Well, we could use this to ensure that the command channel has been cleared,
// but it would only develop a backlog if a lot of queries are executed and then cancelled
// partway through, and then this would only make that situation worse.
Box::pin(future::ok(()))
}
#[doc(hidden)]
fn should_flush(&self) -> bool {
false
}
}
/// Implements a C binding to a progress callback. The function returns `0` if the
/// user-provided callback returns `true`, and `1` otherwise to signal an interrupt.
extern "C" fn progress_callback<F>(callback: *mut c_void) -> c_int
where
F: FnMut() -> bool,
{
unsafe {
let r = catch_unwind(|| {
let callback: *mut F = callback.cast::<F>();
(*callback)()
});
c_int::from(!r.unwrap_or_default())
}
}
extern "C" fn update_hook<F>(
callback: *mut c_void,
op_code: c_int,
database: *const c_char,
table: *const c_char,
rowid: i64,
) where
F: FnMut(UpdateHookResult),
{
unsafe {
let _ = catch_unwind(|| {
let callback: *mut F = callback.cast::<F>();
let operation: SqliteOperation = op_code.into();
let database = CStr::from_ptr(database).to_str().unwrap_or_default();
let table = CStr::from_ptr(table).to_str().unwrap_or_default();
(*callback)(UpdateHookResult {
operation,
database,
table,
rowid,
})
});
}
}
extern "C" fn commit_hook<F>(callback: *mut c_void) -> c_int
where
F: FnMut() -> bool,
{
unsafe {
let r = catch_unwind(|| {
let callback: *mut F = callback.cast::<F>();
(*callback)()
});
c_int::from(!r.unwrap_or_default())
}
}
extern "C" fn rollback_hook<F>(callback: *mut c_void)
where
F: FnMut(),
{
unsafe {
let _ = catch_unwind(|| {
let callback: *mut F = callback.cast::<F>();
(*callback)()
});
}
}
impl LockedSqliteHandle<'_> {
/// Returns the underlying sqlite3* connection handle.
///
/// As long as this `LockedSqliteHandle` exists, it is guaranteed that the background thread
/// is not making FFI calls on this database handle or any of its statements.
///
/// ### Note: The `sqlite3` type is semver-exempt.
/// This API exposes the `sqlite3` type from `libsqlite3-sys` crate for type safety.
/// However, we reserve the right to upgrade `libsqlite3-sys` as necessary.
///
/// Thus, if you are making direct calls via `libsqlite3-sys` you should pin the version
/// of SQLx that you're using, and upgrade it and `libsqlite3-sys` manually as new
/// versions are released.
///
/// See [the driver root docs][crate] for details.
pub fn as_raw_handle(&mut self) -> NonNull<sqlite3> {
self.guard.handle.as_non_null_ptr()
}
/// Apply a collation to the open database.
///
/// See [`SqliteConnectOptions::collation()`] for details.
pub fn create_collation(
&mut self,
name: &str,
compare: impl Fn(&str, &str) -> Ordering + Send + Sync + 'static,
) -> Result<(), Error> {
collation::create_collation(&mut self.guard.handle, name, compare)
}
/// Sets a progress handler that is invoked periodically during long running calls. If the progress callback
/// returns `false`, then the operation is interrupted.
///
/// `num_ops` is the approximate number of [virtual machine instructions](https://www.sqlite.org/opcode.html)
/// that are evaluated between successive invocations of the callback. If `num_ops` is less than one then the
/// progress handler is disabled.
///
/// Only a single progress handler may be defined at one time per database connection; setting a new progress
/// handler cancels the old one.
///
/// The progress handler callback must not do anything that will modify the database connection that invoked
/// the progress handler. Note that sqlite3_prepare_v2() and sqlite3_step() both modify their database connections
/// in this context.
pub fn set_progress_handler<F>(&mut self, num_ops: i32, callback: F)
where
F: FnMut() -> bool + Send + 'static,
{
unsafe {
let callback_boxed = Box::new(callback);
// SAFETY: `Box::into_raw()` always returns a non-null pointer.
let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed));
let handler = callback.as_ptr() as *mut _;
self.guard.remove_progress_handler();
self.guard.progress_handler_callback = Some(Handler(callback));
sqlite3_progress_handler(
self.as_raw_handle().as_mut(),
num_ops,
Some(progress_callback::<F>),
handler,
);
}
}
pub fn set_update_hook<F>(&mut self, callback: F)
where
F: FnMut(UpdateHookResult) + Send + 'static,
{
unsafe {
let callback_boxed = Box::new(callback);
// SAFETY: `Box::into_raw()` always returns a non-null pointer.
let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed));
let handler = callback.as_ptr() as *mut _;
self.guard.remove_update_hook();
self.guard.update_hook_callback = Some(UpdateHookHandler(callback));
sqlite3_update_hook(
self.as_raw_handle().as_mut(),
Some(update_hook::<F>),
handler,
);
}
}
/// Registers a hook that is invoked prior to each `INSERT`, `UPDATE`, and `DELETE` operation on a database table.
/// At most one preupdate hook may be registered at a time on a single database connection.
///
/// The preupdate hook only fires for changes to real database tables;
/// it is not invoked for changes to virtual tables or to system tables like sqlite_sequence or sqlite_stat1.
///
/// See https://sqlite.org/c3ref/preupdate_count.html
#[cfg(feature = "preupdate-hook")]
pub fn set_preupdate_hook<F>(&mut self, callback: F)
where
F: FnMut(PreupdateHookResult) + Send + 'static,
{
unsafe {
let callback_boxed = Box::new(callback);
// SAFETY: `Box::into_raw()` always returns a non-null pointer.
let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed));
let handler = callback.as_ptr() as *mut _;
self.guard.remove_preupdate_hook();
self.guard.preupdate_hook_callback = Some(PreupdateHookHandler(callback));
libsqlite3_sys::sqlite3_preupdate_hook(
self.as_raw_handle().as_mut(),
Some(preupdate_hook::<F>),
handler,
);
}
}
/// Sets a commit hook that is invoked whenever a transaction is committed. If the commit hook callback
/// returns `false`, then the operation is turned into a ROLLBACK.
///
/// Only a single commit hook may be defined at one time per database connection; setting a new commit hook
/// overrides the old one.
///
/// The commit hook callback must not do anything that will modify the database connection that invoked
/// the commit hook. Note that sqlite3_prepare_v2() and sqlite3_step() both modify their database connections
/// in this context.
///
/// See https://www.sqlite.org/c3ref/commit_hook.html
pub fn set_commit_hook<F>(&mut self, callback: F)
where
F: FnMut() -> bool + Send + 'static,
{
unsafe {
let callback_boxed = Box::new(callback);
// SAFETY: `Box::into_raw()` always returns a non-null pointer.
let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed));
let handler = callback.as_ptr() as *mut _;
self.guard.remove_commit_hook();
self.guard.commit_hook_callback = Some(CommitHookHandler(callback));
sqlite3_commit_hook(
self.as_raw_handle().as_mut(),
Some(commit_hook::<F>),
handler,
);
}
}
/// Sets a rollback hook that is invoked whenever a transaction rollback occurs. The rollback callback is not
/// invoked if a transaction is automatically rolled back because the database connection is closed.
///
/// See https://www.sqlite.org/c3ref/commit_hook.html
pub fn set_rollback_hook<F>(&mut self, callback: F)
where
F: FnMut() + Send + 'static,
{
unsafe {
let callback_boxed = Box::new(callback);
// SAFETY: `Box::into_raw()` always returns a non-null pointer.
let callback = NonNull::new_unchecked(Box::into_raw(callback_boxed));
let handler = callback.as_ptr() as *mut _;
self.guard.remove_rollback_hook();
self.guard.rollback_hook_callback = Some(RollbackHookHandler(callback));
sqlite3_rollback_hook(
self.as_raw_handle().as_mut(),
Some(rollback_hook::<F>),
handler,
);
}
}
/// Removes the progress handler on a database connection. The method does nothing if no handler was set.
pub fn remove_progress_handler(&mut self) {
self.guard.remove_progress_handler();
}
pub fn remove_update_hook(&mut self) {
self.guard.remove_update_hook();
}
#[cfg(feature = "preupdate-hook")]
pub fn remove_preupdate_hook(&mut self) {
self.guard.remove_preupdate_hook();
}
pub fn remove_commit_hook(&mut self) {
self.guard.remove_commit_hook();
}
pub fn remove_rollback_hook(&mut self) {
self.guard.remove_rollback_hook();
}
pub fn last_error(&mut self) -> Option<SqliteError> {
self.guard.handle.last_error()
}
pub(crate) fn in_transaction(&mut self) -> bool {
let ret = unsafe { sqlite3_get_autocommit(self.as_raw_handle().as_ptr()) };
ret == 0
}
}
impl Drop for ConnectionState {
fn drop(&mut self) {
// explicitly drop statements before the connection handle is dropped
self.statements.clear();
self.remove_progress_handler();
self.remove_update_hook();
self.remove_commit_hook();
self.remove_rollback_hook();
}
}
impl Statements {
fn new(capacity: usize) -> Self {
Statements {
cached: StatementCache::new(capacity),
temp: None,
}
}
fn get(&mut self, query: &str, persistent: bool) -> Result<&mut VirtualStatement, Error> {
if !persistent || !self.cached.is_enabled() {
return Ok(self.temp.insert(VirtualStatement::new(query, false)?));
}
let exists = self.cached.contains_key(query);
if !exists {
let statement = VirtualStatement::new(query, true)?;
self.cached.insert(query, statement);
}
let statement = self.cached.get_mut(query).unwrap();
if exists {
// as this statement has been executed before, we reset before continuing
statement.reset()?;
}
Ok(statement)
}
fn len(&self) -> usize {
self.cached.len()
}
fn clear(&mut self) {
self.cached.clear();
self.temp = None;
}
}
+160
View File
@@ -0,0 +1,160 @@
use super::SqliteOperation;
use crate::type_info::DataType;
use crate::{SqliteError, SqliteTypeInfo, SqliteValueRef};
use libsqlite3_sys::{
sqlite3, sqlite3_preupdate_count, sqlite3_preupdate_depth, sqlite3_preupdate_new,
sqlite3_preupdate_old, sqlite3_value, sqlite3_value_type, SQLITE_OK,
};
use std::ffi::CStr;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::catch_unwind;
use std::ptr;
use std::ptr::NonNull;
#[derive(Debug, thiserror::Error)]
pub enum PreupdateError {
/// Error returned from the database.
#[error("error returned from database: {0}")]
Database(#[source] SqliteError),
/// Index is not within the valid column range
#[error("{0} is not within the valid column range")]
ColumnIndexOutOfBounds(i32),
/// Column value accessor was invoked from an invalid operation
#[error("column value accessor was invoked from an invalid operation")]
InvalidOperation,
}
pub(crate) struct PreupdateHookHandler(
pub(super) NonNull<dyn FnMut(PreupdateHookResult) + Send + 'static>,
);
unsafe impl Send for PreupdateHookHandler {}
#[derive(Debug)]
pub struct PreupdateHookResult<'a> {
pub operation: SqliteOperation,
pub database: &'a str,
pub table: &'a str,
db: *mut sqlite3,
// The database pointer should not be usable after the preupdate hook.
// The lifetime on this struct needs to ensure it cannot outlive the callback.
_db_lifetime: PhantomData<&'a ()>,
old_row_id: i64,
new_row_id: i64,
}
impl<'a> PreupdateHookResult<'a> {
/// Gets the amount of columns in the row being inserted, deleted, or updated.
pub fn get_column_count(&self) -> i32 {
unsafe { sqlite3_preupdate_count(self.db) }
}
/// Gets the depth of the query that triggered the preupdate hook.
/// Returns 0 if the preupdate callback was invoked as a result of
/// a direct insert, update, or delete operation;
/// 1 for inserts, updates, or deletes invoked by top-level triggers;
/// 2 for changes resulting from triggers called by top-level triggers; and so forth.
pub fn get_query_depth(&self) -> i32 {
unsafe { sqlite3_preupdate_depth(self.db) }
}
/// Gets the row id of the row being updated/deleted.
/// Returns an error if called from an insert operation.
pub fn get_old_row_id(&self) -> Result<i64, PreupdateError> {
if self.operation == SqliteOperation::Insert {
return Err(PreupdateError::InvalidOperation);
}
Ok(self.old_row_id)
}
/// Gets the row id of the row being inserted/updated.
/// Returns an error if called from a delete operation.
pub fn get_new_row_id(&self) -> Result<i64, PreupdateError> {
if self.operation == SqliteOperation::Delete {
return Err(PreupdateError::InvalidOperation);
}
Ok(self.new_row_id)
}
/// Gets the value of the row being updated/deleted at the specified index.
/// Returns an error if called from an insert operation or the index is out of bounds.
pub fn get_old_column_value(&self, i: i32) -> Result<SqliteValueRef<'a>, PreupdateError> {
if self.operation == SqliteOperation::Insert {
return Err(PreupdateError::InvalidOperation);
}
self.validate_column_index(i)?;
let mut p_value: *mut sqlite3_value = ptr::null_mut();
unsafe {
let ret = sqlite3_preupdate_old(self.db, i, &mut p_value);
self.get_value(ret, p_value)
}
}
/// Gets the value of the row being inserted/updated at the specified index.
/// Returns an error if called from a delete operation or the index is out of bounds.
pub fn get_new_column_value(&self, i: i32) -> Result<SqliteValueRef<'a>, PreupdateError> {
if self.operation == SqliteOperation::Delete {
return Err(PreupdateError::InvalidOperation);
}
self.validate_column_index(i)?;
let mut p_value: *mut sqlite3_value = ptr::null_mut();
unsafe {
let ret = sqlite3_preupdate_new(self.db, i, &mut p_value);
self.get_value(ret, p_value)
}
}
fn validate_column_index(&self, i: i32) -> Result<(), PreupdateError> {
if i < 0 || i >= self.get_column_count() {
return Err(PreupdateError::ColumnIndexOutOfBounds(i));
}
Ok(())
}
unsafe fn get_value(
&self,
ret: i32,
p_value: *mut sqlite3_value,
) -> Result<SqliteValueRef<'a>, PreupdateError> {
if ret != SQLITE_OK {
return Err(PreupdateError::Database(SqliteError::new(self.db)));
}
let data_type = DataType::from_code(sqlite3_value_type(p_value));
// SAFETY: SQLite will free the sqlite3_value when the callback returns
Ok(SqliteValueRef::borrowed(p_value, SqliteTypeInfo(data_type)))
}
}
pub(super) extern "C" fn preupdate_hook<F>(
callback: *mut c_void,
db: *mut sqlite3,
op_code: c_int,
database: *const c_char,
table: *const c_char,
old_row_id: i64,
new_row_id: i64,
) where
F: FnMut(PreupdateHookResult) + Send + 'static,
{
unsafe {
let _ = catch_unwind(|| {
let callback: *mut F = callback.cast::<F>();
let operation: SqliteOperation = op_code.into();
let database = CStr::from_ptr(database).to_str().unwrap_or_default();
let table = CStr::from_ptr(table).to_str().unwrap_or_default();
(*callback)(PreupdateHookResult {
operation,
database,
table,
old_row_id,
new_row_id,
db,
_db_lifetime: PhantomData,
})
});
}
}
+297
View File
@@ -0,0 +1,297 @@
use super::ConnectionState;
use crate::{error::Error, SqliteConnection, SqliteError};
use libsqlite3_sys::{
sqlite3_deserialize, sqlite3_free, sqlite3_malloc64, sqlite3_serialize,
SQLITE_DESERIALIZE_FREEONCLOSE, SQLITE_DESERIALIZE_READONLY, SQLITE_DESERIALIZE_RESIZEABLE,
SQLITE_NOMEM, SQLITE_OK,
};
use std::ffi::c_char;
use std::fmt::Debug;
use std::{
ops::{Deref, DerefMut},
ptr,
ptr::NonNull,
};
impl SqliteConnection {
/// Serialize the given SQLite database schema using [`sqlite3_serialize()`].
///
/// The returned buffer is a SQLite managed allocation containing the equivalent data
/// as writing the database to disk. It is freed on-drop.
///
/// To serialize the primary, unqualified schema (`main`), pass `None` for the schema name.
///
/// # Errors
/// * [`Error::InvalidArgument`] if the schema name contains a zero/NUL byte (`\0`).
/// * [`Error::Database`] if the schema does not exist or another error occurs.
///
/// [`sqlite3_serialize()`]: https://sqlite.org/c3ref/serialize.html
pub async fn serialize(&mut self, schema: Option<&str>) -> Result<SqliteOwnedBuf, Error> {
let schema = schema.map(SchemaName::try_from).transpose()?;
self.worker.serialize(schema).await
}
/// Deserialize a SQLite database from a buffer into the specified schema using [`sqlite3_deserialize()`].
///
/// The given schema will be disconnected and re-connected as an in-memory database
/// backed by `data`, which should be the serialized form of a database previously returned
/// by a call to [`Self::serialize()`], documented as being equivalent to
/// the contents of the database file on disk.
///
/// An error will be returned if a schema with the given name is not already attached.
/// You can use `ATTACH ':memory' as "<schema name>"` to create an empty schema first.
///
/// Pass `None` to deserialize to the primary, unqualified schema (`main`).
///
/// The SQLite connection will take ownership of `data` and will free it when the connection
/// is closed or the schema is detached ([`SQLITE_DESERIALIZE_FREEONCLOSE`][deserialize-flags]).
///
/// If `read_only` is `true`, the schema is opened as read-only ([`SQLITE_DESERIALIZE_READONLY`][deserialize-flags]).
/// If `false`, the schema is marked as resizable ([`SQLITE_DESERIALIZE_RESIZABLE`][deserialize-flags]).
///
/// If the database is in WAL mode, an error is returned.
/// See [`sqlite3_deserialize()`] for details.
///
/// # Errors
/// * [`Error::InvalidArgument`] if the schema name contains a zero/NUL byte (`\0`).
/// * [`Error::Database`] if an error occurs during deserialization.
///
/// [`sqlite3_deserialize()`]: https://sqlite.org/c3ref/deserialize.html
/// [deserialize-flags]: https://sqlite.org/c3ref/c_deserialize_freeonclose.html
pub async fn deserialize(
&mut self,
schema: Option<&str>,
data: SqliteOwnedBuf,
read_only: bool,
) -> Result<(), Error> {
let schema = schema.map(SchemaName::try_from).transpose()?;
self.worker.deserialize(schema, data, read_only).await
}
}
pub(crate) fn serialize(
conn: &mut ConnectionState,
schema: Option<SchemaName>,
) -> Result<SqliteOwnedBuf, Error> {
let mut size = 0;
let buf = unsafe {
let ptr = sqlite3_serialize(
conn.handle.as_ptr(),
schema.as_ref().map_or(ptr::null(), SchemaName::as_ptr),
&mut size,
0,
);
// looking at the source, `sqlite3_serialize` actually sets `size = -1` on error:
// https://github.com/sqlite/sqlite/blob/da5f81387843f92652128087a8f8ecef0b79461d/src/memdb.c#L776
usize::try_from(size)
.ok()
.and_then(|size| SqliteOwnedBuf::from_raw(ptr, size))
};
if let Some(buf) = buf {
return Ok(buf);
}
if let Some(error) = conn.handle.last_error() {
return Err(error.into());
}
if size > 0 {
// If `size` is positive but `sqlite3_serialize` still returned NULL,
// the most likely culprit is an out-of-memory condition.
return Err(SqliteError::from_code(SQLITE_NOMEM).into());
}
// Otherwise, the schema was probably not found.
// We return the equivalent error as when you try to execute `PRAGMA <schema>.page_count`
// against a non-existent schema.
Err(SqliteError::generic(format!(
"database {} does not exist",
schema.as_ref().map_or("main", SchemaName::as_str)
))
.into())
}
pub(crate) fn deserialize(
conn: &mut ConnectionState,
schema: Option<SchemaName>,
data: SqliteOwnedBuf,
read_only: bool,
) -> Result<(), Error> {
// SQLITE_DESERIALIZE_FREEONCLOSE causes SQLite to take ownership of the buffer
let mut flags = SQLITE_DESERIALIZE_FREEONCLOSE;
if read_only {
flags |= SQLITE_DESERIALIZE_READONLY;
} else {
flags |= SQLITE_DESERIALIZE_RESIZEABLE;
}
let (buf, size) = data.into_raw();
let rc = unsafe {
sqlite3_deserialize(
conn.handle.as_ptr(),
schema.as_ref().map_or(ptr::null(), SchemaName::as_ptr),
buf,
i64::try_from(size).unwrap(),
i64::try_from(size).unwrap(),
flags,
)
};
match rc {
SQLITE_OK => Ok(()),
SQLITE_NOMEM => Err(SqliteError::from_code(SQLITE_NOMEM).into()),
// SQLite unfortunately doesn't set any specific message for deserialization errors.
_ => Err(SqliteError::generic("an error occurred during deserialization").into()),
}
}
/// Memory buffer owned and allocated by SQLite. Freed on drop.
///
/// Intended primarily for use with [`SqliteConnection::serialize()`] and [`SqliteConnection::deserialize()`].
///
/// Can be created from `&[u8]` using the `TryFrom` impl. The slice must not be empty.
#[derive(Debug)]
pub struct SqliteOwnedBuf {
ptr: NonNull<u8>,
size: usize,
}
unsafe impl Send for SqliteOwnedBuf {}
unsafe impl Sync for SqliteOwnedBuf {}
impl Drop for SqliteOwnedBuf {
fn drop(&mut self) {
unsafe {
sqlite3_free(self.ptr.as_ptr().cast());
}
}
}
impl SqliteOwnedBuf {
/// Uses `sqlite3_malloc` to allocate a buffer and returns a pointer to it.
///
/// # Safety
/// The allocated buffer is uninitialized.
unsafe fn with_capacity(size: usize) -> Option<SqliteOwnedBuf> {
let ptr = sqlite3_malloc64(u64::try_from(size).unwrap()).cast::<u8>();
Self::from_raw(ptr, size)
}
/// Creates a new mem buffer from a pointer that has been created with sqlite_malloc
///
/// # Safety:
/// * The pointer must point to a valid allocation created by `sqlite3_malloc()`, or `NULL`.
unsafe fn from_raw(ptr: *mut u8, size: usize) -> Option<Self> {
Some(Self {
ptr: NonNull::new(ptr)?,
size,
})
}
fn into_raw(self) -> (*mut u8, usize) {
let raw = (self.ptr.as_ptr(), self.size);
// this is used in sqlite_deserialize and
// underlying buffer must not be freed
std::mem::forget(self);
raw
}
}
/// # Errors
/// Returns [`Error::InvalidArgument`] if the slice is empty.
impl TryFrom<&[u8]> for SqliteOwnedBuf {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
unsafe {
// SAFETY: `buf` is not initialized until `ptr::copy_nonoverlapping` completes.
let mut buf = Self::with_capacity(bytes.len()).ok_or_else(|| {
Error::InvalidArgument("SQLite owned buffer cannot be empty".to_string())
})?;
ptr::copy_nonoverlapping(bytes.as_ptr(), buf.ptr.as_mut(), buf.size);
Ok(buf)
}
}
}
impl Deref for SqliteOwnedBuf {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.size) }
}
}
impl DerefMut for SqliteOwnedBuf {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_mut(), self.size) }
}
}
impl AsRef<[u8]> for SqliteOwnedBuf {
fn as_ref(&self) -> &[u8] {
self.deref()
}
}
impl AsMut<[u8]> for SqliteOwnedBuf {
fn as_mut(&mut self) -> &mut [u8] {
self.deref_mut()
}
}
/// Checked schema name to pass to SQLite.
///
/// # Safety:
/// * Valid UTF-8 (not guaranteed by `CString`)
/// * No internal zero bytes (`\0`) (not guaranteed by `String`)
/// * Terminated with a zero byte (`\0`) (not guaranteed by `String`)
#[derive(Debug)]
pub(crate) struct SchemaName(Box<str>);
impl SchemaName {
/// Get the schema name as a string without the zero byte terminator.
pub fn as_str(&self) -> &str {
&self.0[..self.0.len() - 1]
}
/// Get a pointer to the string data, suitable for passing as C's `*const char`.
///
/// # Safety
/// The string data is guaranteed to be terminated with a zero byte.
pub fn as_ptr(&self) -> *const c_char {
self.0.as_ptr() as *const c_char
}
}
impl<'a> TryFrom<&'a str> for SchemaName {
type Error = Error;
fn try_from(name: &'a str) -> Result<Self, Self::Error> {
// SAFETY: we must ensure that the string does not contain an internal NULL byte
if let Some(pos) = name.as_bytes().iter().position(|&b| b == 0) {
return Err(Error::InvalidArgument(format!(
"schema name {name:?} contains a zero byte at index {pos}"
)));
}
let capacity = name.len().checked_add(1).unwrap();
let mut s = String::new();
// `String::with_capacity()` does not guarantee that it will not overallocate,
// which might mean an unnecessary reallocation to make `capacity == len`
// in the conversion to `Box<str>`.
s.reserve_exact(capacity);
s.push_str(name);
s.push('\0');
Ok(SchemaName(s.into()))
}
}
+564
View File
@@ -0,0 +1,564 @@
use std::borrow::Cow;
use std::future::Future;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use futures_channel::oneshot;
use futures_intrusive::sync::{Mutex, MutexGuard};
use tracing::span::Span;
use sqlx_core::describe::Describe;
use sqlx_core::error::Error;
use sqlx_core::transaction::{
begin_ansi_transaction_sql, commit_ansi_transaction_sql, rollback_ansi_transaction_sql,
};
use sqlx_core::Either;
use crate::connection::describe::describe;
use crate::connection::establish::EstablishParams;
use crate::connection::execute;
use crate::connection::ConnectionState;
use crate::{Sqlite, SqliteArguments, SqliteQueryResult, SqliteRow, SqliteStatement};
use super::serialize::{deserialize, serialize, SchemaName, SqliteOwnedBuf};
// Each SQLite connection has a dedicated thread.
// TODO: Tweak this so that we can use a thread pool per pool of SQLite3 connections to reduce
// OS resource usage. Low priority because a high concurrent load for SQLite3 is very
// unlikely.
pub(crate) struct ConnectionWorker {
command_tx: flume::Sender<(Command, tracing::Span)>,
/// Mutex for locking access to the database.
pub(crate) shared: Arc<WorkerSharedState>,
}
pub(crate) struct WorkerSharedState {
transaction_depth: AtomicUsize,
cached_statements_size: AtomicUsize,
pub(crate) conn: Mutex<ConnectionState>,
}
impl WorkerSharedState {
pub(crate) fn get_transaction_depth(&self) -> usize {
self.transaction_depth.load(Ordering::Acquire)
}
pub(crate) fn get_cached_statements_size(&self) -> usize {
self.cached_statements_size.load(Ordering::Acquire)
}
}
enum Command {
Prepare {
query: Box<str>,
tx: oneshot::Sender<Result<SqliteStatement<'static>, Error>>,
},
Describe {
query: Box<str>,
tx: oneshot::Sender<Result<Describe<Sqlite>, Error>>,
},
Execute {
query: Box<str>,
arguments: Option<SqliteArguments<'static>>,
persistent: bool,
tx: flume::Sender<Result<Either<SqliteQueryResult, SqliteRow>, Error>>,
limit: Option<usize>,
},
Serialize {
schema: Option<SchemaName>,
tx: oneshot::Sender<Result<SqliteOwnedBuf, Error>>,
},
Deserialize {
schema: Option<SchemaName>,
data: SqliteOwnedBuf,
read_only: bool,
tx: oneshot::Sender<Result<(), Error>>,
},
Begin {
tx: rendezvous_oneshot::Sender<Result<(), Error>>,
statement: Option<Cow<'static, str>>,
},
Commit {
tx: rendezvous_oneshot::Sender<Result<(), Error>>,
},
Rollback {
tx: Option<rendezvous_oneshot::Sender<Result<(), Error>>>,
},
UnlockDb,
ClearCache {
tx: oneshot::Sender<()>,
},
Ping {
tx: oneshot::Sender<()>,
},
Shutdown {
tx: oneshot::Sender<()>,
},
}
impl ConnectionWorker {
pub(crate) async fn establish(params: EstablishParams) -> Result<Self, Error> {
let (establish_tx, establish_rx) = oneshot::channel();
thread::Builder::new()
.name(params.thread_name.clone())
.spawn(move || {
let (command_tx, command_rx) = flume::bounded(params.command_channel_size);
let conn = match params.establish() {
Ok(conn) => conn,
Err(e) => {
establish_tx.send(Err(e)).ok();
return;
}
};
let shared = Arc::new(WorkerSharedState {
transaction_depth: AtomicUsize::new(0),
cached_statements_size: AtomicUsize::new(0),
// note: must be fair because in `Command::UnlockDb` we unlock the mutex
// and then immediately try to relock it; an unfair mutex would immediately
// grant us the lock even if another task is waiting.
conn: Mutex::new(conn, true),
});
let mut conn = shared.conn.try_lock().unwrap();
if establish_tx
.send(Ok(Self {
command_tx,
shared: Arc::clone(&shared),
}))
.is_err()
{
return;
}
// If COMMIT or ROLLBACK is processed but not acknowledged, there would be another
// ROLLBACK sent when the `Transaction` drops. We need to ignore it otherwise we
// would rollback an already completed transaction.
let mut ignore_next_start_rollback = false;
for (cmd, span) in command_rx {
let _guard = span.enter();
match cmd {
Command::Prepare { query, tx } => {
tx.send(prepare(&mut conn, &query).map(|prepared| {
update_cached_statements_size(
&conn,
&shared.cached_statements_size,
);
prepared
}))
.ok();
}
Command::Describe { query, tx } => {
tx.send(describe(&mut conn, &query)).ok();
}
Command::Execute {
query,
arguments,
persistent,
tx,
limit
} => {
let iter = match execute::iter(&mut conn, &query, arguments, persistent)
{
Ok(iter) => iter,
Err(e) => {
tx.send(Err(e)).ok();
continue;
}
};
match limit {
None => {
for res in iter {
let has_error = res.is_err();
if tx.send(res).is_err() || has_error {
break;
}
}
},
Some(limit) => {
let mut iter = iter;
let mut rows_returned = 0;
while let Some(res) = iter.next() {
if let Ok(ok) = &res {
if ok.is_right() {
rows_returned += 1;
if rows_returned >= limit {
drop(iter);
let _ = tx.send(res);
break;
}
}
}
let has_error = res.is_err();
if tx.send(res).is_err() || has_error {
break;
}
}
},
}
update_cached_statements_size(&conn, &shared.cached_statements_size);
}
Command::Begin { tx, statement } => {
let depth = shared.transaction_depth.load(Ordering::Acquire);
let statement = match statement {
// custom `BEGIN` statements are not allowed if
// we're already in a transaction (we need to
// issue a `SAVEPOINT` instead)
Some(_) if depth > 0 => {
if tx.blocking_send(Err(Error::InvalidSavePointStatement)).is_err() {
break;
}
continue;
},
Some(statement) => statement,
None => begin_ansi_transaction_sql(depth),
};
let res =
conn.handle
.exec(statement)
.map(|_| {
shared.transaction_depth.fetch_add(1, Ordering::Release);
});
let res_ok = res.is_ok();
if tx.blocking_send(res).is_err() && res_ok {
// The BEGIN was processed but not acknowledged. This means no
// `Transaction` was created and so there is no way to commit /
// rollback this transaction. We need to roll it back
// immediately otherwise it would remain started forever.
if let Err(error) = conn
.handle
.exec(rollback_ansi_transaction_sql(depth + 1))
.map(|_| {
shared.transaction_depth.fetch_sub(1, Ordering::Release);
})
{
// The rollback failed. To prevent leaving the connection
// in an inconsistent state we shutdown this worker which
// causes any subsequent operation on the connection to fail.
tracing::error!(%error, "failed to rollback cancelled transaction");
break;
}
}
}
Command::Commit { tx } => {
let depth = shared.transaction_depth.load(Ordering::Acquire);
let res = if depth > 0 {
conn.handle
.exec(commit_ansi_transaction_sql(depth))
.map(|_| {
shared.transaction_depth.fetch_sub(1, Ordering::Release);
})
} else {
Ok(())
};
let res_ok = res.is_ok();
if tx.blocking_send(res).is_err() && res_ok {
// The COMMIT was processed but not acknowledged. This means that
// the `Transaction` doesn't know it was committed and will try to
// rollback on drop. We need to ignore that rollback.
ignore_next_start_rollback = true;
}
}
Command::Rollback { tx } => {
if ignore_next_start_rollback && tx.is_none() {
ignore_next_start_rollback = false;
continue;
}
let depth = shared.transaction_depth.load(Ordering::Acquire);
let res = if depth > 0 {
conn.handle
.exec(rollback_ansi_transaction_sql(depth))
.map(|_| {
shared.transaction_depth.fetch_sub(1, Ordering::Release);
})
} else {
Ok(())
};
let res_ok = res.is_ok();
if let Some(tx) = tx {
if tx.blocking_send(res).is_err() && res_ok {
// The ROLLBACK was processed but not acknowledged. This means
// that the `Transaction` doesn't know it was rolled back and
// will try to rollback again on drop. We need to ignore that
// rollback.
ignore_next_start_rollback = true;
}
}
}
Command::Serialize { schema, tx } => {
tx.send(serialize(&mut conn, schema)).ok();
}
Command::Deserialize { schema, data, read_only, tx } => {
tx.send(deserialize(&mut conn, schema, data, read_only)).ok();
}
Command::ClearCache { tx } => {
conn.statements.clear();
update_cached_statements_size(&conn, &shared.cached_statements_size);
tx.send(()).ok();
}
Command::UnlockDb => {
drop(conn);
conn = futures_executor::block_on(shared.conn.lock());
}
Command::Ping { tx } => {
tx.send(()).ok();
}
Command::Shutdown { tx } => {
// drop the connection references before sending confirmation
// and ending the command loop
drop(conn);
drop(shared);
let _ = tx.send(());
return;
}
}
}
})?;
establish_rx.await.map_err(|_| Error::WorkerCrashed)?
}
pub(crate) async fn prepare(&mut self, query: &str) -> Result<SqliteStatement<'static>, Error> {
self.oneshot_cmd(|tx| Command::Prepare {
query: query.into(),
tx,
})
.await?
}
pub(crate) async fn describe(&mut self, query: &str) -> Result<Describe<Sqlite>, Error> {
self.oneshot_cmd(|tx| Command::Describe {
query: query.into(),
tx,
})
.await?
}
pub(crate) async fn execute(
&mut self,
query: &str,
args: Option<SqliteArguments<'_>>,
chan_size: usize,
persistent: bool,
limit: Option<usize>,
) -> Result<flume::Receiver<Result<Either<SqliteQueryResult, SqliteRow>, Error>>, Error> {
let (tx, rx) = flume::bounded(chan_size);
self.command_tx
.send_async((
Command::Execute {
query: query.into(),
arguments: args.map(SqliteArguments::into_static),
persistent,
tx,
limit,
},
Span::current(),
))
.await
.map_err(|_| Error::WorkerCrashed)?;
Ok(rx)
}
pub(crate) async fn begin(
&mut self,
statement: Option<Cow<'static, str>>,
) -> Result<(), Error> {
self.oneshot_cmd_with_ack(|tx| Command::Begin { tx, statement })
.await?
}
pub(crate) async fn commit(&mut self) -> Result<(), Error> {
self.oneshot_cmd_with_ack(|tx| Command::Commit { tx })
.await?
}
pub(crate) async fn rollback(&mut self) -> Result<(), Error> {
self.oneshot_cmd_with_ack(|tx| Command::Rollback { tx: Some(tx) })
.await?
}
pub(crate) fn start_rollback(&mut self) -> Result<(), Error> {
self.command_tx
.send((Command::Rollback { tx: None }, Span::current()))
.map_err(|_| Error::WorkerCrashed)
}
pub(crate) async fn ping(&mut self) -> Result<(), Error> {
self.oneshot_cmd(|tx| Command::Ping { tx }).await
}
pub(crate) async fn deserialize(
&mut self,
schema: Option<SchemaName>,
data: SqliteOwnedBuf,
read_only: bool,
) -> Result<(), Error> {
self.oneshot_cmd(|tx| Command::Deserialize {
schema,
data,
read_only,
tx,
})
.await?
}
pub(crate) async fn serialize(
&mut self,
schema: Option<SchemaName>,
) -> Result<SqliteOwnedBuf, Error> {
self.oneshot_cmd(|tx| Command::Serialize { schema, tx })
.await?
}
async fn oneshot_cmd<F, T>(&mut self, command: F) -> Result<T, Error>
where
F: FnOnce(oneshot::Sender<T>) -> Command,
{
let (tx, rx) = oneshot::channel();
self.command_tx
.send_async((command(tx), Span::current()))
.await
.map_err(|_| Error::WorkerCrashed)?;
rx.await.map_err(|_| Error::WorkerCrashed)
}
async fn oneshot_cmd_with_ack<F, T>(&mut self, command: F) -> Result<T, Error>
where
F: FnOnce(rendezvous_oneshot::Sender<T>) -> Command,
{
let (tx, rx) = rendezvous_oneshot::channel();
self.command_tx
.send_async((command(tx), Span::current()))
.await
.map_err(|_| Error::WorkerCrashed)?;
rx.recv().await.map_err(|_| Error::WorkerCrashed)
}
pub(crate) async fn clear_cache(&mut self) -> Result<(), Error> {
self.oneshot_cmd(|tx| Command::ClearCache { tx }).await
}
pub(crate) async fn unlock_db(&mut self) -> Result<MutexGuard<'_, ConnectionState>, Error> {
let (guard, res) = futures_util::future::join(
// we need to join the wait queue for the lock before we send the message
self.shared.conn.lock(),
self.command_tx
.send_async((Command::UnlockDb, Span::current())),
)
.await;
res.map_err(|_| Error::WorkerCrashed)?;
Ok(guard)
}
/// Send a command to the worker to shut down the processing thread.
///
/// A `WorkerCrashed` error may be returned if the thread has already stopped.
pub(crate) fn shutdown(&mut self) -> impl Future<Output = Result<(), Error>> {
let (tx, rx) = oneshot::channel();
let send_res = self
.command_tx
.send((Command::Shutdown { tx }, Span::current()))
.map_err(|_| Error::WorkerCrashed);
async move {
send_res?;
// wait for the response
rx.await.map_err(|_| Error::WorkerCrashed)
}
}
}
fn prepare(conn: &mut ConnectionState, query: &str) -> Result<SqliteStatement<'static>, Error> {
// prepare statement object (or checkout from cache)
let statement = conn.statements.get(query, true)?;
let mut parameters = 0;
let mut columns = None;
let mut column_names = None;
while let Some(statement) = statement.prepare_next(&mut conn.handle)? {
parameters += statement.handle.bind_parameter_count();
// the first non-empty statement is chosen as the statement we pull columns from
if !statement.columns.is_empty() && columns.is_none() {
columns = Some(Arc::clone(statement.columns));
column_names = Some(Arc::clone(statement.column_names));
}
}
Ok(SqliteStatement {
sql: Cow::Owned(query.to_string()),
columns: columns.unwrap_or_default(),
column_names: column_names.unwrap_or_default(),
parameters,
})
}
fn update_cached_statements_size(conn: &ConnectionState, size: &AtomicUsize) {
size.store(conn.statements.len(), Ordering::Release);
}
// A oneshot channel where send completes only after the receiver receives the value.
mod rendezvous_oneshot {
use super::oneshot::{self, Canceled};
pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
let (inner_tx, inner_rx) = oneshot::channel();
(Sender { inner: inner_tx }, Receiver { inner: inner_rx })
}
pub struct Sender<T> {
inner: oneshot::Sender<(T, oneshot::Sender<()>)>,
}
impl<T> Sender<T> {
pub async fn send(self, value: T) -> Result<(), Canceled> {
let (ack_tx, ack_rx) = oneshot::channel();
self.inner.send((value, ack_tx)).map_err(|_| Canceled)?;
ack_rx.await
}
pub fn blocking_send(self, value: T) -> Result<(), Canceled> {
futures_executor::block_on(self.send(value))
}
}
pub struct Receiver<T> {
inner: oneshot::Receiver<(T, oneshot::Sender<()>)>,
}
impl<T> Receiver<T> {
pub async fn recv(self) -> Result<T, Canceled> {
let (value, ack_tx) = self.inner.await?;
ack_tx.send(()).map_err(|_| Canceled)?;
Ok(value)
}
}
}
+39
View File
@@ -0,0 +1,39 @@
pub(crate) use sqlx_core::database::{Database, HasStatementCache};
use crate::{
SqliteArgumentValue, SqliteArguments, SqliteColumn, SqliteConnection, SqliteQueryResult,
SqliteRow, SqliteStatement, SqliteTransactionManager, SqliteTypeInfo, SqliteValue,
SqliteValueRef,
};
/// Sqlite database driver.
#[derive(Debug)]
pub struct Sqlite;
impl Database for Sqlite {
type Connection = SqliteConnection;
type TransactionManager = SqliteTransactionManager;
type Row = SqliteRow;
type QueryResult = SqliteQueryResult;
type Column = SqliteColumn;
type TypeInfo = SqliteTypeInfo;
type Value = SqliteValue;
type ValueRef<'r> = SqliteValueRef<'r>;
type Arguments<'q> = SqliteArguments<'q>;
type ArgumentBuffer<'q> = Vec<SqliteArgumentValue<'q>>;
type Statement<'q> = SqliteStatement<'q>;
const NAME: &'static str = "SQLite";
const URL_SCHEMES: &'static [&'static str] = &["sqlite"];
}
impl HasStatementCache for Sqlite {}
+131
View File
@@ -0,0 +1,131 @@
use std::error::Error as StdError;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
use std::os::raw::c_int;
use std::{borrow::Cow, str};
use libsqlite3_sys::{
sqlite3, sqlite3_errmsg, sqlite3_errstr, sqlite3_extended_errcode, SQLITE_CONSTRAINT_CHECK,
SQLITE_CONSTRAINT_FOREIGNKEY, SQLITE_CONSTRAINT_NOTNULL, SQLITE_CONSTRAINT_PRIMARYKEY,
SQLITE_CONSTRAINT_UNIQUE, SQLITE_ERROR,
};
pub(crate) use sqlx_core::error::*;
// Error Codes And Messages
// https://www.sqlite.org/c3ref/errcode.html
#[derive(Debug)]
pub struct SqliteError {
code: c_int,
message: Cow<'static, str>,
}
impl SqliteError {
pub(crate) unsafe fn new(handle: *mut sqlite3) -> Self {
Self::try_new(handle).expect("There should be an error")
}
pub(crate) unsafe fn try_new(handle: *mut sqlite3) -> Option<Self> {
// returns the extended result code even when extended result codes are disabled
let code: c_int = unsafe { sqlite3_extended_errcode(handle) };
if code == 0 {
return None;
}
// return English-language text that describes the error
let message = unsafe {
let msg = sqlite3_errmsg(handle);
debug_assert!(!msg.is_null());
str::from_utf8_unchecked(CStr::from_ptr(msg).to_bytes()).to_owned()
};
Some(Self {
code,
message: message.into(),
})
}
/// For errors during extension load, the error message is supplied via a separate pointer
pub(crate) fn with_message(mut self, error_msg: String) -> Self {
self.message = error_msg.into();
self
}
pub(crate) fn from_code(code: c_int) -> Self {
let message = unsafe {
let errstr = sqlite3_errstr(code);
if !errstr.is_null() {
// SAFETY: `errstr` is guaranteed to be UTF-8
// The lifetime of the string is "internally managed";
// the implementation just selects from an array of static strings.
// We copy to an owned buffer in case `libsqlite3` is dynamically loaded somehow.
Cow::Owned(str::from_utf8_unchecked(CStr::from_ptr(errstr).to_bytes()).into())
} else {
Cow::Borrowed("<error message unavailable>")
}
};
SqliteError { code, message }
}
pub(crate) fn generic(message: impl Into<Cow<'static, str>>) -> Self {
Self {
code: SQLITE_ERROR,
message: message.into(),
}
}
}
impl Display for SqliteError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// We include the code as some produce ambiguous messages:
// SQLITE_BUSY: "database is locked"
// SQLITE_LOCKED: "database table is locked"
// Sadly there's no function to get the string label back from an error code.
write!(f, "(code: {}) {}", self.code, self.message)
}
}
impl StdError for SqliteError {}
impl DatabaseError for SqliteError {
#[inline]
fn message(&self) -> &str {
&self.message
}
/// The extended result code.
#[inline]
fn code(&self) -> Option<Cow<'_, str>> {
Some(format!("{}", self.code).into())
}
#[doc(hidden)]
fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) {
self
}
#[doc(hidden)]
fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) {
self
}
#[doc(hidden)]
fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static> {
self
}
fn kind(&self) -> ErrorKind {
match self.code {
SQLITE_CONSTRAINT_UNIQUE | SQLITE_CONSTRAINT_PRIMARYKEY => ErrorKind::UniqueViolation,
SQLITE_CONSTRAINT_FOREIGNKEY => ErrorKind::ForeignKeyViolation,
SQLITE_CONSTRAINT_NOTNULL => ErrorKind::NotNullViolation,
SQLITE_CONSTRAINT_CHECK => ErrorKind::CheckViolation,
_ => ErrorKind::Other,
}
}
}
+140
View File
@@ -0,0 +1,140 @@
//! **SQLite** database driver.
//!
//! ### Note: linkage is semver-exempt.
//! This driver uses the `libsqlite3-sys` crate which links the native library for SQLite 3.
//! With the "sqlite" feature, we enable the `bundled` feature which builds and links SQLite from
//! source.
//!
//! We reserve the right to upgrade the version of `libsqlite3-sys` as necessary to pick up new
//! `3.x.y` versions of SQLite.
//!
//! Due to Cargo's requirement that only one version of a crate that links a given native library
//! exists in the dependency graph at a time, using SQLx alongside another crate linking
//! `libsqlite3-sys` like `rusqlite` is a semver hazard.
//!
//! If you are doing so, we recommend pinning the version of both SQLx and the other crate you're
//! using to prevent a `cargo update` from breaking things, e.g.:
//!
//! ```toml
//! sqlx = { version = "=0.8.1", features = ["sqlite"] }
//! rusqlite = "=0.32.1"
//! ```
//!
//! and then upgrade these crates in lockstep when necessary.
//!
//! ### Dynamic linking
//! To dynamically link to a system SQLite library, the "sqlite-unbundled" feature can be used
//! instead.
//!
//! This allows updating SQLite independently of SQLx or using forked versions, but you must have
//! SQLite installed on the system or provide a path to the library at build time (See
//! [the `rusqlite` README](https://github.com/rusqlite/rusqlite?tab=readme-ov-file#notes-on-building-rusqlite-and-libsqlite3-sys)
//! for details).
//!
//! It may result in link errors if the SQLite version is too old. Version `3.20.0` or newer is
//! recommended. It can increase build time due to the use of bindgen.
// SQLite is a C library. All interactions require FFI which is unsafe.
// All unsafe blocks should have comments pointing to SQLite docs and ensuring that we maintain
// invariants.
#![allow(unsafe_code)]
#[macro_use]
extern crate sqlx_core;
use std::sync::atomic::AtomicBool;
pub use arguments::{SqliteArgumentValue, SqliteArguments};
pub use column::SqliteColumn;
pub use connection::serialize::SqliteOwnedBuf;
#[cfg(feature = "preupdate-hook")]
pub use connection::PreupdateHookResult;
pub use connection::{LockedSqliteHandle, SqliteConnection, SqliteOperation, UpdateHookResult};
pub use database::Sqlite;
pub use error::SqliteError;
pub use options::{
SqliteAutoVacuum, SqliteConnectOptions, SqliteJournalMode, SqliteLockingMode, SqliteSynchronous,
};
pub use query_result::SqliteQueryResult;
pub use row::SqliteRow;
pub use statement::SqliteStatement;
pub use transaction::SqliteTransactionManager;
pub use type_info::SqliteTypeInfo;
pub use value::{SqliteValue, SqliteValueRef};
use crate::connection::establish::EstablishParams;
pub(crate) use sqlx_core::driver_prelude::*;
use sqlx_core::describe::Describe;
use sqlx_core::error::Error;
use sqlx_core::executor::Executor;
mod arguments;
mod column;
mod connection;
mod database;
mod error;
mod logger;
mod options;
mod query_result;
mod row;
mod statement;
mod transaction;
mod type_checking;
mod type_info;
pub mod types;
mod value;
#[cfg(feature = "any")]
pub mod any;
#[cfg(feature = "regexp")]
mod regexp;
#[cfg(feature = "migrate")]
mod migrate;
#[cfg(feature = "migrate")]
mod testing;
/// An alias for [`Pool`][crate::pool::Pool], specialized for SQLite.
pub type SqlitePool = crate::pool::Pool<Sqlite>;
/// An alias for [`PoolOptions`][crate::pool::PoolOptions], specialized for SQLite.
pub type SqlitePoolOptions = crate::pool::PoolOptions<Sqlite>;
/// An alias for [`Executor<'_, Database = Sqlite>`][Executor].
pub trait SqliteExecutor<'c>: Executor<'c, Database = Sqlite> {}
impl<'c, T: Executor<'c, Database = Sqlite>> SqliteExecutor<'c> for T {}
/// An alias for [`Transaction`][sqlx_core::transaction::Transaction], specialized for SQLite.
pub type SqliteTransaction<'c> = sqlx_core::transaction::Transaction<'c, Sqlite>;
// NOTE: required due to the lack of lazy normalization
impl_into_arguments_for_arguments!(SqliteArguments<'q>);
impl_column_index_for_row!(SqliteRow);
impl_column_index_for_statement!(SqliteStatement);
impl_acquire!(Sqlite, SqliteConnection);
// required because some databases have a different handling of NULL
impl_encode_for_option!(Sqlite);
/// UNSTABLE: for use by `sqlx-cli` only.
#[doc(hidden)]
pub static CREATE_DB_WAL: AtomicBool = AtomicBool::new(true);
/// UNSTABLE: for use by `sqlite-macros-core` only.
#[doc(hidden)]
pub fn describe_blocking(query: &str, database_url: &str) -> Result<Describe<Sqlite>, Error> {
let opts: SqliteConnectOptions = database_url.parse()?;
let params = EstablishParams::from_options(&opts)?;
let mut conn = params.establish()?;
// Execute any ancillary `PRAGMA`s
connection::execute::iter(&mut conn, &opts.pragma_string(), None, false)?.finish()?;
connection::describe::describe(&mut conn, query)
// SQLite database is closed immediately when `conn` is dropped
}
+443
View File
@@ -0,0 +1,443 @@
// Bad casts in this module SHOULD NOT result in a SQL injection
// https://github.com/launchbadge/sqlx/issues/3440
#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
use crate::connection::intmap::IntMap;
use std::collections::HashSet;
use std::fmt::Debug;
use std::hash::Hash;
pub(crate) use sqlx_core::logger::*;
#[derive(Debug)]
pub(crate) enum BranchResult<R: Debug + 'static> {
Result(R),
Dedup(BranchParent),
Halt,
Error,
GasLimit,
LoopLimit,
Branched,
}
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, Ord, PartialOrd)]
pub(crate) struct BranchParent {
pub id: i64,
pub idx: i64,
}
#[derive(Debug)]
pub(crate) struct InstructionHistory<S: Debug + DebugDiff> {
pub program_i: usize,
pub state: S,
}
pub(crate) trait DebugDiff {
fn diff(&self, prev: &Self) -> String;
}
pub struct QueryPlanLogger<'q, R: Debug + 'static, S: Debug + DebugDiff + 'static, P: Debug> {
sql: &'q str,
unknown_operations: HashSet<usize>,
branch_origins: IntMap<BranchParent>,
branch_results: IntMap<BranchResult<R>>,
branch_operations: IntMap<IntMap<InstructionHistory<S>>>,
program: &'q [P],
}
/// convert a string into dot format
fn dot_escape_string(value: impl AsRef<str>) -> String {
value
.as_ref()
.replace('\\', r#"\\"#)
.replace('"', "'")
.replace('\n', r#"\n"#)
.to_string()
}
impl<R: Debug, S: Debug + DebugDiff, P: Debug> core::fmt::Display for QueryPlanLogger<'_, R, S, P> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
//writes query plan history in dot format
f.write_str("digraph {\n")?;
f.write_str("subgraph operations {\n")?;
f.write_str("style=\"rounded\";\nnode [shape=\"point\"];\n")?;
let all_states: std::collections::HashMap<BranchParent, &InstructionHistory<S>> = self
.branch_operations
.iter_entries()
.flat_map(
|(branch_id, instructions): (i64, &IntMap<InstructionHistory<S>>)| {
instructions.iter_entries().map(
move |(idx, ih): (i64, &InstructionHistory<S>)| {
(BranchParent { id: branch_id, idx }, ih)
},
)
},
)
.collect();
let mut instruction_uses: IntMap<Vec<BranchParent>> = Default::default();
for (k, state) in all_states.iter() {
let entry = instruction_uses.get_mut_or_default(&(state.program_i as i64));
entry.push(*k);
}
let mut branch_children: std::collections::HashMap<BranchParent, Vec<BranchParent>> =
Default::default();
let mut branched_with_state: std::collections::HashSet<BranchParent> = Default::default();
for (branch_id, branch_parent) in self.branch_origins.iter_entries() {
let entry = branch_children.entry(*branch_parent).or_default();
entry.push(BranchParent {
id: branch_id,
idx: 0,
});
}
for (idx, instruction) in self.program.iter().enumerate() {
let escaped_instruction = dot_escape_string(format!("{:?}", instruction));
write!(
f,
"subgraph cluster_{} {{ label=\"{}\"",
idx, escaped_instruction
)?;
if self.unknown_operations.contains(&idx) {
f.write_str(" style=dashed")?;
}
f.write_str(";\n")?;
let mut state_list: std::collections::BTreeMap<
String,
Vec<(BranchParent, Option<BranchParent>)>,
> = Default::default();
write!(f, "i{}[style=invis];", idx)?;
if let Some(this_instruction_uses) = instruction_uses.get(&(idx as i64)) {
for curr_ref in this_instruction_uses.iter() {
if let Some(curr_state) = all_states.get(curr_ref) {
let next_ref = BranchParent {
id: curr_ref.id,
idx: curr_ref.idx + 1,
};
if let Some(next_state) = all_states.get(&next_ref) {
let state_diff = next_state.state.diff(&curr_state.state);
state_list
.entry(state_diff)
.or_default()
.push((*curr_ref, Some(next_ref)));
} else {
state_list
.entry(Default::default())
.or_default()
.push((*curr_ref, None));
};
if let Some(children) = branch_children.get(curr_ref) {
for next_ref in children {
if let Some(next_state) = all_states.get(next_ref) {
let state_diff = next_state.state.diff(&curr_state.state);
if !state_diff.is_empty() {
branched_with_state.insert(*next_ref);
}
state_list
.entry(state_diff)
.or_default()
.push((*curr_ref, Some(*next_ref)));
}
}
};
}
}
for curr_ref in this_instruction_uses {
if branch_children.contains_key(curr_ref) {
write!(f, "\"b{}p{}\";", curr_ref.id, curr_ref.idx)?;
}
}
} else {
write!(f, "i{}->i{}[style=invis];", idx - 1, idx)?;
}
for (state_num, (state_diff, ref_list)) in state_list.iter().enumerate() {
if !state_diff.is_empty() {
let escaped_state = dot_escape_string(state_diff);
write!(
f,
"subgraph \"cluster_i{}s{}\" {{\nlabel=\"{}\"\n",
idx, state_num, escaped_state
)?;
}
for (curr_ref, next_ref) in ref_list {
if let Some(next_ref) = next_ref {
let next_program_i = all_states
.get(next_ref)
.map(|s| s.program_i.to_string())
.unwrap_or_default();
if branched_with_state.contains(next_ref) {
write!(
f,
"\"b{}p{}_b{}p{}\"[tooltip=\"next:{}\"];",
curr_ref.id,
curr_ref.idx,
next_ref.id,
next_ref.idx,
next_program_i
)?;
continue;
} else {
write!(
f,
"\"b{}p{}\"[tooltip=\"next:{}\"];",
curr_ref.id, curr_ref.idx, next_program_i
)?;
}
} else {
write!(f, "\"b{}p{}\";", curr_ref.id, curr_ref.idx)?;
}
}
if !state_diff.is_empty() {
f.write_str("}\n")?;
}
}
f.write_str("}\n")?;
}
f.write_str("};\n")?; //subgraph operations
let max_branch_id: i64 = [
self.branch_operations.last_index().unwrap_or(0),
self.branch_results.last_index().unwrap_or(0),
self.branch_results.last_index().unwrap_or(0),
]
.into_iter()
.max()
.unwrap_or(0);
f.write_str("subgraph branches {\n")?;
for branch_id in 0..=max_branch_id {
write!(f, "subgraph b{}{{", branch_id)?;
let branch_num = branch_id as usize;
let color_names = [
"blue",
"red",
"cyan",
"yellow",
"green",
"magenta",
"orange",
"purple",
"orangered",
"sienna",
"olivedrab",
"pink",
];
let color_name_root = color_names[branch_num % color_names.len()];
let color_name_suffix = match (branch_num / color_names.len()) % 4 {
0 => "1",
1 => "4",
2 => "3",
3 => "2",
_ => "",
}; //colors are easily confused after color_names.len() * 2, and outright reused after color_names.len() * 4
write!(
f,
"edge [colorscheme=x11 color={}{}];",
color_name_root, color_name_suffix
)?;
let mut instruction_list: Vec<(BranchParent, &InstructionHistory<S>)> = Vec::new();
if let Some(parent) = self.branch_origins.get(&branch_id) {
if let Some(parent_state) = all_states.get(parent) {
instruction_list.push((*parent, parent_state));
}
}
if let Some(instructions) = self.branch_operations.get(&branch_id) {
for instruction in instructions.iter_entries() {
instruction_list.push((
BranchParent {
id: branch_id,
idx: instruction.0,
},
instruction.1,
))
}
}
let mut instructions_iter = instruction_list.into_iter();
if let Some((cur_ref, _)) = instructions_iter.next() {
let mut prev_ref = cur_ref;
for (cur_ref, _) in instructions_iter {
if branched_with_state.contains(&cur_ref) {
writeln!(
f,
"\"b{}p{}\" -> \"b{}p{}_b{}p{}\" -> \"b{}p{}\"",
prev_ref.id,
prev_ref.idx,
prev_ref.id,
prev_ref.idx,
cur_ref.id,
cur_ref.idx,
cur_ref.id,
cur_ref.idx
)?;
} else {
write!(
f,
"\"b{}p{}\" -> \"b{}p{}\";",
prev_ref.id, prev_ref.idx, cur_ref.id, cur_ref.idx
)?;
}
prev_ref = cur_ref;
}
//draw edge to the result of this branch
if let Some(result) = self.branch_results.get(&branch_id) {
if let BranchResult::Dedup(dedup_ref) = result {
write!(
f,
"\"b{}p{}\"->\"b{}p{}\" [style=dotted]",
prev_ref.id, prev_ref.idx, dedup_ref.id, dedup_ref.idx
)?;
} else {
let escaped_result = dot_escape_string(format!("{:?}", result));
write!(
f,
"\"b{}p{}\" ->\"{}\"; \"{}\" [shape=box];",
prev_ref.id, prev_ref.idx, escaped_result, escaped_result
)?;
}
} else {
write!(
f,
"\"b{}p{}\" ->\"NoResult\"; \"NoResult\" [shape=box];",
prev_ref.id, prev_ref.idx
)?;
}
}
f.write_str("};\n")?;
}
f.write_str("};\n")?; //branches
f.write_str("}\n")?;
Ok(())
}
}
impl<'q, R: Debug, S: Debug + DebugDiff, P: Debug> QueryPlanLogger<'q, R, S, P> {
pub fn new(sql: &'q str, program: &'q [P]) -> Self {
Self {
sql,
unknown_operations: HashSet::new(),
branch_origins: IntMap::new(),
branch_results: IntMap::new(),
branch_operations: IntMap::new(),
program,
}
}
pub fn log_enabled(&self) -> bool {
log::log_enabled!(target: "sqlx::explain", log::Level::Trace)
|| private_tracing_dynamic_enabled!(target: "sqlx::explain", tracing::Level::TRACE)
}
pub fn add_branch<I: Copy>(&mut self, state: I, parent: &BranchParent)
where
BranchParent: From<I>,
{
if !self.log_enabled() {
return;
}
let branch: BranchParent = BranchParent::from(state);
self.branch_origins.insert(branch.id, *parent);
}
pub fn add_operation<I: Copy>(&mut self, program_i: usize, state: I)
where
BranchParent: From<I>,
S: From<I>,
{
if !self.log_enabled() {
return;
}
let branch: BranchParent = BranchParent::from(state);
let state: S = S::from(state);
self.branch_operations
.get_mut_or_default(&branch.id)
.insert(branch.idx, InstructionHistory { program_i, state });
}
pub fn add_result<I>(&mut self, state: I, result: BranchResult<R>)
where
BranchParent: for<'a> From<&'a I>,
S: From<I>,
{
if !self.log_enabled() {
return;
}
let branch: BranchParent = BranchParent::from(&state);
self.branch_results.insert(branch.id, result);
}
pub fn add_unknown_operation(&mut self, operation: usize) {
if !self.log_enabled() {
return;
}
self.unknown_operations.insert(operation);
}
pub fn finish(&self) {
if !self.log_enabled() {
return;
}
let mut summary = parse_query_summary(self.sql);
let sql = if summary != self.sql {
summary.push_str(" …");
format!(
"\n\n{}\n",
self.sql /*
sqlformat::format(
self.sql,
&sqlformat::QueryParams::None,
sqlformat::FormatOptions::default()
)
*/
)
} else {
String::new()
};
sqlx_core::private_tracing_dynamic_event!(
target: "sqlx::explain",
tracing::Level::TRACE,
"{}; program:\n{}\n\n{:?}", summary, self, sql
);
}
}
impl<'q, R: Debug, S: Debug + DebugDiff, P: Debug> Drop for QueryPlanLogger<'q, R, S, P> {
fn drop(&mut self) {
self.finish();
}
}
+213
View File
@@ -0,0 +1,213 @@
use crate::connection::{ConnectOptions, Connection};
use crate::error::Error;
use crate::executor::Executor;
use crate::fs;
use crate::migrate::MigrateError;
use crate::migrate::{AppliedMigration, Migration};
use crate::migrate::{Migrate, MigrateDatabase};
use crate::query::query;
use crate::query_as::query_as;
use crate::{Sqlite, SqliteConnectOptions, SqliteConnection, SqliteJournalMode};
use futures_core::future::BoxFuture;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
pub(crate) use sqlx_core::migrate::*;
impl MigrateDatabase for Sqlite {
fn create_database(url: &str) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(async move {
let mut opts = SqliteConnectOptions::from_str(url)?.create_if_missing(true);
// Since it doesn't make sense to include this flag in the connection URL,
// we just use an `AtomicBool` to pass it.
if super::CREATE_DB_WAL.load(Ordering::Acquire) {
opts = opts.journal_mode(SqliteJournalMode::Wal);
}
// Opening a connection to sqlite creates the database
opts.connect()
.await?
// Ensure WAL mode tempfiles are cleaned up
.close()
.await?;
Ok(())
})
}
fn database_exists(url: &str) -> BoxFuture<'_, Result<bool, Error>> {
Box::pin(async move {
let options = SqliteConnectOptions::from_str(url)?;
if options.in_memory {
Ok(true)
} else {
Ok(options.filename.exists())
}
})
}
fn drop_database(url: &str) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(async move {
let options = SqliteConnectOptions::from_str(url)?;
if !options.in_memory {
fs::remove_file(&*options.filename).await?;
}
Ok(())
})
}
}
impl Migrate for SqliteConnection {
fn ensure_migrations_table(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> {
Box::pin(async move {
// language=SQLite
self.execute(
r#"
CREATE TABLE IF NOT EXISTS _sqlx_migrations (
version BIGINT PRIMARY KEY,
description TEXT NOT NULL,
installed_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
success BOOLEAN NOT NULL,
checksum BLOB NOT NULL,
execution_time BIGINT NOT NULL
);
"#,
)
.await?;
Ok(())
})
}
fn dirty_version(&mut self) -> BoxFuture<'_, Result<Option<i64>, MigrateError>> {
Box::pin(async move {
// language=SQLite
let row: Option<(i64,)> = query_as(
"SELECT version FROM _sqlx_migrations WHERE success = false ORDER BY version LIMIT 1",
)
.fetch_optional(self)
.await?;
Ok(row.map(|r| r.0))
})
}
fn list_applied_migrations(
&mut self,
) -> BoxFuture<'_, Result<Vec<AppliedMigration>, MigrateError>> {
Box::pin(async move {
// language=SQLite
let rows: Vec<(i64, Vec<u8>)> =
query_as("SELECT version, checksum FROM _sqlx_migrations ORDER BY version")
.fetch_all(self)
.await?;
let migrations = rows
.into_iter()
.map(|(version, checksum)| AppliedMigration {
version,
checksum: checksum.into(),
})
.collect();
Ok(migrations)
})
}
fn lock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> {
Box::pin(async move { Ok(()) })
}
fn unlock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> {
Box::pin(async move { Ok(()) })
}
fn apply<'e: 'm, 'm>(
&'e mut self,
migration: &'m Migration,
) -> BoxFuture<'m, Result<Duration, MigrateError>> {
Box::pin(async move {
let mut tx = self.begin().await?;
let start = Instant::now();
// Use a single transaction for the actual migration script and the essential bookeeping so we never
// execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966.
// The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for
// data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1
// and update it once the actual transaction completed.
let _ = tx
.execute(&*migration.sql)
.await
.map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?;
// language=SQL
let _ = query(
r#"
INSERT INTO _sqlx_migrations ( version, description, success, checksum, execution_time )
VALUES ( ?1, ?2, TRUE, ?3, -1 )
"#,
)
.bind(migration.version)
.bind(&*migration.description)
.bind(&*migration.checksum)
.execute(&mut *tx)
.await?;
tx.commit().await?;
// Update `elapsed_time`.
// NOTE: The process may disconnect/die at this point, so the elapsed time value might be lost. We accept
// this small risk since this value is not super important.
let elapsed = start.elapsed();
// language=SQL
#[allow(clippy::cast_possible_truncation)]
let _ = query(
r#"
UPDATE _sqlx_migrations
SET execution_time = ?1
WHERE version = ?2
"#,
)
.bind(elapsed.as_nanos() as i64)
.bind(migration.version)
.execute(self)
.await?;
Ok(elapsed)
})
}
fn revert<'e: 'm, 'm>(
&'e mut self,
migration: &'m Migration,
) -> BoxFuture<'m, Result<Duration, MigrateError>> {
Box::pin(async move {
// Use a single transaction for the actual migration script and the essential bookeeping so we never
// execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966.
let mut tx = self.begin().await?;
let start = Instant::now();
let _ = tx.execute(&*migration.sql).await?;
// language=SQL
let _ = query(r#"DELETE FROM _sqlx_migrations WHERE version = ?1"#)
.bind(migration.version)
.execute(&mut *tx)
.await?;
tx.commit().await?;
let elapsed = start.elapsed();
Ok(elapsed)
})
}
}
+38
View File
@@ -0,0 +1,38 @@
use crate::error::Error;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SqliteAutoVacuum {
#[default]
None,
Full,
Incremental,
}
impl SqliteAutoVacuum {
pub(crate) fn as_str(&self) -> &'static str {
match self {
SqliteAutoVacuum::None => "NONE",
SqliteAutoVacuum::Full => "FULL",
SqliteAutoVacuum::Incremental => "INCREMENTAL",
}
}
}
impl FromStr for SqliteAutoVacuum {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
Ok(match &*s.to_ascii_lowercase() {
"none" => SqliteAutoVacuum::None,
"full" => SqliteAutoVacuum::Full,
"incremental" => SqliteAutoVacuum::Incremental,
_ => {
return Err(Error::Configuration(
format!("unknown value {s:?} for `auto_vacuum`").into(),
));
}
})
}
}
+77
View File
@@ -0,0 +1,77 @@
use crate::{SqliteConnectOptions, SqliteConnection};
use futures_core::future::BoxFuture;
use log::LevelFilter;
use sqlx_core::connection::ConnectOptions;
use sqlx_core::error::Error;
use sqlx_core::executor::Executor;
use std::fmt::Write;
use std::str::FromStr;
use std::time::Duration;
use url::Url;
impl ConnectOptions for SqliteConnectOptions {
type Connection = SqliteConnection;
fn from_url(url: &Url) -> Result<Self, Error> {
// SQLite URL parsing is handled specially;
// we want to treat the following URLs as equivalent:
//
// * sqlite:foo.db
// * sqlite://foo.db
//
// If we used `Url::path()`, the latter would return an empty string
// because `foo.db` gets parsed as the hostname.
Self::from_str(url.as_str())
}
fn to_url_lossy(&self) -> Url {
self.build_url()
}
fn connect(&self) -> BoxFuture<'_, Result<Self::Connection, Error>>
where
Self::Connection: Sized,
{
Box::pin(async move {
let mut conn = SqliteConnection::establish(self).await?;
// Execute PRAGMAs
conn.execute(&*self.pragma_string()).await?;
if !self.collations.is_empty() {
let mut locked = conn.lock_handle().await?;
for collation in &self.collations {
collation.create(&mut locked.guard.handle)?;
}
}
Ok(conn)
})
}
fn log_statements(mut self, level: LevelFilter) -> Self {
self.log_settings.log_statements(level);
self
}
fn log_slow_statements(mut self, level: LevelFilter, duration: Duration) -> Self {
self.log_settings.log_slow_statements(level, duration);
self
}
}
impl SqliteConnectOptions {
/// Collect all `PRAMGA` commands into a single string
pub(crate) fn pragma_string(&self) -> String {
let mut string = String::new();
for (key, opt_value) in &self.pragmas {
if let Some(value) = opt_value {
write!(string, "PRAGMA {key} = {value}; ").ok();
}
}
string
}
}
+50
View File
@@ -0,0 +1,50 @@
use crate::error::Error;
use std::str::FromStr;
/// Refer to [SQLite documentation] for the meaning of the database journaling mode.
///
/// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_journal_mode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SqliteJournalMode {
Delete,
Truncate,
Persist,
Memory,
#[default]
Wal,
Off,
}
impl SqliteJournalMode {
pub(crate) fn as_str(&self) -> &'static str {
match self {
SqliteJournalMode::Delete => "DELETE",
SqliteJournalMode::Truncate => "TRUNCATE",
SqliteJournalMode::Persist => "PERSIST",
SqliteJournalMode::Memory => "MEMORY",
SqliteJournalMode::Wal => "WAL",
SqliteJournalMode::Off => "OFF",
}
}
}
impl FromStr for SqliteJournalMode {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
Ok(match &*s.to_ascii_lowercase() {
"delete" => SqliteJournalMode::Delete,
"truncate" => SqliteJournalMode::Truncate,
"persist" => SqliteJournalMode::Persist,
"memory" => SqliteJournalMode::Memory,
"wal" => SqliteJournalMode::Wal,
"off" => SqliteJournalMode::Off,
_ => {
return Err(Error::Configuration(
format!("unknown value {s:?} for `journal_mode`").into(),
));
}
})
}
}
+38
View File
@@ -0,0 +1,38 @@
use crate::error::Error;
use std::str::FromStr;
/// Refer to [SQLite documentation] for the meaning of the connection locking mode.
///
/// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_locking_mode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SqliteLockingMode {
#[default]
Normal,
Exclusive,
}
impl SqliteLockingMode {
pub(crate) fn as_str(&self) -> &'static str {
match self {
SqliteLockingMode::Normal => "NORMAL",
SqliteLockingMode::Exclusive => "EXCLUSIVE",
}
}
}
impl FromStr for SqliteLockingMode {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
Ok(match &*s.to_ascii_lowercase() {
"normal" => SqliteLockingMode::Normal,
"exclusive" => SqliteLockingMode::Exclusive,
_ => {
return Err(Error::Configuration(
format!("unknown value {s:?} for `locking_mode`").into(),
));
}
})
}
}
+578
View File
@@ -0,0 +1,578 @@
use std::path::Path;
mod auto_vacuum;
mod connect;
mod journal_mode;
mod locking_mode;
mod parse;
mod synchronous;
use crate::connection::LogSettings;
pub use auto_vacuum::SqliteAutoVacuum;
pub use journal_mode::SqliteJournalMode;
pub use locking_mode::SqliteLockingMode;
use std::cmp::Ordering;
use std::sync::Arc;
use std::{borrow::Cow, time::Duration};
pub use synchronous::SqliteSynchronous;
use crate::common::DebugFn;
use crate::connection::collation::Collation;
use sqlx_core::IndexMap;
/// Options and flags which can be used to configure a SQLite connection.
///
/// A value of `SqliteConnectOptions` can be parsed from a connection URL,
/// as described by [SQLite](https://www.sqlite.org/uri.html).
///
/// This type also implements [`FromStr`][std::str::FromStr] so you can parse it from a string
/// containing a connection URL and then further adjust options if necessary (see example below).
///
/// | URL | Description |
/// | -- | -- |
/// `sqlite::memory:` | Open an in-memory database. |
/// `sqlite:data.db` | Open the file `data.db` in the current directory. |
/// `sqlite://data.db` | Open the file `data.db` in the current directory. |
/// `sqlite:///data.db` | Open the file `data.db` from the root (`/`) directory. |
/// `sqlite://data.db?mode=ro` | Open the file `data.db` for read-only access. |
///
/// # Example
///
/// ```rust,no_run
/// # async fn example() -> sqlx::Result<()> {
/// use sqlx::ConnectOptions;
/// use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool};
/// use std::str::FromStr;
///
/// let opts = SqliteConnectOptions::from_str("sqlite://data.db")?
/// .journal_mode(SqliteJournalMode::Wal)
/// .read_only(true);
///
/// // use in a pool
/// let pool = SqlitePool::connect_with(opts).await?;
///
/// // or connect directly
/// # let opts = SqliteConnectOptions::from_str("sqlite://data.db")?;
/// let conn = opts.connect().await?;
/// #
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct SqliteConnectOptions {
pub(crate) filename: Cow<'static, Path>,
pub(crate) in_memory: bool,
pub(crate) read_only: bool,
pub(crate) create_if_missing: bool,
pub(crate) shared_cache: bool,
pub(crate) statement_cache_capacity: usize,
pub(crate) busy_timeout: Duration,
pub(crate) log_settings: LogSettings,
pub(crate) immutable: bool,
pub(crate) vfs: Option<Cow<'static, str>>,
pub(crate) pragmas: IndexMap<Cow<'static, str>, Option<Cow<'static, str>>>,
/// Extensions are specified as a pair of \<Extension Name : Optional Entry Point>, the majority
/// of SQLite extensions will use the default entry points specified in the docs, these should
/// be added to the map with a `None` value.
/// <https://www.sqlite.org/loadext.html#loading_an_extension>
pub(crate) extensions: IndexMap<Cow<'static, str>, Option<Cow<'static, str>>>,
pub(crate) command_channel_size: usize,
pub(crate) row_channel_size: usize,
pub(crate) collations: Vec<Collation>,
pub(crate) serialized: bool,
pub(crate) thread_name: Arc<DebugFn<dyn Fn(u64) -> String + Send + Sync + 'static>>,
pub(crate) optimize_on_close: OptimizeOnClose,
#[cfg(feature = "regexp")]
pub(crate) register_regexp_function: bool,
}
#[derive(Clone, Debug)]
pub enum OptimizeOnClose {
Enabled { analysis_limit: Option<u32> },
Disabled,
}
impl Default for SqliteConnectOptions {
fn default() -> Self {
Self::new()
}
}
impl SqliteConnectOptions {
/// Construct `Self` with default options.
///
/// See the source of this method for the current defaults.
pub fn new() -> Self {
let mut pragmas: IndexMap<Cow<'static, str>, Option<Cow<'static, str>>> = IndexMap::new();
// Standard pragmas
//
// Most of these don't actually need to be sent because they would be set to their
// default values anyway. See the SQLite documentation for default values of these PRAGMAs:
// https://www.sqlite.org/pragma.html
//
// However, by inserting into the map here, we can ensure that they're set in the proper
// order, even if they're overwritten later by their respective setters or
// directly by `pragma()`
// SQLCipher special case: if the `key` pragma is set, it must be executed first.
pragmas.insert("key".into(), None);
// Other SQLCipher pragmas that has to be after the key, but before any other operation on the database.
// https://www.zetetic.net/sqlcipher/sqlcipher-api/
// Bytes of the database file that is not encrypted
// Default for SQLCipher v4 is 0
// If greater than zero 'cipher_salt' pragma must be also defined
pragmas.insert("cipher_plaintext_header_size".into(), None);
// Allows to provide salt manually
// By default SQLCipher sets salt automatically, use only in conjunction with
// 'cipher_plaintext_header_size' pragma
pragmas.insert("cipher_salt".into(), None);
// Number of iterations used in PBKDF2 key derivation.
// Default for SQLCipher v4 is 256000
pragmas.insert("kdf_iter".into(), None);
// Define KDF algorithm to be used.
// Default for SQLCipher v4 is PBKDF2_HMAC_SHA512.
pragmas.insert("cipher_kdf_algorithm".into(), None);
// Enable or disable HMAC functionality.
// Default for SQLCipher v4 is 1.
pragmas.insert("cipher_use_hmac".into(), None);
// Set default encryption settings depending on the version 1,2,3, or 4.
pragmas.insert("cipher_compatibility".into(), None);
// Page size of encrypted database.
// Default for SQLCipher v4 is 4096.
pragmas.insert("cipher_page_size".into(), None);
// Choose algorithm used for HMAC.
// Default for SQLCipher v4 is HMAC_SHA512.
pragmas.insert("cipher_hmac_algorithm".into(), None);
// Normally, page_size must be set before any other action on the database.
// Defaults to 4096 for new databases.
pragmas.insert("page_size".into(), None);
// locking_mode should be set before journal_mode:
// https://www.sqlite.org/wal.html#use_of_wal_without_shared_memory
pragmas.insert("locking_mode".into(), None);
// `auto_vacuum` needs to be executed before `journal_mode`, if set.
//
// Otherwise, a change in the `journal_mode` setting appears to mark even an empty database as dirty,
// requiring a `vacuum` command to be executed to actually apply the new `auto_vacuum` setting.
pragmas.insert("auto_vacuum".into(), None);
// Don't set `journal_mode` unless the user requested it.
// WAL mode is a permanent setting for created databases and changing into or out of it
// requires an exclusive lock that can't be waited on with `sqlite3_busy_timeout()`.
// https://github.com/launchbadge/sqlx/pull/1930#issuecomment-1168165414
pragmas.insert("journal_mode".into(), None);
// We choose to enable foreign key enforcement by default, though SQLite normally
// leaves it off for backward compatibility: https://www.sqlite.org/foreignkeys.html#fk_enable
pragmas.insert("foreign_keys".into(), Some("ON".into()));
// The `synchronous` pragma defaults to FULL
// https://www.sqlite.org/compile.html#default_synchronous.
pragmas.insert("synchronous".into(), None);
// Soft limit on the number of rows that `ANALYZE` touches per index.
pragmas.insert("analysis_limit".into(), None);
Self {
filename: Cow::Borrowed(Path::new(":memory:")),
in_memory: false,
read_only: false,
create_if_missing: false,
shared_cache: false,
statement_cache_capacity: 100,
busy_timeout: Duration::from_secs(5),
log_settings: Default::default(),
immutable: false,
vfs: None,
pragmas,
extensions: Default::default(),
collations: Default::default(),
serialized: false,
thread_name: Arc::new(DebugFn(|id| format!("sqlx-sqlite-worker-{id}"))),
command_channel_size: 50,
row_channel_size: 50,
optimize_on_close: OptimizeOnClose::Disabled,
#[cfg(feature = "regexp")]
register_regexp_function: false,
}
}
/// Sets the name of the database file.
///
/// This is a low-level API, and SQLx will apply no special treatment for `":memory:"` as an
/// in-memory database using this method. Using [`SqliteConnectOptions::from_str()`][SqliteConnectOptions#from_str] may be
/// preferred for simple use cases.
pub fn filename(mut self, filename: impl AsRef<Path>) -> Self {
self.filename = Cow::Owned(filename.as_ref().to_owned());
self
}
/// Gets the current name of the database file.
pub fn get_filename(&self) -> &Path {
&self.filename
}
/// Set the enforcement of [foreign key constraints](https://www.sqlite.org/pragma.html#pragma_foreign_keys).
///
/// SQLx chooses to enable this by default so that foreign keys function as expected,
/// compared to other database flavors.
pub fn foreign_keys(self, on: bool) -> Self {
self.pragma("foreign_keys", if on { "ON" } else { "OFF" })
}
/// Set the [`SQLITE_OPEN_MEMORY` flag](https://sqlite.org/c3ref/open.html).
///
/// By default, this is disabled.
pub fn in_memory(mut self, in_memory: bool) -> Self {
self.in_memory = in_memory;
self
}
/// Set the [`SQLITE_OPEN_SHAREDCACHE` flag](https://sqlite.org/sharedcache.html).
///
/// By default, this is disabled.
pub fn shared_cache(mut self, on: bool) -> Self {
self.shared_cache = on;
self
}
/// Sets the [journal mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) for the database connection.
///
/// Journal modes are ephemeral per connection, with the exception of the
/// [Write-Ahead Log (WAL) mode](https://www.sqlite.org/wal.html).
///
/// A database created in WAL mode retains the setting and will apply it to all connections
/// opened against it that don't set a `journal_mode`.
///
/// Opening a connection to a database created in WAL mode with a different `journal_mode` will
/// erase the setting on the database, requiring an exclusive lock to do so.
/// You may get a `database is locked` (corresponding to `SQLITE_BUSY`) error if another
/// connection is accessing the database file at the same time.
///
/// SQLx does not set a journal mode by default, to avoid unintentionally changing a database
/// into or out of WAL mode.
///
/// The default journal mode for non-WAL databases is `DELETE`, or `MEMORY` for in-memory
/// databases.
///
/// For consistency, any commands in `sqlx-cli` which create a SQLite database will create it
/// in WAL mode.
pub fn journal_mode(self, mode: SqliteJournalMode) -> Self {
self.pragma("journal_mode", mode.as_str())
}
/// Sets the [locking mode](https://www.sqlite.org/pragma.html#pragma_locking_mode) for the database connection.
///
/// The default locking mode is NORMAL.
pub fn locking_mode(self, mode: SqliteLockingMode) -> Self {
self.pragma("locking_mode", mode.as_str())
}
/// Sets the [access mode](https://www.sqlite.org/c3ref/open.html) to open the database
/// for read-only access.
pub fn read_only(mut self, read_only: bool) -> Self {
self.read_only = read_only;
self
}
/// Sets the [access mode](https://www.sqlite.org/c3ref/open.html) to create the database file
/// if the file does not exist.
///
/// By default, a new file **will not be created** if one is not found.
pub fn create_if_missing(mut self, create: bool) -> Self {
self.create_if_missing = create;
self
}
/// Sets the capacity of the connection's statement cache in a number of stored
/// distinct statements. Caching is handled using LRU, meaning when the
/// amount of queries hits the defined limit, the oldest statement will get
/// dropped.
///
/// The default cache capacity is 100 statements.
pub fn statement_cache_capacity(mut self, capacity: usize) -> Self {
self.statement_cache_capacity = capacity;
self
}
/// Sets a timeout value to wait when the database is locked, before
/// returning a busy timeout error.
///
/// The default busy timeout is 5 seconds.
pub fn busy_timeout(mut self, timeout: Duration) -> Self {
self.busy_timeout = timeout;
self
}
/// Sets the [synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous) setting for the database connection.
///
/// The default synchronous settings is FULL. However, if durability is not a concern,
/// then NORMAL is normally all one needs in WAL mode.
pub fn synchronous(self, synchronous: SqliteSynchronous) -> Self {
self.pragma("synchronous", synchronous.as_str())
}
/// Sets the [auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum) setting for the database connection.
///
/// The default auto_vacuum setting is NONE.
///
/// For existing databases, a change to this value does not take effect unless a
/// [`VACUUM` command](https://www.sqlite.org/lang_vacuum.html) is executed.
pub fn auto_vacuum(self, auto_vacuum: SqliteAutoVacuum) -> Self {
self.pragma("auto_vacuum", auto_vacuum.as_str())
}
/// Sets the [page_size](https://www.sqlite.org/pragma.html#pragma_page_size) setting for the database connection.
///
/// The default page_size setting is 4096.
///
/// For existing databases, a change to this value does not take effect unless a
/// [`VACUUM` command](https://www.sqlite.org/lang_vacuum.html) is executed.
/// However, it cannot be changed in WAL mode.
pub fn page_size(self, page_size: u32) -> Self {
self.pragma("page_size", page_size.to_string())
}
/// Sets custom initial pragma for the database connection.
pub fn pragma<K, V>(mut self, key: K, value: V) -> Self
where
K: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>,
{
self.pragmas.insert(key.into(), Some(value.into()));
self
}
/// Add a custom collation for comparing strings in SQL.
///
/// If a collation with the same name already exists, it will be replaced.
///
/// See [`sqlite3_create_collation()`](https://www.sqlite.org/c3ref/create_collation.html) for details.
///
/// Note this excerpt:
/// > The collating function must obey the following properties for all strings A, B, and C:
/// >
/// > If A==B then B==A.
/// > If A==B and B==C then A==C.
/// > If A\<B then B>A.
/// > If A<B and B<C then A<C.
/// >
/// > If a collating function fails any of the above constraints and that collating function is
/// > registered and used, then the behavior of SQLite is undefined.
pub fn collation<N, F>(mut self, name: N, collate: F) -> Self
where
N: Into<Arc<str>>,
F: Fn(&str, &str) -> Ordering + Send + Sync + 'static,
{
self.collations.push(Collation::new(name, collate));
self
}
/// Set to `true` to signal to SQLite that the database file is on read-only media.
///
/// If enabled, SQLite assumes the database file _cannot_ be modified, even by higher
/// privileged processes, and so disables locking and change detection. This is intended
/// to improve performance but can produce incorrect query results or errors if the file
/// _does_ change.
///
/// Note that this is different from the `SQLITE_OPEN_READONLY` flag set by
/// [`.read_only()`][Self::read_only], though the documentation suggests that this
/// does _imply_ `SQLITE_OPEN_READONLY`.
///
/// See [`sqlite3_open`](https://www.sqlite.org/capi3ref.html#sqlite3_open) (subheading
/// "URI Filenames") for details.
pub fn immutable(mut self, immutable: bool) -> Self {
self.immutable = immutable;
self
}
/// Sets the [threading mode](https://www.sqlite.org/threadsafe.html) for the database connection.
///
/// The default setting is `false` corresponding to using `OPEN_NOMUTEX`.
/// If set to `true` then `OPEN_FULLMUTEX`.
///
/// See [open](https://www.sqlite.org/c3ref/open.html) for more details.
///
/// ### Note
/// Setting this to `true` may help if you are getting access violation errors or segmentation
/// faults, but will also incur a significant performance penalty. You should leave this
/// set to `false` if at all possible.
///
/// If you do end up needing to set this to `true` for some reason, please
/// [open an issue](https://github.com/launchbadge/sqlx/issues/new/choose) as this may indicate
/// a concurrency bug in SQLx. Please provide clear instructions for reproducing the issue,
/// including a sample database schema if applicable.
pub fn serialized(mut self, serialized: bool) -> Self {
self.serialized = serialized;
self
}
/// Provide a callback to generate the name of the background worker thread.
///
/// The value passed to the callback is an auto-incremented integer for use as the thread ID.
pub fn thread_name(
mut self,
generator: impl Fn(u64) -> String + Send + Sync + 'static,
) -> Self {
self.thread_name = Arc::new(DebugFn(generator));
self
}
/// Set the maximum number of commands to buffer for the worker thread before backpressure is
/// applied.
///
/// Given that most commands sent to the worker thread involve waiting for a result,
/// the command channel is unlikely to fill up unless a lot queries are executed in a short
/// period but cancelled before their full resultsets are returned.
pub fn command_buffer_size(mut self, size: usize) -> Self {
self.command_channel_size = size;
self
}
/// Set the maximum number of rows to buffer back to the calling task when a query is executed.
///
/// If the calling task cannot keep up, backpressure will be applied to the worker thread
/// in order to limit CPU and memory usage.
pub fn row_buffer_size(mut self, size: usize) -> Self {
self.row_channel_size = size;
self
}
/// Sets the [`vfs`](https://www.sqlite.org/vfs.html) parameter of the database connection.
///
/// The default value is empty, and sqlite will use the default VFS object depending on the
/// operating system.
pub fn vfs(mut self, vfs_name: impl Into<Cow<'static, str>>) -> Self {
self.vfs = Some(vfs_name.into());
self
}
/// Load an [extension](https://www.sqlite.org/loadext.html) at run-time when the database connection
/// is established, using the default entry point.
///
/// Most common SQLite extensions can be loaded using this method, for extensions where you need
/// to specify the entry point, use [`extension_with_entrypoint`][`Self::extension_with_entrypoint`] instead.
///
/// Multiple extensions can be loaded by calling the method repeatedly on the options struct, they
/// will be loaded in the order they are added.
/// ```rust,no_run
/// # use sqlx_core::error::Error;
/// # use std::str::FromStr;
/// # use sqlx_sqlite::SqliteConnectOptions;
/// # fn options() -> Result<SqliteConnectOptions, Error> {
/// let options = SqliteConnectOptions::from_str("sqlite://data.db")?
/// .extension("vsv")
/// .extension("mod_spatialite");
/// # Ok(options)
/// # }
/// ```
pub fn extension(mut self, extension_name: impl Into<Cow<'static, str>>) -> Self {
self.extensions.insert(extension_name.into(), None);
self
}
/// Load an extension with a specified entry point.
///
/// Useful when using non-standard extensions, or when developing your own, the second argument
/// specifies where SQLite should expect to find the extension init routine.
pub fn extension_with_entrypoint(
mut self,
extension_name: impl Into<Cow<'static, str>>,
entry_point: impl Into<Cow<'static, str>>,
) -> Self {
self.extensions
.insert(extension_name.into(), Some(entry_point.into()));
self
}
/// Execute `PRAGMA optimize;` on the SQLite connection before closing.
///
/// The SQLite manual recommends using this for long-lived databases.
///
/// This will collect and store statistics about the layout of data in your tables to help the query planner make better decisions.
/// Over the connection's lifetime, the query planner will make notes about which tables could use up-to-date statistics so this
/// command doesn't have to scan the whole database every time. Thus, the best time to execute this is on connection close.
///
/// `analysis_limit` sets a soft limit on the maximum number of rows to scan per index.
/// It is equivalent to setting [`Self::analysis_limit`] but only takes effect for the `PRAGMA optimize;` call
/// and does not affect the behavior of any `ANALYZE` statements made during the connection's lifetime.
///
/// If not `None`, the `analysis_limit` here overrides the global `analysis_limit` setting,
/// but only for the `PRAGMA optimize;` call.
///
/// Not enabled by default.
///
/// See [the SQLite manual](https://www.sqlite.org/lang_analyze.html#automatically_running_analyze) for details.
pub fn optimize_on_close(
mut self,
enabled: bool,
analysis_limit: impl Into<Option<u32>>,
) -> Self {
self.optimize_on_close = if enabled {
OptimizeOnClose::Enabled {
analysis_limit: (analysis_limit.into()),
}
} else {
OptimizeOnClose::Disabled
};
self
}
/// Set a soft limit on the number of rows that `ANALYZE` touches per index.
///
/// This also affects `PRAGMA optimize` which is set by [Self::optimize_on_close].
///
/// The value recommended by SQLite is `400`. There is no default.
///
/// See [the SQLite manual](https://www.sqlite.org/lang_analyze.html#approx) for details.
pub fn analysis_limit(mut self, limit: impl Into<Option<u32>>) -> Self {
if let Some(limit) = limit.into() {
return self.pragma("analysis_limit", limit.to_string());
}
self.pragmas.insert("analysis_limit".into(), None);
self
}
/// Register a regexp function that allows using regular expressions in queries.
///
/// ```
/// # use std::str::FromStr;
/// # use sqlx::{ConnectOptions, Connection, Row};
/// # use sqlx_sqlite::SqliteConnectOptions;
/// # async fn run() -> sqlx::Result<()> {
/// let mut sqlite = SqliteConnectOptions::from_str("sqlite://:memory:")?
/// .with_regexp()
/// .connect()
/// .await?;
/// let tables = sqlx::query("SELECT name FROM sqlite_schema WHERE name REGEXP 'foo(\\d+)bar'")
/// .fetch_all(&mut sqlite)
/// .await?;
/// # Ok(())
/// # }
/// ```
///
/// This uses the [`regex`] crate, and is only enabled when you enable the `regex` feature is enabled on sqlx
#[cfg(feature = "regexp")]
pub fn with_regexp(mut self) -> Self {
self.register_regexp_function = true;
self
}
}
+231
View File
@@ -0,0 +1,231 @@
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::atomic::{AtomicUsize, Ordering};
use percent_encoding::{percent_decode_str, percent_encode, AsciiSet};
use url::Url;
use crate::error::Error;
use crate::SqliteConnectOptions;
// https://www.sqlite.org/uri.html
static IN_MEMORY_DB_SEQ: AtomicUsize = AtomicUsize::new(0);
impl SqliteConnectOptions {
pub(crate) fn from_db_and_params(database: &str, params: Option<&str>) -> Result<Self, Error> {
let mut options = Self::default();
if database == ":memory:" {
options.in_memory = true;
options.shared_cache = true;
let seqno = IN_MEMORY_DB_SEQ.fetch_add(1, Ordering::Relaxed);
options.filename = Cow::Owned(PathBuf::from(format!("file:sqlx-in-memory-{seqno}")));
} else {
// % decode to allow for `?` or `#` in the filename
options.filename = Cow::Owned(
Path::new(
&*percent_decode_str(database)
.decode_utf8()
.map_err(Error::config)?,
)
.to_path_buf(),
);
}
if let Some(params) = params {
for (key, value) in url::form_urlencoded::parse(params.as_bytes()) {
match &*key {
// The mode query parameter determines if the new database is opened read-only,
// read-write, read-write and created if it does not exist, or that the
// database is a pure in-memory database that never interacts with disk,
// respectively.
"mode" => {
match &*value {
"ro" => {
options.read_only = true;
}
// default
"rw" => {}
"rwc" => {
options.create_if_missing = true;
}
"memory" => {
options.in_memory = true;
options.shared_cache = true;
}
_ => {
return Err(Error::Configuration(
format!("unknown value {value:?} for `mode`").into(),
));
}
}
}
// The cache query parameter specifies the cache behaviour across multiple
// connections to the same database within the process. A shared cache is
// essential for persisting data across connections to an in-memory database.
"cache" => match &*value {
"private" => {
options.shared_cache = false;
}
"shared" => {
options.shared_cache = true;
}
_ => {
return Err(Error::Configuration(
format!("unknown value {value:?} for `cache`").into(),
));
}
},
"immutable" => match &*value {
"true" | "1" => {
options.immutable = true;
}
"false" | "0" => {
options.immutable = false;
}
_ => {
return Err(Error::Configuration(
format!("unknown value {value:?} for `immutable`").into(),
));
}
},
"vfs" => options.vfs = Some(Cow::Owned(value.into_owned())),
_ => {
return Err(Error::Configuration(
format!("unknown query parameter `{key}` while parsing connection URL")
.into(),
));
}
}
}
}
Ok(options)
}
pub(crate) fn build_url(&self) -> Url {
// https://url.spec.whatwg.org/#path-percent-encode-set
static PATH_ENCODE_SET: AsciiSet = percent_encoding::CONTROLS
.add(b' ')
.add(b'"')
.add(b'#')
.add(b'<')
.add(b'>')
.add(b'?')
.add(b'`')
.add(b'{')
.add(b'}');
let filename_encoded = percent_encode(
self.filename.as_os_str().as_encoded_bytes(),
&PATH_ENCODE_SET,
);
let mut url = Url::parse(&format!("sqlite://{filename_encoded}"))
.expect("BUG: generated un-parseable URL");
let mode = match (self.in_memory, self.create_if_missing, self.read_only) {
(true, _, _) => "memory",
(false, true, _) => "rwc",
(false, false, true) => "ro",
(false, false, false) => "rw",
};
url.query_pairs_mut().append_pair("mode", mode);
let cache = match self.shared_cache {
true => "shared",
false => "private",
};
url.query_pairs_mut().append_pair("cache", cache);
if self.immutable {
url.query_pairs_mut().append_pair("immutable", "true");
}
if let Some(vfs) = &self.vfs {
url.query_pairs_mut().append_pair("vfs", vfs);
}
url
}
}
impl FromStr for SqliteConnectOptions {
type Err = Error;
fn from_str(mut url: &str) -> Result<Self, Self::Err> {
// remove scheme from the URL
url = url
.trim_start_matches("sqlite://")
.trim_start_matches("sqlite:");
let mut database_and_params = url.splitn(2, '?');
let database = database_and_params.next().unwrap_or_default();
let params = database_and_params.next();
Self::from_db_and_params(database, params)
}
}
#[test]
fn test_parse_in_memory() -> Result<(), Error> {
let options: SqliteConnectOptions = "sqlite::memory:".parse()?;
assert!(options.in_memory);
assert!(options.shared_cache);
let options: SqliteConnectOptions = "sqlite://?mode=memory".parse()?;
assert!(options.in_memory);
assert!(options.shared_cache);
let options: SqliteConnectOptions = "sqlite://:memory:".parse()?;
assert!(options.in_memory);
assert!(options.shared_cache);
let options: SqliteConnectOptions = "sqlite://?mode=memory&cache=private".parse()?;
assert!(options.in_memory);
assert!(!options.shared_cache);
Ok(())
}
#[test]
fn test_parse_read_only() -> Result<(), Error> {
let options: SqliteConnectOptions = "sqlite://a.db?mode=ro".parse()?;
assert!(options.read_only);
assert_eq!(&*options.filename.to_string_lossy(), "a.db");
Ok(())
}
#[test]
fn test_parse_shared_in_memory() -> Result<(), Error> {
let options: SqliteConnectOptions = "sqlite://a.db?cache=shared".parse()?;
assert!(options.shared_cache);
assert_eq!(&*options.filename.to_string_lossy(), "a.db");
Ok(())
}
#[test]
fn it_returns_the_parsed_url() -> Result<(), Error> {
let url = "sqlite://test.db?mode=rw&cache=shared";
let options: SqliteConnectOptions = url.parse()?;
let expected_url = Url::parse(url).unwrap();
assert_eq!(options.build_url(), expected_url);
Ok(())
}
+44
View File
@@ -0,0 +1,44 @@
use crate::error::Error;
use std::str::FromStr;
/// Refer to [SQLite documentation] for the meaning of various synchronous settings.
///
/// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_synchronous
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SqliteSynchronous {
Off,
Normal,
#[default]
Full,
Extra,
}
impl SqliteSynchronous {
pub(crate) fn as_str(&self) -> &'static str {
match self {
SqliteSynchronous::Off => "OFF",
SqliteSynchronous::Normal => "NORMAL",
SqliteSynchronous::Full => "FULL",
SqliteSynchronous::Extra => "EXTRA",
}
}
}
impl FromStr for SqliteSynchronous {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
Ok(match &*s.to_ascii_lowercase() {
"off" => SqliteSynchronous::Off,
"normal" => SqliteSynchronous::Normal,
"full" => SqliteSynchronous::Full,
"extra" => SqliteSynchronous::Extra,
_ => {
return Err(Error::Configuration(
format!("unknown value {s:?} for `synchronous`").into(),
));
}
})
}
}
+40
View File
@@ -0,0 +1,40 @@
use std::iter::{Extend, IntoIterator};
#[derive(Debug, Default)]
pub struct SqliteQueryResult {
pub(super) changes: u64,
pub(super) last_insert_rowid: i64,
}
impl SqliteQueryResult {
pub fn rows_affected(&self) -> u64 {
self.changes
}
pub fn last_insert_rowid(&self) -> i64 {
self.last_insert_rowid
}
}
impl Extend<SqliteQueryResult> for SqliteQueryResult {
fn extend<T: IntoIterator<Item = SqliteQueryResult>>(&mut self, iter: T) {
for elem in iter {
self.changes += elem.changes;
self.last_insert_rowid = elem.last_insert_rowid;
}
}
}
#[cfg(feature = "any")]
impl From<SqliteQueryResult> for sqlx_core::any::AnyQueryResult {
fn from(done: SqliteQueryResult) -> Self {
let last_insert_id = match done.last_insert_rowid() {
0 => None,
n => Some(n),
};
sqlx_core::any::AnyQueryResult {
rows_affected: done.rows_affected(),
last_insert_id,
}
}
}
+233
View File
@@ -0,0 +1,233 @@
#![deny(missing_docs, clippy::pedantic)]
#![allow(clippy::cast_sign_loss)] // some lengths returned from sqlite3 are `i32`, but rust needs `usize`
//! Here be dragons
//!
//! We need to register a custom REGEX implementation for sqlite
//! some useful resources:
//! - rusqlite has an example implementation: <https://docs.rs/rusqlite/0.28.0/rusqlite/functions/index.html>
//! - sqlite supports registering custom C functions: <https://www.sqlite.org/c3ref/create_function.html>
//! - sqlite also supports a `A REGEXP B` syntax, but ONLY if the user implements `regex(B, A)`
//! - Note that A and B are indeed swapped: the regex comes first, the field comes second
//! - <https://www.sqlite.org/lang_expr.html#regexp>
//! - sqlx has a way to safely get a sqlite3 pointer:
//! - <https://docs.rs/sqlx/0.6.2/sqlx/sqlite/struct.SqliteConnection.html#method.lock_handle>
//! - <https://docs.rs/sqlx/0.6.2/sqlx/sqlite/struct.LockedSqliteHandle.html#method.as_raw_handle>
use libsqlite3_sys as ffi;
use log::error;
use regex::Regex;
use std::sync::Arc;
/// The function name for sqlite3. This must be "regexp\0"
static FN_NAME: &[u8] = b"regexp\0";
/// Register the regex function with sqlite.
///
/// Returns the result code of `sqlite3_create_function_v2`
pub fn register(sqlite3: *mut ffi::sqlite3) -> i32 {
unsafe {
ffi::sqlite3_create_function_v2(
// the database connection
sqlite3,
// the function name. Must be up to 255 bytes, and 0-terminated
FN_NAME.as_ptr().cast(),
// the number of arguments this function accepts. We want 2 arguments: The regex and the field
2,
// we want all our strings to be UTF8, and this function will return the same output with the same inputs
ffi::SQLITE_UTF8 | ffi::SQLITE_DETERMINISTIC,
// pointer to user data. We're not using user data
std::ptr::null_mut(),
// xFunc to be executed when we are invoked
Some(sqlite3_regexp_func),
// xStep, should be NULL for scalar functions
None,
// xFinal, should be NULL for scalar functions
None,
// xDestroy, called when this function is deregistered. Should be used to clean up our pointer to user-data
None,
)
}
}
/// A function to be called on each invocation of `regex(REGEX, FIELD)` from sqlite3
///
/// - `ctx`: a pointer to the current sqlite3 context
/// - `n_arg`: The length of `args`
/// - `args`: the arguments of this function call
unsafe extern "C" fn sqlite3_regexp_func(
ctx: *mut ffi::sqlite3_context,
n_arg: i32,
args: *mut *mut ffi::sqlite3_value,
) {
// check the arg size. sqlite3 should already ensure this is only 2 args but we want to double check
if n_arg != 2 {
eprintln!("n_arg expected to be 2, is {n_arg}");
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION);
return;
}
// arg0: Regex
let Some(regex) = get_regex_from_arg(ctx, *args.offset(0), 0) else {
return;
};
// arg1: value
let Some(value) = get_text_from_arg(ctx, *args.offset(1)) else {
return;
};
// if the regex matches the value, set the result int as 1, else as 0
if regex.is_match(value) {
ffi::sqlite3_result_int(ctx, 1);
} else {
ffi::sqlite3_result_int(ctx, 0);
}
}
/// Get the regex from the given `arg` at the given `index`.
///
/// First this will check to see if the value exists in sqlite's `auxdata`. If it does, that regex will be returned.
/// sqlite is able to clean up this data at any point, but rust's [`Arc`] guarantees make sure things don't break.
///
/// If this value does not exist in `auxdata`, [`try_load_value`] is called and a regex is created from this. If any of
/// those fail, a message is printed and `None` is returned.
///
/// After this regex is created it is stored in `auxdata` and loaded again. If it fails to load, this means that
/// something inside of sqlite3 went wrong, and we return `None`.
///
/// If this value is stored correctly, or if it already existed, the arc reference counter is increased and this value is returned.
unsafe fn get_regex_from_arg(
ctx: *mut ffi::sqlite3_context,
arg: *mut ffi::sqlite3_value,
index: i32,
) -> Option<Arc<Regex>> {
// try to get the auxdata for this field
let ptr = ffi::sqlite3_get_auxdata(ctx, index);
if !ptr.is_null() {
// if we have it, turn it into an Arc.
// we need to make sure to call `increment_strong_count` because the returned `Arc` decrement this when it goes out of scope
let ptr = ptr as *const Regex;
Arc::increment_strong_count(ptr);
return Some(Arc::from_raw(ptr));
}
// get the text for this field
let value = get_text_from_arg(ctx, arg)?;
// try to compile it into a regex
let regex = match Regex::new(value) {
Ok(regex) => Arc::new(regex),
Err(e) => {
error!("Invalid regex {value:?}: {e:?}");
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION);
return None;
}
};
// set the regex as auxdata for the next time around
ffi::sqlite3_set_auxdata(
ctx,
index,
// make sure to call `Arc::clone` here, setting the strong count to 2.
// this will be cleaned up at 2 points:
// - when the returned arc goes out of scope
// - when sqlite decides to clean it up an calls `cleanup_arc_regex_pointer`
Arc::into_raw(Arc::clone(&regex)) as *mut _,
Some(cleanup_arc_regex_pointer),
);
Some(regex)
}
/// Get a text reference of the value of `arg`. If this value is not a string value, an error is printed and `None` is
/// returned.
///
/// The returned `&str` is valid for lifetime `'a` which can be determined by the caller. This lifetime should **not**
/// outlive `ctx`.
unsafe fn get_text_from_arg<'a>(
ctx: *mut ffi::sqlite3_context,
arg: *mut ffi::sqlite3_value,
) -> Option<&'a str> {
let ty = ffi::sqlite3_value_type(arg);
if ty == ffi::SQLITE_TEXT {
let ptr = ffi::sqlite3_value_text(arg);
let len = ffi::sqlite3_value_bytes(arg);
let slice = std::slice::from_raw_parts(ptr.cast(), len as usize);
match std::str::from_utf8(slice) {
Ok(result) => Some(result),
Err(e) => {
log::error!("Incoming text is not valid UTF8: {e:?}");
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION);
None
}
}
} else {
None
}
}
/// Clean up the `Arc<Regex>` that is stored in the given `ptr`.
unsafe extern "C" fn cleanup_arc_regex_pointer(ptr: *mut std::ffi::c_void) {
Arc::decrement_strong_count(ptr.cast::<Regex>());
}
#[cfg(test)]
mod tests {
use sqlx::{ConnectOptions, Row};
use std::str::FromStr;
async fn test_db() -> crate::SqliteConnection {
let mut conn = crate::SqliteConnectOptions::from_str("sqlite://:memory:")
.unwrap()
.with_regexp()
.connect()
.await
.unwrap();
sqlx::query("CREATE TABLE test (col TEXT NOT NULL)")
.execute(&mut conn)
.await
.unwrap();
for i in 0..10 {
sqlx::query("INSERT INTO test VALUES (?)")
.bind(format!("value {i}"))
.execute(&mut conn)
.await
.unwrap();
}
conn
}
#[sqlx::test]
async fn test_regexp_does_not_fail() {
let mut conn = test_db().await;
let result = sqlx::query("SELECT col FROM test WHERE col REGEXP 'foo.*bar'")
.fetch_all(&mut conn)
.await
.expect("Could not execute query");
assert!(result.is_empty());
}
#[sqlx::test]
async fn test_regexp_filters_correctly() {
let mut conn = test_db().await;
let result = sqlx::query("SELECT col FROM test WHERE col REGEXP '.*2'")
.fetch_all(&mut conn)
.await
.expect("Could not execute query");
assert_eq!(result.len(), 1);
assert_eq!(result[0].get::<String, usize>(0), String::from("value 2"));
let result = sqlx::query("SELECT col FROM test WHERE col REGEXP '^3'")
.fetch_all(&mut conn)
.await
.expect("Could not execute query");
assert!(result.is_empty());
}
#[sqlx::test]
async fn test_invalid_regexp_should_fail() {
let mut conn = test_db().await;
let result = sqlx::query("SELECT col from test WHERE col REGEXP '(?:?)'")
.execute(&mut conn)
.await;
assert!(matches!(result, Err(sqlx::Error::Database(_))));
}
}
+89
View File
@@ -0,0 +1,89 @@
#![allow(clippy::rc_buffer)]
use std::sync::Arc;
use sqlx_core::column::ColumnIndex;
use sqlx_core::error::Error;
use sqlx_core::ext::ustr::UStr;
use sqlx_core::row::Row;
use sqlx_core::HashMap;
use crate::statement::StatementHandle;
use crate::{Sqlite, SqliteColumn, SqliteValue, SqliteValueRef};
/// Implementation of [`Row`] for SQLite.
pub struct SqliteRow {
pub(crate) values: Box<[SqliteValue]>,
pub(crate) columns: Arc<Vec<SqliteColumn>>,
pub(crate) column_names: Arc<HashMap<UStr, usize>>,
}
// Accessing values from the statement object is
// safe across threads as long as we don't call [sqlite3_step]
// we block ourselves from doing that by only exposing
// a set interface on [StatementHandle]
unsafe impl Send for SqliteRow {}
unsafe impl Sync for SqliteRow {}
impl SqliteRow {
pub(crate) fn current(
statement: &StatementHandle,
columns: &Arc<Vec<SqliteColumn>>,
column_names: &Arc<HashMap<UStr, usize>>,
) -> Self {
let size = statement.column_count();
let mut values = Vec::with_capacity(size);
for i in 0..size {
values.push(unsafe {
let raw = statement.column_value(i);
SqliteValue::new(raw, columns[i].type_info.clone())
});
}
Self {
values: values.into_boxed_slice(),
columns: Arc::clone(columns),
column_names: Arc::clone(column_names),
}
}
}
impl Row for SqliteRow {
type Database = Sqlite;
fn columns(&self) -> &[SqliteColumn] {
&self.columns
}
fn try_get_raw<I>(&self, index: I) -> Result<SqliteValueRef<'_>, Error>
where
I: ColumnIndex<Self>,
{
let index = index.index(self)?;
Ok(SqliteValueRef::value(&self.values[index]))
}
}
impl ColumnIndex<SqliteRow> for &'_ str {
fn index(&self, row: &SqliteRow) -> Result<usize, Error> {
row.column_names
.get(*self)
.ok_or_else(|| Error::ColumnNotFound((*self).into()))
.copied()
}
}
// #[cfg(feature = "any")]
// impl From<SqliteRow> for crate::any::AnyRow {
// #[inline]
// fn from(row: SqliteRow) -> Self {
// crate::any::AnyRow {
// columns: row.columns.iter().map(|col| col.clone().into()).collect(),
// kind: crate::any::row::AnyRowKind::Sqlite(row),
// }
// }
// }
+371
View File
@@ -0,0 +1,371 @@
use std::ffi::c_void;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;
use std::ptr::NonNull;
use std::slice::from_raw_parts;
use std::str::{from_utf8, from_utf8_unchecked};
use libsqlite3_sys::{
sqlite3, sqlite3_bind_blob64, sqlite3_bind_double, sqlite3_bind_int, sqlite3_bind_int64,
sqlite3_bind_null, sqlite3_bind_parameter_count, sqlite3_bind_parameter_name,
sqlite3_bind_text64, sqlite3_changes, sqlite3_clear_bindings, sqlite3_column_blob,
sqlite3_column_bytes, sqlite3_column_count, sqlite3_column_database_name,
sqlite3_column_decltype, sqlite3_column_double, sqlite3_column_int, sqlite3_column_int64,
sqlite3_column_name, sqlite3_column_origin_name, sqlite3_column_table_name,
sqlite3_column_type, sqlite3_column_value, sqlite3_db_handle, sqlite3_finalize, sqlite3_reset,
sqlite3_sql, sqlite3_step, sqlite3_stmt, sqlite3_stmt_readonly, sqlite3_table_column_metadata,
sqlite3_value, SQLITE_DONE, SQLITE_LOCKED_SHAREDCACHE, SQLITE_MISUSE, SQLITE_OK, SQLITE_ROW,
SQLITE_TRANSIENT, SQLITE_UTF8,
};
use crate::error::{BoxDynError, Error};
use crate::type_info::DataType;
use crate::{SqliteError, SqliteTypeInfo};
use super::unlock_notify;
#[derive(Debug)]
pub(crate) struct StatementHandle(NonNull<sqlite3_stmt>);
// access to SQLite3 statement handles are safe to send and share between threads
// as long as the `sqlite3_step` call is serialized.
unsafe impl Send for StatementHandle {}
macro_rules! expect_ret_valid {
($fn_name:ident($($args:tt)*)) => {{
let val = $fn_name($($args)*);
TryFrom::try_from(val)
// This likely means UB in SQLite itself or our usage of it;
// signed integer overflow is UB in the C standard.
.unwrap_or_else(|_| panic!("{}() returned invalid value: {val:?}", stringify!($fn_name)))
}}
}
macro_rules! check_col_idx {
($idx:ident) => {
c_int::try_from($idx).unwrap_or_else(|_| panic!("invalid column index: {}", $idx))
};
}
// might use some of this later
#[allow(dead_code)]
impl StatementHandle {
pub(super) fn new(ptr: NonNull<sqlite3_stmt>) -> Self {
Self(ptr)
}
#[inline]
pub(super) unsafe fn db_handle(&self) -> *mut sqlite3 {
// O(c) access to the connection handle for this statement handle
// https://sqlite.org/c3ref/db_handle.html
sqlite3_db_handle(self.0.as_ptr())
}
pub(crate) fn read_only(&self) -> bool {
// https://sqlite.org/c3ref/stmt_readonly.html
unsafe { sqlite3_stmt_readonly(self.0.as_ptr()) != 0 }
}
pub(crate) fn sql(&self) -> &str {
// https://sqlite.org/c3ref/expanded_sql.html
unsafe {
let raw = sqlite3_sql(self.0.as_ptr());
debug_assert!(!raw.is_null());
from_utf8_unchecked(CStr::from_ptr(raw).to_bytes())
}
}
#[inline]
pub(crate) fn last_error(&mut self) -> SqliteError {
unsafe { SqliteError::new(self.db_handle()) }
}
#[inline]
pub(crate) fn column_count(&self) -> usize {
// https://sqlite.org/c3ref/column_count.html
unsafe { expect_ret_valid!(sqlite3_column_count(self.0.as_ptr())) }
}
#[inline]
pub(crate) fn changes(&self) -> u64 {
// returns the number of changes of the *last* statement; not
// necessarily this statement.
// https://sqlite.org/c3ref/changes.html
unsafe { expect_ret_valid!(sqlite3_changes(self.db_handle())) }
}
#[inline]
pub(crate) fn column_name(&self, index: usize) -> &str {
// https://sqlite.org/c3ref/column_name.html
unsafe {
let name = sqlite3_column_name(self.0.as_ptr(), check_col_idx!(index));
debug_assert!(!name.is_null());
from_utf8_unchecked(CStr::from_ptr(name).to_bytes())
}
}
pub(crate) fn column_type_info(&self, index: usize) -> SqliteTypeInfo {
SqliteTypeInfo(DataType::from_code(self.column_type(index)))
}
pub(crate) fn column_type_info_opt(&self, index: usize) -> Option<SqliteTypeInfo> {
match DataType::from_code(self.column_type(index)) {
DataType::Null => None,
dt => Some(SqliteTypeInfo(dt)),
}
}
#[inline]
pub(crate) fn column_decltype(&self, index: usize) -> Option<SqliteTypeInfo> {
unsafe {
let decl = sqlite3_column_decltype(self.0.as_ptr(), check_col_idx!(index));
if decl.is_null() {
// If the Nth column of the result set is an expression or subquery,
// then a NULL pointer is returned.
return None;
}
let decl = from_utf8_unchecked(CStr::from_ptr(decl).to_bytes());
let ty: DataType = decl.parse().ok()?;
Some(SqliteTypeInfo(ty))
}
}
pub(crate) fn column_nullable(&self, index: usize) -> Result<Option<bool>, Error> {
unsafe {
let index = check_col_idx!(index);
// https://sqlite.org/c3ref/column_database_name.html
//
// ### Note
// The returned string is valid until the prepared statement is destroyed using
// sqlite3_finalize() or until the statement is automatically reprepared by the
// first call to sqlite3_step() for a particular run or until the same information
// is requested again in a different encoding.
let db_name = sqlite3_column_database_name(self.0.as_ptr(), index);
let table_name = sqlite3_column_table_name(self.0.as_ptr(), index);
let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), index);
if db_name.is_null() || table_name.is_null() || origin_name.is_null() {
return Ok(None);
}
let mut not_null: c_int = 0;
// https://sqlite.org/c3ref/table_column_metadata.html
let status = sqlite3_table_column_metadata(
self.db_handle(),
db_name,
table_name,
origin_name,
// function docs state to provide NULL for return values you don't care about
ptr::null_mut(),
ptr::null_mut(),
&mut not_null,
ptr::null_mut(),
ptr::null_mut(),
);
if status != SQLITE_OK {
// implementation note: the docs for sqlite3_table_column_metadata() specify
// that an error can be returned if the column came from a view; however,
// experimentally we found that the above functions give us the true origin
// for columns in views that came from real tables and so we should never hit this
// error; for view columns that are expressions we are given NULL for their origins
// so we don't need special handling for that case either.
//
// this is confirmed in the `tests/sqlite-macros.rs` integration test
return Err(SqliteError::new(self.db_handle()).into());
}
Ok(Some(not_null == 0))
}
}
// Number Of SQL Parameters
#[inline]
pub(crate) fn bind_parameter_count(&self) -> usize {
// https://www.sqlite.org/c3ref/bind_parameter_count.html
unsafe { expect_ret_valid!(sqlite3_bind_parameter_count(self.0.as_ptr())) }
}
// Name Of A Host Parameter
// NOTE: The first host parameter has an index of 1, not 0.
#[inline]
pub(crate) fn bind_parameter_name(&self, index: usize) -> Option<&str> {
unsafe {
// https://www.sqlite.org/c3ref/bind_parameter_name.html
let name = sqlite3_bind_parameter_name(self.0.as_ptr(), check_col_idx!(index));
if name.is_null() {
return None;
}
Some(from_utf8_unchecked(CStr::from_ptr(name).to_bytes()))
}
}
// Binding Values To Prepared Statements
// https://www.sqlite.org/c3ref/bind_blob.html
#[inline]
pub(crate) fn bind_blob(&self, index: usize, v: &[u8]) -> c_int {
unsafe {
sqlite3_bind_blob64(
self.0.as_ptr(),
check_col_idx!(index),
v.as_ptr() as *const c_void,
v.len() as u64,
SQLITE_TRANSIENT(),
)
}
}
#[inline]
pub(crate) fn bind_text(&self, index: usize, v: &str) -> c_int {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let encoding = SQLITE_UTF8 as u8;
unsafe {
sqlite3_bind_text64(
self.0.as_ptr(),
check_col_idx!(index),
v.as_ptr() as *const c_char,
v.len() as u64,
SQLITE_TRANSIENT(),
encoding,
)
}
}
#[inline]
pub(crate) fn bind_int(&self, index: usize, v: i32) -> c_int {
unsafe { sqlite3_bind_int(self.0.as_ptr(), check_col_idx!(index), v as c_int) }
}
#[inline]
pub(crate) fn bind_int64(&self, index: usize, v: i64) -> c_int {
unsafe { sqlite3_bind_int64(self.0.as_ptr(), check_col_idx!(index), v) }
}
#[inline]
pub(crate) fn bind_double(&self, index: usize, v: f64) -> c_int {
unsafe { sqlite3_bind_double(self.0.as_ptr(), check_col_idx!(index), v) }
}
#[inline]
pub(crate) fn bind_null(&self, index: usize) -> c_int {
unsafe { sqlite3_bind_null(self.0.as_ptr(), check_col_idx!(index)) }
}
// result values from the query
// https://www.sqlite.org/c3ref/column_blob.html
#[inline]
pub(crate) fn column_type(&self, index: usize) -> c_int {
unsafe { sqlite3_column_type(self.0.as_ptr(), check_col_idx!(index)) }
}
#[inline]
pub(crate) fn column_int(&self, index: usize) -> i32 {
unsafe { sqlite3_column_int(self.0.as_ptr(), check_col_idx!(index)) as i32 }
}
#[inline]
pub(crate) fn column_int64(&self, index: usize) -> i64 {
unsafe { sqlite3_column_int64(self.0.as_ptr(), check_col_idx!(index)) as i64 }
}
#[inline]
pub(crate) fn column_double(&self, index: usize) -> f64 {
unsafe { sqlite3_column_double(self.0.as_ptr(), check_col_idx!(index)) }
}
#[inline]
pub(crate) fn column_value(&self, index: usize) -> *mut sqlite3_value {
unsafe { sqlite3_column_value(self.0.as_ptr(), check_col_idx!(index)) }
}
pub(crate) fn column_blob(&self, index: usize) -> &[u8] {
let len = unsafe {
expect_ret_valid!(sqlite3_column_bytes(self.0.as_ptr(), check_col_idx!(index)))
};
if len == 0 {
// empty blobs are NULL so just return an empty slice
return &[];
}
let ptr =
unsafe { sqlite3_column_blob(self.0.as_ptr(), check_col_idx!(index)) } as *const u8;
debug_assert!(!ptr.is_null());
unsafe { from_raw_parts(ptr, len) }
}
pub(crate) fn column_text(&self, index: usize) -> Result<&str, BoxDynError> {
Ok(from_utf8(self.column_blob(index))?)
}
pub(crate) fn clear_bindings(&self) {
unsafe { sqlite3_clear_bindings(self.0.as_ptr()) };
}
pub(crate) fn reset(&mut self) -> Result<(), SqliteError> {
// SAFETY: we have exclusive access to the handle
unsafe {
if sqlite3_reset(self.0.as_ptr()) != SQLITE_OK {
return Err(SqliteError::new(self.db_handle()));
}
}
Ok(())
}
pub(crate) fn step(&mut self) -> Result<bool, SqliteError> {
// SAFETY: we have exclusive access to the handle
unsafe {
loop {
match sqlite3_step(self.0.as_ptr()) {
SQLITE_ROW => return Ok(true),
SQLITE_DONE => return Ok(false),
SQLITE_MISUSE => panic!("misuse!"),
SQLITE_LOCKED_SHAREDCACHE => {
// The shared cache is locked by another connection. Wait for unlock
// notification and try again.
unlock_notify::wait(self.db_handle())?;
// Need to reset the handle after the unlock
// (https://www.sqlite.org/unlock_notify.html)
sqlite3_reset(self.0.as_ptr());
}
_ => return Err(SqliteError::new(self.db_handle())),
}
}
}
}
}
impl Drop for StatementHandle {
fn drop(&mut self) {
// SAFETY: we have exclusive access to the `StatementHandle` here
unsafe {
// https://sqlite.org/c3ref/finalize.html
let status = sqlite3_finalize(self.0.as_ptr());
if status == SQLITE_MISUSE {
// Panic in case of detected misuse of SQLite API.
//
// sqlite3_finalize returns it at least in the
// case of detected double free, i.e. calling
// sqlite3_finalize on already finalized
// statement.
panic!("Detected sqlite3_finalize misuse.");
}
}
}
}
+79
View File
@@ -0,0 +1,79 @@
use crate::column::ColumnIndex;
use crate::error::Error;
use crate::ext::ustr::UStr;
use crate::{Sqlite, SqliteArguments, SqliteColumn, SqliteTypeInfo};
use sqlx_core::{Either, HashMap};
use std::borrow::Cow;
use std::sync::Arc;
pub(crate) use sqlx_core::statement::*;
mod handle;
pub(super) mod unlock_notify;
mod r#virtual;
pub(crate) use handle::StatementHandle;
pub(crate) use r#virtual::VirtualStatement;
#[derive(Debug, Clone)]
#[allow(clippy::rc_buffer)]
pub struct SqliteStatement<'q> {
pub(crate) sql: Cow<'q, str>,
pub(crate) parameters: usize,
pub(crate) columns: Arc<Vec<SqliteColumn>>,
pub(crate) column_names: Arc<HashMap<UStr, usize>>,
}
impl<'q> Statement<'q> for SqliteStatement<'q> {
type Database = Sqlite;
fn to_owned(&self) -> SqliteStatement<'static> {
SqliteStatement::<'static> {
sql: Cow::Owned(self.sql.clone().into_owned()),
parameters: self.parameters,
columns: Arc::clone(&self.columns),
column_names: Arc::clone(&self.column_names),
}
}
fn sql(&self) -> &str {
&self.sql
}
fn parameters(&self) -> Option<Either<&[SqliteTypeInfo], usize>> {
Some(Either::Right(self.parameters))
}
fn columns(&self) -> &[SqliteColumn] {
&self.columns
}
impl_statement_query!(SqliteArguments<'_>);
}
impl ColumnIndex<SqliteStatement<'_>> for &'_ str {
fn index(&self, statement: &SqliteStatement<'_>) -> Result<usize, Error> {
statement
.column_names
.get(*self)
.ok_or_else(|| Error::ColumnNotFound((*self).into()))
.copied()
}
}
// #[cfg(feature = "any")]
// impl<'q> From<SqliteStatement<'q>> for crate::any::AnyStatement<'q> {
// #[inline]
// fn from(statement: SqliteStatement<'q>) -> Self {
// crate::any::AnyStatement::<'q> {
// columns: statement
// .columns
// .iter()
// .map(|col| col.clone().into())
// .collect(),
// column_names: statement.column_names,
// parameters: Some(Either::Right(statement.parameters)),
// sql: statement.sql,
// }
// }
// }
+65
View File
@@ -0,0 +1,65 @@
use std::ffi::c_void;
use std::os::raw::c_int;
use std::slice;
use std::sync::{Condvar, Mutex};
use libsqlite3_sys::{sqlite3, sqlite3_unlock_notify, SQLITE_OK};
use crate::SqliteError;
// Wait for unlock notification (https://www.sqlite.org/unlock_notify.html)
pub unsafe fn wait(conn: *mut sqlite3) -> Result<(), SqliteError> {
let notify = Notify::new();
if sqlite3_unlock_notify(
conn,
Some(unlock_notify_cb),
&notify as *const Notify as *mut Notify as *mut _,
) != SQLITE_OK
{
return Err(SqliteError::new(conn));
}
notify.wait();
Ok(())
}
unsafe extern "C" fn unlock_notify_cb(ptr: *mut *mut c_void, len: c_int) {
let ptr = ptr as *mut &Notify;
// We don't have a choice; we can't panic and unwind into FFI here.
let slice = slice::from_raw_parts(ptr, usize::try_from(len).unwrap_or(0));
for notify in slice {
notify.fire();
}
}
struct Notify {
mutex: Mutex<bool>,
condvar: Condvar,
}
impl Notify {
fn new() -> Self {
Self {
mutex: Mutex::new(false),
condvar: Condvar::new(),
}
}
fn wait(&self) {
// We only want to wait until the lock is available again.
#[allow(let_underscore_lock)]
let _ = self
.condvar
.wait_while(self.mutex.lock().unwrap(), |fired| !*fired)
.unwrap();
}
fn fire(&self) {
let mut lock = self.mutex.lock().unwrap();
*lock = true;
self.condvar.notify_one();
}
}
+203
View File
@@ -0,0 +1,203 @@
#![allow(clippy::rc_buffer)]
use std::cmp;
use std::os::raw::c_char;
use std::ptr::{null, null_mut, NonNull};
use std::sync::Arc;
use libsqlite3_sys::{
sqlite3, sqlite3_prepare_v3, sqlite3_stmt, SQLITE_OK, SQLITE_PREPARE_PERSISTENT,
};
use sqlx_core::bytes::{Buf, Bytes};
use sqlx_core::error::Error;
use sqlx_core::ext::ustr::UStr;
use sqlx_core::{HashMap, SmallVec};
use crate::connection::ConnectionHandle;
use crate::statement::StatementHandle;
use crate::{SqliteColumn, SqliteError};
// A virtual statement consists of *zero* or more raw SQLite3 statements. We chop up a SQL statement
// on `;` to support multiple statements in one query.
#[derive(Debug)]
pub struct VirtualStatement {
persistent: bool,
/// the current index of the actual statement that is executing
/// if `None`, no statement is executing and `prepare()` must be called;
/// if `Some(self.handles.len())` and `self.tail.is_empty()`,
/// there are no more statements to execute and `reset()` must be called
index: Option<usize>,
/// tail of the most recently prepared SQL statement within this container
tail: Bytes,
/// underlying sqlite handles for each inner statement
/// a SQL query string in SQLite is broken up into N statements
/// we use a [`SmallVec`] to optimize for the most likely case of a single statement
pub(crate) handles: SmallVec<[StatementHandle; 1]>,
// each set of columns
pub(crate) columns: SmallVec<[Arc<Vec<SqliteColumn>>; 1]>,
// each set of column names
pub(crate) column_names: SmallVec<[Arc<HashMap<UStr, usize>>; 1]>,
}
pub struct PreparedStatement<'a> {
pub(crate) handle: &'a mut StatementHandle,
pub(crate) columns: &'a Arc<Vec<SqliteColumn>>,
pub(crate) column_names: &'a Arc<HashMap<UStr, usize>>,
}
impl VirtualStatement {
pub(crate) fn new(mut query: &str, persistent: bool) -> Result<Self, Error> {
query = query.trim();
if query.len() > i32::MAX as usize {
return Err(err_protocol!(
"query string must be smaller than {} bytes",
i32::MAX
));
}
Ok(Self {
persistent,
tail: Bytes::from(String::from(query)),
handles: SmallVec::with_capacity(1),
index: None,
columns: SmallVec::with_capacity(1),
column_names: SmallVec::with_capacity(1),
})
}
pub(crate) fn prepare_next(
&mut self,
conn: &mut ConnectionHandle,
) -> Result<Option<PreparedStatement<'_>>, Error> {
// increment `self.index` up to `self.handles.len()`
self.index = self
.index
.map(|idx| cmp::min(idx + 1, self.handles.len()))
.or(Some(0));
while self.handles.len() <= self.index.unwrap_or(0) {
if self.tail.is_empty() {
return Ok(None);
}
if let Some(statement) = prepare(conn.as_ptr(), &mut self.tail, self.persistent)? {
let num = statement.column_count();
let mut columns = Vec::with_capacity(num);
let mut column_names = HashMap::with_capacity(num);
for i in 0..num {
let name: UStr = statement.column_name(i).to_owned().into();
let type_info = statement
.column_decltype(i)
.unwrap_or_else(|| statement.column_type_info(i));
columns.push(SqliteColumn {
ordinal: i,
name: name.clone(),
type_info,
});
column_names.insert(name, i);
}
self.handles.push(statement);
self.columns.push(Arc::new(columns));
self.column_names.push(Arc::new(column_names));
}
}
Ok(self.current())
}
pub fn current(&mut self) -> Option<PreparedStatement<'_>> {
self.index
.filter(|&idx| idx < self.handles.len())
.map(move |idx| PreparedStatement {
handle: &mut self.handles[idx],
columns: &self.columns[idx],
column_names: &self.column_names[idx],
})
}
pub fn reset(&mut self) -> Result<(), Error> {
self.index = None;
for handle in self.handles.iter_mut() {
handle.reset()?;
handle.clear_bindings();
}
Ok(())
}
}
fn prepare(
conn: *mut sqlite3,
query: &mut Bytes,
persistent: bool,
) -> Result<Option<StatementHandle>, Error> {
let mut flags = 0;
// For some reason, when building with the `sqlcipher` feature enabled
// `SQLITE_PREPARE_PERSISTENT` ends up being `i32` instead of `u32`. Crazy, right?
#[allow(trivial_casts, clippy::unnecessary_cast)]
if persistent {
// SQLITE_PREPARE_PERSISTENT
// The SQLITE_PREPARE_PERSISTENT flag is a hint to the query
// planner that the prepared statement will be retained for a long time
// and probably reused many times.
flags |= SQLITE_PREPARE_PERSISTENT as u32;
}
while !query.is_empty() {
let mut statement_handle: *mut sqlite3_stmt = null_mut();
let mut tail: *const c_char = null();
let query_ptr = query.as_ptr() as *const c_char;
let query_len = i32::try_from(query.len()).map_err(|_| {
err_protocol!(
"query string too large for SQLite3 API ({} bytes); \
try breaking it into smaller chunks (< 2 GiB), executed separately",
query.len()
)
})?;
// <https://www.sqlite.org/c3ref/prepare.html>
let status = unsafe {
sqlite3_prepare_v3(
conn,
query_ptr,
query_len,
flags,
&mut statement_handle,
&mut tail,
)
};
if status != SQLITE_OK {
return Err(unsafe { SqliteError::new(conn).into() });
}
// tail should point to the first byte past the end of the first SQL
// statement in zSql. these routines only compile the first statement,
// so tail is left pointing to what remains un-compiled.
let n = (tail as usize) - (query_ptr as usize);
query.advance(n);
if let Some(handle) = NonNull::new(statement_handle) {
return Ok(Some(StatementHandle::new(handle)));
}
}
Ok(None)
}
+84
View File
@@ -0,0 +1,84 @@
use crate::error::Error;
use crate::pool::PoolOptions;
use crate::testing::{FixtureSnapshot, TestArgs, TestContext, TestSupport};
use crate::{Sqlite, SqliteConnectOptions};
use futures_core::future::BoxFuture;
use std::path::{Path, PathBuf};
pub(crate) use sqlx_core::testing::*;
const BASE_PATH: &str = "target/sqlx/test-dbs";
impl TestSupport for Sqlite {
fn test_context(args: &TestArgs) -> BoxFuture<'_, Result<TestContext<Self>, Error>> {
Box::pin(async move { test_context(args).await })
}
fn cleanup_test(db_name: &str) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(async move { Ok(crate::fs::remove_file(db_name).await?) })
}
fn cleanup_test_dbs() -> BoxFuture<'static, Result<Option<usize>, Error>> {
Box::pin(async move {
crate::fs::remove_dir_all(BASE_PATH).await?;
Ok(None)
})
}
fn snapshot(
_conn: &mut Self::Connection,
) -> BoxFuture<'_, Result<FixtureSnapshot<Self>, Error>> {
todo!()
}
fn db_name(args: &TestArgs) -> String {
convert_path(args.test_path)
}
}
async fn test_context(args: &TestArgs) -> Result<TestContext<Sqlite>, Error> {
let db_path = convert_path(args.test_path);
if let Some(parent_path) = Path::parent(db_path.as_ref()) {
crate::fs::create_dir_all(parent_path)
.await
.expect("failed to create folders");
}
if Path::exists(db_path.as_ref()) {
crate::fs::remove_file(&db_path)
.await
.expect("failed to remove database from previous test run");
}
Ok(TestContext {
connect_opts: SqliteConnectOptions::new()
.filename(&db_path)
.create_if_missing(true),
// This doesn't really matter for SQLite as the databases are independent of each other.
// The main limitation is going to be the number of concurrent running tests.
pool_opts: PoolOptions::new().max_connections(1000),
db_name: db_path,
})
}
fn convert_path(test_path: &str) -> String {
let mut path = PathBuf::from(BASE_PATH);
for segment in test_path.split("::") {
path.push(segment);
}
path.set_extension("sqlite");
path.into_os_string()
.into_string()
.expect("path should be UTF-8")
}
#[test]
fn test_convert_path() {
let path = convert_path("foo::bar::baz::quux");
assert_eq!(path, "target/sqlx/test-dbs/foo/bar/baz/quux.sqlite");
}
+48
View File
@@ -0,0 +1,48 @@
use futures_core::future::BoxFuture;
use std::borrow::Cow;
use sqlx_core::error::Error;
use sqlx_core::transaction::TransactionManager;
use crate::{Sqlite, SqliteConnection};
/// Implementation of [`TransactionManager`] for SQLite.
pub struct SqliteTransactionManager;
impl TransactionManager for SqliteTransactionManager {
type Database = Sqlite;
fn begin<'conn>(
conn: &'conn mut SqliteConnection,
statement: Option<Cow<'static, str>>,
) -> BoxFuture<'conn, Result<(), Error>> {
Box::pin(async {
let is_custom_statement = statement.is_some();
conn.worker.begin(statement).await?;
if is_custom_statement {
// Check that custom statement actually put the connection into a transaction.
let mut handle = conn.lock_handle().await?;
if !handle.in_transaction() {
return Err(Error::BeginFailed);
}
}
Ok(())
})
}
fn commit(conn: &mut SqliteConnection) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(conn.worker.commit())
}
fn rollback(conn: &mut SqliteConnection) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(conn.worker.rollback())
}
fn start_rollback(conn: &mut SqliteConnection) {
conn.worker.start_rollback().ok();
}
fn get_transaction_depth(conn: &SqliteConnection) -> usize {
conn.worker.shared.get_transaction_depth()
}
}
+51
View File
@@ -0,0 +1,51 @@
#[allow(unused_imports)]
use sqlx_core as sqlx;
use crate::Sqlite;
// f32 is not included below as REAL represents a floating point value
// stored as an 8-byte IEEE floating point number (i.e. an f64)
// For more info see: https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
impl_type_checking!(
Sqlite {
// Note that since the macro checks `column_type_info == <T>::type_info()` first,
// we can list `bool` without it being automatically picked for all integer types
// due to its `TypeInfo::compatible()` impl.
bool,
// Since it returns `DataType::Int4` for `type_info()`,
// `i32` should only be chosen IFF the column decltype is `INT4`
i32,
i64,
f64,
String,
Vec<u8>,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::NaiveDate,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::NaiveDateTime,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc> | sqlx::types::chrono::DateTime<_>,
#[cfg(feature = "time")]
sqlx::types::time::OffsetDateTime,
#[cfg(feature = "time")]
sqlx::types::time::PrimitiveDateTime,
#[cfg(feature = "time")]
sqlx::types::time::Date,
#[cfg(feature = "uuid")]
sqlx::types::Uuid,
},
ParamChecking::Weak,
// While there are type integrations that must be enabled via Cargo feature,
// SQLite's type system doesn't actually have any type that we cannot decode by default.
//
// The type integrations simply allow the user to skip some intermediate representation,
// which is usually TEXT.
feature-types: _info => None,
);
+161
View File
@@ -0,0 +1,161 @@
use std::fmt::{self, Display, Formatter};
use std::os::raw::c_int;
use std::str::FromStr;
use libsqlite3_sys::{SQLITE_BLOB, SQLITE_FLOAT, SQLITE_INTEGER, SQLITE_NULL, SQLITE_TEXT};
use crate::error::BoxDynError;
pub(crate) use sqlx_core::type_info::*;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))]
pub(crate) enum DataType {
// These variants should correspond to `SQLITE_*` type constants.
Null,
/// Note: SQLite's type system has no notion of integer widths.
/// The `INTEGER` type affinity can store up to 8 byte integers,
/// making `i64` the only safe choice when mapping integer types to Rust.
Integer,
Float,
Text,
Blob,
// Explicitly not supported: see documentation in `types/mod.rs`
#[allow(dead_code)]
Numeric,
// non-standard extensions (chosen based on the column's declared type)
/// Chosen if the column's declared type is `BOOLEAN`.
Bool,
/// Chosen if the column's declared type is `INT4`;
/// instructs the macros to use `i32` instead of `i64`.
/// Legacy feature; no idea if this is actually used anywhere.
Int4,
Date,
Time,
Datetime,
}
/// Type information for a SQLite type.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))]
pub struct SqliteTypeInfo(pub(crate) DataType);
impl Display for SqliteTypeInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad(self.name())
}
}
impl TypeInfo for SqliteTypeInfo {
fn is_null(&self) -> bool {
matches!(self.0, DataType::Null)
}
fn name(&self) -> &str {
match self.0 {
DataType::Null => "NULL",
DataType::Text => "TEXT",
DataType::Float => "REAL",
DataType::Blob => "BLOB",
DataType::Int4 | DataType::Integer => "INTEGER",
DataType::Numeric => "NUMERIC",
// non-standard extensions
DataType::Bool => "BOOLEAN",
DataType::Date => "DATE",
DataType::Time => "TIME",
DataType::Datetime => "DATETIME",
}
}
}
impl DataType {
pub(crate) fn from_code(code: c_int) -> Self {
match code {
SQLITE_INTEGER => DataType::Integer,
SQLITE_FLOAT => DataType::Float,
SQLITE_BLOB => DataType::Blob,
SQLITE_NULL => DataType::Null,
SQLITE_TEXT => DataType::Text,
// https://sqlite.org/c3ref/c_blob.html
_ => panic!("unknown data type code {code}"),
}
}
}
// note: this implementation is particularly important as this is how the macros determine
// what Rust type maps to what *declared* SQL type
// <https://www.sqlite.org/datatype3.html#affname>
impl FromStr for DataType {
type Err = BoxDynError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_ascii_lowercase();
Ok(match &*s {
"int4" => DataType::Int4,
"int8" => DataType::Integer,
"boolean" | "bool" => DataType::Bool,
"date" => DataType::Date,
"time" => DataType::Time,
"datetime" | "timestamp" => DataType::Datetime,
_ if s.contains("int") => DataType::Integer,
_ if s.contains("char") || s.contains("clob") || s.contains("text") => DataType::Text,
_ if s.contains("blob") => DataType::Blob,
_ if s.contains("real") || s.contains("floa") || s.contains("doub") => DataType::Float,
_ => {
return Err(format!("unknown type: `{s}`").into());
}
})
}
}
// #[cfg(feature = "any")]
// impl From<SqliteTypeInfo> for crate::any::AnyTypeInfo {
// #[inline]
// fn from(ty: SqliteTypeInfo) -> Self {
// crate::any::AnyTypeInfo(crate::any::type_info::AnyTypeInfoKind::Sqlite(ty))
// }
// }
#[test]
fn test_data_type_from_str() -> Result<(), BoxDynError> {
assert_eq!(DataType::Int4, "INT4".parse()?);
assert_eq!(DataType::Integer, "INT".parse()?);
assert_eq!(DataType::Integer, "INTEGER".parse()?);
assert_eq!(DataType::Integer, "INTBIG".parse()?);
assert_eq!(DataType::Integer, "MEDIUMINT".parse()?);
assert_eq!(DataType::Integer, "BIGINT".parse()?);
assert_eq!(DataType::Integer, "UNSIGNED BIG INT".parse()?);
assert_eq!(DataType::Integer, "INT8".parse()?);
assert_eq!(DataType::Text, "CHARACTER(20)".parse()?);
assert_eq!(DataType::Text, "NCHAR(55)".parse()?);
assert_eq!(DataType::Text, "TEXT".parse()?);
assert_eq!(DataType::Text, "CLOB".parse()?);
assert_eq!(DataType::Blob, "BLOB".parse()?);
assert_eq!(DataType::Float, "REAL".parse()?);
assert_eq!(DataType::Float, "FLOAT".parse()?);
assert_eq!(DataType::Float, "DOUBLE PRECISION".parse()?);
assert_eq!(DataType::Bool, "BOOLEAN".parse()?);
assert_eq!(DataType::Bool, "BOOL".parse()?);
assert_eq!(DataType::Datetime, "DATETIME".parse()?);
assert_eq!(DataType::Time, "TIME".parse()?);
assert_eq!(DataType::Date, "DATE".parse()?);
Ok(())
}
+33
View File
@@ -0,0 +1,33 @@
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::type_info::DataType;
use crate::types::Type;
use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
impl Type<Sqlite> for bool {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Bool)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Bool | DataType::Int4 | DataType::Integer)
}
}
impl<'q> Encode<'q, Sqlite> for bool {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int((*self).into()));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for bool {
fn decode(value: SqliteValueRef<'r>) -> Result<bool, BoxDynError> {
Ok(value.int64() != 0)
}
}
+103
View File
@@ -0,0 +1,103 @@
use std::borrow::Cow;
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::type_info::DataType;
use crate::types::Type;
use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
impl Type<Sqlite> for [u8] {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Blob)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Blob | DataType::Text)
}
}
impl<'q> Encode<'q, Sqlite> for &'q [u8] {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Borrowed(self)));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for &'r [u8] {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.blob())
}
}
impl Type<Sqlite> for Box<[u8]> {
fn type_info() -> SqliteTypeInfo {
<&[u8] as Type<Sqlite>>::type_info()
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
<&[u8] as Type<Sqlite>>::compatible(ty)
}
}
impl Encode<'_, Sqlite> for Box<[u8]> {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Owned(self.into_vec())));
Ok(IsNull::No)
}
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'_>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Owned(
self.clone().into_vec(),
)));
Ok(IsNull::No)
}
}
impl Decode<'_, Sqlite> for Box<[u8]> {
fn decode(value: SqliteValueRef<'_>) -> Result<Self, BoxDynError> {
Ok(Box::from(value.blob()))
}
}
impl Type<Sqlite> for Vec<u8> {
fn type_info() -> SqliteTypeInfo {
<&[u8] as Type<Sqlite>>::type_info()
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
<&[u8] as Type<Sqlite>>::compatible(ty)
}
}
impl<'q> Encode<'q, Sqlite> for Vec<u8> {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Owned(self)));
Ok(IsNull::No)
}
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Owned(self.clone())));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for Vec<u8> {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.blob().to_owned())
}
}
+220
View File
@@ -0,0 +1,220 @@
use std::fmt::Display;
use crate::value::ValueRef;
use crate::{
decode::Decode,
encode::{Encode, IsNull},
error::BoxDynError,
type_info::DataType,
types::Type,
Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef,
};
use chrono::FixedOffset;
use chrono::{
DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Offset, SecondsFormat, TimeZone, Utc,
};
impl<Tz: TimeZone> Type<Sqlite> for DateTime<Tz> {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Datetime)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
<NaiveDateTime as Type<Sqlite>>::compatible(ty)
}
}
impl Type<Sqlite> for NaiveDateTime {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Datetime)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(
ty.0,
DataType::Datetime
| DataType::Text
| DataType::Integer
| DataType::Int4
| DataType::Float
)
}
}
impl Type<Sqlite> for NaiveDate {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Date)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Date | DataType::Text)
}
}
impl Type<Sqlite> for NaiveTime {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Time)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Time | DataType::Text)
}
}
impl<Tz: TimeZone> Encode<'_, Sqlite> for DateTime<Tz>
where
Tz::Offset: Display,
{
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.to_rfc3339_opts(SecondsFormat::AutoSi, false), buf)
}
}
impl Encode<'_, Sqlite> for NaiveDateTime {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format("%F %T%.f").to_string(), buf)
}
}
impl Encode<'_, Sqlite> for NaiveDate {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format("%F").to_string(), buf)
}
}
impl Encode<'_, Sqlite> for NaiveTime {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format("%T%.f").to_string(), buf)
}
}
impl<'r> Decode<'r, Sqlite> for DateTime<Utc> {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(Utc.from_utc_datetime(&decode_datetime(value)?.naive_utc()))
}
}
impl<'r> Decode<'r, Sqlite> for DateTime<Local> {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(Local.from_utc_datetime(&decode_datetime(value)?.naive_utc()))
}
}
impl<'r> Decode<'r, Sqlite> for DateTime<FixedOffset> {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
decode_datetime(value)
}
}
fn decode_datetime(value: SqliteValueRef<'_>) -> Result<DateTime<FixedOffset>, BoxDynError> {
let dt = match value.type_info().0 {
DataType::Text => decode_datetime_from_text(value.text()?),
DataType::Int4 | DataType::Integer => decode_datetime_from_int(value.int64()),
DataType::Float => decode_datetime_from_float(value.double()),
_ => None,
};
if let Some(dt) = dt {
Ok(dt)
} else {
Err(format!("invalid datetime: {}", value.text()?).into())
}
}
fn decode_datetime_from_text(value: &str) -> Option<DateTime<FixedOffset>> {
if let Ok(dt) = DateTime::parse_from_rfc3339(value) {
return Some(dt);
}
// Loop over common date time patterns, inspired by Diesel
// https://github.com/diesel-rs/diesel/blob/93ab183bcb06c69c0aee4a7557b6798fd52dd0d8/diesel/src/sqlite/types/date_and_time/chrono.rs#L56-L97
let sqlite_datetime_formats = &[
// Most likely format
"%F %T%.f",
// Other formats in order of appearance in docs
"%F %R",
"%F %RZ",
"%F %R%:z",
"%F %T%.fZ",
"%F %T%.f%:z",
"%FT%R",
"%FT%RZ",
"%FT%R%:z",
"%FT%T%.f",
"%FT%T%.fZ",
"%FT%T%.f%:z",
];
for format in sqlite_datetime_formats {
if let Ok(dt) = DateTime::parse_from_str(value, format) {
return Some(dt);
}
if let Ok(dt) = NaiveDateTime::parse_from_str(value, format) {
return Some(Utc.fix().from_utc_datetime(&dt));
}
}
None
}
fn decode_datetime_from_int(value: i64) -> Option<DateTime<FixedOffset>> {
Utc.fix().timestamp_opt(value, 0).single()
}
fn decode_datetime_from_float(value: f64) -> Option<DateTime<FixedOffset>> {
let epoch_in_julian_days = 2_440_587.5;
let seconds_in_day = 86400.0;
let timestamp = (value - epoch_in_julian_days) * seconds_in_day;
if !timestamp.is_finite() {
return None;
}
// We don't really have a choice but to do lossy casts for this conversion
// We checked above if the value is infinite or NaN which could otherwise cause problems
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
{
let seconds = timestamp.trunc() as i64;
let nanos = (timestamp.fract() * 1E9).abs() as u32;
Utc.fix().timestamp_opt(seconds, nanos).single()
}
}
impl<'r> Decode<'r, Sqlite> for NaiveDateTime {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(decode_datetime(value)?.naive_local())
}
}
impl<'r> Decode<'r, Sqlite> for NaiveDate {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(NaiveDate::parse_from_str(value.text()?, "%F")?)
}
}
impl<'r> Decode<'r, Sqlite> for NaiveTime {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
let value = value.text()?;
// Loop over common time patterns, inspired by Diesel
// https://github.com/diesel-rs/diesel/blob/93ab183bcb06c69c0aee4a7557b6798fd52dd0d8/diesel/src/sqlite/types/date_and_time/chrono.rs#L29-L47
#[rustfmt::skip] // don't like how rustfmt mangles the comments
let sqlite_time_formats = &[
// Most likely format
"%T.f", "%T%.f",
// Other formats in order of appearance in docs
"%R", "%RZ", "%T%.fZ", "%R%:z", "%T%.f%:z",
];
for format in sqlite_time_formats {
if let Ok(dt) = NaiveTime::parse_from_str(value, format) {
return Ok(dt);
}
}
Err(format!("invalid time: {value}").into())
}
}
+54
View File
@@ -0,0 +1,54 @@
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::type_info::DataType;
use crate::types::Type;
use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
impl Type<Sqlite> for f32 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Float)
}
}
impl<'q> Encode<'q, Sqlite> for f32 {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Double((*self).into()));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for f32 {
fn decode(value: SqliteValueRef<'r>) -> Result<f32, BoxDynError> {
// Truncation is intentional
#[allow(clippy::cast_possible_truncation)]
Ok(value.double() as f32)
}
}
impl Type<Sqlite> for f64 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Float)
}
}
impl<'q> Encode<'q, Sqlite> for f64 {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Double(*self));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for f64 {
fn decode(value: SqliteValueRef<'r>) -> Result<f64, BoxDynError> {
Ok(value.double())
}
}
+118
View File
@@ -0,0 +1,118 @@
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::type_info::DataType;
use crate::types::Type;
use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
impl Type<Sqlite> for i8 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Int4)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int4 | DataType::Integer)
}
}
impl<'q> Encode<'q, Sqlite> for i8 {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int(*self as i32));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for i8 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
// NOTE: using `sqlite3_value_int64()` here because `sqlite3_value_int()` silently truncates
// which leads to bugs, e.g.:
// https://github.com/launchbadge/sqlx/issues/3179
// Similar bug in Postgres: https://github.com/launchbadge/sqlx/issues/3161
Ok(value.int64().try_into()?)
}
}
impl Type<Sqlite> for i16 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Int4)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int4 | DataType::Integer)
}
}
impl<'q> Encode<'q, Sqlite> for i16 {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int(*self as i32));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for i16 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64().try_into()?)
}
}
impl Type<Sqlite> for i32 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Int4)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int4 | DataType::Integer)
}
}
impl<'q> Encode<'q, Sqlite> for i32 {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int(*self));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for i32 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64().try_into()?)
}
}
impl Type<Sqlite> for i64 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Integer)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int4 | DataType::Integer)
}
}
impl<'q> Encode<'q, Sqlite> for i64 {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int64(*self));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for i64 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64())
}
}
+35
View File
@@ -0,0 +1,35 @@
use serde::{Deserialize, Serialize};
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::types::{Json, Type};
use crate::{type_info::DataType, Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
impl<T> Type<Sqlite> for Json<T> {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Text)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
<&str as Type<Sqlite>>::compatible(ty)
}
}
impl<T> Encode<'_, Sqlite> for Json<T>
where
T: Serialize,
{
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.encode_to_string()?, buf)
}
}
impl<'r, T> Decode<'r, Sqlite> for Json<T>
where
T: 'r + Deserialize<'r>,
{
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Self::decode_from_string(Decode::<Sqlite>::decode(value)?)
}
}
+240
View File
@@ -0,0 +1,240 @@
//! Conversions between Rust and **SQLite** types.
//!
//! # Types
//!
//! | Rust type | SQLite type(s) |
//! |---------------------------------------|------------------------------------------------------|
//! | `bool` | BOOLEAN |
//! | `i8` | INTEGER |
//! | `i16` | INTEGER |
//! | `i32` | INTEGER, INT4 |
//! | `i64` | BIGINT, INT8 |
//! | `u8` | INTEGER |
//! | `u16` | INTEGER |
//! | `u32` | INTEGER |
//! | `u64` | INTEGER (Decode only; see note) |
//! | `f32` | REAL |
//! | `f64` | REAL |
//! | `&str`, [`String`] | TEXT |
//! | `&[u8]`, `Vec<u8>` | BLOB |
//!
//! #### Note: Unsigned Integers
//! Decoding of unsigned integer types simply performs a checked conversion
//! to ensure that overflow does not occur.
//!
//! Encoding of the unsigned integer types `u8`, `u16` and `u32` is implemented by zero-extending to
//! the next-larger signed type. So `u8` becomes `i16`, `u16` becomes `i32`, and `u32` becomes `i64`
//! while still retaining their semantic values.
//!
//! SQLite stores integers in a variable-width encoding and always handles them in memory as 64-bit
//! signed values, so no space is wasted by this implicit widening.
//!
//! However, there is no corresponding larger type for `u64` in SQLite
//! (it would require a native 16-byte integer, i.e. the equivalent of `i128`),
//! and so encoding is not supported for this type.
//!
//! Bit-casting `u64` to `i64`, or storing it as `REAL`, `BLOB` or `TEXT`,
//! would change the semantics of the value in SQL and so violates the principle of least surprise.
//!
//! ### [`chrono`](https://crates.io/crates/chrono)
//!
//! Requires the `chrono` Cargo feature flag.
//!
//! | Rust type | Sqlite type(s) |
//! |---------------------------------------|------------------------------------------------------|
//! | `chrono::NaiveDateTime` | DATETIME (TEXT, INTEGER, REAL) |
//! | `chrono::DateTime<Utc>` | DATETIME (TEXT, INTEGER, REAL) |
//! | `chrono::DateTime<Local>` | DATETIME (TEXT, INTEGER, REAL) |
//! | `chrono::DateTime<FixedOffset>` | DATETIME (TEXT, INTEGER, REAL) |
//! | `chrono::NaiveDate` | DATE (TEXT only) |
//! | `chrono::NaiveTime` | TIME (TEXT only) |
//!
//! ##### NOTE: `DATETIME` conversions
//! SQLite may represent `DATETIME` values as one of three types: `TEXT`, `REAL`, or `INTEGER`.
//! Which one is used is entirely up to you and how you store timestamps in your database.
//!
//! The deserialization for `NaiveDateTime`, `DateTime<Utc>` and `DateTime<Local>` infer the date
//! format from the type of the value they're being decoded from:
//!
//! * If `TEXT`, the format is assumed to be an ISO-8601 compatible datetime string.
//! A number of possible formats are tried; see `sqlx-sqlite/src/types/chrono.rs` for the current
//! set of formats.
//! * If `INTEGER`, it is expected to be the number of seconds since January 1, 1970 00:00 UTC,
//! as if returned from the `unixepoch()` function (without the `subsec` modifier).
//! * If `REAL`, it is expected to be the (possibly fractional) number of days since the Julian epoch,
//! November 24, 4714 BCE 12:00 UTC, as if returned from the `julianday()` function.
//!
//! These types will always encode to a datetime string, either
//! with a timezone offset (`DateTime<Tz>` for any `Tz: TimeZone`) or without (`NaiveDateTime`).
//!
//! ##### NOTE: `CURRENT_TIMESTAMP` and comparison/interoperability of `DATETIME` values
//! As stated previously, `DateTime<Tz>` always encodes to a date-time string
//! _with_ a timezone offset,
//! in [RFC 3339 format][::chrono::DateTime::to_rfc3339_opts] (with `use_z: false`).
//!
//! However, most of SQLite's datetime functions
//! (including `datetime()` and `DEFAULT CURRENT_TIMESTAMP`)
//! do not use this format. They instead use `YYYY-MM-DD HH:MM:SS.SSSS` without a timezone offset.
//!
//! This may cause problems with interoperability with other applications, and especially
//! when comparing datetime values, which compares the actual string values lexicographically.
//!
//! Date-time strings in the SQLite format will generally _not_ compare consistently
//! with date-time strings in the RFC 3339 format.
//!
//! We recommend that you decide up-front whether `DATETIME` values should be stored
//! with explicit time zones or not, and use the corresponding type
//! (and its corresponding offset, if applicable) _consistently_ throughout your
//! application:
//!
//! * RFC 3339 format: `DateTime<Tz>` (e.g. `DateTime<Utc>`, `DateTime<Local>`, `DateTime<FixedOffset>`)
//! * Changing or mixing and matching offsets may break comparisons with existing timestamps.
//! * `DateTime<Local>` is **not recommended** for portable applications.
//! * `DateTime<FixedOffset>` is only recommended if the offset is **constant**.
//! * SQLite format: `NaiveDateTime`
//!
//! Note that non-constant offsets may still cause issues when comparing timestamps,
//! as the comparison operators are not timezone-aware.
//!
//! ### [`time`](https://crates.io/crates/time)
//!
//! Requires the `time` Cargo feature flag.
//!
//! | Rust type | Sqlite type(s) |
//! |---------------------------------------|------------------------------------------------------|
//! | `time::PrimitiveDateTime` | DATETIME (TEXT, INTEGER) |
//! | `time::OffsetDateTime` | DATETIME (TEXT, INTEGER) |
//! | `time::Date` | DATE (TEXT only) |
//! | `time::Time` | TIME (TEXT only) |
//!
//! ##### NOTE: `DATETIME` conversions
//! The behavior here is identical to the corresponding `chrono` types, minus the support for `REAL`
//! values as Julian days (it's just not implemented).
//!
//! `PrimitiveDateTime` and `OffsetDateTime` will always encode to a datetime string, either
//! with a timezone offset (`OffsetDateTime`) or without (`PrimitiveDateTime`).
//!
//! ##### NOTE: `CURRENT_TIMESTAMP` and comparison/interoperability of `DATETIME` values
//! As stated previously, `OffsetDateTime` always encodes to a datetime string _with_ a timezone offset,
//! in [RFC 3339 format][::time::format_description::well_known::Rfc3339] (using `Z` for UTC offsets).
//!
//! However, most of SQLite's datetime functions
//! (including `datetime()` and `DEFAULT CURRENT_TIMESTAMP`)
//! do not use this format. They instead use `YYYY-MM-DD HH:MM:SS.SSSS` without a timezone offset.
//!
//! This may cause problems with interoperability with other applications, and especially
//! when comparing datetime values, which compares the actual string values lexicographically.
//!
//! Date-time strings in the SQLite format will generally _not_ compare consistently
//! with date-time strings in the RFC 3339 format.
//!
//! We recommend that you decide up-front whether `DATETIME` values should be stored
//! with explicit time zones or not, and use the corresponding type
//! (and its corresponding offset, if applicable) _consistently_ throughout your
//! application:
//!
//! * RFC 3339 format: `OffsetDateTime` with a **constant** offset.
//! * Changing or mixing and matching offsets may break comparisons with existing timestamps.
//! * `OffsetDateTime::now_local()` is **not recommended** for portable applications.
//! * Non-UTC offsets are only recommended if the offset is **constant**.
//! * SQLite format: `PrimitiveDateTime`
//!
//! Note that non-constant offsets may still cause issues when comparing timestamps,
//! as the comparison operators are not timezone-aware.
//!
//! ### [`uuid`](https://crates.io/crates/uuid)
//!
//! Requires the `uuid` Cargo feature flag.
//!
//! | Rust type | Sqlite type(s) |
//! |---------------------------------------|------------------------------------------------------|
//! | `uuid::Uuid` | BLOB, TEXT |
//! | `uuid::fmt::Hyphenated` | TEXT |
//! | `uuid::fmt::Simple` | TEXT |
//!
//! ### [`json`](https://crates.io/crates/serde_json)
//!
//! Requires the `json` Cargo feature flag.
//!
//! | Rust type | Sqlite type(s) |
//! |---------------------------------------|------------------------------------------------------|
//! | [`Json<T>`] | TEXT |
//! | `serde_json::JsonValue` | TEXT |
//! | `&serde_json::value::RawValue` | TEXT |
//!
//! # Nullable
//!
//! In addition, `Option<T>` is supported where `T` implements `Type`. An `Option<T>` represents
//! a potentially `NULL` value from SQLite.
//!
//! # Non-feature: `NUMERIC` / `rust_decimal` / `bigdecimal` Support
//! Support for mapping `rust_decimal::Decimal` and `bigdecimal::BigDecimal` to SQLite has been
//! deliberately omitted because SQLite does not have native support for high-
//! or arbitrary-precision decimal arithmetic, and to pretend so otherwise would be a
//! significant misstep in API design.
//!
//! The in-tree [`decimal.c`] extension is unfortunately not included in the [amalgamation],
//! which is used to build the bundled version of SQLite3 for `libsqlite3-sys` (which we have
//! enabled by default for the simpler setup experience), otherwise we could support that.
//!
//! The `NUMERIC` type affinity, while seemingly designed for storing decimal values,
//! stores non-integer real numbers as double-precision IEEE-754 floating point,
//! i.e. `REAL` in SQLite, `f64` in Rust, `double` in C/C++, etc.
//!
//! [Datatypes in SQLite: Type Affinity][type-affinity] (accessed 2023/11/20):
//!
//! > A column with NUMERIC affinity may contain values using all five storage classes.
//! > When text data is inserted into a NUMERIC column, the storage class of the text is converted to
//! > INTEGER or REAL (in order of preference) if the text is a well-formed integer or real literal,
//! > respectively. If the TEXT value is a well-formed integer literal that is too large to fit in a
//! > 64-bit signed integer, it is converted to REAL. For conversions between TEXT and REAL storage
//! > classes, only the first 15 significant decimal digits of the number are preserved.
//!
//! With the SQLite3 interactive CLI, we can see that a higher-precision value
//! (20 digits in this case) is rounded off:
//!
//! ```text
//! sqlite> CREATE TABLE foo(bar NUMERIC);
//! sqlite> INSERT INTO foo(bar) VALUES('1.2345678901234567890');
//! sqlite> SELECT * FROM foo;
//! 1.23456789012346
//! ```
//!
//! It appears the `TEXT` storage class is only used if the value contains invalid characters
//! or extra whitespace.
//!
//! Thus, the `NUMERIC` type affinity is **unsuitable** for storage of high-precision decimal values
//! and should be **avoided at all costs**.
//!
//! Support for `rust_decimal` and `bigdecimal` would only be a trap because users will naturally
//! want to use the `NUMERIC` type affinity, and might otherwise encounter serious bugs caused by
//! rounding errors that they were deliberately avoiding when they chose an arbitrary-precision type
//! over a floating-point type in the first place.
//!
//! Instead, you should only use a type affinity that SQLite will not attempt to convert implicitly,
//! such as `TEXT` or `BLOB`, and map values to/from SQLite as strings. You can do this easily
//! using [the `Text` adapter].
//!
//!
//! [`decimal.c`]: https://www.sqlite.org/floatingpoint.html#the_decimal_c_extension
//! [amalgamation]: https://www.sqlite.org/amalgamation.html
//! [type-affinity]: https://www.sqlite.org/datatype3.html#type_affinity
//! [the `Text` adapter]: Text
pub(crate) use sqlx_core::types::*;
mod bool;
mod bytes;
#[cfg(feature = "chrono")]
mod chrono;
mod float;
mod int;
#[cfg(feature = "json")]
mod json;
mod str;
mod text;
#[cfg(feature = "time")]
mod time;
mod uint;
#[cfg(feature = "uuid")]
mod uuid;
+124
View File
@@ -0,0 +1,124 @@
use std::borrow::Cow;
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::type_info::DataType;
use crate::types::Type;
use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
impl Type<Sqlite> for str {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Text)
}
}
impl<'q> Encode<'q, Sqlite> for &'q str {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Borrowed(*self)));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for &'r str {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
value.text()
}
}
impl Type<Sqlite> for Box<str> {
fn type_info() -> SqliteTypeInfo {
<&str as Type<Sqlite>>::type_info()
}
}
impl Encode<'_, Sqlite> for Box<str> {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(self.into_string())));
Ok(IsNull::No)
}
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'_>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(
self.clone().into_string(),
)));
Ok(IsNull::No)
}
}
impl Decode<'_, Sqlite> for Box<str> {
fn decode(value: SqliteValueRef<'_>) -> Result<Self, BoxDynError> {
value.text().map(Box::from)
}
}
impl Type<Sqlite> for String {
fn type_info() -> SqliteTypeInfo {
<&str as Type<Sqlite>>::type_info()
}
}
impl<'q> Encode<'q, Sqlite> for String {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(self)));
Ok(IsNull::No)
}
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(self.clone())));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for String {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
value.text().map(ToOwned::to_owned)
}
}
impl Type<Sqlite> for Cow<'_, str> {
fn type_info() -> SqliteTypeInfo {
<&str as Type<Sqlite>>::type_info()
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
<&str as Type<Sqlite>>::compatible(ty)
}
}
impl<'q> Encode<'q, Sqlite> for Cow<'q, str> {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(self));
Ok(IsNull::No)
}
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(self.clone()));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for Cow<'r, str> {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
value.text().map(Cow::Borrowed)
}
}
+37
View File
@@ -0,0 +1,37 @@
use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
use sqlx_core::decode::Decode;
use sqlx_core::encode::{Encode, IsNull};
use sqlx_core::error::BoxDynError;
use sqlx_core::types::{Text, Type};
use std::fmt::Display;
use std::str::FromStr;
impl<T> Type<Sqlite> for Text<T> {
fn type_info() -> SqliteTypeInfo {
<String as Type<Sqlite>>::type_info()
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
<String as Type<Sqlite>>::compatible(ty)
}
}
impl<'q, T> Encode<'q, Sqlite> for Text<T>
where
T: Display,
{
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.0.to_string(), buf)
}
}
impl<'r, T> Decode<'r, Sqlite> for Text<T>
where
T: FromStr,
BoxDynError: From<<T as FromStr>::Err>,
{
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
let s: &str = Decode::<Sqlite>::decode(value)?;
Ok(Self(s.parse()?))
}
}
+315
View File
@@ -0,0 +1,315 @@
use crate::value::ValueRef;
use crate::{
decode::Decode,
encode::{Encode, IsNull},
error::BoxDynError,
type_info::DataType,
types::Type,
Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef,
};
use time::format_description::{well_known::Rfc3339, BorrowedFormatItem};
use time::macros::format_description as fd;
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
impl Type<Sqlite> for OffsetDateTime {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Datetime)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
<PrimitiveDateTime as Type<Sqlite>>::compatible(ty)
}
}
impl Type<Sqlite> for PrimitiveDateTime {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Datetime)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(
ty.0,
DataType::Datetime | DataType::Text | DataType::Integer | DataType::Int4
)
}
}
impl Type<Sqlite> for Date {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Date)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Date | DataType::Text)
}
}
impl Type<Sqlite> for Time {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Time)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Time | DataType::Text)
}
}
impl Encode<'_, Sqlite> for OffsetDateTime {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format(&Rfc3339)?, buf)
}
}
impl Encode<'_, Sqlite> for PrimitiveDateTime {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
let format = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
Encode::<Sqlite>::encode(self.format(&format)?, buf)
}
}
impl Encode<'_, Sqlite> for Date {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
let format = fd!("[year]-[month]-[day]");
Encode::<Sqlite>::encode(self.format(&format)?, buf)
}
}
impl Encode<'_, Sqlite> for Time {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
let format = fd!("[hour]:[minute]:[second].[subsecond]");
Encode::<Sqlite>::encode(self.format(&format)?, buf)
}
}
impl<'r> Decode<'r, Sqlite> for OffsetDateTime {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
decode_offset_datetime(value)
}
}
impl<'r> Decode<'r, Sqlite> for PrimitiveDateTime {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
decode_datetime(value)
}
}
impl<'r> Decode<'r, Sqlite> for Date {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(Date::parse(value.text()?, &fd!("[year]-[month]-[day]"))?)
}
}
impl<'r> Decode<'r, Sqlite> for Time {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
let value = value.text()?;
let sqlite_time_formats = &[
fd!("[hour]:[minute]:[second].[subsecond]"),
fd!("[hour]:[minute]:[second]"),
fd!("[hour]:[minute]"),
];
for format in sqlite_time_formats {
if let Ok(dt) = Time::parse(value, &format) {
return Ok(dt);
}
}
Err(format!("invalid time: {value}").into())
}
}
fn decode_offset_datetime(value: SqliteValueRef<'_>) -> Result<OffsetDateTime, BoxDynError> {
let dt = match value.type_info().0 {
DataType::Text => decode_offset_datetime_from_text(value.text()?),
DataType::Int4 | DataType::Integer => {
Some(OffsetDateTime::from_unix_timestamp(value.int64())?)
}
_ => None,
};
if let Some(dt) = dt {
Ok(dt)
} else {
Err(format!("invalid offset datetime: {}", value.text()?).into())
}
}
fn decode_offset_datetime_from_text(value: &str) -> Option<OffsetDateTime> {
if let Ok(dt) = OffsetDateTime::parse(value, &Rfc3339) {
return Some(dt);
}
if let Ok(dt) = OffsetDateTime::parse(value, formats::OFFSET_DATE_TIME) {
return Some(dt);
}
if let Some(dt) = decode_datetime_from_text(value) {
return Some(dt.assume_utc());
}
None
}
fn decode_datetime(value: SqliteValueRef<'_>) -> Result<PrimitiveDateTime, BoxDynError> {
let dt = match value.type_info().0 {
DataType::Text => decode_datetime_from_text(value.text()?),
DataType::Int4 | DataType::Integer => {
let parsed = OffsetDateTime::from_unix_timestamp(value.int64()).unwrap();
Some(PrimitiveDateTime::new(parsed.date(), parsed.time()))
}
_ => None,
};
if let Some(dt) = dt {
Ok(dt)
} else {
Err(format!("invalid datetime: {}", value.text()?).into())
}
}
fn decode_datetime_from_text(value: &str) -> Option<PrimitiveDateTime> {
let default_format = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
if let Ok(dt) = PrimitiveDateTime::parse(value, &default_format) {
return Some(dt);
}
let formats = [
BorrowedFormatItem::Compound(formats::PRIMITIVE_DATE_TIME_SPACE_SEPARATED),
BorrowedFormatItem::Compound(formats::PRIMITIVE_DATE_TIME_T_SEPARATED),
];
if let Ok(dt) = PrimitiveDateTime::parse(value, &BorrowedFormatItem::First(&formats)) {
return Some(dt);
}
None
}
mod formats {
use time::format_description::BorrowedFormatItem::{Component, Literal, Optional};
use time::format_description::{modifier, BorrowedFormatItem, Component::*};
const YEAR: BorrowedFormatItem<'_> = Component(Year({
let mut value = modifier::Year::default();
value.padding = modifier::Padding::Zero;
value.repr = modifier::YearRepr::Full;
value.iso_week_based = false;
value.sign_is_mandatory = false;
value
}));
const MONTH: BorrowedFormatItem<'_> = Component(Month({
let mut value = modifier::Month::default();
value.padding = modifier::Padding::Zero;
value.repr = modifier::MonthRepr::Numerical;
value.case_sensitive = true;
value
}));
const DAY: BorrowedFormatItem<'_> = Component(Day({
let mut value = modifier::Day::default();
value.padding = modifier::Padding::Zero;
value
}));
const HOUR: BorrowedFormatItem<'_> = Component(Hour({
let mut value = modifier::Hour::default();
value.padding = modifier::Padding::Zero;
value.is_12_hour_clock = false;
value
}));
const MINUTE: BorrowedFormatItem<'_> = Component(Minute({
let mut value = modifier::Minute::default();
value.padding = modifier::Padding::Zero;
value
}));
const SECOND: BorrowedFormatItem<'_> = Component(Second({
let mut value = modifier::Second::default();
value.padding = modifier::Padding::Zero;
value
}));
const SUBSECOND: BorrowedFormatItem<'_> = Component(Subsecond({
let mut value = modifier::Subsecond::default();
value.digits = modifier::SubsecondDigits::OneOrMore;
value
}));
const OFFSET_HOUR: BorrowedFormatItem<'_> = Component(OffsetHour({
let mut value = modifier::OffsetHour::default();
value.sign_is_mandatory = true;
value.padding = modifier::Padding::Zero;
value
}));
const OFFSET_MINUTE: BorrowedFormatItem<'_> = Component(OffsetMinute({
let mut value = modifier::OffsetMinute::default();
value.padding = modifier::Padding::Zero;
value
}));
pub(super) const OFFSET_DATE_TIME: &[BorrowedFormatItem<'_>] = {
&[
YEAR,
Literal(b"-"),
MONTH,
Literal(b"-"),
DAY,
Optional(&Literal(b" ")),
Optional(&Literal(b"T")),
HOUR,
Literal(b":"),
MINUTE,
Optional(&Literal(b":")),
Optional(&SECOND),
Optional(&Literal(b".")),
Optional(&SUBSECOND),
Optional(&OFFSET_HOUR),
Optional(&Literal(b":")),
Optional(&OFFSET_MINUTE),
]
};
pub(super) const PRIMITIVE_DATE_TIME_SPACE_SEPARATED: &[BorrowedFormatItem<'_>] = {
&[
YEAR,
Literal(b"-"),
MONTH,
Literal(b"-"),
DAY,
Literal(b" "),
HOUR,
Literal(b":"),
MINUTE,
Optional(&Literal(b":")),
Optional(&SECOND),
Optional(&Literal(b".")),
Optional(&SUBSECOND),
Optional(&Literal(b"Z")),
]
};
pub(super) const PRIMITIVE_DATE_TIME_T_SEPARATED: &[BorrowedFormatItem<'_>] = {
&[
YEAR,
Literal(b"-"),
MONTH,
Literal(b"-"),
DAY,
Literal(b"T"),
HOUR,
Literal(b":"),
MINUTE,
Optional(&Literal(b":")),
Optional(&SECOND),
Optional(&Literal(b".")),
Optional(&SUBSECOND),
Optional(&Literal(b"Z")),
]
};
}
+107
View File
@@ -0,0 +1,107 @@
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::type_info::DataType;
use crate::types::Type;
use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
impl Type<Sqlite> for u8 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Int4)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int4 | DataType::Integer)
}
}
impl<'q> Encode<'q, Sqlite> for u8 {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int(*self as i32));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for u8 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
// NOTE: using `sqlite3_value_int64()` here because `sqlite3_value_int()` silently truncates
// which leads to bugs, e.g.:
// https://github.com/launchbadge/sqlx/issues/3179
// Similar bug in Postgres: https://github.com/launchbadge/sqlx/issues/3161
Ok(value.int64().try_into()?)
}
}
impl Type<Sqlite> for u16 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Int4)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int4 | DataType::Integer)
}
}
impl<'q> Encode<'q, Sqlite> for u16 {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int(*self as i32));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for u16 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64().try_into()?)
}
}
impl Type<Sqlite> for u32 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Integer)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int4 | DataType::Integer)
}
}
impl<'q> Encode<'q, Sqlite> for u32 {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int64(*self as i64));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for u32 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64().try_into()?)
}
}
impl Type<Sqlite> for u64 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Integer)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int4 | DataType::Integer)
}
}
impl<'r> Decode<'r, Sqlite> for u64 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64().try_into()?)
}
}
+93
View File
@@ -0,0 +1,93 @@
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::type_info::DataType;
use crate::types::Type;
use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
use std::borrow::Cow;
use uuid::{
fmt::{Hyphenated, Simple},
Uuid,
};
impl Type<Sqlite> for Uuid {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Blob)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Blob | DataType::Text)
}
}
impl<'q> Encode<'q, Sqlite> for Uuid {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Owned(
self.as_bytes().to_vec(),
)));
Ok(IsNull::No)
}
}
impl Decode<'_, Sqlite> for Uuid {
fn decode(value: SqliteValueRef<'_>) -> Result<Self, BoxDynError> {
// construct a Uuid from the returned bytes
Uuid::from_slice(value.blob()).map_err(Into::into)
}
}
impl Type<Sqlite> for Hyphenated {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Text)
}
}
impl<'q> Encode<'q, Sqlite> for Hyphenated {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(self.to_string())));
Ok(IsNull::No)
}
}
impl Decode<'_, Sqlite> for Hyphenated {
fn decode(value: SqliteValueRef<'_>) -> Result<Self, BoxDynError> {
let uuid: Result<Uuid, BoxDynError> =
Uuid::parse_str(&value.text().map(ToOwned::to_owned)?).map_err(Into::into);
Ok(uuid?.hyphenated())
}
}
impl Type<Sqlite> for Simple {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Text)
}
}
impl<'q> Encode<'q, Sqlite> for Simple {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(self.to_string())));
Ok(IsNull::No)
}
}
impl Decode<'_, Sqlite> for Simple {
fn decode(value: SqliteValueRef<'_>) -> Result<Self, BoxDynError> {
let uuid: Result<Uuid, BoxDynError> =
Uuid::parse_str(&value.text().map(ToOwned::to_owned)?).map_err(Into::into);
Ok(uuid?.simple())
}
}
+244
View File
@@ -0,0 +1,244 @@
use std::borrow::Cow;
use std::marker::PhantomData;
use std::ptr::NonNull;
use std::slice::from_raw_parts;
use std::str::from_utf8;
use std::sync::Arc;
use libsqlite3_sys::{
sqlite3_value, sqlite3_value_blob, sqlite3_value_bytes, sqlite3_value_double,
sqlite3_value_dup, sqlite3_value_free, sqlite3_value_int64, sqlite3_value_type, SQLITE_NULL,
};
pub(crate) use sqlx_core::value::{Value, ValueRef};
use crate::error::BoxDynError;
use crate::type_info::DataType;
use crate::{Sqlite, SqliteTypeInfo};
enum SqliteValueData<'r> {
Value(&'r SqliteValue),
BorrowedHandle(ValueHandle<'r>),
}
pub struct SqliteValueRef<'r>(SqliteValueData<'r>);
impl<'r> SqliteValueRef<'r> {
pub(crate) fn value(value: &'r SqliteValue) -> Self {
Self(SqliteValueData::Value(value))
}
// SAFETY: The supplied sqlite3_value must not be null and SQLite must free it. It will not be freed on drop.
// The lifetime on this struct should tie it to whatever scope it's valid for before SQLite frees it.
#[allow(unused)]
pub(crate) unsafe fn borrowed(value: *mut sqlite3_value, type_info: SqliteTypeInfo) -> Self {
debug_assert!(!value.is_null());
let handle = ValueHandle::new_borrowed(NonNull::new_unchecked(value), type_info);
Self(SqliteValueData::BorrowedHandle(handle))
}
// NOTE: `int()` is deliberately omitted because it will silently truncate a wider value,
// which is likely to cause bugs:
// https://github.com/launchbadge/sqlx/issues/3179
// (Similar bug in Postgres): https://github.com/launchbadge/sqlx/issues/3161
pub(super) fn int64(&self) -> i64 {
match &self.0 {
SqliteValueData::Value(v) => v.0.int64(),
SqliteValueData::BorrowedHandle(v) => v.int64(),
}
}
pub(super) fn double(&self) -> f64 {
match &self.0 {
SqliteValueData::Value(v) => v.0.double(),
SqliteValueData::BorrowedHandle(v) => v.double(),
}
}
pub(super) fn blob(&self) -> &'r [u8] {
match &self.0 {
SqliteValueData::Value(v) => v.0.blob(),
SqliteValueData::BorrowedHandle(v) => v.blob(),
}
}
pub(super) fn text(&self) -> Result<&'r str, BoxDynError> {
match &self.0 {
SqliteValueData::Value(v) => v.0.text(),
SqliteValueData::BorrowedHandle(v) => v.text(),
}
}
}
impl<'r> ValueRef<'r> for SqliteValueRef<'r> {
type Database = Sqlite;
fn to_owned(&self) -> SqliteValue {
match &self.0 {
SqliteValueData::Value(v) => (*v).clone(),
SqliteValueData::BorrowedHandle(v) => unsafe {
SqliteValue::new(v.value.as_ptr(), v.type_info.clone())
},
}
}
fn type_info(&self) -> Cow<'_, SqliteTypeInfo> {
match &self.0 {
SqliteValueData::Value(v) => v.type_info(),
SqliteValueData::BorrowedHandle(v) => v.type_info(),
}
}
fn is_null(&self) -> bool {
match &self.0 {
SqliteValueData::Value(v) => v.is_null(),
SqliteValueData::BorrowedHandle(v) => v.is_null(),
}
}
}
#[derive(Clone)]
pub struct SqliteValue(Arc<ValueHandle<'static>>);
pub(crate) struct ValueHandle<'a> {
value: NonNull<sqlite3_value>,
type_info: SqliteTypeInfo,
free_on_drop: bool,
_sqlite_value_lifetime: PhantomData<&'a ()>,
}
// SAFE: only protected value objects are stored in SqliteValue
unsafe impl<'a> Send for ValueHandle<'a> {}
unsafe impl<'a> Sync for ValueHandle<'a> {}
impl ValueHandle<'static> {
fn new_owned(value: NonNull<sqlite3_value>, type_info: SqliteTypeInfo) -> Self {
Self {
value,
type_info,
free_on_drop: true,
_sqlite_value_lifetime: PhantomData,
}
}
}
impl<'a> ValueHandle<'a> {
fn new_borrowed(value: NonNull<sqlite3_value>, type_info: SqliteTypeInfo) -> Self {
Self {
value,
type_info,
free_on_drop: false,
_sqlite_value_lifetime: PhantomData,
}
}
fn type_info_opt(&self) -> Option<SqliteTypeInfo> {
let dt = DataType::from_code(unsafe { sqlite3_value_type(self.value.as_ptr()) });
if let DataType::Null = dt {
None
} else {
Some(SqliteTypeInfo(dt))
}
}
fn int64(&self) -> i64 {
unsafe { sqlite3_value_int64(self.value.as_ptr()) }
}
fn double(&self) -> f64 {
unsafe { sqlite3_value_double(self.value.as_ptr()) }
}
fn blob<'b>(&self) -> &'b [u8] {
let len = unsafe { sqlite3_value_bytes(self.value.as_ptr()) };
// This likely means UB in SQLite itself or our usage of it;
// signed integer overflow is UB in the C standard.
let len = usize::try_from(len).unwrap_or_else(|_| {
panic!("sqlite3_value_bytes() returned value out of range for usize: {len}")
});
if len == 0 {
// empty blobs are NULL so just return an empty slice
return &[];
}
let ptr = unsafe { sqlite3_value_blob(self.value.as_ptr()) } as *const u8;
debug_assert!(!ptr.is_null());
unsafe { from_raw_parts(ptr, len) }
}
fn text<'b>(&self) -> Result<&'b str, BoxDynError> {
Ok(from_utf8(self.blob())?)
}
fn type_info(&self) -> Cow<'_, SqliteTypeInfo> {
self.type_info_opt()
.map(Cow::Owned)
.unwrap_or(Cow::Borrowed(&self.type_info))
}
fn is_null(&self) -> bool {
unsafe { sqlite3_value_type(self.value.as_ptr()) == SQLITE_NULL }
}
}
impl<'a> Drop for ValueHandle<'a> {
fn drop(&mut self) {
if self.free_on_drop {
unsafe {
sqlite3_value_free(self.value.as_ptr());
}
}
}
}
impl SqliteValue {
// SAFETY: The sqlite3_value must be non-null and SQLite must not free it. It will be freed on drop.
pub(crate) unsafe fn new(value: *mut sqlite3_value, type_info: SqliteTypeInfo) -> Self {
debug_assert!(!value.is_null());
let handle =
ValueHandle::new_owned(NonNull::new_unchecked(sqlite3_value_dup(value)), type_info);
Self(Arc::new(handle))
}
}
impl Value for SqliteValue {
type Database = Sqlite;
fn as_ref(&self) -> SqliteValueRef<'_> {
SqliteValueRef::value(self)
}
fn type_info(&self) -> Cow<'_, SqliteTypeInfo> {
self.0.type_info()
}
fn is_null(&self) -> bool {
self.0.is_null()
}
}
// #[cfg(feature = "any")]
// impl<'r> From<SqliteValueRef<'r>> for crate::any::AnyValueRef<'r> {
// #[inline]
// fn from(value: SqliteValueRef<'r>) -> Self {
// crate::any::AnyValueRef {
// type_info: value.type_info().clone().into_owned().into(),
// kind: crate::any::value::AnyValueRefKind::Sqlite(value),
// }
// }
// }
//
// #[cfg(feature = "any")]
// impl From<SqliteValue> for crate::any::AnyValue {
// #[inline]
// fn from(value: SqliteValue) -> Self {
// crate::any::AnyValue {
// type_info: value.type_info().clone().into_owned().into(),
// kind: crate::any::value::AnyValueKind::Sqlite(value),
// }
// }
// }