nostrdb-rs

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

commit 095f5b71970a72690edcfa12c815812e9e9ef8d8
parent a8f8fabdc2f1eebe939622000c030639b4b6d51f
Author: William Casarin <jb55@jb55.com>
Date:   Wed,  7 Feb 2024 16:32:06 -0800

add ndb_query support

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Msrc/bindings.rs | 17++++++++++++++---
Msrc/error.rs | 2++
Msrc/filter.rs | 3+--
Msrc/lib.rs | 2++
Msrc/ndb.rs | 63++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Asrc/query.rs | 22++++++++++++++++++++++
6 files changed, 101 insertions(+), 8 deletions(-)

diff --git a/src/bindings.rs b/src/bindings.rs @@ -1,4 +1,4 @@ -/* automatically generated by rust-bindgen 0.69.1 */ +/* automatically generated by rust-bindgen 0.69.2 */ #[repr(C)] #[derive(Default)] @@ -4685,6 +4685,7 @@ fn bindgen_test_layout_ndb_block_iterator() { #[derive(Debug, Copy, Clone)] pub struct ndb_query_result { pub note: *mut ndb_note, + pub note_size: u64, pub note_id: u64, } #[test] @@ -4693,7 +4694,7 @@ fn bindgen_test_layout_ndb_query_result() { let ptr = UNINIT.as_ptr(); assert_eq!( ::std::mem::size_of::<ndb_query_result>(), - 16usize, + 24usize, concat!("Size of: ", stringify!(ndb_query_result)) ); assert_eq!( @@ -4712,12 +4713,22 @@ fn bindgen_test_layout_ndb_query_result() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).note_id) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).note_size) as usize - ptr as usize }, 8usize, concat!( "Offset of field: ", stringify!(ndb_query_result), "::", + stringify!(note_size) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).note_id) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(ndb_query_result), + "::", stringify!(note_id) ) ); diff --git a/src/error.rs b/src/error.rs @@ -5,6 +5,7 @@ pub enum Error { DbOpenFailed, NotFound, DecodeError, + QueryError, NoteProcessFailed, TransactionFailed, SubscriptionError, @@ -15,6 +16,7 @@ impl fmt::Display for Error { let s = match self { Error::DbOpenFailed => "Open failed", Error::NotFound => "Not found", + Error::QueryError => "Query failed", Error::DecodeError => "Decode error", Error::NoteProcessFailed => "Note process failed", Error::TransactionFailed => "Transaction failed", diff --git a/src/filter.rs b/src/filter.rs @@ -1,12 +1,11 @@ use crate::bindings; use crate::Note; use std::ffi::CString; -use std::marker::PhantomPinned; use std::os::raw::c_char; use std::ptr::null_mut; use tracing::debug; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Filter { pub data: bindings::ndb_filter, } diff --git a/src/lib.rs b/src/lib.rs @@ -15,6 +15,7 @@ mod filter; mod ndb; mod note; mod profile; +mod query; mod result; mod subscription; mod transaction; @@ -26,6 +27,7 @@ pub use filter::Filter; pub use ndb::Ndb; pub use note::Note; pub use profile::ProfileRecord; +pub use query::QueryResult; pub use result::Result; pub use subscription::Subscription; pub use transaction::Transaction; diff --git a/src/ndb.rs b/src/ndb.rs @@ -3,7 +3,8 @@ use std::ffi::CString; use std::ptr; use crate::{ - bindings, Blocks, Config, Error, Filter, Note, ProfileRecord, Result, Subscription, Transaction, + bindings, Blocks, Config, Error, Filter, Note, ProfileRecord, QueryResult, Result, + Subscription, Transaction, }; use std::fs; use std::os::raw::c_int; @@ -81,6 +82,36 @@ impl Ndb { Ok(()) } + pub fn query<'a>( + &self, + txn: &'a Transaction, + filters: Vec<Filter>, + max_results: i32, + ) -> Result<Vec<QueryResult<'a>>> { + let mut ndb_filters: Vec<bindings::ndb_filter> = filters.iter().map(|a| a.data).collect(); + let mut out: Vec<bindings::ndb_query_result> = vec![]; + let mut returned: i32 = 0; + out.reserve_exact(max_results as usize); + let res = unsafe { + bindings::ndb_query( + txn.as_mut_ptr(), + ndb_filters.as_mut_ptr(), + ndb_filters.len() as i32, + out.as_mut_ptr(), + max_results, + &mut returned as *mut i32, + ) + }; + if res == 1 { + unsafe { + out.set_len(returned as usize); + }; + Ok(out.iter().map(|r| QueryResult::new(r, txn)).collect()) + } else { + Err(Error::QueryError) + } + } + pub fn subscribe(&self, filters: Vec<Filter>) -> Result<Subscription> { unsafe { let mut ndb_filters: Vec<bindings::ndb_filter> = @@ -103,7 +134,7 @@ impl Ndb { vec.reserve_exact(max_notes as usize); let sub_id = sub.id; - let res = unsafe { + unsafe { let res = bindings::ndb_poll_for_notes( self.as_ptr(), sub_id, @@ -255,10 +286,36 @@ mod tests { } #[tokio::test] + async fn query_works() { + let db = "target/testdbs/query"; + test_util::cleanup_db(&db); + + { + let ndb = Ndb::new(db, &Config::new()).expect("ndb"); + + let mut filter = Filter::new(); + filter.kinds(vec![1]); + + let filters = vec![filter]; + let sub = ndb.subscribe(filters.clone()).expect("sub_id"); + let waiter = ndb.wait_for_notes(&sub, 1); + ndb.process_event(r#"["EVENT","b",{"id": "702555e52e82cc24ad517ba78c21879f6e47a7c0692b9b20df147916ae8731a3","pubkey": "32bf915904bfde2d136ba45dde32c88f4aca863783999faea2e847a8fafd2f15","created_at": 1702675561,"kind": 1,"tags": [],"content": "hello, world","sig": "2275c5f5417abfd644b7bc74f0388d70feb5d08b6f90fa18655dda5c95d013bfbc5258ea77c05b7e40e0ee51d8a2efa931dc7a0ec1db4c0a94519762c6625675"}]"#).expect("process ok"); + let res = waiter.await.expect("await ok"); + assert_eq!(res, vec![1]); + let txn = Transaction::new(&ndb).expect("txn"); + let res = ndb.query(&txn, filters, 1).expect("query ok"); + assert_eq!(res.len(), 1); + assert_eq!( + hex::encode(res[0].note.id()), + "702555e52e82cc24ad517ba78c21879f6e47a7c0692b9b20df147916ae8731a3" + ); + } + } + + #[tokio::test] async fn subscribe_event_works() { let db = "target/testdbs/subscribe"; test_util::cleanup_db(&db); - tracing_subscriber::fmt::init(); { let ndb = Ndb::new(db, &Config::new()).expect("ndb"); diff --git a/src/query.rs b/src/query.rs @@ -0,0 +1,22 @@ +use crate::{bindings, Note, Transaction}; + +pub struct QueryResult<'a> { + pub note: Note<'a>, + pub note_size: u64, + pub note_key: u64, +} + +impl<'a> QueryResult<'a> { + pub fn new(result: &bindings::ndb_query_result, txn: &'a Transaction) -> Self { + QueryResult { + note: Note::new_transactional( + result.note, + result.note_size as usize, + result.note_id, + txn, + ), + note_size: result.note_size, + note_key: result.note_id, + } + } +}