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" version = "0.0.0"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"bytes",
"cfg-if", "cfg-if",
"crossbeam", "crossbeam",
"cuprate-helper", "cuprate-helper",

View file

@ -9,14 +9,15 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/database"
keywords = ["cuprate", "database"] keywords = ["cuprate", "database"]
[features] [features]
# default = ["heed", "redb", "service"] default = ["heed", "redb", "service"]
default = ["redb", "service"] # default = ["redb", "service"]
heed = ["dep:heed"] heed = ["dep:heed"]
redb = ["dep:redb"] redb = ["dep:redb"]
service = ["dep:crossbeam", "dep:futures", "dep:tokio", "dep:tokio-util", "dep:tower", "dep:rayon"] service = ["dep:crossbeam", "dep:futures", "dep:tokio", "dep:tokio-util", "dep:tower", "dep:rayon"]
[dependencies] [dependencies]
bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
bytes = { workspace = true }
cfg-if = { workspace = true } cfg-if = { workspace = true }
# FIXME: # FIXME:
# We only need the `thread` feature if `service` is enabled. # 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` | `storable.rs` | Data (de)serialization; `trait Storable`
| `table.rs` | Database table abstraction; `trait Table` | `table.rs` | Database table abstraction; `trait Table`
| `tables.rs` | All the table definitions used by `cuprate-database` | `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}` | `transaction.rs` | Database transaction abstraction; `trait TxR{o,w}`
| `types.rs` | Database table schema types | `types.rs` | Database table schema types
| `value_guard.rs` | Database value "guard" abstraction; `trait ValueGuard`
## `src/ops/` ## `src/ops/`
This folder contains the `cupate_database::ops` module. This folder contains the `cupate_database::ops` module.

View file

