From 87de6de499eadf403adb618bcfa7fa1e77230bf0 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 15 Dec 2024 10:45:02 -0500 Subject: [PATCH] impl `src/entry`, traits, backends --- storage/database/src/backend/heed/database.rs | 14 +++ storage/database/src/backend/redb/database.rs | 16 +++- storage/database/src/database.rs | 6 +- storage/database/src/entry/entry.rs | 96 +++++++++++++++++++ storage/database/src/entry/mod.rs | 10 ++ storage/database/src/entry/occupied_entry.rs | 58 +++++++++++ storage/database/src/entry/vacant_entry.rs | 34 +++++++ storage/database/src/lib.rs | 1 + 8 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 storage/database/src/entry/entry.rs create mode 100644 storage/database/src/entry/mod.rs create mode 100644 storage/database/src/entry/occupied_entry.rs create mode 100644 storage/database/src/entry/vacant_entry.rs diff --git a/storage/database/src/backend/heed/database.rs b/storage/database/src/backend/heed/database.rs index 15f16b4..41ac149 100644 --- a/storage/database/src/backend/heed/database.rs +++ b/storage/database/src/backend/heed/database.rs @@ -6,6 +6,7 @@ use std::{cell::RefCell, ops::RangeBounds}; use crate::{ backend::heed::types::HeedDb, database::{DatabaseIter, DatabaseRo, DatabaseRw}, + entry::{Entry, OccupiedEntry, VacantEntry}, error::{DbResult, RuntimeError}, table::Table, }; @@ -239,6 +240,19 @@ impl DatabaseRw for HeedTableRw<'_, '_, T> { Ok(false) => unreachable!(), } } + + #[inline] + fn entry<'a>(&'a mut self, key: &'a T::Key) -> DbResult> { + match DatabaseRo::get(self, key) { + Ok(value) => Ok(Entry::Occupied(OccupiedEntry { + db: self, + key, + value, + })), + Err(RuntimeError::KeyNotFound) => Ok(Entry::Vacant(VacantEntry { db: self, key })), + Err(e) => Err(e), + } + } } //---------------------------------------------------------------------------------------------------- Tests diff --git a/storage/database/src/backend/redb/database.rs b/storage/database/src/backend/redb/database.rs index 0be58ef..aecf426 100644 --- a/storage/database/src/backend/redb/database.rs +++ b/storage/database/src/backend/redb/database.rs @@ -11,6 +11,7 @@ use crate::{ types::{RedbTableRo, RedbTableRw}, }, database::{DatabaseIter, DatabaseRo, DatabaseRw}, + entry::{Entry, OccupiedEntry, VacantEntry}, error::{DbResult, RuntimeError}, table::Table, }; @@ -162,7 +163,7 @@ unsafe impl DatabaseRo for RedbTableRw<'_, T::Key, T::Val } } -impl DatabaseRw for RedbTableRw<'_, T::Key, T::Value> { +impl DatabaseRw for RedbTableRw<'_, T::Key, T::Value> { // `redb` returns the value after function calls so we end with Ok(()) instead. #[inline] @@ -197,6 +198,19 @@ impl DatabaseRw for RedbTableRw<'_, T::Key, T::Value> { let (key, value) = redb::Table::pop_last(self)?.ok_or(RuntimeError::KeyNotFound)?; Ok((key.value(), value.value())) } + + #[inline] + fn entry<'a>(&'a mut self, key: &'a T::Key) -> DbResult> { + match get::(self, key) { + Ok(value) => Ok(Entry::Occupied(OccupiedEntry { + db: self, + key, + value, + })), + Err(RuntimeError::KeyNotFound) => Ok(Entry::Vacant(VacantEntry { db: self, key })), + Err(e) => Err(e), + } + } } //---------------------------------------------------------------------------------------------------- Tests diff --git a/storage/database/src/database.rs b/storage/database/src/database.rs index c019972..44e4036 100644 --- a/storage/database/src/database.rs +++ b/storage/database/src/database.rs @@ -4,6 +4,7 @@ use std::ops::RangeBounds; use crate::{ + entry::Entry, error::{DbResult, RuntimeError}, table::Table, }; @@ -151,7 +152,7 @@ pub unsafe trait DatabaseRo { /// Database (key-value store) read/write abstraction. /// /// All [`DatabaseRo`] functions are also callable by [`DatabaseRw`]. -pub trait DatabaseRw: DatabaseRo { +pub trait DatabaseRw: DatabaseRo + Sized { /// Insert a key-value pair into the database. /// /// This will overwrite any existing key-value pairs. @@ -211,4 +212,7 @@ pub trait DatabaseRw: DatabaseRo { /// #[doc = doc_database!()] fn pop_last(&mut self) -> DbResult<(T::Key, T::Value)>; + + /// TODO + fn entry<'a>(&'a mut self, key: &'a T::Key) -> DbResult>; } diff --git a/storage/database/src/entry/entry.rs b/storage/database/src/entry/entry.rs new file mode 100644 index 0000000..734efb7 --- /dev/null +++ b/storage/database/src/entry/entry.rs @@ -0,0 +1,96 @@ +//! TODO + +use crate::{ + entry::{OccupiedEntry, VacantEntry}, + DatabaseRw, DbResult, Table, +}; + +pub enum Entry<'a, T, D> +where + T: Table, + D: DatabaseRw, +{ + Vacant(VacantEntry<'a, T, D>), + Occupied(OccupiedEntry<'a, T, D>), +} + +impl<'a, T, D> Entry<'a, T, D> +where + T: Table, + D: DatabaseRw, +{ + /// TODO + pub fn or_insert(self, default: &T::Value) -> DbResult<()> { + match self { + Self::Occupied(_) => Ok(()), + Self::Vacant(entry) => entry.insert(default), + } + } + + /// TODO + pub fn or_insert_with(self, default: F) -> DbResult<()> + where + F: FnOnce() -> &'a T::Value, + { + match self { + Self::Occupied(_) => Ok(()), + Self::Vacant(entry) => entry.insert(default()), + } + } + + /// TODO + pub fn or_insert_with_key(self, default: F) -> DbResult<()> + where + F: FnOnce(&'a T::Key) -> &'a T::Value, + { + match self { + Self::Occupied(_) => Ok(()), + Self::Vacant(entry) => { + let key = entry.key; + entry.insert(default(key)) + } + } + } + + /// TODO + pub const fn key(&self) -> &T::Key { + match self { + Self::Occupied(entry) => entry.key(), + Self::Vacant(entry) => entry.key(), + } + } + + /// TODO + pub const fn value(&self) -> Option<&T::Value> { + match self { + Self::Occupied(entry) => Some(entry.value()), + Self::Vacant(_) => None, + } + } + + /// TODO + pub fn and_update(self, f: F) -> DbResult + where + F: FnOnce(&mut T::Value), + { + Ok(match self { + Self::Occupied(mut entry) => { + entry.update(f)?; + Self::Occupied(entry) + } + Self::Vacant(entry) => Self::Vacant(entry), + }) + } +} + +impl<'a, T, D> Entry<'a, T, D> +where + T: Table, + ::Value: Default, + D: DatabaseRw, +{ + /// TODO + pub fn or_default(self) -> &'a mut T::Value { + todo!() + } +} diff --git a/storage/database/src/entry/mod.rs b/storage/database/src/entry/mod.rs new file mode 100644 index 0000000..2e90aab --- /dev/null +++ b/storage/database/src/entry/mod.rs @@ -0,0 +1,10 @@ +//! TODO + +#[expect(clippy::module_inception)] +mod entry; +mod occupied_entry; +mod vacant_entry; + +pub use entry::Entry; +pub use occupied_entry::OccupiedEntry; +pub use vacant_entry::VacantEntry; diff --git a/storage/database/src/entry/occupied_entry.rs b/storage/database/src/entry/occupied_entry.rs new file mode 100644 index 0000000..f8df8fe --- /dev/null +++ b/storage/database/src/entry/occupied_entry.rs @@ -0,0 +1,58 @@ +//! TODO + +use crate::{DatabaseRw, DbResult, Table}; + +pub struct OccupiedEntry<'a, T, D> +where + T: Table, + D: DatabaseRw, +{ + pub(crate) db: &'a mut D, + pub(crate) key: &'a T::Key, + pub(crate) value: T::Value, +} + +impl OccupiedEntry<'_, T, D> +where + T: Table, + D: DatabaseRw, +{ + /// TODO + pub const fn key(&self) -> &T::Key { + self.key + } + + /// TODO + pub const fn value(&self) -> &T::Value { + &self.value + } + + /// TODO + pub fn update(&mut self, f: F) -> DbResult<()> + where + F: FnOnce(&mut T::Value), + { + f(&mut self.value); + DatabaseRw::put(self.db, self.key, &self.value) + } + + /// TODO + pub fn insert(&mut self, value: &T::Value) -> DbResult<()> { + DatabaseRw::put(self.db, self.key, value) + } + + /// TODO + pub fn remove(self) -> DbResult { + DatabaseRw::delete(self.db, self.key).map(|()| Ok(self.value))? + } + + /// TODO + pub const fn get(&self) -> &T::Value { + &self.value + } + + /// TODO + pub fn into_value(self) -> T::Value { + self.value + } +} diff --git a/storage/database/src/entry/vacant_entry.rs b/storage/database/src/entry/vacant_entry.rs new file mode 100644 index 0000000..b7cdb4f --- /dev/null +++ b/storage/database/src/entry/vacant_entry.rs @@ -0,0 +1,34 @@ +//! TODO + +use crate::{DatabaseRw, DbResult, RuntimeError, Table}; + +pub struct VacantEntry<'a, T, D> +where + T: Table, + D: DatabaseRw, +{ + pub(crate) db: &'a mut D, + pub(crate) key: &'a T::Key, +} + +impl VacantEntry<'_, T, D> +where + T: Table, + D: DatabaseRw, +{ + /// TODO + pub const fn key(&self) -> &T::Key { + self.key + } + + /// TODO + pub fn insert(self, value: &T::Value) -> DbResult<()> { + match DatabaseRw::put(self.db, self.key, value) { + Ok(()) => Ok(()), + Err(RuntimeError::KeyExists) => { + unreachable!("this error popping up while VacantEntry exists is a logical error") + } + Err(e) => Err(e), + } + } +} diff --git a/storage/database/src/lib.rs b/storage/database/src/lib.rs index 8e48fca..d58f272 100644 --- a/storage/database/src/lib.rs +++ b/storage/database/src/lib.rs @@ -42,6 +42,7 @@ mod tables; mod transaction; pub mod config; +pub mod entry; pub mod resize; pub use backend::ConcreteEnv;