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).
|
/// 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)*}};
|
)*}};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue