Overview
Table pages implement the node structures for Databas’s B-tree storage. They come in two flavors: leaf pages store actual row data, while interior pages store separator keys and child pointers for tree navigation. Module:databas_core::table_page
Visibility: pub(crate) - Internal to the core crate
Page Types
Both page types use a common slotted-page layout:- Leaf pages: Type ID = 1, Header size = 8 bytes
- Interior pages: Type ID = 2, Header size = 16 bytes
Common Header (bytes 0-6)
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 1 | Page Type | 1 = leaf, 2 = interior |
| 1 | 1 | Reserved | Unused |
| 2 | 2 | Cell Count | Number of cells (little-endian u16) |
| 4 | 2 | Content Start | Offset where cell data begins |
| 6 | 2 | Reserved | Unused in leaf pages |
Interior Page Header (bytes 8-15)
| Offset | Size | Field | Description |
|---|---|---|---|
| 8 | 8 | Rightmost Child | Page ID of rightmost child (little-endian u64) |
Leaf Pages
Leaf pages store table rows keyed by row ID.Structures
TableLeafPageRef
Immutable view of a leaf page.TableLeafPageMut
Mutable view of a leaf page.LeafCellRef
Borrowed view of one decoded leaf cell.The row identifier (u64) used as the sorting key.
Borrowed slice of the row’s payload bytes. Maximum size is 65,535 bytes.
Leaf Cell Format
10 + payload_length bytes
Leaf Page Methods
init_empty
Initializes an empty leaf page.Page buffer to initialize.
from_bytes
Wraps an existing page buffer with validation.- Page type byte must be 1 (leaf)
- Cell count and content start must be consistent
- Slot directory must not overlap cell content region
TablePageError::InvalidPageType- Wrong page typeTablePageError::CorruptPage- Header validation failed
search
Looks up a cell by row ID.The row identifier to search for.
Returns
Ok(Some(cell)) if found, Ok(None) if not found.- Binary search over sorted slot directory
- O(log n) time complexity
- Returns borrowed payload slice (zero-copy)
insert
Inserts a new row with the given row ID and payload.The row identifier. Must be unique within the page.
Row payload bytes. Maximum size is 65,535 bytes.
- Maintains sorted order by row ID
- Automatically defragments once if space is fragmented
- Inserts slot entry and cell content
TablePageError::DuplicateRowId- Row ID already existsTablePageError::CellTooLarge- Payload exceeds 65,535 bytesTablePageError::PageFull- Insufficient space even after defragmentation
update
Replaces the payload for an existing row ID.The row identifier. Must exist on the page.
New payload bytes.
- If new payload is the same size, updates in-place (no defragmentation)
- If size differs, allocates new cell and marks old as dead space
- Automatically defragments once if needed
TablePageError::RowIdNotFound- Row ID doesn’t existTablePageError::CellTooLarge- New payload too largeTablePageError::PageFull- Insufficient space for larger payload
delete
Deletes the cell for the given row ID.The row identifier to delete.
- Removes slot entry
- Leaves cell content as dead space (not reclaimed until defragmentation)
- O(n) to shift remaining slots
TablePageError::RowIdNotFound- Row ID doesn’t exist
defragment
Compacts live cells and reclaims dead space.- Copies live cells contiguously toward page end
- Rebuilds slot directory with updated offsets
- Reclaims space from deleted or updated cells
cell_count
Returns the number of cells currently stored.free_space
Returns available bytes between slot directory and cell content.defragment() to reclaim.
Interior Pages
Interior pages implement B-tree internal nodes with separator keys.Structures
TableInteriorPageRef
Immutable view of an interior page.TableInteriorPageMut
Mutable view of an interior page.InteriorCell
Decoded interior cell mapping a separator key to a child pointer.Page ID of the child page containing keys less than
row_id.Separator key. Keys in
left_child are < row_id.Interior Cell Format
Rightmost Child
Interior pages have a special rightmost child pointer in the header (bytes 8-15) for keys greater than all separators.Interior Page Methods
init_empty
Initializes an empty interior page.Page buffer to initialize.
Page ID for keys greater than all separators.
from_bytes
Wraps an existing interior page with validation.- Page type byte must be 2 (interior)
- Header layout must be valid
TablePageError::InvalidPageType- Wrong page typeTablePageError::CorruptPage- Validation failed
search
Looks up a separator cell by row ID.Some(cell) if an exact match exists, None otherwise.
Example:
child_for_row_id
Returns the child page ID to descend into for a given row ID.The row ID to route.
Returns the child page ID that may contain
row_id.- Find the first separator
>= row_id - If found, return its
left_child - Otherwise, return
rightmost_child
insert
Inserts a new separator cell.Separator key. Must be unique.
Child page for keys
< row_id.TablePageError::DuplicateRowId- Separator already existsTablePageError::PageFull- No space for new cell
update
Replaces the left child pointer for an existing separator.TablePageError::RowIdNotFound- Separator doesn’t exist
delete
Deletes a separator cell.TablePageError::RowIdNotFound- Separator doesn’t exist
rightmost_child / set_rightmost_child
Gets or sets the rightmost child pointer.defragment
Compacts live cells and reclaims dead space.cell_count / free_space
Same as leaf pages.Runtime Page Type Dispatch
TablePageRef / TablePageMut
Wrappers that dispatch to leaf or interior pages at runtime.from_bytes
Deserializes a page of unknown type.Error Types
TablePageError
InvalidPageType
Page type byte is not 1 (leaf) or 2 (interior).
CorruptPage
Header validation failed. See
TablePageCorruptionKind for details.CorruptCell
Cell at the given slot index is malformed or out of bounds.
DuplicateRowId
Attempted to insert a row ID that already exists.
RowIdNotFound
Attempted to update or delete a non-existent row ID.
CellTooLarge
Payload exceeds 65,535 bytes (leaf) or cell is otherwise too large.
PageFull
Insufficient space for the operation even after defragmentation.
TablePageCorruptionKind
Constants
Page type identifier for leaf pages:
1Page type identifier for interior pages:
2Leaf page header size:
8 bytesInterior page header size:
16 bytesEnd of usable page data (before checksum):
4092 bytesImplementation Notes
Slotted Page Layout
- Slot directory grows from the header toward the page end
- Cell content grows from the page end toward the header
- Free space is the gap between them
- Defragmentation moves cells contiguously to maximize free space
Row ID Ordering
- Both leaf and interior pages maintain slots in sorted order by row ID
- Binary search provides O(log n) lookups
- Insertions shift subsequent slots (O(n) cost)
Maximum Sizes
- Leaf payload: 65,535 bytes (limited by
u16length field) - Practical leaf payload: ~4,070 bytes per cell (including overhead)
- Cells per page: Varies, typically 50-400 depending on payload size
- Interior cells: Fixed at 16 bytes each, ~250 cells per page
Fragmentation
- Deletion: Leaves dead space until defragmentation
- Update: If size changes, old cell becomes dead space
- Automatic defrag: Triggered once by insert/update before reporting page full
- Manual defrag: Call
defragment()explicitly to reclaim space
Related Types
- PageCache - Caches table pages in memory
- RowId - Type alias for
u64, row identifier - PageId - Type alias for
u64, page identifier - PAGE_SIZE - Constant defining page size (4096 bytes)