Files
sqlight/src/worker/sqlitend.rs
2025-05-18 00:08:05 +08:00

267 lines
7.9 KiB
Rust

use sqlite_wasm_rs::*;
use std::ffi::{CStr, CString};
use std::sync::Arc;
use std::sync::atomic::{self, AtomicBool};
use crate::{
InnerError, SQLiteStatementResult, SQLiteStatementTable, SQLiteStatementValues, SQLitendError,
};
type Result<T> = std::result::Result<T, SQLitendError>;
fn cstr(s: &str) -> Result<CString> {
CString::new(s).map_err(|_| SQLitendError::ToCStr)
}
fn sqlite_err(code: i32, db: *mut sqlite3) -> InnerError {
let message = unsafe {
let ptr = sqlite3_errmsg(db);
CStr::from_ptr(ptr).to_string_lossy().to_string()
};
InnerError { code, message }
}
pub struct SQLiteDb {
sqlite3: *mut sqlite3,
}
unsafe impl Send for SQLiteDb {}
unsafe impl Sync for SQLiteDb {}
impl SQLiteDb {
pub fn open(filename: &str) -> Result<Arc<Self>> {
let mut sqlite3 = std::ptr::null_mut();
let ret = unsafe {
sqlite3_open_v2(
cstr(filename)?.as_ptr().cast(),
&mut sqlite3 as *mut _,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
std::ptr::null(),
)
};
if ret != SQLITE_OK {
return Err(SQLitendError::OpenDb(sqlite_err(ret, sqlite3)));
}
Ok(Arc::new(Self { sqlite3 }))
}
pub fn prepare(self: &Arc<Self>, sql: &str) -> Result<SQLiteStatements> {
let sql = cstr(sql)?;
let tail = sql.as_ptr();
Ok(SQLiteStatements {
sql,
db: Arc::clone(self),
tail,
})
}
}
impl Drop for SQLiteDb {
fn drop(&mut self) {
unsafe {
sqlite3_close(self.sqlite3);
}
}
}
pub struct SQLiteStatements {
sql: CString,
db: Arc<SQLiteDb>,
tail: *const i8,
}
unsafe impl Send for SQLiteStatements {}
unsafe impl Sync for SQLiteStatements {}
impl SQLiteStatements {
pub fn prepare_next(&mut self) -> Result<Option<SQLitePreparedStatement>> {
if self.tail.is_null() {
return Ok(None);
}
let sqlite3 = self.db.sqlite3;
let mut stmt: *mut sqlite3_stmt = std::ptr::null_mut();
let mut tail = std::ptr::null();
let ret = unsafe {
sqlite3_prepare_v3(sqlite3, self.tail, -1, 0, &mut stmt as _, &mut tail as _)
};
if ret != SQLITE_OK {
return Err(SQLitendError::Prepare(sqlite_err(ret, sqlite3)));
}
let sql = unsafe { sqlite3_sql(stmt) };
if sql.is_null() {
return Ok(None);
}
let sql = unsafe { CStr::from_ptr(sql).to_string_lossy().to_string() };
let start_offset = self.tail as usize - self.sql.as_ptr() as usize;
let end_offset = start_offset + sql.len();
let position = [start_offset, end_offset];
self.tail = tail;
Ok(Some(SQLitePreparedStatement {
sql,
done: AtomicBool::new(false),
position,
sqlite3,
stmt,
}))
}
pub fn stmts_result(self) -> Result<Vec<SQLiteStatementResult>> {
let mut result = vec![];
for stmt in self {
let stmt = stmt?;
result.push(stmt.pack(stmt.get_all()?));
}
Ok(result)
}
}
impl Iterator for SQLiteStatements {
type Item = Result<SQLitePreparedStatement>;
fn next(&mut self) -> Option<Self::Item> {
self.prepare_next().transpose()
}
}
pub struct SQLitePreparedStatement {
sql: String,
position: [usize; 2],
done: AtomicBool,
sqlite3: *mut sqlite3,
stmt: *mut sqlite3_stmt,
}
unsafe impl Send for SQLitePreparedStatement {}
unsafe impl Sync for SQLitePreparedStatement {}
impl SQLitePreparedStatement {
/// Stepping to the next line
fn step(&self) -> Result<bool> {
let ret = unsafe { sqlite3_step(self.stmt) };
match ret {
SQLITE_DONE => {
self.done.store(true, atomic::Ordering::SeqCst);
Ok(false)
}
SQLITE_ROW => Ok(true),
code => Err(SQLitendError::Step(sqlite_err(code, self.sqlite3))),
}
}
pub fn pack(&self, values: Option<SQLiteStatementValues>) -> SQLiteStatementResult {
let values = SQLiteStatementTable {
sql: self.sql.clone(),
position: self.position,
done: self.done.load(atomic::Ordering::SeqCst),
values,
};
SQLiteStatementResult::Step(values)
}
pub fn get_all(&self) -> Result<Option<SQLiteStatementValues>> {
let mut values = match self.get_one()? {
Some(value) => value,
None => return Ok(None),
};
while let Some(value) = self.get_one()? {
for row in value.rows {
values.rows.push(row);
}
}
Ok(Some(values))
}
/// Get data for all columns of the current row
pub fn get_one(&self) -> Result<Option<SQLiteStatementValues>> {
if !self.step()? {
return Ok(None);
}
let column_count = unsafe { sqlite3_column_count(self.stmt) };
let mut column = Vec::with_capacity(column_count as usize);
let mut row = Vec::with_capacity(column_count as usize);
for col_ndx in 0..column_count {
// column_name as key
let (column_name, column_type) = unsafe {
let ptr = sqlite3_column_name(self.stmt, col_ndx);
if ptr.is_null() {
return Err(SQLitendError::GetColumnName(
"the column name is a null pointer, this shouldn't happen".into(),
));
}
let Ok(column_name) = CStr::from_ptr(ptr).to_str() else {
return Err(SQLitendError::GetColumnName(
"the column name is not a string, this shouldn't happen".into(),
));
};
(column_name, sqlite3_column_type(self.stmt, col_ndx))
};
// https://www.sqlite.org/c3ref/column_blob.html
let value = unsafe {
match column_type {
SQLITE_NULL => "Null".to_string(),
SQLITE_INTEGER => {
let number = sqlite3_column_int64(self.stmt, col_ndx);
number.to_string()
}
SQLITE_FLOAT => sqlite3_column_double(self.stmt, col_ndx).to_string(),
SQLITE_TEXT => {
let slice = {
let text = sqlite3_column_text(self.stmt, col_ndx);
// get text size, there may be problems if use as cstr
let len = sqlite3_column_bytes(self.stmt, col_ndx);
std::slice::from_raw_parts(text, len as usize)
};
// must be UTF-8 TEXT result
let Ok(text) = std::str::from_utf8(slice) else {
return Err(SQLitendError::Utf8Text);
};
format!("{text:?}")
}
SQLITE_BLOB => {
let slice = {
let blob = sqlite3_column_blob(self.stmt, col_ndx);
let len = sqlite3_column_bytes(self.stmt, col_ndx);
std::slice::from_raw_parts(blob.cast::<u8>(), len as usize)
};
let hex = hex::encode(slice);
format!("x'{hex}'")
}
_ => return Err(SQLitendError::UnsupportColumnType(column_type)),
}
};
column.push(column_name.into());
row.push(value);
}
Ok(Some(SQLiteStatementValues {
columns: column,
rows: vec![row],
}))
}
}
impl Drop for SQLitePreparedStatement {
fn drop(&mut self) {
unsafe {
sqlite3_finalize(self.stmt);
};
}
}