1#![warn(missing_docs)]
10#![warn(rustdoc::bare_urls)]
11#![allow(clippy::mutable_key_type)]
12
13use std::path::{Path, PathBuf};
14
15use nostr_database::prelude::*;
16
17mod store;
18
19use self::store::Store;
20
21#[cfg(target_pointer_width = "64")]
23const MAP_SIZE: usize = 1024 * 1024 * 1024 * 32; #[cfg(target_pointer_width = "32")]
27const MAP_SIZE: usize = 0xFFFFF000; #[derive(Debug, Clone)]
31pub struct NostrLmdbBuilder {
32 pub path: PathBuf,
34 pub map_size: Option<usize>,
40 pub max_readers: Option<u32>,
44 pub additional_dbs: Option<u32>,
48}
49
50impl NostrLmdbBuilder {
51 pub fn new<P>(path: P) -> Self
53 where
54 P: AsRef<Path>,
55 {
56 Self {
57 path: path.as_ref().to_path_buf(),
58 map_size: None,
59 max_readers: None,
60 additional_dbs: None,
61 }
62 }
63
64 pub fn map_size(mut self, map_size: usize) -> Self {
70 self.map_size = Some(map_size);
71 self
72 }
73
74 pub fn max_readers(mut self, max_readers: u32) -> Self {
78 self.max_readers = Some(max_readers);
79 self
80 }
81
82 pub fn additional_dbs(mut self, additional_dbs: u32) -> Self {
86 self.additional_dbs = Some(additional_dbs);
87 self
88 }
89
90 pub fn build(self) -> Result<NostrLMDB, DatabaseError> {
92 let map_size: usize = self.map_size.unwrap_or(MAP_SIZE);
93 let max_readers: u32 = self.max_readers.unwrap_or(126);
94 let additional_dbs: u32 = self.additional_dbs.unwrap_or(0);
95 let db: Store = Store::open(self.path, map_size, max_readers, additional_dbs)
96 .map_err(DatabaseError::backend)?;
97 Ok(NostrLMDB { db })
98 }
99}
100
101#[derive(Debug)]
103pub struct NostrLMDB {
104 db: Store,
105}
106
107impl NostrLMDB {
108 #[inline]
110 pub fn open<P>(path: P) -> Result<Self, DatabaseError>
111 where
112 P: AsRef<Path>,
113 {
114 Self::builder(path).build()
115 }
116
117 #[inline]
119 pub fn builder<P>(path: P) -> NostrLmdbBuilder
120 where
121 P: AsRef<Path>,
122 {
123 NostrLmdbBuilder::new(path)
124 }
125}
126
127impl NostrDatabase for NostrLMDB {
128 #[inline]
129 fn backend(&self) -> Backend {
130 Backend::LMDB
131 }
132
133 fn save_event<'a>(
134 &'a self,
135 event: &'a Event,
136 ) -> BoxedFuture<'a, Result<SaveEventStatus, DatabaseError>> {
137 Box::pin(async move {
138 self.db
139 .save_event(event)
140 .await
141 .map_err(DatabaseError::backend)
142 })
143 }
144
145 fn check_id<'a>(
146 &'a self,
147 event_id: &'a EventId,
148 ) -> BoxedFuture<'a, Result<DatabaseEventStatus, DatabaseError>> {
149 Box::pin(async move {
150 if self
151 .db
152 .event_is_deleted(event_id)
153 .map_err(DatabaseError::backend)?
154 {
155 Ok(DatabaseEventStatus::Deleted)
156 } else if self
157 .db
158 .has_event(event_id)
159 .map_err(DatabaseError::backend)?
160 {
161 Ok(DatabaseEventStatus::Saved)
162 } else {
163 Ok(DatabaseEventStatus::NotExistent)
164 }
165 })
166 }
167
168 fn event_by_id<'a>(
169 &'a self,
170 event_id: &'a EventId,
171 ) -> BoxedFuture<'a, Result<Option<Event>, DatabaseError>> {
172 Box::pin(async move {
173 self.db
174 .get_event_by_id(event_id)
175 .map_err(DatabaseError::backend)
176 })
177 }
178
179 fn count(&self, filter: Filter) -> BoxedFuture<Result<usize, DatabaseError>> {
180 Box::pin(async move { self.db.count(filter).map_err(DatabaseError::backend) })
181 }
182
183 fn query(&self, filter: Filter) -> BoxedFuture<Result<Events, DatabaseError>> {
184 Box::pin(async move { self.db.query(filter).map_err(DatabaseError::backend) })
185 }
186
187 fn negentropy_items(
188 &self,
189 filter: Filter,
190 ) -> BoxedFuture<Result<Vec<(EventId, Timestamp)>, DatabaseError>> {
191 Box::pin(async move {
192 self.db
193 .negentropy_items(filter)
194 .map_err(DatabaseError::backend)
195 })
196 }
197
198 fn delete(&self, filter: Filter) -> BoxedFuture<Result<(), DatabaseError>> {
199 Box::pin(async move { self.db.delete(filter).await.map_err(DatabaseError::backend) })
200 }
201
202 #[inline]
203 fn wipe(&self) -> BoxedFuture<Result<(), DatabaseError>> {
204 Box::pin(async move { self.db.wipe().await.map_err(DatabaseError::backend) })
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use std::collections::HashSet;
211 use std::ops::Deref;
212 use std::time::Duration;
213
214 use async_utility::time;
215 use tempfile::TempDir;
216
217 use super::*;
218
219 const EVENTS: [&str; 14] = [
220 r#"{"id":"b7b1fb52ad8461a03e949820ae29a9ea07e35bcd79c95c4b59b0254944f62805","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644581,"kind":1,"tags":[],"content":"Text note","sig":"ed73a8a4e7c26cd797a7b875c634d9ecb6958c57733305fed23b978109d0411d21b3e182cb67c8ad750884e30ca383b509382ae6187b36e76ee76e6a142c4284"}"#,
221 r#"{"id":"7296747d91c53f1d71778ef3e12d18b66d494a41f688ef244d518abf37c959b6","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644586,"kind":32121,"tags":[["d","id-1"]],"content":"Empty 1","sig":"8848989a8e808f7315e950f871b231c1dff7752048f8957d4a541881d2005506c30e85c7dd74dab022b3e01329c88e69c9d5d55d961759272a738d150b7dbefc"}"#,
222 r#"{"id":"ec6ea04ba483871062d79f78927df7979f67545b53f552e47626cb1105590442","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644591,"kind":32122,"tags":[["d","id-1"]],"content":"Empty 2","sig":"89946113a97484850fe35fefdb9120df847b305de1216dae566616fe453565e8707a4da7e68843b560fa22a932f81fc8db2b5a2acb4dcfd3caba9a91320aac92"}"#,
223 r#"{"id":"63b8b829aa31a2de870c3a713541658fcc0187be93af2032ec2ca039befd3f70","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644596,"kind":32122,"tags":[["d","id-2"]],"content":"","sig":"607b1a67bef57e48d17df4e145718d10b9df51831d1272c149f2ab5ad4993ae723f10a81be2403ae21b2793c8ed4c129e8b031e8b240c6c90c9e6d32f62d26ff"}"#,
224 r#"{"id":"6fe9119c7db13ae13e8ecfcdd2e5bf98e2940ba56a2ce0c3e8fba3d88cd8e69d","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644601,"kind":32122,"tags":[["d","id-3"]],"content":"","sig":"d07146547a726fc9b4ec8d67bbbe690347d43dadfe5d9890a428626d38c617c52e6945f2b7144c4e0c51d1e2b0be020614a5cadc9c0256b2e28069b70d9fc26e"}"#,
225 r#"{"id":"a82f6ebfc709f4e7c7971e6bf738e30a3bc112cfdb21336054711e6779fd49ef","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644606,"kind":32122,"tags":[["d","id-1"]],"content":"","sig":"96d3349b42ed637712b4d07f037457ab6e9180d58857df77eb5fa27ff1fd68445c72122ec53870831ada8a4d9a0b484435f80d3ff21a862238da7a723a0d073c"}"#,
226 r#"{"id":"8ab0cb1beceeb68f080ec11a3920b8cc491ecc7ec5250405e88691d733185832","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644611,"kind":32122,"tags":[["d","id-1"]],"content":"Test","sig":"49153b482d7110e2538eb48005f1149622247479b1c0057d902df931d5cea105869deeae908e4e3b903e3140632dc780b3f10344805eab77bb54fb79c4e4359d"}"#,
227 r#"{"id":"63dc49a8f3278a2de8dc0138939de56d392b8eb7a18c627e4d78789e2b0b09f2","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644616,"kind":5,"tags":[["a","32122:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:"]],"content":"","sig":"977e54e5d57d1fbb83615d3a870037d9eb5182a679ca8357523bbf032580689cf481f76c88c7027034cfaf567ba9d9fe25fc8cd334139a0117ad5cf9fe325eef"}"#,
228 r#"{"id":"6975ace0f3d66967f330d4758fbbf45517d41130e2639b54ca5142f37757c9eb","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644621,"kind":5,"tags":[["a","32122:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:id-2"]],"content":"","sig":"9bb09e4759899d86e447c3fa1be83905fe2eda74a5068a909965ac14fcdabaed64edaeb732154dab734ca41f2fc4d63687870e6f8e56e3d9e180e4a2dd6fb2d2"}"#,
229 r#"{"id":"33f5b4e6a38e107638c20f4536db35191d4b8651ba5a2cefec983b9ec2d65084","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704645586,"kind":0,"tags":[],"content":"{\"name\":\"Key A\"}","sig":"285d090f45a6adcae717b33771149f7840a8c27fb29025d63f1ab8d95614034a54e9f4f29cee9527c4c93321a7ebff287387b7a19ba8e6f764512a40e7120429"}"#,
230 r#"{"id":"90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704645606,"kind":0,"tags":[],"content":"{\"name\":\"key-a\",\"display_name\":\"Key A\",\"lud16\":\"keya@ln.address\"}","sig":"ec8f49d4c722b7ccae102d49befff08e62db775e5da43ef51b25c47dfdd6a09dc7519310a3a63cbdb6ec6b3250e6f19518eb47be604edeb598d16cdc071d3dbc"}"#,
231 r#"{"id":"a295422c636d3532875b75739e8dae3cdb4dd2679c6e4994c9a39c7ebf8bc620","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704646569,"kind":5,"tags":[["e","90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9"]],"content":"","sig":"d4dc8368a4ad27eef63cacf667345aadd9617001537497108234fc1686d546c949cbb58e007a4d4b632c65ea135af4fbd7a089cc60ab89b6901f5c3fc6a47b29"}"#, r#"{"id":"999e3e270100d7e1eaa98fcfab4a98274872c1f2dfdab024f32e42a5a12d5b5e","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704646606,"kind":5,"tags":[["e","90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9"]],"content":"","sig":"4f3a33fd52784cea7ca8428fd35d94d65049712e9aa11a70b1a16a1fcd761c7b7e27afac325728b1c00dfa11e33e78b2efd0430a7e4b28f4ede5b579b3f32614"}"#,
233 r#"{"id":"99a022e6d61c4e39c147d08a2be943b664e8030c0049325555ac1766429c2832","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1705241093,"kind":30333,"tags":[["d","multi-id"],["p","aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4"]],"content":"Multi-tags","sig":"0abfb2b696a7ed7c9e8e3bf7743686190f3f1b3d4045b72833ab6187c254f7ed278d289d52dfac3de28be861c1471421d9b1bfb5877413cbc81c84f63207a826"}"#,
234 ];
235
236 fn decode_events() -> Vec<Event> {
237 EVENTS
238 .iter()
239 .map(|e| Event::from_json(e).expect("Failed to parse event"))
240 .collect()
241 }
242
243 async fn setup_db() -> (NostrLMDB, TempDir) {
244 let temp_dir = TempDir::new().expect("Failed to create temp directory");
245 let db = NostrLMDB::open(temp_dir.path()).expect("Failed to open database");
246 (db, temp_dir)
247 }
248
249 #[tokio::test]
250 async fn test_save_and_query() {
251 let (db, _temp_dir) = setup_db().await;
252 let events = decode_events();
253
254 for (i, event) in events.iter().enumerate() {
256 let status = db.save_event(event).await.expect("Failed to save event");
257 if i == 7 || i == 11 {
258 assert!(!status.is_success());
260 } else {
261 assert!(matches!(status, SaveEventStatus::Success));
262 }
263
264 time::sleep(Duration::from_millis(10)).await;
270 }
271
272 let saved_events = db.query(Filter::new()).await.expect("Failed to query");
274 assert_eq!(saved_events.len(), 8);
276 }
277
278 #[tokio::test]
279 async fn test_save_duplicate() {
280 let (db, _temp_dir) = setup_db().await;
281 let events = decode_events();
282 let event = &events[0];
283
284 let status = db.save_event(event).await.expect("Failed to save event");
286 assert!(matches!(status, SaveEventStatus::Success));
287
288 let status = db.save_event(event).await.expect("Failed to save event");
290 assert!(matches!(
291 status,
292 SaveEventStatus::Rejected(nostr_database::RejectedReason::Duplicate)
293 ));
294 }
295
296 #[tokio::test]
297 async fn test_query_by_filter() {
298 let (db, _temp_dir) = setup_db().await;
299 let events = decode_events();
300
301 for event in &events {
303 db.save_event(event).await.expect("Failed to save event");
304 }
305
306 let author_filter = Filter::new().author(events[0].pubkey);
308 let author_events = db.query(author_filter).await.expect("Failed to query");
309 assert!(!author_events.is_empty());
310 assert!(author_events.iter().all(|e| e.pubkey == events[0].pubkey));
311
312 let kind_filter = Filter::new().kind(Kind::TextNote);
314 let kind_events = db.query(kind_filter).await.expect("Failed to query");
315 assert!(!kind_events.is_empty());
316 assert!(kind_events.iter().all(|e| e.kind == Kind::TextNote));
317
318 let since = Timestamp::from_secs(1704644590);
320 let until = Timestamp::from_secs(1704644620);
321 let time_filter = Filter::new().since(since).until(until);
322 let time_events = db.query(time_filter).await.expect("Failed to query");
323 assert!(!time_events.is_empty());
324 assert!(time_events
325 .iter()
326 .all(|e| e.created_at >= since && e.created_at <= until));
327 }
328
329 #[tokio::test]
330 async fn test_delete_by_filter() {
331 let (db, _temp_dir) = setup_db().await;
332 let events = decode_events();
333
334 for event in &events {
336 db.save_event(event).await.expect("Failed to save event");
337 }
338
339 let count_before = db
341 .count(Filter::new())
342 .await
343 .expect("Failed to count events");
344 assert_eq!(count_before, 8);
345
346 let delete_filter = Filter::new().kind(Kind::TextNote);
348 db.delete(delete_filter)
349 .await
350 .expect("Failed to delete events");
351
352 let count_after = db
354 .count(Filter::new())
355 .await
356 .expect("Failed to count events");
357 assert_eq!(count_after, 7); let text_notes = db
361 .query(Filter::new().kind(Kind::TextNote))
362 .await
363 .expect("Failed to query");
364 assert_eq!(text_notes.len(), 0);
365 }
366
367 #[tokio::test]
368 async fn test_replaceable_events() {
369 let (db, _temp_dir) = setup_db().await;
370 let keys = Keys::generate();
371
372 let metadata1 = Metadata::new().name("First");
374 let event1 = EventBuilder::metadata(&metadata1)
375 .custom_created_at(Timestamp::from_secs(1000))
376 .sign_with_keys(&keys)
377 .expect("Failed to sign");
378
379 db.save_event(&event1).await.expect("Failed to save event");
380
381 let metadata2 = Metadata::new().name("Second");
383 let event2 = EventBuilder::metadata(&metadata2)
384 .custom_created_at(Timestamp::from_secs(2000))
385 .sign_with_keys(&keys)
386 .expect("Failed to sign");
387
388 db.save_event(&event2).await.expect("Failed to save event");
389
390 let filter = Filter::new().author(keys.public_key()).kind(Kind::Metadata);
392 let results = db.query(filter).await.expect("Failed to query");
393
394 assert_eq!(results.len(), 1);
396 let result_event = results.first().unwrap();
398 assert!(result_event.content.contains("Second"));
399 }
400
401 #[tokio::test]
402 async fn test_addressable_events() {
403 let (db, _temp_dir) = setup_db().await;
404 let keys = Keys::generate();
405
406 let event1 = EventBuilder::new(Kind::from(32121), "Content 1")
408 .tag(Tag::identifier("test-id"))
409 .custom_created_at(Timestamp::from_secs(1000))
410 .sign_with_keys(&keys)
411 .expect("Failed to sign");
412
413 db.save_event(&event1).await.expect("Failed to save event");
414
415 let event2 = EventBuilder::new(Kind::from(32121), "Content 2")
417 .tag(Tag::identifier("test-id"))
418 .custom_created_at(Timestamp::from_secs(2000))
419 .sign_with_keys(&keys)
420 .expect("Failed to sign");
421
422 db.save_event(&event2).await.expect("Failed to save event");
423
424 let filter = Filter::new()
426 .author(keys.public_key())
427 .kind(Kind::from(32121));
428 let results = db.query(filter).await.expect("Failed to query");
429
430 assert_eq!(results.len(), 1);
432 let result_event = results.first().unwrap();
434 assert_eq!(result_event.content, "Content 2");
435 }
436
437 #[tokio::test]
438 async fn test_event_deletion() {
439 let (db, _temp_dir) = setup_db().await;
440 let keys = Keys::generate();
441
442 let event1 = EventBuilder::text_note("To be deleted 1")
444 .sign_with_keys(&keys)
445 .expect("Failed to sign");
446 let event2 = EventBuilder::text_note("To be deleted 2")
447 .sign_with_keys(&keys)
448 .expect("Failed to sign");
449
450 db.save_event(&event1).await.expect("Failed to save event");
451 db.save_event(&event2).await.expect("Failed to save event");
452
453 let deletion =
455 EventBuilder::delete(EventDeletionRequest::new().id(event1.id).id(event2.id))
456 .sign_with_keys(&keys)
457 .expect("Failed to sign");
458
459 db.save_event(&deletion)
460 .await
461 .expect("Failed to save deletion");
462
463 time::sleep(Duration::from_millis(50)).await;
465
466 let status1 = db
468 .check_id(&event1.id)
469 .await
470 .expect("Failed to check event");
471 let status2 = db
472 .check_id(&event2.id)
473 .await
474 .expect("Failed to check event");
475
476 assert_eq!(status1, DatabaseEventStatus::Deleted);
479 assert_eq!(status2, DatabaseEventStatus::Deleted);
480 }
481
482 #[tokio::test]
483 async fn test_wipe_database() {
484 let (db, _temp_dir) = setup_db().await;
485 let events = decode_events();
486
487 for event in &events {
489 db.save_event(event).await.expect("Failed to save event");
490 }
491
492 let count = db
494 .count(Filter::new())
495 .await
496 .expect("Failed to count events");
497 assert_eq!(count, 8);
498
499 db.wipe().await.expect("Failed to wipe database");
501
502 let count_after = db
504 .count(Filter::new())
505 .await
506 .expect("Failed to count events");
507 assert_eq!(count_after, 0);
508 }
509
510 #[tokio::test]
511 async fn test_negentropy_items() {
512 let (db, _temp_dir) = setup_db().await;
513 let events = decode_events();
514
515 for event in &events {
517 db.save_event(event).await.expect("Failed to save event");
518 }
519
520 let items = db
522 .negentropy_items(Filter::new())
523 .await
524 .expect("Failed to get negentropy items");
525
526 assert_eq!(items.len(), 8);
527
528 let event_ids: HashSet<EventId> = events.iter().map(|e| e.id).collect();
530
531 for (id, _timestamp) in items {
532 assert!(
533 event_ids.contains(&id),
534 "Unexpected event ID in negentropy items"
535 );
536 }
537 }
538
539 struct TempDatabase {
540 db: NostrLMDB,
541 _temp: TempDir,
543 }
544
545 impl Deref for TempDatabase {
546 type Target = NostrLMDB;
547
548 fn deref(&self) -> &Self::Target {
549 &self.db
550 }
551 }
552
553 impl TempDatabase {
554 fn new() -> Self {
555 let path = tempfile::tempdir().unwrap();
556 Self {
557 db: NostrLMDB::open(&path).unwrap(),
558 _temp: path,
559 }
560 }
561
562 async fn add_random_events(&self) -> usize {
564 let keys_a = Keys::generate();
565 let keys_b = Keys::generate();
566
567 let events = vec![
568 EventBuilder::text_note("Text Note A")
569 .sign_with_keys(&keys_a)
570 .unwrap(),
571 EventBuilder::text_note("Text Note B")
572 .sign_with_keys(&keys_b)
573 .unwrap(),
574 EventBuilder::metadata(
575 &Metadata::new().name("account-a").display_name("Account A"),
576 )
577 .sign_with_keys(&keys_a)
578 .unwrap(),
579 EventBuilder::metadata(
580 &Metadata::new().name("account-b").display_name("Account B"),
581 )
582 .sign_with_keys(&keys_b)
583 .unwrap(),
584 EventBuilder::new(Kind::Custom(33_333), "")
585 .tag(Tag::identifier("my-id-a"))
586 .sign_with_keys(&keys_a)
587 .unwrap(),
588 EventBuilder::new(Kind::Custom(33_333), "")
589 .tag(Tag::identifier("my-id-b"))
590 .sign_with_keys(&keys_b)
591 .unwrap(),
592 ];
593
594 for event in events.iter() {
596 self.db.save_event(event).await.unwrap();
597 }
598
599 events.len()
600 }
601
602 async fn add_event(&self, builder: EventBuilder) -> (Keys, Event) {
603 let keys = Keys::generate();
604 let event = builder.sign_with_keys(&keys).unwrap();
605 self.db.save_event(&event).await.unwrap();
606 (keys, event)
607 }
608
609 async fn add_event_with_keys(
610 &self,
611 builder: EventBuilder,
612 keys: &Keys,
613 ) -> (Event, SaveEventStatus) {
614 let event = builder.sign_with_keys(keys).unwrap();
615 let status = self.db.save_event(&event).await.unwrap();
616 (event, status)
617 }
618
619 async fn count_all(&self) -> usize {
620 self.db.count(Filter::new()).await.unwrap()
621 }
622 }
623
624 #[tokio::test]
625 async fn test_event_by_id() {
626 let db = TempDatabase::new();
627
628 let added_events: usize = db.add_random_events().await;
629
630 let (_keys, expected_event) = db.add_event(EventBuilder::text_note("Test")).await;
631
632 let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap();
633 assert_eq!(event, expected_event);
634
635 assert_eq!(db.count_all().await, added_events + 1)
637 }
638
639 #[tokio::test]
640 async fn test_replaceable_event() {
641 let db = TempDatabase::new();
642
643 let added_events: usize = db.add_random_events().await;
644
645 let now = Timestamp::now();
646 let metadata = Metadata::new()
647 .name("my-account")
648 .display_name("My Account");
649
650 let (keys, expected_event) = db
651 .add_event(
652 EventBuilder::metadata(&metadata).custom_created_at(now - Duration::from_secs(120)),
653 )
654 .await;
655
656 let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap();
658 assert_eq!(event, expected_event);
659
660 let events = db
662 .query(Filter::new().author(keys.public_key).kind(Kind::Metadata))
663 .await
664 .unwrap();
665 assert_eq!(events.to_vec(), vec![expected_event.clone()]);
666
667 assert_eq!(db.count_all().await, added_events + 1);
669
670 let (new_expected_event, status) = db
672 .add_event_with_keys(
673 EventBuilder::metadata(&metadata).custom_created_at(now),
674 &keys,
675 )
676 .await;
677 assert!(status.is_success());
678
679 assert!(db.event_by_id(&expected_event.id).await.unwrap().is_none());
681
682 let event = db
684 .event_by_id(&new_expected_event.id)
685 .await
686 .unwrap()
687 .unwrap();
688 assert_eq!(event, new_expected_event);
689
690 let events = db
692 .query(Filter::new().author(keys.public_key).kind(Kind::Metadata))
693 .await
694 .unwrap();
695 assert_eq!(events.to_vec(), vec![new_expected_event]);
696
697 assert_eq!(db.count_all().await, added_events + 1);
699 }
700
701 #[tokio::test]
702 async fn test_param_replaceable_event() {
703 let db = TempDatabase::new();
704
705 let added_events: usize = db.add_random_events().await;
706
707 let now = Timestamp::now();
708
709 let (keys, expected_event) = db
710 .add_event(
711 EventBuilder::new(Kind::Custom(33_333), "")
712 .tag(Tag::identifier("my-id-a"))
713 .custom_created_at(now - Duration::from_secs(120)),
714 )
715 .await;
716 let coordinate = Coordinate::new(Kind::from(33_333), keys.public_key).identifier("my-id-a");
717
718 let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap();
720 assert_eq!(event, expected_event);
721
722 let events = db.query(coordinate.clone().into()).await.unwrap();
724 assert_eq!(events.to_vec(), vec![expected_event.clone()]);
725
726 assert_eq!(db.count_all().await, added_events + 1);
728
729 let (new_expected_event, status) = db
731 .add_event_with_keys(
732 EventBuilder::new(Kind::Custom(33_333), "Test replace")
733 .tag(Tag::identifier("my-id-a"))
734 .custom_created_at(now),
735 &keys,
736 )
737 .await;
738 assert!(status.is_success());
739
740 assert!(db.event_by_id(&expected_event.id).await.unwrap().is_none());
742
743 let event = db
745 .event_by_id(&new_expected_event.id)
746 .await
747 .unwrap()
748 .unwrap();
749 assert_eq!(event, new_expected_event);
750
751 let events = db.query(coordinate.into()).await.unwrap();
753 assert_eq!(events.to_vec(), vec![new_expected_event]);
754
755 assert_eq!(db.count_all().await, added_events + 1);
757
758 let (_, status) = db
760 .add_event_with_keys(
761 EventBuilder::new(Kind::Custom(33_333), "Test replace 2")
762 .tag(Tag::identifier("my-id-a"))
763 .custom_created_at(now - Duration::from_secs(2000)),
764 &keys,
765 )
766 .await;
767 assert!(!status.is_success());
768 }
769
770 #[tokio::test]
771 async fn test_full_text_search() {
772 let db = TempDatabase::new();
773
774 let _added_events: usize = db.add_random_events().await;
775
776 let events = db.query(Filter::new().search("Account A")).await.unwrap();
777 assert_eq!(events.len(), 1);
778
779 let events = db.query(Filter::new().search("account a")).await.unwrap();
780 assert_eq!(events.len(), 1);
781
782 let events = db.query(Filter::new().search("text note")).await.unwrap();
783 assert_eq!(events.len(), 2);
784
785 let events = db.query(Filter::new().search("notes")).await.unwrap();
786 assert_eq!(events.len(), 0);
787
788 let events = db.query(Filter::new().search("hola")).await.unwrap();
789 assert_eq!(events.len(), 0);
790 }
791
792 #[tokio::test]
793 async fn test_expected_query_result() {
794 let db = TempDatabase::new();
795
796 for (i, event_str) in EVENTS.into_iter().enumerate() {
798 let event = Event::from_json(event_str).unwrap();
799 let status = db.save_event(&event).await.unwrap();
800
801 if i == 7 || i == 11 {
803 assert!(!status.is_success(), "Event {} should be rejected", i);
804 }
805
806 time::sleep(Duration::from_millis(10)).await;
808 }
809
810 let expected_output = vec![
813 Event::from_json(EVENTS[13]).unwrap(), Event::from_json(EVENTS[12]).unwrap(), Event::from_json(EVENTS[8]).unwrap(), Event::from_json(EVENTS[6]).unwrap(), Event::from_json(EVENTS[5]).unwrap(), Event::from_json(EVENTS[4]).unwrap(), Event::from_json(EVENTS[1]).unwrap(), Event::from_json(EVENTS[0]).unwrap(), ];
822
823 let actual = db.query(Filter::new()).await.unwrap().to_vec();
824 assert_eq!(actual, expected_output);
825 assert_eq!(db.count_all().await, 8); }
827
828 #[tokio::test]
829 async fn test_kind5_deletion_query_bug_fix() {
830 let temp_dir = tempfile::tempdir().unwrap();
831 let db = NostrLMDB::open(temp_dir.path()).unwrap();
832 let keys = Keys::generate();
833
834 let event = EventBuilder::text_note("Test event")
836 .sign_with_keys(&keys)
837 .expect("Failed to sign");
838
839 let status = db.save_event(&event).await.expect("Failed to save event");
840 assert!(matches!(status, SaveEventStatus::Success));
841
842 time::sleep(Duration::from_millis(50)).await;
844
845 let before_by_id = db
847 .query(Filter::new().id(event.id))
848 .await
849 .expect("Failed to query");
850 assert_eq!(before_by_id.len(), 1);
851
852 let before_by_author = db
854 .query(Filter::new().author(keys.public_key()).kind(Kind::TextNote))
855 .await
856 .expect("Failed to query");
857 assert_eq!(before_by_author.len(), 1);
858
859 let deletion_event = EventBuilder::new(Kind::EventDeletion, "")
861 .tag(Tag::event(event.id))
862 .sign_with_keys(&keys)
863 .expect("Failed to sign");
864
865 let del_status = db
866 .save_event(&deletion_event)
867 .await
868 .expect("Failed to save deletion");
869 assert!(matches!(del_status, SaveEventStatus::Success));
870
871 time::sleep(Duration::from_millis(100)).await;
873
874 let after_by_id = db
876 .query(Filter::new().id(event.id))
877 .await
878 .expect("Failed to query");
879 assert_eq!(
880 after_by_id.len(),
881 0,
882 "Deleted event should not be returned in ID query"
883 );
884
885 let after_by_author = db
887 .query(Filter::new().author(keys.public_key()).kind(Kind::TextNote))
888 .await
889 .expect("Failed to query");
890 assert_eq!(
891 after_by_author.len(),
892 0,
893 "Deleted event should not be returned in author-kind query"
894 );
895
896 let deletion_events = db
898 .query(Filter::new().kind(Kind::EventDeletion))
899 .await
900 .expect("Failed to query");
901 assert_eq!(
902 deletion_events.len(),
903 1,
904 "Deletion event should remain queryable"
905 );
906 }
907
908 #[tokio::test]
909 async fn test_nip01_replaceable_events_with_identical_timestamps() {
910 let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
911 let db = NostrLMDB::open(temp_dir.path()).expect("Failed to open database");
912
913 let event1_json = r#"{"kind":0,"id":"b39eda8475d345d4da75418f0ee4a9ec183eb0483634cfdc8415cefdf5c02b96","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1754066538,"tags":[],"content":"smallest id","sig":"22e9f94de060c8e0a958b8fbc42914fab12c90be5ea9153aa11f92ed8c38c18a3374221fe37cb40b461d391e8ee92d6dd5083b0be8e146bf90af694560f18e17"}"#;
915 let event2_json = r#"{"kind":0,"id":"eedcb07adabb380e853815534568e05cc5678bc8f9d8cf3dbee8513d37f1c47f","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1754066538,"tags":[],"content":"biggest id","sig":"b127afb6b7967b483bffe0d8ca21335509820bbfc37ba09874cc931c6c8c97dec4d44ac5d1071973d3b337c6fe614a8ca2cdc1e89c3c68cd557f4a2eca90cab3"}"#;
916
917 let event1 = Event::from_json(event1_json).expect("Failed to parse event1");
918 let event2 = Event::from_json(event2_json).expect("Failed to parse event2");
919
920 assert_eq!(event1.created_at, event2.created_at);
922 assert_eq!(event1.created_at.as_u64(), 1754066538);
923
924 assert!(event1.id.to_string() < event2.id.to_string());
926
927 {
929 let status1 = db.save_event(&event1).await.expect("Failed to save event1");
930 assert!(matches!(status1, SaveEventStatus::Success));
931
932 let status2 = db.save_event(&event2).await.expect("Failed to save event2");
933 assert!(matches!(
934 status2,
935 SaveEventStatus::Rejected(RejectedReason::Replaced)
936 ));
937
938 let filter = Filter::new()
940 .author(
941 PublicKey::from_hex(
942 "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
943 )
944 .unwrap(),
945 )
946 .kind(Kind::Metadata);
947
948 let results = db.query(filter).await.expect("Failed to query");
949 assert_eq!(results.len(), 1, "Should have exactly one metadata event");
950
951 assert_eq!(
953 results.first().unwrap().id,
954 event1.id,
955 "NIP-01: Event with lowest ID should be retained"
956 );
957 }
958
959 db.wipe().await.expect("Failed to wipe database");
961
962 {
964 let status2 = db.save_event(&event2).await.expect("Failed to save event2");
965 assert!(matches!(status2, SaveEventStatus::Success));
966
967 let status1 = db.save_event(&event1).await.expect("Failed to save event1");
968 assert!(matches!(status1, SaveEventStatus::Success));
969
970 let filter = Filter::new()
972 .author(
973 PublicKey::from_hex(
974 "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
975 )
976 .unwrap(),
977 )
978 .kind(Kind::Metadata);
979
980 let results = db.query(filter).await.expect("Failed to query");
981 assert_eq!(results.len(), 1, "Should have exactly one metadata event");
982
983 assert_eq!(
985 results.first().unwrap().id,
986 event1.id,
987 "NIP-01: Event with lowest ID should be retained"
988 );
989 }
990 }
991
992 #[tokio::test]
993 async fn test_nip01_addressable_events_with_identical_timestamps() {
994 let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
995 let db = NostrLMDB::open(temp_dir.path()).expect("Failed to open database");
996
997 let event1_json = r#"{"kind":30023,"id":"a11eda8475d345d4da75418f0ee4a9ec183eb0483634cfdc8415cefdf5c02b96","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1754066538,"tags":[["d","article-123"],["title","Test Article"]],"content":"Article with smallest id","sig":"22e9f94de060c8e0a958b8fbc42914fab12c90be5ea9153aa11f92ed8c38c18a3374221fe37cb40b461d391e8ee92d6dd5083b0be8e146bf90af694560f18e17"}"#;
999 let event2_json = r#"{"kind":30023,"id":"feedb07adabb380e853815534568e05cc5678bc8f9d8cf3dbee8513d37f1c47f","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1754066538,"tags":[["d","article-123"],["title","Test Article"]],"content":"Article with biggest id","sig":"b127afb6b7967b483bffe0d8ca21335509820bbfc37ba09874cc931c6c8c97dec4d44ac5d1071973d3b337c6fe614a8ca2cdc1e89c3c68cd557f4a2eca90cab3"}"#;
1000
1001 let event1 = Event::from_json(event1_json).expect("Failed to parse event1");
1002 let event2 = Event::from_json(event2_json).expect("Failed to parse event2");
1003
1004 assert_eq!(event1.created_at, event2.created_at);
1006 assert_eq!(event1.created_at.as_u64(), 1754066538);
1007 assert_eq!(event1.tags.identifier(), event2.tags.identifier());
1008 assert_eq!(event1.tags.identifier(), Some("article-123"));
1009
1010 assert!(event1.id.to_string() < event2.id.to_string());
1012
1013 {
1015 let status1 = db.save_event(&event1).await.expect("Failed to save event1");
1016 assert!(matches!(status1, SaveEventStatus::Success));
1017
1018 let status2 = db.save_event(&event2).await.expect("Failed to save event2");
1019 assert!(matches!(
1020 status2,
1021 SaveEventStatus::Rejected(RejectedReason::Replaced)
1022 ));
1023
1024 let filter = Filter::new()
1026 .author(
1027 PublicKey::from_hex(
1028 "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
1029 )
1030 .unwrap(),
1031 )
1032 .kind(Kind::Custom(30023));
1033
1034 let results = db.query(filter).await.expect("Failed to query");
1035 assert_eq!(
1036 results.len(),
1037 1,
1038 "Should have exactly one addressable event"
1039 );
1040
1041 assert_eq!(
1043 results.first().unwrap().id,
1044 event1.id,
1045 "NIP-01: Event with lowest ID should be retained"
1046 );
1047 }
1048
1049 db.wipe().await.expect("Failed to wipe database");
1051
1052 {
1054 let status2 = db.save_event(&event2).await.expect("Failed to save event2");
1055 assert!(matches!(status2, SaveEventStatus::Success));
1056
1057 let status1 = db.save_event(&event1).await.expect("Failed to save event1");
1058 assert!(matches!(status1, SaveEventStatus::Success));
1059
1060 let filter = Filter::new()
1062 .author(
1063 PublicKey::from_hex(
1064 "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
1065 )
1066 .unwrap(),
1067 )
1068 .kind(Kind::Custom(30023));
1069
1070 let results = db.query(filter).await.expect("Failed to query");
1071 assert_eq!(
1072 results.len(),
1073 1,
1074 "Should have exactly one addressable event"
1075 );
1076
1077 assert_eq!(
1079 results.first().unwrap().id,
1080 event1.id,
1081 "NIP-01: Event with lowest ID should be retained"
1082 );
1083 }
1084 }
1085}