From 742cc0e69e4a9cf70cae687f334b48655c467d10 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 15 Dec 2024 13:51:51 -0500 Subject: [PATCH] docs --- storage/blockchain/src/ops/output.rs | 6 +- storage/database/src/database.rs | 4 +- storage/database/src/entry.rs | 315 +++++++++++++++++++ storage/database/src/entry/entry.rs | 160 ---------- storage/database/src/entry/mod.rs | 61 ---- storage/database/src/entry/occupied_entry.rs | 71 ----- storage/database/src/entry/vacant_entry.rs | 35 --- 7 files changed, 320 insertions(+), 332 deletions(-) create mode 100644 storage/database/src/entry.rs delete mode 100644 storage/database/src/entry/entry.rs delete mode 100644 storage/database/src/entry/mod.rs delete mode 100644 storage/database/src/entry/occupied_entry.rs delete mode 100644 storage/database/src/entry/vacant_entry.rs diff --git a/storage/blockchain/src/ops/output.rs b/storage/blockchain/src/ops/output.rs index ff8d14b..b7a1bfb 100644 --- a/storage/blockchain/src/ops/output.rs +++ b/storage/blockchain/src/ops/output.rs @@ -62,13 +62,11 @@ pub fn remove_output( pre_rct_output_id: &PreRctOutputId, tables: &mut impl TablesMut, ) -> DbResult<()> { - // Decrement the amount index by 1, or delete the entry out-right. - // FIXME: this would be much better expressed with a - // `btree_map::Entry`-like API, fix `trait DatabaseRw`. + // INVARIANT: `num_outputs` should never be 0. tables .num_outputs_mut() .entry(&pre_rct_output_id.amount)? - .and_remove(|num_outputs| *num_outputs == 1)? + .remove_if(|num_outputs| *num_outputs == 1)? .and_update(|num_outputs| *num_outputs -= 1)?; // Delete the output data itself. diff --git a/storage/database/src/database.rs b/storage/database/src/database.rs index 4dec239..8a9b245 100644 --- a/storage/database/src/database.rs +++ b/storage/database/src/database.rs @@ -189,6 +189,8 @@ pub trait DatabaseRw: DatabaseRo + Sized { #[doc = doc_database!()] fn pop_last(&mut self) -> DbResult<(T::Key, T::Value)>; - /// TODO + /// Gets the given key's corresponding entry for in-place manipulation. + /// + #[doc = doc_database!()] fn entry<'a>(&'a mut self, key: &'a T::Key) -> DbResult>; } diff --git a/storage/database/src/entry.rs b/storage/database/src/entry.rs new file mode 100644 index 0000000..3253855 --- /dev/null +++ b/storage/database/src/entry.rs @@ -0,0 +1,315 @@ +//! Entry API for [`DatabaseRw`]. +//! +//! This module provides a [`std::collections::btree_map::Entry`]-like API for [`DatabaseRw`]. +//! +//! ## Example +//! ```rust +//! use cuprate_database::{ +//! ConcreteEnv, +//! config::ConfigBuilder, +//! Env, EnvInner, +//! DatabaseRo, DatabaseRw, TxRo, TxRw, RuntimeError, +//! }; +//! +//! # fn main() -> Result<(), Box> { +//! # let tmp_dir = tempfile::tempdir()?; +//! # let db_dir = tmp_dir.path().to_owned(); +//! # let config = ConfigBuilder::new(db_dir.into()).build(); +//! # +//! # let env = ConcreteEnv::open(config)?; +//! # +//! # struct Table; +//! # impl cuprate_database::Table for Table { +//! # const NAME: &'static str = "table"; +//! # type Key = u8; +//! # type Value = u64; +//! # } +//! # +//! # let env_inner = env.env_inner(); +//! # let tx_rw = env_inner.tx_rw()?; +//! # +//! # env_inner.create_db::(&tx_rw)?; +//! # let mut table = env_inner.open_db_rw::
(&tx_rw)?; +//! /// The key to use. +//! const KEY: u8 = u8::MAX; +//! +//! /// The update function applied if the value already exists. +//! fn f(value: &mut u64) { +//! *value += 1; +//! } +//! +//! // No entry exists. +//! assert!(matches!(table.first(), Err(RuntimeError::KeyNotFound))); +//! +//! // Increment the value by `1` or insert a `0` if it doesn't exist. +//! table.entry(&KEY)?.and_update(f)?.or_insert(&0)?; +//! assert_eq!(table.first()?, (KEY, 0)); +//! table.entry(&KEY)?.and_update(f)?.or_insert(&0)?; +//! assert_eq!(table.first()?, (KEY, 1)); +//! +//! // Conditionally remove the entry. +//! table.entry(&KEY)?.remove_if(|v| *v == 0); +//! assert_eq!(table.first()?, (KEY, 1)); +//! table.entry(&KEY)?.remove_if(|v| *v == 1); +//! assert!(matches!(table.first(), Err(RuntimeError::KeyNotFound))); +//! # Ok(()) } +//! ``` + +use crate::{DatabaseRw, DbResult, RuntimeError, Table}; + +//---------------------------------------------------------------------------------------------------- Entry +/// A view into a single entry in a [`DatabaseRw`], which may either be a [`VacantEntry`] or [`OccupiedEntry`]. +/// +/// This enum is constructed from the [`DatabaseRw::entry`] method. +pub enum Entry<'a, T, D> +where + T: Table, + D: DatabaseRw, +{ + /// A vacant entry; this key did not exist. + /// + /// [`RuntimeError::KeyExists`] will never be returned from this type's operations. + Vacant(VacantEntry<'a, T, D>), + + /// An occupied entry; this key already exists. + /// + /// [`RuntimeError::KeyNotFound`] will never be returned from this type's operations. + Occupied(OccupiedEntry<'a, T, D>), +} + +impl<'a, T, D> Entry<'a, T, D> +where + T: Table, + D: DatabaseRw, +{ + /// Returns [`true`] if [`Self::Occupied`]. + pub const fn is_occupied(&self) -> bool { + matches!(self, Self::Occupied(_)) + } + + /// Returns [`true`] if [`Self::Vacant`]. + pub const fn is_vacant(&self) -> bool { + matches!(self, Self::Vacant(_)) + } + + /// Ensures a value is in the entry by inserting the `default` if empty. + /// + /// This only inserts if the entry is [`VacantEntry`]. + pub fn or_insert(self, default: &T::Value) -> DbResult<()> { + match self { + Self::Occupied(_) => Ok(()), + Self::Vacant(entry) => entry.insert(default), + } + } + + /// Ensures a value is in the entry by inserting the result of the `default` function. + /// + /// This only inserts if the entry is [`VacantEntry`]. + pub fn or_insert_with(self, default: F) -> DbResult<()> + where + F: FnOnce() -> T::Value, + { + match self { + Self::Occupied(_) => Ok(()), + Self::Vacant(entry) => entry.insert(&default()), + } + } + + /// Same as [`Self::or_insert_with`] but gives access to the key. + pub fn or_insert_with_key(self, default: F) -> DbResult<()> + where + F: FnOnce(&'a T::Key) -> T::Value, + { + match self { + Self::Occupied(_) => Ok(()), + Self::Vacant(entry) => { + let key = entry.key; + entry.insert(&default(key)) + } + } + } + + /// Returns a reference to this entry's key. + pub const fn key(&self) -> &T::Key { + match self { + Self::Occupied(entry) => entry.key(), + Self::Vacant(entry) => entry.key(), + } + } + + /// Returns a reference to this entry's value (if the entry is [`OccupiedEntry`]). + /// + /// # Errors + /// This returns [`RuntimeError::KeyNotFound`] if the entry is [`VacantEntry`]. + pub const fn value(&self) -> DbResult<&T::Value> { + match self { + Self::Occupied(entry) => Ok(entry.value()), + Self::Vacant(_) => Err(RuntimeError::KeyNotFound), + } + } + + /// Take ownership of entry's value (if the entry is [`OccupiedEntry`]). + /// + /// # Errors + /// This returns [`RuntimeError::KeyNotFound`] if the entry is [`VacantEntry`]. + pub fn into_value(self) -> DbResult { + match self { + Self::Occupied(entry) => Ok(entry.into_value()), + Self::Vacant(_) => Err(RuntimeError::KeyNotFound), + } + } + + /// Conditionally remove the value if it already exists. + /// + /// If `f` returns `true`, the entry will be removed if it exists. + /// + /// This functions does nothing if the entry is [`VacantEntry`]. + pub fn remove_if(self, f: F) -> DbResult + where + F: FnOnce(&T::Value) -> bool, + { + Ok(match self { + Self::Occupied(entry) => { + if f(&entry.value) { + Self::Vacant(entry.remove()?.0) + } else { + Self::Occupied(entry) + } + } + Self::Vacant(entry) => Self::Vacant(entry), + }) + } + + /// Update the value if it already exists. + /// + /// This functions does nothing if the entry is [`VacantEntry`]. + 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 Entry<'_, T, D> +where + T: Table, + ::Value: Default, + D: DatabaseRw, +{ + /// Ensures a value is in the entry by inserting a [`Default`] value if empty. + /// + /// This only inserts if the entry is [`VacantEntry`]. + pub fn or_default(self) -> DbResult<()> { + match self { + Self::Occupied(_) => Ok(()), + Self::Vacant(entry) => entry.insert(&Default::default()), + } + } +} + +//---------------------------------------------------------------------------------------------------- VacantEntry +/// A view into a vacant [`Entry`] in a [`DatabaseRw`]. +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, +{ + /// Gets a reference to the key that is used when [`Self::insert`]ing a value. + pub const fn key(&self) -> &T::Key { + self.key + } + + /// [`DatabaseRw::put`] a new value with [`Self::key`] as the key. + 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), + } + } +} + +//---------------------------------------------------------------------------------------------------- OccupiedEntry +/// A view into an occupied [`Entry`] in a [`DatabaseRw`]. +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<'a, T, D> OccupiedEntry<'a, T, D> +where + T: Table, + D: DatabaseRw, +{ + /// Gets a reference to the key that is used when [`Self::insert`]ing a value. + pub const fn key(&self) -> &T::Key { + self.key + } + + /// Gets a reference to the current value. + /// + /// [`Self::update`] will modify this value. + pub const fn value(&self) -> &T::Value { + &self.value + } + + /// Take ownership of the current value. + pub fn into_value(self) -> T::Value { + self.value + } + + /// Modify the current value and insert it. + /// + /// Calling [`Self::value`] after this will return the modified value. + 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) + } + + /// Replace the current value with a new value. + pub fn insert(self, value: &T::Value) -> DbResult<()> { + DatabaseRw::put(self.db, self.key, value) + } + + /// Remove this entry. + /// + /// The returned values are: + /// - A [`VacantEntry`] + /// - The value that was removed + pub fn remove(self) -> DbResult<(VacantEntry<'a, T, D>, T::Value)> { + DatabaseRw::delete(self.db, self.key)?; + Ok(( + VacantEntry { + db: self.db, + key: self.key, + }, + self.value, + )) + } +} diff --git a/storage/database/src/entry/entry.rs b/storage/database/src/entry/entry.rs deleted file mode 100644 index 4aa9680..0000000 --- a/storage/database/src/entry/entry.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! [`Entry`] - -use crate::{ - entry::{OccupiedEntry, VacantEntry}, - DatabaseRw, DbResult, RuntimeError, Table, -}; - -/// A view into a single entry in a [`DatabaseRw`], which may either be vacant or occupied. -/// -/// This enum is constructed from the [`DatabaseRw::entry`] method. -pub enum Entry<'a, T, D> -where - T: Table, - D: DatabaseRw, -{ - /// A vacant entry; this key did not exist. - /// - /// [`crate::Runtime::KeyExists`] will never be returned from this type's operations. - Vacant(VacantEntry<'a, T, D>), - - /// An occupied entry; this key already exists. - /// - /// [`crate::Runtime::KeyNotFound`] will never be returned from this type's operations. - Occupied(OccupiedEntry<'a, T, D>), -} - -impl<'a, T, D> Entry<'a, T, D> -where - T: Table, - D: DatabaseRw, -{ - /// TODO - pub const fn is_occupied(&self) -> bool { - matches!(self, Self::Occupied(_)) - } - - /// TODO - pub const fn is_vacant(&self) -> bool { - matches!(self, Self::Vacant(_)) - } - - /// Ensures a value is in the entry by inserting the `default` if empty. - /// - /// This only inserts if the entry is [`VacantEntry`]. - pub fn or_insert(self, default: &T::Value) -> DbResult<()> { - match self { - Self::Occupied(_) => Ok(()), - Self::Vacant(entry) => entry.insert(default), - } - } - - /// Ensures a value is in the entry by inserting the result of the `default` function. - /// - /// This only inserts if the entry is [`VacantEntry`]. - pub fn or_insert_with(self, default: F) -> DbResult<()> - where - F: FnOnce() -> T::Value, - { - match self { - Self::Occupied(_) => Ok(()), - Self::Vacant(entry) => entry.insert(&default()), - } - } - - /// Same as [`Self::or_insert_with`] but gives access to the key. - pub fn or_insert_with_key(self, default: F) -> DbResult<()> - where - F: FnOnce(&'a T::Key) -> T::Value, - { - match self { - Self::Occupied(_) => Ok(()), - Self::Vacant(entry) => { - let key = entry.key; - entry.insert(&default(key)) - } - } - } - - /// Returns a reference to this entry's key. - pub const fn key(&self) -> &T::Key { - match self { - Self::Occupied(entry) => entry.key(), - Self::Vacant(entry) => entry.key(), - } - } - - /// Returns a reference to this entry's value (if the entry is [`OccupiedEntry`]). - /// - /// # Errors - /// This returns [`RuntimeError::KeyNotFound`] if the entry is [`VacantEntry`]. - pub const fn value(&self) -> DbResult<&T::Value> { - match self { - Self::Occupied(entry) => Ok(entry.value()), - Self::Vacant(_) => Err(RuntimeError::KeyNotFound), - } - } - - /// Take ownership of entry's value (if the entry is [`OccupiedEntry`]). - /// - /// # Errors - /// This returns [`RuntimeError::KeyNotFound`] if the entry is [`VacantEntry`]. - pub fn into_value(self) -> DbResult { - match self { - Self::Occupied(entry) => Ok(entry.into_value()), - Self::Vacant(_) => Err(RuntimeError::KeyNotFound), - } - } - - /// Conditionally [`OccupiedEntry::remove`] the value if it already exists. - /// - /// This functions does nothing if the entry is [`VacantEntry`]. - pub fn and_remove(self, f: F) -> DbResult - where - F: FnOnce(&T::Value) -> bool, - { - Ok(match self { - Self::Occupied(entry) => { - if f(&entry.value) { - entry.remove()?.0 - } else { - Self::Occupied(entry) - } - } - Self::Vacant(entry) => Self::Vacant(entry), - }) - } - - /// [`OccupiedEntry::update`] the value if it already exists - /// - /// This functions does nothing if the entry is [`VacantEntry`]. - 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 Entry<'_, T, D> -where - T: Table, - ::Value: Default, - D: DatabaseRw, -{ - /// Ensures a value is in the entry by inserting a [`Default`] value if empty. - /// - /// This only inserts if the entry is [`VacantEntry`]. - pub fn or_default(self) -> DbResult<()> { - match self { - Self::Occupied(_) => Ok(()), - Self::Vacant(entry) => entry.insert(&Default::default()), - } - } -} diff --git a/storage/database/src/entry/mod.rs b/storage/database/src/entry/mod.rs deleted file mode 100644 index 072270c..0000000 --- a/storage/database/src/entry/mod.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! `(key, value)` entry API for [`crate::DatabaseRw`]. -//! -//! This module provides a [`std::collections::btree_map::Entry`]-like API for [`crate::DatabaseRw`]. -//! -//! ## Example - modifying a value in place, or inserting it if it doesn't exist -//! ```rust -//! use cuprate_database::{ -//! ConcreteEnv, -//! config::ConfigBuilder, -//! Env, EnvInner, -//! DatabaseRo, DatabaseRw, TxRo, TxRw, RuntimeError, -//! }; -//! -//! # fn main() -> Result<(), Box> { -//! # let tmp_dir = tempfile::tempdir()?; -//! # let db_dir = tmp_dir.path().to_owned(); -//! # let config = ConfigBuilder::new(db_dir.into()).build(); -//! # -//! # let env = ConcreteEnv::open(config)?; -//! # -//! # struct Table; -//! # impl cuprate_database::Table for Table { -//! # const NAME: &'static str = "table"; -//! # type Key = u8; -//! # type Value = u64; -//! # } -//! # -//! # let env_inner = env.env_inner(); -//! # let tx_rw = env_inner.tx_rw()?; -//! # -//! # env_inner.create_db::
(&tx_rw)?; -//! # let mut table = env_inner.open_db_rw::
(&tx_rw)?; -//! /// The key to use. -//! const KEY: u8 = u8::MAX; -//! -//! /// The update function applied if the value already exists. -//! fn f(value: &mut u64) { -//! *value += 1; -//! } -//! -//! // No entry exists. -//! assert!(matches!(table.first(), Err(RuntimeError::KeyNotFound))); -//! -//! // Increment the value by `1` or insert a `0` if it doesn't exist. -//! table.entry(&KEY)?.and_update(f)?.or_insert(&0)?; -//! assert_eq!(table.first()?, (KEY, 0)); -//! -//! // Again. -//! table.entry(&KEY)?.and_update(f)?.or_insert(&0)?; -//! assert_eq!(table.first()?, (KEY, 1)); -//! # Ok(()) } -//! ``` - -#[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 deleted file mode 100644 index 44d5b5d..0000000 --- a/storage/database/src/entry/occupied_entry.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! [`OccupiedEntry`] - -use crate::{DatabaseRw, DbResult, Table}; - -use super::{Entry, VacantEntry}; - -/// A view into an occupied entry in a [`DatabaseRw`]. It is part of [`crate::entry::Entry`]. -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<'a, T, D> OccupiedEntry<'a, T, D> -where - T: Table, - D: DatabaseRw, -{ - /// Gets a reference to the key that is used when [`Self::insert`]ing a value. - pub const fn key(&self) -> &T::Key { - self.key - } - - /// Gets a reference to the current value. - /// - /// [`Self::update`] will modify this value. - pub const fn value(&self) -> &T::Value { - &self.value - } - - /// Take ownership of the current value. - pub fn into_value(self) -> T::Value { - self.value - } - - /// Modify the current value and insert it. - /// - /// Calling [`Self::value`] after this will return the modified value. - 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) - } - - /// Replace the current value with a new value. - pub fn insert(self, value: &T::Value) -> DbResult<()> { - DatabaseRw::put(self.db, self.key, value) - } - - /// Remove this entry. - /// - /// The returns values are: - /// - An [`Entry::VacantEntry`] - /// - The value that was removed - pub fn remove(self) -> DbResult<(Entry<'a, T, D>, T::Value)> { - DatabaseRw::delete(self.db, self.key)?; - Ok(( - Entry::Vacant(VacantEntry { - db: self.db, - key: self.key, - }), - self.value, - )) - } -} diff --git a/storage/database/src/entry/vacant_entry.rs b/storage/database/src/entry/vacant_entry.rs deleted file mode 100644 index fd51aee..0000000 --- a/storage/database/src/entry/vacant_entry.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! [`VacantEntry`] - -use crate::{DatabaseRw, DbResult, RuntimeError, Table}; - -/// A view into a vacant entry in a [`DatabaseRw`]. It is part of [`crate::entry::Entry`]. -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, -{ - /// Gets a reference to the key that is used when [`Self::insert`]ing a value. - pub const fn key(&self) -> &T::Key { - self.key - } - - /// [`DatabaseRw::put`] a new value with [`Self::key`] as the key. - 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), - } - } -}