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}