nostrdb-rs

nostrdb in rust!
git clone git://jb55.com/nostrdb-rs
Log | Files | Refs | Submodules | README | LICENSE

commit 26858ca78a7a87746386e386542414bbbcc45f3f
parent e99e6e30b730b01b6a7ba0db6e927a00f1e40bae
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 15 Dec 2023 17:13:58 -0800

add ndb.process_event and tests

Diffstat:
MCargo.toml | 3+++
Mbuild.rs | 25+++++--------------------
Msrc/error.rs | 1+
Msrc/ndb.rs | 67++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/note.rs | 26++++++++++++++++++++------
Msrc/test_util.rs | 8+++++---
Msrc/transaction.rs | 10+++++-----
7 files changed, 101 insertions(+), 39 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -17,4 +17,7 @@ cc = "1.0" env_logger = "0.10.1" libc = "0.2.151" log = "0.4.20" + +[dev-dependencies] +hex = "0.4.3" #bindgen = "0.69.1" // re-enable when we update bindings diff --git a/build.rs b/build.rs @@ -17,22 +17,6 @@ fn secp256k1_build() { .define("ENABLE_MODULE_SCHNORRSIG", Some("1")) .define("ENABLE_MODULE_EXTRAKEYS", Some("1")); //.define("ENABLE_MODULE_ELLSWIFT", Some("1")) - // upstream sometimes introduces calls to printf, which we cannot compile - // with WASM due to its lack of libc. printf is never necessary and we can - // just #define it away. - //.define("printf(...)", Some("")); - - //if cfg!(feature = "lowmemory") { - // base_config.define("ECMULT_WINDOW_SIZE", Some("4")); // A low-enough value to consume negligible memory - // base_config.define("ECMULT_GEN_PREC_BITS", Some("2")); - //} else { - // base_config.define("ECMULT_GEN_PREC_BITS", Some("4")); - // base_config.define("ECMULT_WINDOW_SIZE", Some("15")); // This is the default in the configure file (`auto`) - //} - - //base_config.define("USE_EXTERNAL_DEFAULT_CALLBACKS", Some("1")); - //#[cfg(feature = "recovery")] - //base_config.define("ENABLE_MODULE_RECOVERY", Some("1")); // WASM headers and size/align defines. if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "wasm32" { @@ -78,10 +62,11 @@ fn main() { // Add other include paths //.flag("-Wall") .flag("-Wno-misleading-indentation") - .flag("-Wno-unused-function") - //.flag("-Werror") - //.flag("-g") - .compile("libnostrdb.a"); + .flag("-Wno-unused-function"); + //.flag("-Werror") + //.flag("-g") + + build.compile("libnostrdb.a"); secp256k1_build(); diff --git a/src/error.rs b/src/error.rs @@ -3,5 +3,6 @@ pub enum Error { DbOpenFailed, NotFound, DecodeError, + NoteProcessFailed, TransactionFailed, } diff --git a/src/ndb.rs b/src/ndb.rs @@ -1,3 +1,4 @@ +use libc; use std::ffi::CString; use std::ptr; @@ -7,19 +8,29 @@ use crate::error::Error; use crate::note::Note; use crate::result::Result; use crate::transaction::Transaction; +use std::fs; +use std::path::Path; +/// A nostrdb context. Construct one of these with [Ndb::new]. pub struct Ndb { ndb: *mut bindings::ndb, } impl Ndb { - // Constructor + /// Construct a new nostrdb context. Takes a directory where the database + /// is/will be located and a nostrdb config. pub fn new(db_dir: &str, config: &Config) -> Result<Self> { let db_dir_cstr = match CString::new(db_dir) { Ok(cstr) => cstr, Err(_) => return Err(Error::DbOpenFailed), }; let mut ndb: *mut bindings::ndb = ptr::null_mut(); + + let path = Path::new(db_dir); + if !path.exists() { + let _ = fs::create_dir_all(&path); + } + let result = unsafe { bindings::ndb_init(&mut ndb, db_dir_cstr.as_ptr(), config.as_ptr()) }; if result == 0 { @@ -29,6 +40,27 @@ impl Ndb { Ok(Ndb { ndb }) } + /// Ingest a relay-sent event in the form `["EVENT","subid", {"id:"...}]` + /// This function returns immediately and doesn't provide any information on + /// if ingestion was successful or not. + pub fn process_event(&mut self, json: &str) -> Result<()> { + // Convert the Rust string to a C-style string + let c_json = CString::new(json).expect("CString::new failed"); + let c_json_ptr = c_json.as_ptr(); + + // Get the length of the string + let len = json.len() as libc::c_int; + + let res = unsafe { bindings::ndb_process_event(self.ndb, c_json_ptr, len) }; + + if res == 0 { + return Err(Error::NoteProcessFailed); + } + + Ok(()) + } + + /// Get a note from the database. Takes a [Transaction] and a 32-byte [Note] Id pub fn get_note_by_id<'a>( &self, transaction: &'a mut Transaction, @@ -55,11 +87,13 @@ impl Ndb { Ok(Note::new_transactional(note_ptr, len, primkey, transaction)) } + /// Get the underlying pointer to the context in C pub fn as_ptr(&self) -> *mut bindings::ndb { return self.ndb; } } +/// The database is automatically closed when [Ndb] is [Drop]ped. impl Drop for Ndb { fn drop(&mut self) { unsafe { @@ -73,16 +107,39 @@ mod tests { use super::*; use crate::config::Config; use crate::test_util; - use std::fs; #[test] fn ndb_init_works() { - // Initialize ndb + let db = "target/testdbs/init_works"; + { let cfg = Config::new(); - let _ = Ndb::new(".", &cfg).expect("ok"); + let _ = Ndb::new(db, &cfg).expect("ok"); + } + + test_util::cleanup_db(db); + } + + #[test] + fn process_event_works() { + let db = "target/testdbs/event_works"; + + { + let mut ndb = Ndb::new(db, &Config::new()).expect("ndb"); + ndb.process_event(r#"["EVENT","s",{"id": "702555e52e82cc24ad517ba78c21879f6e47a7c0692b9b20df147916ae8731a3","pubkey": "32bf915904bfde2d136ba45dde32c88f4aca863783999faea2e847a8fafd2f15","created_at": 1702675561,"kind": 1,"tags": [],"content": "hello, world","sig": "2275c5f5417abfd644b7bc74f0388d70feb5d08b6f90fa18655dda5c95d013bfbc5258ea77c05b7e40e0ee51d8a2efa931dc7a0ec1db4c0a94519762c6625675"}]"#).expect("process ok"); + } + + { + let ndb = Ndb::new(db, &Config::new()).expect("ndb"); + let id = + hex::decode("702555e52e82cc24ad517ba78c21879f6e47a7c0692b9b20df147916ae8731a3") + .expect("hex id"); + let mut txn = Transaction::new(&ndb).expect("txn"); + let id_bytes: [u8; 32] = id.try_into().expect("id bytes"); + let note = ndb.get_note_by_id(&mut txn, &id_bytes).expect("note"); + assert!(note.kind() == 1); } - test_util::cleanup_db(); + test_util::cleanup_db(&db); } } diff --git a/src/note.rs b/src/note.rs @@ -3,7 +3,11 @@ use crate::transaction::Transaction; #[derive(Debug)] pub enum Note<'a> { - /// A note in-memory outside of nostrdb + /// A note in-memory outside of nostrdb. This note is a pointer to a note in + /// memory and will be free'd when [Drop]ped. Method such as [Note::from_json] + /// will create owned notes in memory. + /// + /// [Drop]: std::ops::Drop Owned { ptr: *mut bindings::ndb_note, size: usize, @@ -11,7 +15,7 @@ pub enum Note<'a> { /// A note inside of nostrdb. Tied to the lifetime of a /// [Transaction] to ensure no reading of data outside - /// of a transaction. Construct these with [Note::new_transactional]. + /// of a transaction. Transactional { ptr: *mut bindings::ndb_note, size: usize, @@ -21,13 +25,21 @@ pub enum Note<'a> { } impl<'a> Note<'a> { - pub fn new_owned(ptr: *mut bindings::ndb_note, size: usize) -> Note<'static> { + /// Constructs an owned `Note`. This note is a pointer to a note in + /// memory and will be free'd when [Drop]ped. You normally wouldn't + /// use this method directly, public consumer would use from_json instead. + /// + /// [Drop]: std::ops::Drop + pub(crate) fn new_owned(ptr: *mut bindings::ndb_note, size: usize) -> Note<'static> { Note::Owned { ptr, size } } /// Constructs a `Note` in a transactional context. /// Use [Note::new_transactional] to create a new transactional note. - pub fn new_transactional( + /// You normally wouldn't use this method directly, it is used by + /// functions that get notes from the database like + /// [ndb_get_note_by_id] + pub(crate) fn new_transactional( ptr: *mut bindings::ndb_note, size: usize, key: u64, @@ -80,10 +92,12 @@ mod tests { use crate::ndb::Ndb; use crate::test_util; + let db = "target/testdbs/note_query_works"; + // Initialize ndb { let cfg = Config::new(); - let ndb = Ndb::new(".", &cfg).expect("db open"); + let ndb = Ndb::new(&db, &cfg).expect("db open"); let mut txn = Transaction::new(&ndb).expect("new txn"); let err = ndb @@ -92,6 +106,6 @@ mod tests { assert!(err == Error::NotFound); } - test_util::cleanup_db(); + test_util::cleanup_db(db); } } diff --git a/src/test_util.rs b/src/test_util.rs @@ -1,6 +1,8 @@ use std::fs; +use std::path::Path; -pub fn cleanup_db() { - let _ = fs::remove_file("data.mdb"); - let _ = fs::remove_file("lock.mdb"); +pub fn cleanup_db(path: &str) { + let p = Path::new(path); + let _ = fs::remove_file(p.join("data.mdb")); + let _ = fs::remove_file(p.join("lock.mdb")); } diff --git a/src/transaction.rs b/src/transaction.rs @@ -2,7 +2,6 @@ use crate::bindings; use crate::error::Error; use crate::ndb::Ndb; use crate::result::Result; -use log::debug; /// A `nostrdb` transaction. Only one is allowed to be active per thread. #[derive(Debug)] @@ -64,22 +63,23 @@ mod tests { #[test] fn transaction_inheritence_fails() { + let db = "target/testdbs/txn_inheritence_fails"; // Initialize ndb { let cfg = Config::new(); - let ndb = Ndb::new(".", &cfg).expect("ndb open failed"); + let ndb = Ndb::new(db, &cfg).expect("ndb open failed"); { - let txn = Transaction::new(&ndb).expect("txn1 failed"); + let _txn = Transaction::new(&ndb).expect("txn1 failed"); let txn2 = Transaction::new(&ndb).expect_err("tx2"); assert!(txn2 == Error::TransactionFailed); } { - let txn = Transaction::new(&ndb).expect("txn1 failed"); + let _txn = Transaction::new(&ndb).expect("txn1 failed"); } } - test_util::cleanup_db(); + test_util::cleanup_db(db); } }