Skip to main content

shodh_redb/
error.rs

1use crate::tree_store::{FILE_FORMAT_VERSION3, MAX_VALUE_LENGTH, PageNumber};
2use crate::{ReadTransaction, TypeName};
3use alloc::boxed::Box;
4use alloc::format;
5use alloc::string::String;
6use core::fmt::{Display, Formatter};
7use core::panic;
8
9#[cfg(feature = "std")]
10use crate::group_commit::GroupCommitError;
11#[cfg(feature = "std")]
12use std::sync::PoisonError;
13
14/// Error type for storage backend operations.
15///
16/// Under `std`, this wraps `std::io::Error`. Under `no_std`, it provides
17/// a string-based error representation.
18#[derive(Debug)]
19pub enum BackendError {
20    #[cfg(feature = "std")]
21    Io(std::io::Error),
22    #[cfg(not(feature = "std"))]
23    Message(String),
24}
25
26impl Display for BackendError {
27    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
28        match self {
29            #[cfg(feature = "std")]
30            BackendError::Io(err) => write!(f, "{err}"),
31            #[cfg(not(feature = "std"))]
32            BackendError::Message(msg) => write!(f, "{msg}"),
33        }
34    }
35}
36
37#[cfg(feature = "std")]
38impl std::error::Error for BackendError {}
39
40#[cfg(feature = "std")]
41impl From<std::io::Error> for BackendError {
42    fn from(err: std::io::Error) -> Self {
43        BackendError::Io(err)
44    }
45}
46
47impl BackendError {
48    /// Returns the `ErrorKind` of the underlying I/O error.
49    #[cfg(feature = "std")]
50    pub fn kind(&self) -> std::io::ErrorKind {
51        match self {
52            BackendError::Io(e) => e.kind(),
53        }
54    }
55}
56
57/// General errors directly from the storage layer
58#[derive(Debug)]
59#[non_exhaustive]
60pub enum StorageError {
61    /// The Database is corrupted
62    Corrupted(String),
63    /// An internal invariant was violated, indicating a bug in the database engine.
64    /// This is NOT caused by on-disk corruption -- it means the code has a logic error.
65    /// If you encounter this error, please file a bug report.
66    Internal(String),
67    /// The value being inserted exceeds the maximum of 3GiB
68    ValueTooLarge(usize),
69    /// A blob with the given sequence ID was not found in the blob store
70    BlobNotFound(u64),
71    /// Blob data checksum does not match the stored checksum
72    BlobChecksumMismatch {
73        /// Blob sequence number
74        sequence: u64,
75        /// Checksum stored in metadata
76        expected: u128,
77        /// Checksum computed from the blob data
78        actual: u128,
79    },
80    /// A streaming blob writer is already active on this transaction
81    BlobWriterActive,
82    /// The streaming blob writer has already been finished
83    BlobWriterFinished,
84    /// The requested byte range exceeds the blob's length
85    BlobRangeOutOfBounds {
86        /// Total blob length in bytes
87        blob_length: u64,
88        /// Requested range start offset
89        requested_offset: u64,
90        /// Requested range length
91        requested_length: u64,
92    },
93    /// The configured memory budget has been exceeded and the operation cannot proceed
94    MemoryBudgetExceeded {
95        /// The configured memory budget in bytes
96        budget: usize,
97        /// Current memory usage in bytes
98        used: usize,
99    },
100    /// The requested history snapshot was not found for the given transaction ID
101    HistorySnapshotNotFound(u64),
102    /// A B-tree page has an unexpected type byte
103    InvalidPageType {
104        /// Page region
105        page_region: u32,
106        /// Page index within region
107        page_index: u32,
108        /// Page order
109        page_order: u8,
110        /// The invalid type byte found
111        found: u8,
112    },
113    /// A child pointer or checksum on a B-tree branch page is invalid
114    InvalidChildRef {
115        /// Page region
116        page_region: u32,
117        /// Page index within region
118        page_index: u32,
119        /// Page order
120        page_order: u8,
121        /// Child index within the branch node
122        child_index: usize,
123        /// Whether this is a checksum error (true) or a pointer error (false)
124        is_checksum: bool,
125    },
126    /// An entry index on a B-tree page is invalid or out of range
127    InvalidEntryIndex {
128        /// Page region
129        page_region: u32,
130        /// Page index within region
131        page_index: u32,
132        /// Page order
133        page_order: u8,
134        /// The invalid entry index
135        entry_index: usize,
136    },
137    /// A B-tree page has structural corruption
138    PageCorrupted {
139        /// Page region
140        page_region: u32,
141        /// Page index within region
142        page_index: u32,
143        /// Page order
144        page_order: u8,
145        /// Static description of the corruption
146        detail: &'static str,
147    },
148    /// A CDC cursor position falls behind the retention window, meaning
149    /// entries have been pruned and the consumer may have missed changes.
150    CdcCursorBehindRetention {
151        /// The cursor position (transaction ID the consumer last processed)
152        cursor_txn_id: u64,
153        /// The oldest transaction ID still in the CDC log
154        oldest_retained_txn_id: u64,
155    },
156    /// A configuration parameter is invalid (e.g. page size, region size).
157    InvalidConfiguration {
158        /// Description of the invalid configuration
159        message: String,
160    },
161    /// The IVF-PQ index has not been trained yet
162    IndexNotTrained {
163        /// Name of the untrained index
164        index_name: String,
165    },
166    /// Vector dimensionality does not match the index configuration
167    DimensionMismatch {
168        /// Name of the index
169        index_name: String,
170        /// Dimension the index was configured with
171        expected: usize,
172        /// Dimension of the provided vector
173        actual: usize,
174    },
175    /// IVF-PQ index configuration is invalid
176    InvalidIndexConfig {
177        /// Description of the invalid configuration
178        detail: String,
179    },
180    /// On-disk format validation failed (magic number, version, layout, record structure)
181    FormatError {
182        /// Description of the format violation
183        detail: String,
184    },
185    /// Database requires recovery before this operation can proceed
186    RecoveryRequired,
187    /// Storage space exhausted; the allocator cannot grow further
188    OutOfSpace,
189    /// Timed out waiting for a write transaction lock (`no_std` spin-lock).
190    /// This is transient contention, not corruption.
191    LockTimeout(String),
192    Io(BackendError),
193    PreviousIo,
194    DatabaseClosed,
195    LockPoisoned(&'static panic::Location<'static>),
196}
197
198#[cfg(feature = "std")]
199impl<T> From<PoisonError<T>> for StorageError {
200    fn from(_: PoisonError<T>) -> StorageError {
201        StorageError::LockPoisoned(panic::Location::caller())
202    }
203}
204
205impl From<BackendError> for StorageError {
206    fn from(err: BackendError) -> StorageError {
207        StorageError::Io(err)
208    }
209}
210
211#[cfg(feature = "std")]
212impl From<std::io::Error> for StorageError {
213    fn from(err: std::io::Error) -> StorageError {
214        StorageError::Io(BackendError::Io(err))
215    }
216}
217
218impl From<StorageError> for Error {
219    fn from(err: StorageError) -> Error {
220        match err {
221            StorageError::Corrupted(msg) => Error::Corrupted(msg),
222            StorageError::Internal(msg) => Error::Internal(msg),
223            StorageError::ValueTooLarge(x) => Error::ValueTooLarge(x),
224            StorageError::BlobNotFound(seq) => Error::BlobNotFound(seq),
225            StorageError::BlobChecksumMismatch {
226                sequence,
227                expected,
228                actual,
229            } => Error::BlobChecksumMismatch {
230                sequence,
231                expected,
232                actual,
233            },
234            StorageError::BlobWriterActive => Error::BlobWriterActive,
235            StorageError::BlobWriterFinished => Error::BlobWriterFinished,
236            StorageError::BlobRangeOutOfBounds {
237                blob_length,
238                requested_offset,
239                requested_length,
240            } => Error::BlobRangeOutOfBounds {
241                blob_length,
242                requested_offset,
243                requested_length,
244            },
245            StorageError::MemoryBudgetExceeded { budget, used } => {
246                Error::MemoryBudgetExceeded { budget, used }
247            }
248            StorageError::HistorySnapshotNotFound(id) => Error::HistorySnapshotNotFound(id),
249            StorageError::InvalidPageType {
250                page_region,
251                page_index,
252                page_order,
253                found,
254            } => Error::InvalidPageType {
255                page_region,
256                page_index,
257                page_order,
258                found,
259            },
260            StorageError::InvalidChildRef {
261                page_region,
262                page_index,
263                page_order,
264                child_index,
265                is_checksum,
266            } => Error::InvalidChildRef {
267                page_region,
268                page_index,
269                page_order,
270                child_index,
271                is_checksum,
272            },
273            StorageError::InvalidEntryIndex {
274                page_region,
275                page_index,
276                page_order,
277                entry_index,
278            } => Error::InvalidEntryIndex {
279                page_region,
280                page_index,
281                page_order,
282                entry_index,
283            },
284            StorageError::PageCorrupted {
285                page_region,
286                page_index,
287                page_order,
288                detail,
289            } => Error::PageCorrupted {
290                page_region,
291                page_index,
292                page_order,
293                detail,
294            },
295            StorageError::CdcCursorBehindRetention {
296                cursor_txn_id,
297                oldest_retained_txn_id,
298            } => Error::CdcCursorBehindRetention {
299                cursor_txn_id,
300                oldest_retained_txn_id,
301            },
302            StorageError::InvalidConfiguration { message } => {
303                Error::InvalidConfiguration { message }
304            }
305            StorageError::IndexNotTrained { index_name } => Error::IndexNotTrained { index_name },
306            StorageError::DimensionMismatch {
307                index_name,
308                expected,
309                actual,
310            } => Error::DimensionMismatch {
311                index_name,
312                expected,
313                actual,
314            },
315            StorageError::InvalidIndexConfig { detail } => Error::InvalidIndexConfig { detail },
316            StorageError::FormatError { detail } => Error::FormatError { detail },
317            StorageError::RecoveryRequired => Error::RecoveryRequired,
318            StorageError::OutOfSpace => Error::OutOfSpace,
319            StorageError::LockTimeout(msg) => Error::LockTimeout(msg),
320            StorageError::Io(x) => Error::Io(x),
321            StorageError::PreviousIo => Error::PreviousIo,
322            StorageError::DatabaseClosed => Error::DatabaseClosed,
323            StorageError::LockPoisoned(location) => Error::LockPoisoned(location),
324        }
325    }
326}
327
328impl Display for StorageError {
329    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
330        match self {
331            StorageError::Corrupted(msg) => {
332                write!(f, "DB corrupted: {msg}")
333            }
334            StorageError::Internal(msg) => {
335                write!(f, "Internal error (this is a bug): {msg}")
336            }
337            StorageError::ValueTooLarge(len) => {
338                write!(
339                    f,
340                    "The value (length={len}) being inserted exceeds the maximum of {}GiB",
341                    MAX_VALUE_LENGTH / 1024 / 1024 / 1024
342                )
343            }
344            StorageError::BlobNotFound(seq) => {
345                write!(f, "Blob not found: sequence={seq}")
346            }
347            StorageError::BlobChecksumMismatch {
348                sequence,
349                expected,
350                actual,
351            } => {
352                write!(
353                    f,
354                    "Blob checksum mismatch: sequence={sequence}, expected={expected:#034x}, actual={actual:#034x}"
355                )
356            }
357            StorageError::BlobWriterActive => {
358                write!(
359                    f,
360                    "Cannot create blob writer or store blob while another writer is active"
361                )
362            }
363            StorageError::BlobWriterFinished => {
364                write!(f, "Blob writer has already been finished")
365            }
366            StorageError::BlobRangeOutOfBounds {
367                blob_length,
368                requested_offset,
369                requested_length,
370            } => {
371                write!(
372                    f,
373                    "Blob range out of bounds: blob_length={blob_length}, requested offset={requested_offset}, length={requested_length}"
374                )
375            }
376            StorageError::MemoryBudgetExceeded { budget, used } => {
377                write!(
378                    f,
379                    "Memory budget exceeded: budget={budget} bytes, used={used} bytes"
380                )
381            }
382            StorageError::HistorySnapshotNotFound(id) => {
383                write!(f, "History snapshot not found for transaction id={id}")
384            }
385            StorageError::InvalidPageType {
386                page_region,
387                page_index,
388                page_order,
389                found,
390            } => {
391                write!(
392                    f,
393                    "Invalid page type byte {found} on page ({page_region}, {page_index}, order={page_order}), expected LEAF (1) or BRANCH (2)"
394                )
395            }
396            StorageError::InvalidChildRef {
397                page_region,
398                page_index,
399                page_order,
400                child_index,
401                is_checksum,
402            } => {
403                let kind = if *is_checksum { "checksum" } else { "pointer" };
404                write!(
405                    f,
406                    "Invalid child {kind} at index {child_index} on page ({page_region}, {page_index}, order={page_order})"
407                )
408            }
409            StorageError::InvalidEntryIndex {
410                page_region,
411                page_index,
412                page_order,
413                entry_index,
414            } => {
415                write!(
416                    f,
417                    "Invalid entry index {entry_index} on page ({page_region}, {page_index}, order={page_order})"
418                )
419            }
420            StorageError::PageCorrupted {
421                page_region,
422                page_index,
423                page_order,
424                detail,
425            } => {
426                write!(
427                    f,
428                    "Page ({page_region}, {page_index}, order={page_order}) corrupted: {detail}"
429                )
430            }
431            StorageError::CdcCursorBehindRetention {
432                cursor_txn_id,
433                oldest_retained_txn_id,
434            } => {
435                write!(
436                    f,
437                    "CDC cursor (txn_id={cursor_txn_id}) is behind the retention window (oldest retained txn_id={oldest_retained_txn_id})"
438                )
439            }
440            StorageError::InvalidConfiguration { message } => {
441                write!(f, "Invalid configuration: {message}")
442            }
443            StorageError::IndexNotTrained { index_name } => {
444                write!(f, "IVF-PQ index '{index_name}' has not been trained")
445            }
446            StorageError::DimensionMismatch {
447                index_name,
448                expected,
449                actual,
450            } => {
451                write!(
452                    f,
453                    "Dimension mismatch on index '{index_name}': expected {expected}, got {actual}"
454                )
455            }
456            StorageError::InvalidIndexConfig { detail } => {
457                write!(f, "Invalid index configuration: {detail}")
458            }
459            StorageError::FormatError { detail } => {
460                write!(f, "Format error: {detail}")
461            }
462            StorageError::RecoveryRequired => {
463                write!(
464                    f,
465                    "Database recovery required. Close and re-open the database."
466                )
467            }
468            StorageError::OutOfSpace => {
469                write!(f, "Storage space exhausted: allocator cannot grow further")
470            }
471            StorageError::LockTimeout(msg) => {
472                write!(f, "Lock timeout: {msg}")
473            }
474            StorageError::Io(err) => {
475                write!(f, "I/O error: {err}")
476            }
477            StorageError::DatabaseClosed => {
478                write!(f, "Database has been closed")
479            }
480            StorageError::PreviousIo => {
481                write!(
482                    f,
483                    "Previous I/O error occurred. Please close and re-open the database."
484                )
485            }
486            StorageError::LockPoisoned(location) => {
487                write!(f, "Poisoned internal lock: {location}")
488            }
489        }
490    }
491}
492
493#[cfg(feature = "std")]
494impl std::error::Error for StorageError {}
495
496impl StorageError {
497    pub(crate) fn invalid_page_type(page: PageNumber, found: u8) -> Self {
498        StorageError::InvalidPageType {
499            page_region: page.region,
500            page_index: page.page_index,
501            page_order: page.page_order,
502            found,
503        }
504    }
505
506    pub(crate) fn invalid_child_pointer(page: PageNumber, child_index: usize) -> Self {
507        StorageError::InvalidChildRef {
508            page_region: page.region,
509            page_index: page.page_index,
510            page_order: page.page_order,
511            child_index,
512            is_checksum: false,
513        }
514    }
515
516    pub(crate) fn invalid_child_checksum(page: PageNumber, child_index: usize) -> Self {
517        StorageError::InvalidChildRef {
518            page_region: page.region,
519            page_index: page.page_index,
520            page_order: page.page_order,
521            child_index,
522            is_checksum: true,
523        }
524    }
525
526    pub(crate) fn invalid_entry_index(page: PageNumber, entry_index: usize) -> Self {
527        StorageError::InvalidEntryIndex {
528            page_region: page.region,
529            page_index: page.page_index,
530            page_order: page.page_order,
531            entry_index,
532        }
533    }
534
535    pub(crate) fn page_corrupted(page: PageNumber, detail: &'static str) -> Self {
536        StorageError::PageCorrupted {
537            page_region: page.region,
538            page_index: page.page_index,
539            page_order: page.page_order,
540            detail,
541        }
542    }
543
544    pub(crate) fn invalid_config(message: impl Into<String>) -> Self {
545        StorageError::InvalidConfiguration {
546            message: message.into(),
547        }
548    }
549
550    pub(crate) fn index_not_trained(index_name: impl Into<String>) -> Self {
551        StorageError::IndexNotTrained {
552            index_name: index_name.into(),
553        }
554    }
555
556    pub(crate) fn dimension_mismatch(
557        index_name: impl Into<String>,
558        expected: usize,
559        actual: usize,
560    ) -> Self {
561        StorageError::DimensionMismatch {
562            index_name: index_name.into(),
563            expected,
564            actual,
565        }
566    }
567
568    pub(crate) fn invalid_index_config(detail: impl Into<String>) -> Self {
569        StorageError::InvalidIndexConfig {
570            detail: detail.into(),
571        }
572    }
573
574    pub(crate) fn format_error(detail: impl Into<String>) -> Self {
575        StorageError::FormatError {
576            detail: detail.into(),
577        }
578    }
579
580    pub(crate) fn corrupted(detail: impl Into<String>) -> Self {
581        StorageError::Corrupted(detail.into())
582    }
583}
584
585/// Errors related to opening tables
586#[derive(Debug)]
587#[non_exhaustive]
588pub enum TableError {
589    /// Table types didn't match.
590    TableTypeMismatch {
591        table: String,
592        key: TypeName,
593        value: TypeName,
594    },
595    /// The table is a multimap table
596    TableIsMultimap(String),
597    /// The table is not a multimap table
598    TableIsNotMultimap(String),
599    TypeDefinitionChanged {
600        name: TypeName,
601        alignment: usize,
602        width: Option<usize>,
603    },
604    /// Table name does not match any table in database
605    TableDoesNotExist(String),
606    /// Table name already exists in the database
607    TableExists(String),
608    // Tables cannot be opened for writing multiple times, since they could retrieve immutable &
609    // mutable references to the same dirty pages, or multiple mutable references via insert_reserve()
610    TableAlreadyOpen(String, &'static panic::Location<'static>),
611    /// Error from underlying storage
612    Storage(StorageError),
613}
614
615impl TableError {
616    pub(crate) fn into_storage_error_or_internal(self, msg: &str) -> StorageError {
617        match self {
618            TableError::TableTypeMismatch { .. }
619            | TableError::TableIsMultimap(_)
620            | TableError::TableIsNotMultimap(_)
621            | TableError::TypeDefinitionChanged { .. }
622            | TableError::TableDoesNotExist(_)
623            | TableError::TableExists(_)
624            | TableError::TableAlreadyOpen(_, _) => {
625                StorageError::Internal(format!("{msg}: {self}"))
626            }
627            TableError::Storage(storage) => storage,
628        }
629    }
630}
631
632impl From<TableError> for Error {
633    fn from(err: TableError) -> Error {
634        match err {
635            TableError::TypeDefinitionChanged {
636                name,
637                alignment,
638                width,
639            } => Error::TypeDefinitionChanged {
640                name,
641                alignment,
642                width,
643            },
644            TableError::TableTypeMismatch { table, key, value } => {
645                Error::TableTypeMismatch { table, key, value }
646            }
647            TableError::TableIsMultimap(table) => Error::TableIsMultimap(table),
648            TableError::TableIsNotMultimap(table) => Error::TableIsNotMultimap(table),
649            TableError::TableDoesNotExist(table) => Error::TableDoesNotExist(table),
650            TableError::TableExists(table) => Error::TableExists(table),
651            TableError::TableAlreadyOpen(name, location) => Error::TableAlreadyOpen(name, location),
652            TableError::Storage(storage) => storage.into(),
653        }
654    }
655}
656
657impl From<StorageError> for TableError {
658    fn from(err: StorageError) -> TableError {
659        TableError::Storage(err)
660    }
661}
662
663impl Display for TableError {
664    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
665        match self {
666            TableError::TypeDefinitionChanged {
667                name,
668                alignment,
669                width,
670            } => {
671                write!(
672                    f,
673                    "Current definition of {} does not match stored definition (width={:?}, alignment={})",
674                    name.name(),
675                    width,
676                    alignment,
677                )
678            }
679            TableError::TableTypeMismatch { table, key, value } => {
680                write!(
681                    f,
682                    "{table} is of type Table<{}, {}>",
683                    key.name(),
684                    value.name(),
685                )
686            }
687            TableError::TableIsMultimap(table) => {
688                write!(f, "{table} is a multimap table")
689            }
690            TableError::TableIsNotMultimap(table) => {
691                write!(f, "{table} is not a multimap table")
692            }
693            TableError::TableDoesNotExist(table) => {
694                write!(f, "Table '{table}' does not exist")
695            }
696            TableError::TableExists(table) => {
697                write!(f, "Table '{table}' already exists")
698            }
699            TableError::TableAlreadyOpen(name, location) => {
700                write!(f, "Table '{name}' already opened at: {location}")
701            }
702            TableError::Storage(storage) => storage.fmt(f),
703        }
704    }
705}
706
707#[cfg(feature = "std")]
708impl std::error::Error for TableError {}
709
710/// Errors related to opening a database
711#[derive(Debug)]
712#[non_exhaustive]
713pub enum DatabaseError {
714    /// The Database is already open. Cannot acquire lock.
715    DatabaseAlreadyOpen,
716    /// [`crate::RepairSession::abort`] was called or repair was aborted for another reason (such as the database being read-only).
717    RepairAborted,
718    /// The database file is in an old file format and must be manually upgraded
719    UpgradeRequired(u8),
720    /// Error from underlying storage
721    Storage(StorageError),
722}
723
724impl From<DatabaseError> for Error {
725    fn from(err: DatabaseError) -> Error {
726        match err {
727            DatabaseError::DatabaseAlreadyOpen => Error::DatabaseAlreadyOpen,
728            DatabaseError::RepairAborted => Error::RepairAborted,
729            DatabaseError::UpgradeRequired(x) => Error::UpgradeRequired(x),
730            DatabaseError::Storage(storage) => storage.into(),
731        }
732    }
733}
734
735impl From<BackendError> for DatabaseError {
736    fn from(err: BackendError) -> DatabaseError {
737        DatabaseError::Storage(StorageError::Io(err))
738    }
739}
740
741#[cfg(feature = "std")]
742impl From<std::io::Error> for DatabaseError {
743    fn from(err: std::io::Error) -> DatabaseError {
744        DatabaseError::Storage(StorageError::Io(BackendError::Io(err)))
745    }
746}
747
748impl From<StorageError> for DatabaseError {
749    fn from(err: StorageError) -> DatabaseError {
750        DatabaseError::Storage(err)
751    }
752}
753
754impl Display for DatabaseError {
755    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
756        match self {
757            DatabaseError::UpgradeRequired(actual) => {
758                write!(
759                    f,
760                    "Manual upgrade required. Expected file format version {FILE_FORMAT_VERSION3}, but file is version {actual}"
761                )
762            }
763            DatabaseError::RepairAborted => {
764                write!(f, "Database repair aborted.")
765            }
766            DatabaseError::DatabaseAlreadyOpen => {
767                write!(f, "Database already open. Cannot acquire lock.")
768            }
769            DatabaseError::Storage(storage) => storage.fmt(f),
770        }
771    }
772}
773
774#[cfg(feature = "std")]
775impl std::error::Error for DatabaseError {}
776
777/// Errors related to savepoints
778#[derive(Debug)]
779#[non_exhaustive]
780pub enum SavepointError {
781    /// This savepoint is invalid or cannot be created.
782    ///
783    /// Savepoints become invalid when an older savepoint is restored after it was created,
784    /// and savepoints cannot be created if the transaction is "dirty" (any tables have been opened)
785    InvalidSavepoint,
786    /// Error from underlying storage
787    Storage(StorageError),
788}
789
790impl From<SavepointError> for Error {
791    fn from(err: SavepointError) -> Error {
792        match err {
793            SavepointError::InvalidSavepoint => Error::InvalidSavepoint,
794            SavepointError::Storage(storage) => storage.into(),
795        }
796    }
797}
798
799impl From<StorageError> for SavepointError {
800    fn from(err: StorageError) -> SavepointError {
801        SavepointError::Storage(err)
802    }
803}
804
805impl Display for SavepointError {
806    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
807        match self {
808            SavepointError::InvalidSavepoint => {
809                write!(f, "Savepoint is invalid or cannot be created.")
810            }
811            SavepointError::Storage(storage) => storage.fmt(f),
812        }
813    }
814}
815
816#[cfg(feature = "std")]
817impl std::error::Error for SavepointError {}
818
819/// Errors related to compaction
820#[derive(Debug)]
821#[non_exhaustive]
822pub enum CompactionError {
823    /// A persistent savepoint exists
824    PersistentSavepointExists,
825    /// A ephemeral savepoint exists
826    EphemeralSavepointExists,
827    /// A transaction is still in-progress
828    TransactionInProgress,
829    /// Compaction was cancelled by the progress callback.
830    Cancelled,
831    /// Error from underlying storage
832    Storage(StorageError),
833}
834
835impl From<CompactionError> for Error {
836    fn from(err: CompactionError) -> Error {
837        match err {
838            CompactionError::PersistentSavepointExists => Error::PersistentSavepointExists,
839            CompactionError::EphemeralSavepointExists => Error::EphemeralSavepointExists,
840            CompactionError::TransactionInProgress => Error::TransactionInProgress,
841            CompactionError::Cancelled => Error::CompactionCancelled,
842            CompactionError::Storage(storage) => storage.into(),
843        }
844    }
845}
846
847impl From<StorageError> for CompactionError {
848    fn from(err: StorageError) -> CompactionError {
849        CompactionError::Storage(err)
850    }
851}
852
853impl Display for CompactionError {
854    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
855        match self {
856            CompactionError::PersistentSavepointExists => {
857                write!(
858                    f,
859                    "Persistent savepoint exists. Operation cannot be performed."
860                )
861            }
862            CompactionError::EphemeralSavepointExists => {
863                write!(
864                    f,
865                    "Ephemeral savepoint exists. Operation cannot be performed."
866                )
867            }
868            CompactionError::TransactionInProgress => {
869                write!(
870                    f,
871                    "A transaction is still in progress. Operation cannot be performed."
872                )
873            }
874            CompactionError::Cancelled => {
875                write!(f, "Compaction cancelled by progress callback")
876            }
877            CompactionError::Storage(storage) => storage.fmt(f),
878        }
879    }
880}
881
882#[cfg(feature = "std")]
883impl std::error::Error for CompactionError {}
884
885/// Errors related to transactions
886#[derive(Debug)]
887#[non_exhaustive]
888pub enum SetDurabilityError {
889    /// A persistent savepoint was modified
890    PersistentSavepointModified,
891}
892
893impl From<SetDurabilityError> for Error {
894    fn from(err: SetDurabilityError) -> Error {
895        match err {
896            SetDurabilityError::PersistentSavepointModified => Error::PersistentSavepointModified,
897        }
898    }
899}
900
901impl Display for SetDurabilityError {
902    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
903        match self {
904            SetDurabilityError::PersistentSavepointModified => {
905                write!(
906                    f,
907                    "Persistent savepoint modified. Cannot reduce transaction durability"
908                )
909            }
910        }
911    }
912}
913
914#[cfg(feature = "std")]
915impl std::error::Error for SetDurabilityError {}
916
917/// Errors related to transactions
918#[derive(Debug)]
919#[non_exhaustive]
920pub enum TransactionError {
921    /// Error from underlying storage
922    Storage(StorageError),
923    /// The transaction is still referenced by a table or other object
924    ReadTransactionStillInUse(Box<ReadTransaction>),
925}
926
927impl TransactionError {
928    pub(crate) fn into_storage_error(self) -> StorageError {
929        match self {
930            TransactionError::Storage(storage) => storage,
931            _ => StorageError::Internal(String::from("unexpected non-storage transaction error")),
932        }
933    }
934}
935
936impl From<TransactionError> for Error {
937    fn from(err: TransactionError) -> Error {
938        match err {
939            TransactionError::Storage(storage) => storage.into(),
940            TransactionError::ReadTransactionStillInUse(txn) => {
941                Error::ReadTransactionStillInUse(txn)
942            }
943        }
944    }
945}
946
947impl From<StorageError> for TransactionError {
948    fn from(err: StorageError) -> TransactionError {
949        TransactionError::Storage(err)
950    }
951}
952
953impl Display for TransactionError {
954    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
955        match self {
956            TransactionError::Storage(storage) => storage.fmt(f),
957            TransactionError::ReadTransactionStillInUse(_) => {
958                write!(f, "Transaction still in use")
959            }
960        }
961    }
962}
963
964#[cfg(feature = "std")]
965impl std::error::Error for TransactionError {}
966
967/// Errors related to committing transactions
968#[derive(Debug)]
969#[non_exhaustive]
970pub enum CommitError {
971    /// Error from underlying storage
972    Storage(StorageError),
973}
974
975impl CommitError {
976    pub(crate) fn into_storage_error(self) -> StorageError {
977        match self {
978            CommitError::Storage(storage) => storage,
979        }
980    }
981}
982
983impl From<CommitError> for Error {
984    fn from(err: CommitError) -> Error {
985        match err {
986            CommitError::Storage(storage) => storage.into(),
987        }
988    }
989}
990
991impl From<StorageError> for CommitError {
992    fn from(err: StorageError) -> CommitError {
993        CommitError::Storage(err)
994    }
995}
996
997impl Display for CommitError {
998    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
999        match self {
1000            CommitError::Storage(storage) => storage.fmt(f),
1001        }
1002    }
1003}
1004
1005#[cfg(feature = "std")]
1006impl std::error::Error for CommitError {}
1007
1008/// Superset of all other errors that can occur. Convenience enum so that users can convert all errors into a single type
1009#[derive(Debug)]
1010#[non_exhaustive]
1011pub enum Error {
1012    /// The Database is already open. Cannot acquire lock.
1013    DatabaseAlreadyOpen,
1014    /// This savepoint is invalid or cannot be created.
1015    ///
1016    /// Savepoints become invalid when an older savepoint is restored after it was created,
1017    /// and savepoints cannot be created if the transaction is "dirty" (any tables have been opened)
1018    InvalidSavepoint,
1019    /// [`crate::RepairSession::abort`] was called.
1020    RepairAborted,
1021    /// A persistent savepoint was modified
1022    PersistentSavepointModified,
1023    /// A persistent savepoint exists
1024    PersistentSavepointExists,
1025    /// An Ephemeral savepoint exists
1026    EphemeralSavepointExists,
1027    /// A transaction is still in-progress
1028    TransactionInProgress,
1029    /// The Database is corrupted
1030    Corrupted(String),
1031    /// An internal invariant was violated, indicating a bug in the database engine.
1032    /// This is NOT caused by on-disk corruption -- it means the code has a logic error.
1033    /// If you encounter this error, please file a bug report.
1034    Internal(String),
1035    /// The database file is in an old file format and must be manually upgraded
1036    UpgradeRequired(u8),
1037    /// The value being inserted exceeds the maximum of 3GiB
1038    ValueTooLarge(usize),
1039    /// Table types didn't match.
1040    TableTypeMismatch {
1041        table: String,
1042        key: TypeName,
1043        value: TypeName,
1044    },
1045    /// The table is a multimap table
1046    TableIsMultimap(String),
1047    /// The table is not a multimap table
1048    TableIsNotMultimap(String),
1049    TypeDefinitionChanged {
1050        name: TypeName,
1051        alignment: usize,
1052        width: Option<usize>,
1053    },
1054    /// Table name does not match any table in database
1055    TableDoesNotExist(String),
1056    /// Table name already exists in the database
1057    TableExists(String),
1058    // Tables cannot be opened for writing multiple times, since they could retrieve immutable &
1059    // mutable references to the same dirty pages, or multiple mutable references via insert_reserve()
1060    TableAlreadyOpen(String, &'static panic::Location<'static>),
1061    /// A blob with the given sequence ID was not found in the blob store
1062    BlobNotFound(u64),
1063    /// Blob data checksum does not match the stored checksum
1064    BlobChecksumMismatch {
1065        /// Blob sequence number
1066        sequence: u64,
1067        /// Checksum stored in metadata
1068        expected: u128,
1069        /// Checksum computed from the blob data
1070        actual: u128,
1071    },
1072    /// A streaming blob writer is already active on this transaction
1073    BlobWriterActive,
1074    /// The streaming blob writer has already been finished
1075    BlobWriterFinished,
1076    /// The requested byte range exceeds the blob's length
1077    BlobRangeOutOfBounds {
1078        /// Total blob length in bytes
1079        blob_length: u64,
1080        /// Requested range start offset
1081        requested_offset: u64,
1082        /// Requested range length
1083        requested_length: u64,
1084    },
1085    /// The configured memory budget has been exceeded and the operation cannot proceed
1086    MemoryBudgetExceeded {
1087        /// The configured memory budget in bytes
1088        budget: usize,
1089        /// Current memory usage in bytes
1090        used: usize,
1091    },
1092    /// The requested history snapshot was not found for the given transaction ID
1093    HistorySnapshotNotFound(u64),
1094    /// A B-tree page has an unexpected type byte
1095    InvalidPageType {
1096        page_region: u32,
1097        page_index: u32,
1098        page_order: u8,
1099        found: u8,
1100    },
1101    /// A child pointer or checksum on a B-tree branch page is invalid
1102    InvalidChildRef {
1103        page_region: u32,
1104        page_index: u32,
1105        page_order: u8,
1106        child_index: usize,
1107        is_checksum: bool,
1108    },
1109    /// An entry index on a B-tree page is invalid or out of range
1110    InvalidEntryIndex {
1111        page_region: u32,
1112        page_index: u32,
1113        page_order: u8,
1114        entry_index: usize,
1115    },
1116    /// A B-tree page has structural corruption
1117    PageCorrupted {
1118        page_region: u32,
1119        page_index: u32,
1120        page_order: u8,
1121        detail: &'static str,
1122    },
1123    /// A CDC cursor position falls behind the retention window
1124    CdcCursorBehindRetention {
1125        /// The cursor position (transaction ID the consumer last processed)
1126        cursor_txn_id: u64,
1127        /// The oldest transaction ID still in the CDC log
1128        oldest_retained_txn_id: u64,
1129    },
1130    /// A configuration parameter is invalid (e.g. page size, region size)
1131    InvalidConfiguration {
1132        /// Description of the invalid configuration
1133        message: String,
1134    },
1135    /// The IVF-PQ index has not been trained yet
1136    IndexNotTrained {
1137        /// Name of the untrained index
1138        index_name: String,
1139    },
1140    /// Vector dimensionality does not match the index configuration
1141    DimensionMismatch {
1142        /// Name of the index
1143        index_name: String,
1144        /// Dimension the index was configured with
1145        expected: usize,
1146        /// Dimension of the provided vector
1147        actual: usize,
1148    },
1149    /// IVF-PQ index configuration is invalid
1150    InvalidIndexConfig {
1151        /// Description of the invalid configuration
1152        detail: String,
1153    },
1154    /// On-disk format validation failed (magic number, version, layout, record structure)
1155    FormatError {
1156        /// Description of the format violation
1157        detail: String,
1158    },
1159    /// Database requires recovery before this operation can proceed
1160    RecoveryRequired,
1161    /// Storage space exhausted; the allocator cannot grow further
1162    OutOfSpace,
1163    /// Timed out waiting for a write transaction lock (`no_std` spin-lock).
1164    LockTimeout(String),
1165    Io(BackendError),
1166    DatabaseClosed,
1167    /// A previous IO error occurred. The database must be closed and re-opened
1168    PreviousIo,
1169    LockPoisoned(&'static panic::Location<'static>),
1170    /// The transaction is still referenced by a table or other object
1171    ReadTransactionStillInUse(Box<ReadTransaction>),
1172    /// A group commit batch was rolled back because a peer batch failed
1173    #[cfg(feature = "std")]
1174    GroupCommitPeerFailed,
1175    /// The database group committer is shutting down
1176    #[cfg(feature = "std")]
1177    GroupCommitShutdown,
1178    /// Blob compaction was cancelled by the progress callback.
1179    CompactionCancelled,
1180}
1181
1182#[cfg(feature = "std")]
1183impl From<GroupCommitError> for Error {
1184    fn from(err: GroupCommitError) -> Error {
1185        match err {
1186            GroupCommitError::BatchFailed(e) => e,
1187            GroupCommitError::PeerFailed => Error::GroupCommitPeerFailed,
1188            GroupCommitError::TransactionFailed(e) | GroupCommitError::CommitFailed(e) => e.into(),
1189            GroupCommitError::Shutdown => Error::GroupCommitShutdown,
1190            GroupCommitError::LockPoisoned => Error::LockPoisoned(panic::Location::caller()),
1191        }
1192    }
1193}
1194
1195#[cfg(feature = "std")]
1196impl<T> From<PoisonError<T>> for Error {
1197    fn from(_: PoisonError<T>) -> Error {
1198        Error::LockPoisoned(panic::Location::caller())
1199    }
1200}
1201
1202#[cfg(feature = "std")]
1203impl From<std::io::Error> for Error {
1204    fn from(err: std::io::Error) -> Error {
1205        Error::Io(BackendError::Io(err))
1206    }
1207}
1208
1209impl From<BackendError> for Error {
1210    fn from(err: BackendError) -> Error {
1211        Error::Io(err)
1212    }
1213}
1214
1215impl Display for Error {
1216    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
1217        match self {
1218            Error::Corrupted(msg) => {
1219                write!(f, "DB corrupted: {msg}")
1220            }
1221            Error::Internal(msg) => {
1222                write!(f, "Internal error (this is a bug): {msg}")
1223            }
1224            Error::UpgradeRequired(actual) => {
1225                write!(
1226                    f,
1227                    "Manual upgrade required. Expected file format version {FILE_FORMAT_VERSION3}, but file is version {actual}"
1228                )
1229            }
1230            Error::ValueTooLarge(len) => {
1231                write!(
1232                    f,
1233                    "The value (length={len}) being inserted exceeds the maximum of {}GiB",
1234                    MAX_VALUE_LENGTH / 1024 / 1024 / 1024
1235                )
1236            }
1237            Error::TypeDefinitionChanged {
1238                name,
1239                alignment,
1240                width,
1241            } => {
1242                write!(
1243                    f,
1244                    "Current definition of {} does not match stored definition (width={:?}, alignment={})",
1245                    name.name(),
1246                    width,
1247                    alignment,
1248                )
1249            }
1250            Error::TableTypeMismatch { table, key, value } => {
1251                write!(
1252                    f,
1253                    "{table} is of type Table<{}, {}>",
1254                    key.name(),
1255                    value.name(),
1256                )
1257            }
1258            Error::TableIsMultimap(table) => {
1259                write!(f, "{table} is a multimap table")
1260            }
1261            Error::TableIsNotMultimap(table) => {
1262                write!(f, "{table} is not a multimap table")
1263            }
1264            Error::TableDoesNotExist(table) => {
1265                write!(f, "Table '{table}' does not exist")
1266            }
1267            Error::TableExists(table) => {
1268                write!(f, "Table '{table}' already exists")
1269            }
1270            Error::TableAlreadyOpen(name, location) => {
1271                write!(f, "Table '{name}' already opened at: {location}")
1272            }
1273            Error::BlobNotFound(seq) => {
1274                write!(f, "Blob not found: sequence={seq}")
1275            }
1276            Error::BlobChecksumMismatch {
1277                sequence,
1278                expected,
1279                actual,
1280            } => {
1281                write!(
1282                    f,
1283                    "Blob checksum mismatch: sequence={sequence}, expected={expected:#034x}, actual={actual:#034x}"
1284                )
1285            }
1286            Error::BlobWriterActive => {
1287                write!(
1288                    f,
1289                    "Cannot create blob writer or store blob while another writer is active"
1290                )
1291            }
1292            Error::BlobWriterFinished => {
1293                write!(f, "Blob writer has already been finished")
1294            }
1295            Error::BlobRangeOutOfBounds {
1296                blob_length,
1297                requested_offset,
1298                requested_length,
1299            } => {
1300                write!(
1301                    f,
1302                    "Blob range out of bounds: blob_length={blob_length}, requested offset={requested_offset}, length={requested_length}"
1303                )
1304            }
1305            Error::MemoryBudgetExceeded { budget, used } => {
1306                write!(
1307                    f,
1308                    "Memory budget exceeded: budget={budget} bytes, used={used} bytes"
1309                )
1310            }
1311            Error::HistorySnapshotNotFound(id) => {
1312                write!(f, "History snapshot not found for transaction id={id}")
1313            }
1314            Error::InvalidPageType {
1315                page_region,
1316                page_index,
1317                page_order,
1318                found,
1319            } => {
1320                write!(
1321                    f,
1322                    "Invalid page type byte {found} on page ({page_region}, {page_index}, order={page_order}), expected LEAF (1) or BRANCH (2)"
1323                )
1324            }
1325            Error::InvalidChildRef {
1326                page_region,
1327                page_index,
1328                page_order,
1329                child_index,
1330                is_checksum,
1331            } => {
1332                let kind = if *is_checksum { "checksum" } else { "pointer" };
1333                write!(
1334                    f,
1335                    "Invalid child {kind} at index {child_index} on page ({page_region}, {page_index}, order={page_order})"
1336                )
1337            }
1338            Error::InvalidEntryIndex {
1339                page_region,
1340                page_index,
1341                page_order,
1342                entry_index,
1343            } => {
1344                write!(
1345                    f,
1346                    "Invalid entry index {entry_index} on page ({page_region}, {page_index}, order={page_order})"
1347                )
1348            }
1349            Error::PageCorrupted {
1350                page_region,
1351                page_index,
1352                page_order,
1353                detail,
1354            } => {
1355                write!(
1356                    f,
1357                    "Page ({page_region}, {page_index}, order={page_order}) corrupted: {detail}"
1358                )
1359            }
1360            Error::CdcCursorBehindRetention {
1361                cursor_txn_id,
1362                oldest_retained_txn_id,
1363            } => {
1364                write!(
1365                    f,
1366                    "CDC cursor (txn_id={cursor_txn_id}) is behind the retention window (oldest retained txn_id={oldest_retained_txn_id})"
1367                )
1368            }
1369            Error::InvalidConfiguration { message } => {
1370                write!(f, "Invalid configuration: {message}")
1371            }
1372            Error::IndexNotTrained { index_name } => {
1373                write!(f, "IVF-PQ index '{index_name}' has not been trained")
1374            }
1375            Error::DimensionMismatch {
1376                index_name,
1377                expected,
1378                actual,
1379            } => {
1380                write!(
1381                    f,
1382                    "Dimension mismatch on index '{index_name}': expected {expected}, got {actual}"
1383                )
1384            }
1385            Error::InvalidIndexConfig { detail } => {
1386                write!(f, "Invalid index configuration: {detail}")
1387            }
1388            Error::FormatError { detail } => {
1389                write!(f, "Format error: {detail}")
1390            }
1391            Error::RecoveryRequired => {
1392                write!(
1393                    f,
1394                    "Database recovery required. Close and re-open the database."
1395                )
1396            }
1397            Error::OutOfSpace => {
1398                write!(f, "Storage space exhausted: allocator cannot grow further")
1399            }
1400            Error::LockTimeout(msg) => {
1401                write!(f, "Lock timeout: {msg}")
1402            }
1403            Error::Io(err) => {
1404                write!(f, "I/O error: {err}")
1405            }
1406            Error::DatabaseClosed => {
1407                write!(f, "Database has been closed")
1408            }
1409            Error::PreviousIo => {
1410                write!(
1411                    f,
1412                    "Previous I/O error occurred. Please close and re-open the database."
1413                )
1414            }
1415            Error::LockPoisoned(location) => {
1416                write!(f, "Poisoned internal lock: {location}")
1417            }
1418            Error::DatabaseAlreadyOpen => {
1419                write!(f, "Database already open. Cannot acquire lock.")
1420            }
1421            Error::RepairAborted => {
1422                write!(f, "Database repair aborted.")
1423            }
1424            Error::PersistentSavepointModified => {
1425                write!(
1426                    f,
1427                    "Persistent savepoint modified. Cannot reduce transaction durability"
1428                )
1429            }
1430            Error::PersistentSavepointExists => {
1431                write!(
1432                    f,
1433                    "Persistent savepoint exists. Operation cannot be performed."
1434                )
1435            }
1436            Error::EphemeralSavepointExists => {
1437                write!(
1438                    f,
1439                    "Ephemeral savepoint exists. Operation cannot be performed."
1440                )
1441            }
1442            Error::TransactionInProgress => {
1443                write!(
1444                    f,
1445                    "A transaction is still in progress. Operation cannot be performed."
1446                )
1447            }
1448            Error::InvalidSavepoint => {
1449                write!(f, "Savepoint is invalid or cannot be created.")
1450            }
1451            Error::ReadTransactionStillInUse(_) => {
1452                write!(f, "Transaction still in use")
1453            }
1454            #[cfg(feature = "std")]
1455            Error::GroupCommitPeerFailed => {
1456                write!(
1457                    f,
1458                    "Group commit batch rolled back: another batch in the group failed"
1459                )
1460            }
1461            #[cfg(feature = "std")]
1462            Error::GroupCommitShutdown => {
1463                write!(f, "Group commit failed: database is shutting down")
1464            }
1465            Error::CompactionCancelled => {
1466                write!(f, "Compaction cancelled by progress callback")
1467            }
1468        }
1469    }
1470}
1471
1472#[cfg(feature = "std")]
1473impl std::error::Error for Error {}