use std::ffi::CStr;
use libduckdb_sys::{
duckdb_append_data_chunk, duckdb_append_default_to_chunk, duckdb_appender,
duckdb_appender_clear, duckdb_appender_close, duckdb_appender_create,
duckdb_appender_create_ext, duckdb_appender_destroy, duckdb_appender_error_data,
duckdb_appender_flush, duckdb_connection, duckdb_state, DuckDBSuccess,
};
use crate::data_chunk::DataChunk;
use crate::error_data::ErrorData;
#[inline]
fn opt_ptr(s: Option<&CStr>) -> *const std::os::raw::c_char {
s.map_or(std::ptr::null(), CStr::as_ptr)
}
pub struct Appender {
appender: duckdb_appender,
}
impl Appender {
pub unsafe fn new(
con: duckdb_connection,
schema: Option<&CStr>,
table: &CStr,
) -> Result<Self, ErrorData> {
let mut raw: duckdb_appender = std::ptr::null_mut();
let state =
unsafe { duckdb_appender_create(con, opt_ptr(schema), table.as_ptr(), &raw mut raw) };
let appender = Self { appender: raw };
if state == DuckDBSuccess {
Ok(appender)
} else {
Err(appender.error_data())
}
}
pub unsafe fn with_catalog(
con: duckdb_connection,
catalog: Option<&CStr>,
schema: Option<&CStr>,
table: &CStr,
) -> Result<Self, ErrorData> {
let mut raw: duckdb_appender = std::ptr::null_mut();
let state = unsafe {
duckdb_appender_create_ext(
con,
opt_ptr(catalog),
opt_ptr(schema),
table.as_ptr(),
&raw mut raw,
)
};
let appender = Self { appender: raw };
if state == DuckDBSuccess {
Ok(appender)
} else {
Err(appender.error_data())
}
}
pub fn append_chunk(&self, chunk: &DataChunk) -> Result<(), ErrorData> {
let state = unsafe { duckdb_append_data_chunk(self.appender, chunk.as_raw()) };
self.check(state)
}
pub fn append_default_to_chunk(
&self,
chunk: &DataChunk,
col: u64,
row: u64,
) -> Result<(), ErrorData> {
let state =
unsafe { duckdb_append_default_to_chunk(self.appender, chunk.as_raw(), col, row) };
self.check(state)
}
pub fn flush(&self) -> Result<(), ErrorData> {
let state = unsafe { duckdb_appender_flush(self.appender) };
self.check(state)
}
pub fn close(&self) -> Result<(), ErrorData> {
let state = unsafe { duckdb_appender_close(self.appender) };
self.check(state)
}
pub fn clear(&self) -> Result<(), ErrorData> {
let state = unsafe { duckdb_appender_clear(self.appender) };
self.check(state)
}
#[must_use]
pub fn error_data(&self) -> ErrorData {
let raw = unsafe { duckdb_appender_error_data(self.appender) };
unsafe { ErrorData::from_raw(raw) }
}
#[inline]
#[must_use]
pub const fn as_raw(&self) -> duckdb_appender {
self.appender
}
fn check(&self, state: duckdb_state) -> Result<(), ErrorData> {
if state == DuckDBSuccess {
Ok(())
} else {
Err(self.error_data())
}
}
}
impl Drop for Appender {
fn drop(&mut self) {
if !self.appender.is_null() {
unsafe { duckdb_appender_destroy(&raw mut self.appender) };
}
}
}
#[cfg(all(test, feature = "bundled-test"))]
mod tests {
use super::*;
#[test]
fn create_for_missing_table_reports_error() {
let _db = crate::testing::InMemoryDb::open().unwrap();
let mut db: libduckdb_sys::duckdb_database = std::ptr::null_mut();
let mut con: duckdb_connection = std::ptr::null_mut();
unsafe {
assert_eq!(
libduckdb_sys::duckdb_open(std::ptr::null(), &raw mut db),
DuckDBSuccess
);
assert_eq!(
libduckdb_sys::duckdb_connect(db, &raw mut con),
DuckDBSuccess
);
}
let result = unsafe { Appender::new(con, None, c"does_not_exist") };
assert!(result.is_err(), "expected create to fail for missing table");
let err = result.err().unwrap();
assert!(err.has_error());
unsafe {
libduckdb_sys::duckdb_disconnect(&raw mut con);
libduckdb_sys::duckdb_close(&raw mut db);
}
}
}