@ -13,7 +13,6 @@ use crate::{
database::{DatabaseRo, DatabaseRw}, database::{DatabaseRo, DatabaseRw},
error::RuntimeError, error::RuntimeError,
table::Table, table::Table,
value_guard::ValueGuard,
}; };
//---------------------------------------------------------------------------------------------------- Heed Database Wrappers //---------------------------------------------------------------------------------------------------- Heed Database Wrappers
@ -56,14 +55,12 @@ pub(super) struct HeedTableRw<'env, 'tx, T: Table> {
/// Shared generic `get()` between `HeedTableR{o,w}`. /// Shared generic `get()` between `HeedTableR{o,w}`.
#[inline] #[inline]
fn get<'a, T: Table>( fn get<T: Table>(
db: &'_ HeedDb<T::Key, T::Value>, db: &HeedDb<T::Key, T::Value>,
tx_ro: &'a heed::RoTxn<'_>, tx_ro: &heed::RoTxn<'_>,
key: &T::Key, key: &T::Key,
) -> Result<impl ValueGuard<T::Value> + 'a, RuntimeError> { ) -> Result<T::Value, RuntimeError> {
db.get(tx_ro, key)? db.get(tx_ro, key)?.ok_or(RuntimeError::KeyNotFound)
.map(Cow::Borrowed)
.ok_or(RuntimeError::KeyNotFound)
} }
/// Shared generic `get_range()` between `HeedTableR{o,w}`. /// Shared generic `get_range()` between `HeedTableR{o,w}`.
@ -71,29 +68,26 @@ fn get<'a, T: Table>(
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>,
tx_ro: &'a heed::RoTxn<'_>, tx_ro: &'a heed::RoTxn<'_>,
range: &'a Range, range: Range,
) -> Result<impl Iterator<Item = Result<impl ValueGuard<T::Value> + 'a, RuntimeError>>, RuntimeError> ) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where where
Range: RangeBounds<T::Key> + 'a, 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 //---------------------------------------------------------------------------------------------------- DatabaseRo Impl
impl<'tx, T: Table> DatabaseRo<'tx, T> for HeedTableRo<'tx, T> { impl<'tx, T: Table> DatabaseRo<'tx, T> for HeedTableRo<'tx, T> {
#[inline] #[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) get::<T>(&self.db, self.tx_ro, key)
} }
#[inline] #[inline]
fn get_range<'a, Range>( fn get_range<'a, Range>(
&'a self, &'a self,
range: &'a Range, range: Range,
) -> Result< ) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
impl Iterator<Item = Result<impl ValueGuard<T::Value> + 'a, RuntimeError>>,
RuntimeError,
>
where where
Range: RangeBounds<T::Key> + 'a, Range: RangeBounds<T::Key> + 'a,
{ {
@ -104,18 +98,15 @@ impl<'tx, T: Table> DatabaseRo<'tx, T> for HeedTableRo<'tx, T> {
//---------------------------------------------------------------------------------------------------- DatabaseRw Impl //---------------------------------------------------------------------------------------------------- DatabaseRw Impl
impl<'tx, T: Table> DatabaseRo<'tx, T> for HeedTableRw<'_, 'tx, T> { impl<'tx, T: Table> DatabaseRo<'tx, T> for HeedTableRw<'_, 'tx, T> {
#[inline] #[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) get::<T>(&self.db, self.tx_rw, key)
} }
#[inline] #[inline]
fn get_range<'a, Range>( fn get_range<'a, Range>(
&'a self, &'a self,
range: &'a Range, range: Range,
) -> Result< ) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
impl Iterator<Item = Result<impl ValueGuard<T::Value> + 'a, RuntimeError>>,
RuntimeError,
>
where where
Range: RangeBounds<T::Key> + 'a, 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 heed::{types::Bytes, BoxedError, BytesDecode, BytesEncode, Database};
use crate::storable::Storable; use crate::{storable::Storable, storable::StorableVec};
//---------------------------------------------------------------------------------------------------- StorableHeed //---------------------------------------------------------------------------------------------------- StorableHeed
/// The glue struct that implements `heed`'s (de)serialization /// The glue struct that implements `heed`'s (de)serialization
@ -19,9 +19,9 @@ where
//---------------------------------------------------------------------------------------------------- BytesDecode //---------------------------------------------------------------------------------------------------- BytesDecode
impl<'a, T> BytesDecode<'a> for StorableHeed<T> impl<'a, T> BytesDecode<'a> for StorableHeed<T>
where where
T: Storable + ?Sized + 'a, T: Storable + 'static,
{ {
type DItem = &'a T; type DItem = T;
#[inline] #[inline]
/// This function is infallible (will always return `Ok`). /// This function is infallible (will always return `Ok`).
@ -47,9 +47,8 @@ where
//---------------------------------------------------------------------------------------------------- Tests //---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::fmt::Debug;
use super::*; use super::*;
use crate::{StorableBytes, StorableVec};
// Each `#[test]` function has a `test()` to: // Each `#[test]` function has a `test()` to:
// - log // - log
@ -79,7 +78,8 @@ mod test {
test::<i16>(&-2, &[254, 255]); test::<i16>(&-2, &[254, 255]);
test::<i32>(&-3, &[253, 255, 255, 255]); test::<i32>(&-3, &[253, 255, 255, 255]);
test::<i64>(&-4, &[252, 255, 255, 255, 255, 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; 0]>(&[], &[]);
test::<[u8; 1]>(&[255], &[255]); test::<[u8; 1]>(&[255], &[255]);
test::<[u8; 2]>(&[111, 0], &[111, 0]); test::<[u8; 2]>(&[111, 0], &[111, 0]);
@ -91,12 +91,12 @@ mod test {
fn bytes_decode() { fn bytes_decode() {
fn test<T>(bytes: &[u8], expected: &T) fn test<T>(bytes: &[u8], expected: &T)
where where
T: Storable + ?Sized + PartialEq + ToOwned + Debug, T: Storable + PartialEq + ToOwned + Debug + 'static,
T::Owned: Debug, T::Owned: Debug,
{ {
println!("bytes: {bytes:?}, expected: {expected:?}"); println!("bytes: {bytes:?}, expected: {expected:?}");
assert_eq!( assert_eq!(
<StorableHeed::<T> as BytesDecode>::bytes_decode(bytes).unwrap(), &<StorableHeed::<T> as BytesDecode>::bytes_decode(bytes).unwrap(),
expected expected
); );
} }
@ -110,7 +110,8 @@ mod test {
test::<i16>([254, 255].as_slice(), &-2); test::<i16>([254, 255].as_slice(), &-2);
test::<i32>([253, 255, 255, 255].as_slice(), &-3); test::<i32>([253, 255, 255, 255].as_slice(), &-3);
test::<i64>([252, 255, 255, 255, 255, 255, 255, 255].as_slice(), &-4); 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; 0]>([].as_slice(), &[]);
test::<[u8; 1]>([255].as_slice(), &[255]); test::<[u8; 1]>([255].as_slice(), &[255]);
test::<[u8; 2]>([111, 0].as_slice(), &[111, 0]); test::<[u8; 2]>([111, 0].as_slice(), &[111, 0]);

View file

@ -17,8 +17,6 @@ use crate::{
error::RuntimeError, error::RuntimeError,
storable::Storable, storable::Storable,
table::Table, table::Table,
value_guard::ValueGuard,
ToOwnedDebug,
}; };
//---------------------------------------------------------------------------------------------------- Shared functions //---------------------------------------------------------------------------------------------------- Shared functions
@ -28,108 +26,40 @@ use crate::{
/// Shared generic `get()` between `RedbTableR{o,w}`. /// Shared generic `get()` between `RedbTableR{o,w}`.
#[inline] #[inline]
fn get<'a, T: Table + 'static>( fn get<T: Table + 'static>(
db: &'a impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>, db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
key: &'a T::Key, key: &T::Key,
) -> Result<impl ValueGuard<T::Value> + 'a, RuntimeError> { ) -> Result<T::Value, RuntimeError> {
db.get(Cow::Borrowed(key))?.ok_or(RuntimeError::KeyNotFound) Ok(db.get(key)?.ok_or(RuntimeError::KeyNotFound)?.value())
} }
/// Shared generic `get_range()` between `RedbTableR{o,w}`. /// Shared generic `get_range()` between `RedbTableR{o,w}`.
#[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>>,
range: &'a Range, range: Range,
) -> Result< ) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
impl Iterator<Item = Result<redb::AccessGuard<'a, StorableRedb<T::Value>>, RuntimeError>> + 'a,
RuntimeError,
>
where where
Range: RangeBounds<T::Key> + 'a, 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| { Ok(db.range(range)?.map(|result| {
let (_key, value_guard) = result?; let (_key, value_guard) = result?;
Ok(value_guard) Ok(value_guard.value())
})) }))
} }
//---------------------------------------------------------------------------------------------------- DatabaseRo //---------------------------------------------------------------------------------------------------- DatabaseRo
impl<'tx, T: Table + 'static> DatabaseRo<'tx, T> for RedbTableRo<'tx, T::Key, T::Value> { impl<'tx, T: Table + 'static> DatabaseRo<'tx, T> for RedbTableRo<'tx, T::Key, T::Value> {
#[inline] #[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) get::<T>(self, key)
} }
#[inline] #[inline]
fn get_range<'a, Range>( fn get_range<'a, Range>(
&'a self, &'a self,
range: &'a Range, range: Range,
) -> Result< ) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
impl Iterator<Item = Result<impl ValueGuard<T::Value>, RuntimeError>> + 'a,
RuntimeError,
>
where where
Range: RangeBounds<T::Key> + 'a, Range: RangeBounds<T::Key> + 'a,
{ {
@ -140,18 +70,15 @@ impl<'tx, T: Table + 'static> DatabaseRo<'tx, T> for RedbTableRo<'tx, T::Key, T:
//---------------------------------------------------------------------------------------------------- DatabaseRw //---------------------------------------------------------------------------------------------------- DatabaseRw
impl<'tx, T: Table + 'static> DatabaseRo<'tx, T> for RedbTableRw<'_, 'tx, T::Key, T::Value> { impl<'tx, T: Table + 'static> DatabaseRo<'tx, T> for RedbTableRw<'_, 'tx, T::Key, T::Value> {
#[inline] #[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) get::<T>(self, key)
} }
#[inline] #[inline]
fn get_range<'a, Range>( fn get_range<'a, Range>(
&'a self, &'a self,
range: &'a Range, range: Range,
) -> Result< ) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
impl Iterator<Item = Result<impl ValueGuard<T::Value>, RuntimeError>> + 'a,
RuntimeError,
>
where where
Range: RangeBounds<T::Key> + 'a, Range: RangeBounds<T::Key> + 'a,
{ {
@ -167,13 +94,13 @@ impl<'env, 'tx, T: Table + 'static> DatabaseRw<'env, 'tx, T>
#[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(Cow::Borrowed(key), Cow::Borrowed(value))?; self.insert(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(Cow::Borrowed(key))?; self.remove(key)?;
Ok(()) 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 redb::{RedbKey, RedbValue, TypeName};
use crate::{key::Key, storable::Storable, value_guard::ValueGuard}; use crate::{key::Key, storable::Storable};
//---------------------------------------------------------------------------------------------------- StorableRedb //---------------------------------------------------------------------------------------------------- StorableRedb
/// The glue structs that implements `redb`'s (de)serialization /// The glue structs that implements `redb`'s (de)serialization
@ -15,27 +15,13 @@ use crate::{key::Key, storable::Storable, value_guard::ValueGuard};
#[derive(Debug)] #[derive(Debug)]
pub(super) struct StorableRedb<T>(PhantomData<T>) pub(super) struct StorableRedb<T>(PhantomData<T>)
where where
T: Storable + ?Sized; T: Storable;
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()
}
}
//---------------------------------------------------------------------------------------------------- RedbKey //---------------------------------------------------------------------------------------------------- RedbKey
// If `Key` is also implemented, this can act as a `RedbKey`. // If `Key` is also implemented, this can act as a `RedbKey`.
impl<T> RedbKey for StorableRedb<T> impl<T> RedbKey for StorableRedb<T>
where where
T: Key + ?Sized, T: Key + 'static,
{ {
#[inline] #[inline]
fn compare(left: &[u8], right: &[u8]) -> Ordering { fn compare(left: &[u8], right: &[u8]) -> Ordering {
@ -46,9 +32,9 @@ where
//---------------------------------------------------------------------------------------------------- RedbValue //---------------------------------------------------------------------------------------------------- RedbValue
impl<T> RedbValue for StorableRedb<T> impl<T> RedbValue for StorableRedb<T>
where 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; type AsBytes<'a> = &'a [u8] where Self: 'a;
#[inline] #[inline]
@ -57,18 +43,11 @@ where
} }
#[inline] #[inline]
fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a> fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'static>
where where
Self: 'a, Self: 'a,
{ {
// Use the bytes directly if possible... <T as Storable>::from_bytes(data)
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)
}
} }
#[inline] #[inline]
@ -76,7 +55,7 @@ where
where where
Self: 'a + 'b, Self: 'a + 'b,
{ {
<T as Storable>::as_bytes(value.as_ref()) <T as Storable>::as_bytes(value)
} }
#[inline] #[inline]
@ -90,6 +69,7 @@ where
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
mod test { mod test {
use super::*; use super::*;
use crate::{StorableBytes, StorableVec};
// Each `#[test]` function has a `test()` to: // Each `#[test]` function has a `test()` to:
// - log // - log
@ -101,13 +81,13 @@ mod test {
fn compare() { fn compare() {
fn test<T>(left: T, right: T, expected: Ordering) fn test<T>(left: T, right: T, expected: Ordering)
where where
T: Key, T: Key + 'static,
{ {
println!("left: {left:?}, right: {right:?}, expected: {expected:?}"); println!("left: {left:?}, right: {right:?}, expected: {expected:?}");
assert_eq!( assert_eq!(
<StorableRedb::<T> as RedbKey>::compare( <StorableRedb::<T> as RedbKey>::compare(
<StorableRedb::<T> as RedbValue>::as_bytes(&Cow::Borrowed(&left)), <StorableRedb::<T> as RedbValue>::as_bytes(&left),
<StorableRedb::<T> as RedbValue>::as_bytes(&Cow::Borrowed(&right)) <StorableRedb::<T> as RedbValue>::as_bytes(&right)
), ),
expected expected
); );
@ -124,7 +104,7 @@ mod test {
fn fixed_width() { fn fixed_width() {
fn test<T>(expected: Option<usize>) fn test<T>(expected: Option<usize>)
where where
T: Storable + ?Sized, T: Storable + 'static,
{ {
assert_eq!(<StorableRedb::<T> as RedbValue>::fixed_width(), expected); assert_eq!(<StorableRedb::<T> as RedbValue>::fixed_width(), expected);
} }
@ -138,7 +118,8 @@ mod test {
test::<i16>(Some(2)); test::<i16>(Some(2));
test::<i32>(Some(4)); test::<i32>(Some(4));
test::<i64>(Some(8)); test::<i64>(Some(8));
test::<[u8]>(None); test::<StorableVec<u8>>(None);
test::<StorableBytes>(None);
test::<[u8; 0]>(Some(0)); test::<[u8; 0]>(Some(0));
test::<[u8; 1]>(Some(1)); test::<[u8; 1]>(Some(1));
test::<[u8; 2]>(Some(2)); test::<[u8; 2]>(Some(2));
@ -150,13 +131,10 @@ mod test {
fn as_bytes() { fn as_bytes() {
fn test<T>(t: &T, expected: &[u8]) fn test<T>(t: &T, expected: &[u8])
where where
T: Storable + ?Sized, T: Storable + 'static,
{ {
println!("t: {t:?}, expected: {expected:?}"); println!("t: {t:?}, expected: {expected:?}");
assert_eq!( assert_eq!(<StorableRedb::<T> as RedbValue>::as_bytes(t), expected);
<StorableRedb::<T> as RedbValue>::as_bytes(&Cow::Borrowed(t)),
expected
);
} }
test::<()>(&(), &[]); test::<()>(&(), &[]);
@ -168,7 +146,8 @@ mod test {
test::<i16>(&-2, &[254, 255]); test::<i16>(&-2, &[254, 255]);
test::<i32>(&-3, &[253, 255, 255, 255]); test::<i32>(&-3, &[253, 255, 255, 255]);
test::<i64>(&-4, &[252, 255, 255, 255, 255, 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; 0]>(&[], &[]);
test::<[u8; 1]>(&[255], &[255]); test::<[u8; 1]>(&[255], &[255]);
test::<[u8; 2]>(&[111, 0], &[111, 0]); test::<[u8; 2]>(&[111, 0], &[111, 0]);
@ -180,12 +159,12 @@ mod test {
fn from_bytes() { fn from_bytes() {
fn test<T>(bytes: &[u8], expected: &T) fn test<T>(bytes: &[u8], expected: &T)
where where
T: Storable + PartialEq + ?Sized, T: Storable + PartialEq + 'static,
{ {
println!("bytes: {bytes:?}, expected: {expected:?}"); println!("bytes: {bytes:?}, expected: {expected:?}");
assert_eq!( assert_eq!(
<StorableRedb::<T> as RedbValue>::from_bytes(bytes), &<StorableRedb::<T> as RedbValue>::from_bytes(bytes),
Cow::Borrowed(expected) expected
); );
} }
@ -198,7 +177,8 @@ mod test {
test::<i16>([254, 255].as_slice(), &-2); test::<i16>([254, 255].as_slice(), &-2);
test::<i32>([253, 255, 255, 255].as_slice(), &-3); test::<i32>([253, 255, 255, 255].as_slice(), &-3);
test::<i64>([252, 255, 255, 255, 255, 255, 255, 255].as_slice(), &-4); 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; 0]>([].as_slice(), &[]);
test::<[u8; 1]>([255].as_slice(), &[255]); test::<[u8; 1]>([255].as_slice(), &[255]);
test::<[u8; 2]>([111, 0].as_slice(), &[111, 0]); test::<[u8; 2]>([111, 0].as_slice(), &[111, 0]);
@ -221,7 +201,8 @@ mod test {
<StorableRedb<i16> as RedbValue>::type_name(), <StorableRedb<i16> as RedbValue>::type_name(),
<StorableRedb<i32> as RedbValue>::type_name(), <StorableRedb<i32> as RedbValue>::type_name(),
<StorableRedb<i64> 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; 0]> as RedbValue>::type_name(),
<StorableRedb<[u8; 1]> as RedbValue>::type_name(), <StorableRedb<[u8; 1]> as RedbValue>::type_name(),
<StorableRedb<[u8; 2]> as RedbValue>::type_name(), <StorableRedb<[u8; 2]> as RedbValue>::type_name(),

View file

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

View file

@ -11,7 +11,6 @@ use crate::{
error::RuntimeError, error::RuntimeError,
table::Table, table::Table,
transaction::{TxRo, TxRw}, transaction::{TxRo, TxRw},
value_guard::ValueGuard,
}; };
//---------------------------------------------------------------------------------------------------- DatabaseRo //---------------------------------------------------------------------------------------------------- DatabaseRo
@ -22,19 +21,18 @@ use crate::{
pub trait DatabaseRo<'tx, T: Table> { pub trait DatabaseRo<'tx, T: Table> {
/// Get the value corresponding to a key. /// Get the value corresponding to a key.
/// ///
/// This returns a guard to the value, not the value itself. /// The returned value is _owned_.
/// See [`ValueGuard`] for more info.
/// ///
/// # 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.
/// ///
/// 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<'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. /// Get an iterator of values corresponding to a range of keys.
/// ///
/// This returns guards to the values, not the values themselves. /// Although the returned iterator itself is tied to the lifetime
/// See [`ValueGuard`] for more info. /// of `&'a self`, the returned values from the iterator are _owned_.
/// ///
/// # Errors /// # Errors
/// Each key in the `range` has the potential to error, for example, /// 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. /// from the iterator.
fn get_range<'a, Range>( fn get_range<'a, Range>(
&'a self, &'a self,
range: &'a Range, range: Range,
) -> Result< ) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
impl Iterator<Item = Result<impl ValueGuard<T::Value>, RuntimeError>> + 'a,
RuntimeError,
>
where where
Range: RangeBounds<T::Key> + 'a; Range: RangeBounds<T::Key> + 'a;
} }

View file

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

View file

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

View file

@ -2,16 +2,15 @@
//---------------------------------------------------------------------------------------------------- Import //---------------------------------------------------------------------------------------------------- Import
use std::{ use std::{
borrow::Cow, borrow::{Borrow, Cow},
char::ToLowercase, char::ToLowercase,
fmt::Debug, fmt::Debug,
io::{Read, Write}, io::{Read, Write},
sync::Arc, sync::Arc,
}; };
use bytemuck::Pod; use bytemuck::{Pod, Zeroable};
use bytes::Bytes;
use crate::ToOwnedDebug;
//---------------------------------------------------------------------------------------------------- Storable //---------------------------------------------------------------------------------------------------- Storable
/// A type that can be stored in the database. /// A type that can be stored in the database.
@ -35,6 +34,8 @@ use crate::ToOwnedDebug;
/// - All types in [`tables`](crate::tables) /// - All types in [`tables`](crate::tables)
/// - Slices, e.g, `[T] where T: Storable` /// - Slices, e.g, `[T] where T: Storable`
/// ///
/// See [`StorableVec`] for storing slices of `T: Storable`.
///
/// ```rust /// ```rust
/// # use cuprate_database::*; /// # use cuprate_database::*;
/// # use std::borrow::*; /// # use std::borrow::*;
@ -45,7 +46,7 @@ use crate::ToOwnedDebug;
/// assert_eq!(into, &[0; 8]); /// assert_eq!(into, &[0; 8]);
/// ///
/// // From bytes. /// // From bytes.
/// let from: u64 = *Storable::from_bytes(&into); /// let from: u64 = Storable::from_bytes(&into);
/// assert_eq!(from, number); /// assert_eq!(from, number);
/// ``` /// ```
/// ///
@ -62,36 +63,7 @@ use crate::ToOwnedDebug;
/// ///
/// Most likely, the bytes are little-endian, however /// Most likely, the bytes are little-endian, however
/// that cannot be relied upon when using this trait. /// that cannot be relied upon when using this trait.
pub trait Storable: ToOwnedDebug { pub trait Storable: Debug {
/// 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;
/// Is this type fixed width in byte length? /// Is this type fixed width in byte length?
/// ///
/// I.e., when converting `Self` to bytes, is it /// I.e., when converting `Self` to bytes, is it
@ -113,7 +85,7 @@ pub trait Storable: ToOwnedDebug {
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
/// # use cuprate_database::Storable; /// # use cuprate_database::*;
/// assert_eq!(<()>::BYTE_LENGTH, Some(0)); /// assert_eq!(<()>::BYTE_LENGTH, Some(0));
/// assert_eq!(u8::BYTE_LENGTH, Some(1)); /// assert_eq!(u8::BYTE_LENGTH, Some(1));
/// assert_eq!(u16::BYTE_LENGTH, Some(2)); /// assert_eq!(u16::BYTE_LENGTH, Some(2));
@ -123,46 +95,29 @@ pub trait Storable: ToOwnedDebug {
/// assert_eq!(i16::BYTE_LENGTH, Some(2)); /// assert_eq!(i16::BYTE_LENGTH, Some(2));
/// assert_eq!(i32::BYTE_LENGTH, Some(4)); /// assert_eq!(i32::BYTE_LENGTH, Some(4));
/// assert_eq!(i64::BYTE_LENGTH, Some(8)); /// assert_eq!(i64::BYTE_LENGTH, Some(8));
/// assert_eq!(<[u8]>::BYTE_LENGTH, None); /// assert_eq!(StorableVec::<u8>::BYTE_LENGTH, None);
/// assert_eq!(<[u8; 0]>::BYTE_LENGTH, Some(0)); /// assert_eq!(StorableVec::<u64>::BYTE_LENGTH, None);
/// assert_eq!(<[u8; 1]>::BYTE_LENGTH, Some(1));
/// assert_eq!(<[u8; 2]>::BYTE_LENGTH, Some(2));
/// assert_eq!(<[u8; 3]>::BYTE_LENGTH, Some(3));
/// ``` /// ```
const BYTE_LENGTH: Option<usize>; const BYTE_LENGTH: Option<usize>;
/// Return `self` in byte form. /// Return `self` in byte form.
fn as_bytes(&self) -> &[u8]; fn as_bytes(&self) -> &[u8];
/// Create a borrowed [`Self`] from bytes. /// Create an owned [`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.
/// ///
/// # Blanket implementation /// # Blanket implementation
/// The blanket implementation that covers all types used /// The blanket implementation that covers all types used
/// by `cuprate_database` will simply cast `bytes` into `Self`, /// by `cuprate_database` will simply bitwise copy `bytes`
/// with no copying. /// into `Self`.
fn from_bytes(bytes: &[u8]) -> &Self;
/// Create a [`Self`] from potentially unaligned bytes.
/// ///
/// # Blanket implementation /// The bytes do not have be correctly aligned.
/// The blanket implementation that covers all types used fn from_bytes(bytes: &[u8]) -> Self;
/// by `cuprate_database` will **always** allocate a new buffer
/// or create a new `Self`.
fn from_bytes_unaligned(bytes: &[u8]) -> Cow<'_, Self>;
} }
//---------------------------------------------------------------------------------------------------- Impl
impl<T> Storable for T impl<T> Storable for T
where 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>()); const BYTE_LENGTH: Option<usize> = Some(std::mem::size_of::<T>());
#[inline] #[inline]
@ -171,37 +126,132 @@ where
} }
#[inline] #[inline]
fn from_bytes(bytes: &[u8]) -> &T { fn from_bytes(bytes: &[u8]) -> T {
bytemuck::from_bytes(bytes) bytemuck::pod_read_unaligned(bytes)
}
#[inline]
fn from_bytes_unaligned(bytes: &[u8]) -> Cow<'static, Self> {
Cow::Owned(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 where
T: Pod + ToOwnedDebug<OwnedDebug = T>, T: Pod + Debug,
Self: ToOwnedDebug<OwnedDebug = Vec<T>>,
{ {
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; const BYTE_LENGTH: Option<usize> = None;
#[inline] #[inline]
fn as_bytes(&self) -> &[u8] { fn as_bytes(&self) -> &[u8] {
bytemuck::must_cast_slice(self) &self.0
} }
/// This always allocates a new `Bytes`.
#[inline] #[inline]
fn from_bytes(bytes: &[u8]) -> &[T] { fn from_bytes(bytes: &[u8]) -> Self {
bytemuck::cast_slice(bytes) Self(Bytes::copy_from_slice(bytes))
}
} }
impl std::ops::Deref for StorableBytes {
type Target = [u8];
#[inline] #[inline]
fn from_bytes_unaligned(bytes: &[u8]) -> Cow<'static, Self> { fn deref(&self) -> &[u8] {
Cow::Owned(bytemuck::pod_collect_to_vec(bytes)) &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. // A `Vec` of the numbers to test.
t: Vec<T>, t: Vec<T>,
) where ) where
T: Storable + Copy + PartialEq, T: Storable + Debug + Copy + PartialEq,
{ {
for t in t { for t in t {
let expected_bytes = to_le_bytes(t); let expected_bytes = to_le_bytes(t);
@ -229,7 +279,7 @@ mod test {
// (De)serialize. // (De)serialize.
let se: &[u8] = Storable::as_bytes(&t); 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"); println!("serialized: {se:?}, deserialized: {de:?}\n");
@ -238,7 +288,7 @@ mod test {
assert_eq!(se.len(), expected_bytes.len()); assert_eq!(se.len(), expected_bytes.len());
} }
// Assert the data is the same. // Assert the data is the same.
assert_eq!(de, &t); assert_eq!(de, t);
} }
} }

View file

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