use std::fmt;
use kmp_domain::PortError;
mod snapshot_revision_observer;
pub(crate) mod sqlite;
mod sqlite_snapshot;
mod sqlite_snapshot_read;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum Table {
Nodes,
Relations,
RelationsByTarget,
Details,
DetailHeaders,
Cards,
Anchors,
EventLog,
Aggregates,
Idempotency,
Processed,
Checkpoints,
Snapshots,
Migrations,
}
impl Table {
pub(crate) const fn key_shape(self) -> KeyShape {
match self {
Table::Nodes
| Table::Details
| Table::DetailHeaders
| Table::Anchors
| Table::Aggregates
| Table::Idempotency
| Table::Migrations => KeyShape::Str,
Table::Cards | Table::Processed | Table::Checkpoints | Table::Snapshots => {
KeyShape::Str2
}
Table::Relations | Table::RelationsByTarget => KeyShape::Str3,
Table::EventLog => KeyShape::U64,
}
}
}
impl fmt::Display for Table {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Table::Nodes => "nodes",
Table::Relations => "relations_by_source",
Table::RelationsByTarget => "relations_by_target",
Table::Details => "details",
Table::DetailHeaders => "detail_headers",
Table::Cards => "node_cards",
Table::Anchors => "memory_anchors",
Table::EventLog => "event_log",
Table::Aggregates => "aggregates",
Table::Idempotency => "idempotency",
Table::Processed => "processed_events",
Table::Checkpoints => "projection_checkpoints",
Table::Snapshots => "snapshots",
Table::Migrations => "store_migrations",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum KeyShape {
Str,
Str2,
Str3,
U64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Key<'a> {
Str(&'a str),
Str2(&'a str, &'a str),
Str3(&'a str, &'a str, &'a str),
U64(u64),
}
impl Key<'_> {
pub(crate) const fn shape(&self) -> KeyShape {
match self {
Key::Str(_) => KeyShape::Str,
Key::Str2(..) => KeyShape::Str2,
Key::Str3(..) => KeyShape::Str3,
Key::U64(_) => KeyShape::U64,
}
}
}
pub(crate) type StrRow = (String, Vec<u8>);
pub(crate) type Str3Row = ((String, String, String), Vec<u8>);
pub(crate) type U64Row = (u64, Vec<u8>);
pub(crate) trait ReadTx {
fn get(&self, table: Table, key: Key<'_>) -> Result<Option<Vec<u8>>, PortError>;
fn project_str_json(
&self,
table: Table,
key: &str,
fields: &[&str],
) -> Result<Option<Vec<u8>>, PortError> {
let Some(raw) = self.get(table, Key::Str(key))? else {
return Ok(None);
};
let value: serde_json::Value = serde_json::from_slice(&raw)
.map_err(|e| PortError::InvalidState(format!("invalid JSON record: {e}")))?;
let mut output = serde_json::Map::new();
for field in fields {
let mut selected = &value;
for component in field.split('.') {
selected = selected.get(component).unwrap_or(&serde_json::Value::Null);
}
output.insert((*field).to_string(), selected.clone());
}
serde_json::to_vec(&output)
.map(Some)
.map_err(|e| PortError::InvalidState(e.to_string()))
}
fn value_len(&self, table: Table, key: Key<'_>) -> Result<Option<u64>, PortError>;
fn scan_str(&self, table: Table) -> Result<Vec<StrRow>, PortError>;
fn scan_str3_by_first(&self, table: Table, first: &str) -> Result<Vec<Str3Row>, PortError>;
fn scan_str3_page(
&self,
table: Table,
first: &str,
after: Option<(&str, &str)>,
limit: u32,
relation_type: Option<&str>,
) -> Result<Vec<Str3Row>, PortError>;
fn scan_u64(&self, table: Table) -> Result<Vec<U64Row>, PortError>;
fn last_u64(&self, table: Table) -> Result<Option<U64Row>, PortError>;
fn count(&self, table: Table) -> Result<u64, PortError>;
}
pub(crate) trait WriteTx: ReadTx {
fn insert(&mut self, table: Table, key: Key<'_>, value: &[u8]) -> Result<(), PortError>;
fn remove(&mut self, table: Table, key: Key<'_>) -> Result<(), PortError>;
fn clear(&mut self, table: Table) -> Result<(), PortError>;
fn commit(self: Box<Self>) -> Result<(), PortError>;
}
pub(crate) trait Engine: fmt::Debug + Send + Sync {
fn graph_read_revision(&self) -> Option<kmp_domain::GraphReadRevision> {
None
}
fn read_snapshot(&self) -> Result<std::sync::Arc<dyn Engine>, PortError> {
Err(PortError::Unavailable(
"read snapshots are not supported by this engine".into(),
))
}
fn begin_read(&self) -> Result<Box<dyn ReadTx + '_>, PortError>;
fn begin_write(&self) -> Result<Box<dyn WriteTx + '_>, PortError>;
}
pub(crate) fn key_shape_mismatch(table: Table, key: KeyShape) -> PortError {
PortError::InvalidState(format!(
"embedded engine: table `{table}` is keyed by {:?}, called with a {key:?} key",
table.key_shape()
))
}
pub(crate) fn scan_shape_mismatch(table: Table, wanted: KeyShape) -> PortError {
PortError::InvalidState(format!(
"embedded engine: table `{table}` is keyed by {:?}, scanned as {wanted:?}",
table.key_shape()
))
}