Skip to main content

reddb_server/storage/engine/
page.rs

1//! Page structure for RedDB storage engine
2//!
3//! A page is the fundamental unit of storage (4KB aligned for efficient I/O).
4//! Each page has a fixed header followed by type-specific content.
5//!
6//! # Page Layout (4096 bytes)
7//!
8//! ```text
9//! ┌───────────────────────────────────────────────────────────┐
10//! │ PageHeader (32 bytes)                                     │
11//! ├───────────────────────────────────────────────────────────┤
12//! │ Cell Pointer Array (grows downward from header)           │
13//! │ [u16, u16, u16, ...]                                      │
14//! ├───────────────────────────────────────────────────────────┤
15//! │ Free Space (unallocated)                                  │
16//! │                                                           │
17//! ├───────────────────────────────────────────────────────────┤
18//! │ Cell Content Area (grows upward from bottom)              │
19//! │ [Cell N] [Cell N-1] ... [Cell 1]                          │
20//! └───────────────────────────────────────────────────────────┘
21//! ```
22//!
23//! # References
24//!
25//! - Turso `core/storage/pager.rs:136-152` - PageInner struct
26//! - Turso `core/storage/btree.rs:54-102` - B-tree page header offsets
27//! - Turso `core/storage/sqlite3_ondisk.rs` - PageType definitions
28
29use super::crc32::crc32;
30
31/// Page size in bytes (4KB, standard for most file systems)
32pub const PAGE_SIZE: usize = 4096;
33
34/// Header size in bytes
35pub const HEADER_SIZE: usize = 32;
36
37/// Content area size (page minus header)
38pub const CONTENT_SIZE: usize = PAGE_SIZE - HEADER_SIZE;
39
40/// Maximum number of cells per page (limited by cell pointer array)
41pub const MAX_CELLS: usize = (CONTENT_SIZE - 4) / 6; // ~676 cells
42
43/// Magic bytes for database file identification
44pub const MAGIC_BYTES: [u8; 4] = [0x52, 0x44, 0x44, 0x42]; // "RDDB"
45
46/// Database file version (1.0.0)
47pub const DB_VERSION: u32 = 0x00010000;
48
49/// Page type enumeration
50///
51/// Based on Turso `core/storage/sqlite3_ondisk.rs` PageType definitions.
52#[repr(u8)]
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum PageType {
55    /// Free page (available for allocation)
56    Free = 0,
57    /// B-tree leaf page (contains key-value pairs)
58    BTreeLeaf = 1,
59    /// B-tree interior page (contains keys and child pointers)
60    BTreeInterior = 2,
61    /// Overflow page (continuation of large values)
62    Overflow = 3,
63    /// Vector data page (dense vector storage)
64    Vector = 4,
65    /// Freelist trunk page (tracks free pages)
66    FreelistTrunk = 5,
67    /// Database header page (page 0)
68    Header = 6,
69    /// Graph node page (packed node records)
70    GraphNode = 7,
71    /// Graph edge page (packed edge records)
72    GraphEdge = 8,
73    /// Graph adjacency list page (outgoing edges per node)
74    GraphAdjacency = 9,
75    /// Graph metadata page (statistics, index roots)
76    GraphMeta = 10,
77    /// Native physical metadata page (engine-published auxiliary state)
78    NativeMeta = 11,
79    /// Encrypted auth vault page (users, API keys, bootstrap state)
80    Vault = 12,
81}
82
83impl PageType {
84    /// Convert from u8
85    pub fn from_u8(value: u8) -> Option<Self> {
86        match value {
87            0 => Some(Self::Free),
88            1 => Some(Self::BTreeLeaf),
89            2 => Some(Self::BTreeInterior),
90            3 => Some(Self::Overflow),
91            4 => Some(Self::Vector),
92            5 => Some(Self::FreelistTrunk),
93            6 => Some(Self::Header),
94            7 => Some(Self::GraphNode),
95            8 => Some(Self::GraphEdge),
96            9 => Some(Self::GraphAdjacency),
97            10 => Some(Self::GraphMeta),
98            11 => Some(Self::NativeMeta),
99            12 => Some(Self::Vault),
100            _ => None,
101        }
102    }
103}
104
105/// Page flags (bitfield)
106#[repr(u8)]
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum PageFlag {
109    /// Page has been modified
110    Dirty = 0x01,
111    /// Page is locked for writing
112    Locked = 0x02,
113    /// Page data is loaded in memory
114    Loaded = 0x04,
115    /// Page is pinned in cache (cannot be evicted)
116    Pinned = 0x08,
117    /// Page content is encrypted
118    Encrypted = 0x10,
119}
120
121/// Page header structure (32 bytes)
122///
123/// Layout:
124/// ```text
125/// Offset  Size  Field
126/// ------  ----  -----
127///   0      1    page_type
128///   1      1    flags
129///   2      2    cell_count
130///   4      2    free_start (offset to first free byte in cell pointer array)
131///   6      2    free_end (offset to first free byte before cell content)
132///   8      4    page_id
133///  12      4    parent_id (0 for root)
134///  16      4    right_child (for interior nodes, 0 otherwise)
135///  20      8    lsn (Log Sequence Number for WAL)
136///  28      4    checksum (CRC32 of content)
137/// ```
138#[derive(Debug, Clone, Copy)]
139pub struct PageHeader {
140    /// Type of this page
141    pub page_type: PageType,
142    /// Page flags (dirty, locked, etc.)
143    pub flags: u8,
144    /// Number of cells on this page
145    pub cell_count: u16,
146    /// Offset to start of free space (cell pointer array end)
147    pub free_start: u16,
148    /// Offset to end of free space (cell content start)
149    pub free_end: u16,
150    /// Unique page identifier
151    pub page_id: u32,
152    /// Parent page ID (0 for root or orphan)
153    pub parent_id: u32,
154    /// Right-most child page (interior nodes only)
155    pub right_child: u32,
156    /// Log Sequence Number (for WAL ordering)
157    pub lsn: u64,
158    /// CRC32 checksum of page content
159    pub checksum: u32,
160}
161
162impl PageHeader {
163    /// Create a new header for an empty page
164    pub fn new(page_type: PageType, page_id: u32) -> Self {
165        Self {
166            page_type,
167            flags: 0,
168            cell_count: 0,
169            free_start: HEADER_SIZE as u16,
170            free_end: PAGE_SIZE as u16,
171            page_id,
172            parent_id: 0,
173            right_child: 0,
174            lsn: 0,
175            checksum: 0,
176        }
177    }
178
179    /// Serialize header to bytes
180    pub fn to_bytes(&self) -> [u8; HEADER_SIZE] {
181        let mut buf = [0u8; HEADER_SIZE];
182
183        buf[0] = self.page_type as u8;
184        buf[1] = self.flags;
185        buf[2..4].copy_from_slice(&self.cell_count.to_le_bytes());
186        buf[4..6].copy_from_slice(&self.free_start.to_le_bytes());
187        buf[6..8].copy_from_slice(&self.free_end.to_le_bytes());
188        buf[8..12].copy_from_slice(&self.page_id.to_le_bytes());
189        buf[12..16].copy_from_slice(&self.parent_id.to_le_bytes());
190        buf[16..20].copy_from_slice(&self.right_child.to_le_bytes());
191        buf[20..28].copy_from_slice(&self.lsn.to_le_bytes());
192        buf[28..32].copy_from_slice(&self.checksum.to_le_bytes());
193
194        buf
195    }
196
197    /// Deserialize header from bytes
198    pub fn from_bytes(buf: &[u8; HEADER_SIZE]) -> Result<Self, PageError> {
199        let page_type = PageType::from_u8(buf[0]).ok_or(PageError::InvalidPageType(buf[0]))?;
200
201        Ok(Self {
202            page_type,
203            flags: buf[1],
204            cell_count: u16::from_le_bytes([buf[2], buf[3]]),
205            free_start: u16::from_le_bytes([buf[4], buf[5]]),
206            free_end: u16::from_le_bytes([buf[6], buf[7]]),
207            page_id: u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]),
208            parent_id: u32::from_le_bytes([buf[12], buf[13], buf[14], buf[15]]),
209            right_child: u32::from_le_bytes([buf[16], buf[17], buf[18], buf[19]]),
210            lsn: u64::from_le_bytes([
211                buf[20], buf[21], buf[22], buf[23], buf[24], buf[25], buf[26], buf[27],
212            ]),
213            checksum: u32::from_le_bytes([buf[28], buf[29], buf[30], buf[31]]),
214        })
215    }
216
217    /// Check if page has specific flag
218    #[inline]
219    pub fn has_flag(&self, flag: PageFlag) -> bool {
220        self.flags & (flag as u8) != 0
221    }
222
223    /// Set a flag
224    #[inline]
225    pub fn set_flag(&mut self, flag: PageFlag) {
226        self.flags |= flag as u8;
227    }
228
229    /// Clear a flag
230    #[inline]
231    pub fn clear_flag(&mut self, flag: PageFlag) {
232        self.flags &= !(flag as u8);
233    }
234
235    /// Calculate free space available for new cells
236    #[inline]
237    pub fn free_space(&self) -> usize {
238        if self.free_end <= self.free_start {
239            0
240        } else {
241            (self.free_end - self.free_start) as usize
242        }
243    }
244}
245
246/// Page error types
247#[derive(Debug, Clone)]
248pub enum PageError {
249    /// Invalid page type byte
250    InvalidPageType(u8),
251    /// Page checksum mismatch (corruption detected)
252    ChecksumMismatch { expected: u32, actual: u32 },
253    /// Invalid page size
254    InvalidSize(usize),
255    /// Page is full
256    PageFull,
257    /// Cell index out of bounds
258    CellOutOfBounds(usize),
259    /// Invalid cell pointer
260    InvalidCellPointer(u16),
261    /// Overflow required for large value
262    OverflowRequired,
263}
264
265impl std::fmt::Display for PageError {
266    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267        match self {
268            Self::InvalidPageType(t) => write!(f, "Invalid page type: {}", t),
269            Self::ChecksumMismatch { expected, actual } => {
270                write!(
271                    f,
272                    "Checksum mismatch: expected 0x{:08X}, got 0x{:08X}",
273                    expected, actual
274                )
275            }
276            Self::InvalidSize(s) => write!(f, "Invalid page size: {} (expected {})", s, PAGE_SIZE),
277            Self::PageFull => write!(f, "Page is full"),
278            Self::CellOutOfBounds(i) => write!(f, "Cell index {} out of bounds", i),
279            Self::InvalidCellPointer(p) => write!(f, "Invalid cell pointer: {}", p),
280            Self::OverflowRequired => write!(f, "Value too large, overflow page required"),
281        }
282    }
283}
284
285impl std::error::Error for PageError {}
286
287/// A 4KB page with header and content
288///
289/// This is the core data structure for the storage engine.
290#[derive(Clone)]
291pub struct Page {
292    /// Raw page data
293    data: [u8; PAGE_SIZE],
294}
295
296#[path = "page/impl.rs"]
297mod page_impl;
298impl Default for Page {
299    fn default() -> Self {
300        Self::new(PageType::Free, 0)
301    }
302}
303
304impl std::fmt::Debug for Page {
305    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306        if let Ok(header) = self.header() {
307            f.debug_struct("Page")
308                .field("page_type", &header.page_type)
309                .field("page_id", &header.page_id)
310                .field("cell_count", &header.cell_count)
311                .field("free_space", &header.free_space())
312                .field("lsn", &header.lsn)
313                .finish()
314        } else {
315            f.debug_struct("Page")
316                .field("data", &"[invalid header]")
317                .finish()
318        }
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    #[test]
327    fn test_page_header_roundtrip() {
328        let header = PageHeader {
329            page_type: PageType::BTreeLeaf,
330            flags: 0x05,
331            cell_count: 42,
332            free_start: 100,
333            free_end: 4000,
334            page_id: 12345,
335            parent_id: 99,
336            right_child: 0,
337            lsn: 0xDEADBEEF,
338            checksum: 0x12345678,
339        };
340
341        let bytes = header.to_bytes();
342        let decoded = PageHeader::from_bytes(&bytes).unwrap();
343
344        assert_eq!(decoded.page_type, header.page_type);
345        assert_eq!(decoded.flags, header.flags);
346        assert_eq!(decoded.cell_count, header.cell_count);
347        assert_eq!(decoded.free_start, header.free_start);
348        assert_eq!(decoded.free_end, header.free_end);
349        assert_eq!(decoded.page_id, header.page_id);
350        assert_eq!(decoded.parent_id, header.parent_id);
351        assert_eq!(decoded.right_child, header.right_child);
352        assert_eq!(decoded.lsn, header.lsn);
353        assert_eq!(decoded.checksum, header.checksum);
354    }
355
356    #[test]
357    fn test_page_new() {
358        let page = Page::new(PageType::BTreeLeaf, 42);
359        let header = page.header().unwrap();
360
361        assert_eq!(header.page_type, PageType::BTreeLeaf);
362        assert_eq!(header.page_id, 42);
363        assert_eq!(header.cell_count, 0);
364        assert_eq!(header.free_start, HEADER_SIZE as u16);
365        assert_eq!(header.free_end, PAGE_SIZE as u16);
366    }
367
368    #[test]
369    fn test_page_checksum() {
370        let mut page = Page::new(PageType::BTreeLeaf, 1);
371        page.update_checksum();
372        assert!(page.verify_checksum().is_ok());
373
374        // Corrupt the page
375        page.data[100] ^= 0xFF;
376        assert!(page.verify_checksum().is_err());
377    }
378
379    #[test]
380    fn test_page_insert_cell() {
381        let mut page = Page::new(PageType::BTreeLeaf, 1);
382
383        let key = b"hello";
384        let value = b"world";
385
386        let index = page.insert_cell(key, value).unwrap();
387        assert_eq!(index, 0);
388        assert_eq!(page.cell_count(), 1);
389
390        let (read_key, read_value) = page.read_cell(0).unwrap();
391        assert_eq!(read_key, key.to_vec());
392        assert_eq!(read_value, value.to_vec());
393    }
394
395    #[test]
396    fn test_page_multiple_cells() {
397        let mut page = Page::new(PageType::BTreeLeaf, 1);
398
399        for i in 0..10 {
400            let key = format!("key{:03}", i);
401            let value = format!("value{}", i);
402            page.insert_cell(key.as_bytes(), value.as_bytes()).unwrap();
403        }
404
405        assert_eq!(page.cell_count(), 10);
406
407        for i in 0..10 {
408            let (key, value) = page.read_cell(i).unwrap();
409            assert_eq!(key, format!("key{:03}", i).as_bytes());
410            assert_eq!(value, format!("value{}", i).as_bytes());
411        }
412    }
413
414    #[test]
415    fn test_page_search_key() {
416        let mut page = Page::new(PageType::BTreeLeaf, 1);
417
418        // Insert sorted keys
419        for i in [10, 20, 30, 40, 50] {
420            let key = format!("{:03}", i);
421            page.insert_cell(key.as_bytes(), b"v").unwrap();
422        }
423
424        // Search existing
425        assert_eq!(page.search_key(b"020"), Ok(1));
426        assert_eq!(page.search_key(b"040"), Ok(3));
427
428        // Search non-existing
429        assert_eq!(page.search_key(b"015"), Err(1));
430        assert_eq!(page.search_key(b"000"), Err(0));
431        assert_eq!(page.search_key(b"060"), Err(5));
432    }
433
434    #[test]
435    fn test_page_full() {
436        let mut page = Page::new(PageType::BTreeLeaf, 1);
437
438        // Fill the page
439        let large_value = vec![0xAB; 500];
440        let mut count = 0;
441
442        loop {
443            let key = format!("key{:05}", count);
444            match page.insert_cell(key.as_bytes(), &large_value) {
445                Ok(_) => count += 1,
446                Err(PageError::PageFull) => break,
447                Err(e) => panic!("Unexpected error: {:?}", e),
448            }
449        }
450
451        assert!(count > 0);
452        assert!(count < 10); // With 500 byte values, should fit ~7 cells
453    }
454
455    #[test]
456    fn test_header_page() {
457        let page = Page::new_header_page(100);
458
459        assert!(page.verify_header_page().is_ok());
460        assert_eq!(page.read_page_count(), 100);
461        assert_eq!(page.read_freelist_head(), 0);
462    }
463
464    #[test]
465    fn test_page_flags() {
466        let mut header = PageHeader::new(PageType::BTreeLeaf, 1);
467
468        assert!(!header.has_flag(PageFlag::Dirty));
469        assert!(!header.has_flag(PageFlag::Locked));
470
471        header.set_flag(PageFlag::Dirty);
472        assert!(header.has_flag(PageFlag::Dirty));
473        assert!(!header.has_flag(PageFlag::Locked));
474
475        header.set_flag(PageFlag::Locked);
476        assert!(header.has_flag(PageFlag::Dirty));
477        assert!(header.has_flag(PageFlag::Locked));
478
479        header.clear_flag(PageFlag::Dirty);
480        assert!(!header.has_flag(PageFlag::Dirty));
481        assert!(header.has_flag(PageFlag::Locked));
482    }
483
484    #[test]
485    fn test_free_space_calculation() {
486        let page = Page::new(PageType::BTreeLeaf, 1);
487        let header = page.header().unwrap();
488
489        // New page should have max free space
490        assert_eq!(header.free_space(), PAGE_SIZE - HEADER_SIZE);
491    }
492
493    // ============================================================================
494    // Additional comprehensive tests for page operations
495    // ============================================================================
496
497    #[test]
498    fn test_all_page_types() {
499        // Verify all page types can be created and round-tripped
500        let page_types = [
501            PageType::Free,
502            PageType::BTreeLeaf,
503            PageType::BTreeInterior,
504            PageType::Overflow,
505            PageType::Vector,
506            PageType::FreelistTrunk,
507            PageType::Header,
508            PageType::GraphNode,
509            PageType::GraphEdge,
510            PageType::GraphAdjacency,
511            PageType::GraphMeta,
512            PageType::NativeMeta,
513            PageType::Vault,
514        ];
515
516        for (i, &pt) in page_types.iter().enumerate() {
517            let page = Page::new(pt, i as u32);
518            assert_eq!(page.page_type().unwrap(), pt);
519            assert_eq!(page.page_id(), i as u32);
520        }
521    }
522
523    #[test]
524    fn test_page_type_from_u8() {
525        assert_eq!(PageType::from_u8(0), Some(PageType::Free));
526        assert_eq!(PageType::from_u8(1), Some(PageType::BTreeLeaf));
527        assert_eq!(PageType::from_u8(2), Some(PageType::BTreeInterior));
528        assert_eq!(PageType::from_u8(10), Some(PageType::GraphMeta));
529        assert_eq!(PageType::from_u8(11), Some(PageType::NativeMeta));
530        assert_eq!(PageType::from_u8(12), Some(PageType::Vault));
531        assert_eq!(PageType::from_u8(13), None);
532        assert_eq!(PageType::from_u8(255), None);
533    }
534
535    #[test]
536    fn test_page_from_slice_valid() {
537        let original = Page::new(PageType::BTreeLeaf, 123);
538        let slice = original.as_bytes();
539        let restored = Page::from_slice(slice).unwrap();
540
541        assert_eq!(restored.page_id(), 123);
542        assert_eq!(restored.page_type().unwrap(), PageType::BTreeLeaf);
543    }
544
545    #[test]
546    fn test_page_from_slice_invalid_size() {
547        let short_slice = [0u8; 100];
548        let result = Page::from_slice(&short_slice);
549        assert!(matches!(result, Err(PageError::InvalidSize(100))));
550
551        let long_slice = [0u8; 5000];
552        let result = Page::from_slice(&long_slice);
553        assert!(matches!(result, Err(PageError::InvalidSize(5000))));
554    }
555
556    #[test]
557    fn test_page_parent_and_child() {
558        let mut page = Page::new(PageType::BTreeInterior, 10);
559
560        page.set_parent_id(5);
561        page.set_right_child(15);
562
563        assert_eq!(page.parent_id(), 5);
564        assert_eq!(page.right_child(), 15);
565
566        // Verify through header
567        let header = page.header().unwrap();
568        assert_eq!(header.parent_id, 5);
569        assert_eq!(header.right_child, 15);
570    }
571
572    #[test]
573    fn test_cell_pointer_bounds() {
574        let page = Page::new(PageType::BTreeLeaf, 1);
575
576        // No cells, so index 0 is out of bounds
577        let result = page.get_cell_pointer(0);
578        assert!(matches!(result, Err(PageError::CellOutOfBounds(0))));
579
580        let result = page.get_cell_pointer(100);
581        assert!(matches!(result, Err(PageError::CellOutOfBounds(100))));
582    }
583
584    #[test]
585    fn test_cell_pointer_invalid_value() {
586        let mut page = Page::new(PageType::BTreeLeaf, 1);
587
588        // Pointer too low (inside header)
589        let result = page.set_cell_pointer(0, 10);
590        assert!(matches!(result, Err(PageError::InvalidCellPointer(10))));
591
592        // Pointer too high (past page)
593        let result = page.set_cell_pointer(0, PAGE_SIZE as u16 + 1);
594        assert!(matches!(result, Err(PageError::InvalidCellPointer(_))));
595    }
596
597    #[test]
598    fn test_empty_key_value() {
599        let mut page = Page::new(PageType::BTreeLeaf, 1);
600
601        // Empty key
602        page.insert_cell(b"", b"value").unwrap();
603        let (key, value) = page.read_cell(0).unwrap();
604        assert!(key.is_empty());
605        assert_eq!(value, b"value");
606
607        // Empty value
608        page.insert_cell(b"key", b"").unwrap();
609        let (key, value) = page.read_cell(1).unwrap();
610        assert_eq!(key, b"key");
611        assert!(value.is_empty());
612
613        // Both empty
614        page.insert_cell(b"", b"").unwrap();
615        let (key, value) = page.read_cell(2).unwrap();
616        assert!(key.is_empty());
617        assert!(value.is_empty());
618    }
619
620    #[test]
621    fn test_large_value_overflow() {
622        let mut page = Page::new(PageType::BTreeLeaf, 1);
623
624        // Value larger than content area should require overflow
625        let huge_value = vec![0xAB; CONTENT_SIZE];
626        let result = page.insert_cell(b"key", &huge_value);
627        assert!(matches!(result, Err(PageError::OverflowRequired)));
628    }
629
630    #[test]
631    fn test_checksum_stability() {
632        let mut page = Page::new(PageType::BTreeLeaf, 42);
633        page.insert_cell(b"test", b"data").unwrap();
634
635        page.update_checksum();
636        let checksum1 = page.header().unwrap().checksum;
637
638        // Same content should produce same checksum
639        page.update_checksum();
640        let checksum2 = page.header().unwrap().checksum;
641
642        assert_eq!(checksum1, checksum2);
643    }
644
645    #[test]
646    fn test_checksum_changes_with_content() {
647        let mut page1 = Page::new(PageType::BTreeLeaf, 1);
648        let mut page2 = Page::new(PageType::BTreeLeaf, 1);
649
650        page1.insert_cell(b"key1", b"value1").unwrap();
651        page2.insert_cell(b"key2", b"value2").unwrap();
652
653        page1.update_checksum();
654        page2.update_checksum();
655
656        assert_ne!(
657            page1.header().unwrap().checksum,
658            page2.header().unwrap().checksum
659        );
660    }
661
662    #[test]
663    fn test_free_space_decreases_with_cells() {
664        let mut page = Page::new(PageType::BTreeLeaf, 1);
665        let initial_free = page.header().unwrap().free_space();
666
667        page.insert_cell(b"key", b"value").unwrap();
668        let after_first = page.header().unwrap().free_space();
669
670        page.insert_cell(b"another_key", b"another_value").unwrap();
671        let after_second = page.header().unwrap().free_space();
672
673        assert!(after_first < initial_free);
674        assert!(after_second < after_first);
675    }
676
677    #[test]
678    fn test_search_empty_page() {
679        let page = Page::new(PageType::BTreeLeaf, 1);
680
681        // Search on empty page
682        assert_eq!(page.search_key(b"anything"), Err(0));
683    }
684
685    #[test]
686    fn test_search_single_cell() {
687        let mut page = Page::new(PageType::BTreeLeaf, 1);
688        page.insert_cell(b"middle", b"v").unwrap();
689
690        // Exact match
691        assert_eq!(page.search_key(b"middle"), Ok(0));
692
693        // Before
694        assert_eq!(page.search_key(b"aaa"), Err(0));
695
696        // After
697        assert_eq!(page.search_key(b"zzz"), Err(1));
698    }
699
700    #[test]
701    fn test_binary_data() {
702        let mut page = Page::new(PageType::BTreeLeaf, 1);
703
704        // Binary key and value with null bytes
705        let binary_key = [0x00, 0x01, 0x02, 0xFF, 0xFE];
706        let binary_value = [0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00];
707
708        page.insert_cell(&binary_key, &binary_value).unwrap();
709
710        let (key, value) = page.read_cell(0).unwrap();
711        assert_eq!(key, binary_key.to_vec());
712        assert_eq!(value, binary_value.to_vec());
713    }
714
715    #[test]
716    fn test_max_cells_stress() {
717        let mut page = Page::new(PageType::BTreeLeaf, 1);
718
719        // Insert many small cells
720        let mut inserted = 0;
721        for i in 0..MAX_CELLS {
722            let key = format!("{:04}", i);
723            if page.insert_cell(key.as_bytes(), b"x").is_ok() {
724                inserted += 1;
725            } else {
726                break;
727            }
728        }
729
730        // Verify all inserted cells are readable
731        for i in 0..inserted {
732            let (key, _) = page.read_cell(i).unwrap();
733            assert_eq!(key, format!("{:04}", i).as_bytes());
734        }
735    }
736
737    #[test]
738    fn test_content_mut() {
739        let mut page = Page::new(PageType::BTreeLeaf, 1);
740
741        // Get mutable content and modify
742        let content = page.content_mut();
743        content[0] = 0xAB;
744        content[1] = 0xCD;
745
746        // Verify modification persisted
747        let content = page.content();
748        assert_eq!(content[0], 0xAB);
749        assert_eq!(content[1], 0xCD);
750    }
751
752    #[test]
753    fn test_page_bytes_roundtrip() {
754        let mut page = Page::new(PageType::BTreeLeaf, 999);
755        page.insert_cell(b"key", b"value").unwrap();
756        page.update_checksum();
757
758        // Get bytes and recreate
759        let bytes = *page.as_bytes();
760        let restored = Page::from_bytes(bytes);
761
762        assert_eq!(restored.page_id(), 999);
763        assert!(restored.verify_checksum().is_ok());
764
765        let (key, value) = restored.read_cell(0).unwrap();
766        assert_eq!(key, b"key");
767        assert_eq!(value, b"value");
768    }
769
770    #[test]
771    fn test_header_page_operations() {
772        let mut page = Page::new_header_page(1000);
773
774        assert!(page.verify_header_page().is_ok());
775        assert_eq!(page.read_page_count(), 1000);
776        assert_eq!(page.read_freelist_head(), 0);
777
778        // Update page count
779        page.write_page_count(2000);
780        assert_eq!(page.read_page_count(), 2000);
781
782        // Update freelist head
783        page.write_freelist_head(42);
784        assert_eq!(page.read_freelist_head(), 42);
785    }
786
787    #[test]
788    fn test_page_flags_multiple() {
789        let mut header = PageHeader::new(PageType::BTreeLeaf, 1);
790
791        // Set multiple flags
792        header.set_flag(PageFlag::Dirty);
793        header.set_flag(PageFlag::Locked);
794        header.set_flag(PageFlag::Encrypted);
795
796        assert!(header.has_flag(PageFlag::Dirty));
797        assert!(header.has_flag(PageFlag::Locked));
798        assert!(header.has_flag(PageFlag::Encrypted));
799        assert!(!header.has_flag(PageFlag::Pinned));
800
801        // Clear one flag
802        header.clear_flag(PageFlag::Locked);
803        assert!(header.has_flag(PageFlag::Dirty));
804        assert!(!header.has_flag(PageFlag::Locked));
805        assert!(header.has_flag(PageFlag::Encrypted));
806    }
807
808    #[test]
809    fn test_page_error_display() {
810        let errors = [
811            PageError::InvalidPageType(99),
812            PageError::ChecksumMismatch {
813                expected: 0x1234,
814                actual: 0x5678,
815            },
816            PageError::InvalidSize(100),
817            PageError::PageFull,
818            PageError::CellOutOfBounds(5),
819            PageError::InvalidCellPointer(10),
820            PageError::OverflowRequired,
821        ];
822
823        for error in &errors {
824            // Just verify Display doesn't panic
825            let _msg = format!("{}", error);
826        }
827    }
828
829    #[test]
830    fn test_cell_count_consistency() {
831        let mut page = Page::new(PageType::BTreeLeaf, 1);
832
833        assert_eq!(page.cell_count(), 0);
834
835        page.insert_cell(b"a", b"1").unwrap();
836        assert_eq!(page.cell_count(), 1);
837
838        page.insert_cell(b"b", b"2").unwrap();
839        assert_eq!(page.cell_count(), 2);
840
841        page.insert_cell(b"c", b"3").unwrap();
842        assert_eq!(page.cell_count(), 3);
843
844        // Set cell count manually (for testing)
845        page.set_cell_count(0);
846        assert_eq!(page.cell_count(), 0);
847    }
848
849    #[test]
850    fn test_free_start_end_consistency() {
851        let mut page = Page::new(PageType::BTreeLeaf, 1);
852
853        let initial_start = page.free_start();
854        let initial_end = page.free_end();
855
856        assert_eq!(initial_start, HEADER_SIZE as u16);
857        assert_eq!(initial_end, PAGE_SIZE as u16);
858
859        page.insert_cell(b"test_key", b"test_value").unwrap();
860
861        let after_start = page.free_start();
862        let after_end = page.free_end();
863
864        // free_start should increase (cell pointer added)
865        assert!(after_start > initial_start);
866        // free_end should decrease (cell content added)
867        assert!(after_end < initial_end);
868    }
869}