mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-12-22 19:49:28 +00:00
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:
parent
ae0d9ff36a
commit
21697b8af5
7 changed files with 595 additions and 33 deletions
|
@ -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<T::Key, T::Value>,
|
||||
/// 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<T: Table>(
|
||||
db: &HeedDb<T::Key, T::Value>,
|
||||
|
@ -63,7 +63,7 @@ fn get<T: Table>(
|
|||
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<T::Key, T::Value>,
|
||||
|
@ -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<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
|
||||
impl<T: Table> DatabaseRo<T> for HeedTableRo<'_, T> {
|
||||
#[inline]
|
||||
|
@ -93,6 +156,48 @@ impl<T: Table> DatabaseRo<T> for HeedTableRo<'_, T> {
|
|||
{
|
||||
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
|
||||
|
@ -112,6 +217,48 @@ impl<T: Table> DatabaseRo<T> for HeedTableRw<'_, '_, T> {
|
|||
{
|
||||
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> {
|
||||
|
@ -125,6 +272,53 @@ impl<T: Table> DatabaseRw<T> 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
|
||||
|
|
|
@ -304,6 +304,17 @@ where
|
|||
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
|
||||
|
|
|
@ -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<T: Table + 'static>(
|
||||
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())
|
||||
}
|
||||
|
||||
/// 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<T::Key>, StorableRedb<T::Value>>,
|
||||
|
@ -43,11 +43,78 @@ where
|
|||
Range: RangeBounds<T::Key> + '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<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
|
||||
impl<T: Table + 'static> DatabaseRo<T> for RedbTableRo<T::Key, T::Value> {
|
||||
#[inline]
|
||||
|
@ -65,6 +132,48 @@ impl<T: Table + 'static> DatabaseRo<T> for RedbTableRo<T::Key, T::Value> {
|
|||
{
|
||||
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
|
||||
|
@ -84,23 +193,76 @@ impl<T: Table + 'static> DatabaseRo<T> for RedbTableRw<'_, T::Key, T::Value> {
|
|||
{
|
||||
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> {
|
||||
// `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
|
||||
|
|
|
@ -208,6 +208,32 @@ where
|
|||
// <https://docs.rs/redb/latest/redb/struct.WriteTransaction.html#method.open_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
|
||||
|
|
|
@ -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::<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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
)*}};
|
||||
}
|
||||
|
|
|
@ -29,7 +29,14 @@ pub trait DatabaseRo<T: Table> {
|
|||
/// It will return other [`RuntimeError`]'s on things like IO errors as well.
|
||||
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
|
||||
/// 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,
|
||||
/// [`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<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
|
||||
where
|
||||
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
|
||||
|
@ -65,4 +133,16 @@ pub trait DatabaseRw<T: Table>: DatabaseRo<T> {
|
|||
/// # 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>;
|
||||
}
|
||||
|
|
|
@ -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<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>;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue