use serde::{Deserialize, Serialize};
use redb::TableDefinition;
pub const GRAPH_STATS: TableDefinition<(u64, u64, &str), &[u8]> =
TableDefinition::new("graph_stats");
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SummaryRow {
pub edge_count: u64,
pub distinct_node_count: u64,
pub distinct_label_count: u64,
#[serde(default)]
pub ownership_version: u8,
}
#[derive(zerompk::ToMessagePack, zerompk::FromMessagePack)]
#[msgpack(map)]
struct SummaryRowV1 {
edge_count: u64,
distinct_node_count: u64,
distinct_label_count: u64,
#[msgpack(default)]
ownership_version: u8,
}
#[derive(zerompk::ToMessagePack, zerompk::FromMessagePack)]
struct LegacySummaryRow {
edge_count: u64,
distinct_node_count: u64,
distinct_label_count: u64,
}
impl SummaryRow {
pub fn zero() -> Self {
Self {
edge_count: 0,
distinct_node_count: 0,
distinct_label_count: 0,
ownership_version: 1,
}
}
pub fn encode(&self) -> crate::Result<Vec<u8>> {
zerompk::to_msgpack_vec(&SummaryRowV1 {
edge_count: self.edge_count,
distinct_node_count: self.distinct_node_count,
distinct_label_count: self.distinct_label_count,
ownership_version: self.ownership_version,
})
.map_err(|e| crate::Error::Storage {
engine: "graph".into(),
detail: format!("encode SummaryRow: {e}"),
})
}
pub fn decode(bytes: &[u8]) -> crate::Result<Self> {
if let Ok(row) = zerompk::from_msgpack::<SummaryRowV1>(bytes) {
return Ok(Self {
edge_count: row.edge_count,
distinct_node_count: row.distinct_node_count,
distinct_label_count: row.distinct_label_count,
ownership_version: row.ownership_version,
});
}
zerompk::from_msgpack::<LegacySummaryRow>(bytes)
.map(|row| Self {
edge_count: row.edge_count,
distinct_node_count: row.distinct_node_count,
distinct_label_count: row.distinct_label_count,
ownership_version: 0,
})
.map_err(|e| crate::Error::Storage {
engine: "graph".into(),
detail: format!("decode SummaryRow: {e}"),
})
}
}
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
pub struct LabelRow {
pub count: u64,
}
impl LabelRow {
pub fn encode(&self) -> crate::Result<Vec<u8>> {
zerompk::to_msgpack_vec(self).map_err(|e| crate::Error::Storage {
engine: "graph".into(),
detail: format!("encode LabelRow: {e}"),
})
}
pub fn decode(bytes: &[u8]) -> crate::Result<Self> {
zerompk::from_msgpack(bytes).map_err(|e| crate::Error::Storage {
engine: "graph".into(),
detail: format!("decode LabelRow: {e}"),
})
}
}
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
pub struct NodeRow {
pub refcount: u32,
}
impl NodeRow {
pub fn encode(&self) -> crate::Result<Vec<u8>> {
zerompk::to_msgpack_vec(self).map_err(|e| crate::Error::Storage {
engine: "graph".into(),
detail: format!("encode NodeRow: {e}"),
})
}
pub fn decode(bytes: &[u8]) -> crate::Result<Self> {
zerompk::from_msgpack(bytes).map_err(|e| crate::Error::Storage {
engine: "graph".into(),
detail: format!("decode NodeRow: {e}"),
})
}
}
pub fn summary_key(collection: &str) -> String {
format!("{collection}\x00summary")
}
pub fn label_key(collection: &str, label: &str) -> String {
format!("{collection}\x00label\x00{label}")
}
pub fn node_key(collection: &str, node_id: &str) -> String {
format!("{collection}\x00node\x00{node_id}")
}
pub fn collection_stat_prefix(collection: &str) -> String {
format!("{collection}\x00")
}
pub fn label_prefix(collection: &str) -> String {
format!("{collection}\x00label\x00")
}
#[derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
#[msgpack(map)]
pub struct CollectionStats {
pub collection: String,
pub edge_count: u64,
pub distinct_node_count: u64,
pub distinct_label_count: u64,
pub labels: Vec<(String, u64)>,
#[serde(default)]
#[msgpack(default)]
pub logical_edges: Vec<(String, String, String)>,
#[serde(default)]
#[msgpack(default)]
pub exact_scan: bool,
}
impl CollectionStats {
pub fn zero(collection: String) -> Self {
Self {
collection,
edge_count: 0,
distinct_node_count: 0,
distinct_label_count: 0,
labels: Vec::new(),
logical_edges: Vec::new(),
exact_scan: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn summary_decode_marks_legacy_counters_as_unowned() {
let bytes = zerompk::to_msgpack_vec(&LegacySummaryRow {
edge_count: 2,
distinct_node_count: 4,
distinct_label_count: 1,
})
.expect("encode legacy summary");
let decoded = SummaryRow::decode(&bytes).expect("decode legacy summary");
assert_eq!(decoded.edge_count, 2);
assert_eq!(decoded.ownership_version, 0);
}
#[test]
fn ownership_aware_summary_round_trips_version() {
let summary = SummaryRow::zero();
let bytes = summary.encode().expect("encode current summary");
assert_eq!(SummaryRow::decode(&bytes).unwrap(), summary);
}
}