use libsqlite3_sys::{self as ffi};
use std::ptr;
use super::{
DataType, DatabaseError, StepResult,
database::ffi_db,
error::{BindError, DecodeError, PrepareError, ResetError, StepError},
};
use crate::common::SqliteStr;
macro_rules! ffi_stmt {
(@ $method:ident($db:expr, $stmt:expr $(, $($args:expr),*)?), $into:ty $(, $ret:expr)?) => {
match {
let db = $db;
let result = unsafe { libsqlite3_sys::$method($stmt $(, $($args),*)?) };
(db,result)
} {
(_, libsqlite3_sys::SQLITE_OK) => Ok({ $($ret)? }),
(db, result) => Err(<$into>::from(super::DatabaseError::from_code(result, db))),
}
};
($method:ident($db:expr, $stmt:expr $(, $($args:expr),*)?) as _ $(, $ret:expr)?) => {
super::statement::ffi_stmt!(@ $method($db, $stmt $(, $($args),*)?), super::DatabaseError $(, $ret)?)
};
($method:ident($db:expr, $stmt:expr $(, $($args:expr),*)?) $(, $ret:expr)?) => {
super::statement::ffi_stmt!(@ $method($db, $stmt $(, $($args),*)?), _ $(, $ret)?)
};
}
pub(super) use ffi_stmt;
pub fn prepare_v2<S: SqliteStr>(db: *mut ffi::sqlite3, sql: S) -> Result<*mut ffi::sqlite3_stmt, PrepareError> {
let mut stmt = ptr::null_mut();
let (ptr, len, _) = sql.as_nulstr();
match ffi_db!(sqlite3_prepare_v2(db, ptr, len, &mut stmt, ptr::null_mut())) {
Ok(()) => {
#[cfg(feature = "log")]
log::debug!("prepared {sql:?}");
Ok(stmt)
},
Err(err) => Err(err),
}
}
pub trait Statement {
fn as_stmt_ptr(&self) -> *mut ffi::sqlite3_stmt;
}
impl<S> Statement for &S where S: Statement {
fn as_stmt_ptr(&self) -> *mut ffi::sqlite3_stmt {
S::as_stmt_ptr(self)
}
}
impl Statement for *mut ffi::sqlite3_stmt {
fn as_stmt_ptr(&self) -> *mut ffi::sqlite3_stmt {
*self
}
}
impl<T> StatementExt for T where T: Statement { }
pub trait StatementExt: Statement {
fn as_db_ptr(&self) -> *mut ffi::sqlite3 {
unsafe { ffi::sqlite3_db_handle(self.as_stmt_ptr()) }
}
fn step(&self) -> Result<StepResult, StepError> {
match unsafe { ffi::sqlite3_step(self.as_stmt_ptr()) } {
ffi::SQLITE_ROW => Ok(StepResult::Row),
ffi::SQLITE_DONE => Ok(StepResult::Done),
result => Err(DatabaseError::from_code(result, self.as_db_ptr()).into()),
}
}
fn reset(&self) -> Result<(), ResetError> {
ffi_stmt!(sqlite3_reset(self.as_db_ptr(), self.as_stmt_ptr()))
}
fn clear_bindings(&self) -> Result<(), ResetError> {
ffi_stmt!(sqlite3_clear_bindings(self.as_db_ptr(), self.as_stmt_ptr()))
}
fn bind_int(&self, idx: i32, value: i32) -> Result<(), BindError> {
ffi_stmt!(sqlite3_bind_int(self.as_db_ptr(), self.as_stmt_ptr(), idx, value))
}
fn bind_double(&self, idx: i32, value: f64) -> Result<(), BindError> {
ffi_stmt!(sqlite3_bind_double(self.as_db_ptr(), self.as_stmt_ptr(), idx, value))
}
fn bind_null(&self, idx: i32) -> Result<(), BindError> {
ffi_stmt!(sqlite3_bind_null(self.as_db_ptr(), self.as_stmt_ptr(), idx))
}
fn bind_text<S: SqliteStr>(&self, idx: i32, text: S) -> Result<(), BindError> {
let (ptr, len, dtor) = text.as_sqlite_str()?;
ffi_stmt!(sqlite3_bind_text(self.as_db_ptr(), self.as_stmt_ptr(), idx, ptr, len, dtor))
}
fn bind_blob(&self, idx: i32, data: &[u8]) -> Result<(), BindError> {
ffi_stmt!(sqlite3_bind_blob(
self.as_db_ptr(),
self.as_stmt_ptr(),
idx,
data.as_ptr().cast(),
i32::try_from(data.len()).unwrap_or(i32::MAX),
ffi::SQLITE_TRANSIENT()
))
}
fn column_count(&self) -> i32 {
unsafe { ffi::sqlite3_column_count(self.as_stmt_ptr()) }
}
fn data_count(&self) -> i32 {
unsafe { ffi::sqlite3_data_count(self.as_stmt_ptr()) }
}
fn column_type(&self, idx: i32) -> DataType {
let code = unsafe { ffi::sqlite3_column_type(self.as_stmt_ptr(), idx) };
DataType::from_code(code).expect("sqlite return non datatype from `sqlite3_column_type`")
}
fn column_int(&self, idx: i32) -> i32 {
unsafe { ffi::sqlite3_column_int(self.as_stmt_ptr(), idx) }
}
fn column_double(&self, idx: i32) -> f64 {
unsafe { ffi::sqlite3_column_double(self.as_stmt_ptr(), idx) }
}
fn column_text(&self, idx: i32) -> Result<&str, DecodeError> {
let text = unsafe {
let text = ffi::sqlite3_column_text(self.as_stmt_ptr(), idx);
std::ffi::CStr::from_ptr(text.cast())
};
text.to_str().map_err(DecodeError::Utf8)
}
fn column_blob(&self, idx: i32) -> &[u8] {
unsafe {
let len = self.column_bytes(idx) as usize;
let data = ffi::sqlite3_column_blob(self.as_stmt_ptr(), idx).cast();
std::slice::from_raw_parts(data, len)
}
}
fn column_bytes(&self, idx: i32) -> i32 {
unsafe { ffi::sqlite3_column_bytes(self.as_stmt_ptr(), idx) }
}
fn finalize(&self) -> Result<(), DatabaseError> {
ffi_stmt!(sqlite3_finalize(self.as_db_ptr(), self.as_stmt_ptr()) as _)
}
}