mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-12-26 21:50:09 +00:00
eead49beb0
* cargo.toml: transfer existing lints * rpc/interface: lints * rpc/json-rpc: lints * rpc/types: lints * storage/blockchain: lints * rpc/types: fix lints * cargo.toml: fix lint group priority * storage/blockchain: fix lints * fix misc lints * storage/database: fixes * storage/txpool: opt in lints + fixes * types: opt in + fixes * helper: opt in + fixes * types: remove borsh * rpc/interface: fix test * test fixes * database: fix lints * fix lint * tabs -> spaces * blockchain: `config/` -> `config.rs`
330 lines
11 KiB
Rust
330 lines
11 KiB
Rust
//! Abstracted database environment; `trait Env`.
|
|
|
|
//---------------------------------------------------------------------------------------------------- Import
|
|
use std::num::NonZeroUsize;
|
|
|
|
use crate::{
|
|
config::Config,
|
|
database::{DatabaseIter, DatabaseRo, DatabaseRw},
|
|
error::{InitError, RuntimeError},
|
|
resize::ResizeAlgorithm,
|
|
table::Table,
|
|
transaction::{TxRo, TxRw},
|
|
};
|
|
|
|
//---------------------------------------------------------------------------------------------------- Env
|
|
/// Database environment abstraction.
|
|
///
|
|
/// Essentially, the functions that can be called on [`ConcreteEnv`](crate::ConcreteEnv).
|
|
///
|
|
/// # `Drop`
|
|
/// Objects that implement [`Env`] _should_ probably
|
|
/// [`Env::sync`] in their drop implementations,
|
|
/// although, no invariant relies on this (yet).
|
|
///
|
|
/// # Lifetimes
|
|
/// The lifetimes associated with `Env` have a sequential flow:
|
|
/// ```text
|
|
/// Env -> Tx -> Database
|
|
/// ```
|
|
///
|
|
/// As in:
|
|
/// - open database tables only live as long as...
|
|
/// - transactions which only live as long as the...
|
|
/// - database environment
|
|
pub trait Env: Sized {
|
|
//------------------------------------------------ Constants
|
|
/// Does the database backend need to be manually
|
|
/// resized when the memory-map is full?
|
|
///
|
|
/// # Invariant
|
|
/// If this is `false`, that means this [`Env`]
|
|
/// must _never_ return a [`RuntimeError::ResizeNeeded`].
|
|
///
|
|
/// If this is `true`, [`Env::resize_map`] & [`Env::current_map_size`]
|
|
/// _must_ be re-implemented, as it just panics by default.
|
|
const MANUAL_RESIZE: bool;
|
|
|
|
/// Does the database backend forcefully sync/flush
|
|
/// to disk on every transaction commit?
|
|
///
|
|
/// This is used as an optimization.
|
|
const SYNCS_PER_TX: bool;
|
|
|
|
//------------------------------------------------ Types
|
|
/// The struct representing the actual backend's database environment.
|
|
///
|
|
/// This is used as the `self` in [`EnvInner`] functions, so whatever
|
|
/// this type is, is what will be accessible from those functions.
|
|
///
|
|
// # HACK
|
|
// For `heed`, this is just `heed::Env`, for `redb` this is
|
|
// `(redb::Database, redb::Durability)` as each transaction
|
|
// needs the sync mode set during creation.
|
|
type EnvInner<'env>: EnvInner<'env>
|
|
where
|
|
Self: 'env;
|
|
|
|
/// The read-only transaction type of the backend.
|
|
type TxRo<'env>: TxRo<'env>
|
|
where
|
|
Self: 'env;
|
|
|
|
/// The read/write transaction type of the backend.
|
|
type TxRw<'env>: TxRw<'env>
|
|
where
|
|
Self: 'env;
|
|
|
|
//------------------------------------------------ Required
|
|
/// Open the database environment, using the passed [`Config`].
|
|
///
|
|
/// # Invariants
|
|
/// This function does not create any tables.
|
|
///
|
|
/// You must create all possible tables with [`EnvInner::create_db`]
|
|
/// before attempting to open any.
|
|
///
|
|
/// # Errors
|
|
/// 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`].
|
|
fn open(config: Config) -> Result<Self, InitError>;
|
|
|
|
/// Return the [`Config`] that this database was [`Env::open`]ed with.
|
|
fn config(&self) -> &Config;
|
|
|
|
/// Fully sync the database caches to disk.
|
|
///
|
|
/// # Invariant
|
|
/// This must **fully** and **synchronously** flush the database data to disk.
|
|
///
|
|
/// I.e., after this function returns, there must be no doubts
|
|
/// that the data isn't synced yet, it _must_ be synced.
|
|
///
|
|
// FIXME: either this invariant or `sync()` itself will most
|
|
// likely be removed/changed after `SyncMode` is finalized.
|
|
///
|
|
/// # Errors
|
|
/// If there is a synchronization error, this should return an error.
|
|
fn sync(&self) -> Result<(), RuntimeError>;
|
|
|
|
/// Resize the database's memory map to a
|
|
/// new (bigger) size using a [`ResizeAlgorithm`].
|
|
///
|
|
/// By default, this function will use the `ResizeAlgorithm` in [`Env::config`].
|
|
///
|
|
/// If `resize_algorithm` is `Some`, that will be used instead.
|
|
///
|
|
/// This function returns the _new_ memory map size in bytes.
|
|
///
|
|
/// # Invariant
|
|
/// This function _must_ be re-implemented if [`Env::MANUAL_RESIZE`] is `true`.
|
|
///
|
|
/// Otherwise, this function will panic with `unreachable!()`.
|
|
#[allow(unused_variables)]
|
|
fn resize_map(&self, resize_algorithm: Option<ResizeAlgorithm>) -> NonZeroUsize {
|
|
unreachable!()
|
|
}
|
|
|
|
/// What is the _current_ size of the database's memory map in bytes?
|
|
///
|
|
/// # Invariant
|
|
/// 1. This function _must_ be re-implemented if [`Env::MANUAL_RESIZE`] is `true`.
|
|
/// 2. This function must be accurate, as [`Env::resize_map()`] may depend on it.
|
|
fn current_map_size(&self) -> usize {
|
|
unreachable!()
|
|
}
|
|
|
|
/// Return the [`Env::EnvInner`].
|
|
///
|
|
/// # Locking behavior
|
|
/// When using the `heed` backend, [`Env::EnvInner`] is a
|
|
/// `RwLockReadGuard`, i.e., calling this function takes a
|
|
/// read lock on the `heed::Env`.
|
|
///
|
|
/// Be aware of this, as other functions (currently only
|
|
/// [`Env::resize_map`]) will take a _write_ lock.
|
|
fn env_inner(&self) -> Self::EnvInner<'_>;
|
|
|
|
//------------------------------------------------ Provided
|
|
/// Return the amount of actual of bytes the database is taking up on disk.
|
|
///
|
|
/// This is the current _disk_ value in bytes, not the memory map.
|
|
///
|
|
/// # Errors
|
|
/// This will error if either:
|
|
///
|
|
/// - [`std::fs::File::open`]
|
|
/// - [`std::fs::File::metadata`]
|
|
///
|
|
/// failed on the database file on disk.
|
|
fn disk_size_bytes(&self) -> std::io::Result<u64> {
|
|
// We have the direct PATH to the file,
|
|
// no need to use backend-specific functions.
|
|
//
|
|
// INVARIANT: as we are only accessing the metadata of
|
|
// the file and not reading the bytes, it should be
|
|
// fine even with a memory mapped file being actively
|
|
// written to.
|
|
Ok(std::fs::File::open(&self.config().db_file)?
|
|
.metadata()?
|
|
.len())
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------------- DatabaseRo
|
|
/// Document the INVARIANT that the `heed` backend
|
|
/// must use [`EnvInner::create_db`] when initially
|
|
/// opening/creating tables.
|
|
macro_rules! doc_heed_create_db_invariant {
|
|
() => {
|
|
r#"The first time you open/create tables, you _must_ use [`EnvInner::create_db`]
|
|
to set the proper flags / [`Key`](crate::Key) comparison for the `heed` backend.
|
|
|
|
Subsequent table opens will follow the flags/ordering, but only if
|
|
[`EnvInner::create_db`] was the _first_ function to open/create it."#
|
|
};
|
|
}
|
|
|
|
/// The inner [`Env`] type.
|
|
///
|
|
/// This type is created with [`Env::env_inner`] and represents
|
|
/// the type able to generate transactions and open tables.
|
|
///
|
|
/// # Locking behavior
|
|
/// As noted in `Env::env_inner`, this is a `RwLockReadGuard`
|
|
/// when using the `heed` backend, be aware of this and do
|
|
/// not hold onto an `EnvInner` for a long time.
|
|
///
|
|
/// # Tables
|
|
/// Note that when opening tables with [`EnvInner::open_db_ro`],
|
|
/// they must be created first or else it will return error.
|
|
///
|
|
/// See [`EnvInner::create_db`] for creating tables.
|
|
///
|
|
/// # Invariant
|
|
#[doc = doc_heed_create_db_invariant!()]
|
|
pub trait EnvInner<'env> {
|
|
/// The read-only transaction type of the backend.
|
|
///
|
|
/// `'tx` is the lifetime of the transaction itself.
|
|
type Ro<'tx>: TxRo<'tx>;
|
|
/// The read-write transaction type of the backend.
|
|
///
|
|
/// `'tx` is the lifetime of the transaction itself.
|
|
type Rw<'tx>: TxRw<'tx>;
|
|
|
|
/// Create a read-only transaction.
|
|
///
|
|
/// # Errors
|
|
/// This will only return [`RuntimeError::Io`] if it errors.
|
|
fn tx_ro(&self) -> Result<Self::Ro<'_>, RuntimeError>;
|
|
|
|
/// Create a read/write transaction.
|
|
///
|
|
/// # Errors
|
|
/// This will only return [`RuntimeError::Io`] if it errors.
|
|
fn tx_rw(&self) -> Result<Self::Rw<'_>, RuntimeError>;
|
|
|
|
/// Open a database in read-only mode.
|
|
///
|
|
/// The returned value can have [`DatabaseRo`]
|
|
/// & [`DatabaseIter`] functions called on it.
|
|
///
|
|
/// This will open the database [`Table`]
|
|
/// passed as a generic to this function.
|
|
///
|
|
/// ```rust
|
|
/// # use cuprate_database::{
|
|
/// # ConcreteEnv,
|
|
/// # config::ConfigBuilder,
|
|
/// # Env, EnvInner,
|
|
/// # DatabaseRo, DatabaseRw, TxRo, TxRw,
|
|
/// # };
|
|
/// # 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();
|
|
/// # let env = ConcreteEnv::open(config)?;
|
|
/// #
|
|
/// # struct Table;
|
|
/// # impl cuprate_database::Table for Table {
|
|
/// # const NAME: &'static str = "table";
|
|
/// # type Key = u8;
|
|
/// # type Value = u64;
|
|
/// # }
|
|
/// #
|
|
/// # let env_inner = env.env_inner();
|
|
/// # let tx_rw = env_inner.tx_rw()?;
|
|
/// # env_inner.create_db::<Table>(&tx_rw)?;
|
|
/// # TxRw::commit(tx_rw);
|
|
/// #
|
|
/// # let tx_ro = env_inner.tx_ro()?;
|
|
/// let db = env_inner.open_db_ro::<Table>(&tx_ro);
|
|
/// // ^ ^
|
|
/// // database table table metadata
|
|
/// // (name, key/value type)
|
|
/// # Ok(()) }
|
|
/// ```
|
|
///
|
|
/// # 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`].
|
|
///
|
|
/// # Invariant
|
|
#[doc = doc_heed_create_db_invariant!()]
|
|
fn open_db_ro<T: Table>(
|
|
&self,
|
|
tx_ro: &Self::Ro<'_>,
|
|
) -> Result<impl DatabaseRo<T> + DatabaseIter<T>, RuntimeError>;
|
|
|
|
/// Open a database in read/write mode.
|
|
///
|
|
/// All [`DatabaseRo`] functions are also callable
|
|
/// with the returned [`DatabaseRw`] structure.
|
|
///
|
|
/// Note that [`DatabaseIter`] functions are _not_
|
|
/// available to [`DatabaseRw`] structures.
|
|
///
|
|
/// This will open the database [`Table`]
|
|
/// passed as a generic to this function.
|
|
///
|
|
/// # Errors
|
|
/// This will only return [`RuntimeError::Io`] on errors.
|
|
///
|
|
/// # Invariant
|
|
#[doc = doc_heed_create_db_invariant!()]
|
|
fn open_db_rw<T: Table>(
|
|
&self,
|
|
tx_rw: &Self::Rw<'_>,
|
|
) -> Result<impl DatabaseRw<T>, RuntimeError>;
|
|
|
|
/// Create a database table.
|
|
///
|
|
/// This will create the database [`Table`] passed as a generic to this function.
|
|
///
|
|
/// # Errors
|
|
/// This will only return [`RuntimeError::Io`] on errors.
|
|
///
|
|
/// # Invariant
|
|
#[doc = doc_heed_create_db_invariant!()]
|
|
fn create_db<T: Table>(&self, tx_rw: &Self::Rw<'_>) -> Result<(), RuntimeError>;
|
|
|
|
/// Clear all `(key, value)`'s from a database table.
|
|
///
|
|
/// This will delete all key and values in the passed
|
|
/// `T: Table`, but the table itself will continue to exist.
|
|
///
|
|
/// Note that this operation is tied to `tx_rw`, as such this
|
|
/// function's effects can be aborted using [`TxRw::abort`].
|
|
///
|
|
/// # Errors
|
|
/// This will 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 clear_db<T: Table>(&self, tx_rw: &mut Self::Rw<'_>) -> Result<(), RuntimeError>;
|
|
}
|