Skip to main content

overdrive/
lib.rs

1//! OverDrive-DB Rust SDK v1.0.0
2//!
3//! Zero-config embedded document database with SQL, MVCC transactions,
4//! and 6 storage engines.
5//!
6//! # Quick Start
7//! ```rust,no_run
8//! use overdrive::OverdriveDb;
9//! use serde_json::json;
10//!
11//! let mut odb = OverdriveDb::open("app.odb").unwrap();
12//! odb.create_table("users").unwrap();
13//! let id = odb.insert("users", &json!({"name":"Alice","age":30})).unwrap();
14//! let doc = odb.get("users", &id).unwrap();
15//! println!("{}", doc.unwrap());
16//! odb.close().unwrap();
17//! ```
18
19pub mod ffi;
20pub mod error;
21
22pub use error::{Result, SdkError};
23
24use std::os::raw::c_char;
25use serde_json::Value;
26use libloading::Symbol;
27
28/// MVCC transaction isolation levels.
29#[derive(Debug, Clone, Copy)]
30pub enum IsolationLevel {
31    ReadUncommitted = 0,
32    ReadCommitted   = 1,
33    RepeatableRead  = 2,
34    Serializable    = 3,
35}
36
37/// A live MVCC transaction handle.
38pub struct Transaction {
39    pub id: u64,
40}
41
42/// Options for opening a database.
43#[derive(Default)]
44pub struct OpenOptions {
45    pub password: Option<String>,
46    pub engine:   Option<String>,
47    pub auto_create_tables: bool,
48}
49
50impl OpenOptions {
51    pub fn new() -> Self { Self { auto_create_tables: true, ..Default::default() } }
52    pub fn password(mut self, p: &str) -> Self { self.password = Some(p.into()); self }
53    pub fn engine(mut self, e: &str)   -> Self { self.engine   = Some(e.into()); self }
54}
55
56// ── Internal helper ──────────────────────────────────────────────────────────
57
58fn last_error() -> String {
59    unsafe {
60        let lib = ffi::load();
61        let func: Symbol<unsafe extern "C" fn() -> *const c_char> =
62            lib.get(b"overdrive_last_error").unwrap();
63        let ptr = func();
64        ffi::read_static(ptr)
65    }
66}
67
68// ── Main struct ───────────────────────────────────────────────────────────────
69
70/// OverDrive-DB embedded database handle.
71///
72/// All operations use `odb` as the idiomatic instance name:
73/// ```rust,no_run
74/// let mut odb = OverdriveDb::open("app.odb").unwrap();
75/// odb.insert("users", &serde_json::json!({"name":"Alice"})).unwrap();
76/// ```
77pub struct OverdriveDb {
78    handle: *mut std::ffi::c_void,
79}
80
81unsafe impl Send for OverdriveDb {}
82
83impl Drop for OverdriveDb {
84    fn drop(&mut self) {
85        if !self.handle.is_null() {
86            unsafe {
87                let lib = ffi::load();
88                let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void)> =
89                    lib.get(b"overdrive_close").unwrap();
90                func(self.handle);
91            }
92            self.handle = std::ptr::null_mut();
93        }
94    }
95}
96
97impl OverdriveDb {
98    // ── Lifecycle ─────────────────────────────────────────────────────────
99
100    /// Open or create a database at `path`.
101    pub fn open(path: &str) -> Result<Self> {
102        let c_path = ffi::to_cstr(path);
103        unsafe {
104            let lib = ffi::load();
105            let func: Symbol<unsafe extern "C" fn(*const c_char) -> *mut std::ffi::c_void> =
106                lib.get(b"overdrive_open").unwrap();
107            let handle = func(c_path.as_ptr());
108            if handle.is_null() {
109                return Err(SdkError(last_error()));
110            }
111            Ok(Self { handle })
112        }
113    }
114
115    /// Open with engine and/or password options.
116    pub fn open_with_options(path: &str, opts: OpenOptions) -> Result<Self> {
117        let c_path   = ffi::to_cstr(path);
118        let engine   = opts.engine.as_deref().unwrap_or("Disk");
119        let c_engine = ffi::to_cstr(engine);
120        let options  = serde_json::json!({
121            "password":           opts.password,
122            "auto_create_tables": opts.auto_create_tables,
123        });
124        let c_opts = ffi::to_cstr(&options.to_string());
125        unsafe {
126            let lib = ffi::load();
127            let func: Symbol<unsafe extern "C" fn(*const c_char, *const c_char, *const c_char) -> *mut std::ffi::c_void> =
128                lib.get(b"overdrive_open_with_engine").unwrap();
129            let handle = func(c_path.as_ptr(), c_engine.as_ptr(), c_opts.as_ptr());
130            if handle.is_null() {
131                return Err(SdkError(last_error()));
132            }
133            Ok(Self { handle })
134        }
135    }
136
137    /// Flush all pending writes to disk.
138    pub fn sync(&self) -> Result<()> {
139        unsafe {
140            let lib = ffi::load();
141            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void)> =
142                lib.get(b"overdrive_sync").unwrap();
143            func(self.handle);
144        }
145        Ok(())
146    }
147
148    /// Close the database explicitly (also called on Drop).
149    pub fn close(mut self) -> Result<()> {
150        unsafe {
151            let lib = ffi::load();
152            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void)> =
153                lib.get(b"overdrive_close").unwrap();
154            func(self.handle);
155            self.handle = std::ptr::null_mut();
156        }
157        Ok(())
158    }
159
160    /// Return the native library version string.
161    pub fn version() -> String {
162        unsafe {
163            let lib = ffi::load();
164            let func: Symbol<unsafe extern "C" fn() -> *const c_char> =
165                lib.get(b"overdrive_version").unwrap();
166            ffi::read_static(func())
167        }
168    }
169
170    // ── Tables ────────────────────────────────────────────────────────────
171
172    /// Create a table.
173    pub fn create_table(&mut self, name: &str) -> Result<()> {
174        let c_name = ffi::to_cstr(name);
175        unsafe {
176            let lib = ffi::load();
177            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char) -> i32> =
178                lib.get(b"overdrive_create_table").unwrap();
179            if func(self.handle, c_name.as_ptr()) != 0 {
180                return Err(SdkError(last_error()));
181            }
182        }
183        Ok(())
184    }
185
186    /// Drop a table.
187    pub fn drop_table(&mut self, name: &str) -> Result<()> {
188        let c_name = ffi::to_cstr(name);
189        unsafe {
190            let lib = ffi::load();
191            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char) -> i32> =
192                lib.get(b"overdrive_drop_table").unwrap();
193            if func(self.handle, c_name.as_ptr()) != 0 {
194                return Err(SdkError(last_error()));
195            }
196        }
197        Ok(())
198    }
199
200    /// List all tables.
201    pub fn list_tables(&self) -> Result<Vec<String>> {
202        unsafe {
203            let lib = ffi::load();
204            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void) -> *mut c_char> =
205                lib.get(b"overdrive_list_tables").unwrap();
206            let ptr = func(self.handle);
207            if ptr.is_null() { return Err(SdkError(last_error())); }
208            let s = ffi::read_and_free(ptr);
209            Ok(serde_json::from_str(&s)?)
210        }
211    }
212
213    /// Check if a table exists.
214    pub fn table_exists(&self, name: &str) -> Result<bool> {
215        let c_name = ffi::to_cstr(name);
216        unsafe {
217            let lib = ffi::load();
218            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char) -> i32> =
219                lib.get(b"overdrive_table_exists").unwrap();
220            Ok(func(self.handle, c_name.as_ptr()) == 1)
221        }
222    }
223
224    // ── CRUD ─────────────────────────────────────────────────────────────
225
226    /// Insert a JSON document. Returns the generated `_id`.
227    pub fn insert(&mut self, table: &str, doc: &Value) -> Result<String> {
228        let c_table = ffi::to_cstr(table);
229        let c_json  = ffi::to_cstr(&doc.to_string());
230        unsafe {
231            let lib = ffi::load();
232            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char, *const c_char) -> *mut c_char> =
233                lib.get(b"overdrive_insert").unwrap();
234            let ptr = func(self.handle, c_table.as_ptr(), c_json.as_ptr());
235            if ptr.is_null() { return Err(SdkError(last_error())); }
236            Ok(ffi::read_and_free(ptr))
237        }
238    }
239
240    /// Insert multiple documents. Returns a Vec of generated `_id`s.
241    pub fn insert_batch(&mut self, table: &str, docs: &[Value]) -> Result<Vec<String>> {
242        docs.iter().map(|doc| self.insert(table, doc)).collect()
243    }
244
245    /// Get a document by `_id`. Returns `None` if not found.
246    pub fn get(&self, table: &str, id: &str) -> Result<Option<Value>> {
247        let c_table = ffi::to_cstr(table);
248        let c_id    = ffi::to_cstr(id);
249        unsafe {
250            let lib = ffi::load();
251            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char, *const c_char) -> *mut c_char> =
252                lib.get(b"overdrive_get").unwrap();
253            let ptr = func(self.handle, c_table.as_ptr(), c_id.as_ptr());
254            if ptr.is_null() { return Ok(None); }
255            let s = ffi::read_and_free(ptr);
256            Ok(Some(serde_json::from_str(&s)?))
257        }
258    }
259
260    /// Update a document by `_id`. Returns `true` if updated.
261    pub fn update(&mut self, table: &str, id: &str, patch: &Value) -> Result<bool> {
262        let c_table = ffi::to_cstr(table);
263        let c_id    = ffi::to_cstr(id);
264        let c_json  = ffi::to_cstr(&patch.to_string());
265        unsafe {
266            let lib = ffi::load();
267            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char, *const c_char, *const c_char) -> i32> =
268                lib.get(b"overdrive_update").unwrap();
269            match func(self.handle, c_table.as_ptr(), c_id.as_ptr(), c_json.as_ptr()) {
270                1  => Ok(true),
271                0  => Ok(false),
272                _  => Err(SdkError(last_error())),
273            }
274        }
275    }
276
277    /// Delete a document by `_id`. Returns `true` if deleted.
278    pub fn delete(&mut self, table: &str, id: &str) -> Result<bool> {
279        let c_table = ffi::to_cstr(table);
280        let c_id    = ffi::to_cstr(id);
281        unsafe {
282            let lib = ffi::load();
283            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char, *const c_char) -> i32> =
284                lib.get(b"overdrive_delete").unwrap();
285            match func(self.handle, c_table.as_ptr(), c_id.as_ptr()) {
286                1 => Ok(true),
287                0 => Ok(false),
288                _ => Err(SdkError(last_error())),
289            }
290        }
291    }
292
293    /// Count documents in a table.
294    pub fn count(&self, table: &str) -> Result<usize> {
295        let c_table = ffi::to_cstr(table);
296        unsafe {
297            let lib = ffi::load();
298            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char) -> i32> =
299                lib.get(b"overdrive_count").unwrap();
300            let n = func(self.handle, c_table.as_ptr());
301            if n < 0 { return Err(SdkError(last_error())); }
302            Ok(n as usize)
303        }
304    }
305
306    // ── Query ─────────────────────────────────────────────────────────────
307
308    /// Execute a SQL query. Returns rows as a Vec of JSON Values.
309    pub fn query(&mut self, sql: &str) -> Result<Vec<Value>> {
310        let c_sql = ffi::to_cstr(sql);
311        unsafe {
312            let lib = ffi::load();
313            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char) -> *mut c_char> =
314                lib.get(b"overdrive_query").unwrap();
315            let ptr = func(self.handle, c_sql.as_ptr());
316            if ptr.is_null() { return Err(SdkError(last_error())); }
317            let s = ffi::read_and_free(ptr);
318            let v: Value = serde_json::from_str(&s)?;
319            // Response: {"rows":[...], "ok": true}  or  {"result":"...", "ok": true}
320            if let Some(rows) = v.get("rows").and_then(|r| r.as_array()) {
321                return Ok(rows.clone());
322            }
323            Ok(vec![v])
324        }
325    }
326
327    /// Full-text search across a table.
328    pub fn search(&self, table: &str, text: &str) -> Result<Vec<Value>> {
329        let c_table = ffi::to_cstr(table);
330        let c_text  = ffi::to_cstr(text);
331        unsafe {
332            let lib = ffi::load();
333            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, *const c_char, *const c_char) -> *mut c_char> =
334                lib.get(b"overdrive_search").unwrap();
335            let ptr = func(self.handle, c_table.as_ptr(), c_text.as_ptr());
336            if ptr.is_null() { return Ok(vec![]); }
337            let s = ffi::read_and_free(ptr);
338            Ok(serde_json::from_str(&s).unwrap_or_default())
339        }
340    }
341
342    // ── Transactions ─────────────────────────────────────────────────────
343
344    /// Begin an MVCC transaction.
345    pub fn begin_transaction(&mut self, iso: IsolationLevel) -> Result<Transaction> {
346        unsafe {
347            let lib = ffi::load();
348            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, i32) -> u64> =
349                lib.get(b"overdrive_begin_transaction").unwrap();
350            let id = func(self.handle, iso as i32);
351            if id == 0 { return Err(SdkError(last_error())); }
352            Ok(Transaction { id })
353        }
354    }
355
356    /// Commit a transaction.
357    pub fn commit_transaction(&mut self, txn: &Transaction) -> Result<()> {
358        unsafe {
359            let lib = ffi::load();
360            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, u64) -> i32> =
361                lib.get(b"overdrive_commit_transaction").unwrap();
362            if func(self.handle, txn.id) != 0 {
363                return Err(SdkError(last_error()));
364            }
365        }
366        Ok(())
367    }
368
369    /// Abort (rollback) a transaction.
370    pub fn abort_transaction(&mut self, txn: &Transaction) -> Result<()> {
371        unsafe {
372            let lib = ffi::load();
373            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void, u64) -> i32> =
374                lib.get(b"overdrive_abort_transaction").unwrap();
375            if func(self.handle, txn.id) != 0 {
376                return Err(SdkError(last_error()));
377            }
378        }
379        Ok(())
380    }
381
382    /// Run a closure inside a transaction — auto commits on Ok, aborts on Err.
383    pub fn transaction<F, T>(&mut self, iso: IsolationLevel, f: F) -> Result<T>
384    where F: FnOnce(&mut Self) -> Result<T>
385    {
386        let txn = self.begin_transaction(iso)?;
387        match f(self) {
388            Ok(v)  => { self.commit_transaction(&txn)?; Ok(v) }
389            Err(e) => { let _ = self.abort_transaction(&txn); Err(e) }
390        }
391    }
392
393    // ── Integrity ─────────────────────────────────────────────────────────
394
395    /// Run an integrity check. Returns a JSON report.
396    pub fn verify_integrity(&self) -> Result<Value> {
397        unsafe {
398            let lib = ffi::load();
399            let func: Symbol<unsafe extern "C" fn(*mut std::ffi::c_void) -> *mut c_char> =
400                lib.get(b"overdrive_verify_integrity").unwrap();
401            let ptr = func(self.handle);
402            if ptr.is_null() { return Err(SdkError(last_error())); }
403            let s = ffi::read_and_free(ptr);
404            Ok(serde_json::from_str(&s)?)
405        }
406    }
407}