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};
pub struct SqliteValue(ValueHandle);
pub struct SqliteValueRef<'r>(Cow<'r, ValueHandle>);
impl SqliteValue {
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)
}
pub fn reset_borrow(&mut self) {
self.0.reset_blob_borrow();
}
pub fn try_clone(&self) -> Result<Self, SqliteError> {
self.0.try_dup().map(Self)
}
}
impl Clone for SqliteValue {
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> {
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))
}
#[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))
}
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] {
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> {
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;
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>,
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;
unsafe impl Send for ValueHandle {}
impl ValueHandle {
unsafe fn try_dup_of(
value: *mut sqlite3_value,
column_type: Option<SqliteTypeInfo>,
) -> Result<Self, SqliteError> {
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> {
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();
match &self.column_type {
Some(column_type) if value_type.is_null() => column_type.clone(),
_ => value_type,
}
}
fn int64(&self) -> Result<i64, BorrowedBlobError> {
self.assert_blob_not_borrowed()?;
Ok(unsafe { sqlite3_value_int64(self.value.as_ptr()) })
}
fn double(&self) -> Result<f64, BorrowedBlobError> {
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 });
}
let len = unsafe { sqlite3_value_bytes(self.value.as_ptr()) };
let len = usize::try_from(len).unwrap_or_else(|_| {
panic!("sqlite3_value_bytes() returned value out of range for usize: {len}")
});
if len == 0 {
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(&[]);
};
with_blob(unsafe { blob.as_slice() })
}
unsafe fn blob_borrowed<'a>(&self) -> &'a [u8] {
let Some(blob) = self.get_blob() else {
return &[];
};
let blob = self.borrowed_blob.get_or_init(|| blob);
unsafe { blob.as_slice() }
}
unsafe fn text_borrowed<'b>(&self) -> Result<&'b str, str::Utf8Error> {
let Some(blob) = self.get_blob() else {
return Ok("");
};
let s = str::from_utf8(unsafe { blob.as_slice() })?;
self.borrowed_blob.set(blob).ok();
Ok(s)
}
}
impl Blob {
unsafe fn as_slice<'a>(&self) -> &'a [u8] {
slice::from_raw_parts(self.ptr, self.len)
}
}