use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::panic;
use std::sync::Arc;
use crate::api::Database;
use super::types::{StoolapDB, StoolapRows, StoolapStmt, StoolapValue};
use super::value;
use super::{STOOLAP_ERROR, STOOLAP_OK};
#[no_mangle]
pub unsafe extern "C" fn stoolap_prepare(
db: *mut StoolapDB,
sql: *const c_char,
out_stmt: *mut *mut StoolapStmt,
) -> i32 {
if out_stmt.is_null() {
return STOOLAP_ERROR;
}
*out_stmt = std::ptr::null_mut();
let handle = match db.as_mut() {
Some(h) => h,
None => return STOOLAP_ERROR,
};
handle.last_error = None;
if sql.is_null() {
handle.set_error("SQL string is NULL");
return STOOLAP_ERROR;
}
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let sql_str = match CStr::from_ptr(sql).to_str() {
Ok(s) => s,
Err(e) => {
handle.set_error(&format!("invalid UTF-8 in SQL: {}", e));
return STOOLAP_ERROR;
}
};
match handle.db.prepare(sql_str) {
Ok(stmt) => {
let sql_cstr = CString::new(sql_str).unwrap_or_default();
let db_keepalive = handle.db.keepalive();
let engine_keepalive = handle._engine_keepalive.clone();
let stmt_handle = Box::new(StoolapStmt {
stmt,
last_error: None,
sql_cstr,
cached_columns: None,
_db_keepalive: db_keepalive,
_engine_keepalive: engine_keepalive,
});
*out_stmt = Box::into_raw(stmt_handle);
STOOLAP_OK
}
Err(e) => {
handle.set_error(&e.to_string());
STOOLAP_ERROR
}
}
}));
result.unwrap_or_else(|_| {
handle.set_error("panic during stoolap_prepare");
STOOLAP_ERROR
})
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_stmt_exec(
stmt: *mut StoolapStmt,
params: *const StoolapValue,
params_len: i32,
rows_affected: *mut i64,
) -> i32 {
let handle = match stmt.as_mut() {
Some(h) => h,
None => return STOOLAP_ERROR,
};
handle.last_error = None;
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let param_vec = value::params_to_vec(params, params_len);
match handle.stmt.execute(param_vec) {
Ok(affected) => {
if !rows_affected.is_null() {
*rows_affected = affected;
}
STOOLAP_OK
}
Err(e) => {
handle.set_error(&e.to_string());
STOOLAP_ERROR
}
}
}));
result.unwrap_or_else(|_| {
handle.set_error("panic during stoolap_stmt_exec");
STOOLAP_ERROR
})
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_stmt_query(
stmt: *mut StoolapStmt,
params: *const StoolapValue,
params_len: i32,
out_rows: *mut *mut StoolapRows,
) -> i32 {
if out_rows.is_null() {
return STOOLAP_ERROR;
}
*out_rows = std::ptr::null_mut();
let handle = match stmt.as_mut() {
Some(h) => h,
None => return STOOLAP_ERROR,
};
handle.last_error = None;
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let param_vec = value::params_to_vec(params, params_len);
match handle.stmt.query(param_vec) {
Ok(rows) => {
let actual_columns = rows.columns();
let actual_count = actual_columns.len();
let cache_valid = handle.cached_columns.as_ref().is_some_and(|cached| {
cached.len() == actual_count
&& cached
.iter()
.zip(actual_columns.iter())
.all(|(c, a)| c.as_bytes() == a.as_str().as_bytes())
});
let column_names = if cache_valid {
Arc::clone(handle.cached_columns.as_ref().unwrap())
} else {
let names: Vec<CString> = actual_columns
.iter()
.map(|name| CString::new(name.as_str()).unwrap_or_default())
.collect();
let arc = Arc::new(names);
handle.cached_columns = Some(Arc::clone(&arc));
arc
};
let affected = rows.rows_affected();
let rows_handle = Box::new(StoolapRows {
rows: Some(rows),
has_row: false,
last_error: None,
column_names,
text_cache: Vec::new(),
text_cache_dirty: false,
rows_affected: affected,
});
*out_rows = Box::into_raw(rows_handle);
STOOLAP_OK
}
Err(e) => {
handle.set_error(&e.to_string());
STOOLAP_ERROR
}
}
}));
result.unwrap_or_else(|_| {
handle.set_error("panic during stoolap_stmt_query");
STOOLAP_ERROR
})
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_stmt_sql(stmt: *const StoolapStmt) -> *const c_char {
match stmt.as_ref() {
Some(handle) => handle.sql_cstr.as_ptr(),
None => super::error::empty_cstr(),
}
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_stmt_finalize(stmt: *mut StoolapStmt) {
if stmt.is_null() {
return;
}
let handle = Box::from_raw(stmt);
let engine_owning = match &handle._engine_keepalive {
Some(arc) => Arc::clone(arc),
None => Arc::clone(&handle._db_keepalive),
};
drop(handle);
Database::try_unregister_arc(&engine_owning);
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_stmt_errmsg(stmt: *const StoolapStmt) -> *const c_char {
match stmt.as_ref() {
Some(handle) => handle.error_ptr(),
None => super::error::empty_cstr(),
}
}