use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_void};
use zeroize::Zeroize;
use super::error::{DbError, DbResult};
pub const SQLITE_OK: i32 = 0;
pub const SQLITE_ROW: i32 = 100;
pub const SQLITE_DONE: i32 = 101;
pub const SQLITE_NULL: i32 = 5;
pub const SQLITE_OPEN_READONLY: i32 = 0x0000_0001;
pub const SQLITE_OPEN_READWRITE: i32 = 0x0000_0002;
pub const SQLITE_OPEN_CREATE: i32 = 0x0000_0004;
pub const SQLITE_OPEN_FULLMUTEX: i32 = 0x0001_0000;
const SQLITE_TRANSIENT: isize = -1;
const SQLITE_ERROR: i32 = 1;
pub struct RawDb {
ptr: *mut c_void,
}
pub struct RawStmt<'db> {
ptr: *mut c_void,
db: &'db RawDb,
}
unsafe impl Send for RawDb {}
impl RawDb {
pub fn open(path: &str, flags: i32) -> DbResult<Self> {
let c_path = to_cstring(path)?;
let mut ptr: *mut c_void = std::ptr::null_mut();
let rc = unsafe {
raw::sqlite3_open_v2(
c_path.as_ptr(),
&raw mut ptr,
flags as c_int,
std::ptr::null(),
)
};
if rc != SQLITE_OK as c_int {
let msg = if ptr.is_null() {
format!("sqlite3_open_v2 returned {rc}")
} else {
let m = errmsg_from_ptr(ptr);
unsafe {
raw::sqlite3_close_v2(ptr);
}
m
};
return Err(DbError::new(rc, msg));
}
Ok(Self { ptr })
}
pub fn exec(&self, sql: &str) -> DbResult<()> {
let c_sql = to_cstring(sql)?;
let mut errmsg: *mut c_char = std::ptr::null_mut();
let rc = unsafe {
raw::sqlite3_exec(
self.ptr,
c_sql.as_ptr(),
None,
std::ptr::null_mut(),
&raw mut errmsg,
)
};
if rc == SQLITE_OK as c_int {
return Ok(());
}
let msg = if errmsg.is_null() {
self.errmsg()
} else {
let s = unsafe { CStr::from_ptr(errmsg) }
.to_string_lossy()
.into_owned();
unsafe {
raw::sqlite3_free(errmsg.cast());
}
s
};
Err(DbError::new(rc, msg))
}
pub fn exec_zeroized(&self, sql: &str) -> DbResult<()> {
let c_sql = to_cstring(sql)?;
let mut errmsg: *mut c_char = std::ptr::null_mut();
let rc = unsafe {
raw::sqlite3_exec(
self.ptr,
c_sql.as_ptr(),
None,
std::ptr::null_mut(),
&raw mut errmsg,
)
};
let mut bytes = c_sql.into_bytes_with_nul();
bytes.zeroize();
drop(bytes);
if rc == SQLITE_OK as c_int {
return Ok(());
}
let msg = if errmsg.is_null() {
self.errmsg()
} else {
let s = unsafe { CStr::from_ptr(errmsg) }
.to_string_lossy()
.into_owned();
unsafe {
raw::sqlite3_free(errmsg.cast());
}
s
};
Err(DbError::new(rc, msg))
}
pub fn prepare(&self, sql: &str) -> DbResult<RawStmt<'_>> {
let c_sql = to_cstring(sql)?;
let mut stmt_ptr: *mut c_void = std::ptr::null_mut();
let rc = unsafe {
raw::sqlite3_prepare_v2(
self.ptr,
c_sql.as_ptr(),
-1,
&raw mut stmt_ptr,
std::ptr::null_mut(),
)
};
if rc != SQLITE_OK as c_int || stmt_ptr.is_null() {
return Err(DbError::new(rc, self.errmsg()));
}
Ok(RawStmt {
ptr: stmt_ptr,
db: self,
})
}
pub fn changes(&self) -> i32 {
unsafe { raw::sqlite3_changes(self.ptr) }
}
pub fn last_insert_rowid(&self) -> i64 {
unsafe { raw::sqlite3_last_insert_rowid(self.ptr) }
}
pub fn errmsg(&self) -> String {
errmsg_from_ptr(self.ptr)
}
}
impl Drop for RawDb {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
raw::sqlite3_close_v2(self.ptr);
}
}
}
}
impl std::fmt::Debug for RawDb {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RawDb").finish_non_exhaustive()
}
}
impl RawStmt<'_> {
pub fn step(&mut self) -> DbResult<i32> {
let rc = unsafe { raw::sqlite3_step(self.ptr) };
match rc {
SQLITE_ROW => Ok(SQLITE_ROW),
SQLITE_DONE => Ok(SQLITE_DONE),
other => Err(DbError::new(other, self.errmsg())),
}
}
#[allow(dead_code)]
pub fn reset(&mut self) -> DbResult<()> {
let rc = unsafe { raw::sqlite3_reset(self.ptr) };
if rc == SQLITE_OK as c_int {
Ok(())
} else {
Err(DbError::new(rc, self.errmsg()))
}
}
pub fn bind_i64(&mut self, idx: i32, value: i64) -> DbResult<()> {
let rc = unsafe { raw::sqlite3_bind_int64(self.ptr, idx as c_int, value) };
check(rc, self)
}
pub fn bind_blob(&mut self, idx: i32, value: &[u8]) -> DbResult<()> {
let rc = unsafe {
raw::sqlite3_bind_blob(
self.ptr,
idx as c_int,
value.as_ptr().cast::<c_void>(),
c_int::try_from(value.len()).unwrap_or(c_int::MAX),
SQLITE_TRANSIENT,
)
};
check(rc, self)
}
pub fn bind_text(&mut self, idx: i32, value: &str) -> DbResult<()> {
let rc = unsafe {
raw::sqlite3_bind_text(
self.ptr,
idx as c_int,
value.as_ptr().cast::<c_char>(),
c_int::try_from(value.len()).unwrap_or(c_int::MAX),
SQLITE_TRANSIENT,
)
};
check(rc, self)
}
pub fn bind_null(&mut self, idx: i32) -> DbResult<()> {
let rc = unsafe { raw::sqlite3_bind_null(self.ptr, idx as c_int) };
check(rc, self)
}
pub fn column_i64(&self, col: i32) -> i64 {
unsafe { raw::sqlite3_column_int64(self.ptr, col as c_int) }
}
pub fn column_blob(&self, col: i32) -> Vec<u8> {
unsafe {
let ptr = raw::sqlite3_column_blob(self.ptr, col as c_int);
let len = raw::sqlite3_column_bytes(self.ptr, col as c_int);
if ptr.is_null() || len <= 0 {
Vec::new()
} else {
std::slice::from_raw_parts(
ptr.cast::<u8>(),
usize::try_from(len).unwrap_or(0),
)
.to_vec()
}
}
}
pub fn column_text(&self, col: i32) -> String {
unsafe {
let ptr = raw::sqlite3_column_text(self.ptr, col as c_int);
if ptr.is_null() {
String::new()
} else {
CStr::from_ptr(ptr).to_string_lossy().into_owned()
}
}
}
pub fn column_type(&self, col: i32) -> i32 {
unsafe { raw::sqlite3_column_type(self.ptr, col as c_int) }
}
#[allow(dead_code)]
pub fn column_count(&self) -> i32 {
unsafe { raw::sqlite3_column_count(self.ptr) }
}
fn errmsg(&self) -> String {
self.db.errmsg()
}
}
impl Drop for RawStmt<'_> {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
raw::sqlite3_finalize(self.ptr);
}
}
}
}
fn to_cstring(s: &str) -> DbResult<CString> {
CString::new(s)
.map_err(|e| DbError::new(SQLITE_ERROR, format!("nul byte in string: {e}")))
}
fn errmsg_from_ptr(db: *mut c_void) -> String {
unsafe {
let ptr = raw::sqlite3_errmsg(db);
if ptr.is_null() {
"unknown error".to_string()
} else {
CStr::from_ptr(ptr).to_string_lossy().into_owned()
}
}
}
fn check(rc: c_int, stmt: &RawStmt) -> DbResult<()> {
if rc == SQLITE_OK as c_int {
Ok(())
} else {
Err(DbError::new(rc, stmt.errmsg()))
}
}
#[cfg(not(target_arch = "wasm32"))]
mod raw {
use std::os::raw::{c_char, c_int, c_void};
pub type Sqlite3Callback = Option<
unsafe extern "C" fn(
arg: *mut c_void,
n_cols: c_int,
values: *mut *mut c_char,
names: *mut *mut c_char,
) -> c_int,
>;
#[allow(dead_code, non_camel_case_types)]
type sqlite3 = c_void;
#[allow(dead_code, non_camel_case_types)]
type sqlite3_stmt = c_void;
extern "C" {
pub fn sqlite3_open_v2(
filename: *const c_char,
pp_db: *mut *mut sqlite3,
flags: c_int,
z_vfs: *const c_char,
) -> c_int;
pub fn sqlite3_close_v2(db: *mut sqlite3) -> c_int;
pub fn sqlite3_exec(
db: *mut sqlite3,
sql: *const c_char,
callback: Sqlite3Callback,
arg: *mut c_void,
errmsg: *mut *mut c_char,
) -> c_int;
pub fn sqlite3_free(ptr: *mut c_void);
pub fn sqlite3_prepare_v2(
db: *mut sqlite3,
z_sql: *const c_char,
n_byte: c_int,
pp_stmt: *mut *mut sqlite3_stmt,
pz_tail: *mut *const c_char,
) -> c_int;
pub fn sqlite3_step(stmt: *mut sqlite3_stmt) -> c_int;
pub fn sqlite3_reset(stmt: *mut sqlite3_stmt) -> c_int;
pub fn sqlite3_finalize(stmt: *mut sqlite3_stmt) -> c_int;
pub fn sqlite3_bind_int64(
stmt: *mut sqlite3_stmt,
index: c_int,
value: i64,
) -> c_int;
pub fn sqlite3_bind_blob(
stmt: *mut sqlite3_stmt,
index: c_int,
value: *const c_void,
n: c_int,
destructor: isize,
) -> c_int;
pub fn sqlite3_bind_text(
stmt: *mut sqlite3_stmt,
index: c_int,
value: *const c_char,
n: c_int,
destructor: isize,
) -> c_int;
pub fn sqlite3_bind_null(stmt: *mut sqlite3_stmt, index: c_int) -> c_int;
pub fn sqlite3_column_int64(stmt: *mut sqlite3_stmt, i_col: c_int) -> i64;
pub fn sqlite3_column_blob(
stmt: *mut sqlite3_stmt,
i_col: c_int,
) -> *const c_void;
pub fn sqlite3_column_bytes(stmt: *mut sqlite3_stmt, i_col: c_int) -> c_int;
pub fn sqlite3_column_text(
stmt: *mut sqlite3_stmt,
i_col: c_int,
) -> *const c_char;
pub fn sqlite3_column_type(stmt: *mut sqlite3_stmt, i_col: c_int) -> c_int;
pub fn sqlite3_column_count(stmt: *mut sqlite3_stmt) -> c_int;
pub fn sqlite3_errmsg(db: *mut sqlite3) -> *const c_char;
pub fn sqlite3_changes(db: *mut sqlite3) -> c_int;
pub fn sqlite3_last_insert_rowid(db: *mut sqlite3) -> i64;
}
}
#[cfg(target_arch = "wasm32")]
mod raw {
use sqlite_wasm_rs as wasm;
use std::os::raw::{c_char, c_int, c_void};
pub type Sqlite3Callback = wasm::sqlite3_callback;
pub unsafe fn sqlite3_open_v2(
filename: *const c_char,
pp_db: *mut *mut c_void,
flags: c_int,
z_vfs: *const c_char,
) -> c_int {
wasm::sqlite3_open_v2(filename.cast(), pp_db.cast(), flags, z_vfs.cast())
}
pub unsafe fn sqlite3_close_v2(db: *mut c_void) -> c_int {
wasm::sqlite3_close_v2(db.cast())
}
pub unsafe fn sqlite3_exec(
db: *mut c_void,
sql: *const c_char,
callback: Sqlite3Callback,
arg: *mut c_void,
errmsg: *mut *mut c_char,
) -> c_int {
wasm::sqlite3_exec(db.cast(), sql.cast(), callback, arg, errmsg.cast())
}
pub unsafe fn sqlite3_free(ptr: *mut c_void) {
wasm::sqlite3_free(ptr);
}
pub unsafe fn sqlite3_prepare_v2(
db: *mut c_void,
z_sql: *const c_char,
n_byte: c_int,
pp_stmt: *mut *mut c_void,
pz_tail: *mut *const c_char,
) -> c_int {
wasm::sqlite3_prepare_v2(
db.cast(),
z_sql.cast(),
n_byte,
pp_stmt.cast(),
pz_tail.cast(),
)
}
pub unsafe fn sqlite3_step(stmt: *mut c_void) -> c_int {
wasm::sqlite3_step(stmt.cast())
}
pub unsafe fn sqlite3_reset(stmt: *mut c_void) -> c_int {
wasm::sqlite3_reset(stmt.cast())
}
pub unsafe fn sqlite3_finalize(stmt: *mut c_void) -> c_int {
wasm::sqlite3_finalize(stmt.cast())
}
pub unsafe fn sqlite3_bind_int64(
stmt: *mut c_void,
index: c_int,
value: i64,
) -> c_int {
wasm::sqlite3_bind_int64(stmt.cast(), index, value)
}
pub unsafe fn sqlite3_bind_blob(
stmt: *mut c_void,
index: c_int,
value: *const c_void,
n: c_int,
destructor: isize,
) -> c_int {
wasm::sqlite3_bind_blob(stmt.cast(), index, value, n, destructor)
}
pub unsafe fn sqlite3_bind_text(
stmt: *mut c_void,
index: c_int,
value: *const c_char,
n: c_int,
destructor: isize,
) -> c_int {
wasm::sqlite3_bind_text(stmt.cast(), index, value.cast(), n, destructor)
}
pub unsafe fn sqlite3_bind_null(stmt: *mut c_void, index: c_int) -> c_int {
wasm::sqlite3_bind_null(stmt.cast(), index)
}
pub unsafe fn sqlite3_column_int64(stmt: *mut c_void, i_col: c_int) -> i64 {
wasm::sqlite3_column_int64(stmt.cast(), i_col)
}
pub unsafe fn sqlite3_column_blob(
stmt: *mut c_void,
i_col: c_int,
) -> *const c_void {
wasm::sqlite3_column_blob(stmt.cast(), i_col)
}
pub unsafe fn sqlite3_column_bytes(stmt: *mut c_void, i_col: c_int) -> c_int {
wasm::sqlite3_column_bytes(stmt.cast(), i_col)
}
pub unsafe fn sqlite3_column_text(
stmt: *mut c_void,
i_col: c_int,
) -> *const c_char {
wasm::sqlite3_column_text(stmt.cast(), i_col).cast()
}
pub unsafe fn sqlite3_column_type(stmt: *mut c_void, i_col: c_int) -> c_int {
wasm::sqlite3_column_type(stmt.cast(), i_col)
}
pub unsafe fn sqlite3_column_count(stmt: *mut c_void) -> c_int {
wasm::sqlite3_column_count(stmt.cast())
}
pub unsafe fn sqlite3_errmsg(db: *mut c_void) -> *const c_char {
wasm::sqlite3_errmsg(db.cast()).cast()
}
pub unsafe fn sqlite3_changes(db: *mut c_void) -> c_int {
wasm::sqlite3_changes(db.cast())
}
pub unsafe fn sqlite3_last_insert_rowid(db: *mut c_void) -> i64 {
wasm::sqlite3_last_insert_rowid(db.cast())
}
}