use crate::sqlite::SqliteError;
use crate::sqlite::SqliteUserId;
use crate::sqlite::sqlite_sys;
use crate::sqlite::sqlite_sys::SQLITE_BLOB;
use crate::sqlite::sqlite_sys::SQLITE_DONE;
use crate::sqlite::sqlite_sys::SQLITE_INTEGER;
use crate::sqlite::sqlite_sys::SQLITE_PREPARE_PERSISTENT;
use crate::sqlite::sqlite_sys::SQLITE_ROW;
use crate::sqlite::sqlite_sys::SQLITE_STATIC;
use crate::sqlite::sqlite_sys::SqliteResult;
use crate::sqlite::sqlite_sys::sqlite3;
use crate::sqlite::sqlite_sys::sqlite3_stmt;
use crate::utils::errorfmt::ErrorFmt;
use opera::PhantomNotSync;
use sqlite_sys::Sqlite;
use std::ptr;
use std::rc::Rc;
use std::slice;
use uapi::IntoUstr;
use uapi::c::c_int;
pub struct SqliteDb {
pub api: &'static Sqlite,
pub s: *mut sqlite3,
pub _no_sync: PhantomNotSync,
}
enum DbRef<'a> {
Rc(#[expect(unused)] Rc<SqliteDb>),
Ref(#[expect(unused)] &'a SqliteDb),
}
pub struct SqliteStmt<'a> {
api: &'static Sqlite,
_db: DbRef<'a>,
stmt: *mut sqlite3_stmt,
}
pub struct SqliteActiveStmt<'bind, 'stmt> {
api: &'static Sqlite,
s: &'bind SqliteStmt<'stmt>,
state: StatementState,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum StatementState {
Init,
Row,
Done,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SqliteStep {
Row,
Done,
}
unsafe impl Send for SqliteDb {}
impl Drop for SqliteDb {
fn drop(&mut self) {
let res = unsafe { (self.api.sqlite3_close)(self.s).result() };
if let Err(e) = res {
log::error!("Could not close database: {}", ErrorFmt(e));
}
}
}
impl Drop for SqliteStmt<'_> {
fn drop(&mut self) {
let res = unsafe { (self.api.sqlite3_finalize)(self.stmt).result() };
if let Err(e) = res {
log::error!("Could not finalize statement: {}", ErrorFmt(e));
}
}
}
impl Drop for SqliteActiveStmt<'_, '_> {
fn drop(&mut self) {
if let Err(e) = self.exec_() {
log::error!("Could not step statement: {}", ErrorFmt(e));
}
let res = unsafe { (self.api.sqlite3_reset)(self.s.stmt).result() };
if let Err(e) = res {
log::error!("Could not reset statement: {}", ErrorFmt(e));
}
let res = unsafe { (self.api.sqlite3_clear_bindings)(self.s.stmt).result() };
if let Err(e) = res {
log::error!("Could not clear bindings: {}", ErrorFmt(e));
}
}
}
impl SqliteDb {
pub fn exec(&self, sql: &str) -> Result<(), SqliteError> {
self.exec_(sql).map_err(SqliteError::Exec)
}
pub(super) fn exec_(&self, sql: &str) -> Result<(), SqliteResult> {
let sql = sql.into_ustr();
unsafe {
(self.api.sqlite3_exec)(self.s, sql.as_ptr(), None, ptr::null_mut(), ptr::null_mut())
.result()
}
}
pub fn prepare_tmp(&self, sql: &str) -> Result<SqliteStmt<'_>, SqliteError> {
let stmt = self.prepare_raw(sql)?;
Ok(SqliteStmt {
api: self.api,
_db: DbRef::Ref(self),
stmt,
})
}
pub fn prepare(self: &Rc<Self>, sql: &str) -> Result<SqliteStmt<'static>, SqliteError> {
let stmt = self.prepare_raw(sql)?;
Ok(SqliteStmt {
api: self.api,
_db: DbRef::Rc(self.clone()),
stmt,
})
}
fn prepare_raw(&self, sql: &str) -> Result<*mut sqlite3_stmt, SqliteError> {
let n_bytes = sql
.len()
.try_into()
.map_err(|_| SqliteError::StatementTooLong)?;
let mut stmt = ptr::null_mut();
unsafe {
(self.api.sqlite3_prepare_v3)(
self.s,
sql.as_ptr().cast(),
n_bytes,
SQLITE_PREPARE_PERSISTENT,
&mut stmt,
ptr::null_mut(),
)
.result()
.map_err(SqliteError::PrepareStatement)?;
}
if stmt.is_null() {
return Err(SqliteError::NullStatement);
}
Ok(stmt)
}
}
impl<'stmt> SqliteStmt<'stmt> {
pub fn activate(&mut self) -> SqliteActiveStmt<'_, 'stmt> {
SqliteActiveStmt {
s: self,
api: self.api,
state: StatementState::Init,
}
}
pub fn exec(&mut self) -> Result<(), SqliteError> {
self.activate().exec()
}
}
impl<'bind> SqliteActiveStmt<'bind, '_> {
pub fn bind_user_id(&self, idx: c_int, v: SqliteUserId) -> Result<(), SqliteError> {
self.bind_i64(idx, v.0)
}
pub fn bind_i64(&self, idx: c_int, v: i64) -> Result<(), SqliteError> {
if self.state != StatementState::Init {
return Err(SqliteError::NotInit);
}
unsafe {
(self.api.sqlite3_bind_int64)(self.s.stmt, idx, v)
.result()
.map_err(SqliteError::BindI64)?;
}
Ok(())
}
pub fn bind_blob<'data>(&self, idx: c_int, data: &'data [u8]) -> Result<(), SqliteError>
where
'data: 'bind,
{
if self.state != StatementState::Init {
return Err(SqliteError::NotInit);
}
let n = data
.len()
.try_into()
.map_err(|_| SqliteError::BlobTooLong)?;
unsafe {
(self.api.sqlite3_bind_blob)(self.s.stmt, idx, data.as_ptr().cast(), n, SQLITE_STATIC)
.result()
.map_err(SqliteError::BindBlob)?;
}
Ok(())
}
pub fn exec(mut self) -> Result<(), SqliteError> {
self.exec_()
}
fn exec_(&mut self) -> Result<(), SqliteError> {
while self.state != StatementState::Done {
self.step()?;
}
Ok(())
}
pub fn step(&mut self) -> Result<SqliteStep, SqliteError> {
if self.state == StatementState::Done {
return Err(SqliteError::Done);
}
let res = unsafe { (self.api.sqlite3_step)(self.s.stmt) };
if res.0 == SQLITE_ROW {
self.state = StatementState::Row;
return Ok(SqliteStep::Row);
}
self.state = StatementState::Done;
if res.0 == SQLITE_DONE {
return Ok(SqliteStep::Done);
}
Err(SqliteError::Step(res))
}
pub fn get_i64(&self, idx: c_int) -> Result<i64, SqliteError> {
if self.state != StatementState::Row {
return Err(SqliteError::NotRow);
}
let ty = unsafe { (self.api.sqlite3_column_type)(self.s.stmt, idx) };
if ty != SQLITE_INTEGER {
return Err(SqliteError::NotI64);
}
let res = unsafe { (self.api.sqlite3_column_int64)(self.s.stmt, idx) };
Ok(res)
}
pub fn get_blob(&self, idx: c_int) -> Result<&[u8], SqliteError> {
if self.state != StatementState::Row {
return Err(SqliteError::NotRow);
}
let ty = unsafe { (self.api.sqlite3_column_type)(self.s.stmt, idx) };
if ty != SQLITE_BLOB {
return Err(SqliteError::NotBlob);
}
let len = unsafe { (self.api.sqlite3_column_bytes)(self.s.stmt, idx) };
let Ok(len) = len.try_into() else {
return Err(SqliteError::NegativeBlobSize);
};
let bytes = unsafe { (self.api.sqlite3_column_blob)(self.s.stmt, idx) };
if bytes.is_null() {
return Ok(&[]);
}
let slice = unsafe { slice::from_raw_parts(bytes.cast(), len) };
Ok(slice)
}
}