1use std::ffi::{CStr, CString};
12use std::os::raw::c_char;
13use std::ptr;
14use std::cell::RefCell;
15
16use crate::OverDriveDB;
17
18pub struct ODB {
20 inner: OverDriveDB,
21}
22
23thread_local! {
24 static LAST_ERROR: RefCell<Option<String>> = RefCell::new(None);
25}
26
27fn set_error(msg: String) {
28 LAST_ERROR.with(|e| *e.borrow_mut() = Some(msg));
29}
30
31fn clear_error() {
32 LAST_ERROR.with(|e| *e.borrow_mut() = None);
33}
34
35fn to_c_string(s: &str) -> *mut c_char {
36 CString::new(s).unwrap_or_default().into_raw()
37}
38
39unsafe fn from_c_str(ptr: *const c_char) -> Option<String> {
40 if ptr.is_null() {
41 return None;
42 }
43 Some(CStr::from_ptr(ptr).to_string_lossy().into_owned())
44}
45
46#[no_mangle]
52pub unsafe extern "C" fn overdrive_open(path: *const c_char) -> *mut ODB {
53 clear_error();
54 let path = match from_c_str(path) {
55 Some(p) => p,
56 None => { set_error("Invalid path".into()); return ptr::null_mut(); }
57 };
58 match OverDriveDB::open(&path) {
59 Ok(db) => Box::into_raw(Box::new(ODB { inner: db })),
60 Err(e) => { set_error(e.to_string()); ptr::null_mut() }
61 }
62}
63
64#[no_mangle]
66pub unsafe extern "C" fn overdrive_close(db: *mut ODB) {
67 if !db.is_null() {
68 let odb = Box::from_raw(db);
69 let _ = odb.inner.close();
70 }
71}
72
73#[no_mangle]
75pub unsafe extern "C" fn overdrive_sync(db: *mut ODB) {
76 if db.is_null() { return; }
77 let _ = (*db).inner.sync();
78}
79
80#[no_mangle]
86pub unsafe extern "C" fn overdrive_create_table(db: *mut ODB, name: *const c_char) -> i32 {
87 clear_error();
88 if db.is_null() { set_error("Null db handle".into()); return -1; }
89 let name = match from_c_str(name) {
90 Some(n) => n,
91 None => { set_error("Invalid table name".into()); return -1; }
92 };
93 match (*db).inner.create_table(&name) {
94 Ok(()) => 0,
95 Err(e) => { set_error(e.to_string()); -1 }
96 }
97}
98
99#[no_mangle]
101pub unsafe extern "C" fn overdrive_drop_table(db: *mut ODB, name: *const c_char) -> i32 {
102 clear_error();
103 if db.is_null() { set_error("Null db handle".into()); return -1; }
104 let name = match from_c_str(name) {
105 Some(n) => n,
106 None => { set_error("Invalid table name".into()); return -1; }
107 };
108 match (*db).inner.drop_table(&name) {
109 Ok(()) => 0,
110 Err(e) => { set_error(e.to_string()); -1 }
111 }
112}
113
114#[no_mangle]
116pub unsafe extern "C" fn overdrive_list_tables(db: *mut ODB) -> *mut c_char {
117 clear_error();
118 if db.is_null() { set_error("Null db handle".into()); return ptr::null_mut(); }
119 match (*db).inner.list_tables() {
120 Ok(tables) => {
121 let json = serde_json::to_string(&tables).unwrap_or_else(|_| "[]".into());
122 to_c_string(&json)
123 }
124 Err(e) => { set_error(e.to_string()); ptr::null_mut() }
125 }
126}
127
128#[no_mangle]
130pub unsafe extern "C" fn overdrive_table_exists(db: *mut ODB, name: *const c_char) -> i32 {
131 clear_error();
132 if db.is_null() { return -1; }
133 let name = match from_c_str(name) {
134 Some(n) => n,
135 None => { return -1; }
136 };
137 match (*db).inner.table_exists(&name) {
138 Ok(true) => 1,
139 Ok(false) => 0,
140 Err(e) => { set_error(e.to_string()); -1 }
141 }
142}
143
144#[no_mangle]
150pub unsafe extern "C" fn overdrive_insert(db: *mut ODB, table: *const c_char, json: *const c_char) -> *mut c_char {
151 clear_error();
152 if db.is_null() { set_error("Null db handle".into()); return ptr::null_mut(); }
153 let table = match from_c_str(table) { Some(t) => t, None => { set_error("Invalid table".into()); return ptr::null_mut(); } };
154 let json_str = match from_c_str(json) { Some(j) => j, None => { set_error("Invalid JSON".into()); return ptr::null_mut(); } };
155
156 let value: serde_json::Value = match serde_json::from_str(&json_str) {
157 Ok(v) => v,
158 Err(e) => { set_error(format!("JSON parse error: {}", e)); return ptr::null_mut(); }
159 };
160
161 match (*db).inner.insert(&table, &value) {
162 Ok(id) => to_c_string(&id),
163 Err(e) => { set_error(e.to_string()); ptr::null_mut() }
164 }
165}
166
167#[no_mangle]
169pub unsafe extern "C" fn overdrive_get(db: *mut ODB, table: *const c_char, id: *const c_char) -> *mut c_char {
170 clear_error();
171 if db.is_null() { return ptr::null_mut(); }
172 let table = match from_c_str(table) { Some(t) => t, None => return ptr::null_mut() };
173 let id = match from_c_str(id) { Some(i) => i, None => return ptr::null_mut() };
174
175 match (*db).inner.get(&table, &id) {
176 Ok(Some(val)) => to_c_string(&val.to_string()),
177 Ok(None) => ptr::null_mut(),
178 Err(e) => { set_error(e.to_string()); ptr::null_mut() }
179 }
180}
181
182#[no_mangle]
184pub unsafe extern "C" fn overdrive_update(db: *mut ODB, table: *const c_char, id: *const c_char, json: *const c_char) -> i32 {
185 clear_error();
186 if db.is_null() { return -1; }
187 let table = match from_c_str(table) { Some(t) => t, None => return -1 };
188 let id = match from_c_str(id) { Some(i) => i, None => return -1 };
189 let json_str = match from_c_str(json) { Some(j) => j, None => return -1 };
190
191 let value: serde_json::Value = match serde_json::from_str(&json_str) {
192 Ok(v) => v,
193 Err(e) => { set_error(format!("JSON parse error: {}", e)); return -1; }
194 };
195
196 match (*db).inner.update(&table, &id, &value) {
197 Ok(true) => 1,
198 Ok(false) => 0,
199 Err(e) => { set_error(e.to_string()); -1 }
200 }
201}
202
203#[no_mangle]
205pub unsafe extern "C" fn overdrive_delete(db: *mut ODB, table: *const c_char, id: *const c_char) -> i32 {
206 clear_error();
207 if db.is_null() { return -1; }
208 let table = match from_c_str(table) { Some(t) => t, None => return -1 };
209 let id = match from_c_str(id) { Some(i) => i, None => return -1 };
210
211 match (*db).inner.delete(&table, &id) {
212 Ok(true) => 1,
213 Ok(false) => 0,
214 Err(e) => { set_error(e.to_string()); -1 }
215 }
216}
217
218#[no_mangle]
220pub unsafe extern "C" fn overdrive_count(db: *mut ODB, table: *const c_char) -> i32 {
221 clear_error();
222 if db.is_null() { return -1; }
223 let table = match from_c_str(table) { Some(t) => t, None => return -1 };
224
225 match (*db).inner.count(&table) {
226 Ok(n) => n as i32,
227 Err(e) => { set_error(e.to_string()); -1 }
228 }
229}
230
231#[no_mangle]
237pub unsafe extern "C" fn overdrive_query(db: *mut ODB, sql: *const c_char) -> *mut c_char {
238 clear_error();
239 if db.is_null() { set_error("Null db handle".into()); return ptr::null_mut(); }
240 let sql = match from_c_str(sql) { Some(s) => s, None => return ptr::null_mut() };
241
242 match (*db).inner.query(&sql) {
243 Ok(result) => {
244 let json = serde_json::json!({
245 "rows": result.rows,
246 "columns": result.columns,
247 "rows_affected": result.rows_affected,
248 "execution_time_ms": result.execution_time_ms,
249 });
250 to_c_string(&json.to_string())
251 }
252 Err(e) => { set_error(e.to_string()); ptr::null_mut() }
253 }
254}
255
256#[no_mangle]
258pub unsafe extern "C" fn overdrive_search(db: *mut ODB, table: *const c_char, text: *const c_char) -> *mut c_char {
259 clear_error();
260 if db.is_null() { return ptr::null_mut(); }
261 let table = match from_c_str(table) { Some(t) => t, None => return ptr::null_mut() };
262 let text = match from_c_str(text) { Some(t) => t, None => return ptr::null_mut() };
263
264 match (*db).inner.search(&table, &text) {
265 Ok(results) => {
266 let json = serde_json::to_string(&results).unwrap_or_else(|_| "[]".into());
267 to_c_string(&json)
268 }
269 Err(e) => { set_error(e.to_string()); ptr::null_mut() }
270 }
271}
272
273#[no_mangle]
280pub unsafe extern "C" fn overdrive_last_error() -> *const c_char {
281 LAST_ERROR.with(|e| {
282 match &*e.borrow() {
283 Some(msg) => {
284 let cs = CString::new(msg.as_str()).unwrap_or_default();
286 cs.into_raw() as *const c_char
287 }
288 None => ptr::null(),
289 }
290 })
291}
292
293#[no_mangle]
295pub unsafe extern "C" fn overdrive_free_string(ptr: *mut c_char) {
296 if !ptr.is_null() {
297 let _ = CString::from_raw(ptr);
298 }
299}
300
301#[no_mangle]
303pub extern "C" fn overdrive_version() -> *const c_char {
304 b"1.4.0\0".as_ptr() as *const c_char
306}
307
308#[no_mangle]
315pub unsafe extern "C" fn overdrive_begin_transaction(db: *mut ODB, isolation_level: i32) -> u64 {
316 clear_error();
317 if db.is_null() { set_error("Null db handle".into()); return 0; }
318 let isolation = crate::IsolationLevel::from_i32(isolation_level);
319 match (*db).inner.begin_transaction(isolation) {
320 Ok(txn) => txn.txn_id,
321 Err(e) => { set_error(e.to_string()); 0 }
322 }
323}
324
325#[no_mangle]
327pub unsafe extern "C" fn overdrive_commit_transaction(db: *mut ODB, txn_id: u64) -> i32 {
328 clear_error();
329 if db.is_null() { set_error("Null db handle".into()); return -1; }
330 let txn = crate::TransactionHandle {
331 txn_id,
332 isolation: crate::IsolationLevel::ReadCommitted,
333 active: true,
334 };
335 match (*db).inner.commit_transaction(&txn) {
336 Ok(()) => 0,
337 Err(e) => { set_error(e.to_string()); -1 }
338 }
339}
340
341#[no_mangle]
343pub unsafe extern "C" fn overdrive_abort_transaction(db: *mut ODB, txn_id: u64) -> i32 {
344 clear_error();
345 if db.is_null() { set_error("Null db handle".into()); return -1; }
346 let txn = crate::TransactionHandle {
347 txn_id,
348 isolation: crate::IsolationLevel::ReadCommitted,
349 active: true,
350 };
351 match (*db).inner.abort_transaction(&txn) {
352 Ok(()) => 0,
353 Err(e) => { set_error(e.to_string()); -1 }
354 }
355}
356
357#[no_mangle]
363pub unsafe extern "C" fn overdrive_verify_integrity(db: *mut ODB) -> *mut c_char {
364 clear_error();
365 if db.is_null() { set_error("Null db handle".into()); return ptr::null_mut(); }
366 match (*db).inner.verify_integrity() {
367 Ok(report) => {
368 let json = serde_json::json!({
369 "valid": report.is_valid,
370 "pages_checked": report.pages_checked,
371 "tables_verified": report.tables_verified,
372 "issues": report.issues,
373 });
374 to_c_string(&json.to_string())
375 }
376 Err(e) => { set_error(e.to_string()); ptr::null_mut() }
377 }
378}
379
380#[no_mangle]
382pub unsafe extern "C" fn overdrive_stats(db: *mut ODB) -> *mut c_char {
383 clear_error();
384 if db.is_null() { set_error("Null db handle".into()); return ptr::null_mut(); }
385 match (*db).inner.stats() {
386 Ok(stats) => {
387 let json = serde_json::json!({
388 "tables": stats.tables,
389 "total_records": stats.total_records,
390 "file_size_bytes": stats.file_size_bytes,
391 "path": stats.path,
392 "mvcc_active_versions": stats.mvcc_active_versions,
393 "page_size": stats.page_size,
394 "sdk_version": stats.sdk_version,
395 });
396 to_c_string(&json.to_string())
397 }
398 Err(e) => { set_error(e.to_string()); ptr::null_mut() }
399 }
400}
401