Skip to main content

kimberlite_store/
error.rs

1//! Error types for store operations.
2
3use std::io;
4
5use crate::Key;
6use crate::types::{PageId, TableId};
7
8/// Errors that can occur during store operations.
9#[derive(thiserror::Error, Debug)]
10pub enum StoreError {
11    /// Filesystem I/O error.
12    #[error("filesystem error: {0}")]
13    Io(#[from] io::Error),
14
15    /// Page CRC32 checksum mismatch - data corruption detected.
16    #[error(
17        "page {page_id} corrupted: CRC mismatch (expected {expected:#010x}, got {actual:#010x})"
18    )]
19    PageCorrupted {
20        page_id: PageId,
21        expected: u32,
22        actual: u32,
23    },
24
25    /// Key exceeds maximum allowed length.
26    #[error("key too long: {len} bytes exceeds maximum {max}")]
27    KeyTooLong { len: usize, max: usize },
28
29    /// Value exceeds maximum size that fits in a page.
30    #[error("value too large: {len} bytes exceeds maximum {max}")]
31    ValueTooLarge { len: usize, max: usize },
32
33    /// Page has invalid magic bytes.
34    #[error("invalid page magic: expected {expected:#010x}, got {actual:#010x}")]
35    InvalidPageMagic { expected: u32, actual: u32 },
36
37    /// Page has unsupported version.
38    #[error("unsupported page version: {0}")]
39    UnsupportedPageVersion(u8),
40
41    /// Superblock has invalid magic bytes.
42    #[error("invalid superblock magic")]
43    InvalidSuperblockMagic,
44
45    /// Superblock CRC mismatch.
46    #[error("superblock corrupted: CRC mismatch")]
47    SuperblockCorrupted,
48
49    /// Batch position is not sequential.
50    #[error("non-sequential batch: expected position {expected}, got {actual}")]
51    NonSequentialBatch { expected: u64, actual: u64 },
52
53    /// Table not found.
54    #[error("table {0:?} not found")]
55    TableNotFound(TableId),
56
57    /// Page overflow - not enough space for insert.
58    #[error("page overflow: need {needed} bytes, have {available}")]
59    PageOverflow { needed: usize, available: usize },
60
61    /// A single B+tree leaf entry (one key + its version chain) has
62    /// grown larger than what fits on a page, so splitting the leaf
63    /// can't help. The typical cause is repeated upsert-in-place on the
64    /// same primary key — every overwrite appends a version to the
65    /// MVCC chain, so a hot row that's updated dozens of times
66    /// accumulates a chain whose `serialized_size` exceeds the page
67    /// byte budget.
68    ///
69    /// v0.9.0 surfaced this as the generic
70    /// [`StoreError::PageOverflow`] inside `to_page`, which left
71    /// callers with no actionable signal. v0.9.1 promotes it to a
72    /// typed variant carrying the offending key + sizes so the SDK
73    /// can render a clear error and the operator can pinpoint the hot
74    /// row. Proper version-chain compaction driven by a retention
75    /// horizon is v0.10.0 work.
76    #[error(
77        "leaf entry for key {key:?} is {entry_size} bytes — too large for the page budget of \
78         {page_budget}. The MVCC version chain has grown unbounded under repeated upserts on the \
79         same primary key; bound the update rate per key, or wait for v0.10.0's retention-horizon \
80         compaction."
81    )]
82    EntryTooLarge {
83        /// The key whose version chain exceeded the page budget.
84        key: Key,
85        /// Total serialized size of the leaf entry (key + version chain
86        /// + per-version overhead).
87        entry_size: usize,
88        /// The page-level byte budget the entry must fit into.
89        /// Equal to `PAGE_SIZE - PAGE_HEADER_SIZE - CRC_SIZE`.
90        page_budget: usize,
91    },
92
93    /// Internal B+tree invariant violation.
94    #[error("B+tree invariant violation: {0}")]
95    BTreeInvariant(String),
96
97    /// Duplicate key in batch.
98    #[error("duplicate key in batch: {0:?}")]
99    DuplicateKey(Key),
100
101    /// Page not found in cache or on disk.
102    #[error("page {0} not found")]
103    PageNotFound(PageId),
104
105    /// Store is read-only.
106    #[error("store is read-only")]
107    ReadOnly,
108}