Skip to main content

signet_libmdbx/
error.rs

1//! Error types and result handling for MDBX operations.
2
3use std::{convert::Infallible, ffi::c_int, result};
4
5/// An MDBX result.
6pub type MdbxResult<T, E = MdbxError> = result::Result<T, E>;
7
8/// Result type for codec operations.
9pub type ReadResult<T, E = ReadError> = Result<T, E>;
10
11/// Error type for reading from the database.
12///
13/// This encapsulates errors that can occur during DB operations via
14/// `Self::Mdbx` as well as post-read during the [`TableObject`] decoding
15/// step via `Self::Decoding`.
16///
17/// For simplicity, the decoding error is boxed. [`Self::decoding`] can be used
18/// to create such an error from any error type fits the bounds. E.g.
19/// `result.map_err(ReadError::decoding)`.
20///
21/// [`TableObject`]: crate::codec::TableObject
22#[derive(thiserror::Error, Debug)]
23pub enum ReadError {
24    /// Mdbx error during decoding.
25    #[error(transparent)]
26    Mdbx(#[from] MdbxError),
27    /// Type-associated error while decoding.
28    #[error(transparent)]
29    Decoding(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
30}
31
32impl ReadError {
33    /// Creates a new decoding error from a boxed error.
34    pub fn decoding<E>(err: E) -> Self
35    where
36        E: std::error::Error + Send + Sync + 'static,
37    {
38        Self::Decoding(Box::new(err))
39    }
40}
41
42/// An MDBX error kind.
43///
44/// This represents various error conditions that can occur when interacting
45/// with the MDBX database.
46#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
47pub enum MdbxError {
48    /// Key/data pair already exists.
49    #[error("key/data pair already exists")]
50    KeyExist,
51    /// No matching key/data pair found.
52    #[error("no matching key/data pair found")]
53    NotFound,
54    /// The cursor is already at the end of data.
55    #[error("the cursor is already at the end of data")]
56    NoData,
57    /// Requested page not found.
58    #[error("requested page not found")]
59    PageNotFound,
60    /// Database is corrupted.
61    #[error("database is corrupted")]
62    Corrupted,
63    /// Fatal environment error.
64    #[error("fatal environment error")]
65    Panic,
66    /// DB version mismatch.
67    #[error("DB version mismatch")]
68    VersionMismatch,
69    /// File is not an MDBX file.
70    #[error("file is not an MDBX file")]
71    Invalid,
72    /// Environment map size limit reached.
73    #[error("environment map size limit reached")]
74    MapFull,
75    /// Too many DBI-handles (maxdbs reached).
76    #[error("too many DBI-handles (maxdbs reached)")]
77    DbsFull,
78    /// Too many readers (maxreaders reached).
79    #[error("too many readers (maxreaders reached)")]
80    ReadersFull,
81    /// Transaction has too many dirty pages (i.e., the transaction is too big).
82    #[error("transaction has too many dirty pages (i.e., the transaction is too big)")]
83    TxnFull,
84    /// Cursor stack limit reached.
85    #[error("cursor stack limit reached")]
86    CursorFull,
87    /// Page has no more space.
88    #[error("page has no more space")]
89    PageFull,
90    /// The database engine was unable to extend mapping, e.g. the address
91    /// space is unavailable or busy.
92    ///
93    /// This can mean:
94    ///
95    /// - The database size was extended by other processes beyond the
96    ///   environment map size, and the engine was unable to extend the mapping
97    ///   while starting a read transaction. The environment should be
98    ///   re-opened to continue.
99    /// - The engine was unable to extend the mapping during a write
100    ///   transaction or an explicit call to change the geometry of the
101    ///   environment.
102    #[error("database engine was unable to extend mapping")]
103    UnableExtendMapSize,
104    /// Environment or database is not compatible with the requested operation or flags.
105    #[error("environment or database is not compatible with the requested operation or flags")]
106    Incompatible,
107    /// Invalid reuse of reader locktable slot.
108    #[error("invalid reuse of reader locktable slot")]
109    BadRslot,
110    /// Transaction is not valid for requested operation.
111    #[error("transaction is not valid for requested operation")]
112    BadTxn,
113    /// Invalid size or alignment of key or data for the target database.
114    #[error("invalid size or alignment of key or data for the target database")]
115    BadValSize,
116    /// The specified DBI-handle is invalid.
117    #[error("the specified DBI-handle is invalid")]
118    BadDbi,
119    /// Unexpected internal error.
120    #[error("unexpected internal error")]
121    Problem,
122    /// Another write transaction is running.
123    #[error("another write transaction is running")]
124    Busy,
125    /// The specified key has more than one associated value.
126    #[error("the specified key has more than one associated value")]
127    Multival,
128    /// Wrong signature of a runtime object(s).
129    #[error("wrong signature of a runtime object(s)")]
130    BadSignature,
131    /// Database should be recovered, but cannot be done automatically since
132    /// it's in read-only mode.
133    #[error(
134        "database should be recovered, but cannot be done automatically since it's in read-only mode"
135    )]
136    WannaRecovery,
137    /// The given key value is mismatched to the current cursor position.
138    #[error("the given key value is mismatched to the current cursor position")]
139    KeyMismatch,
140    /// Decode error: An invalid parameter was specified.
141    #[error("invalid parameter specified")]
142    DecodeError,
143    /// The environment opened in read-only.
144    #[error(
145        "the environment opened in read-only, check <https://reth.rs/run/troubleshooting.html> for more"
146    )]
147    Access,
148    /// Database is too large for the current system.
149    #[error("database is too large for the current system")]
150    TooLarge,
151    /// Decode error length difference:
152    ///
153    /// An invalid parameter was specified, or the environment has an active
154    /// write transaction.
155    #[error("invalid parameter specified or active write transaction")]
156    DecodeErrorLenDiff,
157    /// If the [Environment](crate::Environment) was opened with
158    /// [`EnvironmentKind::WriteMap`](crate::sys::EnvironmentKind::WriteMap) flag,
159    /// nested transactions are not supported.
160    #[error("nested transactions are not supported with WriteMap")]
161    NestedTransactionsUnsupportedWithWriteMap,
162    /// If the [Environment](crate::Environment) was opened with in read-only
163    /// mode [`Mode::ReadOnly`](crate::flags::Mode::ReadOnly), write
164    /// transactions can't be opened.
165    #[error("write transactions are not supported in read-only mode")]
166    WriteTransactionUnsupportedInReadOnlyMode,
167    /// Read transaction has been timed out.
168    #[error("read transaction has been timed out")]
169    ReadTransactionTimeout,
170    /// The transaction commit was aborted due to previous errors.
171    ///
172    /// This can happen in exceptionally rare cases and it signals the problem
173    /// coming from inside of mdbx.
174    #[error("botched transaction")]
175    BotchedTransaction,
176    /// Permission defined
177    #[error("permission denied to setup database")]
178    Permission,
179    /// Unknown error code.
180    #[error("unknown error code: {0}")]
181    Other(i32),
182    /// Operation requires DUP_SORT flag on database.
183    #[error("operation requires DUP_SORT flag on database")]
184    RequiresDupSort,
185    /// Operation requires DUP_FIXED flag on database.
186    #[error("operation requires DUP_FIXED flag on database")]
187    RequiresDupFixed,
188}
189
190impl MdbxError {
191    /// Converts a raw error code to an [`MdbxError`].
192    pub const fn from_err_code(err_code: c_int) -> Self {
193        match err_code {
194            ffi::MDBX_KEYEXIST => Self::KeyExist,
195            ffi::MDBX_NOTFOUND => Self::NotFound,
196            ffi::MDBX_ENODATA => Self::NoData,
197            ffi::MDBX_PAGE_NOTFOUND => Self::PageNotFound,
198            ffi::MDBX_CORRUPTED => Self::Corrupted,
199            ffi::MDBX_PANIC => Self::Panic,
200            ffi::MDBX_VERSION_MISMATCH => Self::VersionMismatch,
201            ffi::MDBX_INVALID => Self::Invalid,
202            ffi::MDBX_MAP_FULL => Self::MapFull,
203            ffi::MDBX_DBS_FULL => Self::DbsFull,
204            ffi::MDBX_READERS_FULL => Self::ReadersFull,
205            ffi::MDBX_TXN_FULL => Self::TxnFull,
206            ffi::MDBX_CURSOR_FULL => Self::CursorFull,
207            ffi::MDBX_PAGE_FULL => Self::PageFull,
208            ffi::MDBX_UNABLE_EXTEND_MAPSIZE => Self::UnableExtendMapSize,
209            ffi::MDBX_INCOMPATIBLE => Self::Incompatible,
210            ffi::MDBX_BAD_RSLOT => Self::BadRslot,
211            ffi::MDBX_BAD_TXN => Self::BadTxn,
212            ffi::MDBX_BAD_VALSIZE => Self::BadValSize,
213            ffi::MDBX_BAD_DBI => Self::BadDbi,
214            ffi::MDBX_PROBLEM => Self::Problem,
215            ffi::MDBX_BUSY => Self::Busy,
216            ffi::MDBX_EMULTIVAL => Self::Multival,
217            ffi::MDBX_WANNA_RECOVERY => Self::WannaRecovery,
218            ffi::MDBX_EKEYMISMATCH => Self::KeyMismatch,
219            ffi::MDBX_EINVAL => Self::DecodeError,
220            ffi::MDBX_EACCESS => Self::Access,
221            ffi::MDBX_TOO_LARGE => Self::TooLarge,
222            ffi::MDBX_EBADSIGN => Self::BadSignature,
223            ffi::MDBX_EPERM => Self::Permission,
224            other => Self::Other(other),
225        }
226    }
227
228    /// Converts an [`MdbxError`] to the raw error code.
229    pub const fn to_err_code(&self) -> i32 {
230        match self {
231            Self::KeyExist => ffi::MDBX_KEYEXIST,
232            Self::NotFound => ffi::MDBX_NOTFOUND,
233            Self::NoData => ffi::MDBX_ENODATA,
234            Self::PageNotFound => ffi::MDBX_PAGE_NOTFOUND,
235            Self::Corrupted => ffi::MDBX_CORRUPTED,
236            Self::Panic => ffi::MDBX_PANIC,
237            Self::VersionMismatch => ffi::MDBX_VERSION_MISMATCH,
238            Self::Invalid => ffi::MDBX_INVALID,
239            Self::MapFull => ffi::MDBX_MAP_FULL,
240            Self::DbsFull => ffi::MDBX_DBS_FULL,
241            Self::ReadersFull => ffi::MDBX_READERS_FULL,
242            Self::TxnFull => ffi::MDBX_TXN_FULL,
243            Self::CursorFull => ffi::MDBX_CURSOR_FULL,
244            Self::PageFull => ffi::MDBX_PAGE_FULL,
245            Self::UnableExtendMapSize => ffi::MDBX_UNABLE_EXTEND_MAPSIZE,
246            Self::Incompatible => ffi::MDBX_INCOMPATIBLE,
247            Self::BadRslot => ffi::MDBX_BAD_RSLOT,
248            Self::BadTxn => ffi::MDBX_BAD_TXN,
249            Self::BadValSize => ffi::MDBX_BAD_VALSIZE,
250            Self::BadDbi => ffi::MDBX_BAD_DBI,
251            Self::Problem => ffi::MDBX_PROBLEM,
252            Self::Busy => ffi::MDBX_BUSY,
253            Self::Multival => ffi::MDBX_EMULTIVAL,
254            Self::WannaRecovery => ffi::MDBX_WANNA_RECOVERY,
255            Self::KeyMismatch => ffi::MDBX_EKEYMISMATCH,
256            Self::DecodeErrorLenDiff | Self::DecodeError => ffi::MDBX_EINVAL,
257            Self::TooLarge => ffi::MDBX_TOO_LARGE,
258            Self::BadSignature => ffi::MDBX_EBADSIGN,
259            Self::Access
260            | Self::WriteTransactionUnsupportedInReadOnlyMode
261            | Self::NestedTransactionsUnsupportedWithWriteMap => ffi::MDBX_EACCESS,
262            Self::ReadTransactionTimeout => -96000, // Custom non-MDBX error code
263            Self::BotchedTransaction => -96001,
264            Self::RequiresDupSort => -96002,
265            Self::RequiresDupFixed => -96003,
266            Self::Permission => ffi::MDBX_EPERM,
267            Self::Other(err_code) => *err_code,
268        }
269    }
270}
271
272impl From<MdbxError> for i32 {
273    fn from(value: MdbxError) -> Self {
274        value.to_err_code()
275    }
276}
277
278/// Parses an MDBX error code into a result type.
279///
280/// Note that this function returns `Ok(false)` on `MDBX_SUCCESS` and
281/// `Ok(true)` on `MDBX_RESULT_TRUE`. The return value requires extra
282/// care since its interpretation depends on the callee being called.
283///
284/// The most unintuitive case is `mdbx_txn_commit` which returns `Ok(true)`
285/// when the commit has been aborted.
286#[inline]
287pub(crate) const fn mdbx_result(err_code: c_int) -> MdbxResult<bool> {
288    match err_code {
289        ffi::MDBX_SUCCESS => Ok(false),
290        ffi::MDBX_RESULT_TRUE => Ok(true),
291        other => Err(MdbxError::from_err_code(other)),
292    }
293}
294
295impl From<MdbxError> for Infallible {
296    fn from(_value: MdbxError) -> Self {
297        unreachable!()
298    }
299}
300
301/// Parses an MDBX error code into a result type.
302///
303/// This function returns `Ok(())` on both `MDBX_SUCCESS` and
304/// `MDBX_RESULT_TRUE`, effectively treating them both as non-error outcomes.
305/// This is useful in scenarios where the distinction between these two
306/// success codes is not relevant to the caller, e.g. on a `get` operation
307/// where either outcome indicates a successful operation.
308#[inline]
309#[allow(dead_code)]
310pub(crate) const fn mdbx_result_unit(err_code: c_int) -> MdbxResult<()> {
311    match err_code {
312        ffi::MDBX_SUCCESS => Ok(()),
313        ffi::MDBX_RESULT_TRUE => Ok(()),
314        other => Err(MdbxError::from_err_code(other)),
315    }
316}
317
318/// Unwrap a `Result<Option<T>, MdbxError>`, or return `Ok(None)` if the error
319/// is `NotFound` or `NoData`.
320#[macro_export]
321macro_rules! mdbx_try_optional {
322    ($expr:expr) => {{
323        match $expr {
324            Err(MdbxError::NotFound | MdbxError::NoData) => return Ok(None),
325            Err(e) => return Err(e),
326            Ok(v) => v,
327        }
328    }};
329}
330
331/// Like `mdbx_try_optional!` but for [`ReadError`].
332#[macro_export]
333macro_rules! codec_try_optional {
334    ($expr:expr) => {{
335        match $expr {
336            Err($crate::error::ReadError::Mdbx(
337                $crate::MdbxError::NotFound | $crate::MdbxError::NoData,
338            )) => {
339                return Ok(None);
340            }
341            Err(e) => return Err(e),
342            Ok(v) => v,
343        }
344    }};
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350
351    #[test]
352    fn test_description() {
353        assert_eq!(
354            "the environment opened in read-only, check <https://reth.rs/run/troubleshooting.html> for more",
355            MdbxError::from_err_code(13).to_string()
356        );
357
358        assert_eq!("file is not an MDBX file", MdbxError::Invalid.to_string());
359    }
360
361    #[test]
362    fn test_conversion() {
363        assert_eq!(MdbxError::from_err_code(ffi::MDBX_KEYEXIST), MdbxError::KeyExist);
364    }
365}