database: fix open_db_ro, open_db_rw, create_db behavior

This commit is contained in:
hinto.janai 2024-06-13 16:14:57 -04:00
parent bb81d34868
commit 4a04625a8b
No known key found for this signature in database
GPG key ID: D47CE05FA175A499
18 changed files with 223 additions and 301 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "database"
version = "0.0.0"
name = "cuprate-database"
version = "0.0.1"
edition = "2021"
description = "Cuprate's database abstraction"
license = "MIT"
@ -9,8 +9,8 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/storage/database"
keywords = ["cuprate", "database"]
[features]
# default = ["heed", "redb"]
default = ["redb"]
default = ["heed"]
# default = ["redb"]
# default = ["redb-memory"]
heed = ["dep:heed"]
redb = ["dep:redb"]

View file

@ -8,7 +8,7 @@ For a high-level overview, see the database section in
[Cuprate's architecture book](https://architecture.cuprate.org).
# Purpose
This crate does 3 things abstracts various database backends with traits.
This crate abstracts various database backends with traits.
If you need blockchain specific capabilities, consider using the higher-level
`cuprate-blockchain` crate which builds upon this one.
@ -64,9 +64,6 @@ generic-backed dynamic runtime selection of the database backend, i.e.
the user can select which database backend they use. -->
# Feature flags
The `service` module requires the `service` feature to be enabled.
See the module for more documentation.
Different database backends are enabled by the feature flags:
- `heed` (LMDB)
- `redb`
@ -77,10 +74,10 @@ The default is `heed`.
<!-- FIXME: tracing should be behind a feature flag -->
# Examples
The below is an example of using `database`.
The below is an example of using `cuprate-database`.
```rust
use database::{
use cuprate_database::{
ConcreteEnv,
config::ConfigBuilder,
Env, EnvInner,
@ -97,17 +94,21 @@ let config = ConfigBuilder::new()
// Initialize the database environment.
let env = ConcreteEnv::open(config)?;
// Open up a transaction + tables for writing.
// Define metadata for a table.
struct Table;
impl database::Table for Table {
impl cuprate_database::Table for Table {
// The name of the table is "table".
const NAME: &'static str = "table";
// The key type is a `u8`.
type Key = u8;
type Value = u8;
// The key type is a `u64`.
type Value = u64;
}
// Open up a transaction + tables for writing.
let env_inner = env.env_inner();
let tx_rw = env_inner.tx_rw()?;
env_inner.create_db::<Table>(&tx_rw)?;
env_inner.create_db::<Table>(&tx_rw)?; // we must create it or the next line will panic.
let mut table = env_inner.open_db_rw::<Table>(&tx_rw)?;
// Write data to the table.

View file

@ -25,7 +25,7 @@ use crate::{
//---------------------------------------------------------------------------------------------------- Consts
/// Panic message when there's a table missing.
const PANIC_MSG_MISSING_TABLE: &str =
"database::Env should uphold the invariant that all tables are already created";
"cuprate_database::Env should uphold the invariant that all tables are already created";
//---------------------------------------------------------------------------------------------------- ConcreteEnv
/// A strongly typed, concrete database environment, backed by `heed`.
@ -272,7 +272,7 @@ where
Ok(HeedTableRo {
db: self
.open_database(tx_ro, Some(T::NAME))?
.expect(PANIC_MSG_MISSING_TABLE),
.ok_or(RuntimeError::TableNotFound)?,
tx_ro,
})
}
@ -282,25 +282,16 @@ where
&self,
tx_rw: &RefCell<heed::RwTxn<'env>>,
) -> Result<impl DatabaseRw<T>, RuntimeError> {
let tx_ro = tx_rw.borrow();
// Open up a read/write database using our table's const metadata.
Ok(HeedTableRw {
db: self
.open_database(&tx_ro, Some(T::NAME))?
.expect(PANIC_MSG_MISSING_TABLE),
db: self.create_database(&mut tx_rw.borrow_mut(), Some(T::NAME))?,
tx_rw,
})
}
fn create_db<T: Table>(&self, tx_rw: &RefCell<heed::RwTxn<'env>>) -> Result<(), RuntimeError> {
use crate::backend::heed::storable::StorableHeed;
self.create_database::<StorableHeed<<T as Table>::Key>, StorableHeed<<T as Table>::Value>>(
&mut tx_rw.borrow_mut(),
Some(T::NAME),
)?;
// INVARIANT: `heed` creates tables with `open_database` if they don't exist.
self.open_db_rw::<T>(tx_rw)?;
Ok(())
}

View file

@ -134,12 +134,12 @@ impl From<heed::Error> for crate::RuntimeError {
// Don't use a key that is `>511` bytes.
// <http://www.lmdb.tech/doc/group__mdb.html#gaaf0be004f33828bf2fb09d77eb3cef94>
| E2::BadValSize
=> panic!("fix the database code! {mdb_error:#?}"),
=> panic!("E2: fix the database code! {mdb_error:#?}"),
},
// Only if we write incorrect code.
E1::DatabaseClosing | E1::BadOpenOptions { .. } | E1::Encoding(_) | E1::Decoding(_) => {
panic!("fix the database code! {error:#?}")
panic!("E1: fix the database code! {error:#?}")
}
}
}

View file

@ -1,4 +1,4 @@
//! `database::Storable` <-> `heed` serde trait compatibility layer.
//! `cuprate_database::Storable` <-> `heed` serde trait compatibility layer.
//---------------------------------------------------------------------------------------------------- Use
use std::{borrow::Cow, marker::PhantomData};
@ -9,7 +9,7 @@ use crate::storable::Storable;
//---------------------------------------------------------------------------------------------------- StorableHeed
/// The glue struct that implements `heed`'s (de)serialization
/// traits on any type that implements `database::Storable`.
/// traits on any type that implements `cuprate_database::Storable`.
///
/// Never actually gets constructed, just used for trait bound translations.
pub(super) struct StorableHeed<T>(PhantomData<T>)

View file

@ -21,7 +21,7 @@ pub struct ConcreteEnv {
/// (and in current use).
config: Config,
/// A cached, redb version of `database::config::SyncMode`.
/// A cached, redb version of `cuprate_database::config::SyncMode`.
/// `redb` needs the sync mode to be set _per_ TX, so we
/// will continue to use this value every `Env::tx_rw`.
durability: redb::Durability,
@ -148,7 +148,6 @@ where
let table: redb::TableDefinition<'static, StorableRedb<T::Key>, StorableRedb<T::Value>> =
redb::TableDefinition::new(T::NAME);
// INVARIANT: Our `?` error conversion will panic if the table does not exist.
Ok(tx_ro.open_table(table)?)
}
@ -161,13 +160,13 @@ where
let table: redb::TableDefinition<'static, StorableRedb<T::Key>, StorableRedb<T::Value>> =
redb::TableDefinition::new(T::NAME);
// `redb` creates tables if they don't exist, so this should never panic.
// `redb` creates tables if they don't exist, so this shouldn't return `RuntimeError::TableNotFound`.
// <https://docs.rs/redb/latest/redb/struct.WriteTransaction.html#method.open_table>
Ok(tx_rw.open_table(table)?)
}
fn create_db<T: Table>(&self, tx_rw: &redb::WriteTransaction) -> Result<(), RuntimeError> {
// `redb` creates tables if they don't exist.
// INVARIANT: `redb` creates tables if they don't exist.
self.open_db_rw::<T>(tx_rw)?;
Ok(())
}

View file

@ -12,13 +12,13 @@ use crate::{
//---------------------------------------------------------------------------------------------------- InitError
impl From<redb::DatabaseError> for InitError {
/// Created by `redb` in:
/// - [`redb::Database::open`](https://docs.rs/redb/1.5.0/redb/struct.Database.html#method.open).
/// - [`redb::cuprate_database::open`](https://docs.rs/redb/1.5.0/redb/struct.Database.html#method.open).
fn from(error: redb::DatabaseError) -> Self {
use redb::DatabaseError as E;
use redb::StorageError as E2;
// Reference of all possible errors `redb` will return
// upon using `redb::Database::open`:
// upon using `redb::cuprate_database::open`:
// <https://docs.rs/redb/1.5.0/src/redb/db.rs.html#908-923>
match error {
E::RepairAborted => Self::Corrupt,
@ -39,7 +39,7 @@ impl From<redb::DatabaseError> for InitError {
impl From<redb::StorageError> for InitError {
/// Created by `redb` in:
/// - [`redb::Database::open`](https://docs.rs/redb/1.5.0/redb/struct.Database.html#method.check_integrity)
/// - [`redb::cuprate_database::open`](https://docs.rs/redb/1.5.0/redb/struct.Database.html#method.check_integrity)
fn from(error: redb::StorageError) -> Self {
use redb::StorageError as E;
@ -54,7 +54,7 @@ impl From<redb::StorageError> for InitError {
impl From<redb::TransactionError> for InitError {
/// Created by `redb` in:
/// - [`redb::Database::begin_write`](https://docs.rs/redb/1.5.0/redb/struct.Database.html#method.begin_write)
/// - [`redb::cuprate_database::begin_write`](https://docs.rs/redb/1.5.0/redb/struct.Database.html#method.begin_write)
fn from(error: redb::TransactionError) -> Self {
match error {
redb::TransactionError::Storage(error) => error.into(),
@ -94,8 +94,8 @@ impl From<redb::CommitError> for InitError {
#[allow(clippy::fallible_impl_from)] // We need to panic sometimes.
impl From<redb::TransactionError> for RuntimeError {
/// Created by `redb` in:
/// - [`redb::Database::begin_write`](https://docs.rs/redb/1.5.0/redb/struct.Database.html#method.begin_write)
/// - [`redb::Database::begin_read`](https://docs.rs/redb/1.5.0/redb/struct.Database.html#method.begin_read)
/// - [`redb::cuprate_database::begin_write`](https://docs.rs/redb/1.5.0/redb/struct.Database.html#method.begin_write)
/// - [`redb::cuprate_database::begin_read`](https://docs.rs/redb/1.5.0/redb/struct.Database.html#method.begin_read)
fn from(error: redb::TransactionError) -> Self {
match error {
redb::TransactionError::Storage(error) => error.into(),
@ -131,12 +131,13 @@ impl From<redb::TableError> for RuntimeError {
match error {
E::Storage(error) => error.into(),
E::TableDoesNotExist(_) => Self::TableNotFound,
// Only if we write incorrect code.
E::TableTypeMismatch { .. }
| E::TableIsMultimap(_)
| E::TableIsNotMultimap(_)
| E::TypeDefinitionChanged { .. }
| E::TableDoesNotExist(_)
| E::TableAlreadyOpen(..) => panic!("fix the database code! {error:#?}"),
// HACK: Handle new errors as `redb` adds them.

View file

@ -1,4 +1,4 @@
//! `database::Storable` <-> `redb` serde trait compatibility layer.
//! `cuprate_database::Storable` <-> `redb` serde trait compatibility layer.
//---------------------------------------------------------------------------------------------------- Use
use std::{cmp::Ordering, fmt::Debug, marker::PhantomData};
@ -9,7 +9,7 @@ use crate::{key::Key, storable::Storable};
//---------------------------------------------------------------------------------------------------- StorableRedb
/// The glue structs that implements `redb`'s (de)serialization
/// traits on any type that implements `database::Key`.
/// traits on any type that implements `cuprate_database::Key`.
///
/// Never actually get constructed, just used for trait bound translations.
#[derive(Debug)]

View file

@ -1,149 +1,126 @@
// //! Tests for `database`'s backends.
// //!
// //! These tests are fully trait-based, meaning there
// //! is no reference to `backend/`-specific types.
// //!
// //! As such, which backend is tested is
// //! dependant on the feature flags used.
// //!
// //! | Feature flag | Tested backend |
// //! |---------------|----------------|
// //! | Only `redb` | `redb`
// //! | Anything else | `heed`
// //!
// //! `redb`, and it only must be enabled for it to be tested.
//! Tests for `database`'s backends.
//!
//! These tests are fully trait-based, meaning there
//! is no reference to `backend/`-specific types.
//!
//! As such, which backend is tested is
//! dependant on the feature flags used.
//!
//! | Feature flag | Tested backend |
//! |---------------|----------------|
//! | Only `redb` | `redb`
//! | Anything else | `heed`
//!
//! `redb`, and it only must be enabled for it to be tested.
// //---------------------------------------------------------------------------------------------------- Import
//---------------------------------------------------------------------------------------------------- Import
// use crate::{
// database::{DatabaseIter, DatabaseRo, DatabaseRw},
// env::{Env, EnvInner},
// error::RuntimeError,
// resize::ResizeAlgorithm,
// storable::StorableVec,
// tables::{
// BlockBlobs, BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, PrunableHashes,
// PrunableTxBlobs, PrunedTxBlobs, RctOutputs, TxBlobs, TxHeights, TxIds, TxOutputs,
// TxUnlockTime,
// },
// tables::{TablesIter, TablesMut},
// tests::tmp_concrete_env,
// transaction::{TxRo, TxRw},
// types::{
// Amount, AmountIndex, AmountIndices, BlockBlob, BlockHash, BlockHeight, BlockInfo, KeyImage,
// Output, OutputFlags, PreRctOutputId, PrunableBlob, PrunableHash, PrunedBlob, RctOutput,
// TxBlob, TxHash, TxId, UnlockTime,
// },
// ConcreteEnv,
// };
use crate::{
database::{DatabaseIter, DatabaseRo, DatabaseRw},
env::{Env, EnvInner},
error::RuntimeError,
resize::ResizeAlgorithm,
storable::StorableVec,
tests::{tmp_concrete_env, TestTable},
transaction::{TxRo, TxRw},
ConcreteEnv,
};
// //---------------------------------------------------------------------------------------------------- Tests
// /// Simply call [`Env::open`]. If this fails, something is really wrong.
// #[test]
// fn open() {
// tmp_concrete_env();
// }
//---------------------------------------------------------------------------------------------------- Tests
/// Simply call [`Env::open`]. If this fails, something is really wrong.
#[test]
fn open() {
tmp_concrete_env();
}
// /// Create database transactions, but don't write any data.
// #[test]
// fn tx() {
// let (env, _tempdir) = tmp_concrete_env();
// let env_inner = env.env_inner();
/// Create database transactions, but don't write any data.
#[test]
fn tx() {
let (env, _tempdir) = tmp_concrete_env();
let env_inner = env.env_inner();
// TxRo::commit(env_inner.tx_ro().unwrap()).unwrap();
// TxRw::commit(env_inner.tx_rw().unwrap()).unwrap();
// TxRw::abort(env_inner.tx_rw().unwrap()).unwrap();
// }
TxRo::commit(env_inner.tx_ro().unwrap()).unwrap();
TxRw::commit(env_inner.tx_rw().unwrap()).unwrap();
TxRw::abort(env_inner.tx_rw().unwrap()).unwrap();
}
// /// Open (and verify) that all database tables
// /// exist already after calling [`Env::open`].
// #[test]
// fn open_db() {
// let (env, _tempdir) = tmp_concrete_env();
// let env_inner = env.env_inner();
// let tx_ro = env_inner.tx_ro().unwrap();
// let tx_rw = env_inner.tx_rw().unwrap();
/// Test [`Env::open`] and creating/opening tables.
#[test]
fn open_db() {
let (env, _tempdir) = tmp_concrete_env();
let env_inner = env.env_inner();
// // Open all tables in read-only mode.
// // This should be updated when tables are modified.
// env_inner.open_db_ro::<BlockBlobs>(&tx_ro).unwrap();
// env_inner.open_db_ro::<BlockHeights>(&tx_ro).unwrap();
// env_inner.open_db_ro::<BlockInfos>(&tx_ro).unwrap();
// env_inner.open_db_ro::<KeyImages>(&tx_ro).unwrap();
// env_inner.open_db_ro::<NumOutputs>(&tx_ro).unwrap();
// env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
// env_inner.open_db_ro::<PrunableHashes>(&tx_ro).unwrap();
// env_inner.open_db_ro::<PrunableTxBlobs>(&tx_ro).unwrap();
// env_inner.open_db_ro::<PrunedTxBlobs>(&tx_ro).unwrap();
// env_inner.open_db_ro::<RctOutputs>(&tx_ro).unwrap();
// env_inner.open_db_ro::<TxBlobs>(&tx_ro).unwrap();
// env_inner.open_db_ro::<TxHeights>(&tx_ro).unwrap();
// env_inner.open_db_ro::<TxIds>(&tx_ro).unwrap();
// env_inner.open_db_ro::<TxOutputs>(&tx_ro).unwrap();
// env_inner.open_db_ro::<TxUnlockTime>(&tx_ro).unwrap();
// TxRo::commit(tx_ro).unwrap();
// Create table.
{
let tx_rw = env_inner.tx_rw().unwrap();
env_inner.create_db::<TestTable>(&tx_rw).unwrap();
TxRw::commit(tx_rw).unwrap();
}
// // Open all tables in read/write mode.
// env_inner.open_db_rw::<BlockBlobs>(&tx_rw).unwrap();
// env_inner.open_db_rw::<BlockHeights>(&tx_rw).unwrap();
// env_inner.open_db_rw::<BlockInfos>(&tx_rw).unwrap();
// env_inner.open_db_rw::<KeyImages>(&tx_rw).unwrap();
// env_inner.open_db_rw::<NumOutputs>(&tx_rw).unwrap();
// env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
// env_inner.open_db_rw::<PrunableHashes>(&tx_rw).unwrap();
// env_inner.open_db_rw::<PrunableTxBlobs>(&tx_rw).unwrap();
// env_inner.open_db_rw::<PrunedTxBlobs>(&tx_rw).unwrap();
// env_inner.open_db_rw::<RctOutputs>(&tx_rw).unwrap();
// env_inner.open_db_rw::<TxBlobs>(&tx_rw).unwrap();
// env_inner.open_db_rw::<TxHeights>(&tx_rw).unwrap();
// env_inner.open_db_rw::<TxIds>(&tx_rw).unwrap();
// env_inner.open_db_rw::<TxOutputs>(&tx_rw).unwrap();
// env_inner.open_db_rw::<TxUnlockTime>(&tx_rw).unwrap();
// TxRw::commit(tx_rw).unwrap();
// }
let tx_ro = env_inner.tx_ro().unwrap();
let tx_rw = env_inner.tx_rw().unwrap();
// /// Test `Env` resizes.
// #[test]
// fn resize() {
// // This test is only valid for `Env`'s that need to resize manually.
// if !ConcreteEnv::MANUAL_RESIZE {
// return;
// }
// Open table in read-only mode.
env_inner.open_db_ro::<TestTable>(&tx_ro).unwrap();
TxRo::commit(tx_ro).unwrap();
// let (env, _tempdir) = tmp_concrete_env();
// Open table in read/write mode.
env_inner.open_db_rw::<TestTable>(&tx_rw).unwrap();
TxRw::commit(tx_rw).unwrap();
}
// // Resize by the OS page size.
// let page_size = crate::resize::page_size();
// let old_size = env.current_map_size();
// env.resize_map(Some(ResizeAlgorithm::FixedBytes(page_size)));
/// Assert that opening a read-only table before creating errors.
#[test]
fn open_uncreated_table() {
let (env, _tempdir) = tmp_concrete_env();
let env_inner = env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
// // Assert it resized exactly by the OS page size.
// let new_size = env.current_map_size();
// assert_eq!(new_size, old_size + page_size.get());
// }
// Open uncreated table.
let error = env_inner.open_db_ro::<TestTable>(&tx_ro);
assert!(matches!(error, Err(RuntimeError::TableNotFound)));
}
// /// Test that `Env`'s that don't manually resize.
// #[test]
// #[should_panic = "unreachable"]
// fn non_manual_resize_1() {
// if ConcreteEnv::MANUAL_RESIZE {
// unreachable!();
// } else {
// let (env, _tempdir) = tmp_concrete_env();
// env.resize_map(None);
// }
// }
/// Test `Env` resizes.
#[test]
fn resize() {
// This test is only valid for `Env`'s that need to resize manually.
if !ConcreteEnv::MANUAL_RESIZE {
return;
}
// #[test]
// #[should_panic = "unreachable"]
// fn non_manual_resize_2() {
// if ConcreteEnv::MANUAL_RESIZE {
// unreachable!();
// } else {
// let (env, _tempdir) = tmp_concrete_env();
// env.current_map_size();
// }
// }
let (env, _tempdir) = tmp_concrete_env();
// Resize by the OS page size.
let page_size = crate::resize::page_size();
let old_size = env.current_map_size();
env.resize_map(Some(ResizeAlgorithm::FixedBytes(page_size)));
// Assert it resized exactly by the OS page size.
let new_size = env.current_map_size();
assert_eq!(new_size, old_size + page_size.get());
}
/// Test that `Env`'s that don't manually resize.
#[test]
#[should_panic = "unreachable"]
fn non_manual_resize_1() {
if ConcreteEnv::MANUAL_RESIZE {
unreachable!();
}
let (env, _tempdir) = tmp_concrete_env();
env.resize_map(None);
}
#[test]
#[should_panic = "unreachable"]
fn non_manual_resize_2() {
if ConcreteEnv::MANUAL_RESIZE {
unreachable!();
}
let (env, _tempdir) = tmp_concrete_env();
env.current_map_size();
}
// /// Test all `DatabaseR{o,w}` operations.
// #[test]

View file

@ -199,7 +199,7 @@ impl Config {
/// Same as [`Config::default`].
///
/// ```rust
/// use database::{config::*, resize::*, DATABASE_DATA_FILENAME};
/// use cuprate_database::{config::*, resize::*, DATABASE_DATA_FILENAME};
/// use cuprate_helper::fs::*;
///
/// let config = Config::new();
@ -230,7 +230,7 @@ impl Default for Config {
/// Same as [`Config::new`].
///
/// ```rust
/// # use database::config::*;
/// # use cuprate_database::config::*;
/// assert_eq!(Config::default(), Config::new());
/// ```
fn default() -> Self {

View file

@ -12,7 +12,7 @@
//!
//! # Example
//! ```rust
//! use database::{
//! use cuprate_database::{
//! ConcreteEnv, Env,
//! config::{ConfigBuilder, ReaderThreads, SyncMode}
//! };

View file

@ -50,7 +50,7 @@ pub enum ReaderThreads {
/// as such, it is equal to [`ReaderThreads::OnePerThread`].
///
/// ```rust
/// # use database::config::*;
/// # use cuprate_database::config::*;
/// let reader_threads = ReaderThreads::from(0_usize);
/// assert!(matches!(reader_threads, ReaderThreads::OnePerThread));
/// ```
@ -82,7 +82,7 @@ pub enum ReaderThreads {
/// non-zero, but not 1 thread, the minimum value 1 will be returned.
///
/// ```rust
/// # use database::config::*;
/// # use cuprate_database::config::*;
/// assert_eq!(ReaderThreads::Percent(0.000000001).as_threads().get(), 1);
/// ```
Percent(f32),
@ -98,7 +98,7 @@ impl ReaderThreads {
///
/// # Example
/// ```rust
/// use database::config::ReaderThreads as Rt;
/// use cuprate_database::config::ReaderThreads as Rt;
///
/// let total_threads: std::num::NonZeroUsize =
/// cuprate_helper::thread::threads();

View file

@ -80,14 +80,13 @@ pub trait Env: Sized {
/// Open the database environment, using the passed [`Config`].
///
/// # Invariants
/// TODO: fix me
// This function **must** create all tables listed in [`crate::tables`].
/// This function does not create any tables.
///
/// The rest of the functions depend on the fact
/// they already exist, or else they will panic.
/// You must create all possible tables with [`EnvInner::create_db`]
/// before attempting to open any.
///
/// # Errors
/// This will error if the database could not be opened.
/// This will error if the database file could not be opened.
///
/// This is the only [`Env`] function that will return
/// an [`InitError`] instead of a [`RuntimeError`].
@ -180,10 +179,14 @@ pub trait Env: Sized {
macro_rules! doc_table_error {
() => {
r"# Errors
This will only return [`RuntimeError::Io`] if it errors.
This will only return [`RuntimeError::Io`] on normal errors.
As all tables are created upon [`Env::open`],
this function will never error because a table doesn't exist."
If the specified table is not created upon before this function is called,
this will return an error.
Implementation detail you should NOT rely on:
- This only panics on `heed`
- `redb` will create the table if it does not exist"
};
}
@ -229,7 +232,11 @@ where
/// // (name, key/value type)
/// ```
///
#[doc = doc_table_error!()]
/// # Errors
/// This will only return [`RuntimeError::Io`] on normal errors.
///
/// If the specified table is not created upon before this function is called,
/// this will return [`RuntimeError::TableNotFound`].
fn open_db_ro<T: Table>(
&self,
tx_ro: &Ro,
@ -246,30 +253,23 @@ where
/// This will open the database [`Table`]
/// passed as a generic to this function.
///
#[doc = doc_table_error!()]
/// # Errors
/// This will only return [`RuntimeError::Io`] on errors.
///
/// Implementation details: Both `heed` & `redb` backends create
/// the table with this function if it does not already exist. For safety and
/// clear intent, you should still consider using [`EnvInner::create_db`] instead.
fn open_db_rw<T: Table>(&self, tx_rw: &Rw) -> Result<impl DatabaseRw<T>, RuntimeError>;
// TODO: make equivalent in `cuprate-blockchain`.
/// Create a database table.
///
/// This will create the database [`Table`]
/// passed as a generic to this function.
#[doc = doc_table_error!()]
///
/// # Errors
/// This will only return [`RuntimeError::Io`] on errors.
fn create_db<T: Table>(&self, tx_rw: &Rw) -> Result<(), RuntimeError>;
// /// Open all tables in read-write mode.
// ///
// /// This calls [`EnvInner::open_db_rw`] on all database tables
// /// and returns a structure that allows access to all tables.
// ///
// #[doc = doc_table_error!()]
// fn open_tables_mut(&self, tx_rw: &Rw) -> Result<impl TablesMut, RuntimeError> {
// call_fn_on_all_tables_or_early_return! {
// Self::open_db_rw(self, tx_rw)
// }
// }
/// Clear all `(key, value)`'s from a database table.
///
/// This will delete all key and values in the passed

View file

@ -88,6 +88,10 @@ pub enum RuntimeError {
#[error("database memory map must be resized")]
ResizeNeeded,
/// The given table did not exist in the database.
#[error("database table did not exist")]
TableNotFound,
/// A [`std::io::Error`].
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),

View file

@ -23,7 +23,7 @@ pub trait Key: Storable + Sized {
/// not a comparison of the key's value.
///
/// ```rust
/// # use database::*;
/// # use cuprate_database::*;
/// assert_eq!(
/// <u64 as Key>::compare([0].as_slice(), [1].as_slice()),
/// std::cmp::Ordering::Less,

View file

@ -50,7 +50,7 @@ impl ResizeAlgorithm {
/// Returns [`Self::Monero`].
///
/// ```rust
/// # use database::resize::*;
/// # use cuprate_database::resize::*;
/// assert!(matches!(ResizeAlgorithm::new(), ResizeAlgorithm::Monero));
/// ```
#[inline]
@ -75,7 +75,7 @@ impl Default for ResizeAlgorithm {
/// Calls [`Self::new`].
///
/// ```rust
/// # use database::resize::*;
/// # use cuprate_database::resize::*;
/// assert_eq!(ResizeAlgorithm::new(), ResizeAlgorithm::default());
/// ```
#[inline]
@ -113,7 +113,7 @@ pub fn page_size() -> NonZeroUsize {
/// [^2]: `1_073_745_920`
///
/// ```rust
/// # use database::resize::*;
/// # use cuprate_database::resize::*;
/// // The value this function will increment by
/// // (assuming page multiple of 4096).
/// const N: usize = 1_073_741_824;
@ -129,7 +129,7 @@ pub fn page_size() -> NonZeroUsize {
/// This function will panic if adding onto `current_size_bytes` overflows [`usize::MAX`].
///
/// ```rust,should_panic
/// # use database::resize::*;
/// # use cuprate_database::resize::*;
/// // Ridiculous large numbers panic.
/// monero(usize::MAX);
/// ```
@ -166,7 +166,7 @@ pub fn monero(current_size_bytes: usize) -> NonZeroUsize {
/// and then round up to nearest OS page size.
///
/// ```rust
/// # use database::resize::*;
/// # use cuprate_database::resize::*;
/// let page_size: usize = page_size().get();
///
/// // Anything below the page size will round up to the page size.
@ -185,7 +185,7 @@ pub fn monero(current_size_bytes: usize) -> NonZeroUsize {
/// This function will panic if adding onto `current_size_bytes` overflows [`usize::MAX`].
///
/// ```rust,should_panic
/// # use database::resize::*;
/// # use cuprate_database::resize::*;
/// // Ridiculous large numbers panic.
/// fixed_bytes(1, usize::MAX);
/// ```
@ -221,7 +221,7 @@ pub fn fixed_bytes(current_size_bytes: usize, add_bytes: usize) -> NonZeroUsize
/// (rounded up to the OS page size).
///
/// ```rust
/// # use database::resize::*;
/// # use cuprate_database::resize::*;
/// let page_size: usize = page_size().get();
///
/// // Anything below the page size will round up to the page size.
@ -247,7 +247,7 @@ pub fn fixed_bytes(current_size_bytes: usize, add_bytes: usize) -> NonZeroUsize
/// is closer to [`usize::MAX`] than the OS page size.
///
/// ```rust,should_panic
/// # use database::resize::*;
/// # use cuprate_database::resize::*;
/// // Ridiculous large numbers panic.
/// percent(usize::MAX, 1.001);
/// ```

View file

@ -30,7 +30,7 @@ use bytes::Bytes;
/// See [`StorableVec`] & [`StorableBytes`] for storing slices of `T: Storable`.
///
/// ```rust
/// # use database::*;
/// # use cuprate_database::*;
/// # use std::borrow::*;
/// let number: u64 = 0;
///
@ -78,7 +78,7 @@ pub trait Storable: Debug {
///
/// # Examples
/// ```rust
/// # use database::*;
/// # use cuprate_database::*;
/// assert_eq!(<()>::BYTE_LENGTH, Some(0));
/// assert_eq!(u8::BYTE_LENGTH, Some(1));
/// assert_eq!(u16::BYTE_LENGTH, Some(2));
@ -137,7 +137,7 @@ where
///
/// # Example
/// ```rust
/// # use database::*;
/// # use cuprate_database::*;
/// //---------------------------------------------------- u8
/// let vec: StorableVec<u8> = StorableVec(vec![0,1]);
///
@ -203,7 +203,7 @@ impl<T> Borrow<[T]> for StorableVec<T> {
/// A [`Storable`] version of [`Bytes`].
///
/// ```rust
/// # use database::*;
/// # use cuprate_database::*;
/// # use bytes::Bytes;
/// let bytes: StorableBytes = StorableBytes(Bytes::from_static(&[0,1]));
///

View file

@ -1,85 +1,34 @@
// //! Utilities for `database` testing.
// //!
// //! These types/fn's are only:
// //! - enabled on #[cfg(test)]
// //! - only used internally
//! Utilities for `database` testing.
//!
//! These types/fn's are only:
//! - enabled on #[cfg(test)]
//! - only used internally
// //---------------------------------------------------------------------------------------------------- Import
// use std::fmt::Debug;
//---------------------------------------------------------------------------------------------------- Import
use crate::{config::ConfigBuilder, table::Table, ConcreteEnv, Env};
// use pretty_assertions::assert_eq;
//---------------------------------------------------------------------------------------------------- struct
/// A test table.
pub(crate) struct TestTable;
// use crate::{config::ConfigBuilder, tables::Tables, ConcreteEnv, DatabaseRo, Env, EnvInner};
impl Table for TestTable {
const NAME: &'static str = "test_table";
type Key = u8;
type Value = u8;
}
// //---------------------------------------------------------------------------------------------------- Struct
// /// Named struct to assert the length of all tables.
// ///
// /// This is a struct with fields instead of a function
// /// so that callers can name arguments, otherwise the call-site
// /// is a little confusing, i.e. `assert_table_len(0, 25, 1, 123)`.
// #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
// pub(crate) struct AssertTableLen {
// pub(crate) block_infos: u64,
// pub(crate) block_blobs: u64,
// pub(crate) block_heights: u64,
// pub(crate) key_images: u64,
// pub(crate) num_outputs: u64,
// pub(crate) pruned_tx_blobs: u64,
// pub(crate) prunable_hashes: u64,
// pub(crate) outputs: u64,
// pub(crate) prunable_tx_blobs: u64,
// pub(crate) rct_outputs: u64,
// pub(crate) tx_blobs: u64,
// pub(crate) tx_ids: u64,
// pub(crate) tx_heights: u64,
// pub(crate) tx_unlock_time: u64,
// }
//---------------------------------------------------------------------------------------------------- fn
/// Create an `Env` in a temporarily directory.
/// The directory is automatically removed after the `TempDir` is dropped.
///
/// FIXME: changing this to `-> impl Env` causes lifetime errors...
pub(crate) fn tmp_concrete_env() -> (ConcreteEnv, tempfile::TempDir) {
let tempdir = tempfile::tempdir().unwrap();
let config = ConfigBuilder::new()
.db_directory(tempdir.path().into())
.low_power()
.build();
let env = ConcreteEnv::open(config).unwrap();
// impl AssertTableLen {
// /// Assert the length of all tables.
// pub(crate) fn assert(self, tables: &impl Tables) {
// let other = Self {
// block_infos: tables.block_infos().len().unwrap(),
// block_blobs: tables.block_blobs().len().unwrap(),
// block_heights: tables.block_heights().len().unwrap(),
// key_images: tables.key_images().len().unwrap(),
// num_outputs: tables.num_outputs().len().unwrap(),
// pruned_tx_blobs: tables.pruned_tx_blobs().len().unwrap(),
// prunable_hashes: tables.prunable_hashes().len().unwrap(),
// outputs: tables.outputs().len().unwrap(),
// prunable_tx_blobs: tables.prunable_tx_blobs().len().unwrap(),
// rct_outputs: tables.rct_outputs().len().unwrap(),
// tx_blobs: tables.tx_blobs().len().unwrap(),
// tx_ids: tables.tx_ids().len().unwrap(),
// tx_heights: tables.tx_heights().len().unwrap(),
// tx_unlock_time: tables.tx_unlock_time().len().unwrap(),
// };
// assert_eq!(self, other);
// }
// }
// //---------------------------------------------------------------------------------------------------- fn
// /// Create an `Env` in a temporarily directory.
// /// The directory is automatically removed after the `TempDir` is dropped.
// ///
// /// FIXME: changing this to `-> impl Env` causes lifetime errors...
// pub(crate) fn tmp_concrete_env() -> (ConcreteEnv, tempfile::TempDir) {
// let tempdir = tempfile::tempdir().unwrap();
// let config = ConfigBuilder::new()
// .db_directory(tempdir.path().into())
// .low_power()
// .build();
// let env = ConcreteEnv::open(config).unwrap();
// (env, tempdir)
// }
// /// Assert all the tables in the environment are empty.
// pub(crate) fn assert_all_tables_are_empty(env: &ConcreteEnv) {
// let env_inner = env.env_inner();
// let tx_ro = env_inner.tx_ro().unwrap();
// let tables = env_inner.open_tables(&tx_ro).unwrap();
// assert!(tables.all_tables_empty().unwrap());
// assert_eq!(crate::ops::tx::get_num_tx(tables.tx_ids()).unwrap(), 0);
// }
(env, tempdir)
}