use std::any::Any;
use std::os::raw::c_void;
use std::panic::{AssertUnwindSafe, catch_unwind};
use libduckdb_sys::duckdb_delete_callback_t;
use quack_rs::aggregate::AggregateFunctionInfo;
use quack_rs::prelude::{CastFunctionInfo, ScalarFunctionInfo};
use quack_rs::table::{BindInfo, FunctionInfo, InitInfo};
pub struct DuckExtraInfo(Box<dyn Any + Send + Sync>);
impl std::panic::UnwindSafe for DuckExtraInfo {}
impl std::panic::RefUnwindSafe for DuckExtraInfo {}
impl DuckExtraInfo {
pub fn new<T: Any + Send + Sync>(value: T) -> Self {
Self(Box::new(value))
}
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
self.0.downcast_ref::<T>()
}
#[must_use]
pub fn is<T: Any>(&self) -> bool {
self.0.is::<T>()
}
pub(crate) fn into_raw(self) -> *mut c_void {
Box::into_raw(Box::new(self)).cast::<c_void>()
}
pub(crate) unsafe fn from_raw<'a>(ptr: *mut c_void) -> Option<&'a Self> {
unsafe { ptr.cast::<Self>().as_ref() }
}
}
impl std::fmt::Debug for DuckExtraInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DuckExtraInfo").finish_non_exhaustive()
}
}
pub(crate) unsafe extern "C" fn destroy_extra_info(ptr: *mut c_void) {
if ptr.is_null() {
return;
}
let _ = catch_unwind(AssertUnwindSafe(|| {
unsafe { drop(Box::from_raw(ptr.cast::<DuckExtraInfo>())) };
}));
}
pub(crate) fn raw_extra_info(
extra: Option<DuckExtraInfo>,
) -> Option<(*mut c_void, duckdb_delete_callback_t)> {
extra.map(|extra| {
(
extra.into_raw(),
Some(destroy_extra_info as unsafe extern "C" fn(*mut c_void)),
)
})
}
pub trait DuckExtraInfoSource {
unsafe fn raw_extra_info(&self) -> *mut c_void;
}
impl DuckExtraInfoSource for ScalarFunctionInfo {
unsafe fn raw_extra_info(&self) -> *mut c_void {
unsafe { self.get_extra_info() }
}
}
impl DuckExtraInfoSource for AggregateFunctionInfo {
unsafe fn raw_extra_info(&self) -> *mut c_void {
unsafe { self.get_extra_info() }
}
}
impl DuckExtraInfoSource for CastFunctionInfo {
unsafe fn raw_extra_info(&self) -> *mut c_void {
unsafe { self.get_extra_info() }
}
}
impl DuckExtraInfoSource for BindInfo {
unsafe fn raw_extra_info(&self) -> *mut c_void {
unsafe { self.get_extra_info() }
}
}
impl DuckExtraInfoSource for InitInfo {
unsafe fn raw_extra_info(&self) -> *mut c_void {
unsafe { self.get_extra_info() }
}
}
impl DuckExtraInfoSource for FunctionInfo {
unsafe fn raw_extra_info(&self) -> *mut c_void {
unsafe { self.get_extra_info() }
}
}
#[cfg(feature = "duckdb-1-5")]
mod copy_sources {
use super::{DuckExtraInfoSource, c_void};
use quack_rs::copy_function::{CopyBindInfo, CopyFinalizeInfo, CopyGlobalInitInfo, CopySinkInfo};
impl DuckExtraInfoSource for CopyBindInfo {
unsafe fn raw_extra_info(&self) -> *mut c_void {
unsafe { self.get_extra_info() }
}
}
impl DuckExtraInfoSource for CopyGlobalInitInfo {
unsafe fn raw_extra_info(&self) -> *mut c_void {
unsafe { self.get_extra_info() }
}
}
impl DuckExtraInfoSource for CopySinkInfo {
unsafe fn raw_extra_info(&self) -> *mut c_void {
unsafe { self.get_extra_info() }
}
}
impl DuckExtraInfoSource for CopyFinalizeInfo {
unsafe fn raw_extra_info(&self) -> *mut c_void {
unsafe { self.get_extra_info() }
}
}
}
pub unsafe fn erased_extra_info(source: &impl DuckExtraInfoSource) -> Option<&DuckExtraInfo> {
let ptr = unsafe { source.raw_extra_info() };
unsafe { DuckExtraInfo::from_raw(ptr) }
}
pub unsafe fn extra_info_ref<T: Any>(source: &impl DuckExtraInfoSource) -> Option<&T> {
unsafe { erased_extra_info(source) }?.downcast_ref::<T>()
}
#[cfg(test)]
mod tests {
use super::{DuckExtraInfo, destroy_extra_info, raw_extra_info};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn downcast_matches_the_original_type() {
let info = DuckExtraInfo::new(41_i32);
assert!(info.is::<i32>());
assert_eq!(info.downcast_ref::<i32>(), Some(&41));
assert!(!info.is::<u8>());
assert_eq!(info.downcast_ref::<u8>(), None);
assert!(!format!("{info:?}").is_empty());
}
#[test]
fn raw_round_trip_keeps_the_value() {
let ptr = DuckExtraInfo::new(String::from("cfg")).into_raw();
assert!(!ptr.is_null());
let back = unsafe { DuckExtraInfo::from_raw(ptr) }.expect("non-null pointer");
assert_eq!(back.downcast_ref::<String>().map(String::as_str), Some("cfg"));
unsafe { destroy_extra_info(ptr) };
}
#[test]
fn null_pointer_yields_none_and_destruction_is_a_noop() {
assert!(unsafe { DuckExtraInfo::from_raw(std::ptr::null_mut()) }.is_none());
unsafe { destroy_extra_info(std::ptr::null_mut()) };
}
#[test]
fn nothing_attached_produces_no_raw_pair() {
assert!(raw_extra_info(None).is_none());
}
#[test]
fn destructor_runs_the_value_drop_once() {
struct Tracker(Arc<AtomicUsize>);
impl Drop for Tracker {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
let counter = Arc::new(AtomicUsize::new(0));
let (ptr, destroy) = raw_extra_info(Some(DuckExtraInfo::new(Tracker(Arc::clone(&counter)))))
.expect("extra info was attached");
assert!(destroy.is_some());
assert_eq!(counter.load(Ordering::SeqCst), 0);
unsafe { destroy_extra_info(ptr) };
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
}