use crate::datatypes::Value;
use crate::graph::schema::StringInterner;
use crate::graph::storage::recording::{BeforeImage, 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,
before: Option<NodeState>,
after: Option<NodeState>,
},
Edge {
conn_type: String,
src_type: String,
src_id: Value,
tgt_type: String,
tgt_id: Value,
before: Option<EdgeState>,
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,
before: Option<Box<BeforeImage>>,
},
RemoveNode {
node_type: String,
id: Value,
before: Option<Box<BeforeImage>>,
},
RemoveEdge {
conn_type: String,
src_type: String,
src_id: Value,
tgt_type: String,
tgt_id: Value,
before: Option<Box<BeforeImage>>,
},
}
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, before) => stage_upsert(
&mut entries,
&mut slots,
SlotKey::Node(idx.index() as u32),
*origin,
before.clone(),
),
RawOp::SetNodeLabels(idx, before) => stage_upsert(
&mut entries,
&mut slots,
SlotKey::Node(idx.index() as u32),
CaptureOrigin::Update,
before.clone(),
),
RawOp::UpsertEdge(eidx, origin, before) => stage_upsert(
&mut entries,
&mut slots,
SlotKey::Edge(eidx.index() as u32),
*origin,
before.clone(),
),
RawOp::RemoveNode {
node_type,
id,
before,
} => entries.push(Entry::RemoveNode {
node_type: interner.resolve(*node_type).to_string(),
id: id.clone(),
before: before.clone(),
}),
RawOp::RemoveEdge {
conn_type,
src_type,
src_id,
tgt_type,
tgt_id,
before,
} => 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(),
before: before.clone(),
}),
}
}
let mut out = Vec::with_capacity(entries.len());
for entry in entries {
let event = match entry {
Entry::Vacated => continue,
Entry::Upsert { key, kind, before } => {
let before = match kind {
CdcEventKind::Create => None,
_ => before,
};
match resolve_upsert(key, graph, interner, &secondary_labels, before) {
Some(change) => PendingEvent { kind, change },
None => continue,
}
}
Entry::RemoveNode {
node_type,
id,
before,
} => PendingEvent {
kind: CdcEventKind::Delete,
change: CdcChange::Node {
node_type,
id,
before: before.map(|image| node_state_from(&image, interner, Vec::new)),
after: None,
},
},
Entry::RemoveEdge {
conn_type,
src_type,
src_id,
tgt_type,
tgt_id,
before,
} => PendingEvent {
kind: CdcEventKind::Delete,
change: CdcChange::Edge {
conn_type,
src_type,
src_id,
tgt_type,
tgt_id,
before: before.map(|image| edge_state_from(&image, interner)),
after: None,
},
},
};
out.push(event);
}
out
}
fn stage_upsert(
entries: &mut Vec<Entry>,
slots: &mut HashMap<SlotKey, usize>,
key: SlotKey,
origin: CaptureOrigin,
before: Option<Box<BeforeImage>>,
) {
let kind = match origin {
CaptureOrigin::Create => CdcEventKind::Create,
CaptureOrigin::Update => CdcEventKind::Update,
};
let (kind, before) = match slots.get(&key) {
Some(&at) => {
let previous = std::mem::replace(&mut entries[at], Entry::Vacated);
match previous {
Entry::Upsert {
kind: CdcEventKind::Create,
before: earlier,
..
} => (CdcEventKind::Create, earlier.or(before)),
Entry::Upsert {
before: earlier, ..
} => (kind, earlier.or(before)),
_ => (kind, before),
}
}
None => (kind, before),
};
slots.insert(key, entries.len());
entries.push(Entry::Upsert { key, kind, before });
}
fn resolve_upsert(
key: SlotKey,
graph: &impl GraphRead,
interner: &StringInterner,
secondary_labels: &impl Fn(NodeIndex) -> Vec<String>,
before: Option<Box<BeforeImage>>,
) -> 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(),
before: before
.map(|image| node_state_from(&image, interner, || secondary_labels(idx))),
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,
before: before.map(|image| edge_state_from(&image, interner)),
after: Some(EdgeState {
properties: edge.properties_cloned(interner).into_iter().collect(),
}),
})
}
}
}
fn node_state_from(
image: &BeforeImage,
interner: &StringInterner,
final_labels: impl FnOnce() -> Vec<String>,
) -> NodeState {
NodeState {
title: image.title.clone(),
labels: image.labels.clone().unwrap_or_else(final_labels),
properties: resolve_properties(&image.properties, interner),
}
}
fn edge_state_from(image: &BeforeImage, interner: &StringInterner) -> EdgeState {
EdgeState {
properties: resolve_properties(&image.properties, interner),
}
}
fn resolve_properties(
properties: &[(crate::graph::schema::InternedKey, Value)],
interner: &StringInterner,
) -> Vec<(String, Value)> {
properties
.iter()
.filter_map(|(key, value)| {
interner
.try_resolve(*key)
.map(|name| (name.to_string(), value.clone()))
})
.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(),
))
}