storage: move table generator macro blockchain -> database (#222)
Some checks are pending
Audit / audit (push) Waiting to run
CI / fmt (push) Waiting to run
CI / typo (push) Waiting to run
CI / ci (macos-latest, stable, bash) (push) Waiting to run
CI / ci (ubuntu-latest, stable, bash) (push) Waiting to run
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Waiting to run
Deny / audit (push) Waiting to run
Doc / build (push) Waiting to run
Doc / deploy (push) Blocked by required conditions

* move table generator macro `blockchain` -> `database`

* blockchain: fix imports

* docs

* fix import ordering
This commit is contained in:
hinto-janai 2024-07-11 09:20:56 -04:00 committed by GitHub
parent 824651c8cf
commit fbae3df203
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 491 additions and 552 deletions

2
Cargo.lock generated
View file

@ -507,7 +507,6 @@ dependencies = [
"hex",
"hex-literal",
"monero-serai",
"paste",
"pretty_assertions",
"proptest",
"rayon",
@ -601,6 +600,7 @@ dependencies = [
"cfg-if",
"heed",
"page_size",
"paste",
"redb",
"serde",
"tempfile",

View file

@ -30,7 +30,6 @@ bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min
curve25519-dalek = { workspace = true }
cuprate-pruning = { path = "../../pruning" }
monero-serai = { workspace = true, features = ["std"] }
paste = { workspace = true }
serde = { workspace = true, optional = true }
# `service` feature.

View file

@ -67,8 +67,7 @@ use cuprate_blockchain::{
DatabaseRo, DatabaseRw, TxRo, TxRw,
},
config::ConfigBuilder,
tables::{Tables, TablesMut},
OpenTables,
tables::{Tables, TablesMut, OpenTables},
};
# fn main() -> Result<(), Box<dyn std::error::Error>> {

View file

@ -3,7 +3,7 @@
//---------------------------------------------------------------------------------------------------- Import
use cuprate_database::{ConcreteEnv, Env, EnvInner, InitError, RuntimeError, TxRw};
use crate::{config::Config, open_tables::OpenTables};
use crate::{config::Config, tables::OpenTables};
//---------------------------------------------------------------------------------------------------- Free functions
/// Open the blockchain database, using the passed [`Config`].

View file

@ -114,23 +114,18 @@ compile_error!("Cuprate is only compatible with 64-bit CPUs");
//
// Documentation for each module is located in the respective file.
pub mod config;
mod constants;
pub use constants::{DATABASE_CORRUPT_MSG, DATABASE_VERSION};
mod open_tables;
pub use open_tables::OpenTables;
mod free;
pub use constants::{DATABASE_CORRUPT_MSG, DATABASE_VERSION};
pub use cuprate_database;
pub use free::open;
pub mod config;
pub mod ops;
pub mod tables;
pub mod types;
pub use cuprate_database;
//---------------------------------------------------------------------------------------------------- Feature-gated
#[cfg(feature = "service")]
pub mod service;

View file

@ -1,190 +0,0 @@
//! TODO
//---------------------------------------------------------------------------------------------------- Import
use cuprate_database::{EnvInner, RuntimeError};
use crate::tables::{TablesIter, TablesMut};
//---------------------------------------------------------------------------------------------------- Table function macro
/// `crate`-private macro for callings functions on all tables.
///
/// This calls the function `$fn` with the optional
/// arguments `$args` on all tables - returning early
/// (within whatever scope this is called) if any
/// of the function calls error.
///
/// Else, it evaluates to an `Ok((tuple, of, all, table, types, ...))`,
/// i.e., an `impl Table[Mut]` wrapped in `Ok`.
macro_rules! call_fn_on_all_tables_or_early_return {
(
$($fn:ident $(::)?)*
(
$($arg:ident),* $(,)?
)
) => {{
Ok((
$($fn ::)*<$crate::tables::BlockInfos>($($arg),*)?,
$($fn ::)*<$crate::tables::BlockBlobs>($($arg),*)?,
$($fn ::)*<$crate::tables::BlockHeights>($($arg),*)?,
$($fn ::)*<$crate::tables::KeyImages>($($arg),*)?,
$($fn ::)*<$crate::tables::NumOutputs>($($arg),*)?,
$($fn ::)*<$crate::tables::PrunedTxBlobs>($($arg),*)?,
$($fn ::)*<$crate::tables::PrunableHashes>($($arg),*)?,
$($fn ::)*<$crate::tables::Outputs>($($arg),*)?,
$($fn ::)*<$crate::tables::PrunableTxBlobs>($($arg),*)?,
$($fn ::)*<$crate::tables::RctOutputs>($($arg),*)?,
$($fn ::)*<$crate::tables::TxBlobs>($($arg),*)?,
$($fn ::)*<$crate::tables::TxIds>($($arg),*)?,
$($fn ::)*<$crate::tables::TxHeights>($($arg),*)?,
$($fn ::)*<$crate::tables::TxOutputs>($($arg),*)?,
$($fn ::)*<$crate::tables::TxUnlockTime>($($arg),*)?,
))
}};
}
pub(crate) use call_fn_on_all_tables_or_early_return;
//---------------------------------------------------------------------------------------------------- OpenTables
/// Open all tables at once.
///
/// This trait encapsulates the functionality of opening all tables at once.
/// It can be seen as the "constructor" for the [`Tables`](crate::tables::Tables) object.
///
/// Note that this is already implemented on [`cuprate_database::EnvInner`], thus:
/// - You don't need to implement this
/// - It can be called using `env_inner.open_tables()` notation
///
/// # Example
/// ```rust
/// use cuprate_blockchain::{
/// cuprate_database::{Env, EnvInner},
/// config::ConfigBuilder,
/// tables::{Tables, TablesMut},
/// OpenTables,
/// };
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// // Create a configuration for the database environment.
/// let tmp_dir = tempfile::tempdir()?;
/// let db_dir = tmp_dir.path().to_owned();
/// let config = ConfigBuilder::new()
/// .db_directory(db_dir.into())
/// .build();
///
/// // Initialize the database environment.
/// let env = cuprate_blockchain::open(config)?;
///
/// // Open up a transaction.
/// let env_inner = env.env_inner();
/// let tx_rw = env_inner.tx_rw()?;
///
/// // Open _all_ tables in write mode using [`OpenTables::open_tables_mut`].
/// // Note how this is being called on `env_inner`.
/// // |
/// // v
/// let mut tables = env_inner.open_tables_mut(&tx_rw)?;
/// # Ok(()) }
/// ```
pub trait OpenTables<'env> {
/// The read-only transaction type of the backend.
type Ro<'a>;
/// The read-write transaction type of the backend.
type Rw<'a>;
/// Open all tables in read/iter mode.
///
/// This calls [`EnvInner::open_db_ro`] on all database tables
/// and returns a structure that allows access to all tables.
///
/// # Errors
/// This will only return [`RuntimeError::Io`] if it errors.
///
/// As all tables are created upon [`crate::open`],
/// this function will never error because a table doesn't exist.
fn open_tables(&self, tx_ro: &Self::Ro<'_>) -> Result<impl TablesIter, 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.
///
/// # Errors
/// This will only return [`RuntimeError::Io`] on errors.
fn open_tables_mut(&self, tx_rw: &Self::Rw<'_>) -> Result<impl TablesMut, RuntimeError>;
/// Create all database tables.
///
/// This will create all the [`Table`](cuprate_database::Table)s
/// found in [`tables`](crate::tables).
///
/// # Errors
/// This will only return [`RuntimeError::Io`] on errors.
fn create_tables(&self, tx_rw: &Self::Rw<'_>) -> Result<(), RuntimeError>;
}
impl<'env, Ei> OpenTables<'env> for Ei
where
Ei: EnvInner<'env>,
{
type Ro<'a> = <Ei as EnvInner<'env>>::Ro<'a>;
type Rw<'a> = <Ei as EnvInner<'env>>::Rw<'a>;
fn open_tables(&self, tx_ro: &Self::Ro<'_>) -> Result<impl TablesIter, RuntimeError> {
call_fn_on_all_tables_or_early_return! {
Self::open_db_ro(self, tx_ro)
}
}
fn open_tables_mut(&self, tx_rw: &Self::Rw<'_>) -> Result<impl TablesMut, RuntimeError> {
call_fn_on_all_tables_or_early_return! {
Self::open_db_rw(self, tx_rw)
}
}
fn create_tables(&self, tx_rw: &Self::Rw<'_>) -> Result<(), RuntimeError> {
match call_fn_on_all_tables_or_early_return! {
Self::create_db(self, tx_rw)
} {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
use std::borrow::Cow;
use cuprate_database::{Env, EnvInner};
use crate::{config::ConfigBuilder, tests::tmp_concrete_env};
use super::*;
/// Tests that [`crate::open`] creates all tables.
#[test]
fn test_all_tables_are_created() {
let (env, _tmp) = tmp_concrete_env();
let env_inner = env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
env_inner.open_tables(&tx_ro).unwrap();
}
/// Tests that direct usage of
/// [`cuprate_database::ConcreteEnv`]
/// does NOT create all tables.
#[test]
#[should_panic(expected = "`Result::unwrap()` on an `Err` value: TableNotFound")]
fn test_no_tables_are_created() {
let tempdir = tempfile::tempdir().unwrap();
let config = ConfigBuilder::new()
.db_directory(Cow::Owned(tempdir.path().into()))
.low_power()
.build();
let env = cuprate_database::ConcreteEnv::open(config.db_config).unwrap();
let env_inner = env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
env_inner.open_tables(&tx_ro).unwrap();
}
}

View file

@ -271,8 +271,8 @@ mod test {
use super::*;
use crate::{
open_tables::OpenTables,
ops::tx::{get_tx, tx_exists},
tables::OpenTables,
tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen},
};

View file

@ -87,9 +87,8 @@ mod test {
use super::*;
use crate::{
open_tables::OpenTables,
ops::block::add_block,
tables::Tables,
tables::{OpenTables, Tables},
tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen},
};

View file

@ -52,8 +52,7 @@ mod test {
use super::*;
use crate::{
open_tables::OpenTables,
tables::{Tables, TablesMut},
tables::{OpenTables, Tables, TablesMut},
tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen},
};

View file

@ -61,9 +61,8 @@
//! Env, EnvInner,
//! DatabaseRo, DatabaseRw, TxRo, TxRw,
//! },
//! OpenTables,
//! config::ConfigBuilder,
//! tables::{Tables, TablesMut},
//! tables::{Tables, TablesMut, OpenTables},
//! ops::block::{add_block, pop_block},
//! };
//!

View file

@ -254,8 +254,7 @@ mod test {
use cuprate_database::{Env, EnvInner};
use crate::{
open_tables::OpenTables,
tables::{Tables, TablesMut},
tables::{OpenTables, Tables, TablesMut},
tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen},
types::OutputFlags,
};

View file

@ -331,8 +331,7 @@ mod test {
use cuprate_test_utils::data::{tx_v1_sig0, tx_v1_sig2, tx_v2_rct3};
use crate::{
open_tables::OpenTables,
tables::Tables,
tables::{OpenTables, Tables},
tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen},
};

View file

@ -22,7 +22,6 @@ use cuprate_types::{
use crate::{
config::ReaderThreads,
open_tables::OpenTables,
ops::{
block::{
block_exists, get_block_extended_header_from_height, get_block_height, get_block_info,
@ -35,6 +34,7 @@ use crate::{
free::{compact_history_genesis_not_included, compact_history_index_to_height_offset},
types::{ResponseReceiver, ResponseResult, ResponseSender},
},
tables::OpenTables,
tables::{BlockHeights, BlockInfos, Tables},
types::{Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId},
};

View file

@ -24,14 +24,13 @@ use cuprate_types::{
use crate::{
config::ConfigBuilder,
open_tables::OpenTables,
ops::{
block::{get_block_extended_header_from_height, get_block_info},
blockchain::chain_height,
output::id_to_output_on_chain,
},
service::{init, DatabaseReadHandle, DatabaseWriteHandle},
tables::{Tables, TablesIter},
tables::{OpenTables, Tables, TablesIter},
tests::AssertTableLen,
types::{Amount, AmountIndex, PreRctOutputId},
};

View file

@ -16,8 +16,8 @@ use cuprate_types::{
};
use crate::{
open_tables::OpenTables,
service::types::{ResponseReceiver, ResponseResult, ResponseSender},
tables::OpenTables,
};
//---------------------------------------------------------------------------------------------------- Constants

View file

@ -4,7 +4,7 @@
//! This module contains all the table definitions used by `cuprate_blockchain`.
//!
//! The zero-sized structs here represents the table type;
//! they all are essentially marker types that implement [`Table`].
//! they all are essentially marker types that implement [`cuprate_database::Table`].
//!
//! Table structs are `CamelCase`, and their static string
//! names used by the actual database backend are `snake_case`.
@ -14,311 +14,14 @@
//! # Traits
//! This module also contains a set of traits for
//! accessing _all_ tables defined here at once.
//!
//! For example, this is the object returned by [`OpenTables::open_tables`](crate::OpenTables::open_tables).
//---------------------------------------------------------------------------------------------------- Import
use cuprate_database::{DatabaseIter, DatabaseRo, DatabaseRw, Table};
use crate::types::{
Amount, AmountIndex, AmountIndices, BlockBlob, BlockHash, BlockHeight, BlockInfo, KeyImage,
Output, PreRctOutputId, PrunableBlob, PrunableHash, PrunedBlob, RctOutput, TxBlob, TxHash,
TxId, UnlockTime,
};
//---------------------------------------------------------------------------------------------------- Sealed
/// Private module, should not be accessible outside this crate.
pub(super) mod private {
/// Private sealed trait.
///
/// Cannot be implemented outside this crate.
pub trait Sealed {}
}
//---------------------------------------------------------------------------------------------------- `trait Tables[Mut]`
/// Creates:
/// - `pub trait Tables`
/// - `pub trait TablesIter`
/// - `pub trait TablesMut`
/// - Blanket implementation for `(tuples, containing, all, open, database, tables, ...)`
///
/// For why this exists, see: <https://github.com/Cuprate/cuprate/pull/102#pullrequestreview-1978348871>.
macro_rules! define_trait_tables {
($(
// The `T: Table` type The index in a tuple
// | containing all tables
// v v
$table:ident => $index:literal
),* $(,)?) => { paste::paste! {
/// Object containing all opened [`Table`]s in read-only mode.
///
/// This is an encapsulated object that contains all
/// available [`Table`]'s in read-only mode.
///
/// It is a `Sealed` trait and is only implemented on a
/// `(tuple, containing, all, table, types, ...)`.
///
/// This is used to return a _single_ object from functions like
/// [`OpenTables::open_tables`](crate::OpenTables::open_tables) rather
/// than the tuple containing the tables itself.
///
/// To replace `tuple.0` style indexing, `field_accessor_functions()`
/// are provided on this trait, which essentially map the object to
/// fields containing the particular database table, for example:
/// ```rust,ignore
/// let tables = open_tables();
///
/// // The accessor function `block_infos()` returns the field
/// // containing an open database table for `BlockInfos`.
/// let _ = tables.block_infos();
/// ```
///
/// See also:
/// - [`TablesMut`]
/// - [`TablesIter`]
pub trait Tables: private::Sealed {
// This expands to creating `fn field_accessor_functions()`
// for each passed `$table` type.
//
// It is essentially a mapping to the field
// containing the proper opened database table.
//
// The function name of the function is
// the table type in `snake_case`, e.g., `block_info_v1s()`.
$(
/// Access an opened
#[doc = concat!("[`", stringify!($table), "`]")]
/// database.
fn [<$table:snake>](&self) -> &impl DatabaseRo<$table>;
)*
/// This returns `true` if all tables are empty.
///
/// # Errors
/// This returns errors on regular database errors.
fn all_tables_empty(&self) -> Result<bool, cuprate_database::RuntimeError>;
}
/// Object containing all opened [`Table`]s in read + iter mode.
///
/// This is the same as [`Tables`] but includes `_iter()` variants.
///
/// Note that this trait is a supertrait of `Tables`,
/// as in it can use all of its functions as well.
///
/// See [`Tables`] for documentation - this trait exists for the same reasons.
pub trait TablesIter: private::Sealed + Tables {
$(
/// Access an opened read-only + iterable
#[doc = concat!("[`", stringify!($table), "`]")]
/// database.
fn [<$table:snake _iter>](&self) -> &(impl DatabaseRo<$table> + DatabaseIter<$table>);
)*
}
/// Object containing all opened [`Table`]s in write mode.
///
/// This is the same as [`Tables`] but for mutable accesses.
///
/// Note that this trait is a supertrait of `Tables`,
/// as in it can use all of its functions as well.
///
/// See [`Tables`] for documentation - this trait exists for the same reasons.
pub trait TablesMut: private::Sealed + Tables {
$(
/// Access an opened
#[doc = concat!("[`", stringify!($table), "`]")]
/// database.
fn [<$table:snake _mut>](&mut self) -> &mut impl DatabaseRw<$table>;
)*
}
// Implement `Sealed` for all table types.
impl<$([<$table:upper>]),*> private::Sealed for ($([<$table:upper>]),*) {}
// This creates a blanket-implementation for
// `(tuple, containing, all, table, types)`.
//
// There is a generic defined here _for each_ `$table` input.
// Specifically, the generic letters are just the table types in UPPERCASE.
// Concretely, this expands to something like:
// ```rust
// impl<BLOCKINFOSV1S, BLOCKINFOSV2S, BLOCKINFOSV3S, [...]>
// ```
impl<$([<$table:upper>]),*> Tables
// We are implementing `Tables` on a tuple that
// contains all those generics specified, i.e.,
// a tuple containing all open table types.
//
// Concretely, this expands to something like:
// ```rust
// (BLOCKINFOSV1S, BLOCKINFOSV2S, BLOCKINFOSV3S, [...])
// ```
// which is just a tuple of the generics defined above.
for ($([<$table:upper>]),*)
where
// This expands to a where bound that asserts each element
// in the tuple implements some database table type.
//
// Concretely, this expands to something like:
// ```rust
// BLOCKINFOSV1S: DatabaseRo<BlockInfoV1s> + DatabaseIter<BlockInfoV1s>,
// BLOCKINFOSV2S: DatabaseRo<BlockInfoV2s> + DatabaseIter<BlockInfoV2s>,
// [...]
// ```
$(
[<$table:upper>]: DatabaseRo<$table>,
)*
{
$(
// The function name of the accessor function is
// the table type in `snake_case`, e.g., `block_info_v1s()`.
#[inline]
fn [<$table:snake>](&self) -> &impl DatabaseRo<$table> {
// The index of the database table in
// the tuple implements the table trait.
&self.$index
}
)*
fn all_tables_empty(&self) -> Result<bool, cuprate_database::RuntimeError> {
$(
if !DatabaseRo::is_empty(&self.$index)? {
return Ok(false);
}
)*
Ok(true)
}
}
// This is the same as the above
// `Tables`, but for `TablesIter`.
impl<$([<$table:upper>]),*> TablesIter
for ($([<$table:upper>]),*)
where
$(
[<$table:upper>]: DatabaseRo<$table> + DatabaseIter<$table>,
)*
{
$(
// The function name of the accessor function is
// the table type in `snake_case` + `_iter`, e.g., `block_info_v1s_iter()`.
#[inline]
fn [<$table:snake _iter>](&self) -> &(impl DatabaseRo<$table> + DatabaseIter<$table>) {
&self.$index
}
)*
}
// This is the same as the above
// `Tables`, but for `TablesMut`.
impl<$([<$table:upper>]),*> TablesMut
for ($([<$table:upper>]),*)
where
$(
[<$table:upper>]: DatabaseRw<$table>,
)*
{
$(
// The function name of the mutable accessor function is
// the table type in `snake_case` + `_mut`, e.g., `block_info_v1s_mut()`.
#[inline]
fn [<$table:snake _mut>](&mut self) -> &mut impl DatabaseRw<$table> {
&mut self.$index
}
)*
}
}};
}
// Input format: $table_type => $index
//
// The $index:
// - Simply increments by 1 for each table
// - Must be 0..
// - Must end at the total amount of table types - 1
//
// Compile errors will occur if these aren't satisfied.
//
// $index is just the `tuple.$index`, as the above [`define_trait_tables`]
// macro has a blanket impl for `(all, table, types, ...)` and we must map
// each type to a tuple index explicitly.
//
// FIXME: there's definitely an automatic way to this :)
define_trait_tables! {
BlockInfos => 0,
BlockBlobs => 1,
BlockHeights => 2,
KeyImages => 3,
NumOutputs => 4,
PrunedTxBlobs => 5,
PrunableHashes => 6,
Outputs => 7,
PrunableTxBlobs => 8,
RctOutputs => 9,
TxBlobs => 10,
TxIds => 11,
TxHeights => 12,
TxOutputs => 13,
TxUnlockTime => 14,
}
//---------------------------------------------------------------------------------------------------- Table macro
/// Create all tables, should be used _once_.
///
/// Generating this macro once and using `$()*` is probably
/// faster for compile times than calling the macro _per_ table.
///
/// All tables are zero-sized table structs, and implement the `Table` trait.
///
/// Table structs are automatically `CamelCase`,
/// and their static string names are automatically `snake_case`.
macro_rules! tables {
(
$(
$(#[$attr:meta])* // Documentation and any `derive`'s.
$table:ident, // The table name + doubles as the table struct name.
$key:ty => // Key type.
$value:ty // Value type.
),* $(,)?
) => {
paste::paste! { $(
// Table struct.
$(#[$attr])*
// The below test show the `snake_case` table name in cargo docs.
#[doc = concat!("- Key: [`", stringify!($key), "`]")]
#[doc = concat!("- Value: [`", stringify!($value), "`]")]
///
/// ## Table Name
/// ```rust
/// # use cuprate_blockchain::{*,tables::*};
/// use cuprate_database::Table;
#[doc = concat!(
"assert_eq!(",
stringify!([<$table:camel>]),
"::NAME, \"",
stringify!([<$table:snake>]),
"\");",
)]
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct [<$table:camel>];
// Implement the `Sealed` in this file.
// Required by `Table`.
impl private::Sealed for [<$table:camel>] {}
// Table trait impl.
impl Table for [<$table:camel>] {
const NAME: &'static str = stringify!([<$table:snake>]);
type Key = $key;
type Value = $value;
}
)* }
};
}
//---------------------------------------------------------------------------------------------------- Tables
// Notes:
// - Keep this sorted A-Z (by table name)
@ -326,23 +29,23 @@ macro_rules! tables {
// - If adding/changing a table also edit:
// - the tests in `src/backend/tests.rs`
// - `call_fn_on_all_tables_or_early_return!()` macro in `src/open_tables.rs`
tables! {
cuprate_database::define_tables! {
/// Serialized block blobs (bytes).
///
/// Contains the serialized version of all blocks.
BlockBlobs,
0 => BlockBlobs,
BlockHeight => BlockBlob,
/// Block heights.
///
/// Contains the height of all blocks.
BlockHeights,
1 => BlockHeights,
BlockHash => BlockHeight,
/// Block information.
///
/// Contains metadata of all blocks.
BlockInfos,
2 => BlockInfos,
BlockHeight => BlockInfo,
/// Set of key images.
@ -351,38 +54,38 @@ tables! {
///
/// This table has `()` as the value type, as in,
/// it is a set of key images.
KeyImages,
3 => KeyImages,
KeyImage => (),
/// Maps an output's amount to the number of outputs with that amount.
///
/// For example, if there are 5 outputs with `amount = 123`
/// then calling `get(123)` on this table will return 5.
NumOutputs,
4 => NumOutputs,
Amount => u64,
/// Pre-RCT output data.
Outputs,
5 => Outputs,
PreRctOutputId => Output,
/// Pruned transaction blobs (bytes).
///
/// Contains the pruned portion of serialized transaction data.
PrunedTxBlobs,
6 => PrunedTxBlobs,
TxId => PrunedBlob,
/// Prunable transaction blobs (bytes).
///
/// Contains the prunable portion of serialized transaction data.
// SOMEDAY: impl when `monero-serai` supports pruning
PrunableTxBlobs,
7 => PrunableTxBlobs,
TxId => PrunableBlob,
/// Prunable transaction hashes.
///
/// Contains the prunable portion of transaction hashes.
// SOMEDAY: impl when `monero-serai` supports pruning
PrunableHashes,
8 => PrunableHashes,
TxId => PrunableHash,
// SOMEDAY: impl a properties table:
@ -392,40 +95,40 @@ tables! {
// StorableString => StorableVec,
/// RCT output data.
RctOutputs,
9 => RctOutputs,
AmountIndex => RctOutput,
/// Transaction blobs (bytes).
///
/// Contains the serialized version of all transactions.
// SOMEDAY: remove when `monero-serai` supports pruning
TxBlobs,
10 => TxBlobs,
TxId => TxBlob,
/// Transaction indices.
///
/// Contains the indices all transactions.
TxIds,
11 => TxIds,
TxHash => TxId,
/// Transaction heights.
///
/// Contains the block height associated with all transactions.
TxHeights,
12 => TxHeights,
TxId => BlockHeight,
/// Transaction outputs.
///
/// Contains the list of `AmountIndex`'s of the
/// outputs associated with all transactions.
TxOutputs,
13 => TxOutputs,
TxId => AmountIndices,
/// Transaction unlock time.
///
/// Contains the unlock time of transactions IF they have one.
/// Transactions without unlock times will not exist in this table.
TxUnlockTime,
14 => TxUnlockTime,
TxId => UnlockTime,
}

View file

@ -11,7 +11,10 @@ use pretty_assertions::assert_eq;
use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner};
use crate::{config::ConfigBuilder, open_tables::OpenTables, tables::Tables};
use crate::{
config::ConfigBuilder,
tables::{OpenTables, Tables},
};
//---------------------------------------------------------------------------------------------------- Struct
/// Named struct to assert the length of all tables.

View file

@ -21,6 +21,7 @@ bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_
bytes = { workspace = true }
cfg-if = { workspace = true }
page_size = { version = "0.6.0" } # Needed for database resizes, they must be a multiple of the OS page size.
paste = { workspace = true }
thiserror = { workspace = true }
# Optional features.

View file

@ -80,6 +80,15 @@ and use `<E: Env>` everywhere it is stored instead. This would allow
generic-backed dynamic runtime selection of the database backend, i.e.
the user can select which database backend they use. -->
# Defining tables
Most likely, your crate building on-top of `cuprate_database` will
want to define all tables used at compile time.
If this is the case, consider using the [`define_tables`] macro
to bulk generate zero-sized marker types that implement [`Table`].
This macro also generates other convenient traits specific to _your_ tables.
# Feature flags
Different database backends are enabled by the feature flags:
- `heed` (LMDB)

View file

@ -78,6 +78,8 @@
clippy::module_inception,
clippy::redundant_pub_crate,
clippy::option_if_let_else,
// unused_crate_dependencies, // false-positive with `paste`
)]
// Allow some lints when running in debug mode.
#![cfg_attr(
@ -105,42 +107,39 @@
// Documentation for each module is located in the respective file.
mod backend;
pub use backend::ConcreteEnv;
mod constants;
mod database;
mod env;
mod error;
mod key;
mod storable;
mod table;
mod tables;
mod transaction;
pub mod config;
pub mod resize;
mod constants;
pub use backend::ConcreteEnv;
pub use constants::{
DATABASE_BACKEND, DATABASE_CORRUPT_MSG, DATABASE_DATA_FILENAME, DATABASE_LOCK_FILENAME,
};
mod database;
pub use database::{DatabaseIter, DatabaseRo, DatabaseRw};
mod env;
pub use env::{Env, EnvInner};
mod error;
pub use error::{InitError, RuntimeError};
pub mod resize;
mod key;
pub use key::{Key, KeyCompare};
mod storable;
pub use storable::{Storable, StorableBytes, StorableStr, StorableVec};
mod table;
pub use table::Table;
mod transaction;
pub use transaction::{TxRo, TxRw};
//---------------------------------------------------------------------------------------------------- Private
#[cfg(test)]
pub(crate) mod tests;
// Used inside public facing macros.
#[doc(hidden)]
pub use paste;
//----------------------------------------------------------------------------------------------------
// HACK: needed to satisfy the `unused_crate_dependencies` lint.
cfg_if::cfg_if! {

View file

@ -0,0 +1,427 @@
//! Database table definition macro.
//---------------------------------------------------------------------------------------------------- Import
//---------------------------------------------------------------------------------------------------- Table macro
/// Define all table types.
///
/// # Purpose
/// This macro allows you to define all database tables in one place.
///
/// A by-product of this macro is that it defines some
/// convenient traits specific to _your_ tables
/// (see [Output](#output)).
///
/// # Inputs
/// This macro expects a list of tables, and their key/value types.
///
/// This syntax is as follows:
///
/// ```rust
/// cuprate_database::define_tables! {
/// /// Any extra attributes you'd like to add to
/// /// this table type, e.g. docs or derives.
///
/// 0 => TableName,
/// // ▲ ▲
/// // │ └─ Table struct name. The macro generates this for you.
/// // │
/// // Incrementing index. This must start at 0
/// // and increment by 1 per table added.
///
/// u8 => u64,
/// // ▲ ▲
/// // │ └─ Table value type.
/// // │
/// // Table key type.
///
/// // Another table.
/// 1 => TableName2,
/// i8 => i64,
/// }
/// ```
///
/// An example:
/// ```rust
/// use cuprate_database::{
/// ConcreteEnv, Table,
/// config::ConfigBuilder,
/// Env, EnvInner,
/// DatabaseRo, DatabaseRw, TxRo, TxRw,
/// };
///
/// // This generates `pub struct Table{1,2,3}`
/// // where all those implement `Table` with
/// // the defined name and key/value types.
/// //
/// // It also generate traits specific to our tables.
/// cuprate_database::define_tables! {
/// 0 => Table1,
/// u32 => i32,
///
/// /// This one has extra docs.
/// 1 => Table2,
/// u64 => (),
///
/// 2 => Table3,
/// i32 => i32,
/// }
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let tmp_dir = tempfile::tempdir()?;
/// # let db_dir = tmp_dir.path().to_owned();
/// # let config = ConfigBuilder::new(db_dir.into()).build();
/// // Open the database.
/// let env = ConcreteEnv::open(config)?;
/// let env_inner = env.env_inner();
///
/// // Open the table we just defined.
/// {
/// let tx_rw = env_inner.tx_rw()?;
/// env_inner.create_db::<Table1>(&tx_rw)?;
/// let mut table = env_inner.open_db_rw::<Table1>(&tx_rw)?;
///
/// // Write data to the table.
/// table.put(&0, &1)?;
///
/// drop(table);
/// TxRw::commit(tx_rw)?;
/// }
///
/// // Read the data, assert it is correct.
/// {
/// let tx_ro = env_inner.tx_ro()?;
/// let table = env_inner.open_db_ro::<Table1>(&tx_ro)?;
/// assert_eq!(table.first()?, (0, 1));
/// }
///
/// // Create all tables at once using the
/// // `OpenTables` trait generated with the
/// // macro above.
/// {
/// let tx_rw = env_inner.tx_rw()?;
/// env_inner.create_tables(&tx_rw)?;
/// TxRw::commit(tx_rw)?;
/// }
///
/// // Open all tables at once.
/// {
/// let tx_ro = env_inner.tx_ro()?;
/// let all_tables = env_inner.open_tables(&tx_ro)?;
/// }
/// # Ok(()) }
/// ```
///
/// # Output
/// This macro:
/// 1. Implements [`Table`](crate::Table) on all your table types
/// 1. Creates a `pub trait Tables` trait (in scope)
/// 1. Creates a `pub trait TablesIter` trait (in scope)
/// 1. Creates a `pub trait TablesMut` trait (in scope)
/// 1. Blanket implements a `(tuples, containing, all, open, database, tables, ...)` for the above traits
/// 1. Creates a `pub trait OpenTables` trait (in scope)
///
/// All table types are zero-sized structs that implement the `Table` trait.
///
/// Table structs are automatically `CamelCase`, and their
/// static string names are automatically `snake_case`.
///
/// For why the table traits + blanket implementation on the tuple exists, see:
/// <https://github.com/Cuprate/cuprate/pull/102#pullrequestreview-1978348871>.
///
/// The `OpenTables` trait lets you open all tables you've defined, at once.
///
/// # Example
/// For examples of usage & output, see
/// [`cuprate_blockchain::tables`](https://github.com/Cuprate/cuprate/blob/main/storage/blockchain/src/tables.rs).
#[macro_export]
macro_rules! define_tables {
(
$(
// Documentation and any `derive`'s.
$(#[$attr:meta])*
// The table name + doubles as the table struct name.
$index:literal => $table:ident,
// Key type => Value type.
$key:ty => $value:ty
),* $(,)?
) => { $crate::paste::paste! {
$(
// Table struct.
$(#[$attr])*
#[doc = concat!("- Key: [`", stringify!($key), "`]")]
#[doc = concat!("- Value: [`", stringify!($value), "`]")]
#[doc = concat!("- Name: `", stringify!([<$table:snake>]), "`")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct [<$table:camel>];
// Table trait impl.
impl $crate::Table for [<$table:camel>] {
const NAME: &'static str = stringify!([<$table:snake>]);
type Key = $key;
type Value = $value;
}
)*
/// Object containing all opened [`Table`](cuprate_database::Table)s in read-only mode.
///
/// This is an encapsulated object that contains all
/// available `Table`'s in read-only mode.
///
/// It is a `Sealed` trait and is only implemented on a
/// `(tuple, containing, all, table, types, ...)`.
///
/// This is used to return a _single_ object from functions like
/// [`OpenTables::open_tables`] rather than the tuple containing the tables itself.
///
/// To replace `tuple.0` style indexing, `field_accessor_functions()`
/// are provided on this trait, which essentially map the object to
/// fields containing the particular database table, for example:
/// ```rust,ignore
/// let tables = open_tables();
///
/// // The accessor function `block_infos()` returns the field
/// // containing an open database table for `BlockInfos`.
/// let _ = tables.block_infos();
/// ```
///
/// See also:
/// - [`TablesMut`]
/// - [`TablesIter`]
pub trait Tables {
// This expands to creating `fn field_accessor_functions()`
// for each passed `$table` type.
//
// It is essentially a mapping to the field
// containing the proper opened database table.
//
// The function name of the function is
// the table type in `snake_case`, e.g., `block_info_v1s()`.
$(
/// Access an opened
#[doc = concat!("[`", stringify!($table), "`]")]
/// database.
fn [<$table:snake>](&self) -> &impl $crate::DatabaseRo<$table>;
)*
/// This returns `true` if all tables are empty.
///
/// # Errors
/// This returns errors on regular database errors.
fn all_tables_empty(&self) -> Result<bool, $crate::RuntimeError>;
}
/// Object containing all opened [`Table`](cuprate_database::Table)s in read + iter mode.
///
/// This is the same as [`Tables`] but includes `_iter()` variants.
///
/// Note that this trait is a supertrait of `Tables`,
/// as in it can use all of its functions as well.
///
/// See [`Tables`] for documentation - this trait exists for the same reasons.
pub trait TablesIter: Tables {
$(
/// Access an opened read-only + iterable
#[doc = concat!("[`", stringify!($table), "`]")]
/// database.
fn [<$table:snake _iter>](&self) -> &(impl $crate::DatabaseRo<$table> + $crate::DatabaseIter<$table>);
)*
}
/// Object containing all opened [`Table`](cuprate_database::Table)s in write mode.
///
/// This is the same as [`Tables`] but for mutable accesses.
///
/// Note that this trait is a supertrait of `Tables`,
/// as in it can use all of its functions as well.
///
/// See [`Tables`] for documentation - this trait exists for the same reasons.
pub trait TablesMut: Tables {
$(
/// Access an opened
#[doc = concat!("[`", stringify!($table), "`]")]
/// database.
fn [<$table:snake _mut>](&mut self) -> &mut impl $crate::DatabaseRw<$table>;
)*
}
// This creates a blanket-implementation for
// `(tuple, containing, all, table, types)`.
//
// There is a generic defined here _for each_ `$table` input.
// Specifically, the generic letters are just the table types in UPPERCASE.
// Concretely, this expands to something like:
// ```rust
// impl<BLOCKINFOSV1S, BLOCKINFOSV2S, BLOCKINFOSV3S, [...]>
// ```
impl<$([<$table:upper>]),*> Tables
// We are implementing `Tables` on a tuple that
// contains all those generics specified, i.e.,
// a tuple containing all open table types.
//
// Concretely, this expands to something like:
// ```rust
// (BLOCKINFOSV1S, BLOCKINFOSV2S, BLOCKINFOSV3S, [...])
// ```
// which is just a tuple of the generics defined above.
for ($([<$table:upper>]),*)
where
// This expands to a where bound that asserts each element
// in the tuple implements some database table type.
//
// Concretely, this expands to something like:
// ```rust
// BLOCKINFOSV1S: DatabaseRo<BlockInfoV1s> + DatabaseIter<BlockInfoV1s>,
// BLOCKINFOSV2S: DatabaseRo<BlockInfoV2s> + DatabaseIter<BlockInfoV2s>,
// [...]
// ```
$(
[<$table:upper>]: $crate::DatabaseRo<$table>,
)*
{
$(
// The function name of the accessor function is
// the table type in `snake_case`, e.g., `block_info_v1s()`.
#[inline]
fn [<$table:snake>](&self) -> &impl $crate::DatabaseRo<$table> {
// The index of the database table in
// the tuple implements the table trait.
&self.$index
}
)*
fn all_tables_empty(&self) -> Result<bool, $crate::RuntimeError> {
$(
if !$crate::DatabaseRo::is_empty(&self.$index)? {
return Ok(false);
}
)*
Ok(true)
}
}
// This is the same as the above
// `Tables`, but for `TablesIter`.
impl<$([<$table:upper>]),*> TablesIter
for ($([<$table:upper>]),*)
where
$(
[<$table:upper>]: $crate::DatabaseRo<$table> + $crate::DatabaseIter<$table>,
)*
{
$(
// The function name of the accessor function is
// the table type in `snake_case` + `_iter`, e.g., `block_info_v1s_iter()`.
#[inline]
fn [<$table:snake _iter>](&self) -> &(impl $crate::DatabaseRo<$table> + $crate::DatabaseIter<$table>) {
&self.$index
}
)*
}
// This is the same as the above
// `Tables`, but for `TablesMut`.
impl<$([<$table:upper>]),*> TablesMut
for ($([<$table:upper>]),*)
where
$(
[<$table:upper>]: $crate::DatabaseRw<$table>,
)*
{
$(
// The function name of the mutable accessor function is
// the table type in `snake_case` + `_mut`, e.g., `block_info_v1s_mut()`.
#[inline]
fn [<$table:snake _mut>](&mut self) -> &mut impl $crate::DatabaseRw<$table> {
&mut self.$index
}
)*
}
/// Open all tables at once.
///
/// This trait encapsulates the functionality of opening all tables at once.
/// It can be seen as the "constructor" for the [`Tables`] object.
///
/// Note that this is already implemented on [`cuprate_database::EnvInner`], thus:
/// - You don't need to implement this
/// - It can be called using `env_inner.open_tables()` notation
pub trait OpenTables<'env> {
/// The read-only transaction type of the backend.
type Ro<'a>;
/// The read-write transaction type of the backend.
type Rw<'a>;
/// Open all tables in read/iter mode.
///
/// This calls [`cuprate_database::EnvInner::open_db_ro`] on all database tables
/// and returns a structure that allows access to all tables.
///
/// # Errors
/// This will only return [`cuprate_database::RuntimeError::Io`] if it errors.
///
/// # Invariant
/// All tables should be created with a crate-specific open function.
///
/// TODO: explain why
fn open_tables(&self, tx_ro: &Self::Ro<'_>) -> Result<impl TablesIter, $crate::RuntimeError>;
/// Open all tables in read-write mode.
///
/// This calls [`cuprate_database::EnvInner::open_db_rw`] on all database tables
/// and returns a structure that allows access to all tables.
///
/// # Errors
/// This will only return [`cuprate_database::RuntimeError::Io`] on errors.
fn open_tables_mut(&self, tx_rw: &Self::Rw<'_>) -> Result<impl TablesMut, $crate::RuntimeError>;
/// Create all database tables.
///
/// This will create all the defined [`Table`](cuprate_database::Table)s.
///
/// # Errors
/// This will only return [`cuprate_database::RuntimeError::Io`] on errors.
fn create_tables(&self, tx_rw: &Self::Rw<'_>) -> Result<(), $crate::RuntimeError>;
}
impl<'env, Ei> OpenTables<'env> for Ei
where
Ei: $crate::EnvInner<'env>,
{
type Ro<'a> = <Ei as $crate::EnvInner<'env>>::Ro<'a>;
type Rw<'a> = <Ei as $crate::EnvInner<'env>>::Rw<'a>;
fn open_tables(&self, tx_ro: &Self::Ro<'_>) -> Result<impl TablesIter, $crate::RuntimeError> {
Ok(($(
Self::open_db_ro::<[<$table:camel>]>(self, tx_ro)?,
)*))
}
fn open_tables_mut(&self, tx_rw: &Self::Rw<'_>) -> Result<impl TablesMut, $crate::RuntimeError> {
Ok(($(
Self::open_db_rw::<[<$table:camel>]>(self, tx_rw)?,
)*))
}
fn create_tables(&self, tx_rw: &Self::Rw<'_>) -> Result<(), $crate::RuntimeError> {
let result = Ok(($(
Self::create_db::<[<$table:camel>]>(self, tx_rw),
)*));
match result {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
}
}};
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}