mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-12-22 19:49:28 +00:00
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:
parent
004bb153b4
commit
3656a1ada7
16 changed files with 278 additions and 439 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -592,6 +592,7 @@ name = "cuprate-database"
|
|||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
"crossbeam",
|
||||
"cuprate-helper",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::*;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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::*;
|
||||
}
|
Loading…
Reference in a new issue