use crate::core::LogicalTypeId;
use crate::ffi::{
DuckDbString, duckdb_destroy_value, duckdb_get_bool, duckdb_get_double, duckdb_get_float, duckdb_get_int8,
duckdb_get_int16, duckdb_get_int32, duckdb_get_int64, duckdb_get_list_child, duckdb_get_list_size,
duckdb_get_type_id, duckdb_get_uint8, duckdb_get_uint16, duckdb_get_uint32, duckdb_get_uint64,
duckdb_get_value_type, duckdb_get_varchar, duckdb_is_null_value, duckdb_value,
};
use std::fmt;
#[derive(Debug)]
pub struct Value {
pub(crate) ptr: duckdb_value,
}
macro_rules! primitive_getters {
($($name:ident: $rust_type:ty => $ffi_func:ident),* $(,)?) => {
$(
#[doc = concat!("Returns the value converted to `", stringify!($rust_type), "` using DuckDB's C API.")]
pub fn $name(&self) -> $rust_type {
unsafe { $ffi_func(self.ptr) }
}
)*
};
}
impl From<duckdb_value> for Value {
fn from(ptr: duckdb_value) -> Self {
Self { ptr }
}
}
impl Drop for Value {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
duckdb_destroy_value(&mut self.ptr);
}
}
self.ptr = std::ptr::null_mut();
}
}
impl Value {
primitive_getters!(
to_bool: bool => duckdb_get_bool,
to_int8: i8 => duckdb_get_int8,
to_uint8: u8 => duckdb_get_uint8,
to_int16: i16 => duckdb_get_int16,
to_uint16: u16 => duckdb_get_uint16,
to_int32: i32 => duckdb_get_int32,
to_uint32: u32 => duckdb_get_uint32,
to_int64: i64 => duckdb_get_int64,
to_uint64: u64 => duckdb_get_uint64,
to_float: f32 => duckdb_get_float,
to_double: f64 => duckdb_get_double,
);
pub fn to_list(&self) -> Option<Vec<Value>> {
if self.is_null() || self.logical_type_id() != LogicalTypeId::List {
return None;
}
let size = unsafe { duckdb_get_list_size(self.ptr) };
let mut out = Vec::with_capacity(usize::try_from(size).ok()?);
for i in 0..size {
let child = unsafe { duckdb_get_list_child(self.ptr, i) };
if child.is_null() {
return None;
}
out.push(Value::from(child));
}
Some(out)
}
pub fn is_null(&self) -> bool {
unsafe { duckdb_is_null_value(self.ptr) }
}
pub fn logical_type_id(&self) -> LogicalTypeId {
unsafe {
let logical_type = duckdb_get_value_type(self.ptr);
duckdb_get_type_id(logical_type).into()
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
unsafe {
match DuckDbString::from_nullable_ptr(duckdb_get_varchar(self.ptr)) {
Some(varchar) => write!(f, "{}", varchar.to_string_lossy()),
None => f.write_str("NULL"),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{LogicalTypeHandle, LogicalTypeId};
use crate::ffi::{
duckdb_create_int64, duckdb_create_list_value, duckdb_create_varchar, duckdb_destroy_value, duckdb_value,
};
use std::ffi::CString;
#[test]
fn test_value_to_string() {
let c_str = CString::new("some value").unwrap();
let duckdb_val = unsafe { duckdb_create_varchar(c_str.as_ptr()) };
let val = Value::from(duckdb_val);
assert_eq!(val.to_string(), "some value");
}
#[test]
fn test_value_to_list() {
let list_items: Vec<i64> = vec![1, -200, 2381292];
let val = unsafe {
let logical_type = LogicalTypeHandle::from(LogicalTypeId::Bigint);
let values: Vec<duckdb_value> = list_items.iter().map(|v| duckdb_create_int64(*v)).collect();
let duckdb_val =
duckdb_create_list_value(logical_type.ptr, values.as_ptr().cast_mut(), values.len() as u64);
for mut v in values {
duckdb_destroy_value(&mut v);
}
Value::from(duckdb_val)
};
let list = val.to_list().unwrap();
assert_eq!(list.len(), list_items.len());
assert_eq!(list.iter().map(|v| v.to_int64()).collect::<Vec<i64>>(), list_items);
}
#[test]
fn test_value_to_list_returns_empty_vec_for_empty_list() {
let val = unsafe {
let logical_type = LogicalTypeHandle::from(LogicalTypeId::Bigint);
let values: Vec<duckdb_value> = Vec::new();
let duckdb_val = duckdb_create_list_value(logical_type.ptr, values.as_ptr().cast_mut(), 0);
assert!(!duckdb_val.is_null());
Value::from(duckdb_val)
};
let list = val.to_list().unwrap();
assert!(list.is_empty());
}
#[test]
fn test_value_to_list_returns_none_for_non_list() {
let val = unsafe { Value::from(duckdb_create_int64(42)) };
assert!(val.to_list().is_none());
}
#[test]
fn test_value_primitive_getters() {
use crate::ffi::{
duckdb_create_bool, duckdb_create_double, duckdb_create_float, duckdb_create_int8, duckdb_create_int16,
duckdb_create_int32, duckdb_create_int64, duckdb_create_uint8, duckdb_create_uint16, duckdb_create_uint32,
duckdb_create_uint64,
};
unsafe {
let bool_val = Value::from(duckdb_create_bool(true));
assert!(bool_val.to_bool());
let i8_val = Value::from(duckdb_create_int8(-42));
assert_eq!(i8_val.to_int8(), -42);
let u8_val = Value::from(duckdb_create_uint8(255));
assert_eq!(u8_val.to_uint8(), 255);
let i16_val = Value::from(duckdb_create_int16(-1000));
assert_eq!(i16_val.to_int16(), -1000);
let u16_val = Value::from(duckdb_create_uint16(50000));
assert_eq!(u16_val.to_uint16(), 50000);
let i32_val = Value::from(duckdb_create_int32(-200000));
assert_eq!(i32_val.to_int32(), -200000);
let u32_val = Value::from(duckdb_create_uint32(4000000000));
assert_eq!(u32_val.to_uint32(), 4000000000);
let i64_val = Value::from(duckdb_create_int64(-9000000000000000000));
assert_eq!(i64_val.to_int64(), -9000000000000000000);
let u64_val = Value::from(duckdb_create_uint64(18000000000000000000));
assert_eq!(u64_val.to_uint64(), 18000000000000000000);
let float_val = Value::from(duckdb_create_float(1.25f32));
assert_eq!(float_val.to_float(), 1.25);
let double_val = Value::from(duckdb_create_double(-4.5));
assert_eq!(double_val.to_double(), -4.5);
}
}
#[test]
fn test_value_is_null() {
use crate::ffi::duckdb_create_null_value;
unsafe {
let null_val = Value::from(duckdb_create_null_value());
assert!(null_val.is_null());
assert!(null_val.to_list().is_none());
let non_null_val = Value::from(duckdb_create_int64(42));
assert!(!non_null_val.is_null());
}
}
#[test]
fn test_value_logical_type_id() {
let val = unsafe { Value::from(duckdb_create_int64(42)) };
assert_eq!(val.logical_type_id(), LogicalTypeId::Bigint);
}
}