use core::ffi::c_int;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::rc::Rc;
use crate::columns::{Bindable, ReadableWithIndex};
use crate::cursor::Cursor;
use crate::error::Result;
use crate::value::Type;
pub struct Statement {
pub(crate) raw: (*mut ffi::sqlite3_stmt, *mut ffi::sqlite3),
pub(crate) column_names: Rc<Vec<String>>,
column_mapping: Rc<HashMap<String, usize>>,
phantom: PhantomData<(ffi::sqlite3_stmt, ffi::sqlite3)>,
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct Handle(pub(crate) *mut ffi::sqlite3_stmt);
pub trait ColumnIndex: Copy + std::fmt::Debug {
fn index(self, statement: &Statement) -> Result<usize>;
}
pub trait ParameterIndex: Copy + std::fmt::Debug {
fn index(self, statement: &Statement) -> Result<usize>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum State {
Row,
Done,
}
impl Statement {
#[inline]
pub fn bind<T: Bindable>(&self, value: T) -> Result<()> {
value.bind(self)?;
Ok(())
}
pub fn bind_iter<T, U>(&self, value: T) -> Result<()>
where
T: IntoIterator<Item = U>,
U: Bindable,
{
for value in value {
self.bind(value)?;
}
Ok(())
}
#[inline]
pub fn iter(&self) -> Cursor<'_> {
self.into()
}
#[allow(clippy::should_implement_trait)]
pub fn next(&self) -> Result<State> {
Ok(match unsafe { ffi::sqlite3_step(self.raw.0) } {
ffi::SQLITE_ROW => State::Row,
ffi::SQLITE_DONE => State::Done,
code => error!(self.raw.1, code),
})
}
#[inline]
pub fn read<T, U>(&self, index: U) -> Result<T>
where
T: ReadableWithIndex,
U: ColumnIndex,
{
ReadableWithIndex::read(self, index)
}
#[inline]
pub fn column_count(&self) -> usize {
self.column_names.len()
}
#[doc(hidden)]
#[inline]
pub fn column_mapping(&self) -> Rc<HashMap<String, usize>> {
self.column_mapping.clone()
}
#[inline]
pub fn column_name<T: ColumnIndex>(&self, index: T) -> Result<&str> {
Ok(&self.column_names[index.index(self)?])
}
#[inline]
pub fn column_names(&self) -> &[String] {
&self.column_names
}
pub fn column_type<T: ColumnIndex>(&self, index: T) -> Result<Type> {
Ok(
match unsafe { ffi::sqlite3_column_type(self.raw.0, index.index(self)? as c_int) } {
ffi::SQLITE_BLOB => Type::Binary,
ffi::SQLITE_FLOAT => Type::Float,
ffi::SQLITE_INTEGER => Type::Integer,
ffi::SQLITE_TEXT => Type::String,
ffi::SQLITE_NULL => Type::Null,
_ => unreachable!(),
},
)
}
pub fn parameter_index(&self, parameter: &str) -> Result<Option<usize>> {
let index = unsafe {
ffi::sqlite3_bind_parameter_index(self.raw.0, str_to_cstr!(parameter).as_ptr())
};
match index {
0 => Ok(None),
_ => Ok(Some(index as usize)),
}
}
#[inline]
pub fn reset(&self) -> Result<()> {
unsafe { ok!(self.raw.1, ffi::sqlite3_reset(self.raw.0)) };
Ok(())
}
#[doc(hidden)]
#[inline]
pub fn as_raw(&self) -> *mut ffi::sqlite3_stmt {
self.raw.0
}
}
impl Drop for Statement {
#[inline]
fn drop(&mut self) {
unsafe { ffi::sqlite3_finalize(self.raw.0) };
}
}
impl<'m> From<&'m Statement> for Cursor<'m> {
#[inline]
fn from(statement: &'m Statement) -> Self {
crate::cursor::new(statement)
}
}
impl ColumnIndex for &str {
#[inline]
fn index(self, statement: &Statement) -> Result<usize> {
if statement.column_mapping.contains_key(self) {
Ok(statement.column_mapping[self])
} else {
raise!("the index is out of range ({})", self);
}
}
}
impl ColumnIndex for usize {
#[inline]
fn index(self, statement: &Statement) -> Result<usize> {
if self < statement.column_count() {
Ok(self)
} else {
raise!("the index is out of range ({})", self);
}
}
}
impl ParameterIndex for &str {
#[inline]
fn index(self, statement: &Statement) -> Result<usize> {
match statement.parameter_index(self)? {
Some(index) => Ok(index),
_ => raise!("the index is out of range ({})", self),
}
}
}
impl ParameterIndex for usize {
#[inline]
fn index(self, _: &Statement) -> Result<usize> {
if self > 0 {
Ok(self)
} else {
raise!("the index is out of range ({})", self);
}
}
}
pub(crate) fn new<T>(raw_connection: *mut ffi::sqlite3, statement: T) -> Result<Statement>
where
T: AsRef<str>,
{
let mut raw_statement = std::ptr::null_mut();
unsafe {
ok!(
raw_connection,
ffi::sqlite3_prepare_v2(
raw_connection,
str_to_cstr!(statement.as_ref()).as_ptr(),
-1,
&mut raw_statement,
std::ptr::null_mut(),
)
);
}
let column_count = unsafe { ffi::sqlite3_column_count(raw_statement) as usize };
let column_names = (0..column_count)
.map(|index| unsafe {
let raw = ffi::sqlite3_column_name(raw_statement, index as c_int);
debug_assert!(!raw.is_null());
c_str_to_str!(raw).unwrap().to_string()
})
.collect::<Vec<_>>();
let column_mapping = column_names
.iter()
.enumerate()
.map(|(index, name)| (name.to_string(), index))
.collect();
Ok(Statement {
raw: (raw_statement, raw_connection),
column_names: Rc::new(column_names),
column_mapping: Rc::new(column_mapping),
phantom: PhantomData,
})
}