use std::path::Path;
use std::sync::Arc;
use nodedb_types::TenantId;
use redb::{Database, TableDefinition};
use super::stats::GRAPH_STATS;
pub(super) type BaseKey = (String, String, String, String);
pub(super) type TenantBaseKey = (u64, String, String, String, String);
pub(super) const EDGES: TableDefinition<(u64, &str), &[u8]> = TableDefinition::new("edges");
pub(super) const REVERSE_EDGES: TableDefinition<(u64, &str), &[u8]> =
TableDefinition::new("reverse_edges");
pub(super) fn redb_err<E: std::fmt::Display>(ctx: &str, e: E) -> crate::Error {
crate::Error::Storage {
engine: "graph".into(),
detail: format!("{ctx}: {e}"),
}
}
pub use nodedb_types::graph::Direction;
pub type EdgeRecord = (TenantId, String, String, String, String, Vec<u8>);
#[derive(Debug, Clone)]
pub struct Edge {
pub collection: String,
pub src_id: String,
pub label: String,
pub dst_id: String,
pub properties: Vec<u8>,
}
pub struct EdgeStore {
pub(super) db: Arc<Database>,
}
impl EdgeStore {
pub fn open(path: &Path) -> crate::Result<Self> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let db = Database::create(path).map_err(|e| redb_err("open", e))?;
let write_txn = db.begin_write().map_err(|e| redb_err("begin_write", e))?;
{
let _ = write_txn
.open_table(EDGES)
.map_err(|e| redb_err("open edges", e))?;
let _ = write_txn
.open_table(REVERSE_EDGES)
.map_err(|e| redb_err("open reverse_edges", e))?;
let _ = write_txn
.open_table(GRAPH_STATS)
.map_err(|e| redb_err("open graph_stats", e))?;
}
write_txn.commit().map_err(|e| redb_err("commit", e))?;
Ok(Self { db: Arc::new(db) })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_creates_tables_in_new_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("graph.redb");
let _ = EdgeStore::open(&path).unwrap();
assert!(path.exists());
}
#[test]
fn open_is_idempotent() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("graph.redb");
let _a = EdgeStore::open(&path).unwrap();
drop(_a);
let _b = EdgeStore::open(&path).unwrap();
}
}