2026-04-13 19:10:08 -04:00
|
|
|
// redb index tables
|
|
|
|
|
//
|
|
|
|
|
// capnp logs are source of truth; redb provides indexed access.
|
|
|
|
|
// Tables:
|
|
|
|
|
// nodes: key → offset in capnp log (u64)
|
|
|
|
|
// uuid_to_key: [u8;16] → key
|
|
|
|
|
//
|
|
|
|
|
// To read a node: lookup offset in redb, seek in capnp file, deserialize.
|
|
|
|
|
|
|
|
|
|
use anyhow::{Context, Result};
|
2026-04-13 20:12:54 -04:00
|
|
|
use redb::{Database, ReadableDatabase, ReadableTable, TableDefinition};
|
2026-04-13 19:10:08 -04:00
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
|
|
// Table definitions - nodes maps key to byte offset in capnp log
|
|
|
|
|
pub const NODES: TableDefinition<&str, u64> = TableDefinition::new("nodes");
|
|
|
|
|
pub const UUID_TO_KEY: TableDefinition<&[u8], &str> = TableDefinition::new("uuid_to_key");
|
|
|
|
|
|
|
|
|
|
/// Open or create the redb database, ensuring all tables exist.
|
|
|
|
|
pub fn open_db(path: &Path) -> Result<Database> {
|
|
|
|
|
let db = Database::create(path)
|
|
|
|
|
.with_context(|| format!("create redb {}", path.display()))?;
|
|
|
|
|
|
|
|
|
|
// Ensure tables exist by opening a write transaction
|
|
|
|
|
let txn = db.begin_write()?;
|
|
|
|
|
{
|
|
|
|
|
let _ = txn.open_table(NODES)?;
|
|
|
|
|
let _ = txn.open_table(UUID_TO_KEY)?;
|
|
|
|
|
}
|
|
|
|
|
txn.commit()?;
|
|
|
|
|
|
|
|
|
|
Ok(db)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Record a node's location in the index.
|
|
|
|
|
pub fn index_node(db: &Database, key: &str, offset: u64, uuid: &[u8; 16]) -> Result<()> {
|
|
|
|
|
let txn = db.begin_write()?;
|
|
|
|
|
{
|
|
|
|
|
let mut nodes_table = txn.open_table(NODES)?;
|
|
|
|
|
let mut uuid_table = txn.open_table(UUID_TO_KEY)?;
|
|
|
|
|
|
|
|
|
|
nodes_table.insert(key, offset)?;
|
|
|
|
|
uuid_table.insert(uuid.as_slice(), key)?;
|
|
|
|
|
}
|
|
|
|
|
txn.commit()?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get offset for a node by key.
|
|
|
|
|
pub fn get_offset(db: &Database, key: &str) -> Result<Option<u64>> {
|
|
|
|
|
let txn = db.begin_read()?;
|
|
|
|
|
let table = txn.open_table(NODES)?;
|
|
|
|
|
Ok(table.get(key)?.map(|v| v.value()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a key exists in the index.
|
|
|
|
|
pub fn contains_key(db: &Database, key: &str) -> Result<bool> {
|
|
|
|
|
let txn = db.begin_read()?;
|
|
|
|
|
let table = txn.open_table(NODES)?;
|
|
|
|
|
Ok(table.get(key)?.is_some())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Remove a node from the index.
|
|
|
|
|
pub fn remove_node(db: &Database, key: &str, uuid: &[u8; 16]) -> Result<()> {
|
|
|
|
|
let txn = db.begin_write()?;
|
|
|
|
|
{
|
|
|
|
|
let mut nodes_table = txn.open_table(NODES)?;
|
|
|
|
|
let mut uuid_table = txn.open_table(UUID_TO_KEY)?;
|
|
|
|
|
|
|
|
|
|
nodes_table.remove(key)?;
|
|
|
|
|
uuid_table.remove(uuid.as_slice())?;
|
|
|
|
|
}
|
|
|
|
|
txn.commit()?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Collect all keys from the index.
|
|
|
|
|
pub fn all_keys(db: &Database) -> Result<Vec<String>> {
|
|
|
|
|
let txn = db.begin_read()?;
|
|
|
|
|
let table = txn.open_table(NODES)?;
|
|
|
|
|
let mut keys = Vec::new();
|
|
|
|
|
for entry in table.iter()? {
|
|
|
|
|
let (key, _) = entry?;
|
|
|
|
|
keys.push(key.value().to_string());
|
|
|
|
|
}
|
|
|
|
|
Ok(keys)
|
|
|
|
|
}
|