#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(rustdoc::bare_urls)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(clippy::mutable_key_type)] #![doc = include_str!("../README.md")]
use core::num::NonZeroUsize;
use nostr::prelude::*;
use nostr_database::prelude::*;
use tokio::sync::RwLock;
pub mod builder;
pub mod prelude;
mod store;
use self::builder::MemoryDatabaseBuilder;
use self::store::{MemoryOptions, MemoryStore};
#[derive(Debug)]
pub struct MemoryDatabase {
store: RwLock<MemoryStore>,
}
impl MemoryDatabase {
#[inline]
pub fn unbounded() -> Self {
Self::builder().build()
}
#[inline]
pub fn bounded(max: NonZeroUsize) -> Self {
Self::builder().max_events(max).build()
}
#[inline]
pub fn builder() -> MemoryDatabaseBuilder {
MemoryDatabaseBuilder::default()
}
#[inline]
fn from_builder(builder: MemoryDatabaseBuilder) -> Self {
let options = MemoryOptions {
process_nip09: builder.process_nip09,
process_nip62: builder.process_nip62,
relay_url: builder.relay_url,
};
Self {
store: RwLock::new(MemoryStore::new(builder.max_events, options)),
}
}
}
impl NostrDatabase for MemoryDatabase {
fn backend(&self) -> Backend {
Backend::Memory
}
fn features(&self) -> Features {
Features {
persistent: false,
event_expiration: false,
full_text_search: true,
request_to_vanish: true,
}
}
fn save_event<'a>(
&'a self,
event: &'a Event,
) -> BoxedFuture<'a, Result<SaveEventStatus, DatabaseError>> {
Box::pin(async move {
let mut store = self.store.write().await;
Ok(store.index_event(event))
})
}
fn check_id<'a>(
&'a self,
event_id: &'a EventId,
) -> BoxedFuture<'a, Result<DatabaseEventStatus, DatabaseError>> {
Box::pin(async move {
let store = self.store.read().await;
if store.has_event_id_been_deleted(event_id) {
Ok(DatabaseEventStatus::Deleted)
} else if store.has_event(event_id) {
Ok(DatabaseEventStatus::Saved)
} else {
Ok(DatabaseEventStatus::NotExistent)
}
})
}
fn event_by_id<'a>(
&'a self,
event_id: &'a EventId,
) -> BoxedFuture<'a, Result<Option<Event>, DatabaseError>> {
Box::pin(async move {
let store = self.store.read().await;
Ok(store.event_by_id(event_id).cloned())
})
}
fn count(&self, filter: Filter) -> BoxedFuture<'_, Result<usize, DatabaseError>> {
Box::pin(async move {
let store = self.store.read().await;
Ok(store.count(filter))
})
}
fn query(&self, filter: Filter) -> BoxedFuture<'_, Result<Events, DatabaseError>> {
Box::pin(async move {
let store = self.store.read().await;
let mut events = Events::new(&filter);
events.extend(store.query(filter).cloned());
Ok(events)
})
}
fn negentropy_items(
&self,
filter: Filter,
) -> BoxedFuture<'_, Result<Vec<(EventId, Timestamp)>, DatabaseError>> {
Box::pin(async move {
let store = self.store.read().await;
Ok(store.negentropy_items(filter))
})
}
fn delete(&self, filter: Filter) -> BoxedFuture<'_, Result<(), DatabaseError>> {
Box::pin(async move {
let mut store = self.store.write().await;
store.delete(filter);
Ok(())
})
}
fn wipe(&self) -> BoxedFuture<'_, Result<(), DatabaseError>> {
Box::pin(async move {
let mut store = self.store.write().await;
store.clear();
Ok(())
})
}
}
#[cfg(test)]
mod tests {
use nostr_database_test_suite::database_unit_tests;
use super::*;
struct TestDatabase {
inner: MemoryDatabase,
}
impl Deref for TestDatabase {
type Target = MemoryDatabase;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl TestDatabase {
async fn new() -> Self {
Self {
inner: MemoryDatabase::unbounded(),
}
}
async fn new_with_relay_url(url: RelayUrl) -> Self {
Self {
inner: MemoryDatabase::builder().relay_url(url).build(),
}
}
}
database_unit_tests!(
TestDatabase,
TestDatabase::new,
TestDatabase::new_with_relay_url
);
}