Reference implementation of the monumentum_handler contracts.
Provides concrete storage backends, serialization, indexing, and table/catalog management built on a page-based storage engine with Write-Ahead Logging (WAL), buffer pool caching, and B-tree indexes.
buffer_poolpagerpagecatalogindex
serdestore
tabletable_storagemonumentum_core is a low-level storage engine library that persists data to disk using a fixed-size page format. It replaces earlier in-memory storage with a robust architecture that includes:
All components are designed to work together to provide a reliable, single‑file database backend (with a separate WAL file) suitable for embedded applications.
+------------------+ +------------------+
| FileStorage | | InMemoryStorage |
+--------+---------+ +------------------+
|
v
+------------+ +------------------+
| BufferPool | <-----> | Pager |
+------------+ +------------------+
|
v
+------------+ +------------------+
| Page I/O | <-----> | File (locked) |
+------------+ +------------------+
|
v
+------------+ +------------------+
| Catalog | <-----> | WAL (append) |
+------------+ +------------------+
|
v
+------------+
| B‑tree Index|
+------------+
PageWrite) or table metadata updates (TableMetaUpdate) with LSNs.buffer_poolBufferPoolA simple page cache that stores up to capacity pages. It uses a clock‑based LRU approximation for eviction.
pub struct BufferPool { /* private */ }
| Method | Description |
|---|---|
new(pager: Pager, capacity: usize) -> Result<Self, DbError> |
Creates a new buffer pool. capacity must be > 0. |
get_page(&mut self, page_id: u32) -> Result<&mut Page, DbError> |
Returns a mutable reference to a page, loading it from disk if necessary. Increments pin count. |
unpin_page(&mut self, page_id: u32, dirty: bool) -> Result<(), DbError> |
Decrements pin count. If dirty is true, marks the page as dirty. |
mark_dirty(&mut self, page_id: u32) -> Result<(), DbError> |
Explicitly marks a page as dirty. |
flush_page(&mut self, page_id: u32) -> Result<(), DbError> |
Writes the page to disk if it is dirty and clears the dirty flag. |
flush_all(&mut self) -> Result<(), DbError> |
Flushes all dirty pages and syncs the file. |
allocate_page(&mut self, page_type: PageType) -> Result<u32, DbError> |
Allocates a new page via the pager and adds it to the pool. |
evict_one(&mut self) -> Result<(), DbError> |
Evicts the least‑recently‑used unpinned page. If dirty, flushes it first. |
page_count(&self) -> u32 |
Returns total number of pages in the database file. |
dirty_page_ids(&self) -> Vec<u32> |
Returns a list of page IDs currently marked dirty. |
Note:
get_pagemust be paired withunpin_pageto avoid pin leakage.
pagerPagerHandles low‑level page I/O on a single file. It enforces an exclusive lock and verifies CRC32 checksums.
pub struct Pager { /* private */ }
| Method | Description |
|---|---|
open(path: &Path) -> Result<Self, DbError> |
Opens or creates the database file, locks it exclusively, and validates file size is a multiple of PAGE_SIZE. |
read_page(&mut self, page_id: u32) -> Result<Page, DbError> |
Reads a page from disk and verifies its checksum. Returns DbError::Corruption on mismatch. |
write_page(&mut self, page: &Page) -> Result<(), DbError> |
Computes the page checksum and writes it to disk, then calls sync_data. |
allocate_page(&mut self, page_type: PageType) -> Result<u32, DbError> |
Appends a new zero‑initialized page to the file, updates page_count, and syncs. |
free_page(&mut self, page_id: u32) -> Result<(), DbError> |
Marks the page as PageType::Freelist and resets its header fields. |
sync(&mut self) -> Result<(), DbError> |
Calls sync_all on the underlying file. |
page_count(&self) -> u32 |
Returns the number of pages currently in the file. |
Drop behaviour: The file lock is automatically released when
Pageris dropped.
pagePageTypepub enum PageType {
Meta = 0,
Freelist = 1,
TableMeta = 2,
Data = 3,
Index = 4,
Overflow = 5,
}
PageHeaderpub struct PageHeader {
pub page_id: u32,
pub page_type: PageType,
pub free_space_offset: u16,
pub cell_count: u16,
pub checksum: u32,
pub flags: u32,
}
| Method | Description |
|---|---|
new(page_id, page_type) |
Creates a header with default values. |
to_bytes() |
Serializes the header to a 16‑byte little‑endian array. |
from_bytes(&[u8;16]) |
Deserializes the header. |
Pagepub struct Page {
pub header: PageHeader,
pub data: [u8; PAGE_BODY_SIZE],
}
| Method | Description |
|---|---|
new(page_id, page_type) |
Creates a blank page. |
as_bytes() |
Returns full 4096‑byte representation. |
from_bytes(&[u8]) |
Validates size and parses header/body. |
compute_checksum() |
Computes CRC32 (IEEE) over the header (with checksum field set to 0) and body. |
Constants
| Constant | Value | Description |
|---|---|---|
PAGE_SIZE |
4096 | Total page size in bytes. |
PAGE_HEADER_SIZE |
16 | Size of the page header. |
PAGE_BODY_SIZE |
4080 | Remaining bytes for page content. |
META_PAGE_ID |
0 | ID of the meta page. |
META_LSN_OFFSET |
0 | Offset of LSN in meta page. |
META_CATALOG_PAGE_OFFSET |
8 | Offset of catalog root page ID. |
META_LAST_CHECKPOINT_LSN_OFFSET |
12 | Offset of last checkpoint LSN. |
CATALOG_PAGE_HEADER_SIZE |
8 | Header size for catalog data pages. |
CATALOG_CHUNK_SIZE |
PAGE_BODY_SIZE - 8 |
Maximum catalog bytes per page. |
BTREE_NODE_HEADER_SIZE |
7 | Header size for B‑tree nodes. |
DATA_PAGE_HEADER_SIZE |
8 | Header size for row data pages. |
catalogCatalogA BTreeMap<String, Table> that holds table schemas and metadata. Implements CatalogStore.
pub struct Catalog { /* private */ }
| Method | Description |
|---|---|
new() |
Creates an empty catalog. |
create_table(&mut self, schema: TableSchema) |
Adds a new table; rejects empty names and duplicates. |
drop_table(&mut self, name: &str) |
Removes a table by name. |
replace_table(&mut self, name: &str, table: Table) |
Replaces an existing table; schema name must match. |
get_table(&self, name: &str) |
Returns a reference to a table. |
get_table_mut(&mut self, name: &str) |
Returns a mutable reference. |
tables(&self) |
Iterates over (name, table) pairs. |
len() |
Number of tables. |
is_empty() |
Whether catalog is empty. |
rename_table(&mut self, old, new) |
Renames a table; rejects conflicts and missing tables. |
indexbtreeBTreeOnDiskpub struct BTreeOnDisk {
root_page_id: u32,
}
| Method | Description |
|---|---|
create(buffer_pool: &mut BufferPool) |
Creates a new empty B‑tree by allocating a root index page. |
root_page_id() |
Returns the current root page ID. |
insert_static(buffer_pool, root_page_id: &mut u32, key, value) |
Inserts a key‑value pair. root_page_id may be updated if root splits. |
lookup_static(buffer_pool, root_page_id, key) |
Looks up a key and returns its associated value (usually a row index). |
delete_static(buffer_pool, root_page_id, key) |
Deletes a key and returns the value if it existed. |
range_scan_static(buffer_pool, root_page_id, start, end, result) |
Collects all key‑value pairs in [start, end). |
All methods are static and require an external
&mut BufferPool. This design allows multiple B‑trees to share the same buffer pool.
btree_indexBTreeIndexIn‑memory B‑tree index that maps IndexKey → Vec<usize> (row indices). Implements the Index trait.
| Method | Description |
|---|---|
new() |
Creates an empty index. |
insert(key, row_idx) |
Adds a row index to the key. |
contains(key) |
Returns whether the key exists. |
clear() |
Removes all entries. |
get_indices(key) |
Returns slice of row indices for the key. |
remove(key, row_idx) |
Removes a row index; deletes key when empty. |
hash_indexHashIndexSimilar to BTreeIndex but uses HashMap<IndexKey, Vec<usize>>.
| Method | Description |
|---|---|
new() |
Creates an empty hash index. |
insert(key, row_idx) |
Adds a row index. |
contains(key) |
Checks existence. |
clear() |
Clears the index. |
get_indices(key) |
Returns slice of row indices. |
remove(key, row_idx) |
Removes a row index. |
keyIndexKeypub enum IndexKey {
Integer(i64),
Float(u64), // normalized IEEE 754 bits
Text(String),
Blob(Vec<u8>),
Boolean(bool),
}
| Method | Description |
|---|---|
from_value(&Value) |
Converts a Value to an IndexKey; returns None for unsupported types (e.g., Null). |
to_bytes() |
Serializes key with a 1‑byte type tag followed by payload. |
from_bytes(&[u8]) |
Deserializes key and validates tag/length. |
serdeProvides binary encoding/decoding for all core types.
| Function | Description |
|---|---|
encode_catalog(&Catalog) -> Result<Vec<u8>, DbError> |
Serializes the entire catalog. |
decode_catalog(&[u8]) -> Result<Catalog, DbError> |
Deserializes a catalog, enforcing version and size limits. |
encode_row(&Row) -> Result<Vec<u8>, DbError> |
Serializes a row with a 4‑byte length prefix. |
decode_row(&[u8]) -> Result<Row, DbError> |
Deserializes a row from the length‑prefixed format. |
Encode and Decode are implemented for:
u8, u32, u64, i64, f64, bool)Option<T>, Vec<T>, &TValue, Row, TableSchema, ColumnDef, Table, CatalogEncoding Details
Null=0, Integer=1, Float=2, Text=3, Blob=4, Boolean=5.u64 for byte buffers and u32 for vector counts.MAX_READ_BYTES = 64 MiB, MAX_VEC_ELEMENTS = 1_000_000).storeappend_logWalRecordTypepub enum WalRecordType {
Snapshot = 0,
PageWrite = 1,
TableMetaUpdate = 2,
}
WalRecordpub struct WalRecord {
pub lsn: u64,
pub record_type: WalRecordType,
pub data: Vec<u8>,
}
| Function | Description |
|---|---|
append_record(file, payload) |
Appends a single log record with magic, version, length, CRC32 checksum, and payload. |
append_wal_record(file, lsn, record_type, data) |
Wraps data with LSN and record type, then calls append_record. |
read_records(file) |
Reads all raw records, validating magic, version, length, and checksum. |
read_wal_records(file) |
Parses raw records into structured WalRecord items. |
Record format (on disk):
+----------------+----------------+----------------+----------------+----------------+
| Magic (4) | Version (4) | Length (8) | CRC32 (4) | Payload (...) |
+----------------+----------------+----------------+----------------+----------------+
fileUtility functions for file operations.
| Function | Description |
|---|---|
open_or_create(path) |
Opens or creates a file with read/write permissions and no symlink following (Unix). |
read_file(path) |
Reads entire file into a Vec<u8>. |
write_all_atomic(path, data) |
Writes data to a temporary file, fsyncs, then renames to target path. |
append_to_file(file, data) |
Appends bytes to a file (no automatic sync). |
sync_file(file) |
Calls sync_all on the file. |
recoveryRecoveryResultpub struct RecoveryResult {
pub records: Vec<Vec<u8>>,
}
pub fn recover_wal(path: &Path) -> Result<RecoveryResult, DbError>
Opens the WAL, reads all raw records, and returns them.
storageStorageEnginepub trait StorageEngine {
fn create_table(&mut self, schema: TableSchema) -> Result<(), DbError>;
fn drop_table(&mut self, name: &str) -> Result<(), DbError>;
fn rename_table(&mut self, old_name: &str, new_name: &str) -> Result<(), DbError>;
fn insert_row(&mut self, table: &str, row: &Row) -> Result<(), DbError>;
fn get_row(&mut self, table: &str, row_idx: usize) -> Result<Option<Row>, DbError>;
fn set_cell(&mut self, table: &str, row_idx: usize, col_idx: usize, value: Value) -> Result<(), DbError>;
fn replace_rows(&mut self, table: &str, rows: Vec<Row>) -> Result<(), DbError>;
fn checkpoint(&mut self) -> Result<(), DbError>;
fn get_row_by_key(&mut self, table: &str, key: &Value) -> Result<Option<Row>, DbError>;
}
FileStorageThe main disk‑backed storage engine.
pub struct FileStorage { /* private */ }
| Method | Description |
|---|---|
open(path: &Path, cache_capacity: usize) |
Opens or creates the database and WAL, loads catalog, applies WAL, and runs a checkpoint. |
checkpoint() |
Writes catalog to pages, flushes all dirty pages, updates meta page, truncates WAL. |
sync() |
Syncs the WAL file. |
close(self) |
Calls checkpoint() and unlocks WAL. |
save_catalog(&mut self, catalog: &Catalog) |
Replaces in‑memory catalog and writes it to pages. |
get_catalog() |
Returns a reference to the catalog. |
get_table(name) |
Returns table metadata from catalog. |
get_row_by_key(table, key) |
Looks up a row using primary key B‑tree index. |
FileStorageimplements theStorageEnginetrait fully, including row operations.
InMemoryStorageA simple in‑memory implementation that only stores the catalog and does not support row operations.
pub struct InMemoryStorage { /* private */ }
| Method | Description |
|---|---|
new() |
Creates an empty in‑memory storage. |
save_catalog(&mut self, catalog) |
Replaces catalog. |
get_catalog() |
Returns reference to catalog. |
get_table(name) |
Returns table metadata. |
InMemoryStorageimplementsStorageEnginebut returnsUnsupportedfor row methods.
walWalpub struct Wal {
file: Option<File>,
}
| Method | Description |
|---|---|
open(path) |
Opens or creates the WAL file and locks it exclusively. |
append(payload) |
Appends a raw record. |
append_wal_record(lsn, record_type, data) |
Appends a structured WAL record. |
sync() |
Syncs the WAL file. |
read_all() |
Reads all raw records. |
read_wal_records() |
Reads and parses all structured records. |
truncate() |
Truncates the WAL file and syncs. |
unlock() |
Releases the file lock. |
tableTableA metadata container for a table; no longer holds rows directly.
pub struct Table {
schema: TableSchema,
read_only: bool,
data_page_id: Option<u32>,
index_root_page_id: Option<u32>,
next_row_id: u64,
}
| Method | Description |
|---|---|
new(schema) |
Creates a new table with default values. |
rename_schema(new_name) |
Renames the schema while preserving columns. |
schema() |
Returns schema reference. |
is_read_only() / set_read_only(bool) |
Read‑only flag. |
data_page_id() / set_data_page_id(u32) |
First data page ID. |
index_root_page_id() / set_index_root_page_id(u32) |
B‑tree root page ID. |
clear_index_root_page_id() |
Sets index root to None. |
next_row_id() / set_next_row_id(u64) |
Row counter. |
increment_next_row_id() |
Returns current value and increments it, with overflow check. |
table_storageTableStorageHandles row storage on data pages.
pub struct TableStorage {
first_data_page_id: u32,
}
| Method | Description |
|---|---|
new(buffer_pool) |
Allocates the first data page and initialises it. |
first_data_page_id() |
Returns the first page ID. |
insert_row_static(buffer_pool, first_page_id, row) |
Serializes and appends a row to the data page chain. |
get_row_static(buffer_pool, first_page_id, row_idx) |
Reads and deserializes a row by its logical index. |
insert_row(buffer_pool, row) |
Convenience wrapper. |
get_row(buffer_pool, row_idx) |
Convenience wrapper. |
clear_static(buffer_pool, first_page_id) |
Marks all pages in the chain as empty and resets headers. |
from_first_page_id(id) |
Creates a TableStorage from an existing first page ID. |
TableStoragealso implements theTableStoretrait frommonumentum_handler, but most methods returnUnsupportedbecause they require an external buffer pool.
All fallible functions return Result<T, DbError>, where DbError is defined in monumentum_handler. Common error variants include:
InvalidOperation – e.g., duplicate table, invalid page ID.ConstraintViolation – e.g., duplicate key in B‑tree.Corruption – e.g., checksum mismatch, invalid magic.TableNotFound – e.g., table does not exist.Unsupported – feature not yet implemented.DbError can be converted from std::io::Error for file‑related failures.
next_page_id field; each stores a chunk of the serialized catalog.[4‑byte row length][row bytes].Recovery on startup:
- Read meta page (LSN, catalog root).
- Load catalog from pages.
- Apply WAL records with LSN > last checkpoint LSN.
- Reload catalog from pages (after WAL applied).
- Run checkpoint to flush everything.
use monumentum_core::catalog::Catalog;
use monumentum_core::serde::{encode_catalog, decode_catalog};
use monumentum_handler::core::schema::column::{ColumnDef, DataType};
use monumentum_handler::core::schema::table_schema::TableSchema;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut catalog = Catalog::new();
let schema = TableSchema::try_new(
"users",
vec![
ColumnDef::new("id", DataType::Integer),
ColumnDef::new("name", DataType::Text),
],
)?;
catalog.create_table(schema)?;
let bytes = encode_catalog(&catalog)?;
let decoded = decode_catalog(&bytes)?;
assert_eq!(catalog, decoded);
Ok(())
}
use monumentum_core::store::storage::FileStorage;
use monumentum_handler::core::row::Row;
use monumentum_handler::core::value::Value;
use monumentum_handler::core::schema::column::{ColumnDef, DataType};
use monumentum_handler::core::schema::table_schema::TableSchema;
use monumentum_handler::traits::StorageEngine;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::path::Path::new("test.db");
// Create schema with primary key
let mut id_col = ColumnDef::new("id", DataType::Integer);
id_col.set_primary_key(true);
let schema = TableSchema::try_new("users", vec![id_col, ColumnDef::new("name", DataType::Text)])?;
// Open storage
let mut storage = FileStorage::open(path, 10)?;
// Create table
storage.create_table(schema)?;
// Insert row
let row = Row::new(vec![Value::from(1i64), Value::from("Alice")]);
storage.insert_row("users", &row)?;
// Lookup by primary key
let found = storage.get_row_by_key("users", &Value::from(1i64))?;
assert_eq!(found, Some(row));
// Checkpoint and close
storage.checkpoint()?;
storage.close()?;
Ok(())
}
use monumentum_core::buffer_pool::BufferPool;
use monumentum_core::pager::Pager;
use monumentum_core::index::btree::BTreeOnDisk;
use monumentum_core::index::key::IndexKey;
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = Path::new("btree.db");
let pager = Pager::open(path)?;
let mut pool = BufferPool::new(pager, 10)?;
let btree = BTreeOnDisk::create(&mut pool)?;
let mut root_id = btree.root_page_id();
// Insert keys
for i in 0..100_i64 {
BTreeOnDisk::insert_static(&mut pool, &mut root_id, IndexKey::Integer(i), i as u64)?;
}
// Lookup
let value = BTreeOnDisk::lookup_static(&mut pool, root_id, &IndexKey::Integer(50))?;
assert_eq!(value, Some(50));
// Range scan
let mut result = Vec::new();
BTreeOnDisk::range_scan_static(&mut pool, root_id, &IndexKey::Integer(10), &IndexKey::Integer(20), &mut result)?;
assert_eq!(result.len(), 10);
// Delete
let removed = BTreeOnDisk::delete_static(&mut pool, root_id, &IndexKey::Integer(50))?;
assert_eq!(removed, Some(50));
Ok(())
}
Run all workspace tests:
cargo test --workspace
Run only monumentum_core tests:
cargo test -p monumentum_core
Run with strict lints:
cargo clippy --workspace --all-targets --all-features -- -D warnings
The test suite covers:
MIT