use libduckdb_sys::{duckdb_blob, duckdb_free, duckdb_get_blob};
use super::Value;
use crate::error::ExtensionError;
fn blob_size(blob: &duckdb_blob) -> Result<Option<usize>, ExtensionError> {
if blob.data.is_null() {
return if blob.size == 0 {
Ok(None)
} else {
Err(ExtensionError::new("duckdb_get_blob returned null data"))
};
}
usize::try_from(blob.size)
.map(Some)
.map_err(|_| ExtensionError::new("duckdb_get_blob returned an unsupported blob size"))
}
#[mutants::skip] unsafe fn free_blob_data(data: *mut core::ffi::c_void) {
if !data.is_null() {
unsafe { duckdb_free(data) };
}
}
impl Value {
pub fn as_blob(&self) -> Result<Vec<u8>, ExtensionError> {
if self.raw.is_null() {
return Err(ExtensionError::new("Value is null"));
}
let blob: duckdb_blob = unsafe { duckdb_get_blob(self.raw) };
let size = match blob_size(&blob) {
Ok(None) => return Ok(Vec::new()),
Ok(Some(size)) => size,
Err(error) => {
unsafe { free_blob_data(blob.data) };
return Err(error);
}
};
let slice = unsafe { std::slice::from_raw_parts(blob.data.cast::<u8>(), size) };
let out = slice.to_vec();
unsafe { free_blob_data(blob.data) };
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blob_size_null_empty_blob_is_valid() {
let blob = duckdb_blob {
data: std::ptr::null_mut(),
size: 0,
};
assert!(matches!(blob_size(&blob), Ok(None)));
}
#[test]
fn blob_size_null_non_empty_blob_is_invalid() {
let blob = duckdb_blob {
data: std::ptr::null_mut(),
size: 1,
};
assert!(blob_size(&blob).is_err());
}
#[test]
fn null_value_as_blob_returns_error() {
let value = unsafe { Value::from_raw(std::ptr::null_mut()) };
assert!(value.as_blob().is_err());
}
#[cfg(feature = "_duckdb-testing")]
#[test]
fn blob_value_non_utf8_bytes_round_trip() {
let _db = crate::testing::InMemoryDb::open().expect("should initialize DuckDB");
let payload = [
0x80u8, 0xF0, 0x01, 0x42, 0xFF, 0x00, 0xFE, 0x7F, 0xAA, 0x55, 0xC0, 0xAF, 0x90,
];
let len = u64::try_from(payload.len()).expect("test payload length fits in u64");
let raw = unsafe { libduckdb_sys::duckdb_create_blob(payload.as_ptr(), len) };
let value = unsafe { Value::from_raw(raw) };
assert_eq!(value.as_blob().expect("blob should be readable"), payload);
}
#[cfg(feature = "_duckdb-testing")]
#[test]
fn blob_value_empty_bytes_round_trip() {
let _db = crate::testing::InMemoryDb::open().expect("should initialize DuckDB");
let payload = [];
let raw = unsafe { libduckdb_sys::duckdb_create_blob(payload.as_ptr(), 0) };
let value = unsafe { Value::from_raw(raw) };
assert!(value.as_blob().expect("blob should be readable").is_empty());
}
}