use crate::datatypes::Value;
use crate::graph::schema::StringInterner;
use crate::graph::storage::recording::{CaptureOrigin, RawOp};
use crate::graph::storage::GraphRead;
use petgraph::graph::NodeIndex;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CdcEventKind {
Create,
Update,
Delete,
}
impl CdcEventKind {
pub fn as_str(&self) -> &'static str {
match self {
CdcEventKind::Create => "create",
CdcEventKind::Update => "update",
CdcEventKind::Delete => "delete",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct NodeState {
pub title: Value,
pub labels: Vec<String>,
pub properties: Vec<(String, Value)>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EdgeState {
pub properties: Vec<(String, Value)>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CdcChange {
Node {
node_type: String,
id: Value,
after: Option<NodeState>,
},
Edge {
conn_type: String,
src_type: String,
src_id: Value,
tgt_type: String,
tgt_id: Value,
after: Option<EdgeState>,
},
}
impl CdcChange {
pub fn element(&self) -> &'static str {
match self {
CdcChange::Node { .. } => "node",
CdcChange::Edge { .. } => "edge",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CdcEvent {
pub seq: u64,
pub kind: CdcEventKind,
pub change: CdcChange,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PendingEvent {
pub kind: CdcEventKind,
pub change: CdcChange,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum SlotKey {
Node(u32),
Edge(u32),
}
enum Entry {
Vacated,
Upsert {
key: SlotKey,
kind: CdcEventKind,
},
RemoveNode {
node_type: String,
id: Value,
},
RemoveEdge {
conn_type: String,
src_type: String,
src_id: Value,
tgt_type: String,
tgt_id: Value,
},
}
pub(super) fn events_from_raw(
raw: &[RawOp],
graph: &impl GraphRead,
interner: &StringInterner,
secondary_labels: impl Fn(NodeIndex) -> Vec<String>,
) -> Vec<PendingEvent> {
let mut entries: Vec<Entry> = Vec::with_capacity(raw.len());
let mut slots: HashMap<SlotKey, usize> = HashMap::new();
for op in raw {
match op {
RawOp::UpsertNode(idx, origin) => stage_upsert(
&mut entries,
&mut slots,
SlotKey::Node(idx.index() as u32),
*origin,
),
RawOp::SetNodeLabels(idx) => stage_upsert(
&mut entries,
&mut slots,
SlotKey::Node(idx.index() as u32),
CaptureOrigin::Update,
),
RawOp::UpsertEdge(eidx, origin) => stage_upsert(
&mut entries,
&mut slots,
SlotKey::Edge(eidx.index() as u32),
*origin,
),
RawOp::RemoveNode { node_type, id } => entries.push(Entry::RemoveNode {
node_type: interner.resolve(*node_type).to_string(),
id: id.clone(),
}),
RawOp::RemoveEdge {
conn_type,
src_type,
src_id,
tgt_type,
tgt_id,
} => entries.push(Entry::RemoveEdge {
conn_type: interner.resolve(*conn_type).to_string(),
src_type: interner.resolve(*src_type).to_string(),
src_id: src_id.clone(),
tgt_type: interner.resolve(*tgt_type).to_string(),
tgt_id: tgt_id.clone(),
}),
}
}
let mut out = Vec::with_capacity(entries.len());
for entry in entries {
let event = match entry {
Entry::Vacated => continue,
Entry::Upsert { key, kind } => {
match resolve_upsert(key, graph, interner, &secondary_labels) {
Some(change) => PendingEvent { kind, change },
None => continue,
}
}
Entry::RemoveNode { node_type, id } => PendingEvent {
kind: CdcEventKind::Delete,
change: CdcChange::Node {
node_type,
id,
after: None,
},
},
Entry::RemoveEdge {
conn_type,
src_type,
src_id,
tgt_type,
tgt_id,
} => PendingEvent {
kind: CdcEventKind::Delete,
change: CdcChange::Edge {
conn_type,
src_type,
src_id,
tgt_type,
tgt_id,
after: None,
},
},
};
out.push(event);
}
out
}
fn stage_upsert(
entries: &mut Vec<Entry>,
slots: &mut HashMap<SlotKey, usize>,
key: SlotKey,
origin: CaptureOrigin,
) {
let kind = match origin {
CaptureOrigin::Create => CdcEventKind::Create,
CaptureOrigin::Update => CdcEventKind::Update,
};
let kind = match slots.get(&key) {
Some(&at) => {
let previous = std::mem::replace(&mut entries[at], Entry::Vacated);
match previous {
Entry::Upsert {
kind: CdcEventKind::Create,
..
} => CdcEventKind::Create,
_ => kind,
}
}
None => kind,
};
slots.insert(key, entries.len());
entries.push(Entry::Upsert { key, kind });
}
fn resolve_upsert(
key: SlotKey,
graph: &impl GraphRead,
interner: &StringInterner,
secondary_labels: &impl Fn(NodeIndex) -> Vec<String>,
) -> Option<CdcChange> {
match key {
SlotKey::Node(raw) => {
let idx = NodeIndex::new(raw as usize);
let node = graph.node_view(idx)?;
Some(CdcChange::Node {
node_type: node.node_type_str(interner).to_string(),
id: node.id().into_owned(),
after: Some(NodeState {
title: node.title().into_owned(),
labels: secondary_labels(idx),
properties: node.properties_cloned(interner).into_iter().collect(),
}),
})
}
SlotKey::Edge(raw) => {
let eidx = petgraph::graph::EdgeIndex::new(raw as usize);
let (a, b) = graph.edge_endpoints(eidx)?;
let edge = graph.edge_weight(eidx)?;
let (src_type, src_id) = logical_node(graph, a, interner)?;
let (tgt_type, tgt_id) = logical_node(graph, b, interner)?;
Some(CdcChange::Edge {
conn_type: edge.connection_type_str(interner).to_string(),
src_type,
src_id,
tgt_type,
tgt_id,
after: Some(EdgeState {
properties: edge.properties_cloned(interner).into_iter().collect(),
}),
})
}
}
}
fn logical_node(
graph: &impl GraphRead,
idx: NodeIndex,
interner: &StringInterner,
) -> Option<(String, Value)> {
let node = graph.node_view(idx)?;
Some((
node.node_type_str(interner).to_string(),
node.id().into_owned(),
))
}