database: add more fn to trait DatabaseR{o,w} (#96)

* database: add more fn signatures

* database: fix docs

* heed: add new fn signatures with `todo!()`

* heed: add shared fn sigantures

* database: make `iter()` return `(key, value)`

* database: make `get_range()` return `(key, value)`

* database: make `first()/last()` return `(key, value)`

* heed: impl new fn signatures

* redb: impl new fn signatures

* backend: update tests for new `fn`s

* backend: update table tests

* heed: fix `retain()` predicate behavior

* database: add `keys()`, `values()`, `pop_first()`, `pop_last()`

* heed: impl `keys()`, `values()`, `pop_first()`, `pop_last()`

* redb: impl `keys()`, `values()`, `pop_first()`, `pop_last()`

* database: add `contains()`

* backend: add `contains()` to tests

* database: remove `retain()`

* database: only return value in `get_range()`

* database: `DatabaseRw::clear()` -> `EnvInner::clear_db()`

* redb: update `clear_db()` invariant doc

* fix redb

* cargo.toml: avoid merge conflict

* update `get_range()` docs

* redb: re-create table in `clear_db()`
This commit is contained in:
hinto-janai 2024-03-29 21:33:38 -04:00 committed by GitHub
parent ae0d9ff36a
commit 21697b8af5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 595 additions and 33 deletions

View file

@ -42,7 +42,7 @@ pub(super) struct HeedTableRo<'tx, T: Table> {
/// ///
/// Matches `redb::Table` (read & write). /// Matches `redb::Table` (read & write).
pub(super) struct HeedTableRw<'env, 'tx, T: Table> { pub(super) struct HeedTableRw<'env, 'tx, T: Table> {
/// TODO /// An already opened database table.
pub(super) db: HeedDb<T::Key, T::Value>, pub(super) db: HeedDb<T::Key, T::Value>,
/// The associated read/write transaction that opened this table. /// The associated read/write transaction that opened this table.
pub(super) tx_rw: &'tx mut heed::RwTxn<'env>, 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 // call the functions since the database is held by value, so
// just use these generic functions that both can call instead. // just use these generic functions that both can call instead.
/// Shared generic `get()` between `HeedTableR{o,w}`. /// Shared [`DatabaseRo::get()`].
#[inline] #[inline]
fn get<T: Table>( fn get<T: Table>(
db: &HeedDb<T::Key, T::Value>, db: &HeedDb<T::Key, T::Value>,
@ -63,7 +63,7 @@ fn get<T: Table>(
db.get(tx_ro, key)?.ok_or(RuntimeError::KeyNotFound) db.get(tx_ro, key)?.ok_or(RuntimeError::KeyNotFound)
} }
/// Shared generic `get_range()` between `HeedTableR{o,w}`. /// Shared [`DatabaseRo::get_range()`].
#[inline] #[inline]
fn get_range<'a, T: Table, Range>( fn get_range<'a, T: Table, Range>(
db: &'a HeedDb<T::Key, T::Value>, db: &'a HeedDb<T::Key, T::Value>,
@ -76,6 +76,69 @@ where
Ok(db.range(tx_ro, &range)?.map(|res| Ok(res?.1))) Ok(db.range(tx_ro, &range)?.map(|res| Ok(res?.1)))
} }
/// Shared [`DatabaseRo::iter()`].
#[inline]
fn iter<'a, T: Table>(
db: &'a HeedDb<T::Key, T::Value>,
tx_ro: &'a heed::RoTxn<'_>,
) -> Result<impl Iterator<Item = Result<(T::Key, T::Value), RuntimeError>> + 'a, RuntimeError> {
Ok(db.iter(tx_ro)?.map(|res| Ok(res?)))
}
/// Shared [`DatabaseRo::keys()`].
#[inline]
fn keys<'a, T: Table>(
db: &'a HeedDb<T::Key, T::Value>,
tx_ro: &'a heed::RoTxn<'_>,
) -> Result<impl Iterator<Item = Result<T::Key, RuntimeError>> + 'a, RuntimeError> {
Ok(db.iter(tx_ro)?.map(|res| Ok(res?.0)))
}
/// Shared [`DatabaseRo::values()`].
#[inline]
fn values<'a, T: Table>(
db: &'a HeedDb<T::Key, T::Value>,
tx_ro: &'a heed::RoTxn<'_>,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError> {
Ok(db.iter(tx_ro)?.map(|res| Ok(res?.1)))
}
/// Shared [`DatabaseRo::len()`].
#[inline]
fn len<T: Table>(
db: &HeedDb<T::Key, T::Value>,
tx_ro: &heed::RoTxn<'_>,
) -> Result<u64, RuntimeError> {
Ok(db.len(tx_ro)?)
}
/// Shared [`DatabaseRo::first()`].
#[inline]
fn first<T: Table>(
db: &HeedDb<T::Key, T::Value>,
tx_ro: &heed::RoTxn<'_>,
) -> Result<(T::Key, T::Value), RuntimeError> {
db.first(tx_ro)?.ok_or(RuntimeError::KeyNotFound)
}
/// Shared [`DatabaseRo::last()`].
#[inline]
fn last<T: Table>(
db: &HeedDb<T::Key, T::Value>,
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<T: Table>(
db: &HeedDb<T::Key, T::Value>,
tx_ro: &heed::RoTxn<'_>,
) -> Result<bool, RuntimeError> {
Ok(db.is_empty(tx_ro)?)
}
//---------------------------------------------------------------------------------------------------- DatabaseRo Impl //---------------------------------------------------------------------------------------------------- DatabaseRo Impl
impl<T: Table> DatabaseRo<T> for HeedTableRo<'_, T> { impl<T: Table> DatabaseRo<T> for HeedTableRo<'_, T> {
#[inline] #[inline]
@ -93,6 +156,48 @@ impl<T: Table> DatabaseRo<T> for HeedTableRo<'_, T> {
{ {
get_range::<T, Range>(&self.db, self.tx_ro, range) get_range::<T, Range>(&self.db, self.tx_ro, range)
} }
#[inline]
fn iter(
&self,
) -> Result<impl Iterator<Item = Result<(T::Key, T::Value), RuntimeError>> + '_, RuntimeError>
{
iter::<T>(&self.db, self.tx_ro)
}
#[inline]
fn keys(
&self,
) -> Result<impl Iterator<Item = Result<T::Key, RuntimeError>> + '_, RuntimeError> {
keys::<T>(&self.db, self.tx_ro)
}
#[inline]
fn values(
&self,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + '_, RuntimeError> {
values::<T>(&self.db, self.tx_ro)
}
#[inline]
fn len(&self) -> Result<u64, RuntimeError> {
len::<T>(&self.db, self.tx_ro)
}
#[inline]
fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> {
first::<T>(&self.db, self.tx_ro)
}
#[inline]
fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> {
last::<T>(&self.db, self.tx_ro)
}
#[inline]
fn is_empty(&self) -> Result<bool, RuntimeError> {
is_empty::<T>(&self.db, self.tx_ro)
}
} }
//---------------------------------------------------------------------------------------------------- DatabaseRw Impl //---------------------------------------------------------------------------------------------------- DatabaseRw Impl
@ -112,6 +217,48 @@ impl<T: Table> DatabaseRo<T> for HeedTableRw<'_, '_, T> {
{ {
get_range::<T, Range>(&self.db, self.tx_rw, range) get_range::<T, Range>(&self.db, self.tx_rw, range)
} }
#[inline]
fn iter(
&self,
) -> Result<impl Iterator<Item = Result<(T::Key, T::Value), RuntimeError>> + '_, RuntimeError>
{
iter::<T>(&self.db, self.tx_rw)
}
#[inline]
fn keys(
&self,
) -> Result<impl Iterator<Item = Result<T::Key, RuntimeError>> + '_, RuntimeError> {
keys::<T>(&self.db, self.tx_rw)
}
#[inline]
fn values(
&self,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + '_, RuntimeError> {
values::<T>(&self.db, self.tx_rw)
}
#[inline]
fn len(&self) -> Result<u64, RuntimeError> {
len::<T>(&self.db, self.tx_rw)
}
#[inline]
fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> {
first::<T>(&self.db, self.tx_rw)
}
#[inline]
fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> {
last::<T>(&self.db, self.tx_rw)
}
#[inline]
fn is_empty(&self) -> Result<bool, RuntimeError> {
is_empty::<T>(&self.db, self.tx_rw)
}
} }
impl<T: Table> DatabaseRw<T> for HeedTableRw<'_, '_, T> { impl<T: Table> DatabaseRw<T> for HeedTableRw<'_, '_, T> {
@ -125,6 +272,53 @@ impl<T: Table> DatabaseRw<T> for HeedTableRw<'_, '_, T> {
self.db.delete(self.tx_rw, key)?; self.db.delete(self.tx_rw, key)?;
Ok(()) 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 //---------------------------------------------------------------------------------------------------- Tests

View file

@ -304,6 +304,17 @@ where
tx_rw, tx_rw,
}) })
} }
#[inline]
fn clear_db<T: Table>(&self, tx_rw: &mut heed::RwTxn<'env>) -> Result<(), RuntimeError> {
// Open the table first...
let db: HeedDb<T::Key, T::Value> = self
.open_database(tx_rw, Some(T::NAME))?
.expect(PANIC_MSG_MISSING_TABLE);
// ...then clear it.
Ok(db.clear(tx_rw)?)
}
} }
//---------------------------------------------------------------------------------------------------- Tests //---------------------------------------------------------------------------------------------------- Tests

