database: return owned T in Storable (#92)

* add `storable_slice.rs`

* storable: `from_bytes(&[u8]) -> Self`

* heed: return `T` in `Storable`, use `StorableSlice` in tests

* remove `value_guard.rs`

* redb: update `Storable` to return owned `T`

* database: return `T` directly, remove `'a` lifetime

* replace `[]` with `StorableSlice`

* tables: add lifetime to `tables!()` and generated structs

* backend: fix tests

* heed: update fn signatures

* storable: add `StorableVec`, remove `Storable::ALIGN`

* tables/types: remove slice lifetimes, use `StorableVec`

* backend: use `StorableVec`, fix tests

* types: fix tests to use owned returned value

* heed: fix `Storable` impl and tests

* storable: `StorableVec` docs + tests

* backend: assert values are detached from `get_range()` iterator

* database: add docs

* udpate docs, remove unneeded clippy `allow`

* replace `ToOwnedDebug` with `std::fmt::Debug`

* impl `StorableBytes`

* backend: add `StorableBytes` to tests

* readme: remove `to_owned_debug.rs` reference
This commit is contained in:
hinto-janai 2024-03-22 17:11:48 -04:00 committed by GitHub
parent 004bb153b4
commit 3656a1ada7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 278 additions and 439 deletions

1
Cargo.lock generated
View file

@ -592,6 +592,7 @@ name = "cuprate-database"
version = "0.0.0"
dependencies = [
"bytemuck",
"bytes",
"cfg-if",
"crossbeam",
"cuprate-helper",

View file

@ -9,14 +9,15 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/database"
keywords = ["cuprate", "database"]
[features]
# default = ["heed", "redb", "service"]
default = ["redb", "service"]
default = ["heed", "redb", "service"]
# default = ["redb", "service"]
heed = ["dep:heed"]
redb = ["dep:redb"]
service = ["dep:crossbeam", "dep:futures", "dep:tokio", "dep:tokio-util", "dep:tower", "dep:rayon"]
[dependencies]
bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
bytes = { workspace = true }
cfg-if = { workspace = true }
# FIXME:
# We only need the `thread` feature if `service` is enabled.

View file

@ -79,10 +79,8 @@ The top-level `src/` files.
| `storable.rs` | Data (de)serialization; `trait Storable`
| `table.rs` | Database table abstraction; `trait Table`
| `tables.rs` | All the table definitions used by `cuprate-database`
| `to_owned_debug.rs` | Borrowed/owned data abstraction; `trait ToOwnedDebug`
| `transaction.rs` | Database transaction abstraction; `trait TxR{o,w}`
| `types.rs` | Database table schema types
| `value_guard.rs` | Database value "guard" abstraction; `trait ValueGuard`
## `src/ops/`
This folder contains the `cupate_database::ops` module.

View file

@ -13,7 +13,6 @@ use crate::{
database::{DatabaseRo, DatabaseRw},
error::RuntimeError,
table::Table,
value_guard::ValueGuard,
};
//---------------------------------------------------------------------------------------------------- Heed Database Wrappers
@ -56,14 +55,12 @@ pub(super) struct HeedTableRw<'env, 'tx, T: Table> {
/// Shared generic `get()` between `HeedTableR{o,w}`.
#[inline]
fn get<'a, T: Table>(
db: &'_ HeedDb<T::Key, T::Value>,
tx_ro: &'a heed::RoTxn<'_>,
fn get<T: Table>(
db: &HeedDb<T::Key, T::Value>,
tx_ro: &heed::RoTxn<'_>,
key: &T::Key,
) -> Result<impl ValueGuard<T::Value> + 'a, RuntimeError> {
db.get(tx_ro, key)?
.map(Cow::Borrowed)
.ok_or(RuntimeError::KeyNotFound)
) -> Result<T::Value, RuntimeError> {
db.get(tx_ro, key)?.ok_or(RuntimeError::KeyNotFound)
}
/// Shared generic `get_range()` between `HeedTableR{o,w}`.
@ -71,29 +68,26 @@ fn get<'a, T: Table>(
fn get_range<'a, T: Table, Range>(
db: &'a HeedDb<T::Key, T::Value>,
tx_ro: &'a heed::RoTxn<'_>,
range: &'a Range,
) -> Result<impl Iterator<Item = Result<impl ValueGuard<T::Value> + 'a, RuntimeError>>, RuntimeError>
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
Ok(db.range(tx_ro, range)?.map(|res| Ok(Cow::Borrowed(res?.1))))
Ok(db.range(tx_ro, &range)?.map(|res| Ok(res?.1)))
}
//---------------------------------------------------------------------------------------------------- DatabaseRo Impl
impl<'tx, T: Table> DatabaseRo<'tx, T> for HeedTableRo<'tx, T> {
#[inline]
fn get<'a>(&'a self, key: &'a T::Key) -> Result<impl ValueGuard<T::Value> + 'a, RuntimeError> {
fn get(&self, key: &T::Key) -> Result<T::Value, RuntimeError> {
get::<T>(&self.db, self.tx_ro, key)
}
#[inline]
fn get_range<'a, Range>(
&'a self,
range: &'a Range,
) -> Result<
impl Iterator<Item = Result<impl ValueGuard<T::Value> + 'a, RuntimeError>>,
RuntimeError,
>
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
@ -104,18 +98,15 @@ impl<'tx, T: Table> DatabaseRo<'tx, T> for HeedTableRo<'tx, T> {
//---------------------------------------------------------------------------------------------------- DatabaseRw Impl
impl<'tx, T: Table> DatabaseRo<'tx, T> for HeedTableRw<'_, 'tx, T> {
#[inline]
fn get<'a>(&'a self, key: &'a T::Key) -> Result<impl ValueGuard<T::Value> + 'a, RuntimeError> {
fn get(&self, key: &T::Key) -> Result<T::Value, RuntimeError> {
get::<T>(&self.db, self.tx_rw, key)
}
#[inline]
fn get_range<'a, Range>(
&'a self,
range: &'a Range,
) -> Result<
impl Iterator<Item = Result<impl ValueGuard<T::Value> + 'a, RuntimeError>>,
RuntimeError,
>
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{

View file

@ -5,7 +5,7 @@ use std::{borrow::Cow, fmt::Debug, marker::PhantomData};
use heed::{types::Bytes, BoxedError, BytesDecode, BytesEncode, Database};
use crate::storable::Storable;
use crate::{storable::Storable, storable::StorableVec};
//---------------------------------------------------------------------------------------------------- StorableHeed
/// The glue struct that implements `heed`'s (de)serialization
@ -19,9 +19,9 @@ where
//---------------------------------------------------------------------------------------------------- BytesDecode
impl<'a, T> BytesDecode<'a> for StorableHeed<T>
where
T: Storable + ?Sized + 'a,
T: Storable + 'static,
{
type DItem = &'a T;
type DItem = T;
#[inline]
/// This function is infallible (will always return `Ok`).
@ -47,9 +47,8 @@ where
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
use std::fmt::Debug;
use super::*;
use crate::{StorableBytes, StorableVec};
// Each `#[test]` function has a `test()` to:
// - log
@ -79,7 +78,8 @@ mod test {
test::<i16>(&-2, &[254, 255]);
test::<i32>(&-3, &[253, 255, 255, 255]);
test::<i64>(&-4, &[252, 255, 255, 255, 255, 255, 255, 255]);
test::<[u8]>(&[1, 2], &[1, 2]);
test::<StorableVec<u8>>(&StorableVec(vec![1, 2]), &[1, 2]);
test::<StorableBytes>(&StorableBytes(bytes::Bytes::from_static(&[1, 2])), &[1, 2]);
test::<[u8; 0]>(&[], &[]);
test::<[u8; 1]>(&[255], &[255]);
test::<[u8; 2]>(&[111, 0], &[111, 0]);
@ -91,12 +91,12 @@ mod test {
fn bytes_decode() {
fn test<T>(bytes: &[u8], expected: &T)
where
T: Storable + ?Sized + PartialEq + ToOwned + Debug,
T: Storable + PartialEq + ToOwned + Debug + 'static,
T::Owned: Debug,
{
println!("bytes: {bytes:?}, expected: {expected:?}");
assert_eq!(
<StorableHeed::<T> as BytesDecode>::bytes_decode(bytes).unwrap(),
&<StorableHeed::<T> as BytesDecode>::bytes_decode(bytes).unwrap(),
expected
);
}
@ -110,7 +110,8 @@ mod test {
test::<i16>([254, 255].as_slice(), &-2);
test::<i32>([253, 255, 255, 255].as_slice(), &-3);
test::<i64>([252, 255, 255, 255, 255, 255, 255, 255].as_slice(), &-4);
test::<[u8]>([1, 2].as_slice(), &[1, 2]);
test::<StorableVec<u8>>(&[1, 2], &StorableVec(vec![1, 2]));
test::<StorableBytes>(&[1, 2], &StorableBytes(bytes::Bytes::from_static(&[1, 2])));
test::<[u8; 0]>([].as_slice(), &[]);
test::<[u8; 1]>([255].as_slice(), &[255]);
test::<[u8; 2]>([111, 0].as_slice(), &[111, 0]);

View file

@ -17,8 +17,6 @@ use crate::{
error::RuntimeError,
storable::Storable,
table::Table,
value_guard::ValueGuard,
ToOwnedDebug,
};
//---------------------------------------------------------------------------------------------------- Shared functions
@ -28,108 +26,40 @@ use crate::{
/// Shared generic `get()` between `RedbTableR{o,w}`.
#[inline]
fn get<'a, T: Table + 'static>(
db: &'a impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
key: &'a T::Key,
) -> Result<impl ValueGuard<T::Value> + 'a, RuntimeError> {
db.get(Cow::Borrowed(key))?.ok_or(RuntimeError::KeyNotFound)
fn get<T: Table + 'static>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
key: &T::Key,
) -> Result<T::Value, RuntimeError> {
Ok(db.get(key)?.ok_or(RuntimeError::KeyNotFound)?.value())
}
/// Shared generic `get_range()` between `RedbTableR{o,w}`.
#[inline]
fn get_range<'a, T: Table, Range>(
db: &'a impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
range: &'a Range,
) -> Result<
impl Iterator<Item = Result<redb::AccessGuard<'a, StorableRedb<T::Value>>, RuntimeError>> + 'a,
RuntimeError,
>
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
/// HACK: `redb` sees the database's key type as `Cow<'_, T::Key>`,
/// not `T::Key` directly like `heed` does. As such, it wants the
/// range to be over `Cow<'_, T::Key>`, not `T::Key` directly.
///
/// If `DatabaseRo` were to want `Cow<'_, T::Key>` as input in `get()`,
/// `get_range()`, it would complicate the API:
/// ```rust,ignore
/// // This would be needed...
/// let range = Cow::Owned(0)..Cow::Owned(1);
/// // ...instead of the more obvious
/// let range = 0..1;
/// ```
///
/// As such, `DatabaseRo` only wants `RangeBounds<T::Key>` and
/// we create a compatibility struct here, essentially converting
/// this functions input:
/// ```rust,ignore
/// RangeBound<T::Key>
/// ```
/// into `redb`'s desired:
/// ```rust,ignore
/// RangeBound<Cow<'_, T::Key>>
/// ```
struct CowRange<'a, K>
where
K: ToOwnedDebug,
{
/// The start bound of `Range`.
start_bound: Bound<Cow<'a, K>>,
/// The end bound of `Range`.
end_bound: Bound<Cow<'a, K>>,
}
/// This impl forwards our `T::Key` to be wrapped in a Cow.
impl<'a, K> RangeBounds<Cow<'a, K>> for CowRange<'a, K>
where
K: ToOwnedDebug,
{
fn start_bound(&self) -> Bound<&Cow<'a, K>> {
self.start_bound.as_ref()
}
fn end_bound(&self) -> Bound<&Cow<'a, K>> {
self.end_bound.as_ref()
}
}
let start_bound = match range.start_bound() {
Bound::Included(t) => Bound::Included(Cow::Borrowed(t)),
Bound::Excluded(t) => Bound::Excluded(Cow::Borrowed(t)),
Bound::Unbounded => Bound::Unbounded,
};
let end_bound = match range.end_bound() {
Bound::Included(t) => Bound::Included(Cow::Borrowed(t)),
Bound::Excluded(t) => Bound::Excluded(Cow::Borrowed(t)),
Bound::Unbounded => Bound::Unbounded,
};
let range = CowRange {
start_bound,
end_bound,
};
Ok(db.range(range)?.map(|result| {
let (_key, value_guard) = result?;
Ok(value_guard)
Ok(value_guard.value())
}))
}
//---------------------------------------------------------------------------------------------------- DatabaseRo
impl<'tx, T: Table + 'static> DatabaseRo<'tx, T> for RedbTableRo<'tx, T::Key, T::Value> {
#[inline]
fn get<'a>(&'a self, key: &'a T::Key) -> Result<impl ValueGuard<T::Value> + 'a, RuntimeError> {
fn get(&self, key: &T::Key) -> Result<T::Value, RuntimeError> {
get::<T>(self, key)
}
#[inline]
fn get_range<'a, Range>(
&'a self,
range: &'a Range,
) -> Result<
impl Iterator<Item = Result<impl ValueGuard<T::Value>, RuntimeError>> + 'a,
RuntimeError,
>
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
@ -140,18 +70,15 @@ impl<'tx, T: Table + 'static> DatabaseRo<'tx, T> for RedbTableRo<'tx, T::Key, T:
//---------------------------------------------------------------------------------------------------- DatabaseRw
impl<'tx, T: Table + 'static> DatabaseRo<'tx, T> for RedbTableRw<'_, 'tx, T::Key, T::Value> {
#[inline]
fn get<'a>(&'a self, key: &'a T::Key) -> Result<impl ValueGuard<T::Value> + 'a, RuntimeError> {
fn get(&self, key: &T::Key) -> Result<T::Value, RuntimeError> {
get::<T>(self, key)
}
#[inline]
fn get_range<'a, Range>(
&'a self,
range: &'a Range,
) -> Result<
impl Iterator<Item = Result<impl ValueGuard<T::Value>, RuntimeError>> + 'a,
RuntimeError,
>
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
@ -167,13 +94,13 @@ impl<'env, 'tx, T: Table + 'static> DatabaseRw<'env, 'tx, T>
#[inline]
fn put(&mut self, key: &T::Key, value: &T::Value) -> Result<(), RuntimeError> {
self.insert(Cow::Borrowed(key), Cow::Borrowed(value))?;
self.insert(key, value)?;
Ok(())
}
#[inline]
fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError> {
self.remove(Cow::Borrowed(key))?;
self.remove(key)?;
Ok(())
}
}

View file

@ -5,7 +5,7 @@ use std::{any::Any, borrow::Cow, cmp::Ordering, fmt::Debug, marker::PhantomData}
use redb::{RedbKey, RedbValue, TypeName};
use crate::{key::Key, storable::Storable, value_guard::ValueGuard};
use crate::{key::Key, storable::Storable};
//---------------------------------------------------------------------------------------------------- StorableRedb
/// The glue structs that implements `redb`'s (de)serialization
@ -15,27 +15,13 @@ use crate::{key::Key, storable::Storable, value_guard::ValueGuard};
#[derive(Debug)]
pub(super) struct StorableRedb<T>(PhantomData<T>)
where
T: Storable + ?Sized;
impl<T: Storable + ?Sized> ValueGuard<T> for redb::AccessGuard<'_, StorableRedb<T>> {
#[inline]
fn unguard(&self) -> Cow<'_, T> {
self.value()
}
}
impl<T: Storable + ?Sized> ValueGuard<T> for &redb::AccessGuard<'_, StorableRedb<T>> {
#[inline]
fn unguard(&self) -> Cow<'_, T> {
self.value()
}
}
T: Storable;
//---------------------------------------------------------------------------------------------------- RedbKey
// If `Key` is also implemented, this can act as a `RedbKey`.
impl<T> RedbKey for StorableRedb<T>
where
T: Key + ?Sized,
T: Key + 'static,
{
#[inline]
fn compare(left: &[u8], right: &[u8]) -> Ordering {
@ -46,9 +32,9 @@ where
//---------------------------------------------------------------------------------------------------- RedbValue
impl<T> RedbValue for StorableRedb<T>
where
T: Storable + ?Sized,
T: Storable + 'static,
{
type SelfType<'a> = Cow<'a, T> where Self: 'a;
type SelfType<'a> = T where Self: 'a;
type AsBytes<'a> = &'a [u8] where Self: 'a;
#[inline]
@ -57,18 +43,11 @@ where
}
#[inline]
fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'static>
where
Self: 'a,
{
// Use the bytes directly if possible...
if T::ALIGN == 1 {
Cow::Borrowed(<T as Storable>::from_bytes(data))
// ...else, make sure the bytes are aligned
// when casting by allocating a new buffer.
} else {
<T as Storable>::from_bytes_unaligned(data)
}
<T as Storable>::from_bytes(data)
}
#[inline]
@ -76,7 +55,7 @@ where
where
Self: 'a + 'b,
{
<T as Storable>::as_bytes(value.as_ref())
<T as Storable>::as_bytes(value)
}
#[inline]
@ -90,6 +69,7 @@ where
#[allow(clippy::needless_pass_by_value)]
mod test {
use super::*;
use crate::{StorableBytes, StorableVec};
// Each `#[test]` function has a `test()` to:
// - log
@ -101,13 +81,13 @@ mod test {
fn compare() {
fn test<T>(left: T, right: T, expected: Ordering)
where
T: Key,
T: Key + 'static,
{
println!("left: {left:?}, right: {right:?}, expected: {expected:?}");
assert_eq!(
<StorableRedb::<T> as RedbKey>::compare(
<StorableRedb::<T> as RedbValue>::as_bytes(&Cow::Borrowed(&left)),
<StorableRedb::<T> as RedbValue>::as_bytes(&Cow::Borrowed(&right))
<StorableRedb::<T> as RedbValue>::as_bytes(&left),
<StorableRedb::<T> as RedbValue>::as_bytes(&right)
),
expected
);
@ -124,7 +104,7 @@ mod test {
fn fixed_width() {
fn test<T>(expected: Option<usize>)
where
T: Storable + ?Sized,
T: Storable + 'static,
{
assert_eq!(<StorableRedb::<T> as RedbValue>::fixed_width(), expected);
}
@ -138,7 +118,8 @@ mod test {
test::<i16>(Some(2));
test::<i32>(Some(4));
test::<i64>(Some(8));
test::<[u8]>(None);
test::<StorableVec<u8>>(None);
test::<StorableBytes>(None);
test::<[u8; 0]>(Some(0));
test::<[u8; 1]>(Some(1));
test::<[u8; 2]>(Some(2));
@ -150,13 +131,10 @@ mod test {
fn as_bytes() {
fn test<T>(t: &T, expected: &[u8])
where
T: Storable + ?Sized,
T: Storable + 'static,
{
println!("t: {t:?}, expected: {expected:?}");
assert_eq!(
<StorableRedb::<T> as RedbValue>::as_bytes(&Cow::Borrowed(t)),
expected
);
assert_eq!(<StorableRedb::<T> as RedbValue>::as_bytes(t), expected);
}
test::<()>(&(), &[]);
@ -168,7 +146,8 @@ mod test {
test::<i16>(&-2, &[254, 255]);
test::<i32>(&-3, &[253, 255, 255, 255]);
test::<i64>(&-4, &[252, 255, 255, 255, 255, 255, 255, 255]);
test::<[u8]>(&[1, 2], &[1, 2]);
test::<StorableVec<u8>>(&StorableVec([1, 2].to_vec()), &[1, 2]);
test::<StorableBytes>(&StorableBytes(bytes::Bytes::from_static(&[1, 2])), &[1, 2]);
test::<[u8; 0]>(&[], &[]);
test::<[u8; 1]>(&[255], &[255]);
test::<[u8; 2]>(&[111, 0], &[111, 0]);
@ -180,12 +159,12 @@ mod test {
fn from_bytes() {
fn test<T>(bytes: &[u8], expected: &T)
where
T: Storable + PartialEq + ?Sized,
T: Storable + PartialEq + 'static,
{
println!("bytes: {bytes:?}, expected: {expected:?}");
assert_eq!(
<StorableRedb::<T> as RedbValue>::from_bytes(bytes),
Cow::Borrowed(expected)
&<StorableRedb::<T> as RedbValue>::from_bytes(bytes),
expected
);
}
@ -198,7 +177,8 @@ mod test {
test::<i16>([254, 255].as_slice(), &-2);
test::<i32>([253, 255, 255, 255].as_slice(), &-3);
test::<i64>([252, 255, 255, 255, 255, 255, 255, 255].as_slice(), &-4);
test::<[u8]>([1, 2].as_slice(), &[1, 2]);
test::<StorableVec<u8>>(&[1, 2], &StorableVec(vec![1, 2]));
test::<StorableBytes>(&[1, 2], &StorableBytes(bytes::Bytes::from_static(&[1, 2])));
test::<[u8; 0]>([].as_slice(), &[]);
test::<[u8; 1]>([255].as_slice(), &[255]);
test::<[u8; 2]>([111, 0].as_slice(), &[111, 0]);
@ -221,7 +201,8 @@ mod test {
<StorableRedb<i16> as RedbValue>::type_name(),
<StorableRedb<i32> as RedbValue>::type_name(),
<StorableRedb<i64> as RedbValue>::type_name(),
<StorableRedb<[u8]> as RedbValue>::type_name(),
<StorableRedb<StorableVec<u8>> as RedbValue>::type_name(),
<StorableRedb<StorableBytes> as RedbValue>::type_name(),
<StorableRedb<[u8; 0]> as RedbValue>::type_name(),
<StorableRedb<[u8; 1]> as RedbValue>::type_name(),
<StorableRedb<[u8; 2]> as RedbValue>::type_name(),

View file

@ -24,6 +24,7 @@ use crate::{
env::{Env, EnvInner},
error::{InitError, RuntimeError},
resize::ResizeAlgorithm,
storable::StorableVec,
table::Table,
tables::{
BlockBlobs, BlockHeights, BlockInfoV1s, BlockInfoV2s, BlockInfoV3s, KeyImages, NumOutputs,
@ -36,7 +37,6 @@ use crate::{
BlockInfoV2, BlockInfoV3, KeyImage, Output, PreRctOutputId, PrunableBlob, PrunableHash,
PrunedBlob, RctOutput, TxHash, TxId, UnlockTime,
},
value_guard::ValueGuard,
ConcreteEnv,
};
@ -181,10 +181,10 @@ fn db_read_write() {
tx_idx: 2_353_487,
};
/// Assert a passed `Output` is equal to the const value.
fn assert_eq(output: &Output) {
assert_eq!(output, &VALUE);
// Make sure all field accesses are aligned.
/// Assert 2 `Output`'s are equal, and that accessing
/// their fields don't result in an unaligned panic.
fn assert_same(output: Output) {
assert_eq!(output, VALUE);
assert_eq!(output.key, VALUE.key);
assert_eq!(output.height, VALUE.height);
assert_eq!(output.output_flags, VALUE.output_flags);
@ -200,31 +200,47 @@ fn db_read_write() {
// Assert the 1st key is there.
{
let guard = table.get(&KEY).unwrap();
let cow: Cow<'_, Output> = guard.unguard();
let value: &Output = cow.as_ref();
assert_eq(value);
let value: Output = table.get(&KEY).unwrap();
assert_same(value);
}
// Assert the whole range is there.
{
let range = table.get_range(&..).unwrap();
let range = table.get_range(..).unwrap();
let mut i = 0;
for result in range {
let guard = result.unwrap();
let cow: Cow<'_, Output> = guard.unguard();
let value: &Output = cow.as_ref();
assert_eq(value);
let value: Output = result.unwrap();
assert_same(value);
i += 1;
}
assert_eq!(i, 100);
}
// Assert `get_range()` works.
// `get_range()` tests.
let mut key = KEY;
key.amount += 100;
let range = KEY..key;
assert_eq!(100, table.get_range(&range).unwrap().count());
// Assert count is correct.
assert_eq!(100, table.get_range(range.clone()).unwrap().count());
// Assert each returned value from the iterator is owned.
{
let mut iter = table.get_range(range.clone()).unwrap();
let value: Output = iter.next().unwrap().unwrap(); // 1. take value out
drop(iter); // 2. drop the `impl Iterator + 'a`
assert_same(value); // 3. assert even without the iterator, the value is alive
}
// Assert each value is the same.
{
let mut iter = table.get_range(range).unwrap();
for _ in 0..100 {
let value: Output = iter.next().unwrap().unwrap();
assert_same(value);
}
}
// Assert deleting works.
table.delete(&KEY).unwrap();
@ -261,33 +277,29 @@ macro_rules! test_tables {
/// The expected key.
const KEY: $key_type = $key;
/// The expected value.
const VALUE: &$value_type = &$value;
// The expected value.
let value: $value_type = $value;
/// Assert a passed value is equal to the const value.
fn assert_eq(value: &$value_type) {
assert_eq!(value, VALUE);
}
// Assert a passed value is equal to the const value.
let assert_eq = |v: &$value_type| {
assert_eq!(v, &value);
};
// Insert the key.
table.put(&KEY, VALUE).unwrap();
table.put(&KEY, &value).unwrap();
// Assert key is there.
{
let guard = table.get(&KEY).unwrap();
let cow: Cow<'_, $value_type> = guard.unguard();
let value: &$value_type = cow.as_ref();
assert_eq(value);
let value: $value_type = table.get(&KEY).unwrap();
assert_eq(&value);
}
// Assert `get_range()` works.
{
let range = KEY..;
assert_eq!(1, table.get_range(&range).unwrap().count());
let mut iter = table.get_range(&range).unwrap();
let guard = iter.next().unwrap().unwrap();
let cow = guard.unguard();
let value = cow.as_ref();
assert_eq(value);
assert_eq!(1, table.get_range(range.clone()).unwrap().count());
let mut iter = table.get_range(range).unwrap();
let value = iter.next().unwrap().unwrap();
assert_eq(&value);
}
// Assert deleting works.
@ -303,7 +315,7 @@ macro_rules! test_tables {
test_tables! {
BlockBlobs, // Table type
BlockHeight => BlockBlob, // Key type => Value type
123 => [1,2,3,4,5,6,7,8].as_slice(), // Actual key => Actual value
123 => StorableVec(vec![1,2,3,4,5,6,7,8]), // Actual key => Actual value
BlockHeights,
BlockHash => BlockHeight,
@ -377,11 +389,11 @@ test_tables! {
PrunedTxBlobs,
TxId => PrunedBlob,
123 => [1,2,3,4,5,6,7,8].as_slice(),
123 => StorableVec(vec![1,2,3,4,5,6,7,8]),
PrunableTxBlobs,
TxId => PrunableBlob,
123 => [1,2,3,4,5,6,7,8].as_slice(),
123 => StorableVec(vec![1,2,3,4,5,6,7,8]),
PrunableHashes,
TxId => PrunableHash,

View file

@ -11,7 +11,6 @@ use crate::{
error::RuntimeError,
table::Table,
transaction::{TxRo, TxRw},
value_guard::ValueGuard,
};
//---------------------------------------------------------------------------------------------------- DatabaseRo
@ -22,19 +21,18 @@ use crate::{
pub trait DatabaseRo<'tx, T: Table> {
/// Get the value corresponding to a key.
///
/// This returns a guard to the value, not the value itself.
/// See [`ValueGuard`] for more info.
/// The returned value is _owned_.
///
/// # Errors
/// This will return [`RuntimeError::KeyNotFound`] wrapped in [`Err`] if `key` does not exist.
///
/// It will return other [`RuntimeError`]'s on things like IO errors as well.
fn get<'a>(&'a self, key: &'a T::Key) -> Result<impl ValueGuard<T::Value> + 'a, RuntimeError>;
fn get(&self, key: &T::Key) -> Result<T::Value, RuntimeError>;
/// Get an iterator of values corresponding to a range of keys.
///
/// This returns guards to the values, not the values themselves.
/// See [`ValueGuard`] for more info.
/// Although the returned iterator itself is tied to the lifetime
/// of `&'a self`, the returned values from the iterator are _owned_.
///
/// # Errors
/// Each key in the `range` has the potential to error, for example,
@ -43,11 +41,8 @@ pub trait DatabaseRo<'tx, T: Table> {
/// from the iterator.
fn get_range<'a, Range>(
&'a self,
range: &'a Range,
) -> Result<
impl Iterator<Item = Result<impl ValueGuard<T::Value>, RuntimeError>> + 'a,
RuntimeError,
>
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a;
}

View file

@ -5,10 +5,7 @@ use std::{cmp::Ordering, fmt::Debug};
use bytemuck::Pod;
use crate::{
storable::{self, Storable},
ToOwnedDebug,
};
use crate::storable::{self, Storable};
//---------------------------------------------------------------------------------------------------- Table
/// Database [`Table`](crate::table::Table) key metadata.

View file

@ -129,7 +129,6 @@
redundant_semicolons,
unused_allocation,
coherence_leak_check,
single_use_lifetimes,
while_true,
clippy::missing_docs_in_private_items,
@ -142,6 +141,7 @@
keyword_idents,
non_ascii_idents,
variant_size_differences,
single_use_lifetimes,
// Probably can be put into `#[deny]`.
future_incompatible,
@ -240,7 +240,7 @@ pub use key::Key;
mod macros;
mod storable;
pub use storable::Storable;
pub use storable::{Storable, StorableBytes, StorableVec};
pub mod ops;
@ -254,12 +254,6 @@ pub mod types;
mod transaction;
pub use transaction::{TxRo, TxRw};
mod to_owned_debug;
pub use to_owned_debug::ToOwnedDebug;
mod value_guard;
pub use value_guard::ValueGuard;
//---------------------------------------------------------------------------------------------------- Feature-gated
#[cfg(feature = "service")]
pub mod service;

View file

@ -2,16 +2,15 @@
//---------------------------------------------------------------------------------------------------- Import
use std::{
borrow::Cow,
borrow::{Borrow, Cow},
char::ToLowercase,
fmt::Debug,
io::{Read, Write},
sync::Arc,
};
use bytemuck::Pod;
use crate::ToOwnedDebug;
use bytemuck::{Pod, Zeroable};
use bytes::Bytes;
//---------------------------------------------------------------------------------------------------- Storable
/// A type that can be stored in the database.
@ -35,6 +34,8 @@ use crate::ToOwnedDebug;
/// - All types in [`tables`](crate::tables)
/// - Slices, e.g, `[T] where T: Storable`
///
/// See [`StorableVec`] for storing slices of `T: Storable`.
///
/// ```rust
/// # use cuprate_database::*;
/// # use std::borrow::*;
@ -45,7 +46,7 @@ use crate::ToOwnedDebug;
/// assert_eq!(into, &[0; 8]);
///
/// // From bytes.
/// let from: u64 = *Storable::from_bytes(&into);
/// let from: u64 = Storable::from_bytes(&into);
/// assert_eq!(from, number);
/// ```
///
@ -62,36 +63,7 @@ use crate::ToOwnedDebug;
///
/// Most likely, the bytes are little-endian, however
/// that cannot be relied upon when using this trait.
pub trait Storable: ToOwnedDebug {
/// What is the alignment of `Self`?
///
/// For `[T]` types, this is set to the alignment of `T`.
///
/// This is used to prevent copying when unneeded, e.g.
/// `[u8] -> [u8]` does not need to account for unaligned bytes,
/// since no cast needs to occur.
///
/// # Examples
/// ```rust
/// # use cuprate_database::Storable;
/// assert_eq!(<()>::ALIGN, 1);
/// assert_eq!(u8::ALIGN, 1);
/// assert_eq!(u16::ALIGN, 2);
/// assert_eq!(u32::ALIGN, 4);
/// assert_eq!(u64::ALIGN, 8);
/// assert_eq!(i8::ALIGN, 1);
/// assert_eq!(i16::ALIGN, 2);
/// assert_eq!(i32::ALIGN, 4);
/// assert_eq!(i64::ALIGN, 8);
/// assert_eq!(<[u8]>::ALIGN, 1);
/// assert_eq!(<[u64]>::ALIGN, 8);
/// assert_eq!(<[u8; 0]>::ALIGN, 1);
/// assert_eq!(<[u8; 1]>::ALIGN, 1);
/// assert_eq!(<[u8; 2]>::ALIGN, 1);
/// assert_eq!(<[u64; 2]>::ALIGN, 8);
/// ```
const ALIGN: usize;
pub trait Storable: Debug {
/// Is this type fixed width in byte length?
///
/// I.e., when converting `Self` to bytes, is it
@ -113,7 +85,7 @@ pub trait Storable: ToOwnedDebug {
///
/// # Examples
/// ```rust
/// # use cuprate_database::Storable;
/// # use cuprate_database::*;
/// assert_eq!(<()>::BYTE_LENGTH, Some(0));
/// assert_eq!(u8::BYTE_LENGTH, Some(1));
/// assert_eq!(u16::BYTE_LENGTH, Some(2));
@ -123,46 +95,29 @@ pub trait Storable: ToOwnedDebug {
/// assert_eq!(i16::BYTE_LENGTH, Some(2));
/// assert_eq!(i32::BYTE_LENGTH, Some(4));
/// assert_eq!(i64::BYTE_LENGTH, Some(8));
/// assert_eq!(<[u8]>::BYTE_LENGTH, None);
/// assert_eq!(<[u8; 0]>::BYTE_LENGTH, Some(0));
/// assert_eq!(<[u8; 1]>::BYTE_LENGTH, Some(1));
/// assert_eq!(<[u8; 2]>::BYTE_LENGTH, Some(2));
/// assert_eq!(<[u8; 3]>::BYTE_LENGTH, Some(3));
/// assert_eq!(StorableVec::<u8>::BYTE_LENGTH, None);
/// assert_eq!(StorableVec::<u64>::BYTE_LENGTH, None);
/// ```
const BYTE_LENGTH: Option<usize>;
/// Return `self` in byte form.
fn as_bytes(&self) -> &[u8];
/// Create a borrowed [`Self`] from bytes.
///
/// # Invariant
/// `bytes` must be perfectly aligned for `Self`
/// or else this function may cause UB.
///
/// This function _may_ panic if `bytes` isn't aligned.
/// Create an owned [`Self`] from bytes.
///
/// # Blanket implementation
/// The blanket implementation that covers all types used
/// by `cuprate_database` will simply cast `bytes` into `Self`,
/// with no copying.
fn from_bytes(bytes: &[u8]) -> &Self;
/// Create a [`Self`] from potentially unaligned bytes.
/// by `cuprate_database` will simply bitwise copy `bytes`
/// into `Self`.
///
/// # Blanket implementation
/// The blanket implementation that covers all types used
/// by `cuprate_database` will **always** allocate a new buffer
/// or create a new `Self`.
fn from_bytes_unaligned(bytes: &[u8]) -> Cow<'_, Self>;
/// The bytes do not have be correctly aligned.
fn from_bytes(bytes: &[u8]) -> Self;
}
//---------------------------------------------------------------------------------------------------- Impl
impl<T> Storable for T
where
Self: Pod + ToOwnedDebug<OwnedDebug = T>,
Self: Pod + Debug,
{
const ALIGN: usize = std::mem::align_of::<T>();
const BYTE_LENGTH: Option<usize> = Some(std::mem::size_of::<T>());
#[inline]
@ -171,37 +126,132 @@ where
}
#[inline]
fn from_bytes(bytes: &[u8]) -> &T {
bytemuck::from_bytes(bytes)
}
#[inline]
fn from_bytes_unaligned(bytes: &[u8]) -> Cow<'static, Self> {
Cow::Owned(bytemuck::pod_read_unaligned(bytes))
fn from_bytes(bytes: &[u8]) -> T {
bytemuck::pod_read_unaligned(bytes)
}
}
impl<T> Storable for [T]
//---------------------------------------------------------------------------------------------------- StorableVec
/// A [`Storable`] vector of `T: Storable`.
///
/// This is a wrapper around `Vec<T> where T: Storable`.
///
/// Slice types are owned both:
/// - when returned from the database
/// - in `put()`
///
/// This is needed as `impl Storable for Vec<T>` runs into impl conflicts.
///
/// ```rust
/// # use cuprate_database::*;
/// //---------------------------------------------------- u8
/// let vec: StorableVec<u8> = StorableVec(vec![0,1]);
///
/// // Into bytes.
/// let into = Storable::as_bytes(&vec);
/// assert_eq!(into, &[0,1]);
///
/// // From bytes.
/// let from: StorableVec<u8> = Storable::from_bytes(&into);
/// assert_eq!(from, vec);
///
/// //---------------------------------------------------- u64
/// let vec: StorableVec<u64> = StorableVec(vec![0,1]);
///
/// // Into bytes.
/// let into = Storable::as_bytes(&vec);
/// assert_eq!(into, &[0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0]);
///
/// // From bytes.
/// let from: StorableVec<u64> = Storable::from_bytes(&into);
/// assert_eq!(from, vec);
/// ```
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, bytemuck::TransparentWrapper)]
#[repr(transparent)]
pub struct StorableVec<T>(pub Vec<T>);
impl<T> Storable for StorableVec<T>
where
T: Pod + ToOwnedDebug<OwnedDebug = T>,
Self: ToOwnedDebug<OwnedDebug = Vec<T>>,
T: Pod + Debug,
{
const ALIGN: usize = std::mem::align_of::<T>();
const BYTE_LENGTH: Option<usize> = None;
/// Casts the inner `Vec<T>` directly as bytes.
#[inline]
fn as_bytes(&self) -> &[u8] {
bytemuck::must_cast_slice(&self.0)
}
/// This always allocates a new `Vec<T>`,
/// casting `bytes` into a vector of type `T`.
#[inline]
fn from_bytes(bytes: &[u8]) -> Self {
Self(bytemuck::pod_collect_to_vec(bytes))
}
}
impl<T> std::ops::Deref for StorableVec<T> {
type Target = [T];
#[inline]
fn deref(&self) -> &[T] {
&self.0
}
}
impl<T> Borrow<[T]> for StorableVec<T> {
#[inline]
fn borrow(&self) -> &[T] {
&self.0
}
}
//---------------------------------------------------------------------------------------------------- StorableBytes
/// A [`Storable`] version of [`Bytes`].
///
/// ```rust
/// # use cuprate_database::*;
/// # use bytes::Bytes;
/// let bytes: StorableBytes = StorableBytes(Bytes::from_static(&[0,1]));
///
/// // Into bytes.
/// let into = Storable::as_bytes(&bytes);
/// assert_eq!(into, &[0,1]);
///
/// // From bytes.
/// let from: StorableBytes = Storable::from_bytes(&into);
/// assert_eq!(from, bytes);
/// ```
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct StorableBytes(pub Bytes);
impl Storable for StorableBytes {
const BYTE_LENGTH: Option<usize> = None;
#[inline]
fn as_bytes(&self) -> &[u8] {
bytemuck::must_cast_slice(self)
&self.0
}
/// This always allocates a new `Bytes`.
#[inline]
fn from_bytes(bytes: &[u8]) -> &[T] {
bytemuck::cast_slice(bytes)
fn from_bytes(bytes: &[u8]) -> Self {
Self(Bytes::copy_from_slice(bytes))
}
}
impl std::ops::Deref for StorableBytes {
type Target = [u8];
#[inline]
fn from_bytes_unaligned(bytes: &[u8]) -> Cow<'static, Self> {
Cow::Owned(bytemuck::pod_collect_to_vec(bytes))
fn deref(&self) -> &[u8] {
&self.0
}
}
impl Borrow<[u8]> for StorableBytes {
#[inline]
fn borrow(&self) -> &[u8] {
&self.0
}
}
@ -220,7 +270,7 @@ mod test {
// A `Vec` of the numbers to test.
t: Vec<T>,
) where
T: Storable + Copy + PartialEq,
T: Storable + Debug + Copy + PartialEq,
{
for t in t {
let expected_bytes = to_le_bytes(t);
@ -229,7 +279,7 @@ mod test {
// (De)serialize.
let se: &[u8] = Storable::as_bytes(&t);
let de: &T = Storable::from_bytes(se);
let de = <T as Storable>::from_bytes(se);
println!("serialized: {se:?}, deserialized: {de:?}\n");
@ -238,7 +288,7 @@ mod test {
assert_eq!(se.len(), expected_bytes.len());
}
// Assert the data is the same.
assert_eq!(de, &t);
assert_eq!(de, t);
}
}

View file

@ -3,7 +3,7 @@
//---------------------------------------------------------------------------------------------------- Import
use std::fmt::Debug;
use crate::{key::Key, storable::Storable, to_owned_debug::ToOwnedDebug};
use crate::{key::Key, storable::Storable};
//---------------------------------------------------------------------------------------------------- Table
/// Database table metadata.
@ -19,10 +19,10 @@ pub trait Table: crate::tables::private::Sealed + 'static {
const NAME: &'static str;
/// Primary key type.
type Key: Key + ?Sized + 'static;
type Key: Key + 'static;
/// Value type.
type Value: Storable + ?Sized + 'static;
type Value: Storable + 'static;
}
//---------------------------------------------------------------------------------------------------- Tests

View file

@ -1,51 +0,0 @@
//! Borrowed/owned data abstraction; `trait ToOwnedDebug`.
//---------------------------------------------------------------------------------------------------- Import
use std::fmt::Debug;
use crate::{key::Key, storable::Storable};
//---------------------------------------------------------------------------------------------------- Table
/// `T: Debug` and `T::Owned: Debug`.
///
/// This trait simply combines [`Debug`] and [`ToOwned`]
/// such that the `Owned` version must also be [`Debug`].
///
/// An example is `[u8]` which is [`Debug`], and
/// its owned version `Vec<u8>` is also [`Debug`].
///
/// # Explanation (not needed for practical use)
/// This trait solely exists due to the `redb` backend
/// requiring [`Debug`] bounds on keys and values.
///
/// As we have `?Sized` types like `[u8]`, and due to `redb` requiring
/// allocation upon deserialization, we must make our values `ToOwned`.
///
/// However, this requires that the `Owned` version is also `Debug`.
/// Combined with:
/// - [`Table::Key`](crate::Table::Key)
/// - [`Table::Value`](crate::Table::Value)
/// - [`Key::Primary`]
///
/// this quickly permutates into many many many `where` bounds on
/// each function that touchs any data that must be deserialized.
///
/// This trait and the blanket impl it provides get applied all these types
/// automatically, which means we don't have to write these bounds everywhere.
pub trait ToOwnedDebug: Debug + ToOwned<Owned = Self::OwnedDebug> {
/// The owned version of [`Self`].
///
/// Should be equal to `<T as ToOwned>::Owned`.
type OwnedDebug: Debug;
}
// The blanket impl that covers all our types.
impl<O: Debug, T: ToOwned<Owned = O> + Debug + ?Sized> ToOwnedDebug for T {
type OwnedDebug = O;
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

View file

@ -37,7 +37,6 @@
*/
// actually i still don't trust you. no unsafe.
#![forbid(unsafe_code)] // if you remove this line i will steal your monero
#![allow(missing_docs)] // bytemuck auto-generates some non-documented structs
//---------------------------------------------------------------------------------------------------- Import
use bytemuck::{AnyBitPattern, NoUninit, Pod, Zeroable};
@ -45,6 +44,8 @@ use bytemuck::{AnyBitPattern, NoUninit, Pod, Zeroable};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::storable::StorableVec;
//---------------------------------------------------------------------------------------------------- Aliases
// TODO: document these, why they exist, and their purpose.
//
@ -58,10 +59,10 @@ pub type Amount = u64;
pub type AmountIndex = u64;
/// TODO
pub type AmountIndices = [AmountIndex];
pub type AmountIndices = StorableVec<AmountIndex>;
/// TODO
pub type BlockBlob = [u8];
pub type BlockBlob = StorableVec<u8>;
/// TODO
pub type BlockHash = [u8; 32];
@ -73,10 +74,10 @@ pub type BlockHeight = u64;
pub type KeyImage = [u8; 32];
/// TODO
pub type PrunedBlob = [u8];
pub type PrunedBlob = StorableVec<u8>;
/// TODO
pub type PrunableBlob = [u8];
pub type PrunableBlob = StorableVec<u8>;
/// TODO
pub type PrunableHash = [u8; 32];
@ -102,10 +103,8 @@ pub type UnlockTime = u64;
/// amount_index: 123,
/// };
/// let b = Storable::as_bytes(&a);
/// let c: &PreRctOutputId = Storable::from_bytes(b);
/// let c2: Cow<'_, PreRctOutputId> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// let c: PreRctOutputId = Storable::from_bytes(b);
/// assert_eq!(a, c);
/// ```
///
/// # Size & Alignment
@ -140,10 +139,8 @@ pub struct PreRctOutputId {
/// block_hash: [54; 32],
/// };
/// let b = Storable::as_bytes(&a);
/// let c: &BlockInfoV1 = Storable::from_bytes(b);
/// let c2: Cow<'_, BlockInfoV1> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// let c: BlockInfoV1 = Storable::from_bytes(b);
/// assert_eq!(a, c);
/// ```
///
/// # Size & Alignment
@ -185,10 +182,8 @@ pub struct BlockInfoV1 {
/// cumulative_rct_outs: 2389,
/// };
/// let b = Storable::as_bytes(&a);
/// let c: &BlockInfoV2 = Storable::from_bytes(b);
/// let c2: Cow<'_, BlockInfoV2> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// let c: BlockInfoV2 = Storable::from_bytes(b);
/// assert_eq!(a, c);
/// ```
///
/// # Size & Alignment
@ -237,10 +232,8 @@ pub struct BlockInfoV2 {
/// long_term_weight: 2389,
/// };
/// let b = Storable::as_bytes(&a);
/// let c: &BlockInfoV3 = Storable::from_bytes(b);
/// let c2: Cow<'_, BlockInfoV3> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// let c: BlockInfoV3 = Storable::from_bytes(b);
/// assert_eq!(a, c);
/// ```
///
/// # Size & Alignment
@ -287,10 +280,8 @@ pub struct BlockInfoV3 {
/// tx_idx: 3,
/// };
/// let b = Storable::as_bytes(&a);
/// let c: &Output = Storable::from_bytes(b);
/// let c2: Cow<'_, Output> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// let c: Output = Storable::from_bytes(b);
/// assert_eq!(a, c);
/// ```
///
/// # Size & Alignment
@ -329,10 +320,8 @@ pub struct Output {
/// commitment: [3; 32],
/// };
/// let b = Storable::as_bytes(&a);
/// let c: &RctOutput = Storable::from_bytes(b);
/// let c2: Cow<'_, RctOutput> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// let c: RctOutput = Storable::from_bytes(b);
/// assert_eq!(a, c);
/// ```
///
/// # Size & Alignment

View file

@ -1,47 +0,0 @@
//! Database table value "guard" abstraction; `trait ValueGuard`.
//---------------------------------------------------------------------------------------------------- Import
use std::borrow::{Borrow, Cow};
use crate::{table::Table, Storable, ToOwnedDebug};
//---------------------------------------------------------------------------------------------------- Table
/// A guard that allows you to access a value.
///
/// This trait acts as an object that must be kept alive,
/// and will give you access to a [`Table`]'s value.
///
/// # Explanation (not needed for practical use)
/// This trait solely exists due to the `redb` backend
/// not _directly_ returning the value, but a
/// [guard object](https://docs.rs/redb/1.5.0/redb/struct.AccessGuard.html)
/// that has a lifetime attached to the key.
/// It does not implement `Deref` or `Borrow` and such.
///
/// Also, due to `redb` requiring `Cow`, this object builds on that.
///
/// - `heed` will always be `Cow::Borrowed`
/// - `redb` will always be `Cow::Borrowed` for `[u8]`
/// or any type where `Storable::ALIGN == 1`
/// - `redb` will always be `Cow::Owned` for everything else
pub trait ValueGuard<T: ToOwnedDebug + ?Sized> {
/// Retrieve the data from the guard.
fn unguard(&self) -> Cow<'_, T>;
}
impl<T: ToOwnedDebug + ?Sized> ValueGuard<T> for Cow<'_, T> {
#[inline]
fn unguard(&self) -> Cow<'_, T> {
Cow::Borrowed(self.borrow())
}
}
// HACK:
// This is implemented for `redb::AccessGuard<'_>` in
// `src/backend/redb/storable.rs` due to struct privacy.
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}