use std::ffi::CString;
use std::os::raw::{c_char, c_void};
use libduckdb_sys::{
duckdb_add_replacement_scan, duckdb_create_varchar_length, duckdb_database,
duckdb_delete_callback_t, duckdb_destroy_value, duckdb_replacement_scan_add_parameter,
duckdb_replacement_scan_info, duckdb_replacement_scan_set_error,
duckdb_replacement_scan_set_function_name, duckdb_value,
};
fn str_to_cstring(s: &str) -> CString {
CString::new(s).unwrap_or_else(|_| {
let pos = s.bytes().position(|b| b == 0).unwrap_or(s.len());
CString::new(&s.as_bytes()[..pos]).unwrap_or_default()
})
}
pub type ReplacementScanFn = unsafe extern "C" fn(
info: duckdb_replacement_scan_info,
table_name: *const c_char,
data: *mut c_void,
);
pub struct ReplacementScanInfo {
info: duckdb_replacement_scan_info,
}
impl ReplacementScanInfo {
#[inline]
#[must_use]
pub const unsafe fn new(info: duckdb_replacement_scan_info) -> Self {
Self { info }
}
pub fn set_function(&self, function_name: &str) -> &Self {
let c_name = str_to_cstring(function_name);
unsafe {
duckdb_replacement_scan_set_function_name(self.info, c_name.as_ptr());
}
self
}
pub fn add_varchar_parameter(&self, value: &str) -> &Self {
let duckdb_val = unsafe {
duckdb_create_varchar_length(
value.as_ptr().cast::<std::os::raw::c_char>(),
value.len() as u64,
)
};
unsafe {
duckdb_replacement_scan_add_parameter(self.info, duckdb_val);
}
let mut val = duckdb_val;
unsafe {
duckdb_destroy_value(&raw mut val);
}
self
}
pub unsafe fn add_parameter_raw(&self, value: duckdb_value) -> &Self {
unsafe {
duckdb_replacement_scan_add_parameter(self.info, value);
}
self
}
pub fn add_i64_parameter(&self, value: i64) -> &Self {
let duckdb_val = unsafe { libduckdb_sys::duckdb_create_int64(value) };
unsafe {
duckdb_replacement_scan_add_parameter(self.info, duckdb_val);
}
let mut val = duckdb_val;
unsafe {
duckdb_destroy_value(&raw mut val);
}
self
}
pub fn add_bool_parameter(&self, value: bool) -> &Self {
let duckdb_val = unsafe { libduckdb_sys::duckdb_create_bool(value) };
unsafe {
duckdb_replacement_scan_add_parameter(self.info, duckdb_val);
}
let mut val = duckdb_val;
unsafe {
duckdb_destroy_value(&raw mut val);
}
self
}
#[mutants::skip] pub fn set_error(&self, message: &str) {
let c_msg = str_to_cstring(message);
unsafe {
duckdb_replacement_scan_set_error(self.info, c_msg.as_ptr());
}
}
#[must_use]
#[inline]
pub const fn as_raw(&self) -> duckdb_replacement_scan_info {
self.info
}
}
pub struct ReplacementScanBuilder;
impl ReplacementScanBuilder {
pub unsafe fn register(
db: duckdb_database,
callback: ReplacementScanFn,
extra_data: *mut c_void,
delete_callback: duckdb_delete_callback_t,
) {
unsafe {
duckdb_add_replacement_scan(db, Some(callback), extra_data, delete_callback);
}
}
pub unsafe fn register_with_data<T: 'static>(
db: duckdb_database,
callback: ReplacementScanFn,
data: T,
) {
unsafe extern "C" fn drop_box<T>(ptr: *mut c_void) {
if !ptr.is_null() {
unsafe { drop(Box::from_raw(ptr.cast::<T>())) };
}
}
let raw = Box::into_raw(Box::new(data)).cast::<c_void>();
unsafe {
duckdb_add_replacement_scan(db, Some(callback), raw, Some(drop_box::<T>));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn replacement_scan_info_wraps_null() {
let _info = unsafe { ReplacementScanInfo::new(std::ptr::null_mut()) };
}
#[test]
fn str_to_cstring_truncates_at_null() {
let c = super::str_to_cstring("bad\0message");
assert_eq!(c.to_str().unwrap(), "bad");
}
}