diff --git a/database/src/backend/heed/database.rs b/database/src/backend/heed/database.rs index 5153f281..778ffd56 100644 --- a/database/src/backend/heed/database.rs +++ b/database/src/backend/heed/database.rs @@ -42,7 +42,7 @@ pub(super) struct HeedTableRo<'tx, T: Table> { /// /// Matches `redb::Table` (read & write). pub(super) struct HeedTableRw<'env, 'tx, T: Table> { - /// TODO + /// An already opened database table. pub(super) db: HeedDb, /// The associated read/write transaction that opened this table. pub(super) tx_rw: &'tx mut heed::RwTxn<'env>, @@ -53,7 +53,7 @@ pub(super) struct HeedTableRw<'env, 'tx, T: Table> { // call the functions since the database is held by value, so // just use these generic functions that both can call instead. -/// Shared generic `get()` between `HeedTableR{o,w}`. +/// Shared [`DatabaseRo::get()`]. #[inline] fn get( db: &HeedDb, @@ -63,7 +63,7 @@ fn get( db.get(tx_ro, key)?.ok_or(RuntimeError::KeyNotFound) } -/// Shared generic `get_range()` between `HeedTableR{o,w}`. +/// Shared [`DatabaseRo::get_range()`]. #[inline] fn get_range<'a, T: Table, Range>( db: &'a HeedDb, @@ -76,6 +76,69 @@ where Ok(db.range(tx_ro, &range)?.map(|res| Ok(res?.1))) } +/// Shared [`DatabaseRo::iter()`]. +#[inline] +fn iter<'a, T: Table>( + db: &'a HeedDb, + tx_ro: &'a heed::RoTxn<'_>, +) -> Result> + 'a, RuntimeError> { + Ok(db.iter(tx_ro)?.map(|res| Ok(res?))) +} + +/// Shared [`DatabaseRo::keys()`]. +#[inline] +fn keys<'a, T: Table>( + db: &'a HeedDb, + tx_ro: &'a heed::RoTxn<'_>, +) -> Result> + 'a, RuntimeError> { + Ok(db.iter(tx_ro)?.map(|res| Ok(res?.0))) +} + +/// Shared [`DatabaseRo::values()`]. +#[inline] +fn values<'a, T: Table>( + db: &'a HeedDb, + tx_ro: &'a heed::RoTxn<'_>, +) -> Result> + 'a, RuntimeError> { + Ok(db.iter(tx_ro)?.map(|res| Ok(res?.1))) +} + +/// Shared [`DatabaseRo::len()`]. +#[inline] +fn len( + db: &HeedDb, + tx_ro: &heed::RoTxn<'_>, +) -> Result { + Ok(db.len(tx_ro)?) +} + +/// Shared [`DatabaseRo::first()`]. +#[inline] +fn first( + db: &HeedDb, + tx_ro: &heed::RoTxn<'_>, +) -> Result<(T::Key, T::Value), RuntimeError> { + db.first(tx_ro)?.ok_or(RuntimeError::KeyNotFound) +} + +/// Shared [`DatabaseRo::last()`]. +#[inline] +fn last( + db: &HeedDb, + tx_ro: &heed::RoTxn<'_>, +) -> Result<(T::Key, T::Value), RuntimeError> { + db.last(tx_ro)?.ok_or(RuntimeError::KeyNotFound) +} + +/// Shared [`DatabaseRo::is_empty()`]. +#[inline] +fn is_empty( + db: &HeedDb, + tx_ro: &heed::RoTxn<'_>, +) -> Result { + Ok(db.is_empty(tx_ro)?) +} + //---------------------------------------------------------------------------------------------------- DatabaseRo Impl impl DatabaseRo for HeedTableRo<'_, T> { #[inline] @@ -93,6 +156,48 @@ impl DatabaseRo for HeedTableRo<'_, T> { { get_range::(&self.db, self.tx_ro, range) } + + #[inline] + fn iter( + &self, + ) -> Result> + '_, RuntimeError> + { + iter::(&self.db, self.tx_ro) + } + + #[inline] + fn keys( + &self, + ) -> Result> + '_, RuntimeError> { + keys::(&self.db, self.tx_ro) + } + + #[inline] + fn values( + &self, + ) -> Result> + '_, RuntimeError> { + values::(&self.db, self.tx_ro) + } + + #[inline] + fn len(&self) -> Result { + len::(&self.db, self.tx_ro) + } + + #[inline] + fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> { + first::(&self.db, self.tx_ro) + } + + #[inline] + fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> { + last::(&self.db, self.tx_ro) + } + + #[inline] + fn is_empty(&self) -> Result { + is_empty::(&self.db, self.tx_ro) + } } //---------------------------------------------------------------------------------------------------- DatabaseRw Impl @@ -112,6 +217,48 @@ impl DatabaseRo for HeedTableRw<'_, '_, T> { { get_range::(&self.db, self.tx_rw, range) } + + #[inline] + fn iter( + &self, + ) -> Result> + '_, RuntimeError> + { + iter::(&self.db, self.tx_rw) + } + + #[inline] + fn keys( + &self, + ) -> Result> + '_, RuntimeError> { + keys::(&self.db, self.tx_rw) + } + + #[inline] + fn values( + &self, + ) -> Result> + '_, RuntimeError> { + values::(&self.db, self.tx_rw) + } + + #[inline] + fn len(&self) -> Result { + len::(&self.db, self.tx_rw) + } + + #[inline] + fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> { + first::(&self.db, self.tx_rw) + } + + #[inline] + fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> { + last::(&self.db, self.tx_rw) + } + + #[inline] + fn is_empty(&self) -> Result { + is_empty::(&self.db, self.tx_rw) + } } impl DatabaseRw for HeedTableRw<'_, '_, T> { @@ -125,6 +272,53 @@ impl DatabaseRw for HeedTableRw<'_, '_, T> { self.db.delete(self.tx_rw, key)?; Ok(()) } + + #[inline] + fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { + // Get the first value first... + let Some(first) = self.db.first(self.tx_rw)? else { + return Err(RuntimeError::KeyNotFound); + }; + + // ...then remove it. + // + // We use an iterator because we want to semantically + // remove the _first_ and only the first `(key, value)`. + // `delete()` removes all keys including duplicates which + // is slightly different behavior. + let mut iter = self.db.iter_mut(self.tx_rw)?; + + // SAFETY: + // It is undefined behavior to keep a reference of + // a value from this database while modifying it. + // We are deleting the value and never accessing + // the iterator again so this should be safe. + unsafe { + iter.del_current()?; + } + + Ok(first) + } + + #[inline] + fn pop_last(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { + let Some(first) = self.db.last(self.tx_rw)? else { + return Err(RuntimeError::KeyNotFound); + }; + + let mut iter = self.db.rev_iter_mut(self.tx_rw)?; + + // SAFETY: + // It is undefined behavior to keep a reference of + // a value from this database while modifying it. + // We are deleting the value and never accessing + // the iterator again so this should be safe. + unsafe { + iter.del_current()?; + } + + Ok(first) + } } //---------------------------------------------------------------------------------------------------- Tests diff --git a/database/src/backend/heed/env.rs b/database/src/backend/heed/env.rs index 1bf5bee1..dcf0ddf7 100644 --- a/database/src/backend/heed/env.rs +++ b/database/src/backend/heed/env.rs @@ -304,6 +304,17 @@ where tx_rw, }) } + + #[inline] + fn clear_db(&self, tx_rw: &mut heed::RwTxn<'env>) -> Result<(), RuntimeError> { + // Open the table first... + let db: HeedDb = self + .open_database(tx_rw, Some(T::NAME))? + .expect(PANIC_MSG_MISSING_TABLE); + + // ...then clear it. + Ok(db.clear(tx_rw)?) + } } //---------------------------------------------------------------------------------------------------- Tests diff --git a/database/src/backend/redb/database.rs b/database/src/backend/redb/database.rs index 40ea7a92..853a91ce 100644 --- a/database/src/backend/redb/database.rs +++ b/database/src/backend/redb/database.rs @@ -24,7 +24,7 @@ use crate::{ // call the functions since the database is held by value, so // just use these generic functions that both can call instead. -/// Shared generic `get()` between `RedbTableR{o,w}`. +/// Shared [`DatabaseRo::get()`]. #[inline] fn get( db: &impl redb::ReadableTable, StorableRedb>, @@ -33,7 +33,7 @@ fn get( Ok(db.get(key)?.ok_or(RuntimeError::KeyNotFound)?.value()) } -/// Shared generic `get_range()` between `RedbTableR{o,w}`. +/// Shared [`DatabaseRo::get_range()`]. #[inline] fn get_range<'a, T: Table, Range>( db: &'a impl redb::ReadableTable, StorableRedb>, @@ -43,11 +43,78 @@ where Range: RangeBounds + 'a, { Ok(db.range(range)?.map(|result| { - let (_key, value_guard) = result?; - Ok(value_guard.value()) + let (_key, value) = result?; + Ok(value.value()) })) } +/// Shared [`DatabaseRo::iter()`]. +#[inline] +fn iter( + db: &impl redb::ReadableTable, StorableRedb>, +) -> Result> + '_, RuntimeError> { + Ok(db.iter()?.map(|result| { + let (key, value) = result?; + Ok((key.value(), value.value())) + })) +} + +/// Shared [`DatabaseRo::iter()`]. +#[inline] +fn keys( + db: &impl redb::ReadableTable, StorableRedb>, +) -> Result> + '_, RuntimeError> { + Ok(db.iter()?.map(|result| { + let (key, _value) = result?; + Ok(key.value()) + })) +} + +/// Shared [`DatabaseRo::values()`]. +#[inline] +fn values( + db: &impl redb::ReadableTable, StorableRedb>, +) -> Result> + '_, RuntimeError> { + Ok(db.iter()?.map(|result| { + let (_key, value) = result?; + Ok(value.value()) + })) +} + +/// Shared [`DatabaseRo::len()`]. +#[inline] +fn len( + db: &impl redb::ReadableTable, StorableRedb>, +) -> Result { + Ok(db.len()?) +} + +/// Shared [`DatabaseRo::first()`]. +#[inline] +fn first( + db: &impl redb::ReadableTable, StorableRedb>, +) -> Result<(T::Key, T::Value), RuntimeError> { + let (key, value) = db.first()?.ok_or(RuntimeError::KeyNotFound)?; + Ok((key.value(), value.value())) +} + +/// Shared [`DatabaseRo::last()`]. +#[inline] +fn last( + db: &impl redb::ReadableTable, StorableRedb>, +) -> Result<(T::Key, T::Value), RuntimeError> { + let (key, value) = db.last()?.ok_or(RuntimeError::KeyNotFound)?; + Ok((key.value(), value.value())) +} + +/// Shared [`DatabaseRo::is_empty()`]. +#[inline] +fn is_empty( + db: &impl redb::ReadableTable, StorableRedb>, +) -> Result { + Ok(db.is_empty()?) +} + //---------------------------------------------------------------------------------------------------- DatabaseRo impl DatabaseRo for RedbTableRo { #[inline] @@ -65,6 +132,48 @@ impl DatabaseRo for RedbTableRo { { get_range::(self, range) } + + #[inline] + fn iter( + &self, + ) -> Result> + '_, RuntimeError> + { + iter::(self) + } + + #[inline] + fn keys( + &self, + ) -> Result> + '_, RuntimeError> { + keys::(self) + } + + #[inline] + fn values( + &self, + ) -> Result> + '_, RuntimeError> { + values::(self) + } + + #[inline] + fn len(&self) -> Result { + len::(self) + } + + #[inline] + fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> { + first::(self) + } + + #[inline] + fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> { + last::(self) + } + + #[inline] + fn is_empty(&self) -> Result { + is_empty::(self) + } } //---------------------------------------------------------------------------------------------------- DatabaseRw @@ -84,23 +193,76 @@ impl DatabaseRo for RedbTableRw<'_, T::Key, T::Value> { { get_range::(self, range) } + + #[inline] + fn iter( + &self, + ) -> Result> + '_, RuntimeError> + { + iter::(self) + } + + #[inline] + fn keys( + &self, + ) -> Result> + '_, RuntimeError> { + keys::(self) + } + + #[inline] + fn values( + &self, + ) -> Result> + '_, RuntimeError> { + values::(self) + } + + #[inline] + fn len(&self) -> Result { + len::(self) + } + + #[inline] + fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> { + first::(self) + } + + #[inline] + fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> { + last::(self) + } + + #[inline] + fn is_empty(&self) -> Result { + is_empty::(self) + } } impl DatabaseRw for RedbTableRw<'_, T::Key, T::Value> { - // `redb` returns the value after `insert()/remove()` - // we end with Ok(()) instead. + // `redb` returns the value after function calls so we end with Ok(()) instead. #[inline] fn put(&mut self, key: &T::Key, value: &T::Value) -> Result<(), RuntimeError> { - self.insert(key, value)?; + redb::Table::insert(self, key, value)?; Ok(()) } #[inline] fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError> { - self.remove(key)?; + redb::Table::remove(self, key)?; Ok(()) } + + #[inline] + fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { + let (key, value) = redb::Table::pop_first(self)?.ok_or(RuntimeError::KeyNotFound)?; + Ok((key.value(), value.value())) + } + + #[inline] + fn pop_last(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { + let (key, value) = redb::Table::pop_last(self)?.ok_or(RuntimeError::KeyNotFound)?; + Ok((key.value(), value.value())) + } } //---------------------------------------------------------------------------------------------------- Tests diff --git a/database/src/backend/redb/env.rs b/database/src/backend/redb/env.rs index 5708d6fa..74e55b4b 100644 --- a/database/src/backend/redb/env.rs +++ b/database/src/backend/redb/env.rs @@ -208,6 +208,32 @@ where // Ok(tx_rw.open_table(table)?) } + + #[inline] + fn clear_db(&self, tx_rw: &mut redb::WriteTransaction) -> Result<(), RuntimeError> { + let table: redb::TableDefinition< + 'static, + StorableRedb<::Key>, + StorableRedb<::Value>, + > = redb::TableDefinition::new(::NAME); + + // INVARIANT: + // This `delete_table()` will not run into this `TableAlreadyOpen` error: + // + // which will panic in the `From` impl, as: + // + // 1. Only 1 `redb::WriteTransaction` can exist at a time + // 2. We have exclusive access to it + // 3. So it's not being used to open a table since that needs `&tx_rw` + // + // Reader-open tables do not affect this, if they're open the below is still OK. + redb::WriteTransaction::delete_table(tx_rw, table)?; + // Re-create the table. + // `redb` creates tables if they don't exist, so this should never panic. + redb::WriteTransaction::open_table(tx_rw, table)?; + + Ok(()) + } } //---------------------------------------------------------------------------------------------------- Tests diff --git a/database/src/backend/tests.rs b/database/src/backend/tests.rs index 960d44df..cf3bf4bc 100644 --- a/database/src/backend/tests.rs +++ b/database/src/backend/tests.rs @@ -13,7 +13,11 @@ //! //! `redb`, and it only must be enabled for it to be tested. -#![allow(clippy::items_after_statements, clippy::significant_drop_tightening)] +#![allow( + clippy::items_after_statements, + clippy::significant_drop_tightening, + clippy::cast_possible_truncation +)] //---------------------------------------------------------------------------------------------------- Import use std::borrow::{Borrow, Cow}; @@ -180,6 +184,8 @@ fn db_read_write() { output_flags: 0, tx_idx: 2_353_487, }; + /// How many `(key, value)` pairs will be inserted. + const N: u64 = 100; /// Assert 2 `Output`'s are equal, and that accessing /// their fields don't result in an unaligned panic. @@ -191,17 +197,28 @@ fn db_read_write() { assert_eq!(output.tx_idx, VALUE.tx_idx); } - // Insert `0..100` keys. + assert!(table.is_empty().unwrap()); + + // Insert keys. let mut key = KEY; - for i in 0..100 { + for i in 0..N { table.put(&key, &VALUE).unwrap(); key.amount += 1; } - // Assert the 1st key is there. + assert_eq!(table.len().unwrap(), N); + + // Assert the first/last `(key, value)`s are there. { - let value: Output = table.get(&KEY).unwrap(); - assert_same(value); + assert!(table.contains(&KEY).unwrap()); + let get: Output = table.get(&KEY).unwrap(); + assert_same(get); + + let first: Output = table.first().unwrap().1; + assert_same(first); + + let last: Output = table.last().unwrap().1; + assert_same(last); } // Assert the whole range is there. @@ -214,16 +231,16 @@ fn db_read_write() { i += 1; } - assert_eq!(i, 100); + assert_eq!(i, N); } // `get_range()` tests. let mut key = KEY; - key.amount += 100; + key.amount += N; let range = KEY..key; // Assert count is correct. - assert_eq!(100, table.get_range(range.clone()).unwrap().count()); + assert_eq!(N as usize, table.get_range(range.clone()).unwrap().count()); // Assert each returned value from the iterator is owned. { @@ -236,16 +253,52 @@ fn db_read_write() { // Assert each value is the same. { let mut iter = table.get_range(range).unwrap(); - for _ in 0..100 { + for _ in 0..N { let value: Output = iter.next().unwrap().unwrap(); assert_same(value); } } // Assert deleting works. - table.delete(&KEY).unwrap(); - let value = table.get(&KEY); - assert!(matches!(value, Err(RuntimeError::KeyNotFound))); + { + table.delete(&KEY).unwrap(); + let value = table.get(&KEY); + assert!(!table.contains(&KEY).unwrap()); + assert!(matches!(value, Err(RuntimeError::KeyNotFound))); + // Assert the other `(key, value)` pairs are still there. + let mut key = KEY; + key.amount += N - 1; // we used inclusive `0..N` + let value = table.get(&key).unwrap(); + assert_same(value); + } + + drop(table); + tx_rw.commit().unwrap(); + let mut tx_rw = env_inner.tx_rw().unwrap(); + + // Assert `clear_db()` works. + { + // Make sure this works even if readers have the table open. + let tx_ro = env_inner.tx_ro().unwrap(); + let reader_table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + env_inner.clear_db::(&mut tx_rw).unwrap(); + let table = env_inner.open_db_rw::(&mut tx_rw).unwrap(); + assert!(table.is_empty().unwrap()); + for n in 0..N { + let mut key = KEY; + key.amount += n; + let value = table.get(&key); + assert!(matches!(value, Err(RuntimeError::KeyNotFound))); + assert!(!table.contains(&key).unwrap()); + } + + // Reader still sees old value. + assert!(!reader_table.is_empty().unwrap()); + + // Writer sees updated value (nothing). + assert!(table.is_empty().unwrap()); + } } //---------------------------------------------------------------------------------------------------- Table Tests @@ -253,11 +306,7 @@ fn db_read_write() { /// /// Each one of these tests: /// - Opens a specific table -/// - Inserts a key + value -/// - Retrieves the key + value -/// - Asserts it is the same -/// - Tests `get_range()` -/// - Tests `delete()` +/// - Essentially does the `db_read_write` test macro_rules! test_tables { ($( $table:ident, // Table type @@ -293,6 +342,9 @@ macro_rules! test_tables { assert_eq(&value); } + assert!(table.contains(&KEY).unwrap()); + assert_eq!(table.len().unwrap(), 1); + // Assert `get_range()` works. { let range = KEY..; @@ -303,9 +355,26 @@ macro_rules! test_tables { } // Assert deleting works. - table.delete(&KEY).unwrap(); - let value = table.get(&KEY); - assert!(matches!(value, Err(RuntimeError::KeyNotFound))); + { + table.delete(&KEY).unwrap(); + let value = table.get(&KEY); + assert!(matches!(value, Err(RuntimeError::KeyNotFound))); + assert!(!table.contains(&KEY).unwrap()); + assert_eq!(table.len().unwrap(), 0); + } + + table.put(&KEY, &value).unwrap(); + + // Assert `clear_db()` works. + { + drop(table); + env_inner.clear_db::<$table>(&mut tx_rw).unwrap(); + let table = env_inner.open_db_rw::<$table>(&mut tx_rw).unwrap(); + let value = table.get(&KEY); + assert!(matches!(value, Err(RuntimeError::KeyNotFound))); + assert!(!table.contains(&KEY).unwrap()); + assert_eq!(table.len().unwrap(), 0); + } } )*}}; } diff --git a/database/src/database.rs b/database/src/database.rs index c94896d9..6b711988 100644 --- a/database/src/database.rs +++ b/database/src/database.rs @@ -29,7 +29,14 @@ pub trait DatabaseRo { /// It will return other [`RuntimeError`]'s on things like IO errors as well. fn get(&self, key: &T::Key) -> Result; - /// Get an iterator of values corresponding to a range of keys. + /// Get an iterator of value's corresponding to a range of keys. + /// + /// For example: + /// ```rust,ignore + /// // This will return all 100 values corresponding + /// // to the keys `{0, 1, 2, ..., 100}`. + /// self.get_range(0..100); + /// ``` /// /// Although the returned iterator itself is tied to the lifetime /// of `&'a self`, the returned values from the iterator are _owned_. @@ -39,12 +46,73 @@ pub trait DatabaseRo { /// if a particular key in the `range` does not exist, /// [`RuntimeError::KeyNotFound`] wrapped in [`Err`] will be returned /// from the iterator. + #[allow(clippy::iter_not_returning_iterator)] fn get_range<'a, Range>( &'a self, range: Range, ) -> Result> + 'a, RuntimeError> where Range: RangeBounds + 'a; + + /// TODO + /// + /// # Errors + /// TODO + #[allow(clippy::iter_not_returning_iterator)] + fn iter( + &self, + ) -> Result> + '_, RuntimeError>; + + /// TODO + /// + /// # Errors + /// TODO + fn keys(&self) + -> Result> + '_, RuntimeError>; + + /// TODO + /// + /// # Errors + /// TODO + fn values( + &self, + ) -> Result> + '_, RuntimeError>; + + /// TODO + /// + /// # Errors + /// TODO + fn contains(&self, key: &T::Key) -> Result { + match self.get(key) { + Ok(_) => Ok(true), + Err(RuntimeError::KeyNotFound) => Ok(false), + Err(e) => Err(e), + } + } + + /// TODO + /// + /// # Errors + /// TODO + fn len(&self) -> Result; + + /// TODO + /// + /// # Errors + /// TODO + fn first(&self) -> Result<(T::Key, T::Value), RuntimeError>; + + /// TODO + /// + /// # Errors + /// TODO + fn last(&self) -> Result<(T::Key, T::Value), RuntimeError>; + + /// TODO + /// + /// # Errors + /// TODO + fn is_empty(&self) -> Result; } //---------------------------------------------------------------------------------------------------- DatabaseRw @@ -65,4 +133,16 @@ pub trait DatabaseRw: DatabaseRo { /// # Errors /// This will return [`RuntimeError::KeyNotFound`] wrapped in [`Err`] if `key` does not exist. fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError>; + + /// TODO + /// + /// # Errors + /// TODO + fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError>; + + /// TODO + /// + /// # Errors + /// TODO + fn pop_last(&mut self) -> Result<(T::Key, T::Value), RuntimeError>; } diff --git a/database/src/env.rs b/database/src/env.rs index c815d0bd..65f1c479 100644 --- a/database/src/env.rs +++ b/database/src/env.rs @@ -196,6 +196,8 @@ where /// ``` /// /// # Errors + /// This function errors upon internal database/IO errors. + /// /// As [`Table`] is `Sealed`, and all tables are created /// upon [`Env::open`], this function will never error because /// a table doesn't exist. @@ -210,8 +212,26 @@ where /// passed as a generic to this function. /// /// # Errors + /// This function errors upon internal database/IO errors. + /// /// As [`Table`] is `Sealed`, and all tables are created /// upon [`Env::open`], this function will never error because /// a table doesn't exist. fn open_db_rw(&self, tx_rw: &mut Rw) -> Result, RuntimeError>; + + /// Clear all `(key, value)`'s from a database table. + /// + /// This will delete all key and values in the passed + /// `T: Table`, but the table itself will continue to exist. + /// + /// Note that this operation is tied to `tx_rw`, as such this + /// function's effects can be aborted using [`TxRw::abort`]. + /// + /// # Errors + /// This function errors upon internal database/IO errors. + /// + /// As [`Table`] is `Sealed`, and all tables are created + /// upon [`Env::open`], this function will never error because + /// a table doesn't exist. + fn clear_db(&self, tx_rw: &mut Rw) -> Result<(), RuntimeError>; }