use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::panic;
use std::sync::Arc;
use crate::api::Database;
use crate::common::version::VERSION;
use super::error;
use super::types::{StoolapDB, StoolapRows, StoolapValue};
use super::value;
use super::{STOOLAP_ERROR, STOOLAP_OK};
static VERSION_CSTR: std::sync::OnceLock<CString> = std::sync::OnceLock::new();
#[no_mangle]
pub extern "C" fn stoolap_version() -> *const c_char {
VERSION_CSTR
.get_or_init(|| CString::new(VERSION).unwrap_or_default())
.as_ptr()
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_open(dsn: *const c_char, out_db: *mut *mut StoolapDB) -> i32 {
if out_db.is_null() {
return STOOLAP_ERROR;
}
*out_db = std::ptr::null_mut();
if dsn.is_null() {
error::set_global_error("DSN string is NULL");
return STOOLAP_ERROR;
}
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let dsn_str = match CStr::from_ptr(dsn).to_str() {
Ok(s) => s,
Err(e) => {
error::set_global_error(&format!("invalid UTF-8 in DSN: {}", e));
return STOOLAP_ERROR;
}
};
match Database::open(dsn_str) {
Ok(db) => {
let handle = Box::new(StoolapDB {
db,
last_error: None,
_engine_keepalive: None,
});
*out_db = Box::into_raw(handle);
STOOLAP_OK
}
Err(e) => {
error::set_global_error(&e.to_string());
STOOLAP_ERROR
}
}
}));
result.unwrap_or_else(|_| {
error::set_global_error("panic during stoolap_open");
STOOLAP_ERROR
})
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_open_in_memory(out_db: *mut *mut StoolapDB) -> i32 {
if out_db.is_null() {
return STOOLAP_ERROR;
}
*out_db = std::ptr::null_mut();
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
match Database::open_in_memory() {
Ok(db) => {
let handle = Box::new(StoolapDB {
db,
last_error: None,
_engine_keepalive: None,
});
*out_db = Box::into_raw(handle);
STOOLAP_OK
}
Err(e) => {
error::set_global_error(&e.to_string());
STOOLAP_ERROR
}
}
}));
result.unwrap_or_else(|_| {
error::set_global_error("panic during stoolap_open_in_memory");
STOOLAP_ERROR
})
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_close(db: *mut StoolapDB) -> i32 {
if db.is_null() {
return STOOLAP_OK;
}
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let handle = Box::from_raw(db);
match &handle._engine_keepalive {
None => Database::try_unregister_arc(handle.db.inner_arc()),
Some(keepalive) => Database::try_unregister_arc(keepalive),
}
drop(handle);
STOOLAP_OK
}));
result.unwrap_or(STOOLAP_ERROR)
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_clone(db: *const StoolapDB, out_db: *mut *mut StoolapDB) -> i32 {
if out_db.is_null() {
error::set_global_error("out_db pointer is NULL");
return STOOLAP_ERROR;
}
*out_db = std::ptr::null_mut();
let handle = match db.as_ref() {
Some(h) => h,
None => {
error::set_global_error("db handle is NULL");
return STOOLAP_ERROR;
}
};
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let keepalive = match &handle._engine_keepalive {
Some(arc) => std::sync::Arc::clone(arc),
None => handle.db.keepalive(),
};
let cloned = handle.db.clone();
let new_handle = Box::new(StoolapDB {
db: cloned,
last_error: None,
_engine_keepalive: Some(keepalive),
});
*out_db = Box::into_raw(new_handle);
STOOLAP_OK
}));
result.unwrap_or_else(|_| {
error::set_global_error("panic during stoolap_clone");
STOOLAP_ERROR
})
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_errmsg(db: *const StoolapDB) -> *const c_char {
match db.as_ref() {
Some(handle) => handle.error_ptr(),
None => error::global_error_ptr(),
}
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_exec(
db: *mut StoolapDB,
sql: *const c_char,
rows_affected: *mut i64,
) -> i32 {
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.execute(sql_str, ()) {
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_exec");
STOOLAP_ERROR
})
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_exec_params(
db: *mut StoolapDB,
sql: *const c_char,
params: *const StoolapValue,
params_len: i32,
rows_affected: *mut i64,
) -> i32 {
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;
}
};
let param_vec = value::params_to_vec(params, params_len);
match handle.db.execute(sql_str, 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_exec_params");
STOOLAP_ERROR
})
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_query(
db: *mut StoolapDB,
sql: *const c_char,
out_rows: *mut *mut StoolapRows,
) -> i32 {
stoolap_query_params(db, sql, std::ptr::null(), 0, out_rows)
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_query_params(
db: *mut StoolapDB,
sql: *const c_char,
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 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;
}
};
let param_vec = value::params_to_vec(params, params_len);
match handle.db.query(sql_str, param_vec) {
Ok(rows) => {
let column_names: Vec<CString> = rows
.columns()
.iter()
.map(|name| CString::new(name.as_str()).unwrap_or_default())
.collect();
let affected = rows.rows_affected();
let rows_handle = Box::new(StoolapRows {
rows: Some(rows),
has_row: false,
last_error: None,
column_names: Arc::new(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_query_params");
STOOLAP_ERROR
})
}
#[no_mangle]
pub unsafe extern "C" fn stoolap_string_free(s: *mut c_char) {
if !s.is_null() {
let _ = CString::from_raw(s);
}
}