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