mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-01-20 15:46:30 +00:00
417 lines
14 KiB
Rust
417 lines
14 KiB
Rust
use std::borrow::Cow;
|
|
use std::cell::OnceCell;
|
|
use std::ptr::NonNull;
|
|
use std::slice;
|
|
use std::str;
|
|
|
|
use libsqlite3_sys::{
|
|
sqlite3_value, sqlite3_value_blob, sqlite3_value_bytes, sqlite3_value_double,
|
|
sqlite3_value_dup, sqlite3_value_free, sqlite3_value_int64, sqlite3_value_type,
|
|
};
|
|
use sqlx_core::type_info::TypeInfo;
|
|
pub(crate) use sqlx_core::value::{Value, ValueRef};
|
|
|
|
use crate::type_info::DataType;
|
|
use crate::{Sqlite, SqliteError, SqliteTypeInfo};
|
|
|
|
/// An owned handle to a [`sqlite3_value`].
|
|
///
|
|
/// # Note: Decoding is Stateful
|
|
/// The [`sqlite3_value` interface][value-methods] reserves the right to be stateful:
|
|
///
|
|
/// > Other interfaces might change the datatype for an sqlite3_value object.
|
|
/// > For example, if the datatype is initially SQLITE_INTEGER and sqlite3_value_text(V) is called
|
|
/// > to extract a text value for that integer, then subsequent calls to sqlite3_value_type(V)
|
|
/// > might return SQLITE_TEXT. Whether or not a persistent internal datatype conversion occurs is
|
|
/// > undefined and may change from one release of SQLite to the next.
|
|
///
|
|
/// Thus, this type is `!Sync` and [`SqliteValueRef`] is `!Send` and `!Sync` to prevent data races.
|
|
///
|
|
/// Additionally, this statefulness means that the return values of `sqlite3_value_bytes()` and
|
|
/// `sqlite3_value_blob()` could be invalidated by later calls to other `sqlite3_value*` methods.
|
|
///
|
|
/// To prevent undefined behavior from accessing dangling pointers, this type (and any
|
|
/// [`SqliteValueRef`] instances created from it) remembers when it was used to decode a
|
|
/// borrowed `&[u8]` or `&str` and returns an error if it is used to decode any other type.
|
|
///
|
|
/// To bypass this error, you must prove that no outstanding borrows exist.
|
|
///
|
|
/// This may be done in one of a few ways:
|
|
/// * If you hold mutable access, call [`Self::reset_borrow()`] which resets the borrowed state.
|
|
/// * If you have an immutable reference, call [`Self::clone()`] to get a new instance
|
|
/// with no outstanding borrows.
|
|
/// * If you hold a [`SqliteValueRef`], call [`SqliteValueRef::to_owned()`]
|
|
/// to get a new `SqliteValue` with no outstanding borrows.
|
|
///
|
|
/// This is *only* necessary if using the same `SqliteValue` or [`SqliteValueRef`] to decode
|
|
/// multiple different types. The vast majority of use-cases employing once-through decoding
|
|
/// should not have to worry about this.
|
|
///
|
|
/// [`sqlite3_value`]: https://www.sqlite.org/c3ref/value.html
|
|
/// [value-methods]: https://www.sqlite.org/c3ref/value_blob.html
|
|
pub struct SqliteValue(ValueHandle);
|
|
|
|
/// A borrowed reference to a [`sqlite3_value`].
|
|
///
|
|
/// Semantically, this behaves as a reference to [`SqliteValue`].
|
|
///
|
|
/// # Note: Decoding is Stateful
|
|
/// See [`SqliteValue`] for details.
|
|
pub struct SqliteValueRef<'r>(Cow<'r, ValueHandle>);
|
|
|
|
impl SqliteValue {
|
|
// SAFETY: The sqlite3_value must be non-null and SQLite must not free it. It will be freed on drop.
|
|
pub(crate) unsafe fn dup(
|
|
value: *mut sqlite3_value,
|
|
column_type: Option<SqliteTypeInfo>,
|
|
) -> Self {
|
|
debug_assert!(!value.is_null());
|
|
let handle = ValueHandle::try_dup_of(value, column_type)
|
|
.expect("SQLite failed to allocate memory for duplicated value");
|
|
Self(handle)
|
|
}
|
|
|
|
/// Prove that there are no outstanding borrows of this instance.
|
|
///
|
|
/// Call this after decoding a borrowed `&[u8]` or `&str`
|
|
/// to reset the internal borrowed state and allow decoding of other types.
|
|
pub fn reset_borrow(&mut self) {
|
|
self.0.reset_blob_borrow();
|
|
}
|
|
|
|
/// Call [`sqlite3_value_dup()`] to create a new instance of this type.
|
|
///
|
|
/// Returns an error if the call returns a null pointer, indicating that
|
|
/// SQLite was unable to allocate the additional memory required.
|
|
///
|
|
/// Non-panicking version of [`Self::clone()`].
|
|
///
|
|
/// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html
|
|
pub fn try_clone(&self) -> Result<Self, SqliteError> {
|
|
self.0.try_dup().map(Self)
|
|
}
|
|
}
|
|
|
|
impl Clone for SqliteValue {
|
|
/// Call [`sqlite3_value_dup()`] to create a new instance of this type.
|
|
///
|
|
/// # Panics
|
|
/// If [`sqlite3_value_dup()`] returns a null pointer, indicating an out-of-memory condition.
|
|
///
|
|
/// See [`Self::try_clone()`] for a non-panicking version.
|
|
///
|
|
/// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html
|
|
fn clone(&self) -> Self {
|
|
self.try_clone().expect("failed to clone `SqliteValue`")
|
|
}
|
|
}
|
|
|
|
impl Value for SqliteValue {
|
|
type Database = Sqlite;
|
|
|
|
fn as_ref(&self) -> SqliteValueRef<'_> {
|
|
SqliteValueRef::value(self)
|
|
}
|
|
|
|
fn type_info(&self) -> Cow<'_, SqliteTypeInfo> {
|
|
Cow::Owned(self.0.type_info())
|
|
}
|
|
|
|
fn is_null(&self) -> bool {
|
|
self.0.is_null()
|
|
}
|
|
}
|
|
|
|
impl<'r> SqliteValueRef<'r> {
|
|
/// Attempt to duplicate the internal `sqlite3_value` with [`sqlite3_value_dup()`].
|
|
///
|
|
/// Returns an error if the call returns a null pointer, indicating that
|
|
/// SQLite was unable to allocate the additional memory required.
|
|
///
|
|
/// Non-panicking version of [`Self::try_to_owned()`].
|
|
///
|
|
/// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html
|
|
pub fn try_to_owned(&self) -> Result<SqliteValue, SqliteError> {
|
|
self.0.try_dup().map(SqliteValue)
|
|
}
|
|
|
|
pub(crate) fn value(value: &'r SqliteValue) -> Self {
|
|
Self(Cow::Borrowed(&value.0))
|
|
}
|
|
|
|
/// # Safety
|
|
/// The supplied sqlite3_value must not be null and SQLite must free it.
|
|
/// It will not be freed on drop.
|
|
/// The lifetime on this struct should tie it to whatever scope it's valid for before SQLite frees it.
|
|
#[allow(unused)]
|
|
pub(crate) unsafe fn borrowed(value: *mut sqlite3_value) -> Self {
|
|
debug_assert!(!value.is_null());
|
|
let handle = ValueHandle::temporary(NonNull::new_unchecked(value));
|
|
Self(Cow::Owned(handle))
|
|
}
|
|
|
|
// NOTE: `int()` is deliberately omitted because it will silently truncate a wider value,
|
|
// which is likely to cause bugs:
|
|
// https://github.com/launchbadge/sqlx/issues/3179
|
|
// (Similar bug in Postgres): https://github.com/launchbadge/sqlx/issues/3161
|
|
pub(super) fn int64(&self) -> Result<i64, BorrowedBlobError> {
|
|
self.0.int64()
|
|
}
|
|
|
|
pub(super) fn double(&self) -> Result<f64, BorrowedBlobError> {
|
|
self.0.double()
|
|
}
|
|
|
|
pub(super) fn blob_borrowed(&self) -> &'r [u8] {
|
|
// SAFETY: lifetime is matched to `'r`
|
|
unsafe { self.0.blob_borrowed() }
|
|
}
|
|
|
|
pub(super) fn with_temp_blob<R>(&self, op: impl FnOnce(&[u8]) -> R) -> R {
|
|
self.0.with_blob(op)
|
|
}
|
|
|
|
pub(super) fn blob_owned(&self) -> Vec<u8> {
|
|
self.with_temp_blob(|blob| blob.to_vec())
|
|
}
|
|
|
|
pub(super) fn text_borrowed(&self) -> Result<&'r str, str::Utf8Error> {
|
|
// SAFETY: lifetime is matched to `'r`
|
|
unsafe { self.0.text_borrowed() }
|
|
}
|
|
|
|
pub(super) fn with_temp_text<R>(
|
|
&self,
|
|
op: impl FnOnce(&str) -> R,
|
|
) -> Result<R, str::Utf8Error> {
|
|
self.0.with_blob(|blob| str::from_utf8(blob).map(op))
|
|
}
|
|
|
|
pub(super) fn text_owned(&self) -> Result<String, str::Utf8Error> {
|
|
self.with_temp_text(|text| text.to_string())
|
|
}
|
|
}
|
|
|
|
impl<'r> ValueRef<'r> for SqliteValueRef<'r> {
|
|
type Database = Sqlite;
|
|
|
|
/// Attempt to duplicate the internal `sqlite3_value` with [`sqlite3_value_dup()`].
|
|
///
|
|
/// # Panics
|
|
/// If [`sqlite3_value_dup()`] returns a null pointer, indicating an out-of-memory condition.
|
|
///
|
|
/// See [`Self::try_to_owned()`] for a non-panicking version.
|
|
///
|
|
/// [`sqlite3_value_dup()`]: https://www.sqlite.org/c3ref/value_dup.html
|
|
fn to_owned(&self) -> SqliteValue {
|
|
SqliteValue(
|
|
self.0
|
|
.try_dup()
|
|
.expect("failed to convert SqliteValueRef to owned SqliteValue"),
|
|
)
|
|
}
|
|
|
|
fn type_info(&self) -> Cow<'_, SqliteTypeInfo> {
|
|
Cow::Owned(self.0.type_info())
|
|
}
|
|
|
|
fn is_null(&self) -> bool {
|
|
self.0.is_null()
|
|
}
|
|
}
|
|
|
|
pub(crate) struct ValueHandle {
|
|
value: NonNull<sqlite3_value>,
|
|
column_type: Option<SqliteTypeInfo>,
|
|
// Note: `std::cell` version
|
|
borrowed_blob: OnceCell<Blob>,
|
|
free_on_drop: bool,
|
|
}
|
|
|
|
struct Blob {
|
|
ptr: *const u8,
|
|
len: usize,
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
#[error("given `SqliteValue` was previously decoded as BLOB or TEXT; `SqliteValue::reset_borrow()` must be called first")]
|
|
pub(crate) struct BorrowedBlobError;
|
|
|
|
// SAFE: only protected value objects are stored in SqliteValue
|
|
unsafe impl Send for ValueHandle {}
|
|
|
|
// SAFETY: the `sqlite3_value_*()` methods reserve the right to be stateful,
|
|
// which means method calls aren't thread-safe without mutual exclusion.
|
|
//
|
|
// impl !Sync for ValueHandle {}
|
|
|
|
impl ValueHandle {
|
|
/// # Safety
|
|
/// The `sqlite3_value` must be valid and SQLite must not free it. It will be freed on drop.
|
|
unsafe fn try_dup_of(
|
|
value: *mut sqlite3_value,
|
|
column_type: Option<SqliteTypeInfo>,
|
|
) -> Result<Self, SqliteError> {
|
|
// SAFETY: caller must ensure `value` is valid.
|
|
let value =
|
|
unsafe { NonNull::new(sqlite3_value_dup(value)).ok_or_else(SqliteError::nomem)? };
|
|
|
|
Ok(Self {
|
|
value,
|
|
column_type,
|
|
borrowed_blob: OnceCell::new(),
|
|
free_on_drop: true,
|
|
})
|
|
}
|
|
|
|
fn temporary(value: NonNull<sqlite3_value>) -> Self {
|
|
Self {
|
|
value,
|
|
column_type: None,
|
|
borrowed_blob: OnceCell::new(),
|
|
free_on_drop: false,
|
|
}
|
|
}
|
|
|
|
fn try_dup(&self) -> Result<Self, SqliteError> {
|
|
// SAFETY: `value` is initialized
|
|
unsafe { Self::try_dup_of(self.value.as_ptr(), self.column_type.clone()) }
|
|
}
|
|
|
|
fn value_type_info(&self) -> SqliteTypeInfo {
|
|
SqliteTypeInfo(DataType::from_code(unsafe {
|
|
sqlite3_value_type(self.value.as_ptr())
|
|
}))
|
|
}
|
|
|
|
fn type_info(&self) -> SqliteTypeInfo {
|
|
let value_type = self.value_type_info();
|
|
|
|
// Assume the actual value type is more accurate, if it's not NULL.
|
|
match &self.column_type {
|
|
Some(column_type) if value_type.is_null() => column_type.clone(),
|
|
_ => value_type,
|
|
}
|
|
}
|
|
|
|
fn int64(&self) -> Result<i64, BorrowedBlobError> {
|
|
// SAFETY: we have to be certain the caller isn't still holding a borrow from `.blob_borrowed()`
|
|
self.assert_blob_not_borrowed()?;
|
|
|
|
Ok(unsafe { sqlite3_value_int64(self.value.as_ptr()) })
|
|
}
|
|
|
|
fn double(&self) -> Result<f64, BorrowedBlobError> {
|
|
// SAFETY: we have to be certain the caller isn't still holding a borrow from `.blob_borrowed()`
|
|
self.assert_blob_not_borrowed()?;
|
|
|
|
Ok(unsafe { sqlite3_value_double(self.value.as_ptr()) })
|
|
}
|
|
|
|
fn is_null(&self) -> bool {
|
|
self.value_type_info().is_null()
|
|
}
|
|
}
|
|
|
|
impl Clone for ValueHandle {
|
|
fn clone(&self) -> Self {
|
|
self.try_dup().unwrap()
|
|
}
|
|
}
|
|
|
|
impl Drop for ValueHandle {
|
|
fn drop(&mut self) {
|
|
if self.free_on_drop {
|
|
unsafe {
|
|
sqlite3_value_free(self.value.as_ptr());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ValueHandle {
|
|
fn assert_blob_not_borrowed(&self) -> Result<(), BorrowedBlobError> {
|
|
if self.borrowed_blob.get().is_none() {
|
|
Ok(())
|
|
} else {
|
|
Err(BorrowedBlobError)
|
|
}
|
|
}
|
|
|
|
fn reset_blob_borrow(&mut self) {
|
|
self.borrowed_blob.take();
|
|
}
|
|
|
|
fn get_blob(&self) -> Option<Blob> {
|
|
if let Some(blob) = self.borrowed_blob.get() {
|
|
return Some(Blob { ..*blob });
|
|
}
|
|
|
|
// SAFETY: calling `sqlite3_value_bytes` from multiple threads at once is a data race.
|
|
let len = unsafe { sqlite3_value_bytes(self.value.as_ptr()) };
|
|
|
|
// This likely means UB in SQLite itself or our usage of it;
|
|
// signed integer overflow is UB in the C standard.
|
|
let len = usize::try_from(len).unwrap_or_else(|_| {
|
|
panic!("sqlite3_value_bytes() returned value out of range for usize: {len}")
|
|
});
|
|
|
|
if len == 0 {
|
|
// empty blobs are NULL
|
|
return None;
|
|
}
|
|
|
|
let ptr = unsafe { sqlite3_value_blob(self.value.as_ptr()) } as *const u8;
|
|
debug_assert!(!ptr.is_null());
|
|
|
|
Some(Blob { ptr, len })
|
|
}
|
|
|
|
fn with_blob<R>(&self, with_blob: impl FnOnce(&[u8]) -> R) -> R {
|
|
let Some(blob) = self.get_blob() else {
|
|
return with_blob(&[]);
|
|
};
|
|
|
|
// SAFETY: the slice cannot outlive the call
|
|
with_blob(unsafe { blob.as_slice() })
|
|
}
|
|
|
|
/// # Safety
|
|
/// Caller must ensure lifetime '`b` cannot outlive `self`.
|
|
unsafe fn blob_borrowed<'a>(&self) -> &'a [u8] {
|
|
let Some(blob) = self.get_blob() else {
|
|
return &[];
|
|
};
|
|
|
|
// SAFETY: we need to store that the blob was borrowed
|
|
// to prevent
|
|
let blob = self.borrowed_blob.get_or_init(|| blob);
|
|
|
|
unsafe { blob.as_slice() }
|
|
}
|
|
|
|
/// # Safety
|
|
/// Caller must ensure lifetime '`b` cannot outlive `self`.
|
|
unsafe fn text_borrowed<'b>(&self) -> Result<&'b str, str::Utf8Error> {
|
|
let Some(blob) = self.get_blob() else {
|
|
return Ok("");
|
|
};
|
|
|
|
// SAFETY: lifetime of `blob` will be tied to `'b`.
|
|
let s = str::from_utf8(unsafe { blob.as_slice() })?;
|
|
|
|
// We only store the borrow after we ensure the string is valid.
|
|
self.borrowed_blob.set(blob).ok();
|
|
|
|
Ok(s)
|
|
}
|
|
}
|
|
|
|
impl Blob {
|
|
/// # Safety
|
|
/// `'a` must not outlive the `sqlite3_value` this blob came from.
|
|
unsafe fn as_slice<'a>(&self) -> &'a [u8] {
|
|
slice::from_raw_parts(self.ptr, self.len)
|
|
}
|
|
}
|