use std::ffi::c_void;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;
use std::ptr::NonNull;
use std::slice::from_raw_parts;
use std::str::{from_utf8, from_utf8_unchecked};
use std::sync::{Condvar, Mutex};
use crate::type_info::DataType;
use crate::{SqliteError, SqliteTypeInfo};
use libsqlite3_sys::{
sqlite3, sqlite3_bind_blob64, sqlite3_bind_double, sqlite3_bind_int, sqlite3_bind_int64,
sqlite3_bind_null, sqlite3_bind_parameter_count, sqlite3_bind_parameter_name,
sqlite3_bind_text64, sqlite3_changes, sqlite3_clear_bindings, sqlite3_column_blob,
sqlite3_column_bytes, sqlite3_column_count, sqlite3_column_database_name,
sqlite3_column_decltype, sqlite3_column_double, sqlite3_column_int, sqlite3_column_int64,
sqlite3_column_name, sqlite3_column_origin_name, sqlite3_column_table_name,
sqlite3_column_type, sqlite3_column_value, sqlite3_db_handle, sqlite3_finalize, sqlite3_reset,
sqlite3_sql, sqlite3_step, sqlite3_stmt, sqlite3_stmt_readonly, sqlite3_table_column_metadata,
sqlite3_unlock_notify, sqlite3_value, SQLITE_DONE, SQLITE_LOCKED_SHAREDCACHE, SQLITE_MISUSE,
SQLITE_OK, SQLITE_ROW, SQLITE_TRANSIENT, SQLITE_UTF8,
};
use rbdc::Error;
#[derive(Debug)]
pub(crate) struct StatementHandle(NonNull<sqlite3_stmt>);
unsafe impl Send for StatementHandle {}
#[allow(dead_code)]
impl StatementHandle {
pub(super) fn new(ptr: NonNull<sqlite3_stmt>) -> Self {
Self(ptr)
}
#[inline]
pub(super) unsafe fn db_handle(&self) -> *mut sqlite3 {
sqlite3_db_handle(self.0.as_ptr())
}
pub(crate) fn read_only(&self) -> bool {
unsafe { sqlite3_stmt_readonly(self.0.as_ptr()) != 0 }
}
pub(crate) fn sql(&self) -> &str {
unsafe {
let raw = sqlite3_sql(self.0.as_ptr());
debug_assert!(!raw.is_null());
from_utf8_unchecked(CStr::from_ptr(raw).to_bytes())
}
}
#[inline]
pub(crate) fn last_error(&self) -> SqliteError {
SqliteError::new(unsafe { self.db_handle() })
}
#[inline]
pub(crate) fn column_count(&self) -> usize {
unsafe { sqlite3_column_count(self.0.as_ptr()) as usize }
}
#[inline]
pub(crate) fn changes(&self) -> u64 {
unsafe { sqlite3_changes(self.db_handle()) as u64 }
}
#[inline]
pub(crate) fn column_name(&self, index: usize) -> &str {
unsafe {
let name = sqlite3_column_name(self.0.as_ptr(), index as c_int);
debug_assert!(!name.is_null());
from_utf8_unchecked(CStr::from_ptr(name).to_bytes())
}
}
pub(crate) fn column_type_info(&self, index: usize) -> SqliteTypeInfo {
SqliteTypeInfo(DataType::from_code(self.column_type(index)))
}
pub(crate) fn column_type_info_opt(&self, index: usize) -> Option<SqliteTypeInfo> {
match DataType::from_code(self.column_type(index)) {
DataType::Null => None,
dt => Some(SqliteTypeInfo(dt)),
}
}
#[inline]
pub(crate) fn column_decltype(&self, index: usize) -> Option<SqliteTypeInfo> {
unsafe {
let decl = sqlite3_column_decltype(self.0.as_ptr(), index as c_int);
if decl.is_null() {
return None;
}
let decl = from_utf8_unchecked(CStr::from_ptr(decl).to_bytes());
let ty: DataType = decl.parse().ok()?;
Some(SqliteTypeInfo(ty))
}
}
pub(crate) fn column_nullable(&self, index: usize) -> Result<Option<bool>, Error> {
unsafe {
let db_name = sqlite3_column_database_name(self.0.as_ptr(), index as c_int);
let table_name = sqlite3_column_table_name(self.0.as_ptr(), index as c_int);
let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), index as c_int);
if db_name.is_null() || table_name.is_null() || origin_name.is_null() {
return Ok(None);
}
let mut not_null: c_int = 0;
let status = sqlite3_table_column_metadata(
self.db_handle(),
db_name,
table_name,
origin_name,
ptr::null_mut(),
ptr::null_mut(),
&mut not_null,
ptr::null_mut(),
ptr::null_mut(),
);
if status != SQLITE_OK {
return Err(SqliteError::new(self.db_handle()).into());
}
Ok(Some(not_null == 0))
}
}
#[inline]
pub(crate) fn bind_parameter_count(&self) -> usize {
unsafe { sqlite3_bind_parameter_count(self.0.as_ptr()) as usize }
}
#[inline]
pub(crate) fn bind_parameter_name(&self, index: usize) -> Option<&str> {
unsafe {
let name = sqlite3_bind_parameter_name(self.0.as_ptr(), index as c_int);
if name.is_null() {
return None;
}
Some(from_utf8_unchecked(CStr::from_ptr(name).to_bytes()))
}
}
#[inline]
pub(crate) fn bind_blob(&self, index: usize, v: &[u8]) -> c_int {
unsafe {
sqlite3_bind_blob64(
self.0.as_ptr(),
index as c_int,
v.as_ptr() as *const c_void,
v.len() as u64,
SQLITE_TRANSIENT(),
)
}
}
#[inline]
pub(crate) fn bind_text(&self, index: usize, v: &str) -> c_int {
unsafe {
sqlite3_bind_text64(
self.0.as_ptr(),
index as c_int,
v.as_ptr() as *const c_char,
v.len() as u64,
SQLITE_TRANSIENT(),
SQLITE_UTF8 as u8,
)
}
}
#[inline]
pub(crate) fn bind_int(&self, index: usize, v: i32) -> c_int {
unsafe { sqlite3_bind_int(self.0.as_ptr(), index as c_int, v as c_int) }
}
#[inline]
pub(crate) fn bind_int64(&self, index: usize, v: i64) -> c_int {
unsafe { sqlite3_bind_int64(self.0.as_ptr(), index as c_int, v) }
}
#[inline]
pub(crate) fn bind_double(&self, index: usize, v: f64) -> c_int {
unsafe { sqlite3_bind_double(self.0.as_ptr(), index as c_int, v) }
}
#[inline]
pub(crate) fn bind_null(&self, index: usize) -> c_int {
unsafe { sqlite3_bind_null(self.0.as_ptr(), index as c_int) }
}
#[inline]
pub(crate) fn column_type(&self, index: usize) -> c_int {
unsafe { sqlite3_column_type(self.0.as_ptr(), index as c_int) }
}
#[inline]
pub(crate) fn column_int(&self, index: usize) -> i32 {
unsafe { sqlite3_column_int(self.0.as_ptr(), index as c_int) as i32 }
}
#[inline]
pub(crate) fn column_int64(&self, index: usize) -> i64 {
unsafe { sqlite3_column_int64(self.0.as_ptr(), index as c_int) as i64 }
}
#[inline]
pub(crate) fn column_double(&self, index: usize) -> f64 {
unsafe { sqlite3_column_double(self.0.as_ptr(), index as c_int) }
}
#[inline]
pub(crate) fn column_value(&self, index: usize) -> *mut sqlite3_value {
unsafe { sqlite3_column_value(self.0.as_ptr(), index as c_int) }
}
pub(crate) fn column_blob(&self, index: usize) -> &[u8] {
let index = index as c_int;
let len = unsafe { sqlite3_column_bytes(self.0.as_ptr(), index) } as usize;
if len == 0 {
return &[];
}
let ptr = unsafe { sqlite3_column_blob(self.0.as_ptr(), index) } as *const u8;
debug_assert!(!ptr.is_null());
unsafe { from_raw_parts(ptr, len) }
}
pub(crate) fn column_text(&self, index: usize) -> Result<&str, Error> {
Ok(from_utf8(self.column_blob(index))?)
}
pub(crate) fn clear_bindings(&self) {
unsafe { sqlite3_clear_bindings(self.0.as_ptr()) };
}
pub(crate) fn reset(&mut self) -> Result<(), SqliteError> {
unsafe {
if sqlite3_reset(self.0.as_ptr()) != SQLITE_OK {
return Err(SqliteError::new(self.db_handle()));
}
}
Ok(())
}
pub(crate) fn step(&mut self) -> Result<bool, SqliteError> {
unsafe {
loop {
match sqlite3_step(self.0.as_ptr()) {
SQLITE_ROW => return Ok(true),
SQLITE_DONE => return Ok(false),
SQLITE_MISUSE => panic!("misuse!"),
SQLITE_LOCKED_SHAREDCACHE => {
wait_for_unlock_notify(self.db_handle())?;
sqlite3_reset(self.0.as_ptr());
}
_ => return Err(SqliteError::new(self.db_handle())),
}
}
}
}
}
impl Drop for StatementHandle {
fn drop(&mut self) {
unsafe {
let status = sqlite3_finalize(self.0.as_ptr());
if status == SQLITE_MISUSE {
panic!("Detected sqlite3_finalize misuse.");
}
}
}
}
unsafe fn wait_for_unlock_notify(conn: *mut sqlite3) -> Result<(), SqliteError> {
let notify = Notify::new();
if sqlite3_unlock_notify(
conn,
Some(unlock_notify_cb),
¬ify as *const Notify as *mut Notify as *mut _,
) != SQLITE_OK
{
return Err(SqliteError::new(conn));
}
notify.wait();
Ok(())
}
unsafe extern "C" fn unlock_notify_cb(ptr: *mut *mut c_void, len: c_int) {
let ptr = ptr as *mut &Notify;
let slice = from_raw_parts(ptr, len as usize);
for notify in slice {
notify.fire();
}
}
struct Notify {
mutex: Mutex<bool>,
condvar: Condvar,
}
impl Notify {
fn new() -> Self {
Self {
mutex: Mutex::new(false),
condvar: Condvar::new(),
}
}
fn wait(&self) {
let _lock = self
.condvar
.wait_while(self.mutex.lock().unwrap(), |fired| !*fired)
.unwrap();
}
fn fire(&self) {
*self.mutex.lock().unwrap() = true;
self.condvar.notify_one();
}
}