Files
reth/crates/storage/libmdbx-rs/src/error.rs
2026-01-14 23:56:27 +00:00

273 lines
11 KiB
Rust

use std::{ffi::c_int, result};
/// An MDBX result.
pub type Result<T> = result::Result<T, Error>;
/// An MDBX error kind.
#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
pub enum Error {
/// Key/data pair already exists.
#[error("key/data pair already exists")]
KeyExist,
/// No matching key/data pair found.
#[error("no matching key/data pair found")]
NotFound,
/// The cursor is already at the end of data.
#[error("the cursor is already at the end of data")]
NoData,
/// Requested page not found.
#[error("requested page not found")]
PageNotFound,
/// Database is corrupted.
#[error("database is corrupted")]
Corrupted,
/// Fatal environment error.
#[error("fatal environment error")]
Panic,
/// DB version mismatch.
#[error("DB version mismatch")]
VersionMismatch,
/// File is not an MDBX file.
#[error("file is not an MDBX file")]
Invalid,
/// Environment map size limit reached.
#[error("environment map size limit reached")]
MapFull,
/// Too many DBI-handles (maxdbs reached).
#[error("too many DBI-handles (maxdbs reached)")]
DbsFull,
/// Too many readers (maxreaders reached).
#[error("too many readers (maxreaders reached)")]
ReadersFull,
/// Transaction has too many dirty pages (i.e., the transaction is too big).
#[error("transaction has too many dirty pages (i.e., the transaction is too big)")]
TxnFull,
/// Cursor stack limit reached.
#[error("cursor stack limit reached")]
CursorFull,
/// Page has no more space.
#[error("page has no more space")]
PageFull,
/// The database engine was unable to extend mapping, e.g. the address space is unavailable or
/// busy.
///
/// This can mean:
/// - The database size was extended by other processes beyond the environment map size, and
/// the engine was unable to extend the mapping while starting a read transaction. The
/// environment should be re-opened to continue.
/// - The engine was unable to extend the mapping during a write transaction or an explicit
/// call to change the geometry of the environment.
#[error("database engine was unable to extend mapping")]
UnableExtendMapSize,
/// Environment or database is not compatible with the requested operation or flags.
#[error("environment or database is not compatible with the requested operation or flags")]
Incompatible,
/// Invalid reuse of reader locktable slot.
#[error("invalid reuse of reader locktable slot")]
BadRslot,
/// Transaction is not valid for requested operation.
#[error("transaction is not valid for requested operation")]
BadTxn,
/// Invalid size or alignment of key or data for the target database.
#[error("invalid size or alignment of key or data for the target database")]
BadValSize,
/// The specified DBI-handle is invalid.
#[error("the specified DBI-handle is invalid")]
BadDbi,
/// Unexpected internal error.
#[error("unexpected internal error")]
Problem,
/// Another write transaction is running.
#[error("another write transaction is running")]
Busy,
/// The specified key has more than one associated value.
#[error("the specified key has more than one associated value")]
Multival,
/// Wrong signature of a runtime object(s).
#[error("wrong signature of a runtime object(s)")]
BadSignature,
/// Database should be recovered, but cannot be done automatically since it's in read-only
/// mode.
#[error(
"database should be recovered, but cannot be done automatically since it's in read-only mode"
)]
WannaRecovery,
/// The given key value is mismatched to the current cursor position.
#[error("the given key value is mismatched to the current cursor position")]
KeyMismatch,
/// Decode error: An invalid parameter was specified.
#[error("invalid parameter specified")]
DecodeError,
/// The environment opened in read-only.
#[error(
"the environment opened in read-only, check <https://reth.rs/run/troubleshooting.html> for more"
)]
Access,
/// Database is too large for the current system.
#[error("database is too large for the current system")]
TooLarge,
/// Decode error length difference:
///
/// An invalid parameter was specified, or the environment has an active write transaction.
#[error("invalid parameter specified or active write transaction")]
DecodeErrorLenDiff,
/// If the [Environment](crate::Environment) was opened with
/// [`EnvironmentKind::WriteMap`](crate::EnvironmentKind::WriteMap) flag, nested transactions
/// are not supported.
#[error("nested transactions are not supported with WriteMap")]
NestedTransactionsUnsupportedWithWriteMap,
/// If the [Environment](crate::Environment) was opened with in read-only mode
/// [`Mode::ReadOnly`](crate::flags::Mode::ReadOnly), write transactions can't be opened.
#[error("write transactions are not supported in read-only mode")]
WriteTransactionUnsupportedInReadOnlyMode,
/// Read transaction has been timed out.
#[error("read transaction has been timed out")]
ReadTransactionTimeout,
/// The transaction commit was aborted due to previous errors.
///
/// This can happen in exceptionally rare cases and it signals the problem coming from inside
/// of mdbx.
#[error("botched transaction")]
BotchedTransaction,
/// Permission defined
#[error("permission denied to setup database")]
Permission,
/// Unknown error code.
#[error("unknown error code: {0}")]
Other(i32),
}
impl Error {
/// Converts a raw error code to an [Error].
pub const fn from_err_code(err_code: c_int) -> Self {
match err_code {
ffi::MDBX_KEYEXIST => Self::KeyExist,
ffi::MDBX_NOTFOUND => Self::NotFound,
ffi::MDBX_ENODATA => Self::NoData,
ffi::MDBX_PAGE_NOTFOUND => Self::PageNotFound,
ffi::MDBX_CORRUPTED => Self::Corrupted,
ffi::MDBX_PANIC => Self::Panic,
ffi::MDBX_VERSION_MISMATCH => Self::VersionMismatch,
ffi::MDBX_INVALID => Self::Invalid,
ffi::MDBX_MAP_FULL => Self::MapFull,
ffi::MDBX_DBS_FULL => Self::DbsFull,
ffi::MDBX_READERS_FULL => Self::ReadersFull,
ffi::MDBX_TXN_FULL => Self::TxnFull,
ffi::MDBX_CURSOR_FULL => Self::CursorFull,
ffi::MDBX_PAGE_FULL => Self::PageFull,
ffi::MDBX_UNABLE_EXTEND_MAPSIZE => Self::UnableExtendMapSize,
ffi::MDBX_INCOMPATIBLE => Self::Incompatible,
ffi::MDBX_BAD_RSLOT => Self::BadRslot,
ffi::MDBX_BAD_TXN => Self::BadTxn,
ffi::MDBX_BAD_VALSIZE => Self::BadValSize,
ffi::MDBX_BAD_DBI => Self::BadDbi,
ffi::MDBX_PROBLEM => Self::Problem,
ffi::MDBX_BUSY => Self::Busy,
ffi::MDBX_EMULTIVAL => Self::Multival,
ffi::MDBX_WANNA_RECOVERY => Self::WannaRecovery,
ffi::MDBX_EKEYMISMATCH => Self::KeyMismatch,
ffi::MDBX_EINVAL => Self::DecodeError,
ffi::MDBX_EACCESS => Self::Access,
ffi::MDBX_TOO_LARGE => Self::TooLarge,
ffi::MDBX_EBADSIGN => Self::BadSignature,
ffi::MDBX_EPERM => Self::Permission,
other => Self::Other(other),
}
}
/// Converts an [Error] to the raw error code.
pub const fn to_err_code(&self) -> i32 {
match self {
Self::KeyExist => ffi::MDBX_KEYEXIST,
Self::NotFound => ffi::MDBX_NOTFOUND,
Self::NoData => ffi::MDBX_ENODATA,
Self::PageNotFound => ffi::MDBX_PAGE_NOTFOUND,
Self::Corrupted => ffi::MDBX_CORRUPTED,
Self::Panic => ffi::MDBX_PANIC,
Self::VersionMismatch => ffi::MDBX_VERSION_MISMATCH,
Self::Invalid => ffi::MDBX_INVALID,
Self::MapFull => ffi::MDBX_MAP_FULL,
Self::DbsFull => ffi::MDBX_DBS_FULL,
Self::ReadersFull => ffi::MDBX_READERS_FULL,
Self::TxnFull => ffi::MDBX_TXN_FULL,
Self::CursorFull => ffi::MDBX_CURSOR_FULL,
Self::PageFull => ffi::MDBX_PAGE_FULL,
Self::UnableExtendMapSize => ffi::MDBX_UNABLE_EXTEND_MAPSIZE,
Self::Incompatible => ffi::MDBX_INCOMPATIBLE,
Self::BadRslot => ffi::MDBX_BAD_RSLOT,
Self::BadTxn => ffi::MDBX_BAD_TXN,
Self::BadValSize => ffi::MDBX_BAD_VALSIZE,
Self::BadDbi => ffi::MDBX_BAD_DBI,
Self::Problem => ffi::MDBX_PROBLEM,
Self::Busy => ffi::MDBX_BUSY,
Self::Multival => ffi::MDBX_EMULTIVAL,
Self::WannaRecovery => ffi::MDBX_WANNA_RECOVERY,
Self::KeyMismatch => ffi::MDBX_EKEYMISMATCH,
Self::DecodeErrorLenDiff | Self::DecodeError => ffi::MDBX_EINVAL,
Self::TooLarge => ffi::MDBX_TOO_LARGE,
Self::BadSignature => ffi::MDBX_EBADSIGN,
Self::Access |
Self::WriteTransactionUnsupportedInReadOnlyMode |
Self::NestedTransactionsUnsupportedWithWriteMap => ffi::MDBX_EACCESS,
Self::ReadTransactionTimeout => -96000, // Custom non-MDBX error code
Self::BotchedTransaction => -96001,
Self::Permission => ffi::MDBX_EPERM,
Self::Other(err_code) => *err_code,
}
}
}
impl From<Error> for i32 {
fn from(value: Error) -> Self {
value.to_err_code()
}
}
/// Parses an MDBX error code into a result type.
///
/// Note that this function returns `Ok(false)` on `MDBX_SUCCESS` and
/// `Ok(true)` on `MDBX_RESULT_TRUE`. The return value requires extra
/// care since its interpretation depends on the callee being called.
///
/// The most unintuitive case is `mdbx_txn_commit` which returns `Ok(true)`
/// when the commit has been aborted.
#[inline]
pub(crate) const fn mdbx_result(err_code: c_int) -> Result<bool> {
match err_code {
ffi::MDBX_SUCCESS => Ok(false),
ffi::MDBX_RESULT_TRUE => Ok(true),
other => Err(Error::from_err_code(other)),
}
}
#[macro_export]
macro_rules! mdbx_try_optional {
($expr:expr) => {{
match $expr {
Err(Error::NotFound | Error::NoData) => return Ok(None),
Err(e) => return Err(e),
Ok(v) => v,
}
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_description() {
assert_eq!(
"the environment opened in read-only, check <https://reth.rs/run/troubleshooting.html> for more",
Error::from_err_code(13).to_string()
);
assert_eq!("file is not an MDBX file", Error::Invalid.to_string());
}
#[test]
fn test_conversion() {
assert_eq!(Error::from_err_code(ffi::MDBX_KEYEXIST), Error::KeyExist);
}
}