use std::ffi::CStr;
use libduckdb_sys::{
duckdb_append_blob, duckdb_append_data_chunk, duckdb_append_date, duckdb_append_default,
duckdb_append_hugeint, duckdb_append_interval, duckdb_append_null, duckdb_append_time,
duckdb_append_timestamp, duckdb_append_uhugeint, duckdb_append_value,
duckdb_append_varchar_length, duckdb_appender, duckdb_appender_add_column,
duckdb_appender_clear_columns, duckdb_appender_close, duckdb_appender_column_count,
duckdb_appender_column_type, duckdb_appender_create, duckdb_appender_create_ext,
duckdb_appender_destroy, duckdb_appender_end_row, duckdb_appender_error, duckdb_appender_flush,
duckdb_connection, duckdb_date, duckdb_hugeint, duckdb_interval, duckdb_state, duckdb_time,
duckdb_timestamp, duckdb_uhugeint, idx_t, DuckDBSuccess,
};
#[cfg(feature = "duckdb-1-5")]
use libduckdb_sys::{duckdb_append_default_to_chunk, duckdb_appender_clear};
use crate::data_chunk::DataChunk;
#[cfg(feature = "duckdb-1-5")]
use crate::error_data::ErrorData;
use crate::interval::DuckInterval;
use crate::types::LogicalType;
use crate::value::Value;
#[cfg(feature = "duckdb-1-5")]
pub type AppendError = ErrorData;
#[cfg(not(feature = "duckdb-1-5"))]
pub type AppendError = crate::error::ExtensionError;
const MAX_VARCHAR_LEN: usize = u32::MAX as usize;
#[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, AppendError> {
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.last_error())
}
}
pub unsafe fn with_catalog(
con: duckdb_connection,
catalog: Option<&CStr>,
schema: Option<&CStr>,
table: &CStr,
) -> Result<Self, AppendError> {
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.last_error())
}
}
#[must_use]
pub fn column_count(&self) -> u64 {
unsafe { duckdb_appender_column_count(self.appender) }
}
#[must_use]
pub fn column_type(&self, index: u64) -> Option<LogicalType> {
let raw = unsafe { duckdb_appender_column_type(self.appender, index as idx_t) };
if raw.is_null() {
None
} else {
Some(unsafe { LogicalType::from_raw(raw) })
}
}
pub fn add_column(&self, name: &CStr) -> Result<(), AppendError> {
let state = unsafe { duckdb_appender_add_column(self.appender, name.as_ptr()) };
self.check(state)
}
pub fn clear_columns(&self) -> Result<(), AppendError> {
let state = unsafe { duckdb_appender_clear_columns(self.appender) };
self.check(state)
}
pub fn row<F>(&self, append: F) -> Result<(), AppendError>
where
F: FnOnce(&Self) -> Result<(), AppendError>,
{
append(self)?;
self.end_row()
}
pub fn end_row(&self) -> Result<(), AppendError> {
let state = unsafe { duckdb_appender_end_row(self.appender) };
self.check(state)
}
pub fn append_null(&self) -> Result<(), AppendError> {
self.check(unsafe { duckdb_append_null(self.appender) })
}
pub fn append_default(&self) -> Result<(), AppendError> {
self.check(unsafe { duckdb_append_default(self.appender) })
}
pub fn append_str(&self, value: &str) -> Result<(), AppendError> {
self.append_bytes_as(value.as_bytes(), true)
}
pub fn append_bytes(&self, value: &[u8]) -> Result<(), AppendError> {
self.append_bytes_as(value, false)
}
fn append_bytes_as(&self, value: &[u8], varchar: bool) -> Result<(), AppendError> {
if varchar {
if value.len() > MAX_VARCHAR_LEN {
return Err(append_error(&format!(
"VARCHAR of {} bytes exceeds DuckDB's {MAX_VARCHAR_LEN}-byte appender limit",
value.len()
)));
}
let state = unsafe {
duckdb_append_varchar_length(
self.appender,
value.as_ptr().cast::<std::os::raw::c_char>(),
value.len() as idx_t,
)
};
return self.check(state);
}
let state = unsafe {
duckdb_append_blob(
self.appender,
value.as_ptr().cast::<std::os::raw::c_void>(),
value.len() as idx_t,
)
};
self.check(state)
}
pub fn append_date(&self, days: i32) -> Result<(), AppendError> {
self.check(unsafe { duckdb_append_date(self.appender, duckdb_date { days }) })
}
pub fn append_time(&self, micros: i64) -> Result<(), AppendError> {
self.check(unsafe { duckdb_append_time(self.appender, duckdb_time { micros }) })
}
pub fn append_timestamp(&self, micros: i64) -> Result<(), AppendError> {
self.check(unsafe { duckdb_append_timestamp(self.appender, duckdb_timestamp { micros }) })
}
pub fn append_interval(&self, value: DuckInterval) -> Result<(), AppendError> {
let raw = duckdb_interval {
months: value.months,
days: value.days,
micros: value.micros,
};
self.check(unsafe { duckdb_append_interval(self.appender, raw) })
}
pub fn append_value(&self, value: &Value) -> Result<(), AppendError> {
if value.as_raw().is_null() {
return Err(append_error("cannot append a null duckdb_value handle"));
}
self.check(unsafe { duckdb_append_value(self.appender, value.as_raw()) })
}
pub fn append_chunk(&self, chunk: &DataChunk) -> Result<(), AppendError> {
let state = unsafe { duckdb_append_data_chunk(self.appender, chunk.as_raw()) };
self.check(state)
}
#[cfg(feature = "duckdb-1-5")]
pub fn append_default_to_chunk(
&self,
chunk: &DataChunk,
col: u64,
row: u64,
) -> Result<(), AppendError> {
let state =
unsafe { duckdb_append_default_to_chunk(self.appender, chunk.as_raw(), col, row) };
self.check(state)
}
pub fn flush(&self) -> Result<(), AppendError> {
let state = unsafe { duckdb_appender_flush(self.appender) };
self.check(state)
}
pub fn close(&self) -> Result<(), AppendError> {
let state = unsafe { duckdb_appender_close(self.appender) };
self.check(state)
}
#[cfg(feature = "duckdb-1-5")]
pub fn clear(&self) -> Result<(), AppendError> {
let state = unsafe { duckdb_appender_clear(self.appender) };
self.check(state)
}
#[cfg(feature = "duckdb-1-5")]
#[must_use]
pub fn error_data(&self) -> ErrorData {
let raw = unsafe { libduckdb_sys::duckdb_appender_error_data(self.appender) };
unsafe { ErrorData::from_raw(raw) }
}
#[must_use]
pub fn error_message(&self) -> Option<String> {
if self.appender.is_null() {
return None;
}
let ptr = unsafe { duckdb_appender_error(self.appender) };
if ptr.is_null() {
return None;
}
Some(
unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned(),
)
}
#[inline]
#[must_use]
pub const fn as_raw(&self) -> duckdb_appender {
self.appender
}
#[cfg(feature = "duckdb-1-5")]
fn last_error(&self) -> AppendError {
self.error_data()
}
#[cfg(not(feature = "duckdb-1-5"))]
fn last_error(&self) -> AppendError {
self.error_message().map_or_else(
|| append_error("appender operation failed"),
crate::error::ExtensionError::new,
)
}
fn check(&self, state: duckdb_state) -> Result<(), AppendError> {
if state == DuckDBSuccess {
Ok(())
} else {
Err(self.last_error())
}
}
}
#[cfg(feature = "duckdb-1-5")]
fn append_error(message: &str) -> AppendError {
ErrorData::new(crate::error_data::DuckDbErrorType::InvalidInput, message)
}
#[cfg(not(feature = "duckdb-1-5"))]
fn append_error(message: &str) -> AppendError {
crate::error::ExtensionError::new(message)
}
macro_rules! append_scalar {
($($(#[$attr:meta])* $name:ident($ty:ty) => $c_fn:ident),* $(,)?) => {
impl Appender {
$(
$(#[$attr])*
///
/// # Errors
///
/// Returns an [`AppendError`] if the append fails.
pub fn $name(&self, value: $ty) -> Result<(), AppendError> {
self.check(unsafe { libduckdb_sys::$c_fn(self.appender, value) })
}
)*
}
};
}
append_scalar! {
append_bool(bool) => duckdb_append_bool,
append_i8(i8) => duckdb_append_int8,
append_i16(i16) => duckdb_append_int16,
append_i32(i32) => duckdb_append_int32,
append_i64(i64) => duckdb_append_int64,
append_u8(u8) => duckdb_append_uint8,
append_u16(u16) => duckdb_append_uint16,
append_u32(u32) => duckdb_append_uint32,
append_u64(u64) => duckdb_append_uint64,
append_f32(f32) => duckdb_append_float,
append_f64(f64) => duckdb_append_double,
}
impl Appender {
pub fn append_i128(&self, value: i128) -> Result<(), AppendError> {
let raw = duckdb_hugeint {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
lower: value as u64,
#[allow(clippy::cast_possible_truncation)]
upper: (value >> 64) as i64,
};
self.check(unsafe { duckdb_append_hugeint(self.appender, raw) })
}
pub fn append_u128(&self, value: u128) -> Result<(), AppendError> {
let raw = duckdb_uhugeint {
#[allow(clippy::cast_possible_truncation)]
lower: value as u64,
#[allow(clippy::cast_possible_truncation)]
upper: (value >> 64) as u64,
};
self.check(unsafe { duckdb_append_uhugeint(self.appender, raw) })
}
}
impl Drop for Appender {
fn drop(&mut self) {
if !self.appender.is_null() {
unsafe { duckdb_appender_destroy(&raw mut self.appender) };
}
}
}
crate::debug_repr::impl_handle_debug!(Appender.appender);