View file

@ -24,7 +24,7 @@ use crate::{
// call the functions since the database is held by value, so // call the functions since the database is held by value, so
// just use these generic functions that both can call instead. // just use these generic functions that both can call instead.
/// Shared generic `get()` between `RedbTableR{o,w}`. /// Shared [`DatabaseRo::get()`].
#[inline] #[inline]
fn get<T: Table + 'static>( fn get<T: Table + 'static>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>, db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
@ -33,7 +33,7 @@ fn get<T: Table + 'static>(
Ok(db.get(key)?.ok_or(RuntimeError::KeyNotFound)?.value()) Ok(db.get(key)?.ok_or(RuntimeError::KeyNotFound)?.value())
} }
/// Shared generic `get_range()` between `RedbTableR{o,w}`. /// Shared [`DatabaseRo::get_range()`].
#[inline] #[inline]
fn get_range<'a, T: Table, Range>( fn get_range<'a, T: Table, Range>(
db: &'a impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>, db: &'a impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
@ -43,11 +43,78 @@ where
Range: RangeBounds<T::Key> + 'a, Range: RangeBounds<T::Key> + 'a,
{ {
Ok(db.range(range)?.map(|result| { Ok(db.range(range)?.map(|result| {
let (_key, value_guard) = result?; let (_key, value) = result?;
Ok(value_guard.value()) Ok(value.value())
})) }))
} }
/// Shared [`DatabaseRo::iter()`].
#[inline]
fn iter<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> Result<impl Iterator<Item = Result<(T::Key, T::Value), RuntimeError>> + '_, RuntimeError> {
Ok(db.iter()?.map(|result| {
let (key, value) = result?;
Ok((key.value(), value.value()))
}))
}
/// Shared [`DatabaseRo::iter()`].
#[inline]
fn keys<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> Result<impl Iterator<Item = Result<T::Key, RuntimeError>> + '_, RuntimeError> {
Ok(db.iter()?.map(|result| {
let (key, _value) = result?;
Ok(key.value())
}))
}
/// Shared [`DatabaseRo::values()`].
#[inline]
fn values<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + '_, RuntimeError> {
Ok(db.iter()?.map(|result| {
let (_key, value) = result?;
Ok(value.value())
}))
}
/// Shared [`DatabaseRo::len()`].
#[inline]
fn len<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> Result<u64, RuntimeError> {
Ok(db.len()?)
}
/// Shared [`DatabaseRo::first()`].
#[inline]
fn first<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> 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<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> 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<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> Result<bool, RuntimeError> {
Ok(db.is_empty()?)
}
//---------------------------------------------------------------------------------------------------- DatabaseRo //---------------------------------------------------------------------------------------------------- DatabaseRo
impl<T: Table + 'static> DatabaseRo<T> for RedbTableRo<T::Key, T::Value> { impl<T: Table + 'static> DatabaseRo<T> for RedbTableRo<T::Key, T::Value> {
#[inline] #[inline]
@ -65,6 +132,48 @@ impl<T: Table + 'static> DatabaseRo<T> for RedbTableRo<T::Key, T::Value> {
{ {
get_range::<T, Range>(self, range) get_range::<T, Range>(self, range)
} }
#[inline]
fn iter(
&self,
) -> Result<impl Iterator<Item = Result<(T::Key, T::Value), RuntimeError>> + '_, RuntimeError>
{
iter::<T>(self)
}
#[inline]
fn keys(
&self,
) -> Result<impl Iterator<Item = Result<T::Key, RuntimeError>> + '_, RuntimeError> {
keys::<T>(self)
}
#[inline]
fn values(
&self,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + '_, RuntimeError> {
values::<T>(self)
}
#[inline]
fn len(&self) -> Result<u64, RuntimeError> {
len::<T>(self)
}
#[inline]
fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> {
first::<T>(self)
}
#[inline]
fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> {
last::<T>(self)
}
#[inline]
fn is_empty(&self) -> Result<bool, RuntimeError> {
is_empty::<T>(self)
}
} }
//---------------------------------------------------------------------------------------------------- DatabaseRw //---------------------------------------------------------------------------------------------------- DatabaseRw
@ -84,23 +193,76 @@ impl<T: Table + 'static> DatabaseRo<T> for RedbTableRw<'_, T::Key, T::Value> {
{ {
get_range::<T, Range>(self, range) get_range::<T, Range>(self, range)
} }
#[inline]
fn iter(
&self,
) -> Result<impl Iterator<Item = Result<(T::Key, T::Value), RuntimeError>> + '_, RuntimeError>
{
iter::<T>(self)
}
#[inline]
fn keys(
&self,
) -> Result<impl Iterator<Item = Result<T::Key, RuntimeError>> + '_, RuntimeError> {
keys::<T>(self)
}
#[inline]
fn values(
&self,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + '_, RuntimeError> {
values::<T>(self)
}
#[inline]
fn len(&self) -> Result<u64, RuntimeError> {
len::<T>(self)
}
#[inline]
fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> {
first::<T>(self)
}
#[inline]
fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> {
last::<T>(self)
}
#[inline]
fn is_empty(&self) -> Result<bool, RuntimeError> {
is_empty::<T>(self)
}
} }
impl<T: Table + 'static> DatabaseRw<T> for RedbTableRw<'_, T::Key, T::Value> { impl<T: Table + 'static> DatabaseRw<T> for RedbTableRw<'_, T::Key, T::Value> {
// `redb` returns the value after `insert()/remove()` // `redb` returns the value after function calls so we end with Ok(()) instead.
// we end with Ok(()) instead.
#[inline] #[inline]
fn put(&mut self, key: &T::Key, value: &T::Value) -> Result<(), RuntimeError> { fn put(&mut self, key: &T::Key, value: &T::Value) -> Result<(), RuntimeError> {
self.insert(key, value)?; redb::Table::insert(self, key, value)?;
Ok(()) Ok(())
} }
#[inline] #[inline]
fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError> { fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError> {
self.remove(key)?; redb::Table::remove(self, key)?;
Ok(()) 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 //---------------------------------------------------------------------------------------------------- Tests

View file

@ -208,6 +208,32 @@ where
// <https://docs.rs/redb/latest/redb/struct.WriteTransaction.html#method.open_table> // <https://docs.rs/redb/latest/redb/struct.WriteTransaction.html#method.open_table>
Ok(tx_rw.open_table(table)?) Ok(tx_rw.open_table(table)?)
} }
#[inline]
fn clear_db<T: Table>(&self, tx_rw: &mut redb::WriteTransaction) -> Result<(), RuntimeError> {
let table: redb::TableDefinition<
'static,
StorableRedb<<T as Table>::Key>,
StorableRedb<<T as Table>::Value>,
> = redb::TableDefinition::new(<T as Table>::NAME);
// INVARIANT:
// This `delete_table()` will not run into this `TableAlreadyOpen` error:
// <https://docs.rs/redb/2.0.0/src/redb/transactions.rs.html#382>
// 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 //---------------------------------------------------------------------------------------------------- Tests

View file

@ -13,7 +13,11 @@
//! //!
//! `redb`, and it only must be enabled for it to be tested. //! `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 //---------------------------------------------------------------------------------------------------- Import
use std::borrow::{Borrow, Cow}; use std::borrow::{Borrow, Cow};
@ -180,6 +184,8 @@ fn db_read_write() {
output_flags: 0, output_flags: 0,
tx_idx: 2_353_487, 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 /// Assert 2 `Output`'s are equal, and that accessing
/// their fields don't result in an unaligned panic. /// 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); assert_eq!(output.tx_idx, VALUE.tx_idx);
} }
// Insert `0..100` keys. assert!(table.is_empty().unwrap());
// Insert keys.
let mut key = KEY; let mut key = KEY;
for i in 0..100 { for i in 0..N {
table.put(&key, &VALUE).unwrap(); table.put(&key, &VALUE).unwrap();
key.amount += 1; 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!(table.contains(&KEY).unwrap());
assert_same(value); 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. // Assert the whole range is there.
@ -214,16 +231,16 @@ fn db_read_write() {
i += 1; i += 1;
} }
assert_eq!(i, 100); assert_eq!(i, N);
} }
// `get_range()` tests. // `get_range()` tests.
let mut key = KEY; let mut key = KEY;
key.amount += 100; key.amount += N;
let range = KEY..key; let range = KEY..key;
// Assert count is correct. // 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. // Assert each returned value from the iterator is owned.
{ {
@ -236,16 +253,52 @@ fn db_read_write() {
// Assert each value is the same. // Assert each value is the same.
{ {
let mut iter = table.get_range(range).unwrap(); let mut iter = table.get_range(range).unwrap();
for _ in 0..100 { for _ in 0..N {
let value: Output = iter.next().unwrap().unwrap(); let value: Output = iter.next().unwrap().unwrap();
assert_same(value); assert_same(value);
} }
} }
// Assert deleting works. // Assert deleting works.
table.delete(&KEY).unwrap(); {
let value = table.get(&KEY); table.delete(&KEY).unwrap();
assert!(matches!(value, Err(RuntimeError::KeyNotFound))); 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::<Outputs>(&tx_ro).unwrap();
env_inner.clear_db::<Outputs>(&mut tx_rw).unwrap();
let table = env_inner.open_db_rw::<Outputs>(&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 //---------------------------------------------------------------------------------------------------- Table Tests
@ -253,11 +306,7 @@ fn db_read_write() {
/// ///
/// Each one of these tests: /// Each one of these tests:
/// - Opens a specific table /// - Opens a specific table
/// - Inserts a key + value /// - Essentially does the `db_read_write` test
/// - Retrieves the key + value
/// - Asserts it is the same
/// - Tests `get_range()`
/// - Tests `delete()`
macro_rules! test_tables { macro_rules! test_tables {
($( ($(
$table:ident, // Table type $table:ident, // Table type
@ -293,6 +342,9 @@ macro_rules! test_tables {
assert_eq(&value); assert_eq(&value);
} }
assert!(table.contains(&KEY).unwrap());
assert_eq!(table.len().unwrap(), 1);
// Assert `get_range()` works. // Assert `get_range()` works.
{ {
let range = KEY..; let range = KEY..;
@ -303,9 +355,26 @@ macro_rules! test_tables {
} }
// Assert deleting works. // Assert deleting works.
table.delete(&KEY).unwrap(); {
let value = table.get(&KEY); table.delete(&KEY).unwrap();
assert!(matches!(value, Err(RuntimeError::KeyNotFound))); 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);
}
} }
)*}}; )*}};
} }

View file

@ -29,7 +29,14 @@ pub trait DatabaseRo<T: Table> {
/// It will return other [`RuntimeError`]'s on things like IO errors as well. /// It will return other [`RuntimeError`]'s on things like IO errors as well.
fn get(&self, key: &T::Key) -> Result<T::Value, RuntimeError>; fn get(&self, key: &T::Key) -> Result<T::Value, RuntimeError>;
/// 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 /// Although the returned iterator itself is tied to the lifetime
/// of `&'a self`, the returned values from the iterator are _owned_. /// of `&'a self`, the returned values from the iterator are _owned_.
@ -39,12 +46,73 @@ pub trait DatabaseRo<T: Table> {
/// if a particular key in the `range` does not exist, /// if a particular key in the `range` does not exist,
/// [`RuntimeError::KeyNotFound`] wrapped in [`Err`] will be returned /// [`RuntimeError::KeyNotFound`] wrapped in [`Err`] will be returned
/// from the iterator. /// from the iterator.
#[allow(clippy::iter_not_returning_iterator)]
fn get_range<'a, Range>( fn get_range<'a, Range>(
&'a self, &'a self,
range: Range, range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError> ) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where where
Range: RangeBounds<T::Key> + 'a; Range: RangeBounds<T::Key> + 'a;
/// TODO
///
/// # Errors
/// TODO
#[allow(clippy::iter_not_returning_iterator)]
fn iter(
&self,
) -> Result<impl Iterator<Item = Result<(T::Key, T::Value), RuntimeError>> + '_, RuntimeError>;
/// TODO
///
/// # Errors
/// TODO
fn keys(&self)
-> Result<impl Iterator<Item = Result<T::Key, RuntimeError>> + '_, RuntimeError>;
/// TODO
///
/// # Errors
/// TODO
fn values(
&self,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + '_, RuntimeError>;
/// TODO
///
/// # Errors
/// TODO
fn contains(&self, key: &T::Key) -> Result<bool, RuntimeError> {
match self.get(key) {
Ok(_) => Ok(true),
Err(RuntimeError::KeyNotFound) => Ok(false),
Err(e) => Err(e),
}
}
/// TODO
///
/// # Errors
/// TODO
fn len(&self) -> Result<u64, RuntimeError>;
/// 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<bool, RuntimeError>;
} }
//---------------------------------------------------------------------------------------------------- DatabaseRw //---------------------------------------------------------------------------------------------------- DatabaseRw
@ -65,4 +133,16 @@ pub trait DatabaseRw<T: Table>: DatabaseRo<T> {
/// # Errors /// # Errors
/// This will return [`RuntimeError::KeyNotFound`] wrapped in [`Err`] if `key` does not exist. /// This will return [`RuntimeError::KeyNotFound`] wrapped in [`Err`] if `key` does not exist.
fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError>; 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>;
} }

View file

@ -196,6 +196,8 @@ where
/// ``` /// ```
/// ///
/// # Errors /// # Errors
/// This function errors upon internal database/IO errors.
///
/// As [`Table`] is `Sealed`, and all tables are created /// As [`Table`] is `Sealed`, and all tables are created
/// upon [`Env::open`], this function will never error because /// upon [`Env::open`], this function will never error because
/// a table doesn't exist. /// a table doesn't exist.
@ -210,8 +212,26 @@ where
/// passed as a generic to this function. /// passed as a generic to this function.
/// ///
/// # Errors /// # Errors
/// This function errors upon internal database/IO errors.
///
/// As [`Table`] is `Sealed`, and all tables are created /// As [`Table`] is `Sealed`, and all tables are created
/// upon [`Env::open`], this function will never error because /// upon [`Env::open`], this function will never error because
/// a table doesn't exist. /// a table doesn't exist.
fn open_db_rw<T: Table>(&self, tx_rw: &mut Rw) -> Result<impl DatabaseRw<T>, RuntimeError>; fn open_db_rw<T: Table>(&self, tx_rw: &mut Rw) -> Result<impl DatabaseRw<T>, 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<T: Table>(&self, tx_rw: &mut Rw) -> Result<(), RuntimeError>;
} }