breaking(sqlite): libsqlite3-sys versioning, feature flags, safety changes (#3928)

This commit is contained in:
Austin Bonander
2025-07-17 01:13:32 -07:00
committed by GitHub
parent f7ef1ed1e9
commit 21598cfec6
52 changed files with 1152 additions and 623 deletions

View File

@@ -199,22 +199,6 @@ impl<'a> TryFrom<&'a AnyConnectOptions> for SqliteConnectOptions {
let mut opts_out = SqliteConnectOptions::from_url(&opts.database_url)?;
opts_out.log_settings = opts.log_settings.clone();
if let Some(ref path) = opts.enable_config {
if path.exists() {
let config = match sqlx_core::config::Config::try_from_path(path.to_path_buf()) {
Ok(cfg) => cfg,
Err(sqlx_core::config::ConfigError::NotFound { path: _ }) => {
return Ok(opts_out)
}
Err(err) => return Err(Self::Error::ConfigFile(err)),
};
for extension in config.common.drivers.sqlite.load_extensions.iter() {
opts_out = opts_out.extension(extension.to_owned());
}
}
}
Ok(opts_out)
}
}

View File

@@ -26,6 +26,7 @@ impl SqliteConnection {
/// * [`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()?;
@@ -59,6 +60,7 @@ impl SqliteConnection {
///
/// [`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>,

View File

@@ -2,52 +2,34 @@ use crate::connection::handle::ConnectionHandle;
use crate::connection::LogSettings;
use crate::connection::{ConnectionState, Statements};
use crate::error::Error;
use crate::{SqliteConnectOptions, SqliteError};
use crate::SqliteConnectOptions;
use libsqlite3_sys::{
sqlite3, sqlite3_busy_timeout, sqlite3_db_config, sqlite3_extended_result_codes, sqlite3_free,
sqlite3_load_extension, sqlite3_open_v2, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_OK,
SQLITE_OPEN_CREATE, SQLITE_OPEN_FULLMUTEX, SQLITE_OPEN_MEMORY, SQLITE_OPEN_NOMUTEX,
SQLITE_OPEN_PRIVATECACHE, SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_SHAREDCACHE,
SQLITE_OPEN_URI,
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 sqlx_core::IndexMap;
use std::collections::BTreeMap;
use std::ffi::{c_void, CStr, CString};
use std::ffi::CString;
use std::io;
use std::os::raw::c_int;
use std::ptr::{addr_of_mut, null, null_mut};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
#[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);
#[derive(Copy, Clone)]
enum SqliteLoadExtensionMode {
/// Enables only the C-API, leaving the SQL function disabled.
Enable,
/// Disables both the C-API and the SQL function.
DisableAll,
}
impl SqliteLoadExtensionMode {
fn to_int(self) -> c_int {
match self {
SqliteLoadExtensionMode::Enable => 1,
SqliteLoadExtensionMode::DisableAll => 0,
}
}
}
pub struct EstablishParams {
filename: CString,
open_flags: i32,
busy_timeout: Duration,
statement_cache_capacity: usize,
log_settings: LogSettings,
#[cfg(feature = "load-extension")]
extensions: IndexMap<CString, Option<CString>>,
pub(crate) thread_name: String,
pub(crate) command_channel_size: usize,
@@ -124,6 +106,7 @@ impl EstablishParams {
)
})?;
#[cfg(feature = "load-extension")]
let extensions = options
.extensions
.iter()
@@ -159,6 +142,7 @@ impl EstablishParams {
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,
@@ -167,109 +151,19 @@ impl EstablishParams {
})
}
// Enable extension loading via the db_config function, as recommended by the docs rather
// than the more obvious `sqlite3_enable_load_extension`
// https://www.sqlite.org/c3ref/db_config.html
// https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension
unsafe fn sqlite3_set_load_extension(
db: *mut sqlite3,
mode: SqliteLoadExtensionMode,
) -> Result<(), Error> {
let status = sqlite3_db_config(
db,
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
mode.to_int(),
null::<i32>(),
);
if status != SQLITE_OK {
return Err(Error::Database(Box::new(SqliteError::new(db))));
}
Ok(())
}
pub(crate) fn establish(&self) -> Result<ConnectionState, Error> {
let mut handle = null_mut();
let mut handle = ConnectionHandle::open(&self.filename, self.open_flags)?;
// <https://www.sqlite.org/c3ref/open.html>
let mut status = unsafe {
sqlite3_open_v2(self.filename.as_ptr(), &mut handle, self.open_flags, null())
};
if handle.is_null() {
// Failed to allocate memory
return Err(Error::Io(io::Error::new(
io::ErrorKind::OutOfMemory,
"SQLite is unable to allocate memory to hold the sqlite3 object",
)));
}
// SAFE: tested for NULL just above
// This allows any returns below to close this handle with RAII
let mut handle = unsafe { ConnectionHandle::new(handle) };
if status != SQLITE_OK {
return Err(Error::Database(Box::new(handle.expect_error())));
}
// Enable extended result codes
// https://www.sqlite.org/c3ref/extended_result_codes.html
#[cfg(feature = "load-extension")]
unsafe {
// NOTE: ignore the failure here
sqlite3_extended_result_codes(handle.as_ptr(), 1);
}
if !self.extensions.is_empty() {
// Enable loading extensions
unsafe {
Self::sqlite3_set_load_extension(handle.as_ptr(), SqliteLoadExtensionMode::Enable)?;
}
for ext in self.extensions.iter() {
// `sqlite3_load_extension` is unusual as it returns its errors via an out-pointer
// rather than by calling `sqlite3_errmsg`
let mut error_msg = null_mut();
status = unsafe {
sqlite3_load_extension(
handle.as_ptr(),
ext.0.as_ptr(),
ext.1.as_ref().map_or(null(), |e| e.as_ptr()),
addr_of_mut!(error_msg),
)
};
if status != SQLITE_OK {
let mut e = handle.expect_error();
// SAFETY: We become responsible for any memory allocation at `&error`, so test
// for null and take an RAII version for returns
if !error_msg.is_null() {
e = e.with_message(unsafe {
let msg = CStr::from_ptr(error_msg).to_string_lossy().into();
sqlite3_free(error_msg as *mut c_void);
msg
});
}
return Err(Error::Database(Box::new(e)));
}
} // Preempt any hypothetical security issues arising from leaving ENABLE_LOAD_EXTENSION
// on by disabling the flag again once we've loaded all the requested modules.
// Fail-fast (via `?`) if disabling the extension loader didn't work for some reason,
// avoids an unexpected state going undetected.
unsafe {
Self::sqlite3_set_load_extension(
handle.as_ptr(),
SqliteLoadExtensionMode::DisableAll,
)?;
}
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 != SQLITE_OK {
if status != libsqlite3_sys::SQLITE_OK {
return Err(Error::Database(Box::new(handle.expect_error())));
}
}
@@ -282,11 +176,7 @@ impl EstablishParams {
let ms = i32::try_from(self.busy_timeout.as_millis())
.expect("Given busy timeout value is too big.");
status = unsafe { sqlite3_busy_timeout(handle.as_ptr(), ms) };
if status != SQLITE_OK {
return Err(Error::Database(Box::new(handle.expect_error())));
}
handle.call_with_result(|db| unsafe { sqlite3_busy_timeout(db, ms) })?;
Ok(ConnectionState {
handle,
@@ -300,4 +190,77 @@ impl EstablishParams {
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)
}
}

View File

@@ -1,17 +1,17 @@
use std::ffi::CString;
use std::ptr;
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_last_insert_rowid, SQLITE_LOCKED_SHAREDCACHE,
SQLITE_OK,
sqlite3, sqlite3_close, sqlite3_exec, sqlite3_extended_result_codes, sqlite3_last_insert_rowid,
sqlite3_open_v2, SQLITE_OK,
};
use crate::{statement::unlock_notify, SqliteError};
use crate::SqliteError;
/// Managed handle to the raw SQLite3 database handle.
/// The database handle will be closed when this is dropped and no `ConnectionHandleRef`s exist.
/// Managed SQLite3 database handle.
/// The database handle will be closed when this is dropped.
#[derive(Debug)]
pub(crate) struct ConnectionHandle(NonNull<sqlite3>);
@@ -19,17 +19,43 @@ pub(crate) struct ConnectionHandle(NonNull<sqlite3>);
// one is accessing it at the same time. This is upheld as long as [SQLITE_CONFIG_MULTITHREAD] is
// enabled and [SQLITE_THREADSAFE] was enabled when sqlite was compiled. We refuse to work
// if these conditions are not upheld.
//
// <https://www.sqlite.org/c3ref/threadsafe.html>
// <https://www.sqlite.org/c3ref/c_config_covering_index_scan.html#sqliteconfigmultithread>
unsafe impl Send for ConnectionHandle {}
impl ConnectionHandle {
#[inline]
pub(super) unsafe fn new(ptr: *mut sqlite3) -> Self {
Self(NonNull::new_unchecked(ptr))
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]
@@ -41,6 +67,17 @@ impl ConnectionHandle {
self.0
}
pub(crate) fn call_with_result(
&mut self,
call: impl FnOnce(*mut sqlite3) -> c_int,
) -> Result<(), SqliteError> {
if call(self.as_ptr()) == SQLITE_OK {
Ok(())
} else {
Err(self.expect_error())
}
}
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()) }
@@ -63,6 +100,7 @@ impl ConnectionHandle {
// 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(),
@@ -77,7 +115,10 @@ impl ConnectionHandle {
match status {
SQLITE_OK => return Ok(()),
SQLITE_LOCKED_SHAREDCACHE => unlock_notify::wait(self.as_ptr())?,
#[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()),
}
}

View File

@@ -40,7 +40,9 @@ mod handle;
pub(crate) mod intmap;
#[cfg(feature = "preupdate-hook")]
mod preupdate_hook;
pub(crate) mod serialize;
#[cfg(feature = "deserialize")]
pub(crate) mod deserialize;
mod worker;

View File

@@ -1,10 +1,9 @@
use super::SqliteOperation;
use crate::type_info::DataType;
use crate::{SqliteError, SqliteTypeInfo, SqliteValueRef};
use crate::{SqliteError, SqliteValueRef};
use libsqlite3_sys::{
sqlite3, sqlite3_preupdate_count, sqlite3_preupdate_depth, sqlite3_preupdate_new,
sqlite3_preupdate_old, sqlite3_value, sqlite3_value_type, SQLITE_OK,
sqlite3_preupdate_old, sqlite3_value, SQLITE_OK,
};
use std::ffi::CStr;
use std::marker::PhantomData;
@@ -122,9 +121,7 @@ impl<'a> PreupdateHookResult<'a> {
if ret != SQLITE_OK {
return Err(PreupdateError::Database(SqliteError::new(self.db)));
}
let data_type = DataType::from_code(sqlite3_value_type(p_value));
// SAFETY: SQLite will free the sqlite3_value when the callback returns
Ok(SqliteValueRef::borrowed(p_value, SqliteTypeInfo(data_type)))
Ok(SqliteValueRef::borrowed(p_value))
}
}

View File

@@ -21,7 +21,8 @@ use crate::connection::execute;
use crate::connection::ConnectionState;
use crate::{Sqlite, SqliteArguments, SqliteQueryResult, SqliteRow, SqliteStatement};
use super::serialize::{deserialize, serialize, SchemaName, SqliteOwnedBuf};
#[cfg(feature = "deserialize")]
use crate::connection::deserialize::{deserialize, serialize, SchemaName, SqliteOwnedBuf};
// Each SQLite connection has a dedicated thread.
@@ -67,10 +68,12 @@ enum Command {
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,
@@ -302,9 +305,11 @@ impl ConnectionWorker {
}
}
}
#[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();
}
@@ -397,6 +402,7 @@ impl ConnectionWorker {
self.oneshot_cmd(|tx| Command::Ping { tx }).await
}
#[cfg(feature = "deserialize")]
pub(crate) async fn deserialize(
&mut self,
schema: Option<SchemaName>,
@@ -412,6 +418,7 @@ impl ConnectionWorker {
.await?
}
#[cfg(feature = "deserialize")]
pub(crate) async fn serialize(
&mut self,
schema: Option<SchemaName>,

View File

@@ -7,7 +7,7 @@ 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_CONSTRAINT_UNIQUE, SQLITE_ERROR, SQLITE_NOMEM,
};
pub(crate) use sqlx_core::error::*;
@@ -49,11 +49,13 @@ impl SqliteError {
}
/// 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);
@@ -72,12 +74,18 @@ impl SqliteError {
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 {

View File

@@ -1,38 +1,68 @@
//! **SQLite** database driver.
//!
//! ### Note: linkage is semver-exempt.
//! ### Note: `libsqlite3-sys` Version
//! This driver uses the `libsqlite3-sys` crate which links the native library for SQLite 3.
//! With the "sqlite" feature, we enable the `bundled` feature which builds and links SQLite from
//! source.
//! Only one version of `libsqlite3-sys` may appear in the dependency tree of your project.
//!
//! We reserve the right to upgrade the version of `libsqlite3-sys` as necessary to pick up new
//! `3.x.y` versions of SQLite.
//! 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.
//!
//! Due to Cargo's requirement that only one version of a crate that links a given native library
//! exists in the dependency graph at a time, using SQLx alongside another crate linking
//! `libsqlite3-sys` like `rusqlite` is a semver hazard.
//! If you are using `rusqlite` or any other crate that indirectly depends on `libsqlite3-sys`,
//! this should allow Cargo to select a compatible version.
//!
//! If you are doing so, we recommend pinning the version of both SQLx and the other crate you're
//! using to prevent a `cargo update` from breaking things, e.g.:
//! 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
//! sqlx = { version = "=0.8.1", features = ["sqlite"] }
//! rusqlite = "=0.32.1"
//! [dependencies]
//! # for example, if 0.35.0 breaks the build
//! libsqlite3-sys = "0.34"
//! ```
//!
//! and then upgrade these crates in lockstep when necessary.
//! ### 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 a system SQLite library, the "sqlite-unbundled" feature can be used
//! 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](https://github.com/rusqlite/rusqlite?tab=readme-ov-file#notes-on-building-rusqlite-and-libsqlite3-sys)
//! for details).
//! 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
//!
//! It may result in link errors if the SQLite version is too old. Version `3.20.0` or newer is
//! recommended. It can increase build time due to the use of bindgen.
// SQLite is a C library. All interactions require FFI which is unsafe.
// All unsafe blocks should have comments pointing to SQLite docs and ensuring that we maintain
@@ -46,8 +76,11 @@ use std::sync::atomic::AtomicBool;
pub use arguments::{SqliteArgumentValue, SqliteArguments};
pub use column::SqliteColumn;
pub use connection::serialize::SqliteOwnedBuf;
#[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;
@@ -57,7 +90,6 @@ pub use options::{
};
pub use query_result::SqliteQueryResult;
pub use row::SqliteRow;
use sqlx_core::sql_str::{AssertSqlSafe, SqlSafeStr};
pub use statement::SqliteStatement;
pub use transaction::SqliteTransactionManager;
pub use type_info::SqliteTypeInfo;
@@ -67,9 +99,11 @@ 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;
@@ -127,18 +161,14 @@ pub static CREATE_DB_WAL: AtomicBool = AtomicBool::new(true);
/// UNSTABLE: for use by `sqlite-macros-core` only.
#[doc(hidden)]
pub fn describe_blocking(query: &str, database_url: &str) -> Result<Describe<Sqlite>, Error> {
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()?;
match sqlx_core::config::Config::try_from_crate_or_default() {
Ok(config) => {
for extension in config.common.drivers.sqlite.load_extensions.iter() {
opts = opts.extension(extension.to_owned());
}
}
Err(sqlx_core::config::ConfigError::NotFound { path: _ }) => {}
Err(err) => return Err(Error::ConfigFile(err)),
}
opts = opts.apply_driver_config(&driver_config.sqlite)?;
let params = EstablishParams::from_options(&opts)?;
let mut conn = params.establish()?;

View File

@@ -1,5 +1,6 @@
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;
@@ -57,6 +58,13 @@ impl ConnectOptions for SqliteConnectOptions {
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 {

View File

@@ -18,7 +18,7 @@ pub use synchronous::SqliteSynchronous;
use crate::common::DebugFn;
use crate::connection::collation::Collation;
use sqlx_core::IndexMap;
use sqlx_core::{config, IndexMap};
/// Options and flags which can be used to configure a SQLite connection.
///
@@ -72,10 +72,12 @@ pub struct SqliteConnectOptions {
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,
@@ -203,6 +205,7 @@ impl SqliteConnectOptions {
immutable: false,
vfs: None,
pragmas,
#[cfg(feature = "load-extension")]
extensions: Default::default(),
collations: Default::default(),
serialized: false,
@@ -465,35 +468,94 @@ impl SqliteConnectOptions {
self
}
/// Load an [extension](https://www.sqlite.org/loadext.html) at run-time when the database connection
/// is established, using the default entry point.
/// 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 specify the entry point, use [`extension_with_entrypoint`][`Self::extension_with_entrypoint`] instead.
/// 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 the method repeatedly on the options struct, they
/// will be loaded in the order they are added.
/// 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 options = SqliteConnectOptions::from_str("sqlite://data.db")?
/// .extension("vsv")
/// .extension("mod_spatialite");
/// let mut options = SqliteConnectOptions::from_str("sqlite://data.db")?;
///
/// // SAFETY: these are trusted extensions.
/// unsafe {
/// options = options
/// .extension("vsv")
/// .extension("mod_spatialite");
/// }
///
/// # Ok(options)
/// # }
/// ```
pub fn extension(mut self, extension_name: impl Into<Cow<'static, str>>) -> Self {
#[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
}
/// Load an extension with a specified entry point.
/// Add a [SQLite extension](https://www.sqlite.org/loadext.html) to be loaded into the database
/// connection at startup, overriding the entrypoint.
///
/// Useful when using non-standard extensions, or when developing your own, the second argument
/// specifies where SQLite should expect to find the extension init routine.
pub fn extension_with_entrypoint(
/// 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>>,
@@ -575,4 +637,31 @@ impl SqliteConnectOptions {
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
self = unsafe { self.extension(extension.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)
}
}

View File

@@ -40,7 +40,7 @@ impl SqliteRow {
values.push(unsafe {
let raw = statement.column_value(i);
SqliteValue::new(raw, columns[i].type_info.clone())
SqliteValue::dup(raw, Some(columns[i].type_info.clone()))
});
}

View File

@@ -13,8 +13,8 @@ use libsqlite3_sys::{
sqlite3_column_name, sqlite3_column_origin_name, sqlite3_column_table_name,
sqlite3_column_type, sqlite3_column_value, sqlite3_db_handle, sqlite3_finalize, sqlite3_reset,
sqlite3_sql, sqlite3_step, sqlite3_stmt, sqlite3_stmt_readonly, sqlite3_table_column_metadata,
sqlite3_value, SQLITE_DONE, SQLITE_LOCKED_SHAREDCACHE, SQLITE_MISUSE, SQLITE_OK, SQLITE_ROW,
SQLITE_TRANSIENT, SQLITE_UTF8,
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};
@@ -24,8 +24,6 @@ use std::slice::from_raw_parts;
use std::str::{from_utf8, from_utf8_unchecked};
use std::sync::Arc;
use super::unlock_notify;
#[derive(Debug)]
pub(crate) struct StatementHandle(NonNull<sqlite3_stmt>);
@@ -393,15 +391,17 @@ impl StatementHandle {
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!"),
SQLITE_LOCKED_SHAREDCACHE => {
#[cfg(feature = "unlock-notify")]
libsqlite3_sys::SQLITE_LOCKED_SHAREDCACHE => {
// The shared cache is locked by another connection. Wait for unlock
// notification and try again.
unlock_notify::wait(self.db_handle())?;
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());

View File

@@ -9,6 +9,8 @@ use std::sync::Arc;
pub(crate) use sqlx_core::statement::*;
mod handle;
#[cfg(feature = "unlock-notify")]
pub(super) mod unlock_notify;
mod r#virtual;

View File

@@ -28,6 +28,6 @@ impl<'q> Encode<'q, Sqlite> for bool {
impl<'r> Decode<'r, Sqlite> for bool {
fn decode(value: SqliteValueRef<'r>) -> Result<bool, BoxDynError> {
Ok(value.int64() != 0)
Ok(value.int64()? != 0)
}
}

View File

@@ -32,7 +32,7 @@ impl<'q> Encode<'q, Sqlite> for &'q [u8] {
impl<'r> Decode<'r, Sqlite> for &'r [u8] {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.blob())
Ok(value.blob_borrowed())
}
}
@@ -84,7 +84,7 @@ impl<'q> Encode<'q, Sqlite> for Vec<u8> {
impl<'r> Decode<'r, Sqlite> for Vec<u8> {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.blob().to_owned())
Ok(value.blob_owned())
}
}

View File

@@ -108,9 +108,9 @@ impl<'r> Decode<'r, Sqlite> for DateTime<FixedOffset> {
fn decode_datetime(value: SqliteValueRef<'_>) -> Result<DateTime<FixedOffset>, BoxDynError> {
let dt = match value.type_info().0 {
DataType::Text => decode_datetime_from_text(value.text()?),
DataType::Int4 | DataType::Integer => decode_datetime_from_int(value.int64()),
DataType::Float => decode_datetime_from_float(value.double()),
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,
};
@@ -118,7 +118,7 @@ fn decode_datetime(value: SqliteValueRef<'_>) -> Result<DateTime<FixedOffset>, B
if let Some(dt) = dt {
Ok(dt)
} else {
Err(format!("invalid datetime: {}", value.text()?).into())
Err(format!("invalid datetime: {}", value.text_borrowed()?).into())
}
}
@@ -191,13 +191,13 @@ impl<'r> Decode<'r, Sqlite> for NaiveDateTime {
impl<'r> Decode<'r, Sqlite> for NaiveDate {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(NaiveDate::parse_from_str(value.text()?, "%F")?)
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()?;
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

View File

@@ -26,7 +26,7 @@ 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)
Ok(value.double()? as f32)
}
}
@@ -49,6 +49,6 @@ impl<'q> Encode<'q, Sqlite> for f64 {
impl<'r> Decode<'r, Sqlite> for f64 {
fn decode(value: SqliteValueRef<'r>) -> Result<f64, BoxDynError> {
Ok(value.double())
Ok(value.double()?)
}
}

View File

@@ -32,7 +32,7 @@ impl<'r> Decode<'r, Sqlite> for i8 {
// 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()?)
Ok(value.int64()?.try_into()?)
}
}
@@ -59,7 +59,7 @@ impl<'q> Encode<'q, Sqlite> for i16 {
impl<'r> Decode<'r, Sqlite> for i16 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64().try_into()?)
Ok(value.int64()?.try_into()?)
}
}
@@ -86,7 +86,7 @@ impl<'q> Encode<'q, Sqlite> for i32 {
impl<'r> Decode<'r, Sqlite> for i32 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64().try_into()?)
Ok(value.int64()?.try_into()?)
}
}
@@ -113,6 +113,6 @@ impl<'q> Encode<'q, Sqlite> for i64 {
impl<'r> Decode<'r, Sqlite> for i64 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64())
Ok(value.int64()?)
}
}

View File

@@ -30,6 +30,7 @@ where
T: 'r + Deserialize<'r>,
{
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Self::decode_from_string(Decode::<Sqlite>::decode(value)?)
// Saves a pass over the data by making `serde_json` check UTF-8.
Self::decode_from_bytes(Decode::<Sqlite>::decode(value)?)
}
}

View File

@@ -28,7 +28,7 @@ impl<'q> Encode<'q, Sqlite> for &'q str {
impl<'r> Decode<'r, Sqlite> for &'r str {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
value.text()
Ok(value.text_borrowed()?)
}
}
@@ -76,7 +76,7 @@ impl<'q> Encode<'q, Sqlite> for String {
impl<'r> Decode<'r, Sqlite> for String {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
value.text().map(ToOwned::to_owned)
Ok(value.text_owned()?)
}
}

View File

@@ -31,7 +31,6 @@ where
BoxDynError: From<<T as FromStr>::Err>,
{
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
let s: &str = Decode::<Sqlite>::decode(value)?;
Ok(Self(s.parse()?))
Ok(Self(value.with_temp_text(|text| text.parse::<T>())??))
}
}

