Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "003b698e99e024f3621b8043a2426fde5b741171"
},
"path_in_vcs": "sqlx-sqlite"
}
+1365
View File
File diff suppressed because it is too large Load Diff
+198
View File
@@ -0,0 +1,198 @@
# 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"
rust-version = "1.94.0"
name = "sqlx-sqlite"
version = "0.9.0"
authors = [
"Ryan Leckey <leckey.ryan@gmail.com>",
"Austin Bonander <austin.bonander@gmail.com>",
"Chloe Ross <orangesnowfox@gmail.com>",
"Daniel Akhterov <akhterovd@gmail.com>",
]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "SQLite driver implementation for SQLx. Not for direct use; see the `sqlx` crate for details."
documentation = "https://docs.rs/sqlx"
readme = false
license = "MIT OR Apache-2.0"
repository = "https://github.com/launchbadge/sqlx"
[package.metadata.docs.rs]
features = ["__unstable_docs"]
[features]
_unstable-all-sqlite-features = [
"deserialize",
"load-extension",
"preupdate-hook",
"unlock-notify",
]
_unstable-all-types = [
"json",
"chrono",
"time",
"uuid",
]
_unstable-docs = [
"bundled",
"any",
"_unstable-all-types",
"_unstable-all-sqlite-features",
]
any = ["sqlx-core/any"]
bigdecimal = []
bundled = ["libsqlite3-sys/bundled"]
chrono = [
"dep:chrono",
"sqlx-core/chrono",
]
deserialize = []
json = [
"sqlx-core/json",
"serde",
]
load-extension = []
migrate = ["sqlx-core/migrate"]
offline = [
"sqlx-core/offline",
"serde",
]
preupdate-hook = ["libsqlite3-sys/preupdate_hook"]
regexp = ["dep:regex"]
rust_decimal = []
sqlx-toml = ["sqlx-core/sqlx-toml"]
time = [
"dep:time",
"sqlx-core/time",
]
unbundled = ["libsqlite3-sys/buildtime_bindgen"]
unlock-notify = ["libsqlite3-sys/unlock_notify"]
uuid = [
"dep:uuid",
"sqlx-core/uuid",
]
[lib]
name = "sqlx_sqlite"
path = "src/lib.rs"
[dependencies.atoi]
version = "2.0"
[dependencies.chrono]
version = "0.4.34"
features = [
"std",
"clock",
]
optional = true
default-features = false
[dependencies.flume]
version = "0.12.0"
features = ["async"]
default-features = false
[dependencies.form_urlencoded]
version = "1.2.2"
[dependencies.futures-channel]
version = "0.3.32"
features = [
"sink",
"alloc",
"std",
]
default-features = false
[dependencies.futures-core]
version = "0.3.32"
default-features = false
[dependencies.futures-executor]
version = "0.3.32"
[dependencies.futures-intrusive]
version = "0.5.0"
[dependencies.futures-util]
version = "0.3.32"
features = [
"alloc",
"sink",
]
default-features = false
[dependencies.libsqlite3-sys]
version = ">=0.30.1, <0.38.0"
features = [
"pkg-config",
"vcpkg",
]
default-features = false
[dependencies.log]
version = "0.4.18"
[dependencies.percent-encoding]
version = "2.3.0"
[dependencies.regex]
version = "1.6.0"
optional = true
[dependencies.serde]
version = "1.0.219"
features = ["derive"]
optional = true
[dependencies.sqlx-core]
version = "=0.9.0"
[dependencies.thiserror]
version = "2.0.18"
features = ["std"]
default-features = false
[dependencies.time]
version = "0.3.47"
features = [
"formatting",
"parsing",
"macros",
]
optional = true
[dependencies.tracing]
version = "0.1.37"
features = ["log"]
[dependencies.url]
version = "2.2.2"
[dependencies.uuid]
version = "1.12.1"
optional = true
[dev-dependencies]
[lints.clippy]
cast_possible_truncation = "deny"
cast_possible_wrap = "deny"
cast_sign_loss = "deny"
disallowed_methods = "deny"
+108
View File
@@ -0,0 +1,108 @@
[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
rust-version.workspace = true
[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"]
# Conditionally compiled SQLite features
deserialize = []
load-extension = []
preupdate-hook = ["libsqlite3-sys/preupdate_hook"]
unlock-notify = ["libsqlite3-sys/unlock_notify"]
bundled = ["libsqlite3-sys/bundled"]
unbundled = ["libsqlite3-sys/buildtime_bindgen"]
sqlx-toml = ["sqlx-core/sqlx-toml"]
# Note: currently unused, only to satisfy "unexpected `cfg` condition" lint
bigdecimal = []
rust_decimal = []
_unstable-all-types = [
"json", "chrono", "time", "uuid",
]
_unstable-all-sqlite-features = [
"deserialize",
"load-extension",
"preupdate-hook",
"unlock-notify",
]
_unstable-docs = [
"bundled", "any",
"_unstable-all-types",
"_unstable-all-sqlite-features"
]
[dependencies.libsqlite3-sys]
# See `sqlx-sqlite/src/lib.rs` for details.
version = ">=0.30.1, <0.38.0"
default-features = false
features = [
"pkg-config",
"vcpkg",
]
[dependencies]
futures-core = { version = "0.3.32", default-features = false }
futures-channel = { version = "0.3.32", 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.32" }
futures-intrusive = "0.5.0"
futures-util = { version = "0.3.32", 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.3.0"
form_urlencoded = "1.2.2"
flume = { version = "0.12.0", default-features = false, features = ["async"] }
atoi = "2.0"
log = "0.4.18"
tracing = { version = "0.1.37", features = ["log"] }
thiserror.workspace = true
serde = { version = "1.0.219", features = ["derive"], optional = true }
regex = { version = "1.6.0", optional = true }
[dependencies.sqlx-core]
workspace = true
[dev-dependencies.sqlx]
# FIXME: https://github.com/rust-lang/cargo/issues/15622
# workspace = true
path = ".."
default-features = false
features = ["macros", "runtime-tokio", "tls-none", "sqlite"]
[lints]
workspace = true
[package.metadata.docs.rs]
features = ["__unstable_docs"]
+1
View File
@@ -0,0 +1 @@
../LICENSE-APACHE
+1
View File
@@ -0,0 +1 @@
../LICENSE-MIT
+242
View File
@@ -0,0 +1,242 @@
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::{FutureExt, StreamExt, TryFutureExt, TryStreamExt};
use sqlx_core::any::{
AnyArguments, AnyColumn, AnyConnectOptions, AnyConnectionBackend, AnyQueryResult, AnyRow,
AnyStatement, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind,
};
use sqlx_core::sql_str::SqlStr;
use crate::arguments::SqliteArgumentsBuffer;
use crate::type_info::DataType;
use sqlx_core::connection::{ConnectOptions, Connection};
use sqlx_core::database::Database;
use sqlx_core::executor::Executor;
use sqlx_core::transaction::TransactionManager;
use std::pin::pin;
use std::sync::Arc;
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).boxed()
}
fn close_hard(self: Box<Self>) -> BoxFuture<'static, sqlx_core::Result<()>> {
Connection::close_hard(*self).boxed()
}
fn ping(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> {
Connection::ping(self).boxed()
}
fn begin(&mut self, statement: Option<SqlStr>) -> BoxFuture<'_, sqlx_core::Result<()>> {
SqliteTransactionManager::begin(self, statement).boxed()
}
fn commit(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> {
SqliteTransactionManager::commit(self).boxed()
}
fn rollback(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> {
SqliteTransactionManager::rollback(self).boxed()
}
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).boxed()
}
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(
&mut self,
query: SqlStr,
persistent: bool,
arguments: Option<AnyArguments>,
) -> BoxStream<'_, 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(
&mut self,
query: SqlStr,
persistent: bool,
arguments: Option<AnyArguments>,
) -> BoxFuture<'_, 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: SqlStr,
_parameters: &[AnyTypeInfo],
) -> BoxFuture<'c, sqlx_core::Result<AnyStatement>> {
Box::pin(async move {
let statement = Executor::prepare_with(self, sql, &[]).await?;
let column_names = statement.column_names.clone();
AnyStatement::try_from_statement(statement, column_names)
})
}
#[cfg(feature = "offline")]
fn describe(
&mut self,
sql: SqlStr,
) -> BoxFuture<'_, sqlx_core::Result<sqlx_core::describe::Describe<sqlx_core::any::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)
}
}
// Infallible alternative to AnyArguments::convert_into()
fn map_arguments(args: AnyArguments) -> SqliteArguments {
let 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(Arc::new(t.to_string())),
AnyValueKind::Blob(b) => SqliteArgumentValue::Blob(Arc::new(b.to_vec())),
// AnyValueKind is `#[non_exhaustive]` but we should have covered everything
_ => unreachable!("BUG: missing mapping for {val:?}"),
})
.collect();
SqliteArguments {
values: SqliteArgumentsBuffer::new(values),
}
}
fn map_result(res: SqliteQueryResult) -> AnyQueryResult {
AnyQueryResult {
rows_affected: res.rows_affected(),
last_insert_id: None,
}
}
+148
View File
@@ -0,0 +1,148 @@
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::sync::Arc;
pub(crate) use sqlx_core::arguments::*;
use sqlx_core::error::BoxDynError;
#[derive(Debug, Clone)]
pub enum SqliteArgumentValue {
Null,
Text(Arc<String>),
TextSlice(Arc<str>),
Blob(Arc<Vec<u8>>),
Double(f64),
Int(i32),
Int64(i64),
}
#[derive(Default, Debug, Clone)]
pub struct SqliteArguments {
pub(crate) values: SqliteArgumentsBuffer,
}
#[derive(Default, Debug, Clone)]
pub struct SqliteArgumentsBuffer(Vec<SqliteArgumentValue>);
impl SqliteArguments {
pub(crate) fn add<'t, T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'t, Sqlite>,
{
let value_length_before_encoding = self.values.0.len();
match value.encode(&mut self.values) {
Ok(IsNull::Yes) => self.values.0.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.0.truncate(value_length_before_encoding);
return Err(error);
}
};
Ok(())
}
}
impl Arguments for SqliteArguments {
type Database = Sqlite;
fn reserve(&mut self, len: usize, _size_hint: usize) {
self.values.0.reserve(len);
}
fn add<'t, T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'t, Self::Database>,
{
self.add(value)
}
fn len(&self) -> usize {
self.values.0.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.0.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.0[n - 1].bind(handle, param_i)?;
}
Ok(arg_i - offset)
}
}
impl SqliteArgumentsBuffer {
#[allow(dead_code)] // clippy incorrectly reports this as unused
pub(crate) fn new(values: Vec<SqliteArgumentValue>) -> SqliteArgumentsBuffer {
Self(values)
}
pub(crate) fn push(&mut self, value: SqliteArgumentValue) {
self.0.push(value);
}
}
impl SqliteArgumentValue {
fn bind(&self, handle: &mut StatementHandle, i: usize) -> Result<(), Error> {
use SqliteArgumentValue::*;
let status = match self {
Text(v) => handle.bind_text(i, v),
TextSlice(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(())
}
}
+35
View File
@@ -0,0 +1,35 @@
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,
#[cfg_attr(feature = "offline", serde(default))]
pub(crate) origin: ColumnOrigin,
}
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
}
fn origin(&self) -> ColumnOrigin {
self.origin.clone()
}
}
+158
View File
@@ -0,0 +1,158 @@
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::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}"));
// SQLite explicitly documents that invalid UTF-8 may be passed into
// application-defined collating sequences. The safe `Fn(&str, &str)`
// signature exposed to users must never observe invalid UTF-8, so
// lossily coerce the raw bytes here.
let s1 = {
let c_slice = slice::from_raw_parts(left_ptr as *const u8, left_len);
String::from_utf8_lossy(c_slice)
};
let s2 = {
let c_slice = slice::from_raw_parts(right_ptr as *const u8, right_len);
String::from_utf8_lossy(c_slice)
};
let t = (*boxed_f)(&s1, &s2);
match t {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
}
}
+101
View File
@@ -0,0 +1,101 @@
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::sql_str::SqlStr;
use sqlx_core::Either;
use std::convert::identity;
pub(crate) fn describe(
conn: &mut ConnectionState,
query: SqlStr,
) -> 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.as_str(), 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 origin = stmt.handle.column_origin(col);
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,
origin,
});
}
}
Ok(Describe {
columns,
parameters: Some(Either::Right(num_params)),
nullable,
})
}
+299
View File
@@ -0,0 +1,299 @@
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
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite-deserialize")))]
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
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite-deserialize")))]
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()))
}
}
+271
View File
@@ -0,0 +1,271 @@
use crate::connection::handle::ConnectionHandle;
use crate::connection::LogSettings;
use crate::connection::{ConnectionState, Statements};
use crate::error::Error;
use crate::SqliteConnectOptions;
use libsqlite3_sys::{
sqlite3_busy_timeout, 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 std::collections::BTreeMap;
use std::ffi::CString;
use std::io;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
#[cfg(feature = "load-extension")]
use sqlx_core::IndexMap;
// 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);
pub struct EstablishParams {
filename: CString,
open_flags: i32,
busy_timeout: Duration,
statement_cache_capacity: usize,
log_settings: LogSettings,
#[cfg(feature = "load-extension")]
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),
);
// Suffix serializer automatically handles `&` separators for us.
let filename_len = filename.len();
filename = form_urlencoded::Serializer::for_suffix(filename, filename_len)
.extend_pairs(query_params)
.finish();
}
let filename = CString::new(filename).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"filename passed to SQLite must not contain nul bytes",
)
})?;
#[cfg(feature = "load-extension")]
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(),
#[cfg(feature = "load-extension")]
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,
})
}
pub(crate) fn establish(&self) -> Result<ConnectionState, Error> {
let mut handle = ConnectionHandle::open(&self.filename, self.open_flags)?;
#[cfg(feature = "load-extension")]
unsafe {
self.apply_extensions(&mut handle)?;
}
#[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 != libsqlite3_sys::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.");
handle.call_with_result(|db| unsafe { sqlite3_busy_timeout(db, ms) })?;
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,
})
}
#[cfg(feature = "load-extension")]
unsafe fn apply_extensions(&self, handle: &mut ConnectionHandle) -> Result<(), Error> {
use libsqlite3_sys::{sqlite3_free, sqlite3_load_extension};
use std::ffi::{c_int, CStr};
use std::ptr;
/// `true` enables *just* `sqlite3_load_extension`, false disables *all* extension loading.
fn enable_load_extension(
handle: &mut ConnectionHandle,
enabled: bool,
) -> Result<(), Error> {
use libsqlite3_sys::{sqlite3_db_config, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION};
// SAFETY: we have exclusive access and this matches the expected signature
// <https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension>
handle.call_with_result(|db| unsafe {
// https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.bool-char-as-int
sqlite3_db_config(
db,
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
enabled as c_int,
ptr::null_mut::<c_int>(),
)
})?;
Ok(())
}
if self.extensions.is_empty() {
return Ok(());
}
// We enable extension loading only so long as *we're* doing it.
enable_load_extension(handle, true)?;
for (name, entrypoint) in &self.extensions {
let name_ptr = name.as_ptr();
let entrypoint_ptr = entrypoint.as_ref().map_or_else(ptr::null, |s| s.as_ptr());
let mut err_msg_ptr = ptr::null_mut();
// SAFETY:
// * we have exclusive access
// * all pointers are initialized
// * we warn the user about loading extensions in documentation
handle
.call_with_result(|db| unsafe {
sqlite3_load_extension(db, name_ptr, entrypoint_ptr, &mut err_msg_ptr)
})
.map_err(|e| {
if !err_msg_ptr.is_null() {
// SAFETY: pointer is not-null,
// and we copy the error message to an allocation we own.
let err_msg = unsafe { CStr::from_ptr(err_msg_ptr) }
// In practice, the string *should* be UTF-8.
.to_string_lossy()
.into_owned();
// SAFETY: we're expected to free the error message afterward.
unsafe {
sqlite3_free(err_msg_ptr.cast());
}
e.with_message(err_msg)
} else {
e
}
})?;
}
// We then disable extension loading immediately afterward.
enable_load_extension(handle, false)
}
}
+132
View File
@@ -0,0 +1,132 @@
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::sql_str::SqlSafeStr;
use sqlx_core::Either;
pub struct ExecuteIter<'a> {
handle: &'a mut ConnectionHandle,
statement: &'a mut VirtualStatement,
logger: QueryLogger,
args: Option<SqliteArguments>,
/// 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(
conn: &mut ConnectionState,
query: impl SqlSafeStr,
args: Option<SqliteArguments>,
persistent: bool,
) -> Result<ExecuteIter<'_>, Error> {
let query = query.into_sql_str();
// fetch the cached statement or allocate a new one
let statement = conn.statements.get(query.as_str(), 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(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();
}
}
+101
View File
@@ -0,0 +1,101 @@
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::error::Error;
use sqlx_core::executor::{Execute, Executor};
use sqlx_core::sql_str::SqlStr;
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 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();
let sql = query.sql();
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 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 sql = query.sql();
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>(
self,
sql: SqlStr,
_parameters: &[SqliteTypeInfo],
) -> BoxFuture<'e, Result<SqliteStatement, Error>>
where
'c: 'e,
{
Box::pin(async move {
let statement = self.worker.prepare(sql).await?;
Ok(statement)
})
}
#[doc(hidden)]
#[cfg(feature = "offline")]
fn describe<'e>(
self,
sql: SqlStr,
) -> BoxFuture<'e, Result<sqlx_core::describe::Describe<Sqlite>, Error>>
where
'c: 'e,
{
Box::pin(async move { self.worker.describe(sql).await })
}
}
File diff suppressed because it is too large Load Diff
+151
View File
@@ -0,0 +1,151 @@
use std::ffi::{c_int, CStr, CString};
use std::ptr::NonNull;
use std::{io, ptr};
use crate::error::Error;
use libsqlite3_sys::{
sqlite3, sqlite3_close, sqlite3_exec, sqlite3_extended_result_codes, sqlite3_get_autocommit,
sqlite3_last_insert_rowid, sqlite3_open_v2, SQLITE_OK,
};
use crate::SqliteError;
/// Managed SQLite3 database handle.
/// The database handle will be closed when this is dropped.
#[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 {
pub(crate) fn open(filename: &CStr, flags: c_int) -> Result<Self, Error> {
let mut handle = ptr::null_mut();
// <https://www.sqlite.org/c3ref/open.html>
let status = unsafe { sqlite3_open_v2(filename.as_ptr(), &mut handle, flags, ptr::null()) };
// SAFETY: the database is still initialized as long as the pointer is not `NULL`.
// We need to close it even if there's an error.
let mut handle = Self(NonNull::new(handle).ok_or_else(|| {
Error::Io(io::Error::new(
io::ErrorKind::OutOfMemory,
"SQLite is unable to allocate memory to hold the sqlite3 object",
))
})?);
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 {
// This only returns a non-OK code if SQLite is built with `SQLITE_ENABLE_API_ARMOR`
// and the database pointer is `NULL` or already closed.
//
// The invariants of this type guarantee that neither is true.
sqlite3_extended_result_codes(handle.as_ptr(), 1);
}
Ok(handle)
}
#[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 call_with_result(
&mut self,
call: impl FnOnce(*mut sqlite3) -> c_int,
) -> Result<(), SqliteError> {
let res = call(self.as_ptr());
if res == SQLITE_OK {
Ok(())
} else {
Err(self
.last_error()
.unwrap_or_else(|| SqliteError::from_code(res)))
}
}
pub(crate) fn in_transaction(&mut self) -> bool {
// SAFETY: we have exclusive access to the database handle
let ret = unsafe { sqlite3_get_autocommit(self.as_ptr()) };
ret == 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 {
#[cfg_attr(not(feature = "unlock-notify"), expect(clippy::never_loop))]
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(()),
#[cfg(feature = "unlock-notify")]
libsqlite3_sys::SQLITE_LOCKED_SHAREDCACHE => {
crate::statement::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()));
}
}
}
}
+175
View File
@@ -0,0 +1,175 @@
// 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 = usize::try_from(size).expect("negative column index unsupported");
if idx >= self.0.len() {
let new_len = idx.checked_add(1).expect("idx + 1 overflowed");
self.0.resize_with(new_len, || 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);
self.0[idx].replace(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(&mut self, idx: &i64) -> &mut V {
let idx: usize = self.expand(*idx);
self.0[idx].get_or_insert_default()
}
}
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_n(None, 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
}
}
+599
View File
@@ -0,0 +1,599 @@
use std::cmp::Ordering;
use std::ffi::CStr;
use std::fmt::Write;
use std::fmt::{self, Debug, Formatter};
use std::future::Future;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::catch_unwind;
use std::ptr;
use std::ptr::NonNull;
use futures_intrusive::sync::MutexGuard;
use libsqlite3_sys::{
sqlite3, sqlite3_commit_hook, 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::sql_str::{AssertSqlSafe, SqlSafeStr};
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;
#[cfg(feature = "deserialize")]
pub(crate) mod deserialize;
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;
async fn close(mut self) -> Result<(), Error> {
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(AssertSqlSafe(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
}
async fn close_hard(self) -> Result<(), Error> {
drop(self);
Ok(())
}
/// Ensure the background worker thread is alive and accepting commands.
fn ping(&mut self) -> impl Future<Output = Result<(), Error>> + Send + '_ {
self.worker.ping()
}
fn begin(
&mut self,
) -> impl Future<Output = Result<Transaction<'_, Self::Database>, Error>> + Send + '_ {
Transaction::begin(self, None)
}
fn begin_with(
&mut self,
statement: impl SqlSafeStr,
) -> impl Future<Output = Result<Transaction<'_, Self::Database>, Error>> + Send + '_
where
Self: Sized,
{
Transaction::begin(self, Some(statement.into_sql_str()))
}
fn cached_statements_size(&self) -> usize {
self.worker.shared.get_cached_statements_size()
}
fn clear_cached_statements(&mut self) -> impl Future<Output = Result<(), Error>> + Send + '_ {
self.worker.clear_cache()
}
#[inline]
fn shrink_buffers(&mut self) {
// No-op.
}
#[doc(hidden)]
async fn flush(&mut self) -> 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.
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()
}
}
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;
}
}
+157
View File
@@ -0,0 +1,157 @@
use super::SqliteOperation;
use crate::{SqliteError, SqliteValueRef};
use libsqlite3_sys::{
sqlite3, sqlite3_preupdate_count, sqlite3_preupdate_depth, sqlite3_preupdate_new,
sqlite3_preupdate_old, sqlite3_value, 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)));
}
Ok(SqliteValueRef::borrowed(p_value))
}
}
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,
})
});
}
}
+573
View File
@@ -0,0 +1,573 @@
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 sqlx_core::sql_str::SqlStr;
use tracing::span::Span;
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::establish::EstablishParams;
use crate::connection::execute;
use crate::connection::ConnectionState;
use crate::{SqliteArguments, SqliteQueryResult, SqliteRow, SqliteStatement};
#[cfg(feature = "deserialize")]
use crate::connection::deserialize::{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: SqlStr,
tx: oneshot::Sender<Result<SqliteStatement, Error>>,
},
#[cfg(feature = "offline")]
Describe {
query: SqlStr,
tx: oneshot::Sender<Result<sqlx_core::describe::Describe<crate::Sqlite>, Error>>,
},
Execute {
query: SqlStr,
arguments: Option<SqliteArguments>,
persistent: bool,
tx: flume::Sender<Result<Either<SqliteQueryResult, SqliteRow>, Error>>,
limit: Option<usize>,
},
#[cfg(feature = "deserialize")]
Serialize {
schema: Option<SchemaName>,
tx: oneshot::Sender<Result<SqliteOwnedBuf, Error>>,
},
#[cfg(feature = "deserialize")]
Deserialize {
schema: Option<SchemaName>,
data: SqliteOwnedBuf,
read_only: bool,
tx: oneshot::Sender<Result<(), Error>>,
},
Begin {
tx: rendezvous_oneshot::Sender<Result<(), Error>>,
statement: Option<SqlStr>,
},
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)).ok();
// This may issue an unnecessary write on failure,
// but it doesn't matter in the grand scheme of things.
update_cached_statements_size(
&conn,
&shared.cached_statements_size,
);
}
#[cfg(feature = "offline")]
Command::Describe { query, tx } => {
tx.send(crate::connection::describe::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 is_custom_statement = statement.is_some();
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.as_str())
.and_then(|res| {
if is_custom_statement && !conn.handle.in_transaction() {
return Err(Error::BeginFailed)
}
shared.transaction_depth.fetch_add(1, Ordering::Release);
Ok(res)
});
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).as_str())
.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).as_str())
.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).as_str())
.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;
}
}
}
#[cfg(feature = "deserialize")]
Command::Serialize { schema, tx } => {
tx.send(serialize(&mut conn, schema)).ok();
}
#[cfg(feature = "deserialize")]
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: SqlStr) -> Result<SqliteStatement, Error> {
self.oneshot_cmd(|tx| Command::Prepare { query, tx })
.await?
}
#[cfg(feature = "offline")]
pub(crate) async fn describe(
&mut self,
query: SqlStr,
) -> Result<sqlx_core::describe::Describe<crate::Sqlite>, Error> {
self.oneshot_cmd(|tx| Command::Describe { query, tx })
.await?
}
pub(crate) async fn execute(
&mut self,
query: SqlStr,
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,
arguments: args,
persistent,
tx,
limit,
},
Span::current(),
))
.await
.map_err(|_| Error::WorkerCrashed)?;
Ok(rx)
}
pub(crate) async fn begin(&mut self, statement: Option<SqlStr>) -> 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
}
#[cfg(feature = "deserialize")]
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?
}
#[cfg(feature = "deserialize")]
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: SqlStr) -> Result<SqliteStatement, Error> {
// prepare statement object (or checkout from cache)
let statement = conn.statements.get(query.as_str(), 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: query,
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::arguments::SqliteArgumentsBuffer;
use crate::{
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 = SqliteArguments;
type ArgumentBuffer = SqliteArgumentsBuffer;
type Statement = SqliteStatement;
const NAME: &'static str = "SQLite";
const URL_SCHEMES: &'static [&'static str] = &["sqlite"];
}
impl HasStatementCache for Sqlite {}
+139
View File
@@ -0,0 +1,139 @@
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, SQLITE_NOMEM,
};
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());
String::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()).into_owned()
};
Some(Self {
code,
message: message.into(),
})
}
/// For errors during extension load, the error message is supplied via a separate pointer
#[allow(dead_code)]
pub(crate) fn with_message(mut self, error_msg: String) -> Self {
self.message = error_msg.into();
self
}
#[allow(dead_code)]
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 }
}
#[allow(dead_code)]
pub(crate) fn generic(message: impl Into<Cow<'static, str>>) -> Self {
Self {
code: SQLITE_ERROR,
message: message.into(),
}
}
/// Return `SQLITE_NOMEM`.
pub(crate) fn nomem() -> Self {
Self::from_code(SQLITE_NOMEM)
}
}
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,
}
}
}
+183
View File
@@ -0,0 +1,183 @@
//! **SQLite** database driver.
//!
//! ### Note: `libsqlite3-sys` Version
//! This driver uses the `libsqlite3-sys` crate which links the native library for SQLite 3.
//! Only one version of `libsqlite3-sys` may appear in the dependency tree of your project.
//!
//! As of SQLx 0.9.0, the version of `libsqlite3-sys` is now a range instead of any specific version.
//! See the `Cargo.toml` of the `sqlx-sqlite` crate for the current version range.
//!
//! If you are using `rusqlite` or any other crate that indirectly depends on `libsqlite3-sys`,
//! this should allow Cargo to select a compatible version.
//!
//! If Cargo **fails to select a compatible version**, this means the other crate is using
//! a `libsqlite3-sys` version outside of this range.
//!
//! We may increase the *maximum* version of the range at our discretion,
//! in patch (SemVer-compatible) releases, to allow users to upgrade to newer versions as desired.
//!
//! The *minimum* version of the range may be increased over time to drop very old or
//! insecure versions of SQLite, but this will only occur in major (SemVer-incompatible) releases.
//!
//! Note that this means a `cargo update` may increase the `libsqlite3-sys` version,
//! which could, in rare cases, break your build.
//!
//! To prevent this, you can pin the `libsqlite3-sys` version in your own dependencies:
//!
//! ```toml
//! [dependencies]
//! # for example, if 0.35.0 breaks the build
//! libsqlite3-sys = "0.34"
//! ```
//!
//! ### Static Linking (Default)
//! The `sqlite` feature enables the `bundled` feature of `libsqlite3-sys`,
//! which builds SQLite 3 from included source code and statically links it into the final binary.
//!
//! This requires some C build tools to be installed on the system; see
//! [the `rusqlite` README][rusqlite-readme-building] for details.
//!
//! This version of SQLite is generally much newer than system-installed versions of SQLite
//! (especially for LTS Linux distributions), and can be updated with a `cargo update`,
//! so this is the recommended option for ease of use and keeping up-to-date.
//!
//! ### Dynamic linking
//! To dynamically link to an existing 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][rusqlite-readme-building] for details).
//!
//! Note that this _may_ result in link errors if the SQLite version is too old,
//! or has [certain features disabled at compile-time](https://www.sqlite.org/compile.html).
//!
//! SQLite version `3.20.0` (released August 2018) or newer is recommended.
//!
//! **Please check your SQLite version and the flags it was built with before opening
//! a GitHub issue because of errors in `libsqlite3-sys`.** Thank you.
//!
//! [rusqlite-readme-building]: https://github.com/rusqlite/rusqlite?tab=readme-ov-file#notes-on-building-rusqlite-and-libsqlite3-sys
//!
//! ### Optional Features
//!
//! The following features
//!
// 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, SqliteArgumentsBuffer};
pub use column::SqliteColumn;
#[cfg(feature = "deserialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "deserialize")))]
pub use connection::deserialize::SqliteOwnedBuf;
#[cfg(feature = "preupdate-hook")]
#[cfg_attr(docsrs, doc(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::config;
use sqlx_core::describe::Describe;
use sqlx_core::error::Error;
use sqlx_core::executor::Executor;
use sqlx_core::sql_str::{AssertSqlSafe, SqlSafeStr};
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);
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,
driver_config: &config::drivers::Config,
) -> Result<Describe<Sqlite>, Error> {
let mut opts: SqliteConnectOptions = database_url.parse()?;
opts = opts.apply_driver_config(&driver_config.sqlite)?;
let params = EstablishParams::from_options(&opts)?;
let mut conn = params.establish()?;
// Execute any ancillary `PRAGMA`s
connection::execute::iter(&mut conn, AssertSqlSafe(opts.pragma_string()), None, false)?
.finish()?;
connection::describe::describe(&mut conn, AssertSqlSafe(query.to_string()).into_sql_str())
// 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<R: Debug, S: Debug + DebugDiff, P: Debug> Drop for QueryPlanLogger<'_, R, S, P> {
fn drop(&mut self) {
self.finish();
}
}
+297
View File
@@ -0,0 +1,297 @@
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 sqlx_core::sql_str::AssertSqlSafe;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
pub(crate) use sqlx_core::migrate::*;
use sqlx_core::query_scalar::query_scalar;
impl MigrateDatabase for Sqlite {
async fn create_database(url: &str) -> Result<(), Error> {
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(())
}
async fn database_exists(url: &str) -> Result<bool, Error> {
let options = SqliteConnectOptions::from_str(url)?;
if options.in_memory {
Ok(true)
} else {
Ok(options.filename.exists())
}
}
async fn drop_database(url: &str) -> Result<(), Error> {
let options = SqliteConnectOptions::from_str(url)?;
if !options.in_memory {
fs::remove_file(&*options.filename).await?;
}
Ok(())
}
}
impl Migrate for SqliteConnection {
fn create_schema_if_not_exists<'e>(
&'e mut self,
schema_name: &'e str,
) -> BoxFuture<'e, Result<(), MigrateError>> {
Box::pin(async move {
// Check if the schema already exists; if so, don't error.
let schema_version: Option<i64> = query_scalar(AssertSqlSafe(format!(
"PRAGMA {schema_name}.schema_version"
)))
.fetch_optional(&mut *self)
.await?;
if schema_version.is_some() {
return Ok(());
}
Err(MigrateError::CreateSchemasNotSupported(
format!("cannot create new schema {schema_name}; creation of additional schemas in SQLite requires attaching extra database files"),
))
})
}
fn ensure_migrations_table<'e>(
&'e mut self,
table_name: &'e str,
) -> BoxFuture<'e, Result<(), MigrateError>> {
Box::pin(async move {
// language=SQLite
self.execute(AssertSqlSafe(format!(
r#"
CREATE TABLE IF NOT EXISTS {table_name} (
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<'e>(
&'e mut self,
table_name: &'e str,
) -> BoxFuture<'e, Result<Option<i64>, MigrateError>> {
Box::pin(async move {
// language=SQLite
let row: Option<(i64,)> = query_as(AssertSqlSafe(format!(
"SELECT version FROM {table_name} WHERE success = false ORDER BY version LIMIT 1"
)))
.fetch_optional(self)
.await?;
Ok(row.map(|r| r.0))
})
}
fn list_applied_migrations<'e>(
&'e mut self,
table_name: &'e str,
) -> BoxFuture<'e, Result<Vec<AppliedMigration>, MigrateError>> {
Box::pin(async move {
// language=SQLite
let rows: Vec<(i64, Vec<u8>)> = query_as(AssertSqlSafe(format!(
"SELECT version, checksum FROM {table_name} 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>(
&'e mut self,
table_name: &'e str,
migration: &'e Migration,
) -> BoxFuture<'e, Result<Duration, MigrateError>> {
Box::pin(async move {
let start = Instant::now();
if migration.no_tx {
execute_migration(self, table_name, migration).await?;
} else {
// Use a single transaction for the actual migration script and the essential bookkeeping 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 mut tx = self.begin().await?;
execute_migration(&mut tx, table_name, migration).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=SQLite
#[allow(clippy::cast_possible_truncation)]
let _ = query(AssertSqlSafe(format!(
r#"
UPDATE {table_name}
SET execution_time = ?1
WHERE version = ?2
"#
)))
.bind(elapsed.as_nanos() as i64)
.bind(migration.version)
.execute(self)
.await?;
Ok(elapsed)
})
}
fn revert<'e>(
&'e mut self,
table_name: &'e str,
migration: &'e Migration,
) -> BoxFuture<'e, Result<Duration, MigrateError>> {
Box::pin(async move {
let start = Instant::now();
if migration.no_tx {
revert_migration(self, table_name, migration).await?;
} else {
// Use a single transaction for the actual migration script and the essential bookkeeping so we never
// execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966.
let mut tx = self.begin().await?;
revert_migration(&mut tx, table_name, migration).await?;
tx.commit().await?;
}
let elapsed = start.elapsed();
Ok(elapsed)
})
}
fn skip<'e>(
&'e mut self,
table_name: &'e str,
migration: &'e Migration,
) -> BoxFuture<'e, Result<(), MigrateError>> {
Box::pin(async move {
// language=SQLite
let _ = query(AssertSqlSafe(format!(
r#"
INSERT INTO {table_name} ( version, description, success, checksum, execution_time )
VALUES ( ?1, ?2, TRUE, ?3, -1 )
"#
)))
.bind(migration.version)
.bind(&*migration.description)
.bind(&*migration.checksum)
.execute(self)
.await?;
Ok(())
})
}
}
async fn execute_migration(
conn: &mut SqliteConnection,
table_name: &str,
migration: &Migration,
) -> Result<(), MigrateError> {
let _ = conn
.execute(migration.sql.clone())
.await
.map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?;
// language=SQLite
let _ = query(AssertSqlSafe(format!(
r#"
INSERT INTO {table_name} ( version, description, success, checksum, execution_time )
VALUES ( ?1, ?2, TRUE, ?3, -1 )
"#
)))
.bind(migration.version)
.bind(&*migration.description)
.bind(&*migration.checksum)
.execute(conn)
.await?;
Ok(())
}
async fn revert_migration(
conn: &mut SqliteConnection,
table_name: &str,
migration: &Migration,
) -> Result<(), MigrateError> {
let _ = conn
.execute(migration.sql.clone())
.await
.map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?;
// language=SQLite
let _ = query(AssertSqlSafe(format!(
r#"
DELETE FROM {table_name}
WHERE version = ?1
"#
)))
.bind(migration.version)
.execute(conn)
.await?;
Ok(())
}
+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(),
));
}
})
}
}
+83
View File
@@ -0,0 +1,83 @@
use crate::{SqliteConnectOptions, SqliteConnection};
use log::LevelFilter;
use sqlx_core::config;
use sqlx_core::connection::ConnectOptions;
use sqlx_core::error::Error;
use sqlx_core::executor::Executor;
use sqlx_core::sql_str::AssertSqlSafe;
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()
}
async fn connect(&self) -> Result<Self::Connection, Error>
where
Self::Connection: Sized,
{
let mut conn = SqliteConnection::establish(self).await?;
// Execute PRAGMAs
conn.execute(AssertSqlSafe(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
}
fn __unstable_apply_driver_config(
self,
config: &config::drivers::Config,
) -> crate::Result<Self> {
self.apply_driver_config(&config.sqlite)
}
}
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(),
));
}
})
}
}
+675
View File
@@ -0,0 +1,675 @@
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::{config, 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>
#[cfg(feature = "load-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,
#[cfg(feature = "load-extension")]
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
}
/// Add a [SQLite extension](https://www.sqlite.org/loadext.html) to be loaded into the database
/// connection at startup, using the default entrypoint.
///
/// Most common SQLite extensions can be loaded using this method.
/// For extensions where you need to override the entry point,
/// use [`.extension_with_entrypoint()`].
///
/// Multiple extensions can be loaded by calling this method,
/// or [`.extension_with_entrypoint()`] where applicable,
/// once for each extension.
///
/// Extension loading is only enabled during the initialization of the connection,
/// and disabled before `connect()` returns by setting
/// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`] to 0.
///
/// This will not enable the SQL `load_extension()` function.
///
/// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension
/// [`.extension_with_entrypoint()`]: Self::extension_with_entrypoint
///
/// # Safety
/// This causes arbitrary DLLs on the filesystem to be loaded at runtime,
/// which can easily result in undefined behavior, memory corruption,
/// or exploitable vulnerabilities if misused.
///
/// It is not possible to provide a truly safe version of this API.
///
/// Use this method with care, and only load extensions that you trust.
///
/// # Example
/// ```rust,no_run
/// # use sqlx_core::error::Error;
/// # use std::str::FromStr;
/// # use sqlx_sqlite::SqliteConnectOptions;
/// # fn options() -> Result<SqliteConnectOptions, Error> {
/// let mut options = SqliteConnectOptions::from_str("sqlite://data.db")?;
///
/// // SAFETY: these are trusted extensions.
/// unsafe {
/// options = options
/// .extension("vsv")
/// .extension("mod_spatialite");
/// }
///
/// # Ok(options)
/// # }
/// ```
#[cfg(feature = "load-extension")]
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite-load-extension")))]
pub unsafe fn extension(mut self, extension_name: impl Into<Cow<'static, str>>) -> Self {
self.extensions.insert(extension_name.into(), None);
self
}
/// Add a [SQLite extension](https://www.sqlite.org/loadext.html) to be loaded into the database
/// connection at startup, overriding the entrypoint.
///
/// See also [`.extension()`] for extensions using the standard entrypoint name
/// `sqlite3_extension_init` or `sqlite3_<extension name>_init`.
///
/// Multiple extensions can be loaded by calling this method,
/// or [`.extension()`] where applicable,
/// once for each extension.
///
/// Extension loading is only enabled during the initialization of the connection,
/// and disabled before `connect()` returns by setting
/// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`] to 0.
///
/// This will not enable the SQL `load_extension()` function.
///
/// [`SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension
/// [`.extension_with_entrypoint()`]: Self::extension_with_entrypoint
///
/// # Safety
/// This causes arbitrary DLLs on the filesystem to be loaded at runtime,
/// which can easily result in undefined behavior, memory corruption,
/// or exploitable vulnerabilities if misused.
///
/// If you specify the wrong entrypoint name, it _may_ simply result in an error,
/// or it may end up invoking the wrong routine, leading to undefined behavior.
///
/// It is not possible to provide a truly safe version of this API.
///
/// Use this method with care, only load extensions that you trust,
/// and double-check the entrypoint name with the extension's documentation or source code.
#[cfg(feature = "load-extension")]
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite-load-extension")))]
pub unsafe 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
}
#[cfg_attr(not(feature = "load-extension"), expect(unused_mut))]
pub(crate) fn apply_driver_config(
mut self,
config: &config::drivers::SqliteConfig,
) -> crate::Result<Self> {
#[cfg(feature = "load-extension")]
for extension in &config.unsafe_load_extensions {
// SAFETY: the documentation warns the user about loading extensions
match extension {
config::drivers::SqliteExtension::Path(path) => {
self = unsafe { self.extension(path.clone()) }
}
config::drivers::SqliteExtension::PathWithEntrypoint { path, entrypoint } => {
self =
unsafe { self.extension_with_entrypoint(path.clone(), entrypoint.clone()) }
}
}
}
#[cfg(not(feature = "load-extension"))]
if !config.unsafe_load_extensions.is_empty() {
return Err(sqlx_core::Error::Configuration(
format!(
"sqlx.toml specifies `drivers.sqlite.unsafe-load-extensions = {:?}` \
but extension loading is not enabled; \
enable the `sqlite-load-extension` feature of SQLx to use SQLite extensions",
config.unsafe_load_extensions,
)
.into(),
));
}
Ok(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,
}
}
}
+281
View File
@@ -0,0 +1,281 @@
#![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, 0) else {
return;
};
// arg1: value
let Some(value) = get_text_from_arg(ctx, *args.add(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`. Returns `None` for NULL values.
///
/// For non-NULL values, `sqlite3_value_text()` is called directly, which lets SQLite
/// coerce INTEGER, REAL, and BLOB values to their text representation. This matches
/// the coercion behavior documented at <https://www.sqlite.org/c3ref/value_blob.html>.
///
/// 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_NULL {
return None;
}
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
}
}
}
/// 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_regexp_coerces_non_text_values() {
let mut conn = crate::SqliteConnectOptions::from_str("sqlite://:memory:")
.unwrap()
.with_regexp()
.connect()
.await
.unwrap();
// INTEGER coercion
let result: Option<i32> = sqlx::query_scalar("SELECT 123 REGEXP '23'")
.fetch_one(&mut conn)
.await
.unwrap();
assert_eq!(result, Some(1));
// REAL coercion
let result: Option<i32> = sqlx::query_scalar("SELECT 12.5 REGEXP '12\\.5'")
.fetch_one(&mut conn)
.await
.unwrap();
assert_eq!(result, Some(1));
// INTEGER column
sqlx::query("CREATE TABLE int_test (x INTEGER NOT NULL)")
.execute(&mut conn)
.await
.unwrap();
sqlx::query("INSERT INTO int_test VALUES (123), (45)")
.execute(&mut conn)
.await
.unwrap();
let rows: Vec<i64> = sqlx::query_scalar("SELECT x FROM int_test WHERE x REGEXP '23'")
.fetch_all(&mut conn)
.await
.unwrap();
assert_eq!(rows, vec![123]);
// NULL should return NULL, not match
let result: Option<i32> = sqlx::query_scalar("SELECT NULL REGEXP '.*'")
.fetch_one(&mut conn)
.await
.unwrap();
assert_eq!(result, None);
}
#[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(_))));
}
}
+95
View File
@@ -0,0 +1,95 @@
#![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::{debug_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::dup(raw, Some(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()
}
}
impl std::fmt::Debug for SqliteRow {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
debug_row(self, f)
}
}
// #[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),
// }
// }
// }
+466
View File
@@ -0,0 +1,466 @@
use std::ffi::c_void;
use std::ffi::CStr;
use crate::error::{BoxDynError, Error};
use crate::type_info::DataType;
use crate::{SqliteError, SqliteTypeInfo};
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_MISUSE, SQLITE_OK, SQLITE_ROW, SQLITE_TRANSIENT,
SQLITE_UTF8,
};
use sqlx_core::column::{ColumnOrigin, TableColumn};
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;
use std::sync::Arc;
#[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 {}
// Most of the getters below allocate internally, and unsynchronized access is undefined.
// unsafe impl !Sync 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(CStr::from_ptr(raw).to_bytes())
.expect("sqlite3_sql() returned non-UTF-8 string")
}
}
#[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(CStr::from_ptr(name).to_bytes())
.expect("sqlite3_column_name() returned non-UTF-8 column name")
}
}
pub(crate) fn column_origin(&self, index: usize) -> ColumnOrigin {
if let Some((table, name)) = self
.column_table_name(index)
.zip(self.column_origin_name(index))
{
let table: Arc<str> = self
.column_db_name(index)
.filter(|&db| db != "main")
.map_or_else(
|| table.into(),
// TODO: check that SQLite returns the names properly quoted if necessary
|db| format!("{db}.{table}").into(),
);
ColumnOrigin::Table(TableColumn {
table,
name: name.into(),
})
} else {
ColumnOrigin::Expression
}
}
fn column_db_name(&self, index: usize) -> Option<&str> {
unsafe {
let db_name = sqlite3_column_database_name(self.0.as_ptr(), check_col_idx!(index));
if !db_name.is_null() {
Some(
from_utf8(CStr::from_ptr(db_name).to_bytes())
.expect("sqlite3_column_database_name() returned non-UTF-8 string"),
)
} else {
None
}
}
}
fn column_table_name(&self, index: usize) -> Option<&str> {
unsafe {
let table_name = sqlite3_column_table_name(self.0.as_ptr(), check_col_idx!(index));
if !table_name.is_null() {
Some(
from_utf8(CStr::from_ptr(table_name).to_bytes())
.expect("sqlite3_column_table_name() returned non-UTF-8 string"),
)
} else {
None
}
}
}
fn column_origin_name(&self, index: usize) -> Option<&str> {
unsafe {
let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), check_col_idx!(index));
if !origin_name.is_null() {
Some(
from_utf8(CStr::from_ptr(origin_name).to_bytes())
.expect("sqlite3_column_origin_name() returned non-UTF-8 string"),
)
} else {
None
}
}
}
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(CStr::from_ptr(decl).to_bytes())
.expect("sqlite3_column_decltype() returned non-UTF-8 string");
let ty: DataType = decl.parse().ok()?;
Some(SqliteTypeInfo(ty))
}
}
/// Use sqlite3_column_metadata to determine if a specific column is nullable.
///
/// Returns None in the case of INTEGER PRIMARY KEYs
/// This is because this column is an alias to rowid if the table does not use a compound
/// primary key. In this case the row is not nullable, and the output of
/// sqlite3_column_metadata may be incorrect.
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;
let mut datatype: *const c_char = ptr::null();
let mut primary_key: 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,
&mut datatype,
// function docs state to provide NULL for return values you don't care about
ptr::null_mut(),
&mut not_null,
&mut primary_key,
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());
}
let datatype = CStr::from_ptr(datatype);
Ok(
if primary_key != 0
&& datatype
.to_bytes()
.eq_ignore_ascii_case("integer".as_bytes())
{
None
} else {
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(CStr::from_ptr(name).to_bytes())
.expect("sqlite3_bind_parameter_name() returned non-UTF-8 string"),
)
}
}
// 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 {
#[cfg_attr(not(feature = "unlock-notify"), expect(clippy::never_loop))]
loop {
match sqlite3_step(self.0.as_ptr()) {
SQLITE_ROW => return Ok(true),
SQLITE_DONE => return Ok(false),
SQLITE_MISUSE => panic!("misuse!"),
#[cfg(feature = "unlock-notify")]
libsqlite3_sys::SQLITE_LOCKED_SHAREDCACHE => {
// The shared cache is locked by another connection. Wait for unlock
// notification and try again.
super::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.");
}
}
}
}
+59
View File
@@ -0,0 +1,59 @@
use crate::column::ColumnIndex;
use crate::error::Error;
use crate::ext::ustr::UStr;
use crate::{Sqlite, SqliteArguments, SqliteColumn, SqliteTypeInfo};
use sqlx_core::sql_str::SqlStr;
use sqlx_core::{Either, HashMap};
use std::sync::Arc;
pub(crate) use sqlx_core::statement::*;
mod handle;
#[cfg(feature = "unlock-notify")]
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 {
pub(crate) sql: SqlStr,
pub(crate) parameters: usize,
pub(crate) columns: Arc<Vec<SqliteColumn>>,
pub(crate) column_names: Arc<HashMap<UStr, usize>>,
}
impl Statement for SqliteStatement {
type Database = Sqlite;
fn into_sql(self) -> SqlStr {
self.sql
}
fn sql(&self) -> &SqlStr {
&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()
}
}
+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();
}
}
+204
View File
@@ -0,0 +1,204 @@
#![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,
origin: statement.column_origin(i),
});
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)
}
+83
View File
@@ -0,0 +1,83 @@
use crate::error::Error;
use crate::pool::PoolOptions;
use crate::testing::{FixtureSnapshot, TestArgs, TestContext, TestSupport};
use crate::{Sqlite, SqliteConnectOptions};
use std::future::Future;
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,
) -> impl Future<Output = Result<TestContext<Self>, Error>> + Send + '_ {
test_context(args)
}
async fn cleanup_test(db_name: &str) -> Result<(), Error> {
crate::fs::remove_file(db_name).await?;
Ok(())
}
async fn cleanup_test_dbs() -> Result<Option<usize>, Error> {
crate::fs::remove_dir_all(BASE_PATH).await?;
Ok(None)
}
async fn snapshot(_conn: &mut Self::Connection) -> 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");
}
+35
View File
@@ -0,0 +1,35 @@
use std::future::Future;
use sqlx_core::transaction::TransactionManager;
use sqlx_core::{error::Error, sql_str::SqlStr};
use crate::{Sqlite, SqliteConnection};
/// Implementation of [`TransactionManager`] for SQLite.
pub struct SqliteTransactionManager;
impl TransactionManager for SqliteTransactionManager {
type Database = Sqlite;
async fn begin(conn: &mut SqliteConnection, statement: Option<SqlStr>) -> Result<(), Error> {
conn.worker.begin(statement).await
}
fn commit(conn: &mut SqliteConnection) -> impl Future<Output = Result<(), Error>> + Send + '_ {
conn.worker.commit()
}
fn rollback(
conn: &mut SqliteConnection,
) -> impl Future<Output = Result<(), Error>> + Send + '_ {
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()
}
}
+56
View File
@@ -0,0 +1,56 @@
use crate::Sqlite;
#[allow(unused_imports)]
use sqlx_core as sqlx;
// 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(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,
// The expansion of the macro automatically applies the correct feature name
// and checks `[macros.preferred-crates]`
datetime-types: {
chrono: {
sqlx::types::chrono::NaiveDate,
sqlx::types::chrono::NaiveDateTime,
sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>
| sqlx::types::chrono::DateTime<_>,
},
time: {
sqlx::types::time::OffsetDateTime,
sqlx::types::time::PrimitiveDateTime,
sqlx::types::time::Date,
},
},
numeric-types: {
bigdecimal: { },
rust_decimal: { },
},
);
+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(())
}
+31
View File
@@ -0,0 +1,31 @@
use crate::arguments::SqliteArgumentsBuffer;
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 Encode<'_, Sqlite> for bool {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> 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)
}
}
+105
View File
@@ -0,0 +1,105 @@
use std::borrow::Cow;
use std::rc::Rc;
use std::sync::Arc;
use crate::arguments::SqliteArgumentsBuffer;
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 Encode<'_, Sqlite> for &'_ [u8] {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Arc::new(self.to_vec())));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for &'r [u8] {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.blob_borrowed())
}
}
impl Encode<'_, Sqlite> for Box<[u8]> {
fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Arc::new(self.into_vec())));
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Arc::new(self.clone().into_vec())));
Ok(IsNull::No)
}
}
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 Encode<'_, Sqlite> for Vec<u8> {
fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Arc::new(self)));
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Arc::new(self.clone())));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for Vec<u8> {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.blob_owned())
}
}
impl Encode<'_, Sqlite> for Cow<'_, [u8]> {
fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Arc::new(self.into())));
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Arc::new(self.to_vec())));
Ok(IsNull::No)
}
}
impl Encode<'_, Sqlite> for Arc<[u8]> {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
<Vec<u8> as Encode<'_, Sqlite>>::encode(self.to_vec(), args)
}
}
impl Encode<'_, Sqlite> for Rc<[u8]> {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
<Vec<u8> as Encode<'_, Sqlite>>::encode(self.to_vec(), args)
}
}
+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, SqliteArgumentsBuffer, 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 SqliteArgumentsBuffer) -> 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 SqliteArgumentsBuffer) -> 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 SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format("%F").to_string(), buf)
}
}
impl Encode<'_, Sqlite> for NaiveTime {
fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> 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_borrowed()?),
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_borrowed()?).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_borrowed()?, "%F")?)
}
}
impl<'r> Decode<'r, Sqlite> for NaiveTime {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
let value = value.text_borrowed()?;
// 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())
}
}
+49
View File
@@ -0,0 +1,49 @@
use crate::arguments::SqliteArgumentsBuffer;
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 Encode<'_, Sqlite> for f32 {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> 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 Encode<'_, Sqlite> for f64 {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> 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()?)
}
}
+107
View File
@@ -0,0 +1,107 @@
use crate::arguments::SqliteArgumentsBuffer;
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 Encode<'_, Sqlite> for i8 {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> 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 Encode<'_, Sqlite> for i16 {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> 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 Encode<'_, Sqlite> for i32 {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> 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 Encode<'_, Sqlite> for i64 {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> 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()?)
}
}
+37
View File
@@ -0,0 +1,37 @@
use serde::{Deserialize, Serialize};
use crate::arguments::SqliteArgumentsBuffer;
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::types::{Json, Type};
use crate::{type_info::DataType, Sqlite, 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 SqliteArgumentsBuffer) -> 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> {
// Saves a pass over the data by making `serde_json` check UTF-8.
Self::decode_from_bytes(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;
+106
View File
@@ -0,0 +1,106 @@
use crate::arguments::SqliteArgumentsBuffer;
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 sqlx_core::database::Database;
use std::borrow::Cow;
use std::rc::Rc;
use std::sync::Arc;
impl Type<Sqlite> for str {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Text)
}
}
impl Encode<'_, Sqlite> for &'_ str {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Arc::new(self.to_string())));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for &'r str {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.text_borrowed()?)
}
}
impl Encode<'_, Sqlite> for Box<str> {
fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::TextSlice(Arc::from(self)));
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Arc::new(self.to_string())));
Ok(IsNull::No)
}
}
impl Type<Sqlite> for String {
fn type_info() -> SqliteTypeInfo {
<&str as Type<Sqlite>>::type_info()
}
}
impl Encode<'_, Sqlite> for String {
fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Arc::new(self)));
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Arc::new(self.clone())));
Ok(IsNull::No)
}
}
impl<'r> Decode<'r, Sqlite> for String {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.text_owned()?)
}
}
impl Encode<'_, Sqlite> for Cow<'_, str> {
fn encode(self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Arc::new(self.into())));
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Arc::new(self.to_string())));
Ok(IsNull::No)
}
}
impl Encode<'_, Sqlite> for Arc<str> {
fn encode(self, args: &mut <Sqlite as Database>::ArgumentBuffer) -> Result<IsNull, BoxDynError>
where
Self: Sized,
{
args.push(SqliteArgumentValue::TextSlice(self));
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
<String as Encode<'_, Sqlite>>::encode(self.to_string(), args)
}
}
impl Encode<'_, Sqlite> for Rc<str> {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
<String as Encode<'_, Sqlite>>::encode(self.to_string(), args)
}
}
+37
View File
@@ -0,0 +1,37 @@
use crate::arguments::SqliteArgumentsBuffer;
use crate::{Sqlite, 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<T> Encode<'_, Sqlite> for Text<T>
where
T: Display,
{
fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> 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> {
Ok(Self(value.with_temp_text(|text| text.parse::<T>())??))
}
}
+319
View File
@@ -0,0 +1,319 @@
use crate::arguments::SqliteArgumentsBuffer;
use crate::value::ValueRef;
use crate::{
decode::Decode,
encode::{Encode, IsNull},
error::BoxDynError,
type_info::DataType,
types::Type,
Sqlite, 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 SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format(&Rfc3339)?, buf)
}
}
impl Encode<'_, Sqlite> for PrimitiveDateTime {
fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> 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 SqliteArgumentsBuffer) -> 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 SqliteArgumentsBuffer) -> 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_borrowed()?,
&fd!("[year]-[month]-[day]"),
)?)
}
}
impl<'r> Decode<'r, Sqlite> for Time {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
let value = value.text_borrowed()?;
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_borrowed()?),
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_borrowed()?).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_borrowed()?),
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_borrowed()?).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")),
]
};
}
+99
View File
@@ -0,0 +1,99 @@
use crate::arguments::SqliteArgumentsBuffer;
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 Encode<'_, Sqlite> for u8 {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> 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 Encode<'_, Sqlite> for u16 {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> 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 Encode<'_, Sqlite> for u32 {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> 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()?)
}
}
+85
View File
@@ -0,0 +1,85 @@
use crate::arguments::SqliteArgumentsBuffer;
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::sync::Arc;
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 Encode<'_, Sqlite> for Uuid {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Arc::new(
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_borrowed()).map_err(Into::into)
}
}
impl Type<Sqlite> for Hyphenated {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Text)
}
}
impl Encode<'_, Sqlite> for Hyphenated {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Arc::new(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_borrowed().map(ToOwned::to_owned)?).map_err(Into::into);
Ok(uuid?.hyphenated())
}
}
impl Type<Sqlite> for Simple {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Text)
}
}
impl Encode<'_, Sqlite> for Simple {
fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Arc::new(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_borrowed().map(ToOwned::to_owned)?).map_err(Into::into);
Ok(uuid?.simple())
}
}
+416
View File
@@ -0,0 +1,416 @@
use std::borrow::Cow;
use std::cell::OnceCell;
use std::ptr::NonNull;
use std::slice;
use std::str;
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,
};
use sqlx_core::type_info::TypeInfo;
pub(crate) use sqlx_core::value::{Value, ValueRef};
use crate::type_info::DataType;
use crate::{Sqlite, SqliteError, SqliteTypeInfo};
/// An owned handle to a [`sqlite3_value`].
///
/// # Note: Decoding is Stateful
/// The [`sqlite3_value` interface][value-methods] reserves the right to be stateful:
///
/// > Other interfaces might change the datatype for an sqlite3_value object.
/// > For example, if the datatype is initially SQLITE_INTEGER and sqlite3_value_text(V) is called
/// > to extract a text value for that integer, then subsequent calls to sqlite3_value_type(V)
/// > might return SQLITE_TEXT. Whether or not a persistent internal datatype conversion occurs is
/// > undefined and may change from one release of SQLite to the next.
///
/// Thus, this type is `!Sync` and [`SqliteValueRef`] is `!Send` and `!Sync` to prevent data races.
///
/// Additionally, this statefulness means that the return values of `sqlite3_value_bytes()` and
/// `sqlite3_value_blob()` could be invalidated by later calls to other `sqlite3_value*` methods.
///
/// To prevent undefined behavior from accessing dangling pointers, this type (and any
/// [`SqliteValueRef`] instances created from it) remembers when it was used to decode a
/// borrowed `&[u8]` or `&str` and returns an error if it is used to decode any other type.
///
/// To bypass this error, you must prove that no outstanding borrows exist.
///
/// This may be done in one of a few ways:
/// * If you hold mutable access, call [`Self::reset_borrow()`] which resets the borrowed state.
/// * If you have an immutable reference, call [`Self::clone()`] to get a new instance
/// with no outstanding borrows.
/// * If you hold a [`SqliteValueRef`], call [`SqliteValueRef::to_owned()`]
/// to get a new `SqliteValue` with no outstanding borrows.
///
/// This is *only* necessary if using the same `SqliteValue` or [`SqliteValueRef`] to decode
/// multiple different types. The vast majority of use-cases employing once-through decoding
/// should not have to worry about this.
///
/// [`sqlite3_value`]: https://www.sqlite.org/c3ref/value.html
/// [value-methods]: https://www.sqlite.org/c3ref/value_blob.html
pub struct SqliteValue(ValueHandle);
/// A borrowed reference to a [`sqlite3_value`].
///
/// Semantically, this behaves as a reference to [`SqliteValue`].
///
/// # Note: Decoding is Stateful
/// See [`SqliteValue`] for details.
pub struct SqliteValueRef<'r>(Cow<'r, ValueHandle>);
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 dup(
value: *mut sqlite3_value,
column_type: Option<SqliteTypeInfo>,
) -> Self {
debug_assert!(!value.is_null());
let handle = ValueHandle::try_dup_of(value, column_type)
.expect("SQLite failed to allocate memory for duplicated value");
Self(handle)
}
/// Prove that there are no outstanding borrows of this instance.
///
/// Call this after decoding a borrowed `&[u8]` or `&str`
/// to reset the internal borrowed state and allow decoding of other types.
pub fn reset_borrow(&mut self) {
self.0.reset_blob_borrow();
}
/// Call [`sqlite3_value_dup()`] to create a new instance of this type.
///
/// Returns an error if the call returns a null pointer, indicating that
/// SQLite was unable to allocate the additional memory required.
///
/// Non-panicking version of [`Self::clone()`].
///
/// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html
pub fn try_clone(&self) -> Result<Self, SqliteError> {
self.0.try_dup().map(Self)
}
}
impl Clone for SqliteValue {
/// Call [`sqlite3_value_dup()`] to create a new instance of this type.
///
/// # Panics
/// If [`sqlite3_value_dup()`] returns a null pointer, indicating an out-of-memory condition.
///
/// See [`Self::try_clone()`] for a non-panicking version.
///
/// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html
fn clone(&self) -> Self {
self.try_clone().expect("failed to clone `SqliteValue`")
}
}
impl Value for SqliteValue {
type Database = Sqlite;
fn as_ref(&self) -> SqliteValueRef<'_> {
SqliteValueRef::value(self)
}
fn type_info(&self) -> Cow<'_, SqliteTypeInfo> {
Cow::Owned(self.0.type_info())
}
fn is_null(&self) -> bool {
self.0.is_null()
}
}
impl<'r> SqliteValueRef<'r> {
/// Attempt to duplicate the internal `sqlite3_value` with [`sqlite3_value_dup()`].
///
/// Returns an error if the call returns a null pointer, indicating that
/// SQLite was unable to allocate the additional memory required.
///
/// Non-panicking version of [`Self::try_to_owned()`].
///
/// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html
pub fn try_to_owned(&self) -> Result<SqliteValue, SqliteError> {
self.0.try_dup().map(SqliteValue)
}
pub(crate) fn value(value: &'r SqliteValue) -> Self {
Self(Cow::Borrowed(&value.0))
}
/// # 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) -> Self {
debug_assert!(!value.is_null());
let handle = ValueHandle::temporary(NonNull::new_unchecked(value));
Self(Cow::Owned(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) -> Result<i64, BorrowedBlobError> {
self.0.int64()
}
pub(super) fn double(&self) -> Result<f64, BorrowedBlobError> {
self.0.double()
}
pub(super) fn blob_borrowed(&self) -> &'r [u8] {
// SAFETY: lifetime is matched to `'r`
unsafe { self.0.blob_borrowed() }
}
pub(super) fn with_temp_blob<R>(&self, op: impl FnOnce(&[u8]) -> R) -> R {
self.0.with_blob(op)
}
pub(super) fn blob_owned(&self) -> Vec<u8> {
self.with_temp_blob(|blob| blob.to_vec())
}
pub(super) fn text_borrowed(&self) -> Result<&'r str, str::Utf8Error> {
// SAFETY: lifetime is matched to `'r`
unsafe { self.0.text_borrowed() }
}
pub(super) fn with_temp_text<R>(
&self,
op: impl FnOnce(&str) -> R,
) -> Result<R, str::Utf8Error> {
self.0.with_blob(|blob| str::from_utf8(blob).map(op))
}
pub(super) fn text_owned(&self) -> Result<String, str::Utf8Error> {
self.with_temp_text(|text| text.to_string())
}
}
impl<'r> ValueRef<'r> for SqliteValueRef<'r> {
type Database = Sqlite;
/// Attempt to duplicate the internal `sqlite3_value` with [`sqlite3_value_dup()`].
///
/// # Panics
/// If [`sqlite3_value_dup()`] returns a null pointer, indicating an out-of-memory condition.
///
/// See [`Self::try_to_owned()`] for a non-panicking version.
///
/// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html
fn to_owned(&self) -> SqliteValue {
SqliteValue(
self.0
.try_dup()
.expect("failed to convert SqliteValueRef to owned SqliteValue"),
)
}
fn type_info(&self) -> Cow<'_, SqliteTypeInfo> {
Cow::Owned(self.0.type_info())
}
fn is_null(&self) -> bool {
self.0.is_null()
}
}
pub(crate) struct ValueHandle {
value: NonNull<sqlite3_value>,
column_type: Option<SqliteTypeInfo>,
// Note: `std::cell` version
borrowed_blob: OnceCell<Blob>,
free_on_drop: bool,
}
struct Blob {
ptr: *const u8,
len: usize,
}
#[derive(Debug, thiserror::Error)]
#[error("given `SqliteValue` was previously decoded as BLOB or TEXT; `SqliteValue::reset_borrow()` must be called first")]
pub(crate) struct BorrowedBlobError;
// SAFE: only protected value objects are stored in SqliteValue
unsafe impl Send for ValueHandle {}
// SAFETY: the `sqlite3_value_*()` methods reserve the right to be stateful,
// which means method calls aren't thread-safe without mutual exclusion.
//
// impl !Sync for ValueHandle {}
impl ValueHandle {
/// # Safety
/// The `sqlite3_value` must be valid and SQLite must not free it. It will be freed on drop.
unsafe fn try_dup_of(
value: *mut sqlite3_value,
column_type: Option<SqliteTypeInfo>,
) -> Result<Self, SqliteError> {
// SAFETY: caller must ensure `value` is valid.
let value =
unsafe { NonNull::new(sqlite3_value_dup(value)).ok_or_else(SqliteError::nomem)? };
Ok(Self {
value,
column_type,
borrowed_blob: OnceCell::new(),
free_on_drop: true,
})
}
fn temporary(value: NonNull<sqlite3_value>) -> Self {
Self {
value,
column_type: None,
borrowed_blob: OnceCell::new(),
free_on_drop: false,
}
}
fn try_dup(&self) -> Result<Self, SqliteError> {
// SAFETY: `value` is initialized
unsafe { Self::try_dup_of(self.value.as_ptr(), self.column_type.clone()) }
}
fn value_type_info(&self) -> SqliteTypeInfo {
SqliteTypeInfo(DataType::from_code(unsafe {
sqlite3_value_type(self.value.as_ptr())
}))
}
fn type_info(&self) -> SqliteTypeInfo {
let value_type = self.value_type_info();
// Assume the actual value type is more accurate, if it's not NULL.
match &self.column_type {
Some(column_type) if value_type.is_null() => column_type.clone(),
_ => value_type,
}
}
fn int64(&self) -> Result<i64, BorrowedBlobError> {
// SAFETY: we have to be certain the caller isn't still holding a borrow from `.blob_borrowed()`
self.assert_blob_not_borrowed()?;
Ok(unsafe { sqlite3_value_int64(self.value.as_ptr()) })
}
fn double(&self) -> Result<f64, BorrowedBlobError> {
// SAFETY: we have to be certain the caller isn't still holding a borrow from `.blob_borrowed()`
self.assert_blob_not_borrowed()?;
Ok(unsafe { sqlite3_value_double(self.value.as_ptr()) })
}
fn is_null(&self) -> bool {
self.value_type_info().is_null()
}
}
impl Clone for ValueHandle {
fn clone(&self) -> Self {
self.try_dup().unwrap()
}
}
impl Drop for ValueHandle {
fn drop(&mut self) {
if self.free_on_drop {
unsafe {
sqlite3_value_free(self.value.as_ptr());
}
}
}
}
impl ValueHandle {
fn assert_blob_not_borrowed(&self) -> Result<(), BorrowedBlobError> {
if self.borrowed_blob.get().is_none() {
Ok(())
} else {
Err(BorrowedBlobError)
}
}
fn reset_blob_borrow(&mut self) {
self.borrowed_blob.take();
}
fn get_blob(&self) -> Option<Blob> {
if let Some(blob) = self.borrowed_blob.get() {
return Some(Blob { ..*blob });
}
// SAFETY: calling `sqlite3_value_bytes` from multiple threads at once is a data race.
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
return None;
}
let ptr = unsafe { sqlite3_value_blob(self.value.as_ptr()) } as *const u8;
debug_assert!(!ptr.is_null());
Some(Blob { ptr, len })
}
fn with_blob<R>(&self, with_blob: impl FnOnce(&[u8]) -> R) -> R {
let Some(blob) = self.get_blob() else {
return with_blob(&[]);
};
// SAFETY: the slice cannot outlive the call
with_blob(unsafe { blob.as_slice() })
}
/// # Safety
/// Caller must ensure lifetime '`b` cannot outlive `self`.
unsafe fn blob_borrowed<'a>(&self) -> &'a [u8] {
let Some(blob) = self.get_blob() else {
return &[];
};
// SAFETY: we need to store that the blob was borrowed
// to prevent
let blob = self.borrowed_blob.get_or_init(|| blob);
unsafe { blob.as_slice() }
}
/// # Safety
/// Caller must ensure lifetime '`b` cannot outlive `self`.
unsafe fn text_borrowed<'b>(&self) -> Result<&'b str, str::Utf8Error> {
let Some(blob) = self.get_blob() else {
return Ok("");
};
// SAFETY: lifetime of `blob` will be tied to `'b`.
let s = str::from_utf8(unsafe { blob.as_slice() })?;
// We only store the borrow after we ensure the string is valid.
self.borrowed_blob.set(blob).ok();
Ok(s)
}
}
impl Blob {
/// # Safety
/// `'a` must not outlive the `sqlite3_value` this blob came from.
unsafe fn as_slice<'a>(&self) -> &'a [u8] {
slice::from_raw_parts(self.ptr, self.len)
}
}