use std::{ffi::CString, ptr::NonNull};
use libsqlite3_sys::sqlite3;
use crate::{
Error, Result,
sqlite::{DEFAULT_MAX_RETRIES, ffi, statement::unlock_notify},
};
#[derive(Debug)]
pub struct ConnectionHandle {
ptr: NonNull<sqlite3>,
closed: bool,
}
unsafe impl Send for ConnectionHandle {}
impl ConnectionHandle {
pub(super) unsafe fn new(ptr: *mut sqlite3) -> Self {
Self {
ptr: unsafe { NonNull::new_unchecked(ptr) },
closed: false,
}
}
pub(crate) fn as_ptr(&self) -> *mut sqlite3 {
self.ptr.as_ptr()
}
pub(crate) fn last_insert_rowid(&self) -> i64 {
ffi::last_insert_rowid(self.as_ptr())
}
pub(crate) fn exec(&self, query: impl Into<String>) -> Result<()> {
let query = query.into();
let query =
CString::new(query).map_err(|_| Error::Protocol("query contains nul bytes".into()))?;
let mut attempts = 0;
loop {
match ffi::exec(self.as_ptr(), query.as_ptr()) {
Ok(()) => return Ok(()),
Err(e) if e.should_retry() => {
if attempts >= DEFAULT_MAX_RETRIES {
return Err(Error::UnlockNotify);
}
attempts += 1;
unlock_notify::wait(self.as_ptr(), None)?;
}
Err(e) => return Err(e.into()),
}
}
}
pub(crate) fn close(&mut self) -> Result<()> {
if self.closed {
return Ok(());
}
match ffi::close(self.ptr.as_ptr()) {
Ok(()) => {
self.closed = true;
Ok(())
}
Err(e) => Err(e.into()),
}
}
}
impl Drop for ConnectionHandle {
fn drop(&mut self) {
if !self.closed
&& let Err(e) = ffi::close(self.ptr.as_ptr())
{
tracing::error!(db_ptr = ?self.ptr, "sqlite3_close failed: {}", e);
}
}
}