use sqlite3ext_sys::{sqlite3, sqlite3_stmt};
use std::{
error::Error,
ffi::{c_char, c_void, CString},
};
use crate::{
constants::SQLITE_OKAY,
ext::{
sqlite3ext_bind_int, sqlite3ext_bind_text, sqlite3ext_column_bytes,
sqlite3ext_column_int64, sqlite3ext_column_text, sqlite3ext_finalize,
sqlite3ext_prepare_v2, sqlite3ext_step,
},
};
pub struct Statement {
stmt: *mut sqlite3_stmt,
}
unsafe extern "C" fn cstring_destructor(raw: *mut c_void) {
drop(CString::from_raw(raw.cast::<c_char>()));
}
impl Statement {
pub fn prepare(db: *mut sqlite3, sql: &str) -> Result<Self, Box<dyn Error>> {
let s = unsafe { CString::from_vec_unchecked(sql.into()) };
let n: i32 = sql.len().try_into()?;
let mut stmt: *mut sqlite3_stmt = std::ptr::null_mut();
let result =
unsafe { sqlite3ext_prepare_v2(db, s.as_ptr(), n, &mut stmt, std::ptr::null_mut()) };
if result != SQLITE_OKAY {
Err("".into())
} else {
Ok(Statement { stmt })
}
}
pub fn bind_i32(&mut self, param_idx: i32, value: i32) -> Result<(), Box<dyn Error>> {
let result = unsafe { sqlite3ext_bind_int(self.stmt, param_idx, value) };
if result == SQLITE_OKAY {
Ok(())
} else {
Err("".into())
}
}
pub fn bind_text(&mut self, param_idx: i32, value: &str) -> Result<(), Box<dyn Error>> {
let bytes = value.as_bytes();
unsafe {
let s = CString::from_vec_unchecked(bytes.into());
let n: i32 = bytes.len().try_into().unwrap();
sqlite3ext_bind_text(
self.stmt,
param_idx,
s.into_raw(),
n,
Some(cstring_destructor),
);
}
Ok(())
}
fn bind_blob(&mut self, _param_idx: i32, _value: &[u8]) -> Result<(), Box<dyn Error>> {
todo!()
}
pub fn execute(&mut self) -> Rows {
Rows { stmt: self.stmt }
}
}
impl Drop for Statement {
fn drop(&mut self) {
unsafe { sqlite3ext_finalize(self.stmt) };
}
}
pub struct Rows {
stmt: *mut sqlite3_stmt,
}
impl Iterator for Rows {
type Item = Result<Row, ()>;
fn next(&mut self) -> Option<Self::Item> {
let code = unsafe { sqlite3ext_step(self.stmt) };
match code {
100 => Some(Ok(Row { stmt: self.stmt })),
_ => None,
}
}
}
pub struct Row {
stmt: *mut sqlite3_stmt,
}
impl Row {
pub fn get<T: Value>(&self, idx: i32) -> Result<T, ()> {
Value::value_result(self.stmt, idx)
}
}
pub trait Value: Sized {
fn value_result(stmt: *mut sqlite3_stmt, column_idx: i32) -> Result<Self, ()>;
}
impl Value for i64 {
fn value_result(stmt: *mut sqlite3_stmt, column_idx: i32) -> Result<i64, ()> {
Ok(unsafe { sqlite3ext_column_int64(stmt, column_idx) })
}
}
impl Value for String {
fn value_result(stmt: *mut sqlite3_stmt, column_idx: i32) -> Result<String, ()> {
unsafe {
let n = sqlite3ext_column_bytes(stmt, column_idx);
let s = sqlite3ext_column_text(stmt, column_idx);
let string = std::str::from_utf8(std::slice::from_raw_parts(s, n as usize));
Ok(string.unwrap().to_string())
}
}
}