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