use std::{ marker::PhantomData, hash::{ Hash, Hasher, BuildHasher, }, collections::hash_map::RandomState, fmt, fmt::Debug, }; use std::fmt::Formatter; use rkyv::{ Archive, Archived, Serialize, Deserialize, Fallible, }; use super::{ DB, Adapter, OutputBuffer, Environment, DatabaseFlags, WriteFlags, Transaction, RwTransaction, }; #[derive(Archive, Serialize, Deserialize, Debug)] /// The entry as it is stored inside the database. pub struct Entry { pub key: K, pub val: V, } #[derive(Clone, Copy)] pub struct HashAdapter { k: PhantomData, a: PhantomData, } impl HashAdapter { pub fn new() -> Self { Self { k: PhantomData, a: PhantomData } } } impl Debug for HashAdapter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { use core::any::type_name; write!(f, "HashAdapter<{}, {}>", type_name::(), type_name::()) } } impl Fallible for HashAdapter { type Error = ::Error; } impl Adapter for HashAdapter where K: Archive, Entry: Serialize, { type Serializer = A::Serializer; type Value = Entry; fn new_serializer() -> Self::Serializer { A::new_serializer() } fn from_ser_err(e: ::Error) -> ::Error { A::from_ser_err(e) } fn from_db_err(e: lmdb::Error) -> ::Error { A::from_db_err(e) } } const DEFAULT_HASH_FLAGS: libc::c_uint = DatabaseFlags::INTEGER_KEY.bits() + DatabaseFlags::DUP_SORT.bits(); pub struct HashDB { db: DB>, hash_builder: H, } impl fmt::Debug for HashDB { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let adapter = HashAdapter::::new(); f.debug_struct("HashDB") .field("db", &adapter) .field("hasher", &self.hash_builder) .finish() } } impl HashDB { pub unsafe fn create(env: &Environment, name: Option<&str>) -> lmdb::Result { Self::create_with_hasher(env, name, RandomState::new()) } pub unsafe fn open(env: &Environment, name: Option<&str>) -> lmdb::Result { Self::open_with_hasher(env, name, RandomState::new()) } } impl HashDB { fn new(db: DB>, hash_builder: H) -> Self { Self { db, hash_builder } } pub unsafe fn create_with_hasher(env: &Environment, name: Option<&str>, hash_builder: H) -> lmdb::Result { let flags = DatabaseFlags::from_bits(DEFAULT_HASH_FLAGS).unwrap(); DB::create(env, name, flags).map(|db| Self::new(db, hash_builder)) } pub unsafe fn open_with_hasher(env: &Environment, name: Option<&str>, hash_builder: H) -> lmdb::Result { DB::open(env, name).map(|db| Self::new(db, hash_builder)) } } impl HashDB where A: Adapter, HashAdapter: Adapter>, H: BuildHasher, K: Hash + Archive, K::Archived: PartialEq, { /// Retrieve an entry from the hashdb /// /// The result is a view pinned to the lifetime of the transaction. You can get owned Values /// using [`Deserialize`]. pub fn get<'txn, T: Transaction>(&self, txn: &'txn T, key: &K) -> Result< Option<&'txn Archived< as Adapter>::Value>>, as Fallible>::Error > { let mut hasher = self.hash_builder.build_hasher(); key.hash(&mut hasher); let hash = hasher.finish(); let mut cursor = self.db.open_ro_cursor(txn)?; let i = cursor .iter_dup_of(&hash.to_ne_bytes()).filter_map(|r| r.ok()) .map(|(_keybuf, entry)| entry); for entry in i { let entry: &Archived> = entry; if entry.key == *key { return Ok(Some(entry)); } } Ok(None) } } impl<'a, A, K, H> HashDB where A: Adapter, A::Serializer: OutputBuffer, H: BuildHasher, K: Hash + Serialize, K::Archived: PartialEq, { pub fn insert_entry(&self, txn: &mut RwTransaction, entry: &Entry) -> Result<(), A::Error> { let mut hasher = self.hash_builder.build_hasher(); entry.key.hash(&mut hasher); let hash = hasher.finish(); self.db.put(txn, &hash.to_ne_bytes(), entry, WriteFlags::empty())?; Ok(()) } }