use std::{ffi::CStr, sync::Arc};
use arrow::{
array::StructArray,
datatypes::{DataType, SchemaRef},
};
use super::{Result, ffi};
use crate::{
Error,
core::{LogicalTypeHandle, LogicalTypeId},
error::result_from_duckdb_result,
executed_result::{ExecutedResult, logical_type_from_duckdb_column, reject_unsupported_result_logical_type},
};
#[cfg(feature = "polars")]
use polars_core::utils::arrow as polars_arrow;
#[derive(Debug)]
pub struct RawStatement {
ptr: ffi::duckdb_prepared_statement,
result: Option<ExecutedResult>,
statement_cache_key: Option<Arc<str>>,
}
impl RawStatement {
#[inline]
pub unsafe fn new(stmt: ffi::duckdb_prepared_statement) -> Self {
Self {
ptr: stmt,
result: None,
statement_cache_key: None,
}
}
#[inline]
pub fn is_null(&self) -> bool {
self.ptr.is_null()
}
#[inline]
pub unsafe fn ptr(&self) -> ffi::duckdb_prepared_statement {
self.ptr
}
#[inline]
pub(crate) fn set_statement_cache_key(&mut self, p: impl Into<Arc<str>>) {
self.statement_cache_key = Some(p.into());
}
#[inline]
pub(crate) fn statement_cache_key(&self) -> Option<Arc<str>> {
self.statement_cache_key.clone()
}
#[inline]
pub fn clear_bindings(&self) -> ffi::duckdb_state {
unsafe { ffi::duckdb_clear_bindings(self.ptr) }
}
#[inline]
pub fn row_count(&self) -> usize {
self.executed().row_count()
}
#[inline]
pub fn step(&self) -> Result<Option<StructArray>> {
self.result.as_ref().map_or(Ok(None), ExecutedResult::step)
}
#[cfg(feature = "polars")]
#[inline]
pub(crate) fn step_polars(&self) -> Result<Option<polars_arrow::array::StructArray>> {
self.result.as_ref().map_or(Ok(None), ExecutedResult::step_polars)
}
#[inline]
pub fn column_count(&self) -> usize {
self.schema_ref().fields().len()
}
#[inline]
pub fn column_type(&self, idx: usize) -> DataType {
self.schema().field(idx).data_type().to_owned()
}
#[inline]
pub fn column_logical_type(&self, idx: usize) -> LogicalTypeHandle {
self.try_column_logical_type(idx)
.unwrap_or_else(|e| panic!("could not retrieve logical type for result column at index {idx}: {e}"))
}
#[inline]
pub(crate) fn try_column_logical_type(&self, idx: usize) -> Result<LogicalTypeHandle> {
if let Some(result) = &self.result {
return result.try_column_logical_type(idx);
}
unsafe {
let ptr = ffi::duckdb_prepared_statement_column_logical_type(self.ptr, idx as u64);
logical_type_from_duckdb_column(ptr, idx)
}
}
#[inline]
pub(crate) fn result_column_logical_id(&self, idx: usize) -> Option<LogicalTypeId> {
self.result.as_ref().map(|result| result.result_column_logical_id(idx))
}
#[inline]
pub fn schema(&self) -> SchemaRef {
self.schema_ref().clone()
}
#[inline]
fn schema_ref(&self) -> &SchemaRef {
self.executed().schema_ref()
}
#[inline]
fn executed(&self) -> &ExecutedResult {
self.result.as_ref().expect("The statement was not executed yet")
}
#[inline]
pub fn column_name(&self, idx: usize) -> Option<&String> {
self.executed().column_name(idx)
}
#[inline]
pub fn column_index(&self, name: &str) -> Option<usize> {
self.executed().column_index(name)
}
pub fn execute(&mut self) -> Result<usize> {
self.reset_result();
self.reject_unsupported_prepared_result_columns_for_dml()?;
unsafe {
let mut out: ffi::duckdb_result = std::mem::zeroed();
let rc = ffi::duckdb_execute_prepared(self.ptr, &mut out);
result_from_duckdb_result(rc, &mut out)?;
let rows_changed = ffi::duckdb_rows_changed(&mut out);
self.result = Some(ExecutedResult::new(out)?);
Ok(rows_changed as usize)
}
}
pub fn execute_streaming(&mut self) -> Result<()> {
self.reset_result();
self.reject_unsupported_prepared_result_columns_for_dml()?;
unsafe {
let mut out: ffi::duckdb_result = std::mem::zeroed();
let rc = ffi::duckdb_execute_prepared_streaming(self.ptr, &mut out);
result_from_duckdb_result(rc, &mut out)?;
self.result = Some(ExecutedResult::new(out)?);
Ok(())
}
}
#[inline]
pub fn reset_result(&mut self) {
self.result = None;
}
#[inline]
pub fn bind_parameter_count(&self) -> usize {
unsafe { ffi::duckdb_nparams(self.ptr) as usize }
}
pub fn parameter_name(&self, idx: usize) -> Result<String> {
let count = self.bind_parameter_count();
if idx == 0 || idx > count {
return Err(Error::InvalidParameterIndex(idx));
}
unsafe {
let name_ptr = ffi::duckdb_parameter_name(self.ptr, idx as u64);
let name = match ffi::DuckDbString::from_nullable_ptr(name_ptr) {
Some(name) => name.to_string_lossy().to_string(),
None => {
return Err(Error::DuckDBFailure(
ffi::Error::new(ffi::DuckDBError),
Some(format!("Could not retrieve parameter name for index {idx}")),
));
}
};
Ok(name)
}
}
fn reject_unsupported_prepared_result_columns_for_dml(&self) -> Result<()> {
if !self.is_dml_statement() {
return Ok(());
}
let column_count = unsafe { ffi::duckdb_prepared_statement_column_count(self.ptr) as usize };
for idx in 0..column_count {
let logical_type = self.try_column_logical_type(idx)?;
reject_unsupported_result_logical_type(idx, &logical_type)?;
}
Ok(())
}
fn is_dml_statement(&self) -> bool {
matches!(
unsafe { ffi::duckdb_prepared_statement_type(self.ptr) },
ffi::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_INSERT
| ffi::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_UPDATE
| ffi::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_DELETE
)
}
#[inline]
pub fn sql(&self) -> Option<&CStr> {
panic!("not supported")
}
}
impl Drop for RawStatement {
fn drop(&mut self) {
self.reset_result();
if !self.ptr.is_null() {
unsafe { ffi::duckdb_destroy_prepare(&mut self.ptr) };
}
}
}
unsafe impl Send for RawStatement {}