use crate::types::TypeId;
use libduckdb_sys::{
duckdb_array_type_array_size, duckdb_array_type_child_type, duckdb_create_array_type,
duckdb_create_decimal_type, duckdb_create_enum_type, duckdb_create_list_type,
duckdb_create_logical_type, duckdb_create_map_type, duckdb_create_struct_type,
duckdb_create_union_type, duckdb_decimal_internal_type, duckdb_decimal_scale,
duckdb_decimal_width, duckdb_destroy_logical_type, duckdb_enum_dictionary_size,
duckdb_enum_dictionary_value, duckdb_enum_internal_type, duckdb_free, duckdb_get_type_id,
duckdb_list_type_child_type, duckdb_logical_type, duckdb_logical_type_get_alias,
duckdb_logical_type_set_alias, duckdb_map_type_key_type, duckdb_map_type_value_type,
duckdb_struct_type_child_count, duckdb_struct_type_child_name, duckdb_struct_type_child_type,
duckdb_union_type_member_count, duckdb_union_type_member_name, duckdb_union_type_member_type,
};
use std::fmt;
#[derive(Debug, Clone)]
pub struct LogicalTypeError {
api_func: &'static str,
}
impl fmt::Display for LogicalTypeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} returned null", self.api_func)
}
}
impl std::error::Error for LogicalTypeError {}
pub struct LogicalType {
inner: duckdb_logical_type,
}
impl LogicalType {
#[must_use]
pub unsafe fn from_raw(ptr: duckdb_logical_type) -> Self {
assert!(
!ptr.is_null(),
"LogicalType::from_raw called with null pointer"
);
Self { inner: ptr }
}
#[must_use]
pub fn new(type_id: TypeId) -> Self {
let inner = unsafe { duckdb_create_logical_type(type_id.to_duckdb_type()) };
assert!(!inner.is_null(), "duckdb_create_logical_type returned null");
Self { inner }
}
#[must_use]
pub fn list(element_type: TypeId) -> Self {
let element_lt = Self::new(element_type);
let inner = unsafe { duckdb_create_list_type(element_lt.as_raw()) };
assert!(!inner.is_null(), "duckdb_create_list_type returned null");
Self { inner }
}
#[must_use]
pub fn map(key_type: TypeId, value_type: TypeId) -> Self {
let key_lt = Self::new(key_type);
let val_lt = Self::new(value_type);
let inner = unsafe { duckdb_create_map_type(key_lt.as_raw(), val_lt.as_raw()) };
assert!(!inner.is_null(), "duckdb_create_map_type returned null");
Self { inner }
}
#[must_use]
pub fn struct_type(fields: &[(&str, TypeId)]) -> Self {
use std::ffi::CString;
let field_types: Vec<Self> = fields.iter().map(|&(_, t)| Self::new(t)).collect();
let c_names: Vec<CString> = fields
.iter()
.map(|&(n, _)| CString::new(n).expect("field name must not contain null bytes"))
.collect();
let mut type_ptrs: Vec<duckdb_logical_type> =
field_types.iter().map(Self::as_raw).collect();
let mut name_ptrs: Vec<*const std::os::raw::c_char> =
c_names.iter().map(|s| s.as_ptr()).collect();
let inner = unsafe {
duckdb_create_struct_type(
type_ptrs.as_mut_ptr(),
name_ptrs.as_mut_ptr(),
fields.len() as libduckdb_sys::idx_t,
)
};
assert!(!inner.is_null(), "duckdb_create_struct_type returned null");
Self { inner }
}
#[must_use]
pub fn decimal(width: u8, scale: u8) -> Self {
let inner = unsafe { duckdb_create_decimal_type(width, scale) };
assert!(!inner.is_null(), "duckdb_create_decimal_type returned null");
Self { inner }
}
#[must_use]
pub fn array(element_type: TypeId, size: u64) -> Self {
let element_lt = Self::new(element_type);
let inner =
unsafe { duckdb_create_array_type(element_lt.as_raw(), size as libduckdb_sys::idx_t) };
assert!(!inner.is_null(), "duckdb_create_array_type returned null");
Self { inner }
}
#[must_use]
pub fn array_from_logical(element: &Self, size: u64) -> Self {
let inner =
unsafe { duckdb_create_array_type(element.as_raw(), size as libduckdb_sys::idx_t) };
assert!(!inner.is_null(), "duckdb_create_array_type returned null");
Self { inner }
}
#[must_use]
pub fn union_type(members: &[(&str, TypeId)]) -> Self {
use std::ffi::CString;
let member_types: Vec<Self> = members.iter().map(|&(_, t)| Self::new(t)).collect();
let c_names: Vec<CString> = members
.iter()
.map(|&(n, _)| CString::new(n).expect("member name must not contain null bytes"))
.collect();
let mut type_ptrs: Vec<duckdb_logical_type> =
member_types.iter().map(Self::as_raw).collect();
let mut name_ptrs: Vec<*const std::os::raw::c_char> =
c_names.iter().map(|s| s.as_ptr()).collect();
let inner = unsafe {
duckdb_create_union_type(
type_ptrs.as_mut_ptr(),
name_ptrs.as_mut_ptr(),
members.len() as libduckdb_sys::idx_t,
)
};
assert!(!inner.is_null(), "duckdb_create_union_type returned null");
Self { inner }
}
#[must_use]
pub fn union_type_from_logical(members: &[(&str, Self)]) -> Self {
use std::ffi::CString;
let c_names: Vec<CString> = members
.iter()
.map(|&(n, _)| CString::new(n).expect("member name must not contain null bytes"))
.collect();
let mut type_ptrs: Vec<duckdb_logical_type> =
members.iter().map(|(_, lt)| lt.as_raw()).collect();
let mut name_ptrs: Vec<*const std::os::raw::c_char> =
c_names.iter().map(|s| s.as_ptr()).collect();
let inner = unsafe {
duckdb_create_union_type(
type_ptrs.as_mut_ptr(),
name_ptrs.as_mut_ptr(),
members.len() as libduckdb_sys::idx_t,
)
};
assert!(!inner.is_null(), "duckdb_create_union_type returned null");
Self { inner }
}
#[must_use]
pub fn enum_type(members: &[&str]) -> Self {
use std::ffi::CString;
let c_names: Vec<CString> = members
.iter()
.map(|n| CString::new(*n).expect("enum member name must not contain null bytes"))
.collect();
let mut name_ptrs: Vec<*const std::os::raw::c_char> =
c_names.iter().map(|s| s.as_ptr()).collect();
let inner = unsafe {
duckdb_create_enum_type(
name_ptrs.as_mut_ptr(),
members.len() as libduckdb_sys::idx_t,
)
};
assert!(!inner.is_null(), "duckdb_create_enum_type returned null");
Self { inner }
}
#[must_use]
pub fn list_from_logical(element: &Self) -> Self {
let inner = unsafe { duckdb_create_list_type(element.as_raw()) };
assert!(!inner.is_null(), "duckdb_create_list_type returned null");
Self { inner }
}
#[must_use]
pub fn map_from_logical(key: &Self, value: &Self) -> Self {
let inner = unsafe { duckdb_create_map_type(key.as_raw(), value.as_raw()) };
assert!(!inner.is_null(), "duckdb_create_map_type returned null");
Self { inner }
}
#[must_use]
pub fn struct_type_from_logical(fields: &[(&str, Self)]) -> Self {
use std::ffi::CString;
let c_names: Vec<CString> = fields
.iter()
.map(|&(n, _)| CString::new(n).expect("field name must not contain null bytes"))
.collect();
let mut type_ptrs: Vec<duckdb_logical_type> =
fields.iter().map(|(_, lt)| lt.as_raw()).collect();
let mut name_ptrs: Vec<*const std::os::raw::c_char> =
c_names.iter().map(|s| s.as_ptr()).collect();
let inner = unsafe {
duckdb_create_struct_type(
type_ptrs.as_mut_ptr(),
name_ptrs.as_mut_ptr(),
fields.len() as libduckdb_sys::idx_t,
)
};
assert!(!inner.is_null(), "duckdb_create_struct_type returned null");
Self { inner }
}
pub fn try_new(type_id: TypeId) -> Result<Self, LogicalTypeError> {
let inner = unsafe { duckdb_create_logical_type(type_id.to_duckdb_type()) };
if inner.is_null() {
return Err(LogicalTypeError {
api_func: "duckdb_create_logical_type",
});
}
Ok(Self { inner })
}
pub fn try_list(element_type: TypeId) -> Result<Self, LogicalTypeError> {
let element_lt = Self::try_new(element_type)?;
let inner = unsafe { duckdb_create_list_type(element_lt.as_raw()) };
if inner.is_null() {
return Err(LogicalTypeError {
api_func: "duckdb_create_list_type",
});
}
Ok(Self { inner })
}
pub fn try_map(key_type: TypeId, value_type: TypeId) -> Result<Self, LogicalTypeError> {
let key_lt = Self::try_new(key_type)?;
let val_lt = Self::try_new(value_type)?;
let inner = unsafe { duckdb_create_map_type(key_lt.as_raw(), val_lt.as_raw()) };
if inner.is_null() {
return Err(LogicalTypeError {
api_func: "duckdb_create_map_type",
});
}
Ok(Self { inner })
}
pub fn try_struct_type(fields: &[(&str, TypeId)]) -> Result<Self, LogicalTypeError> {
use std::ffi::CString;
let field_types: Vec<Self> = fields
.iter()
.map(|&(_, t)| Self::try_new(t))
.collect::<Result<_, _>>()?;
let c_names: Vec<CString> = fields
.iter()
.map(|&(n, _)| {
CString::new(n).map_err(|_| LogicalTypeError {
api_func: "CString::new (field name contains null byte)",
})
})
.collect::<Result<_, _>>()?;
let mut type_ptrs: Vec<duckdb_logical_type> =
field_types.iter().map(Self::as_raw).collect();
let mut name_ptrs: Vec<*const std::os::raw::c_char> =
c_names.iter().map(|s| s.as_ptr()).collect();
let inner = unsafe {
duckdb_create_struct_type(
type_ptrs.as_mut_ptr(),
name_ptrs.as_mut_ptr(),
fields.len() as libduckdb_sys::idx_t,
)
};
if inner.is_null() {
return Err(LogicalTypeError {
api_func: "duckdb_create_struct_type",
});
}
Ok(Self { inner })
}
#[must_use]
pub unsafe fn get_type_id(&self) -> TypeId {
TypeId::from_duckdb_type(unsafe { duckdb_get_type_id(self.inner) })
}
#[must_use]
pub unsafe fn get_alias(&self) -> Option<String> {
let ptr = unsafe { duckdb_logical_type_get_alias(self.inner) };
if ptr.is_null() {
return None;
}
let s = unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned();
unsafe { duckdb_free(ptr.cast::<core::ffi::c_void>()) };
Some(s)
}
pub unsafe fn set_alias(&self, alias: &str) {
let c_alias = std::ffi::CString::new(alias).expect("alias must not contain null bytes");
unsafe { duckdb_logical_type_set_alias(self.inner, c_alias.as_ptr()) };
}
#[must_use]
pub unsafe fn decimal_width(&self) -> u8 {
unsafe { duckdb_decimal_width(self.inner) }
}
#[must_use]
pub unsafe fn decimal_scale(&self) -> u8 {
unsafe { duckdb_decimal_scale(self.inner) }
}
#[must_use]
pub unsafe fn decimal_internal_type(&self) -> TypeId {
TypeId::from_duckdb_type(unsafe { duckdb_decimal_internal_type(self.inner) })
}
#[must_use]
pub unsafe fn enum_internal_type(&self) -> TypeId {
TypeId::from_duckdb_type(unsafe { duckdb_enum_internal_type(self.inner) })
}
#[must_use]
pub unsafe fn enum_dictionary_size(&self) -> u32 {
unsafe { duckdb_enum_dictionary_size(self.inner) }
}
#[must_use]
pub unsafe fn enum_dictionary_value(&self, index: u64) -> String {
let ptr =
unsafe { duckdb_enum_dictionary_value(self.inner, index as libduckdb_sys::idx_t) };
assert!(!ptr.is_null(), "duckdb_enum_dictionary_value returned null");
let s = unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned();
unsafe { duckdb_free(ptr.cast::<core::ffi::c_void>()) };
s
}
#[must_use]
pub unsafe fn list_child_type(&self) -> Self {
unsafe { Self::from_raw(duckdb_list_type_child_type(self.inner)) }
}
#[must_use]
pub unsafe fn map_key_type(&self) -> Self {
unsafe { Self::from_raw(duckdb_map_type_key_type(self.inner)) }
}
#[must_use]
pub unsafe fn map_value_type(&self) -> Self {
unsafe { Self::from_raw(duckdb_map_type_value_type(self.inner)) }
}
#[must_use]
pub unsafe fn struct_child_count(&self) -> u64 {
unsafe { duckdb_struct_type_child_count(self.inner) as u64 }
}
#[must_use]
pub unsafe fn struct_child_name(&self, index: u64) -> String {
unsafe {
let ptr = duckdb_struct_type_child_name(self.inner, index as libduckdb_sys::idx_t);
assert!(
!ptr.is_null(),
"duckdb_struct_type_child_name returned null"
);
let s = std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned();
duckdb_free(ptr.cast::<core::ffi::c_void>());
s
}
}
#[must_use]
pub unsafe fn struct_child_type(&self, index: u64) -> Self {
unsafe {
Self::from_raw(duckdb_struct_type_child_type(
self.inner,
index as libduckdb_sys::idx_t,
))
}
}
#[must_use]
pub unsafe fn union_member_count(&self) -> u64 {
unsafe { duckdb_union_type_member_count(self.inner) as u64 }
}
#[must_use]
pub unsafe fn union_member_name(&self, index: u64) -> String {
unsafe {
let ptr = duckdb_union_type_member_name(self.inner, index as libduckdb_sys::idx_t);
assert!(
!ptr.is_null(),
"duckdb_union_type_member_name returned null"
);
let s = std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned();
duckdb_free(ptr.cast::<core::ffi::c_void>());
s
}
}
#[must_use]
pub unsafe fn union_member_type(&self, index: u64) -> Self {
unsafe {
Self::from_raw(duckdb_union_type_member_type(
self.inner,
index as libduckdb_sys::idx_t,
))
}
}
#[must_use]
pub unsafe fn array_size(&self) -> u64 {
unsafe { duckdb_array_type_array_size(self.inner) as u64 }
}
#[must_use]
pub unsafe fn array_child_type(&self) -> Self {
unsafe { Self::from_raw(duckdb_array_type_child_type(self.inner)) }
}
#[must_use]
#[inline]
pub const fn as_raw(&self) -> duckdb_logical_type {
self.inner
}
#[must_use]
pub const fn into_raw(self) -> duckdb_logical_type {
let raw = self.inner;
std::mem::forget(self);
raw
}
}
impl Drop for LogicalType {
#[mutants::skip]
fn drop(&mut self) {
unsafe {
duckdb_destroy_logical_type(&raw mut self.inner);
}
}
}
impl From<TypeId> for LogicalType {
fn from(type_id: TypeId) -> Self {
Self::new(type_id)
}
}
#[cfg(test)]
mod tests {
#[test]
fn logical_type_error_display() {
let err = super::LogicalTypeError {
api_func: "duckdb_create_logical_type",
};
assert_eq!(err.to_string(), "duckdb_create_logical_type returned null");
}
#[test]
fn size_of_logical_type_struct() {
use super::LogicalType;
assert_eq!(
std::mem::size_of::<LogicalType>(),
std::mem::size_of::<*mut ()>()
);
}
}