database: Errors (#62)

* error: add variants to `RuntimeError`

* error: remove `<BackendError>` generic

we can just map each backend error variant <-> our error as needed

* backend: impl `From<heed::Error>` 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<heed::Error>` for `InitError`

* sanakirja: impl `From<sanakirja::Error>` 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
This commit is contained in:
hinto-janai 2024-02-17 08:00:14 -05:00 committed by GitHub
parent 475c8c5ac0
commit f7bd1304e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 360 additions and 166 deletions

View file

@ -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 }
serde = { workspace = true, optional = true }

View file

@ -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]>`.
<!-- Did you know markdown automatically increments number lists, even if they are all 1...? -->
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.

View file

@ -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<T: Table> Database<T> for heed::Database<T::Key, T::Value> {
impl<T: Table> Database<T> for HeedDb {
type RoTx<'db> = heed::RoTxn<'db>;
type RwTx<'db> = heed::RwTxn<'db>;

View file

@ -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<P: AsRef<Path>>(path: P) -> Result<Self, RuntimeError> {
fn open<P: AsRef<Path>>(path: P) -> Result<Self, InitError> {
todo!()
}
@ -70,7 +76,7 @@ impl Env for ConcreteEnv {
&self,
to_rw: &Self::RoTx<'_>,
) -> Result<impl Database<T>, RuntimeError> {
let tx: heed::Database<T::Key, T::Value> = todo!();
let tx: HeedDb = todo!();
Ok(tx)
}
}

View file

@ -0,0 +1,131 @@
//! Conversion from `heed::Error` -> `cuprate_database::RuntimeError`.
//---------------------------------------------------------------------------------------------------- Use
use crate::constants::CUPRATE_DATABASE_CORRUPT_MSG;
//---------------------------------------------------------------------------------------------------- InitError
impl From<heed::Error> 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`]:
// <https://docs.rs/heed/latest/src/heed/env.rs.html#149-219>
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".
// <https://docs.rs/heed/latest/heed/enum.MdbError.html#variant.Corrupted>
//
// "Requested page not found - this usually indicates corruption."
// <https://docs.rs/heed/latest/heed/enum.MdbError.html#variant.PageNotFound>
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<heed::Error> 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".
// <https://docs.rs/heed/latest/heed/enum.MdbError.html#variant.Corrupted>
//
// "Requested page not found - this usually indicates corruption."
// <https://docs.rs/heed/latest/heed/enum.MdbError.html#variant.PageNotFound>
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 // <https://docs.rs/heed/0.20.0-alpha.9/heed/enum.MdbError.html#variant.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::*;
}

View file

@ -4,6 +4,6 @@ mod env;
pub use env::ConcreteEnv;
mod database;
mod serde;
mod error;
mod transaction;
mod types;

View file

@ -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<Cow<'a, [u8]>, 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<Self::DItem, heed::BoxedError> {
Ok(Pod::from_bytes(bytes))
}
}
)*
};
}
/// TODO
struct Test;
impl_heed! {
Test => u8,
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

View file

@ -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<Bytes, Bytes>;

View file

@ -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<T: Table> Database<T> for sanakirja::btree::Db<T::Key, T::Value> {
type RoTx<'db> = sanakirja::Txn<&'_ sanakirja::Env>;
type RwTx<'db> = sanakirja::MutTxn<&'_ sanakirja::Env, ()>;
impl<T: Table> Database<T> 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<Option<T::Value>, RuntimeError> {
todo!()

View file

@ -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<P: AsRef<Path>>(path: P) -> Result<Self, RuntimeError> {
fn open<P: AsRef<Path>>(path: P) -> Result<Self, InitError> {
todo!()
}
@ -72,9 +78,9 @@ impl Env for ConcreteEnv {
fn open_database<T: Table>(
&self,
to_rw: &Self::RoTx<'_>,
) -> Result<Option<impl Database<T>>, RuntimeError> {
let tx: sanakirja::btree::Db<T::Key, T::Value> = todo!();
Ok(Some(tx))
) -> Result<impl Database<T>, RuntimeError> {
let tx: SanakirjaDb = todo!();
Ok(tx)
}
}

View file

@ -0,0 +1,51 @@
//! Conversion from `sanakirja::Error` -> `cuprate_database::RuntimeError`.
//---------------------------------------------------------------------------------------------------- Import
use crate::constants::CUPRATE_DATABASE_CORRUPT_MSG;
//---------------------------------------------------------------------------------------------------- InitError
impl From<sanakirja::Error> 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.
// <https://docs.rs/sanakirja/latest/sanakirja/enum.Error.html#variant.CRC>
E::Corrupt(_) | E::CRC(_) => Self::Corrupt,
// A database lock was poisoned.
// <https://docs.rs/sanakirja/latest/sanakirja/enum.Error.html#variant.Poison>
E::Poison => Self::Unknown(Box::new(error)),
}
}
}
//---------------------------------------------------------------------------------------------------- RuntimeError
#[allow(clippy::fallible_impl_from)] // We need to panic sometimes.
impl From<sanakirja::Error> 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.
// <https://docs.rs/sanakirja/latest/sanakirja/enum.Error.html#variant.CRC>
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::*;
}

View file

@ -3,6 +3,10 @@
mod env;
pub use env::ConcreteEnv;
mod error;
mod database;
mod transaction;
mod types;

View file

@ -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]>>;

View file

@ -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`.

View file

@ -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<P: AsRef<Path>>(path: P) -> Result<Self, RuntimeError>;
fn open<P: AsRef<Path>>(path: P) -> Result<Self, InitError>;
/// TODO
/// # Errors

View file

@ -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<dyn std::error::Error + Send + Sync + 'static>;
//---------------------------------------------------------------------------------------------------- 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<BackendError: Debug> {
/// 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,
}

View file

@ -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":
// <https://doc.rust-lang.org/std/convert/enum.Infallible.html#future-compatibility>.
@ -121,14 +106,11 @@ impl_key! {
// Implement `Key` for any [`DupKey`] using [`Copy`] types.
impl<P, S> Key for DupKey<P, S>
where
// TODO: fix sanakirja serde bound.
P: Pod + Copy,
S: Pod + Copy,
{
const DUPLICATE: bool = true;
type Primary = P;
type Secondary = S;
#[inline]

View file

@ -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;

View file

@ -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