use core::{
ffi::{c_uchar, c_void},
fmt, mem,
ops::Deref,
ptr, slice,
};
#[cfg(all(feature = "functions", target_pointer_width = "32"))]
use sqlite::sqlite3_result_blob;
#[cfg(all(feature = "functions", target_pointer_width = "64"))]
use sqlite::sqlite3_result_blob64;
#[cfg(target_pointer_width = "32")]
use sqlite::{sqlite3_bind_blob, sqlite3_malloc};
#[cfg(target_pointer_width = "64")]
use sqlite::{sqlite3_bind_blob64, sqlite3_malloc64, sqlite3_uint64};
use sqlite::{sqlite3_destructor_type, sqlite3_free};
#[cfg(feature = "functions")]
use super::{bind::result, func::ContextRef};
use super::{
bind::{Bind, bind},
statement::Statement,
};
use crate::{
error::{Error, ErrorCategory, Result},
types::BindIndex,
};
const EMPTY: &[u8] = &[];
pub struct Bytes {
data: *const c_uchar,
len: usize,
}
impl Bytes {
pub fn new(data: impl AsRef<[u8]>) -> Result<Self> {
let data = data.as_ref();
Self::allocate(data.len(), |dest| {
dest.copy_from_slice(data);
Ok(())
})
}
#[inline]
pub const fn empty() -> Self {
Self {
data: ptr::null(),
len: 0,
}
}
pub fn allocate<F: FnOnce(&mut [u8]) -> Result<()>>(len: usize, populate: F) -> Result<Self> {
if len == 0 {
return Ok(Self::empty());
}
#[cfg(target_pointer_width = "32")]
let ptr = unsafe { sqlite3_malloc(len) as *mut c_uchar };
#[cfg(target_pointer_width = "64")]
let ptr = unsafe { sqlite3_malloc64(len as u64) as *mut c_uchar };
if ptr.is_null() {
return Err(ErrorCategory::OutOfMemory.into());
}
let data = unsafe { slice::from_raw_parts_mut(ptr, len) };
populate(data)?;
Ok(unsafe { Self::from_raw_parts(ptr, len) })
}
pub fn zeroed(len: usize) -> Result<Self> {
Self::allocate(len, |data| {
data.fill(0);
Ok(())
})
}
#[inline]
pub const unsafe fn from_raw_parts(ptr: *const c_uchar, len: usize) -> Self {
match ptr {
_ if ptr.is_null() && len == 0 => panic!("expected non-null Bytes pointer"),
data => Self { data, len },
}
}
#[inline]
pub const fn into_raw_parts(self) -> (*const c_uchar, usize) {
let (ptr, len) = (self.as_ptr(), self.len);
mem::forget(self);
(ptr, len)
}
#[inline]
pub const fn data(&self) -> &[u8] {
if self.is_empty() {
EMPTY
} else {
unsafe { slice::from_raw_parts(self.data, self.len) }
}
}
#[inline]
pub const fn as_ptr(&self) -> *const c_uchar {
self.data
}
#[inline]
pub const fn len(&self) -> usize {
self.len
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
impl AsRef<[u8]> for Bytes {
#[inline]
fn as_ref(&self) -> &[u8] {
self.data()
}
}
impl Deref for Bytes {
type Target = [u8];
#[inline]
fn deref(&self) -> &Self::Target {
self.data()
}
}
impl Drop for Bytes {
fn drop(&mut self) {
unsafe { sqlite3_free(self.as_ptr() as *mut c_void) }
}
}
impl<const N: usize> From<[u8; N]> for Bytes {
fn from(value: [u8; N]) -> Self {
Self::allocate(N, move |dest| {
dest.copy_from_slice(&value[..]);
Ok(())
})
.expect("malloc")
}
}
impl fmt::Debug for Bytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Bytes").field(&self.data()).finish()
}
}
#[cfg_attr(
target_pointer_width = "32",
doc = "[Binds](Bind) [`ffi::Bytes`](Bytes) via [`sqlite3_bind_blob`]."
)]
#[cfg_attr(
target_pointer_width = "64",
doc = "[Binds](Bind) [`ffi::Bytes`](Bytes) via [`sqlite3_bind_blob64`]."
)]
impl<'b> Bind<'b> for Bytes {
unsafe fn bind_parameter<'s>(self, statement: &Statement<'s>, index: BindIndex) -> Result<()>
where
's: 'b,
{
let (ptr, len) = self.into_raw_parts();
let ptr = ptr as *const c_void;
let destructor = sqlite3_destructor_type::new(sqlite3_free);
#[cfg(target_pointer_width = "32")]
bind! { sqlite3_bind_blob(statement, index, ptr, len as c_int, destructor) }?;
#[cfg(target_pointer_width = "64")]
bind! { sqlite3_bind_blob64(statement, index, ptr, len as sqlite3_uint64, destructor) }?;
Ok(())
}
#[cfg(feature = "functions")]
unsafe fn bind_return<'c>(self, context: &ContextRef<'c>)
where
'b: 'c,
{
let (ptr, len) = self.into_raw_parts();
let ptr = ptr as *const c_void;
let destructor = sqlite3_destructor_type::new(sqlite3_free);
#[cfg(target_pointer_width = "32")]
result! { sqlite3_result_blob(context, ptr, len as c_int, destructor) };
#[cfg(target_pointer_width = "64")]
result! { sqlite3_result_blob64(context, ptr, len as sqlite3_uint64, destructor) };
}
}
#[cfg(test)]
mod tests {
use sqlite::{SQLITE_OPEN_CREATE, SQLITE_OPEN_READWRITE};
use super::*;
use crate::ffi::Connection;
#[test]
fn test_new() {
let data = b"hello, world!";
let bytes = Bytes::new(data).expect("new");
assert_eq!(bytes.len(), 13);
assert!(!bytes.is_empty());
assert_eq!(&*bytes, data);
}
#[test]
fn test_new_empty() {
let bytes = Bytes::new([]).expect("new");
assert_eq!(bytes.len(), 0);
assert!(bytes.is_empty());
assert!(bytes.as_ptr().is_null());
assert_eq!(&*bytes, &[] as &[u8]);
}
#[test]
fn test_empty() {
let bytes = Bytes::empty();
assert_eq!(bytes.len(), 0);
assert!(bytes.is_empty());
assert_eq!(&*bytes, &[] as &[u8]);
}
#[test]
fn test_allocate() {
let bytes = Bytes::allocate(5, |dest| {
dest.copy_from_slice(&[1, 2, 3, 4, 5]);
Ok(())
})
.expect("allocate");
assert_eq!(bytes.len(), 5);
assert_eq!(&*bytes, &[1, 2, 3, 4, 5]);
}
#[test]
fn test_from_array() {
let bytes: Bytes = [0xDE, 0xAD, 0xBE, 0xEF].into();
assert_eq!(bytes.len(), 4);
assert_eq!(&*bytes, &[0xDE, 0xAD, 0xBE, 0xEF]);
}
#[test]
fn test_as_ref() {
let bytes = Bytes::new([1, 2, 3]).expect("new");
let slice: &[u8] = bytes.as_ref();
assert_eq!(slice, &[1, 2, 3]);
}
#[test]
fn test_deref() {
let bytes = Bytes::new([4, 5, 6]).expect("new");
assert_eq!(&*bytes, &[4, 5, 6]);
assert_eq!(bytes[0], 4);
assert_eq!(bytes[1], 5);
assert_eq!(bytes[2], 6);
}
#[test]
fn test_into_raw_parts() {
let bytes = Bytes::new([7, 8, 9]).expect("new");
let (ptr, len) = bytes.into_raw_parts();
assert!(!ptr.is_null());
assert_eq!(len, 3);
let bytes = unsafe { Bytes::from_raw_parts(ptr, len) };
assert_eq!(&*bytes, &[7, 8, 9]);
}
#[test]
fn test_debug() {
let bytes = Bytes::new([0x01, 0x02, 0x03]).expect("new");
assert_eq!(format!("{bytes:?}"), "Bytes([1, 2, 3])");
}
#[test]
fn test_bind_bytes() {
use crate::Borrowed;
use crate::ffi::Statement;
let conn = Connection::open(
c":memory:",
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
None,
)
.expect("open");
let (stmt, _) = Statement::prepare(&conn, "SELECT ?", 0).expect("prepare");
{
let data = b"binary\x00data\xFF";
let bytes = Bytes::new(data).expect("new");
let index = BindIndex::new(1).expect("valid index");
unsafe { stmt.bind(index, bytes) }.expect("bind");
let has_row = unsafe { stmt.row() }.expect("row");
assert!(has_row);
let col = crate::types::ColumnIndex::new(0);
let value: Borrowed<'_, [u8]> = unsafe { stmt.fetch(col) };
assert_eq!(&*value, data);
}
stmt.close().expect("close stmt");
conn.close().expect("close conn");
}
}