From 5fd85b9d98b2f4b82cfda533e115ba0a8ac6a621 Mon Sep 17 00:00:00 2001 From: Ziyang Hu Date: Fri, 18 Nov 2022 16:36:47 +0800 Subject: [PATCH] update the C library --- cozo-core/src/runtime/db.rs | 14 ++ cozo-core/src/storage/sqlite.rs | 1 + cozo-lib-c/Cargo.toml | 24 ++- cozo-lib-c/cozo_c.h | 50 ++++++- cozo-lib-c/src/lib.rs | 255 +++++++++++++++++++++++++++++--- 5 files changed, 322 insertions(+), 22 deletions(-) diff --git a/cozo-core/src/runtime/db.rs b/cozo-core/src/runtime/db.rs index e776f94b..a788c42a 100644 --- a/cozo-core/src/runtime/db.rs +++ b/cozo-core/src/runtime/db.rs @@ -330,6 +330,13 @@ impl<'s, S: Storage<'s>> Db { #[cfg(not(feature = "storage-sqlite"))] bail!("backup requires the 'storage-sqlite' feature to be enabled") } + /// Backup the running database into an Sqlite file, with JSON string return value + pub fn backup_db_str(&'s self, out_file: &str) -> String { + match self.backup_db(out_file.to_string()) { + Ok(_) => json!({"ok": true}).to_string(), + Err(err) => json!({"ok": false, "message": err.to_string()}).to_string(), + } + } /// Restore from an Sqlite backup pub fn restore_backup(&'s self, in_file: String) -> Result<()> { #[cfg(feature = "storage-sqlite")] @@ -344,6 +351,13 @@ impl<'s, S: Storage<'s>> Db { #[cfg(not(feature = "storage-sqlite"))] bail!("backup requires the 'storage-sqlite' feature to be enabled") } + /// Restore from an Sqlite backup, with JSON string return value + pub fn restore_backup_str(&'s self, in_file: &str) -> String { + match self.restore_backup(in_file.to_string()) { + Ok(_) => json!({"ok": true}).to_string(), + Err(err) => json!({"ok": false, "message": err.to_string()}).to_string(), + } + } fn compact_relation(&'s self) -> Result<()> { let l = Tuple::default().encode_as_key(RelationId(0)); diff --git a/cozo-core/src/storage/sqlite.rs b/cozo-core/src/storage/sqlite.rs index 2c02e1e3..1e6c1bee 100644 --- a/cozo-core/src/storage/sqlite.rs +++ b/cozo-core/src/storage/sqlite.rs @@ -18,6 +18,7 @@ use crate::runtime::relation::decode_tuple_from_kv; use crate::storage::{Storage, StoreTx}; /// The Sqlite storage engine +#[derive(Clone)] pub struct SqliteStorage { lock: Arc>, name: String, diff --git a/cozo-lib-c/Cargo.toml b/cozo-lib-c/Cargo.toml index 347f49f2..8bbf35d6 100644 --- a/cozo-lib-c/Cargo.toml +++ b/cozo-lib-c/Cargo.toml @@ -12,13 +12,35 @@ description = "C bindings for CozoDB" crate-type = ["cdylib", "staticlib"] [features] +#! # Features + +## Enables the `minimal`, `requests` and `graph-algo` features +compact = ["minimal", "requests", "graph-algo", "rayon"] +## Enables the `minimal`, `requests` and `graph-algo` features in single threaded mode +compact-single-threaded = ["minimal", "requests", "graph-algo"] +## Enables the `storage-sqlite` feature +minimal = ["storage-sqlite"] +## Enables the [Sqlite](https://www.sqlite.org/index.html) backend, also allows backup and restore with Sqlite data files. +storage-sqlite = ["cozo/storage-sqlite"] +## Enables the [RocksDB](http://rocksdb.org/) backend +storage-rocksdb = ["cozo/storage-rocksdb"] +## Enables the graph algorithms +graph-algo = ["cozo/graph-algo"] +## Allows the utilities to make web requests to fetch data +requests = ["cozo/requests"] +## Uses jemalloc as the global allocator, can make a difference in performance jemalloc = ["cozo/jemalloc"] +## Enables io-uring option for the RocksDB storage io-uring = ["cozo/io-uring"] +## Allows threading and enables the use of the `rayon` library for parallelizing algorithms +rayon = ["cozo/rayon"] +## Disallows the use of threads +nothread = ["cozo/nothread"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cozo = { version = "0.1.2", path = "../cozo-core" } +cozo = { version = "0.1.2", path = "../cozo-core", default_features = false } lazy_static = "1.4.0" [build-dependencies] diff --git a/cozo-lib-c/cozo_c.h b/cozo-lib-c/cozo_c.h index ceb4ddbc..80b72e19 100644 --- a/cozo-lib-c/cozo_c.h +++ b/cozo-lib-c/cozo_c.h @@ -17,14 +17,15 @@ extern "C" { /** * Open a database. * - * `path`: should contain the UTF-8 encoded path name as a null-terminated C-string. - * `db_id`: will contain the id of the database opened. + * `engine`: Which storage engine to use, can be "mem", "sqlite" or "rocksdb". + * `path`: should contain the UTF-8 encoded path name as a null-terminated C-string. + * `db_id`: will contain the id of the database opened. * * When the function is successful, null pointer is returned, * otherwise a pointer to a C-string containing the error message will be returned. * The returned C-string must be freed with `cozo_free_str`. */ -char *cozo_open_db(const char *path, int32_t *db_id); +char *cozo_open_db(const char *engine, const char *path, int32_t *db_id); /** * Close a database. @@ -53,6 +54,49 @@ bool cozo_close_db(int32_t id); */ char *cozo_run_query(int32_t db_id, const char *script_raw, const char *params_raw); +/** + * Import data into a relation + * `db_id`: the ID representing the database. + * `json_payload`: a UTF-8 encoded JSON payload, see the manual for the expected fields. + * + * Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`. + */ +char *cozo_import_relation(int32_t db_id, + const char *json_payload); + +/** + * Export relations into JSON + * + * `db_id`: the ID representing the database. + * `json_payload`: a UTF-8 encoded JSON payload, see the manual for the expected fields. + * + * Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`. + */ +char *cozo_export_relations(int32_t db_id, + const char *json_payload); + +/** + * Backup the database. + * + * `db_id`: the ID representing the database. + * `out_path`: path of the output file. + * + * Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`. + */ +char *cozo_backup(int32_t db_id, + const char *out_path); + +/** + * Restore the database from a backup. + * + * `db_id`: the ID representing the database. + * `in_path`: path of the input file. + * + * Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`. + */ +char *cozo_restore(int32_t db_id, + const char *in_path); + /** * Free any C-string returned from the Cozo C API. * Must be called exactly once for each returned C-string. diff --git a/cozo-lib-c/src/lib.rs b/cozo-lib-c/src/lib.rs index b37e6334..899e4ab1 100644 --- a/cozo-lib-c/src/lib.rs +++ b/cozo-lib-c/src/lib.rs @@ -15,16 +15,104 @@ use std::sync::Mutex; use lazy_static::lazy_static; -use cozo::RocksDbStorage; -use cozo::{new_cozo_rocksdb, Db}; +use cozo::*; -struct Handles { +#[derive(Clone)] +enum DbInstance { + Mem(Db), + #[cfg(feature = "storage-sqlite")] + Sqlite(Db), + #[cfg(feature = "storage-rocksdb")] + RocksDb(Db), +} + +impl DbInstance { + fn new(engine: &str, path: &str) -> Result { + match engine { + "mem" => Ok(Self::Mem(new_cozo_mem().map_err(|err| err.to_string())?)), + "sqlite" => { + #[cfg(feature = "storage-sqlite")] + { + return Ok(Self::Sqlite( + new_cozo_sqlite(path.to_string()).map_err(|err| err.to_string())?, + )); + } + + #[cfg(not(feature = "storage-sqlite"))] + { + return Err("support for sqlite not compiled".to_string()); + } + } + "rocksdb" => { + #[cfg(feature = "storage-rocksdb")] + { + return Ok(Self::RocksDb( + new_cozo_rocksdb(path.to_string()).map_err(|err| err.to_string())?, + )); + } + + #[cfg(not(feature = "storage-rocksdb"))] + { + return Err("support for rocksdb not compiled".to_string()); + } + } + _ => Err(format!("unsupported engine: {}", engine)), + } + } + fn run_script_str(&self, payload: &str, params: &str) -> String { + match self { + DbInstance::Mem(db) => db.run_script_str(payload, params), + #[cfg(feature = "storage-sqlite")] + DbInstance::Sqlite(db) => db.run_script_str(payload, params), + #[cfg(feature = "storage-rocksdb")] + DbInstance::RocksDb(db) => db.run_script_str(payload, params), + } + } + fn import_relations(&self, data: &str) -> String { + match self { + DbInstance::Mem(db) => db.import_relation_str(data), + #[cfg(feature = "storage-sqlite")] + DbInstance::Sqlite(db) => db.import_relation_str(data), + #[cfg(feature = "storage-rocksdb")] + DbInstance::RocksDb(db) => db.import_relation_str(data), + } + } + fn export_relations(&self, data: &str) -> String { + match self { + DbInstance::Mem(db) => db.export_relations_str(data), + #[cfg(feature = "storage-sqlite")] + DbInstance::Sqlite(db) => db.export_relations_str(data), + #[cfg(feature = "storage-rocksdb")] + DbInstance::RocksDb(db) => db.export_relations_str(data), + } + } + fn backup(&self, path: &str) -> String { + match self { + DbInstance::Mem(db) => db.backup_db_str(path), + #[cfg(feature = "storage-sqlite")] + DbInstance::Sqlite(db) => db.backup_db_str(path), + #[cfg(feature = "storage-rocksdb")] + DbInstance::RocksDb(db) => db.backup_db_str(path), + } + } + fn restore(&self, path: &str) -> String { + match self { + DbInstance::Mem(db) => db.restore_backup_str(path), + #[cfg(feature = "storage-sqlite")] + DbInstance::Sqlite(db) => db.restore_backup_str(path), + #[cfg(feature = "storage-rocksdb")] + DbInstance::RocksDb(db) => db.restore_backup_str(path), + } + } +} + +struct Handles { current: AtomicI32, - dbs: Mutex>>, + dbs: Mutex>, } lazy_static! { - static ref HANDLES: Handles = Handles { + static ref HANDLES: Handles = Handles { current: Default::default(), dbs: Mutex::new(Default::default()) }; @@ -32,29 +120,39 @@ lazy_static! { /// Open a database. /// -/// `path`: should contain the UTF-8 encoded path name as a null-terminated C-string. -/// `db_id`: will contain the id of the database opened. +/// `engine`: Which storage engine to use, can be "mem", "sqlite" or "rocksdb". +/// `path`: should contain the UTF-8 encoded path name as a null-terminated C-string. +/// `db_id`: will contain the id of the database opened. /// /// When the function is successful, null pointer is returned, /// otherwise a pointer to a C-string containing the error message will be returned. /// The returned C-string must be freed with `cozo_free_str`. #[no_mangle] -pub unsafe extern "C" fn cozo_open_db(path: *const c_char, db_id: &mut i32) -> *mut c_char { +pub unsafe extern "C" fn cozo_open_db( + engine: *const c_char, + path: *const c_char, + db_id: &mut i32, +) -> *mut c_char { let path = match CStr::from_ptr(path).to_str() { Ok(p) => p, Err(err) => return CString::new(format!("{}", err)).unwrap().into_raw(), }; - match new_cozo_rocksdb(path) { - Ok(db) => { - let id = HANDLES.current.fetch_add(1, Ordering::AcqRel); - let mut dbs = HANDLES.dbs.lock().unwrap(); - dbs.insert(id, db); - *db_id = id; - null_mut() - } - Err(err) => CString::new(format!("{}", err)).unwrap().into_raw(), - } + let engine = match CStr::from_ptr(engine).to_str() { + Ok(p) => p, + Err(err) => return CString::new(format!("{}", err)).unwrap().into_raw(), + }; + + let db = match DbInstance::new(engine, path) { + Ok(db) => db, + Err(err) => return CString::new(err).unwrap().into_raw(), + }; + + let id = HANDLES.current.fetch_add(1, Ordering::AcqRel); + let mut dbs = HANDLES.dbs.lock().unwrap(); + dbs.insert(id, db); + *db_id = id; + null_mut() } /// Close a database. @@ -128,6 +226,127 @@ pub unsafe extern "C" fn cozo_run_query( CString::new(result).unwrap().into_raw() } +#[no_mangle] +/// Import data into a relation +/// `db_id`: the ID representing the database. +/// `json_payload`: a UTF-8 encoded JSON payload, see the manual for the expected fields. +/// +/// Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`. +pub unsafe extern "C" fn cozo_import_relation( + db_id: i32, + json_payload: *const c_char, +) -> *mut c_char { + let db = { + let db_ref = { + let dbs = HANDLES.dbs.lock().unwrap(); + dbs.get(&db_id).cloned() + }; + match db_ref { + None => { + return CString::new(r##"{"ok":false,"message":"database closed"}"##) + .unwrap() + .into_raw(); + } + Some(db) => db, + } + }; + let data = match CStr::from_ptr(json_payload).to_str() { + Ok(p) => p, + Err(err) => return CString::new(format!("{}", err)).unwrap().into_raw(), + }; + CString::new(db.import_relations(data)).unwrap().into_raw() +} + +#[no_mangle] +/// Export relations into JSON +/// +/// `db_id`: the ID representing the database. +/// `json_payload`: a UTF-8 encoded JSON payload, see the manual for the expected fields. +/// +/// Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`. +pub unsafe extern "C" fn cozo_export_relations( + db_id: i32, + json_payload: *const c_char, +) -> *mut c_char { + let db = { + let db_ref = { + let dbs = HANDLES.dbs.lock().unwrap(); + dbs.get(&db_id).cloned() + }; + match db_ref { + None => { + return CString::new(r##"{"ok":false,"message":"database closed"}"##) + .unwrap() + .into_raw(); + } + Some(db) => db, + } + }; + let data = match CStr::from_ptr(json_payload).to_str() { + Ok(p) => p, + Err(err) => return CString::new(format!("{}", err)).unwrap().into_raw(), + }; + CString::new(db.export_relations(data)).unwrap().into_raw() +} + +#[no_mangle] +/// Backup the database. +/// +/// `db_id`: the ID representing the database. +/// `out_path`: path of the output file. +/// +/// Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`. +pub unsafe extern "C" fn cozo_backup(db_id: i32, out_path: *const c_char) -> *mut c_char { + let db = { + let db_ref = { + let dbs = HANDLES.dbs.lock().unwrap(); + dbs.get(&db_id).cloned() + }; + match db_ref { + None => { + return CString::new(r##"{"ok":false,"message":"database closed"}"##) + .unwrap() + .into_raw(); + } + Some(db) => db, + } + }; + let data = match CStr::from_ptr(out_path).to_str() { + Ok(p) => p, + Err(err) => return CString::new(format!("{}", err)).unwrap().into_raw(), + }; + CString::new(db.backup(data)).unwrap().into_raw() +} + +#[no_mangle] +/// Restore the database from a backup. +/// +/// `db_id`: the ID representing the database. +/// `in_path`: path of the input file. +/// +/// Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`. +pub unsafe extern "C" fn cozo_restore(db_id: i32, in_path: *const c_char) -> *mut c_char { + let db = { + let db_ref = { + let dbs = HANDLES.dbs.lock().unwrap(); + dbs.get(&db_id).cloned() + }; + match db_ref { + None => { + return CString::new(r##"{"ok":false,"message":"database closed"}"##) + .unwrap() + .into_raw(); + } + Some(db) => db, + } + }; + let data = match CStr::from_ptr(in_path).to_str() { + Ok(p) => p, + Err(err) => return CString::new(format!("{}", err)).unwrap().into_raw(), + }; + CString::new(db.restore(data)).unwrap().into_raw() +} + /// Free any C-string returned from the Cozo C API. /// Must be called exactly once for each returned C-string. ///