View File

@@ -95,13 +95,16 @@ impl<'r> Decode<'r, Sqlite> for PrimitiveDateTime {
impl<'r> Decode<'r, Sqlite> for Date {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(Date::parse(value.text()?, &fd!("[year]-[month]-[day]"))?)
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()?;
let value = value.text_borrowed()?;
let sqlite_time_formats = &[
fd!("[hour]:[minute]:[second].[subsecond]"),
@@ -121,9 +124,9 @@ impl<'r> Decode<'r, Sqlite> for Time {
fn decode_offset_datetime(value: SqliteValueRef<'_>) -> Result<OffsetDateTime, BoxDynError> {
let dt = match value.type_info().0 {
DataType::Text => decode_offset_datetime_from_text(value.text()?),
DataType::Text => decode_offset_datetime_from_text(value.text_borrowed()?),
DataType::Int4 | DataType::Integer => {
Some(OffsetDateTime::from_unix_timestamp(value.int64())?)
Some(OffsetDateTime::from_unix_timestamp(value.int64()?)?)
}
_ => None,
@@ -132,7 +135,7 @@ fn decode_offset_datetime(value: SqliteValueRef<'_>) -> Result<OffsetDateTime, B
if let Some(dt) = dt {
Ok(dt)
} else {
Err(format!("invalid offset datetime: {}", value.text()?).into())
Err(format!("invalid offset datetime: {}", value.text_borrowed()?).into())
}
}
@@ -154,9 +157,9 @@ fn decode_offset_datetime_from_text(value: &str) -> Option<OffsetDateTime> {
fn decode_datetime(value: SqliteValueRef<'_>) -> Result<PrimitiveDateTime, BoxDynError> {
let dt = match value.type_info().0 {
DataType::Text => decode_datetime_from_text(value.text()?),
DataType::Text => decode_datetime_from_text(value.text_borrowed()?),
DataType::Int4 | DataType::Integer => {
let parsed = OffsetDateTime::from_unix_timestamp(value.int64()).unwrap();
let parsed = OffsetDateTime::from_unix_timestamp(value.int64()?).unwrap();
Some(PrimitiveDateTime::new(parsed.date(), parsed.time()))
}
@@ -166,7 +169,7 @@ fn decode_datetime(value: SqliteValueRef<'_>) -> Result<PrimitiveDateTime, BoxDy
if let Some(dt) = dt {
Ok(dt)
} else {
Err(format!("invalid datetime: {}", value.text()?).into())
Err(format!("invalid datetime: {}", value.text_borrowed()?).into())
}
}

View File

@@ -32,7 +32,7 @@ impl<'r> Decode<'r, Sqlite> for u8 {
// 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()?)
Ok(value.int64()?.try_into()?)
}
}
@@ -59,7 +59,7 @@ impl<'q> Encode<'q, Sqlite> for u16 {
impl<'r> Decode<'r, Sqlite> for u16 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64().try_into()?)
Ok(value.int64()?.try_into()?)
}
}
@@ -86,7 +86,7 @@ impl<'q> Encode<'q, Sqlite> for u32 {
impl<'r> Decode<'r, Sqlite> for u32 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64().try_into()?)
Ok(value.int64()?.try_into()?)
}
}
@@ -102,6 +102,6 @@ impl Type<Sqlite> for u64 {
impl<'r> Decode<'r, Sqlite> for u64 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int64().try_into()?)
Ok(value.int64()?.try_into()?)
}
}

View File

@@ -36,7 +36,7 @@ impl<'q> Encode<'q, Sqlite> for Uuid {
impl Decode<'_, Sqlite> for Uuid {
fn decode(value: SqliteValueRef<'_>) -> Result<Self, BoxDynError> {
// construct a Uuid from the returned bytes
Uuid::from_slice(value.blob()).map_err(Into::into)
Uuid::from_slice(value.blob_borrowed()).map_err(Into::into)
}
}
@@ -60,7 +60,7 @@ impl<'q> Encode<'q, Sqlite> for Hyphenated {
impl Decode<'_, Sqlite> for Hyphenated {
fn decode(value: SqliteValueRef<'_>) -> Result<Self, BoxDynError> {
let uuid: Result<Uuid, BoxDynError> =
Uuid::parse_str(&value.text().map(ToOwned::to_owned)?).map_err(Into::into);
Uuid::parse_str(&value.text_borrowed().map(ToOwned::to_owned)?).map_err(Into::into);
Ok(uuid?.hyphenated())
}
@@ -86,7 +86,7 @@ impl<'q> Encode<'q, Sqlite> for Simple {
impl Decode<'_, Sqlite> for Simple {
fn decode(value: SqliteValueRef<'_>) -> Result<Self, BoxDynError> {
let uuid: Result<Uuid, BoxDynError> =
Uuid::parse_str(&value.text().map(ToOwned::to_owned)?).map_err(Into::into);
Uuid::parse_str(&value.text_borrowed().map(ToOwned::to_owned)?).map_err(Into::into);
Ok(uuid?.simple())
}

View File

@@ -1,207 +1,108 @@
use std::borrow::Cow;
use std::marker::PhantomData;
use std::cell::OnceCell;
use std::ptr::NonNull;
use std::slice::from_raw_parts;
use std::str::from_utf8;
use std::sync::Arc;
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, SQLITE_NULL,
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::error::BoxDynError;
use crate::type_info::DataType;
use crate::{Sqlite, SqliteTypeInfo};
use crate::{Sqlite, SqliteError, SqliteTypeInfo};
enum SqliteValueData<'r> {
Value(&'r SqliteValue),
BorrowedHandle(ValueHandle<'r>),
}
/// 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);
pub struct SqliteValueRef<'r>(SqliteValueData<'r>);
impl<'r> SqliteValueRef<'r> {
pub(crate) fn value(value: &'r SqliteValue) -> Self {
Self(SqliteValueData::Value(value))
}
// SAFETY: The supplied sqlite3_value must not be null and SQLite must free it. It will not be freed on drop.
// The lifetime on this struct should tie it to whatever scope it's valid for before SQLite frees it.
#[allow(unused)]
pub(crate) unsafe fn borrowed(value: *mut sqlite3_value, type_info: SqliteTypeInfo) -> Self {
debug_assert!(!value.is_null());
let handle = ValueHandle::new_borrowed(NonNull::new_unchecked(value), type_info);
Self(SqliteValueData::BorrowedHandle(handle))
}
// NOTE: `int()` is deliberately omitted because it will silently truncate a wider value,
// which is likely to cause bugs:
// https://github.com/launchbadge/sqlx/issues/3179
// (Similar bug in Postgres): https://github.com/launchbadge/sqlx/issues/3161
pub(super) fn int64(&self) -> i64 {
match &self.0 {
SqliteValueData::Value(v) => v.0.int64(),
SqliteValueData::BorrowedHandle(v) => v.int64(),
}
}
pub(super) fn double(&self) -> f64 {
match &self.0 {
SqliteValueData::Value(v) => v.0.double(),
SqliteValueData::BorrowedHandle(v) => v.double(),
}
}
pub(super) fn blob(&self) -> &'r [u8] {
match &self.0 {
SqliteValueData::Value(v) => v.0.blob(),
SqliteValueData::BorrowedHandle(v) => v.blob(),
}
}
pub(super) fn text(&self) -> Result<&'r str, BoxDynError> {
match &self.0 {
SqliteValueData::Value(v) => v.0.text(),
SqliteValueData::BorrowedHandle(v) => v.text(),
}
}
}
impl<'r> ValueRef<'r> for SqliteValueRef<'r> {
type Database = Sqlite;
fn to_owned(&self) -> SqliteValue {
match &self.0 {
SqliteValueData::Value(v) => (*v).clone(),
SqliteValueData::BorrowedHandle(v) => unsafe {
SqliteValue::new(v.value.as_ptr(), v.type_info.clone())
},
}
}
fn type_info(&self) -> Cow<'_, SqliteTypeInfo> {
match &self.0 {
SqliteValueData::Value(v) => v.type_info(),
SqliteValueData::BorrowedHandle(v) => v.type_info(),
}
}
fn is_null(&self) -> bool {
match &self.0 {
SqliteValueData::Value(v) => v.is_null(),
SqliteValueData::BorrowedHandle(v) => v.is_null(),
}
}
}
#[derive(Clone)]
pub struct SqliteValue(Arc<ValueHandle<'static>>);
pub(crate) struct ValueHandle<'a> {
value: NonNull<sqlite3_value>,
type_info: SqliteTypeInfo,
free_on_drop: bool,
_sqlite_value_lifetime: PhantomData<&'a ()>,
}
// SAFE: only protected value objects are stored in SqliteValue
unsafe impl Send for ValueHandle<'_> {}
unsafe impl Sync for ValueHandle<'_> {}
impl ValueHandle<'static> {
fn new_owned(value: NonNull<sqlite3_value>, type_info: SqliteTypeInfo) -> Self {
Self {
value,
type_info,
free_on_drop: true,
_sqlite_value_lifetime: PhantomData,
}
}
}
impl ValueHandle<'_> {
fn new_borrowed(value: NonNull<sqlite3_value>, type_info: SqliteTypeInfo) -> Self {
Self {
value,
type_info,
free_on_drop: false,
_sqlite_value_lifetime: PhantomData,
}
}
fn type_info_opt(&self) -> Option<SqliteTypeInfo> {
let dt = DataType::from_code(unsafe { sqlite3_value_type(self.value.as_ptr()) });
if let DataType::Null = dt {
None
} else {
Some(SqliteTypeInfo(dt))
}
}
fn int64(&self) -> i64 {
unsafe { sqlite3_value_int64(self.value.as_ptr()) }
}
fn double(&self) -> f64 {
unsafe { sqlite3_value_double(self.value.as_ptr()) }
}
fn blob<'b>(&self) -> &'b [u8] {
let len = unsafe { sqlite3_value_bytes(self.value.as_ptr()) };
// This likely means UB in SQLite itself or our usage of it;
// signed integer overflow is UB in the C standard.
let len = usize::try_from(len).unwrap_or_else(|_| {
panic!("sqlite3_value_bytes() returned value out of range for usize: {len}")
});
if len == 0 {
// empty blobs are NULL so just return an empty slice
return &[];
}
let ptr = unsafe { sqlite3_value_blob(self.value.as_ptr()) } as *const u8;
debug_assert!(!ptr.is_null());
unsafe { from_raw_parts(ptr, len) }
}
fn text<'b>(&self) -> Result<&'b str, BoxDynError> {
Ok(from_utf8(self.blob())?)
}
fn type_info(&self) -> Cow<'_, SqliteTypeInfo> {
self.type_info_opt()
.map(Cow::Owned)
.unwrap_or(Cow::Borrowed(&self.type_info))
}
fn is_null(&self) -> bool {
unsafe { sqlite3_value_type(self.value.as_ptr()) == SQLITE_NULL }
}
}
impl Drop for ValueHandle<'_> {
fn drop(&mut self) {
if self.free_on_drop {
unsafe {
sqlite3_value_free(self.value.as_ptr());
}
}
}
}
/// 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 new(value: *mut sqlite3_value, type_info: SqliteTypeInfo) -> Self {
pub(crate) unsafe fn dup(
value: *mut sqlite3_value,
column_type: Option<SqliteTypeInfo>,
) -> Self {
debug_assert!(!value.is_null());
let handle =
ValueHandle::new_owned(NonNull::new_unchecked(sqlite3_value_dup(value)), type_info);
Self(Arc::new(handle))
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`")
}
}
@@ -213,7 +114,7 @@ impl Value for SqliteValue {
}
fn type_info(&self) -> Cow<'_, SqliteTypeInfo> {
self.0.type_info()
Cow::Owned(self.0.type_info())
}
fn is_null(&self) -> bool {
@@ -221,24 +122,291 @@ impl Value for SqliteValue {
}
}
// #[cfg(feature = "any")]
// impl<'r> From<SqliteValueRef<'r>> for crate::any::AnyValueRef<'r> {
// #[inline]
// fn from(value: SqliteValueRef<'r>) -> Self {
// crate::any::AnyValueRef {
// type_info: value.type_info().clone().into_owned().into(),
// kind: crate::any::value::AnyValueRefKind::Sqlite(value),
// }
// }
// }
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.
//
// #[cfg(feature = "any")]
// impl From<SqliteValue> for crate::any::AnyValue {
// #[inline]
// fn from(value: SqliteValue) -> Self {
// crate::any::AnyValue {
// type_info: value.type_info().clone().into_owned().into(),
// kind: crate::any::value::AnyValueKind::Sqlite(value),
// }
// }
// }
// 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 type_info(&self) -> SqliteTypeInfo {
let value_type = SqliteTypeInfo(DataType::from_code(unsafe {
sqlite3_value_type(self.value.as_ptr())
}));
// 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.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)
}
}