use std::{ffi::CString, mem, ptr};
use crate::ffi::{
duckdb_clear_bindings, duckdb_destroy_prepare, duckdb_execute_prepared, duckdb_nparams,
duckdb_prepare, duckdb_result, DuckDBSuccess,
};
use crate::{
error::{Error, Result},
ffi,
ffi::duckdb_prepared_statement,
helpers::duck_result::{result_from_duckdb_prepare, result_from_duckdb_result},
raw::{connection::RawConnection, result::DuckResult},
types::appendable::AppendAble,
};
pub struct Statement<'a> {
con: &'a RawConnection,
stmt: duckdb_prepared_statement,
bind_idx: u64,
}
impl Statement<'_> {
pub(super) fn new<'a, 'b: 'a>(
con: &'b RawConnection,
sql: &str,
) -> Result<Statement<'a>> {
let mut stmt: duckdb_prepared_statement = ptr::null_mut();
let c_str = std::ffi::CString::new(sql)?;
let resp = unsafe { duckdb_prepare(con.con, c_str.as_ptr(), &mut stmt) };
result_from_duckdb_prepare(resp, stmt)?;
Ok(Statement { con, stmt, bind_idx: 0 })
}
#[allow(unused)]
#[inline]
fn raw(&self) -> &duckdb_prepared_statement {
&self.stmt
}
#[allow(unused)]
#[inline]
fn connection(&self) -> &RawConnection {
self.con
}
}
impl Statement<'_> {
#[must_use = "bind result should be checked"]
#[allow(unused)]
#[inline]
pub fn bind<T: AppendAble>(
&mut self,
binder: &mut T,
) -> Result<()> {
self.bind_idx += 1;
self.bind_at(binder, self.bind_idx)
}
#[allow(unused)]
#[inline]
pub fn bind_at<T: AppendAble>(
&self,
binder: &mut T,
idx: u64,
) -> Result<()> {
binder.stmt_append(idx, self.stmt)
}
#[must_use = "execute returns the query result; dropping it without reading discards rows"]
#[allow(unused)]
pub fn execute(&mut self) -> Result<DuckResult> {
let mut out = unsafe { mem::zeroed::<duckdb_result>() };
let resp = unsafe { duckdb_execute_prepared(self.stmt, &mut out as *mut duckdb_result) };
result_from_duckdb_result(resp, &mut out as *mut duckdb_result)?;
Ok(DuckResult::new(out))
}
#[allow(unused)]
#[inline]
pub fn bind_parameter_count(&self) -> usize {
unsafe { duckdb_nparams(self.stmt) as usize }
}
#[must_use = "clear_bindings result should be checked"]
#[allow(unused)]
#[inline]
pub fn clear_bindings(&mut self) -> Result<()> {
let res = unsafe { duckdb_clear_bindings(self.stmt) };
if res != DuckDBSuccess {
Err(Error::DuckDBFailure(
crate::ffi::Error::new(crate::ffi::DuckDBError),
Some("Failed to clear bindings".to_owned()),
))
} else {
self.bind_idx = 0;
Ok(())
}
}
#[allow(unused)]
#[inline]
pub fn is_null(&self) -> bool {
self.stmt.is_null()
}
}
impl Drop for Statement<'_> {
fn drop(&mut self) {
unsafe {
if !self.stmt.is_null() {
duckdb_destroy_prepare(&mut self.stmt);
}
}
}
}
pub struct CachedStatement {
#[allow(dead_code)]
pub(crate) sql: Box<str>,
stmt: ffi::duckdb_prepared_statement,
}
impl CachedStatement {
pub fn prepare(
conn: &RawConnection,
sql: impl AsRef<str>,
) -> Result<Self> {
let sql_str = sql.as_ref();
let mut stmt: ffi::duckdb_prepared_statement = ptr::null_mut();
let c_str = CString::new(sql_str)?;
let r = unsafe { ffi::duckdb_prepare(conn.con, c_str.as_ptr(), &mut stmt) };
result_from_duckdb_prepare(r, stmt)?;
Ok(CachedStatement { sql: sql_str.into(), stmt })
}
pub fn reset_bindings(&mut self) -> Result<()> {
let r = unsafe { ffi::duckdb_clear_bindings(self.stmt) };
if r == ffi::DuckDBSuccess {
Ok(())
} else {
Err(Error::DuckDBFailure(ffi::Error::new(r), None))
}
}
pub fn bind<T: AppendAble + ?Sized>(
&mut self,
idx: u64,
value: &mut T,
) -> Result<()> {
value.stmt_append(idx, self.stmt)
}
#[must_use = "the DuckResult carries both affected-row count (.changes()) and row iterator — consume it"]
pub fn execute(&mut self) -> Result<DuckResult> {
let mut out = unsafe { mem::zeroed::<ffi::duckdb_result>() };
let r =
unsafe { ffi::duckdb_execute_prepared(self.stmt, &mut out as *mut ffi::duckdb_result) };
result_from_duckdb_result(r, &mut out as *mut ffi::duckdb_result)?;
Ok(DuckResult::new(out))
}
}
impl Drop for CachedStatement {
fn drop(&mut self) {
if !self.stmt.is_null() {
unsafe { ffi::duckdb_destroy_prepare(&mut self.stmt) };
}
}
}
unsafe impl Send for CachedStatement {}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
use crate::helpers::path::path_to_cstring;
use crate::raw::connection::RawConnection;
use crate::types::appendable::AppendAble;
struct DummyAppendAble;
impl AppendAble for DummyAppendAble {
fn stmt_append(
&mut self,
_idx: u64,
_stmt: duckdb_prepared_statement,
) -> Result<()> {
Ok(())
}
fn appender_append(
&mut self,
_appender: crate::ffi::duckdb_appender,
) -> Result<()> {
Ok(())
}
}
fn get_test_connection() -> RawConnection {
let c_path = path_to_cstring(":memory:".as_ref()).unwrap();
let config = Config::default().with("duckdb_api", "rust").unwrap();
RawConnection::open_with_flags(&c_path, config).unwrap()
}
#[test]
fn test_new() {
let con = get_test_connection();
let sql = "SELECT 1";
let stmt = Statement::new(&con, sql);
assert!(stmt.is_ok());
}
#[test]
fn test_bind_and_bind_at() {
let con = get_test_connection();
let sql = "SELECT ?";
let mut stmt = Statement::new(&con, sql).unwrap();
let mut dummy = DummyAppendAble;
assert!(stmt.bind(&mut dummy).is_ok());
assert!(stmt.bind_at(&mut dummy, 1).is_ok());
}
#[test]
fn test_raw_and_connection() {
let con = get_test_connection();
let sql = "SELECT 1";
let stmt = Statement::new(&con, sql).unwrap();
let _raw = stmt.raw();
let _con = stmt.connection();
}
#[test]
fn test_execute() {
let con = get_test_connection();
let sql = "SELECT 1";
let mut stmt = Statement::new(&con, sql).unwrap();
let result = stmt.execute();
assert!(result.is_ok());
}
#[test]
fn test_execute_can_be_called_multiple_times() {
let con = get_test_connection();
let sql = "SELECT 1";
let mut stmt = Statement::new(&con, sql).unwrap();
assert!(stmt.execute().is_ok());
assert!(stmt.execute().is_ok());
}
#[test]
fn test_clear_bindings_resets_idx() {
let con = get_test_connection();
let sql = "SELECT $1";
let mut stmt = Statement::new(&con, sql).unwrap();
let mut dummy = DummyAppendAble;
stmt.bind(&mut dummy).unwrap();
assert_eq!(stmt.bind_idx, 1);
stmt.clear_bindings().unwrap();
assert_eq!(stmt.bind_idx, 0);
}
}