use std::ffi::CStr;
use std::os::raw::c_char;
use libduckdb_sys::{
duckdb_destroy_value, duckdb_free, duckdb_get_bool, duckdb_get_double, duckdb_get_float,
duckdb_get_hugeint, duckdb_get_int16, duckdb_get_int32, duckdb_get_int64, duckdb_get_int8,
duckdb_get_uint16, duckdb_get_uint32, duckdb_get_uint64, duckdb_get_uint8, duckdb_get_varchar,
duckdb_value,
};
use crate::error::ExtensionError;
pub struct Value {
raw: duckdb_value,
}
impl Value {
#[inline]
#[must_use]
pub const unsafe fn from_raw(raw: duckdb_value) -> Self {
Self { raw }
}
pub fn as_str(&self) -> Result<String, ExtensionError> {
if self.raw.is_null() {
return Err(ExtensionError::new("Value is null"));
}
let c_str: *mut c_char = unsafe { duckdb_get_varchar(self.raw) };
if c_str.is_null() {
return Err(ExtensionError::new("duckdb_get_varchar returned null"));
}
let result = unsafe { CStr::from_ptr(c_str) }
.to_str()
.map(str::to_owned)
.map_err(|_| ExtensionError::new("Value contains invalid UTF-8"));
unsafe { duckdb_free(c_str.cast()) };
result
}
#[inline]
#[must_use]
pub fn as_i32(&self) -> i32 {
unsafe { duckdb_get_int32(self.raw) }
}
#[inline]
#[must_use]
pub fn as_i64(&self) -> i64 {
unsafe { duckdb_get_int64(self.raw) }
}
#[inline]
#[must_use]
pub fn as_f32(&self) -> f32 {
unsafe { duckdb_get_float(self.raw) }
}
#[inline]
#[must_use]
pub fn as_f64(&self) -> f64 {
unsafe { duckdb_get_double(self.raw) }
}
#[inline]
#[must_use]
pub fn as_bool(&self) -> bool {
unsafe { duckdb_get_bool(self.raw) }
}
#[inline]
#[must_use]
pub fn as_i8(&self) -> i8 {
unsafe { duckdb_get_int8(self.raw) }
}
#[inline]
#[must_use]
pub fn as_i16(&self) -> i16 {
unsafe { duckdb_get_int16(self.raw) }
}
#[inline]
#[must_use]
pub fn as_u8(&self) -> u8 {
unsafe { duckdb_get_uint8(self.raw) }
}
#[inline]
#[must_use]
pub fn as_u16(&self) -> u16 {
unsafe { duckdb_get_uint16(self.raw) }
}
#[inline]
#[must_use]
pub fn as_u32(&self) -> u32 {
unsafe { duckdb_get_uint32(self.raw) }
}
#[inline]
#[must_use]
pub fn as_u64(&self) -> u64 {
unsafe { duckdb_get_uint64(self.raw) }
}
#[inline]
#[must_use]
pub fn as_i128(&self) -> i128 {
let h = unsafe { duckdb_get_hugeint(self.raw) };
#[allow(clippy::cast_lossless)]
let result = (h.upper as i128) << 64 | (h.lower as i128);
result
}
#[inline]
#[must_use]
pub fn as_str_or(&self, default: &str) -> String {
self.as_str().unwrap_or_else(|_| default.to_owned())
}
#[inline]
#[must_use]
pub fn as_str_or_default(&self) -> String {
self.as_str().unwrap_or_default()
}
#[inline]
#[must_use]
pub fn as_i32_or(&self, default: i32) -> i32 {
if self.is_null() {
default
} else {
self.as_i32()
}
}
#[inline]
#[must_use]
pub fn as_i64_or(&self, default: i64) -> i64 {
if self.is_null() {
default
} else {
self.as_i64()
}
}
#[inline]
#[must_use]
pub fn as_f32_or(&self, default: f32) -> f32 {
if self.is_null() {
default
} else {
self.as_f32()
}
}
#[inline]
#[must_use]
pub fn as_f64_or(&self, default: f64) -> f64 {
if self.is_null() {
default
} else {
self.as_f64()
}
}
#[inline]
#[must_use]
pub fn as_bool_or(&self, default: bool) -> bool {
if self.is_null() {
default
} else {
self.as_bool()
}
}
#[inline]
#[must_use]
pub fn as_i8_or(&self, default: i8) -> i8 {
if self.is_null() {
default
} else {
self.as_i8()
}
}
#[inline]
#[must_use]
pub fn as_i16_or(&self, default: i16) -> i16 {
if self.is_null() {
default
} else {
self.as_i16()
}
}
#[inline]
#[must_use]
pub fn as_u8_or(&self, default: u8) -> u8 {
if self.is_null() {
default
} else {
self.as_u8()
}
}
#[inline]
#[must_use]
pub fn as_u16_or(&self, default: u16) -> u16 {
if self.is_null() {
default
} else {
self.as_u16()
}
}
#[inline]
#[must_use]
pub fn as_u32_or(&self, default: u32) -> u32 {
if self.is_null() {
default
} else {
self.as_u32()
}
}
#[inline]
#[must_use]
pub fn as_u64_or(&self, default: u64) -> u64 {
if self.is_null() {
default
} else {
self.as_u64()
}
}
#[inline]
#[must_use]
pub fn as_i128_or(&self, default: i128) -> i128 {
if self.is_null() {
default
} else {
self.as_i128()
}
}
#[inline]
#[must_use]
pub const fn is_null(&self) -> bool {
self.raw.is_null()
}
#[inline]
#[must_use]
pub const fn as_raw(&self) -> duckdb_value {
self.raw
}
#[inline]
#[must_use]
pub const fn into_raw(self) -> duckdb_value {
let raw = self.raw;
std::mem::forget(self);
raw
}
}
impl Drop for Value {
fn drop(&mut self) {
if !self.raw.is_null() {
unsafe { duckdb_destroy_value(&raw mut self.raw) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn null_value_is_null() {
let val = unsafe { Value::from_raw(std::ptr::null_mut()) };
assert!(val.is_null());
}
#[test]
fn null_value_as_str_returns_error() {
let val = unsafe { Value::from_raw(std::ptr::null_mut()) };
assert!(val.as_str().is_err());
}
#[test]
fn into_raw_prevents_double_free() {
let val = unsafe { Value::from_raw(std::ptr::null_mut()) };
let raw = val.into_raw();
assert!(raw.is_null());
}
#[test]
fn size_of_value() {
assert_eq!(std::mem::size_of::<Value>(), std::mem::size_of::<usize>());
}
#[test]
fn as_str_or_returns_default_for_null() {
let val = unsafe { Value::from_raw(std::ptr::null_mut()) };
assert_eq!(val.as_str_or("fallback"), "fallback");
}
#[test]
fn as_str_or_default_returns_empty_for_null() {
let val = unsafe { Value::from_raw(std::ptr::null_mut()) };
assert_eq!(val.as_str_or_default(), "");
}
#[test]
fn as_i64_or_returns_default_for_null() {
let val = unsafe { Value::from_raw(std::ptr::null_mut()) };
assert_eq!(val.as_i64_or(99), 99);
}
#[test]
fn as_i32_or_returns_default_for_null() {
let val = unsafe { Value::from_raw(std::ptr::null_mut()) };
assert_eq!(val.as_i32_or(42), 42);
}
#[test]
fn as_bool_or_returns_default_for_null() {
let val = unsafe { Value::from_raw(std::ptr::null_mut()) };
assert!(val.as_bool_or(true));
assert!(!val.as_bool_or(false));
}
#[test]
fn as_f64_or_returns_default_for_null() {
let val = unsafe { Value::from_raw(std::ptr::null_mut()) };
assert!((val.as_f64_or(2.72) - 2.72).abs() < f64::EPSILON);
}
#[test]
fn as_f32_or_returns_default_for_null() {
let val = unsafe { Value::from_raw(std::ptr::null_mut()) };
assert!((val.as_f32_or(2.5) - 2.5).abs() < f32::EPSILON);
}
}