Skip to main content

reddb_server/storage/engine/page/
impl.rs

1use super::*;
2
3impl Page {
4    /// Create a new empty page
5    pub fn new(page_type: PageType, page_id: u32) -> Self {
6        let mut page = Self {
7            data: [0u8; PAGE_SIZE],
8        };
9
10        let header = PageHeader::new(page_type, page_id);
11        page.set_header(&header);
12        page
13    }
14
15    /// Create a page from raw bytes
16    pub fn from_bytes(data: [u8; PAGE_SIZE]) -> Self {
17        Self { data }
18    }
19
20    /// Create a page from a byte slice (must be exactly PAGE_SIZE)
21    pub fn from_slice(slice: &[u8]) -> Result<Self, PageError> {
22        if slice.len() != PAGE_SIZE {
23            return Err(PageError::InvalidSize(slice.len()));
24        }
25        let mut data = [0u8; PAGE_SIZE];
26        data.copy_from_slice(slice);
27        Ok(Self { data })
28    }
29
30    /// Get raw page data
31    #[inline]
32    pub fn as_bytes(&self) -> &[u8; PAGE_SIZE] {
33        &self.data
34    }
35
36    /// Get mutable raw page data
37    #[inline]
38    pub fn as_bytes_mut(&mut self) -> &mut [u8; PAGE_SIZE] {
39        &mut self.data
40    }
41
42    /// Get page header
43    pub fn header(&self) -> Result<PageHeader, PageError> {
44        let header_bytes: [u8; HEADER_SIZE] = self.data[..HEADER_SIZE]
45            .try_into()
46            .expect("header size mismatch");
47        PageHeader::from_bytes(&header_bytes)
48    }
49
50    /// Set page header
51    pub fn set_header(&mut self, header: &PageHeader) {
52        let bytes = header.to_bytes();
53        self.data[..HEADER_SIZE].copy_from_slice(&bytes);
54    }
55
56    /// Get page type
57    pub fn page_type(&self) -> Result<PageType, PageError> {
58        let page_type = reddb_file::paged_page_type(&self.data);
59        PageType::from_u8(page_type).ok_or(PageError::InvalidPageType(page_type))
60    }
61
62    /// Get page ID
63    pub fn page_id(&self) -> u32 {
64        reddb_file::paged_page_id(&self.data)
65    }
66
67    /// Get the WAL Log Sequence Number stamped on this page.
68    ///
69    /// `0` means "no WAL guarantee" — the page was modified through a
70    /// path that did not append a WAL record (freelist trunks, header
71    /// shadow pages). The double-write buffer is responsible for the
72    /// integrity of `lsn == 0` pages.
73    ///
74    /// See `src/storage/engine/btree/README.md` § Invariant 3.
75    pub fn lsn(&self) -> u64 {
76        reddb_file::paged_page_lsn(&self.data)
77    }
78
79    /// Stamp the WAL LSN of the record describing the most recent
80    /// mutation to this page. The pager's flush path guarantees that
81    /// the WAL is durable up to this LSN before writing the page to
82    /// disk (WAL-first ordering — see `PLAN.md` § Target 3).
83    ///
84    /// Callers should pass the LSN returned by `WalWriter::append`
85    /// for the change record. Pass `0` only on legacy / non-WAL
86    /// write paths (DWB-protected freelist + header writes).
87    pub fn set_lsn(&mut self, lsn: u64) {
88        reddb_file::set_paged_page_lsn(&mut self.data, lsn);
89    }
90
91    /// Get cell count
92    pub fn cell_count(&self) -> u16 {
93        reddb_file::paged_page_cell_count(&self.data)
94    }
95
96    /// Set cell count
97    pub fn set_cell_count(&mut self, count: u16) {
98        reddb_file::set_paged_page_cell_count(&mut self.data, count);
99    }
100
101    /// Get parent page ID
102    pub fn parent_id(&self) -> u32 {
103        reddb_file::paged_page_parent_id(&self.data)
104    }
105
106    /// Set parent page ID
107    pub fn set_parent_id(&mut self, parent_id: u32) {
108        reddb_file::set_paged_page_parent_id(&mut self.data, parent_id);
109    }
110
111    /// Get right child page ID (for interior nodes)
112    pub fn right_child(&self) -> u32 {
113        reddb_file::paged_page_right_child(&self.data)
114    }
115
116    /// Set right child page ID (for interior nodes)
117    pub fn set_right_child(&mut self, child_id: u32) {
118        reddb_file::set_paged_page_right_child(&mut self.data, child_id);
119    }
120
121    /// Get free_start offset
122    pub fn free_start(&self) -> u16 {
123        reddb_file::paged_page_free_start(&self.data)
124    }
125
126    /// Set free_start offset
127    pub fn set_free_start(&mut self, offset: u16) {
128        reddb_file::set_paged_page_free_start(&mut self.data, offset);
129    }
130
131    /// Get free_end offset
132    pub fn free_end(&self) -> u16 {
133        reddb_file::paged_page_free_end(&self.data)
134    }
135
136    /// Set free_end offset
137    pub fn set_free_end(&mut self, offset: u16) {
138        reddb_file::set_paged_page_free_end(&mut self.data, offset);
139    }
140
141    /// Get content area (everything after header)
142    #[inline]
143    pub fn content(&self) -> &[u8] {
144        &self.data[HEADER_SIZE..]
145    }
146
147    /// Get mutable content area
148    #[inline]
149    pub fn content_mut(&mut self) -> &mut [u8] {
150        &mut self.data[HEADER_SIZE..]
151    }
152
153    /// Calculate and update checksum
154    pub fn update_checksum(&mut self) {
155        // Zero out checksum field before calculating
156        reddb_file::clear_paged_page_checksum(&mut self.data);
157        // Calculate CRC32 of entire page
158        let checksum = crc32(&self.data);
159        // Store checksum
160        reddb_file::set_paged_page_checksum(&mut self.data, checksum);
161    }
162
163    /// Verify page checksum
164    pub fn verify_checksum(&self) -> Result<(), PageError> {
165        let stored = reddb_file::paged_page_checksum(&self.data);
166
167        // Calculate checksum with stored value zeroed
168        let mut temp = self.data;
169        reddb_file::clear_paged_page_checksum(&mut temp);
170        let calculated = crc32(&temp);
171
172        if stored != calculated {
173            Err(PageError::ChecksumMismatch {
174                expected: stored,
175                actual: calculated,
176            })
177        } else {
178            Ok(())
179        }
180    }
181
182    /// Get cell pointer at index
183    ///
184    /// Cell pointers are stored as u16 offsets starting at HEADER_SIZE.
185    pub fn get_cell_pointer(&self, index: usize) -> Result<u16, PageError> {
186        let count = self.cell_count() as usize;
187        if index >= count {
188            return Err(PageError::CellOutOfBounds(index));
189        }
190
191        reddb_file::paged_cell_pointer(&self.data, index).ok_or(PageError::CellOutOfBounds(index))
192    }
193
194    /// Set cell pointer at index
195    pub fn set_cell_pointer(&mut self, index: usize, pointer: u16) -> Result<(), PageError> {
196        if !reddb_file::paged_cell_pointer_is_valid(pointer) {
197            return Err(PageError::InvalidCellPointer(pointer));
198        }
199
200        if !reddb_file::set_paged_cell_pointer(&mut self.data, index, pointer) {
201            return Err(PageError::CellOutOfBounds(index));
202        }
203        Ok(())
204    }
205
206    /// Get cell data by index
207    pub fn get_cell(&self, index: usize) -> Result<&[u8], PageError> {
208        let pointer = self.get_cell_pointer(index)? as usize;
209
210        reddb_file::paged_cell_bytes(&self.data, pointer as u16)
211            .ok_or(PageError::InvalidCellPointer(pointer as u16))
212    }
213
214    /// Insert a new cell (key-value pair) into the page
215    ///
216    /// Returns the cell index on success.
217    pub fn insert_cell(&mut self, key: &[u8], value: &[u8]) -> Result<usize, PageError> {
218        let key_len = key.len();
219        let value_len = value.len();
220
221        // Check size limits
222        if key_len > u16::MAX as usize {
223            return Err(PageError::OverflowRequired);
224        }
225
226        let cell_size =
227            reddb_file::paged_cell_len(key_len, value_len).ok_or(PageError::OverflowRequired)?;
228
229        // Check if we need overflow
230        if cell_size > CONTENT_SIZE - 2 {
231            return Err(PageError::OverflowRequired);
232        }
233
234        // Read current header
235        let mut header = self.header()?;
236
237        // Check available space (need room for cell pointer + cell data)
238        let space_needed = 2 + cell_size;
239        if header.free_space() < space_needed {
240            return Err(PageError::PageFull);
241        }
242
243        // Allocate cell from end of page (growing upward)
244        let cell_offset = header.free_end as usize - cell_size;
245
246        // Write cell data
247        if !reddb_file::write_paged_cell(&mut self.data, cell_offset as u16, key, value) {
248            return Err(PageError::InvalidCellPointer(cell_offset as u16));
249        }
250
251        // Write cell pointer
252        let cell_index = header.cell_count as usize;
253        if !reddb_file::set_paged_cell_pointer(&mut self.data, cell_index, cell_offset as u16) {
254            return Err(PageError::CellOutOfBounds(cell_index));
255        }
256
257        // Update header
258        header.cell_count += 1;
259        header.free_start += 2;
260        header.free_end = cell_offset as u16;
261        header.set_flag(PageFlag::Dirty);
262        self.set_header(&header);
263
264        Ok(cell_index)
265    }
266
267    /// Read key and value from cell at index
268    pub fn read_cell(&self, index: usize) -> Result<(Vec<u8>, Vec<u8>), PageError> {
269        let cell = self.get_cell(index)?;
270
271        let (key, value) =
272            reddb_file::paged_cell_key_value(cell).ok_or(PageError::InvalidCellPointer(0))?;
273
274        Ok((key.to_vec(), value.to_vec()))
275    }
276
277    /// Binary search for key in sorted cell array
278    ///
279    /// Returns Ok(index) if key is found, Err(insert_pos) if not.
280    pub fn search_key(&self, key: &[u8]) -> Result<usize, usize> {
281        let count = self.cell_count() as usize;
282        if count == 0 {
283            return Err(0);
284        }
285
286        let mut low = 0;
287        let mut high = count;
288
289        while low < high {
290            let mid = (low + high) / 2;
291            let (cell_key, _) = self.read_cell(mid).map_err(|_| mid)?;
292
293            match cell_key.as_slice().cmp(key) {
294                std::cmp::Ordering::Less => low = mid + 1,
295                std::cmp::Ordering::Greater => high = mid,
296                std::cmp::Ordering::Equal => return Ok(mid),
297            }
298        }
299
300        Err(low)
301    }
302
303    /// Create a database header page (page 0)
304    pub fn new_header_page(page_count: u32) -> Self {
305        let mut page = Self::new(PageType::Header, 0);
306
307        reddb_file::init_database_header_page(&mut page.data, page_count)
308            .expect("fixed-size page can hold database header");
309
310        page.update_checksum();
311        page
312    }
313
314    /// Read page count from header page
315    pub fn read_page_count(&self) -> u32 {
316        reddb_file::database_header_page_count(&self.data).expect("fixed-size page has page count")
317    }
318
319    /// Write page count to header page
320    pub fn write_page_count(&mut self, count: u32) {
321        reddb_file::set_database_header_page_count(&mut self.data, count)
322            .expect("fixed-size page has page count");
323    }
324
325    /// Read freelist head from header page
326    pub fn read_freelist_head(&self) -> u32 {
327        reddb_file::database_header_freelist_head(&self.data)
328            .expect("fixed-size page has freelist head")
329    }
330
331    /// Write freelist head to header page
332    pub fn write_freelist_head(&mut self, page_id: u32) {
333        reddb_file::set_database_header_freelist_head(&mut self.data, page_id)
334            .expect("fixed-size page has freelist head");
335    }
336
337    /// Verify this is a valid header page
338    pub fn verify_header_page(&self) -> Result<(), PageError> {
339        // Check magic bytes
340        if !reddb_file::database_header_magic_matches(&self.data) {
341            return Err(PageError::InvalidPageType(self.data[0]));
342        }
343
344        // Check page size
345        let stored_page_size = reddb_file::database_header_page_size(&self.data)
346            .map_err(|_| PageError::InvalidSize(self.data.len()))?
347            as usize;
348
349        if stored_page_size != PAGE_SIZE {
350            return Err(PageError::InvalidSize(stored_page_size));
351        }
352
353        Ok(())
354    }
355}