diff --git a/storage/database/src/entry/entry.rs b/storage/database/src/entry/entry.rs index 734efb7..ec63485 100644 --- a/storage/database/src/entry/entry.rs +++ b/storage/database/src/entry/entry.rs @@ -1,16 +1,26 @@ -//! TODO +//! [`Entry`] use crate::{ entry::{OccupiedEntry, VacantEntry}, DatabaseRw, DbResult, 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>), } @@ -19,7 +29,9 @@ where T: Table, D: DatabaseRw, { - /// TODO + /// 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(()), @@ -27,7 +39,9 @@ where } } - /// TODO + /// 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() -> &'a T::Value, @@ -38,7 +52,7 @@ where } } - /// TODO + /// 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) -> &'a T::Value, @@ -52,7 +66,7 @@ where } } - /// TODO + /// Returns a reference to this entry's key. pub const fn key(&self) -> &T::Key { match self { Self::Occupied(entry) => entry.key(), @@ -60,7 +74,7 @@ where } } - /// TODO + /// Returns a reference to this entry's key (if the entry is [`OccupiedEntry`]). pub const fn value(&self) -> Option<&T::Value> { match self { Self::Occupied(entry) => Some(entry.value()), @@ -68,7 +82,7 @@ where } } - /// TODO + /// Provides in-place mutable access to an occupied entry before any potential inserts. pub fn and_update(self, f: F) -> DbResult where F: FnOnce(&mut T::Value), @@ -83,14 +97,19 @@ where } } -impl<'a, T, D> Entry<'a, T, D> +impl Entry<'_, T, D> where T: Table, ::Value: Default, D: DatabaseRw, { - /// TODO - pub fn or_default(self) -> &'a mut T::Value { - todo!() + /// 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 index 2e90aab..072270c 100644 --- a/storage/database/src/entry/mod.rs +++ b/storage/database/src/entry/mod.rs @@ -1,4 +1,55 @@ -//! TODO +//! `(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; diff --git a/storage/database/src/entry/occupied_entry.rs b/storage/database/src/entry/occupied_entry.rs index f8df8fe..af3f756 100644 --- a/storage/database/src/entry/occupied_entry.rs +++ b/storage/database/src/entry/occupied_entry.rs @@ -1,7 +1,8 @@ -//! TODO +//! [`OccupiedEntry`] use crate::{DatabaseRw, DbResult, Table}; +/// 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, @@ -17,17 +18,26 @@ where T: Table, D: DatabaseRw, { - /// TODO + /// Gets a reference to the key that is used when [`Self::insert`]ing a value. pub const fn key(&self) -> &T::Key { self.key } - /// TODO + /// Gets a reference to the current value. + /// + /// [`Self::update`] will modify this value. pub const fn value(&self) -> &T::Value { &self.value } - /// TODO + /// 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), @@ -36,23 +46,13 @@ where DatabaseRw::put(self.db, self.key, &self.value) } - /// TODO - pub fn insert(&mut self, value: &T::Value) -> DbResult<()> { + /// Replace the current value with a new value. + pub fn insert(self, value: &T::Value) -> DbResult<()> { DatabaseRw::put(self.db, self.key, value) } - /// TODO + /// Remove this entry. 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 index b7cdb4f..fd51aee 100644 --- a/storage/database/src/entry/vacant_entry.rs +++ b/storage/database/src/entry/vacant_entry.rs @@ -1,7 +1,8 @@ -//! TODO +//! [`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, @@ -16,12 +17,12 @@ where T: Table, D: DatabaseRw, { - /// TODO + /// Gets a reference to the key that is used when [`Self::insert`]ing a value. pub const fn key(&self) -> &T::Key { self.key } - /// TODO + /// [`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(()),