From f7bd1304e2db1cb45492ca962fe936469cca5403 Mon Sep 17 00:00:00 2001 From: hinto-janai Date: Sat, 17 Feb 2024 08:00:14 -0500 Subject: [PATCH] database: Errors (#62) * error: add variants to `RuntimeError` * error: remove `` generic we can just map each backend error variant <-> our error as needed * backend: impl `From` for `RuntimeError` * add `Never` type to allow foreign trait implementations This is a newtype to workaround `sanakirja::Storable` not being able to be implemented on `std::convert::Infallible` due to "foreign trait on foreign type" rules. * revert 0342848, fix `sanakirja` trait bounds K/V will always be `[u8]`, not the concrete type so it does not need to be bounded. * backend: fix `sanakijra` K/V generics * sanakirja: add `error.rs` * error: remove serde traits * heed: add `todo!()` for error mappings * error: add `Corrupt` variant * sanakirja: finish error mappings * heed: finish error mappings * error: add to error types * env: use `InitError` in `Env::open()` * error: docs * heed: remove `serde.rs` Not needed if all K/V's stored are `[u8]`. * heed: use `heed::types::Bytes` as K/V * database: docs * heed: impl `From` for `InitError` * sanakirja: impl `From` for `InitError` * error: fix doc warnings * heed: fix `clippy::match_same_arms` in error match * readme: add TODO * error: add `BoxError`, and fatal/unknown variants * heed: use `Fatal/Unknown` variants in errors * sanakirja: use `Fatal/Unknown` variants in errors * clippy * sanakijra: remove `fallible_impl_from` * error: remove `RuntimeError::InvalidVersion` * error: remove `RuntimeError` variants that should panic * error: remove `InitError::Fatal` We will exit on all errors regardless. Any non-enumrated variants will use `InitError::Unknown`. * heed: fix error mappings * constants: add `CUPRATE_DATABASE_CORRUPT_MSG` * sanakijra: fix error mappings * heed: fix import * comments/docs * key: fix docs --- database/Cargo.toml | 2 +- database/README.md | 38 ++++-- database/src/backend/heed/database.rs | 4 +- database/src/backend/heed/env.rs | 12 +- database/src/backend/heed/error.rs | 131 +++++++++++++++++++++ database/src/backend/heed/mod.rs | 4 +- database/src/backend/heed/serde.rs | 53 --------- database/src/backend/heed/types.rs | 8 ++ database/src/backend/sanakirja/database.rs | 10 +- database/src/backend/sanakirja/env.rs | 16 ++- database/src/backend/sanakirja/error.rs | 51 ++++++++ database/src/backend/sanakirja/mod.rs | 4 + database/src/backend/sanakirja/types.rs | 6 + database/src/constants.rs | 18 ++- database/src/env.rs | 4 +- database/src/error.rs | 110 ++++++++++------- database/src/key.rs | 36 ++---- database/src/lib.rs | 7 +- database/src/table.rs | 12 +- 19 files changed, 360 insertions(+), 166 deletions(-) create mode 100644 database/src/backend/heed/error.rs delete mode 100644 database/src/backend/heed/serde.rs create mode 100644 database/src/backend/heed/types.rs create mode 100644 database/src/backend/sanakirja/error.rs create mode 100644 database/src/backend/sanakirja/types.rs diff --git a/database/Cargo.toml b/database/Cargo.toml index 0caa358..91930c9 100644 --- a/database/Cargo.toml +++ b/database/Cargo.toml @@ -30,4 +30,4 @@ tower = { workspace = true, features = ["full"], optional = true } borsh = { workspace = true, optional = true } heed = { git = "https://github.com/Cuprate/heed", rev = "5aa75b7", optional = true } sanakirja = { version = "1.4.0", optional = true } -serde = { workspace = true, optional = true } \ No newline at end of file +serde = { workspace = true, optional = true } diff --git a/database/README.md b/database/README.md index afaba23..622a146 100644 --- a/database/README.md +++ b/database/README.md @@ -1,10 +1,13 @@ # Database Cuprate's database implementation. +TODO: document `Pod` and how databases use (de)serialize objects when storing/fetching, essentially using `<[u8], [u8]>`. + 1. [Documentation](#documentation) 1. [File Structure](#file-structure) - [`src/`](#src) + - [`src/ops`](#src-ops) - [`src/service/`](#src-service) - [`src/backend/`](#src-backend) 1. [Backends](#backends) @@ -60,7 +63,7 @@ Note that `lib.rs/mod.rs` files are purely for re-exporting/visibility/lints, an ## `src/` The top-level `src/` files. -| File/Folder | Purpose | +| File | Purpose | |------------------|---------| | `constants.rs` | General constants used throughout `cuprate-database` | `database.rs` | Abstracted database; `trait Database` @@ -73,15 +76,31 @@ The top-level `src/` files. | `tables.rs` | All the table definitions used by `cuprate-database` | `transaction.rs` | Database transaction abstraction; `trait RoTx`, `trait RwTx` +## `src/ops/` +This folder contains the `cupate_database::ops` module. + +TODO: more detailed descriptions. + +| File | Purpose | +|-----------------|---------| +| `alt_block.rs` | Alternative blocks +| `block.rs` | Blocks +| `blockchain.rs` | Blockchain-related +| `output.rs` | Outputs +| `property.rs` | Properties +| `spent_key.rs` | Spent keys +| `tx.rs` | Transactions + ## `src/service/` This folder contains the `cupate_database::service` module. -| File/Folder | Purpose | +| File | Purpose | |----------------|---------| | `free.rs` | General free functions used (related to `cuprate_database::service`) | `read.rs` | Read thread-pool definitions and logic | `request.rs` | Read/write `Request`s to the database | `response.rs` | Read/write `Response`'s from the database +| `tests.rs` | Thread-pool tests and test helper functions | `write.rs` | Write thread-pool definitions and logic ## `src/backend/` @@ -89,25 +108,20 @@ This folder contains the actual database crates used as the backend for `cuprate Each backend has its own folder. -| File/Folder | Purpose | +| Folder | Purpose | |--------------|---------| | `heed/` | Backend using using forked [`heed`](https://github.com/Cuprate/heed) | `sanakirja/` | Backend using [`sanakirja`](https://docs.rs/sanakirja) -### `src/backend/heed/` -| File/Folder | Purpose | -|------------------|---------| -| `database.rs` | Implementation of `trait Database` -| `env.rs` | Implementation of `trait Env` -| `serde.rs` | Data (de)serialization implementations -| `transaction.rs` | Implementation of `trait RoTx/RwTx` +All backends follow the same file structure: -### `src/backend/sanakirja/` -| File/Folder | Purpose | +| File | Purpose | |------------------|---------| | `database.rs` | Implementation of `trait Database` | `env.rs` | Implementation of `trait Env` +| `error.rs` | Implementation of backend's errors to `cuprate_database`'s error types | `transaction.rs` | Implementation of `trait RoTx/RwTx` +| `types.rs` | Type aliases for long backend-specific types # Backends `cuprate-database`'s `trait`s abstract over various actual databases. diff --git a/database/src/backend/heed/database.rs b/database/src/backend/heed/database.rs index 5ea6274..3a310fb 100644 --- a/database/src/backend/heed/database.rs +++ b/database/src/backend/heed/database.rs @@ -1,10 +1,10 @@ //! Implementation of `trait Database` for `heed`. //---------------------------------------------------------------------------------------------------- Import -use crate::{database::Database, error::RuntimeError, table::Table}; +use crate::{backend::heed::types::HeedDb, database::Database, error::RuntimeError, table::Table}; //---------------------------------------------------------------------------------------------------- Database Impls -impl Database for heed::Database { +impl Database for HeedDb { type RoTx<'db> = heed::RoTxn<'db>; type RwTx<'db> = heed::RwTxn<'db>; diff --git a/database/src/backend/heed/env.rs b/database/src/backend/heed/env.rs index 4a3545a..7026f1b 100644 --- a/database/src/backend/heed/env.rs +++ b/database/src/backend/heed/env.rs @@ -3,7 +3,13 @@ //---------------------------------------------------------------------------------------------------- Import use std::path::Path; -use crate::{database::Database, env::Env, error::RuntimeError, table::Table}; +use crate::{ + backend::heed::types::HeedDb, + database::Database, + env::Env, + error::{InitError, RuntimeError}, + table::Table, +}; //---------------------------------------------------------------------------------------------------- Env /// A strongly typed, concrete database environment, backed by `heed`. @@ -23,7 +29,7 @@ impl Env for ConcreteEnv { /// TODO /// # Errors /// TODO - fn open>(path: P) -> Result { + fn open>(path: P) -> Result { todo!() } @@ -70,7 +76,7 @@ impl Env for ConcreteEnv { &self, to_rw: &Self::RoTx<'_>, ) -> Result, RuntimeError> { - let tx: heed::Database = todo!(); + let tx: HeedDb = todo!(); Ok(tx) } } diff --git a/database/src/backend/heed/error.rs b/database/src/backend/heed/error.rs new file mode 100644 index 0000000..d371217 --- /dev/null +++ b/database/src/backend/heed/error.rs @@ -0,0 +1,131 @@ +//! Conversion from `heed::Error` -> `cuprate_database::RuntimeError`. + +//---------------------------------------------------------------------------------------------------- Use +use crate::constants::CUPRATE_DATABASE_CORRUPT_MSG; + +//---------------------------------------------------------------------------------------------------- InitError +impl From for crate::InitError { + fn from(error: heed::Error) -> Self { + use heed::Error as E1; + use heed::MdbError as E2; + + // Reference of all possible errors `heed` will return + // upon using [`heed::EnvOpenOptions::open`]: + // + match error { + E1::Io(io_error) => Self::Io(io_error), + E1::DatabaseClosing => Self::ShuttingDown, + + // LMDB errors. + E1::Mdb(mdb_error) => match mdb_error { + E2::Invalid => Self::Invalid, + E2::VersionMismatch => Self::InvalidVersion, + E2::Other(c_int) => Self::Unknown(Box::new(mdb_error)), + + // "Located page was wrong type". + // + // + // "Requested page not found - this usually indicates corruption." + // + E2::Corrupted | E2::PageNotFound => Self::Corrupt, + + // These errors shouldn't be returned on database init. + E2::Incompatible + | E2::BadTxn + | E2::Problem + | E2::KeyExist + | E2::NotFound + | E2::MapFull + | E2::ReadersFull + | E2::PageFull + | E2::DbsFull + | E2::TlsFull + | E2::TxnFull + | E2::CursorFull + | E2::MapResized + | E2::BadRslot + | E2::BadValSize + | E2::BadDbi + | E2::Panic => Self::Unknown(Box::new(mdb_error)), + }, + + E1::InvalidDatabaseTyping + | E1::BadOpenOptions { .. } + | E1::Encoding(_) + | E1::Decoding(_) => Self::Unknown(Box::new(error)), + } + } +} + +//---------------------------------------------------------------------------------------------------- RuntimeError +#[allow(clippy::fallible_impl_from)] // We need to panic sometimes. +impl From for crate::RuntimeError { + /// # Panics + /// This will panic on unrecoverable errors for safety. + fn from(error: heed::Error) -> Self { + use heed::Error as E1; + use heed::MdbError as E2; + + match error { + // I/O errors. + E1::Io(io_error) => Self::Io(io_error), + + // LMDB errors. + E1::Mdb(mdb_error) => match mdb_error { + E2::KeyExist => Self::KeyExists, + E2::NotFound => Self::KeyNotFound, + + // Corruption errors, these have special panic messages. + // + // "Located page was wrong type". + // + // + // "Requested page not found - this usually indicates corruption." + // + E2::Corrupted | E2::PageNotFound => panic!("{mdb_error:?}\n{CUPRATE_DATABASE_CORRUPT_MSG}"), + + // These errors should not occur, and if they do, + // the best thing `cuprate_database` can do for + // safety is to panic right here. + E2::Panic + | E2::PageFull + | E2::Other(_) + | E2::BadTxn + | E2::Problem + | E2::Invalid + | E2::TlsFull + | E2::TxnFull + | E2::BadRslot + | E2::VersionMismatch + | E2::BadDbi => panic!("{mdb_error:?}"), + + // These errors are the same as above, but instead + // of being errors we can't control, these are errors + // that only happen if we write incorrect code. + E2::MapFull // Resize the map when needed. + | E2::ReadersFull // Don't spawn too many reader threads. + | E2::DbsFull // Don't create too many database tables. + | E2::CursorFull // Don't do crazy multi-nested LMDB cursor stuff. + | E2::MapResized // Resize the map when needed. + | E2::Incompatible // + | E2::BadValSize // Unsupported size of key/DB name/data, or wrong DUP_FIXED size. + => panic!("fix the database code! {mdb_error:?}"), + }, + + // Database is shutting down. + E1::DatabaseClosing => Self::ShuttingDown, + + // Only if we write incorrect code. + E1::InvalidDatabaseTyping + | E1::BadOpenOptions { .. } + | E1::Encoding(_) + | E1::Decoding(_) => panic!("fix the database code! {error:?}"), + } + } +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/database/src/backend/heed/mod.rs b/database/src/backend/heed/mod.rs index be88433..760fc72 100644 --- a/database/src/backend/heed/mod.rs +++ b/database/src/backend/heed/mod.rs @@ -4,6 +4,6 @@ mod env; pub use env::ConcreteEnv; mod database; - -mod serde; +mod error; mod transaction; +mod types; diff --git a/database/src/backend/heed/serde.rs b/database/src/backend/heed/serde.rs deleted file mode 100644 index eb127c8..0000000 --- a/database/src/backend/heed/serde.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! (De)serialization trait implementations for `heed`. - -//---------------------------------------------------------------------------------------------------- Import -use std::borrow::Cow; - -use crate::pod::Pod; - -//---------------------------------------------------------------------------------------------------- Serde -/// Implement `heed` (de)serialization traits -/// for anything that implements [`crate::pod::Pod`]. -/// -/// Blanket implementation breaks orphan impl rules, so this is used instead. -macro_rules! impl_heed { - ($( - $name:ident => // The name that implements [`crate::pod::Pod`] - $t:ident // The type to (de)serialize into/from - ),* $(,)?) => { - $( - // `heed` Encode. - impl<'a> heed::BytesEncode<'a> for $name { - type EItem = $t; - - #[inline] - fn bytes_encode(item: &'a Self::EItem) -> Result, heed::BoxedError> { - Ok(item.into_bytes()) - } - } - - // `heed` Decode. - impl<'a> heed::BytesDecode<'a> for $name { - type DItem = $t; - - #[inline] - fn bytes_decode(bytes: &'a [u8]) -> Result { - Ok(Pod::from_bytes(bytes)) - } - } - )* - }; -} - -/// TODO -struct Test; - -impl_heed! { - Test => u8, -} - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test { - // use super::*; -} diff --git a/database/src/backend/heed/types.rs b/database/src/backend/heed/types.rs new file mode 100644 index 0000000..e123767 --- /dev/null +++ b/database/src/backend/heed/types.rs @@ -0,0 +1,8 @@ +//! `heed` type aliases. + +//---------------------------------------------------------------------------------------------------- Use +use heed::{types::Bytes, Database}; + +//---------------------------------------------------------------------------------------------------- Types +/// The concrete database type for `heed`. +pub(super) type HeedDb = Database; diff --git a/database/src/backend/sanakirja/database.rs b/database/src/backend/sanakirja/database.rs index a4c54fd..9c9b891 100644 --- a/database/src/backend/sanakirja/database.rs +++ b/database/src/backend/sanakirja/database.rs @@ -1,12 +1,14 @@ //! Implementation of `trait Database` for `sanakirja`. //---------------------------------------------------------------------------------------------------- Import -use crate::{database::Database, error::RuntimeError, table::Table}; +use crate::{ + backend::sanakirja::types::SanakirjaDb, database::Database, error::RuntimeError, table::Table, +}; //---------------------------------------------------------------------------------------------------- Database Impls -impl Database for sanakirja::btree::Db { - type RoTx<'db> = sanakirja::Txn<&'_ sanakirja::Env>; - type RwTx<'db> = sanakirja::MutTxn<&'_ sanakirja::Env, ()>; +impl Database for SanakirjaDb { + type RoTx<'db> = sanakirja::Txn<&'db sanakirja::Env>; + type RwTx<'db> = sanakirja::MutTxn<&'db sanakirja::Env, ()>; fn get(&self, ro_tx: &Self::RoTx<'_>, key: &T::Key) -> Result, RuntimeError> { todo!() diff --git a/database/src/backend/sanakirja/env.rs b/database/src/backend/sanakirja/env.rs index e348c64..823a41e 100644 --- a/database/src/backend/sanakirja/env.rs +++ b/database/src/backend/sanakirja/env.rs @@ -3,7 +3,13 @@ //---------------------------------------------------------------------------------------------------- Import use std::path::Path; -use crate::{database::Database, env::Env, error::RuntimeError, table::Table}; +use crate::{ + backend::sanakirja::types::SanakirjaDb, + database::Database, + env::Env, + error::{InitError, RuntimeError}, + table::Table, +}; //---------------------------------------------------------------------------------------------------- ConcreteEnv /// A strongly typed, concrete database environment, backed by `sanakirja`. @@ -26,7 +32,7 @@ impl Env for ConcreteEnv { /// TODO /// # Errors /// TODO - fn open>(path: P) -> Result { + fn open>(path: P) -> Result { todo!() } @@ -72,9 +78,9 @@ impl Env for ConcreteEnv { fn open_database( &self, to_rw: &Self::RoTx<'_>, - ) -> Result>, RuntimeError> { - let tx: sanakirja::btree::Db = todo!(); - Ok(Some(tx)) + ) -> Result, RuntimeError> { + let tx: SanakirjaDb = todo!(); + Ok(tx) } } diff --git a/database/src/backend/sanakirja/error.rs b/database/src/backend/sanakirja/error.rs new file mode 100644 index 0000000..952881b --- /dev/null +++ b/database/src/backend/sanakirja/error.rs @@ -0,0 +1,51 @@ +//! Conversion from `sanakirja::Error` -> `cuprate_database::RuntimeError`. + +//---------------------------------------------------------------------------------------------------- Import +use crate::constants::CUPRATE_DATABASE_CORRUPT_MSG; + +//---------------------------------------------------------------------------------------------------- InitError +impl From for crate::InitError { + fn from(error: sanakirja::Error) -> Self { + use sanakirja::Error as E; + + match error { + E::IO(io_error) => Self::Io(io_error), + E::VersionMismatch => Self::InvalidVersion, + + // A CRC failure essentially means a `sanakirja` page was corrupt. + // + E::Corrupt(_) | E::CRC(_) => Self::Corrupt, + + // A database lock was poisoned. + // + E::Poison => Self::Unknown(Box::new(error)), + } + } +} + +//---------------------------------------------------------------------------------------------------- RuntimeError +#[allow(clippy::fallible_impl_from)] // We need to panic sometimes. +impl From for crate::RuntimeError { + fn from(error: sanakirja::Error) -> Self { + use sanakirja::Error as E; + + match error { + E::IO(io_error) => Self::Io(io_error), + + // A CRC failure essentially means a `sanakirja` page was corrupt. + // + E::Corrupt(_) | E::CRC(_) => panic!("{error:?}\n{CUPRATE_DATABASE_CORRUPT_MSG}"), + + // These errors should not occur, and if they do, + // the best thing `cuprate_database` can do for + // safety is to panic right here. + E::Poison | E::VersionMismatch => panic!("{error:?}"), + } + } +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/database/src/backend/sanakirja/mod.rs b/database/src/backend/sanakirja/mod.rs index 595dece..0ac3d15 100644 --- a/database/src/backend/sanakirja/mod.rs +++ b/database/src/backend/sanakirja/mod.rs @@ -3,6 +3,10 @@ mod env; pub use env::ConcreteEnv; +mod error; + mod database; mod transaction; + +mod types; diff --git a/database/src/backend/sanakirja/types.rs b/database/src/backend/sanakirja/types.rs new file mode 100644 index 0000000..d3383e5 --- /dev/null +++ b/database/src/backend/sanakirja/types.rs @@ -0,0 +1,6 @@ +//! `sanakirja` type aliases. + +//---------------------------------------------------------------------------------------------------- Types +/// The concrete database type for `sanakirja`. +pub(super) type SanakirjaDb = + sanakirja::btree::Db_<[u8], [u8], sanakirja::btree::page_unsized::Page<[u8], [u8]>>; diff --git a/database/src/constants.rs b/database/src/constants.rs index b30cb7e..7a4ffd8 100644 --- a/database/src/constants.rs +++ b/database/src/constants.rs @@ -2,7 +2,7 @@ //---------------------------------------------------------------------------------------------------- Import -//---------------------------------------------------------------------------------------------------- Constants +//---------------------------------------------------------------------------------------------------- Directory/Files /// The directory that contains database-related files. /// /// This is a sub-directory within the Cuprate folder, e.g: @@ -25,6 +25,22 @@ pub const CUPRATE_DATABASE_DIR: &str = "database"; /// ``` pub const CUPRATE_DATABASE_FILE: &str = "data"; +//---------------------------------------------------------------------------------------------------- Error Messages +/// Corrupt database error message. +/// +/// The error message shown to end-users in panic +/// messages if we think the database is corrupted. +/// +/// This is meant to be user-friendly. +pub const CUPRATE_DATABASE_CORRUPT_MSG: &str = r"Cuprate has encountered a fatal error. The database may be corrupted. + +TODO: instructions on: +1. What to do +2. How to fix (re-sync, recover, etc) +3. General advice for preventing corruption +4. etc"; + +//---------------------------------------------------------------------------------------------------- Misc cfg_if::cfg_if! { // If both backends are enabled, fallback to `heed`. // This is useful when using `--all-features`. diff --git a/database/src/env.rs b/database/src/env.rs index c6f05be..e9da72f 100644 --- a/database/src/env.rs +++ b/database/src/env.rs @@ -5,7 +5,7 @@ use std::path::Path; use crate::{ database::Database, - error::RuntimeError, + error::{InitError, RuntimeError}, table::Table, transaction::{RoTx, RwTx}, }; @@ -26,7 +26,7 @@ pub trait Env: Sized { /// TODO /// # Errors /// TODO - fn open>(path: P) -> Result; + fn open>(path: P) -> Result; /// TODO /// # Errors diff --git a/database/src/error.rs b/database/src/error.rs index a0e2c8c..f3990d1 100644 --- a/database/src/error.rs +++ b/database/src/error.rs @@ -2,61 +2,89 @@ //! TODO: `InitError/RuntimeError` are maybe bad names. //---------------------------------------------------------------------------------------------------- Import -use std::{borrow::Cow, fmt::Debug}; +use std::fmt::Debug; -use crate::constants::DATABASE_BACKEND; +#[allow(unused_imports)] // docs +use crate::env::Env; + +//---------------------------------------------------------------------------------------------------- Types +/// Alias for a thread-safe boxed error. +type BoxError = Box; //---------------------------------------------------------------------------------------------------- InitError -/// Database errors that occur during initialization. +/// Errors that occur during ([`Env::open`]). /// -/// `BackendError` is an error specifically from the -/// database backend being used. TODO: this may not -/// be needed if we can convert all error types into -/// "generic" database errors. +/// # Handling +/// As this is a database initialization error, the correct +/// way to handle any of these occurring is probably just to +/// exit the program. +/// +/// There is not much we as Cuprate can do +/// to recover from any of these errors. #[derive(thiserror::Error, Debug)] -pub enum InitError { - /// TODO - #[error("database PATH is inaccessible: {0}")] - Path(std::io::Error), +pub enum InitError { + /// The given `Path/File` existed and was accessible, + /// but was not a valid database file. + #[error("database file exists but is not valid")] + Invalid, - /// TODO - #[error("{DATABASE_BACKEND} error: {0}")] - Backend(BackendError), + /// The given `Path/File` existed, was a valid + /// database, but the version is incorrect. + #[error("database file is valid, but version is incorrect")] + InvalidVersion, - /// TODO + /// I/O error. + #[error("database I/O error: {0}")] + Io(#[from] std::io::Error), + + /// The given `Path/File` existed, + /// was a valid database, but it is corrupt. + #[error("database file is corrupt")] + Corrupt, + + /// The database is currently in the process + /// of shutting down and cannot respond. /// + /// TODO: This might happen if we try to open + /// while we are shutting down, `unreachable!()`? + #[error("database is shutting down")] + ShuttingDown, + /// An unknown error occurred. + /// + /// This is for errors that cannot be recovered from, + /// but we'd still like to panic gracefully. #[error("unknown error: {0}")] - Unknown(Cow<'static, str>), + Unknown(BoxError), } //---------------------------------------------------------------------------------------------------- RuntimeError -/// Database errors that occur _after_ successful initialization. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr( - feature = "borsh", - derive(borsh::BorshSerialize, borsh::BorshDeserialize) -)] -#[derive(thiserror::Error, Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +/// Errors that occur _after_ successful ([`Env::open`]). +/// +/// There are no errors for: +/// 1. Missing tables +/// 2. (De)serialization +/// +/// as `cuprate_database` upholds the invariant that: +/// +/// 1. All tables exist +/// 2. (De)serialization never fails +#[derive(thiserror::Error, Debug)] pub enum RuntimeError { - // TODO: replace string with actual error type. - /// - /// An error occurred when attempting to - /// serialize the key data into bytes. - #[error("serialize error: {0}")] - Serialize(String), + /// The given key already existed in the database. + #[error("key already existed")] + KeyExists, - // TODO: replace string with actual error type. - /// - /// An error occurred when attempting to - /// deserialize the response value from - /// the database. - #[error("deserialize error: {0}")] - Deserialize(String), + /// The given key did not exist in the database. + #[error("key/value pair was not found")] + KeyNotFound, - /// TODO - /// - /// An unknown error occurred. - #[error("unknown error: {0}")] - Unknown(Cow<'static, str>), + /// A [`std::io::Error`]. + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + /// The database is currently in the process + /// of shutting down and cannot respond. + #[error("database is shutting down")] + ShuttingDown, } diff --git a/database/src/key.rs b/database/src/key.rs index 69beff0..57fe9b4 100644 --- a/database/src/key.rs +++ b/database/src/key.rs @@ -16,32 +16,15 @@ pub trait Key { /// If [`Key::DUPLICATE`] is `true`, [`Key::Secondary`] will contain /// the "subkey", or secondary key needed to access the actual value. /// - /// If [`Key::DUPLICATE`] is `false`, [`Key::Secondary`] - /// will just be the same type as [`Key::Primary`]. + /// If [`Key::DUPLICATE`] is `false`, [`Key::Secondary`] is ignored. + /// Consider using [`std::convert::Infallible`] as the type. const DUPLICATE: bool; - // TODO: fix this sanakirja bound. - cfg_if::cfg_if! { - if #[cfg(all(feature = "sanakirja", not(feature = "heed")))] { - /// The primary key type. - type Primary: Pod + sanakirja::Storable; + /// The primary key type. + type Primary: Pod; - /// The secondary key type. - /// - /// Only needs to be different than [`Key::Primary`] - /// if [`Key::DUPLICATE`] is `true`. - type Secondary: Pod + sanakirja::Storable; - } else { - /// The primary key type. - type Primary: Pod; - - /// The secondary key type. - /// - /// Only needs to be different than [`Key::Primary`] - /// if [`Key::DUPLICATE`] is `true`. - type Secondary: Pod; - } - } + /// The secondary key type. + type Secondary: Pod; /// Acquire [`Key::Primary`]. fn primary(self) -> Self::Primary; @@ -50,7 +33,7 @@ pub trait Key { /// /// This only needs to be implemented on types that are [`Self::DUPLICATE`]. /// - /// It is `unreachable!()` on non-duplicate key tables. + /// Consider using [`unreachable!()`] on non-duplicate key tables. fn primary_secondary(self) -> (Self::Primary, Self::Secondary); } @@ -84,7 +67,9 @@ macro_rules! impl_key { $( impl Key for $t { const DUPLICATE: bool = false; + type Primary = $t; + // This 0 variant enum is unconstructable, // and "has the same role as the ! “never” type": // . @@ -121,14 +106,11 @@ impl_key! { // Implement `Key` for any [`DupKey`] using [`Copy`] types. impl Key for DupKey where - // TODO: fix sanakirja serde bound. P: Pod + Copy, S: Pod + Copy, { const DUPLICATE: bool = true; - type Primary = P; - type Secondary = S; #[inline] diff --git a/database/src/lib.rs b/database/src/lib.rs index db23a49..1efb0eb 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -229,14 +229,15 @@ compile_error!("Cuprate is only compatible with 64-bit CPUs"); //---------------------------------------------------------------------------------------------------- Public API // Import private modules, export public types. // -// Documentation for each module is -// located in the respective file. +// Documentation for each module is located in the respective file. mod backend; pub use backend::ConcreteEnv; mod constants; -pub use constants::{CUPRATE_DATABASE_DIR, CUPRATE_DATABASE_FILE, DATABASE_BACKEND}; +pub use constants::{ + CUPRATE_DATABASE_CORRUPT_MSG, CUPRATE_DATABASE_DIR, CUPRATE_DATABASE_FILE, DATABASE_BACKEND, +}; mod database; pub use database::Database; diff --git a/database/src/table.rs b/database/src/table.rs index c44742f..9ad459a 100644 --- a/database/src/table.rs +++ b/database/src/table.rs @@ -27,16 +27,8 @@ pub trait Table { /// Primary key type. type Key: Key; - // TODO: fix this sanakirja bound. - cfg_if::cfg_if! { - if #[cfg(all(feature = "sanakirja", not(feature = "heed")))] { - /// Value type. - type Value: Pod + sanakirja::Storable; - } else { - /// Value type. - type Value: Pod; - } - } + /// Value type. + type Value: Pod; } //---------------------------------------------------------------------------------------------------- Tests