use std::mem::size_of;
use crate::GraphError;
use crate::csr::index::CsrIndex;
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum CsrWriteOp {
AddEdge {
src: String,
label: String,
dst: String,
collection: String,
weight: f64,
force_weights: bool,
},
PutEdge {
src: String,
label: String,
dst: String,
collection: String,
weight: f64,
},
RemoveEdge {
src: String,
label: String,
dst: String,
collection: String,
},
RemoveNodeEdges {
node: String,
},
AddNode {
name: String,
},
SetNodeSurrogate {
node: String,
surrogate: u32,
},
RestoreNodeSurrogate {
node: String,
prior: u32,
},
AddNodeLabel {
node: String,
label: String,
},
RemoveNodeLabel {
node: String,
label: String,
},
WithdrawNewestNode {
node: String,
},
WithdrawNewestNodeLabel {
label: String,
},
}
impl CsrWriteOp {
pub(crate) fn kind(&self) -> &'static str {
match self {
Self::AddEdge { .. } => "add_edge",
Self::PutEdge { .. } => "put_edge",
Self::RemoveEdge { .. } => "remove_edge",
Self::RemoveNodeEdges { .. } => "remove_node_edges",
Self::AddNode { .. } => "add_node",
Self::SetNodeSurrogate { .. } => "set_node_surrogate",
Self::RestoreNodeSurrogate { .. } => "restore_node_surrogate",
Self::AddNodeLabel { .. } => "add_node_label",
Self::RemoveNodeLabel { .. } => "remove_node_label",
Self::WithdrawNewestNode { .. } => "withdraw_newest_node",
Self::WithdrawNewestNodeLabel { .. } => "withdraw_newest_node_label",
}
}
fn byte_cost(&self) -> usize {
let strings = match self {
Self::AddEdge {
src,
label,
dst,
collection,
..
}
| Self::PutEdge {
src,
label,
dst,
collection,
..
}
| Self::RemoveEdge {
src,
label,
dst,
collection,
} => src.len() + label.len() + dst.len() + collection.len(),
Self::RemoveNodeEdges { node }
| Self::SetNodeSurrogate { node, .. }
| Self::RestoreNodeSurrogate { node, .. }
| Self::WithdrawNewestNode { node } => node.len(),
Self::AddNode { name } => name.len(),
Self::AddNodeLabel { node, label } | Self::RemoveNodeLabel { node, label } => {
node.len() + label.len()
}
Self::WithdrawNewestNodeLabel { label } => label.len(),
};
size_of::<(Self, OpOutcome)>() + strings
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OpOutcome {
Applied,
Ignored,
Failed,
}
impl OpOutcome {
pub(crate) fn of<T>(result: &Result<T, GraphError>) -> Self {
if result.is_ok() {
Self::Applied
} else {
Self::Failed
}
}
pub(crate) fn of_label(result: &Result<bool, GraphError>) -> Self {
match result {
Ok(true) => Self::Applied,
Ok(false) => Self::Ignored,
Err(_) => Self::Failed,
}
}
}
#[derive(Debug)]
pub struct CsrJournal {
pub(crate) token: u64,
cap_bytes: usize,
used_bytes: usize,
ops: Vec<(CsrWriteOp, OpOutcome)>,
overflowed: bool,
}
impl CsrJournal {
pub(crate) fn new(token: u64, cap_bytes: usize) -> Self {
Self {
token,
cap_bytes,
used_bytes: 0,
ops: Vec::new(),
overflowed: false,
}
}
fn record(&mut self, op: CsrWriteOp, outcome: OpOutcome) {
if self.overflowed {
return;
}
let cost = op.byte_cost();
if self.used_bytes.saturating_add(cost) > self.cap_bytes {
self.overflowed = true;
self.ops = Vec::new();
self.used_bytes = 0;
return;
}
self.used_bytes += cost;
self.ops.push((op, outcome));
}
pub(crate) fn into_ops(self) -> Result<Vec<(CsrWriteOp, OpOutcome)>, GraphError> {
if self.overflowed {
return Err(GraphError::RebuildJournalOverflow {
cap_bytes: self.cap_bytes,
});
}
Ok(self.ops)
}
}
impl CsrIndex {
pub(crate) fn journal_record(&mut self, op: impl FnOnce() -> CsrWriteOp, outcome: OpOutcome) {
if let Some(journal) = self.rebuild_journal.as_mut() {
journal.record(op(), outcome);
}
}
pub(crate) fn replay_op(&mut self, op: &CsrWriteOp) -> OpOutcome {
match op {
CsrWriteOp::AddEdge {
src,
label,
dst,
collection,
weight,
force_weights,
} => OpOutcome::of(&self.apply_add_edge(
src,
label,
dst,
collection,
*weight,
*force_weights,
)),
CsrWriteOp::PutEdge {
src,
label,
dst,
collection,
weight,
} => OpOutcome::of(&self.apply_put_edge(src, label, dst, collection, *weight)),
CsrWriteOp::RemoveEdge {
src,
label,
dst,
collection,
} => {
self.apply_remove_edge(src, label, dst, collection);
OpOutcome::Applied
}
CsrWriteOp::RemoveNodeEdges { node } => {
self.apply_remove_node_edges(node);
OpOutcome::Applied
}
CsrWriteOp::AddNode { name } => OpOutcome::of(&self.ensure_node(name)),
CsrWriteOp::SetNodeSurrogate { node, surrogate } => {
self.apply_set_node_surrogate(node, *surrogate);
OpOutcome::Applied
}
CsrWriteOp::RestoreNodeSurrogate { node, prior } => {
self.apply_restore_node_surrogate(node, *prior);
OpOutcome::Applied
}
CsrWriteOp::AddNodeLabel { node, label } => {
OpOutcome::of_label(&self.apply_add_node_label(node, label))
}
CsrWriteOp::RemoveNodeLabel { node, label } => {
self.apply_remove_node_label(node, label);
OpOutcome::Applied
}
CsrWriteOp::WithdrawNewestNode { node } => {
OpOutcome::of(&self.apply_withdraw_newest_node(node))
}
CsrWriteOp::WithdrawNewestNodeLabel { label } => {
OpOutcome::of(&self.apply_withdraw_newest_node_label(label))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn overflow_drops_entries_and_reports_the_bound() {
let op = CsrWriteOp::AddNode {
name: "n".to_string(),
};
let cap = op.byte_cost() * 2;
let mut journal = CsrJournal::new(1, cap);
journal.record(op.clone(), OpOutcome::Applied);
journal.record(op.clone(), OpOutcome::Applied);
assert_eq!(journal.ops.len(), 2);
journal.record(op, OpOutcome::Applied);
assert!(
journal.ops.is_empty(),
"an overflowed journal frees its entries"
);
assert!(matches!(
journal.into_ops(),
Err(GraphError::RebuildJournalOverflow { cap_bytes }) if cap_bytes == cap
));
}
}