Skip to main content

kimberlite_store/
types.rs

1//! Core types for the projection store.
2
3use std::fmt::{self, Debug, Display};
4
5use bytes::Bytes;
6
7// ============================================================================
8// Constants
9// ============================================================================
10
11/// Page size in bytes (4KB).
12///
13/// This is the fundamental unit of I/O and storage. All pages are exactly
14/// this size, enabling:
15/// - Page-aligned I/O for optimal disk performance
16/// - Predictable memory allocation
17/// - Simple free space management
18pub const PAGE_SIZE: usize = 4096;
19
20/// Maximum key length in bytes.
21///
22/// Keys must fit in a single page along with their value and overhead.
23/// This limit ensures reasonable B+tree fanout.
24pub const MAX_KEY_LENGTH: usize = 1024;
25
26/// Maximum value length in bytes.
27///
28/// Values must fit in a page with key and overhead. For larger values,
29/// applications should store references to external storage.
30#[allow(dead_code)]
31pub const MAX_VALUE_LENGTH: usize = PAGE_SIZE - 128; // Leave room for overhead
32
33/// Page header size in bytes.
34pub const PAGE_HEADER_SIZE: usize = 32;
35
36/// CRC32 checksum size in bytes.
37pub const CRC_SIZE: usize = 4;
38
39/// Minimum B+tree order (minimum keys per node, except root).
40pub const BTREE_MIN_KEYS: usize = 4;
41
42// ============================================================================
43// Page ID
44// ============================================================================
45
46/// Unique identifier for a page within the store.
47///
48/// Page 0 is always the superblock. Page IDs are assigned sequentially
49/// as new pages are allocated.
50#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
51pub struct PageId(u64);
52
53impl PageId {
54    /// The superblock page (always page 0).
55    pub const SUPERBLOCK: PageId = PageId(0);
56
57    /// Creates a new page ID.
58    pub fn new(id: u64) -> Self {
59        Self(id)
60    }
61
62    /// Returns the page ID as a u64.
63    pub fn as_u64(self) -> u64 {
64        self.0
65    }
66
67    /// Returns the byte offset of this page in the file.
68    pub fn byte_offset(self) -> u64 {
69        self.0 * PAGE_SIZE as u64
70    }
71
72    /// Returns the next page ID.
73    pub fn next(self) -> Self {
74        Self(self.0 + 1)
75    }
76}
77
78impl Debug for PageId {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(f, "PageId({})", self.0)
81    }
82}
83
84impl Display for PageId {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        write!(f, "{}", self.0)
87    }
88}
89
90impl From<u64> for PageId {
91    fn from(id: u64) -> Self {
92        Self(id)
93    }
94}
95
96impl From<PageId> for u64 {
97    fn from(id: PageId) -> Self {
98        id.0
99    }
100}
101
102// ============================================================================
103// Table ID
104// ============================================================================
105
106/// Unique identifier for a table within the store.
107///
108/// Tables provide namespace isolation for keys. Different projections
109/// can use different tables to avoid key conflicts.
110#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
111pub struct TableId(u64);
112
113impl TableId {
114    /// Creates a new table ID.
115    pub fn new(id: u64) -> Self {
116        Self(id)
117    }
118
119    /// Returns the table ID as a u64.
120    pub fn as_u64(self) -> u64 {
121        self.0
122    }
123}
124
125impl Debug for TableId {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        write!(f, "TableId({})", self.0)
128    }
129}
130
131impl Display for TableId {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(f, "{}", self.0)
134    }
135}
136
137impl From<u64> for TableId {
138    fn from(id: u64) -> Self {
139        Self(id)
140    }
141}
142
143impl From<TableId> for u64 {
144    fn from(id: TableId) -> Self {
145        id.0
146    }
147}
148
149// ============================================================================
150// Key
151// ============================================================================
152
153/// A key in the projection store.
154///
155/// Keys are arbitrary byte sequences up to [`MAX_KEY_LENGTH`] bytes.
156/// They are compared lexicographically for B+tree ordering.
157#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
158pub struct Key(Bytes);
159
160impl Key {
161    /// Creates a new key from bytes.
162    ///
163    /// # Panics
164    ///
165    /// Debug builds panic if the key exceeds [`MAX_KEY_LENGTH`].
166    pub fn new(data: impl Into<Bytes>) -> Self {
167        let bytes = data.into();
168        debug_assert!(
169            bytes.len() <= MAX_KEY_LENGTH,
170            "key length {} exceeds maximum {}",
171            bytes.len(),
172            MAX_KEY_LENGTH
173        );
174        Self(bytes)
175    }
176
177    /// Creates a key from a static byte slice.
178    pub fn from_static(data: &'static [u8]) -> Self {
179        Self::new(Bytes::from_static(data))
180    }
181
182    /// Returns the key as a byte slice.
183    pub fn as_bytes(&self) -> &[u8] {
184        &self.0
185    }
186
187    /// Returns the length of the key in bytes.
188    pub fn len(&self) -> usize {
189        self.0.len()
190    }
191
192    /// Returns true if the key is empty.
193    pub fn is_empty(&self) -> bool {
194        self.0.is_empty()
195    }
196
197    /// Returns the underlying Bytes.
198    pub fn into_bytes(self) -> Bytes {
199        self.0
200    }
201
202    /// The minimum possible key (empty).
203    pub fn min() -> Self {
204        Self(Bytes::new())
205    }
206
207    /// The maximum possible key (all 0xFF bytes at max length).
208    pub fn max() -> Self {
209        Self(Bytes::from(vec![0xFF; MAX_KEY_LENGTH]))
210    }
211}
212
213impl Debug for Key {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        // Show first 16 bytes in hex for debugging
216        write!(f, "Key(")?;
217        for (i, byte) in self.0.iter().take(16).enumerate() {
218            if i > 0 {
219                write!(f, " ")?;
220            }
221            write!(f, "{byte:02x}")?;
222        }
223        if self.0.len() > 16 {
224            write!(f, "...+{} more", self.0.len() - 16)?;
225        }
226        write!(f, ")")
227    }
228}
229
230impl Display for Key {
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        // Try to display as UTF-8 string if valid, otherwise hex
233        if let Ok(s) = std::str::from_utf8(&self.0) {
234            if s.chars().all(|c| c.is_ascii_graphic() || c == ' ') {
235                return write!(f, "{s}");
236            }
237        }
238        // Fall back to hex
239        for byte in &self.0 {
240            write!(f, "{byte:02x}")?;
241        }
242        Ok(())
243    }
244}
245
246impl From<&[u8]> for Key {
247    fn from(data: &[u8]) -> Self {
248        Self::new(Bytes::copy_from_slice(data))
249    }
250}
251
252impl From<Vec<u8>> for Key {
253    fn from(data: Vec<u8>) -> Self {
254        Self::new(Bytes::from(data))
255    }
256}
257
258impl From<Bytes> for Key {
259    fn from(data: Bytes) -> Self {
260        Self::new(data)
261    }
262}
263
264impl From<&str> for Key {
265    fn from(s: &str) -> Self {
266        Self::new(Bytes::copy_from_slice(s.as_bytes()))
267    }
268}
269
270impl From<String> for Key {
271    fn from(s: String) -> Self {
272        Self::new(Bytes::from(s))
273    }
274}
275
276impl AsRef<[u8]> for Key {
277    fn as_ref(&self) -> &[u8] {
278        &self.0
279    }
280}