use crate::{
cbox::CBox, date_to_duckdb_date, decimal_to_duckdb_decimal, i128_to_duckdb_hugeint,
interval_to_duckdb_interval, offsetdatetime_to_duckdb_timestamp,
primitive_date_time_to_duckdb_timestamp, time_to_duckdb_time, u128_to_duckdb_uhugeint,
};
use anyhow::anyhow;
use libduckdb_sys::*;
use std::{
ffi::c_void,
fmt::{self, Display},
};
use tank_core::{error_message_from_ptr, AsValue, Error, Prepared, Result, Value};
#[derive(Debug)]
pub struct DuckDBPrepared {
pub(crate) statement: CBox<duckdb_prepared_statement>,
pub(crate) index: u64,
}
impl DuckDBPrepared {
pub(crate) fn new(statement: CBox<duckdb_prepared_statement>) -> Self {
Self {
statement: statement.into(),
index: 1,
}
}
pub(crate) fn statement(&self) -> duckdb_prepared_statement {
*self.statement
}
}
impl Display for DuckDBPrepared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"<prepared statement at {:p}, SQL not retrievable from DuckDB C API>",
self.statement()
)
}
}
impl Prepared for DuckDBPrepared {
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
fn clear_bindings(&mut self) -> Result<&mut Self> {
self.index = 1;
unsafe {
let rc = duckdb_clear_bindings(self.statement());
if rc != duckdb_state_DuckDBSuccess {
let error = anyhow!("Could not clear the bindings from DuckDB statement");
log::error!("{error:#}");
return Err(error);
}
}
Ok(self)
}
fn bind(&mut self, value: impl AsValue) -> Result<&mut Self> {
self.bind_index(value, self.index)?;
Ok(self)
}
fn bind_index(&mut self, v: impl AsValue, index: u64) -> Result<&mut Self> {
unsafe {
let prepared = self.statement();
let value = v.as_value();
let state = match value {
_ if value.is_null() => duckdb_bind_null(prepared, index),
Value::Boolean(Some(v), ..) => duckdb_bind_boolean(prepared, index, v),
Value::Int8(Some(v), ..) => duckdb_bind_int8(prepared, index, v),
Value::Int16(Some(v), ..) => duckdb_bind_int16(prepared, index, v),
Value::Int32(Some(v), ..) => duckdb_bind_int32(prepared, index, v),
Value::Int64(Some(v), ..) => duckdb_bind_int64(prepared, index, v),
Value::Int128(Some(v), ..) => {
duckdb_bind_hugeint(prepared, index, i128_to_duckdb_hugeint(v))
}
Value::UInt8(Some(v), ..) => duckdb_bind_uint8(prepared, index, v),
Value::UInt16(Some(v), ..) => duckdb_bind_uint16(prepared, index, v),
Value::UInt32(Some(v), ..) => duckdb_bind_uint32(prepared, index, v),
Value::UInt64(Some(v), ..) => duckdb_bind_uint64(prepared, index, v),
Value::UInt128(Some(v), ..) => {
duckdb_bind_uhugeint(prepared, index, u128_to_duckdb_uhugeint(v))
}
Value::Float32(Some(v), ..) => duckdb_bind_float(prepared, index, v),
Value::Float64(Some(v), ..) => duckdb_bind_double(prepared, index, v),
Value::Decimal(Some(v), w, s) => {
duckdb_bind_decimal(prepared, index, decimal_to_duckdb_decimal(&v, w, s))
}
Value::Char(Some(v), ..) => {
let v = v.to_string();
let status = duckdb_bind_varchar_length(
prepared,
index,
v.as_ptr() as *const i8,
v.len() as u64,
);
status
}
Value::Varchar(Some(v), ..) => {
let status = duckdb_bind_varchar_length(
prepared,
index,
v.as_ptr() as *const i8,
v.len() as u64,
);
status
}
Value::Blob(Some(v), ..) => {
let status = duckdb_bind_blob(
prepared,
index,
v.as_ptr() as *const c_void,
v.len() as u64,
);
status
}
Value::Date(Some(v), ..) => {
duckdb_bind_date(prepared, index, date_to_duckdb_date(&v))
}
Value::Time(Some(v), ..) => {
duckdb_bind_time(prepared, index, time_to_duckdb_time(&v))
}
Value::Timestamp(Some(v), ..) => duckdb_bind_timestamp(
prepared,
index,
primitive_date_time_to_duckdb_timestamp(&v),
),
Value::TimestampWithTimezone(Some(v), ..) => duckdb_bind_timestamp_tz(
prepared,
index,
offsetdatetime_to_duckdb_timestamp(&v),
),
Value::Interval(Some(v), ..) => {
duckdb_bind_interval(prepared, index, interval_to_duckdb_interval(&v))
}
Value::Uuid(Some(v), ..) => {
let v = CBox::new(
duckdb_create_uuid(u128_to_duckdb_uhugeint(v.as_u128())),
|mut ptr| duckdb_destroy_value(&mut ptr),
);
duckdb_bind_value(prepared, index, *v)
}
_ => {
let error = anyhow!("Cannot use a {:?} as a query parameter", value);
log::error!("{error:#}");
return Err(error);
}
};
if state != duckdb_state_DuckDBSuccess {
let error =
Error::msg(error_message_from_ptr(&duckdb_prepare_error(prepared)).to_string())
.context(format!("While trying to bind the parameter {}", index));
log::error!("{error:#}");
return Err(error);
}
self.index = index + 1;
Ok(self)
}
}
}
impl From<CBox<duckdb_prepared_statement>> for DuckDBPrepared {
fn from(value: CBox<duckdb_prepared_statement>) -> Self {
Self::new(value)
}
}