mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-26 12:00:26 +00:00
breaking(sqlite): libsqlite3-sys versioning, feature flags, safety changes (#3928)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>())??))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user