use compact_str::CompactString;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::graph::definition::{Definition, DefinitionKind};
use crate::graph::schema::{ElementKind, GraphSchema};
use crate::value::{ElementProperty, PropertyMap};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct ElementRef {
pub kind: ElementKind,
pub id: String,
}
impl ElementRef {
pub fn vertex(id: impl Into<String>) -> Self {
Self {
kind: ElementKind::Vertex,
id: id.into(),
}
}
pub fn edge(id: impl Into<String>) -> Self {
Self {
kind: ElementKind::Edge,
id: id.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct VertexState {
pub labels: Vec<String>,
#[cfg_attr(feature = "serde", serde(with = "tagged_element"))]
pub properties: PropertyMap,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct EdgeState {
pub label: Option<String>,
pub out_id: String,
pub in_id: String,
#[cfg_attr(feature = "serde", serde(with = "tagged_element"))]
pub properties: PropertyMap,
}
impl VertexState {
pub fn new(
labels: impl IntoIterator<Item = impl Into<String>>,
properties: PropertyMap,
) -> Self {
Self {
labels: labels.into_iter().map(Into::into).collect(),
properties,
}
}
}
impl EdgeState {
pub fn new(
label: Option<&str>,
out_id: impl Into<String>,
in_id: impl Into<String>,
properties: PropertyMap,
) -> Self {
Self {
label: label.map(str::to_string),
out_id: out_id.into(),
in_id: in_id.into(),
properties,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(tag = "op", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum Mutation {
#[non_exhaustive]
AddVertex {
id: String,
labels: Vec<String>,
#[cfg_attr(feature = "serde", serde(with = "tagged_element"))]
properties: PropertyMap,
},
#[non_exhaustive]
DropVertex {
id: String,
before: VertexState,
},
#[non_exhaustive]
AddEdge {
id: String,
label: Option<String>,
out_id: String,
in_id: String,
#[cfg_attr(feature = "serde", serde(with = "tagged_element"))]
properties: PropertyMap,
},
#[non_exhaustive]
DropEdge {
id: String,
before: EdgeState,
},
#[non_exhaustive]
SetProperty {
element: ElementRef,
key: String,
#[cfg_attr(
feature = "serde",
serde(
default,
skip_serializing_if = "Option::is_none",
serialize_with = "tagged_option::serialize",
deserialize_with = "tagged_option::deserialize"
)
)]
before: Option<ElementProperty>,
#[cfg_attr(feature = "serde", serde(with = "tagged_value"))]
after: ElementProperty,
},
#[non_exhaustive]
RemoveProperty {
element: ElementRef,
key: String,
#[cfg_attr(feature = "serde", serde(with = "tagged_value"))]
before: ElementProperty,
},
#[non_exhaustive]
AddLabel {
id: String,
label: String,
},
#[non_exhaustive]
RemoveLabel {
id: String,
label: String,
},
#[non_exhaustive]
SetEdgeLabel {
id: String,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
before: Option<String>,
after: String,
},
#[non_exhaustive]
SetSchema {
before: Box<GraphSchema>,
after: Box<GraphSchema>,
},
#[non_exhaustive]
SetDefinition {
kind: DefinitionKind,
name: String,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
before: Option<Box<Definition>>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
after: Option<Box<Definition>>,
},
}
impl Mutation {
pub fn add_vertex(
id: impl Into<String>,
labels: impl IntoIterator<Item = impl Into<String>>,
properties: PropertyMap,
) -> Self {
Mutation::AddVertex {
id: id.into(),
labels: labels.into_iter().map(Into::into).collect(),
properties,
}
}
pub fn drop_vertex(id: impl Into<String>, before: VertexState) -> Self {
Mutation::DropVertex {
id: id.into(),
before,
}
}
pub fn add_edge(
id: impl Into<String>,
label: Option<&str>,
out_id: impl Into<String>,
in_id: impl Into<String>,
properties: PropertyMap,
) -> Self {
Mutation::AddEdge {
id: id.into(),
label: label.map(str::to_string),
out_id: out_id.into(),
in_id: in_id.into(),
properties,
}
}
pub fn drop_edge(id: impl Into<String>, before: EdgeState) -> Self {
Mutation::DropEdge {
id: id.into(),
before,
}
}
pub fn set_property(
element: ElementRef,
key: impl Into<String>,
before: Option<ElementProperty>,
after: ElementProperty,
) -> Self {
Mutation::SetProperty {
element,
key: key.into(),
before,
after,
}
}
pub fn remove_property(
element: ElementRef,
key: impl Into<String>,
before: ElementProperty,
) -> Self {
Mutation::RemoveProperty {
element,
key: key.into(),
before,
}
}
pub fn add_label(id: impl Into<String>, label: impl Into<String>) -> Self {
Mutation::AddLabel {
id: id.into(),
label: label.into(),
}
}
pub fn remove_label(id: impl Into<String>, label: impl Into<String>) -> Self {
Mutation::RemoveLabel {
id: id.into(),
label: label.into(),
}
}
pub fn set_edge_label(
id: impl Into<String>,
before: Option<&str>,
after: impl Into<String>,
) -> Self {
Mutation::SetEdgeLabel {
id: id.into(),
before: before.map(str::to_string),
after: after.into(),
}
}
pub fn set_schema(before: GraphSchema, after: GraphSchema) -> Self {
Mutation::SetSchema {
before: Box::new(before),
after: Box::new(after),
}
}
pub fn set_definition(
kind: DefinitionKind,
name: impl Into<String>,
before: Option<Definition>,
after: Option<Definition>,
) -> Self {
Mutation::SetDefinition {
kind,
name: name.into(),
before: before.map(Box::new),
after: after.map(Box::new),
}
}
fn invalid_reason(&self) -> Option<String> {
match self {
Mutation::SetDefinition {
kind,
name,
before,
after,
} => {
if before.is_none() && after.is_none() {
return Some(format!(
"the definition change of {kind} '{name}' has neither a before nor an \
after image"
));
}
let mismatch = [before, after]
.into_iter()
.flatten()
.find(|image| image.kind() != *kind || image.name() != name.as_str());
mismatch.map(|image| {
format!(
"the definition change of {kind} '{name}' carries an image of {} '{}'",
image.kind(),
image.name()
)
})
}
Mutation::AddVertex { .. }
| Mutation::DropVertex { .. }
| Mutation::AddEdge { .. }
| Mutation::DropEdge { .. }
| Mutation::SetProperty { .. }
| Mutation::RemoveProperty { .. }
| Mutation::AddLabel { .. }
| Mutation::RemoveLabel { .. }
| Mutation::SetEdgeLabel { .. }
| Mutation::SetSchema { .. } => None,
}
}
pub(crate) fn make_logical(&mut self) {
fn element(properties: &mut PropertyMap) {
properties
.values_mut()
.for_each(ElementProperty::make_logical);
}
match self {
Mutation::AddVertex { properties, .. } | Mutation::AddEdge { properties, .. } => {
element(properties)
}
Mutation::DropVertex { before, .. } => element(&mut before.properties),
Mutation::DropEdge { before, .. } => element(&mut before.properties),
Mutation::SetProperty { before, after, .. } => {
if let Some(before) = before {
before.make_logical();
}
after.make_logical();
}
Mutation::RemoveProperty { before, .. } => before.make_logical(),
Mutation::AddLabel { .. }
| Mutation::RemoveLabel { .. }
| Mutation::SetEdgeLabel { .. }
| Mutation::SetSchema { .. }
| Mutation::SetDefinition { .. } => {}
}
}
}
#[cfg(feature = "serde")]
mod tagged_value {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::value::ElementProperty;
use crate::value::physical::{Tagged, TaggedOwned};
pub(super) fn serialize<S: Serializer>(
value: &ElementProperty,
serializer: S,
) -> Result<S::Ok, S::Error> {
Tagged(value).serialize(serializer)
}
pub(super) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<ElementProperty, D::Error> {
TaggedOwned::deserialize(deserializer).map(|TaggedOwned(value)| value)
}
}
#[cfg(feature = "serde")]
mod tagged_option {
use serde::{Deserializer, Serializer};
use crate::value::ElementProperty;
pub(super) fn serialize<S: Serializer>(
value: &Option<ElementProperty>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match value {
Some(value) => super::tagged_value::serialize(value, serializer),
None => serializer.serialize_none(),
}
}
pub(super) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<ElementProperty>, D::Error> {
super::tagged_value::deserialize(deserializer).map(Some)
}
}
#[cfg(feature = "serde")]
mod tagged_element {
use serde::de::{MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserializer, Serializer};
use crate::value::PropertyMap;
use crate::value::physical::{Tagged, TaggedOwned};
pub(super) fn serialize<S: Serializer>(
element: &PropertyMap,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(element.len()))?;
for (key, value) in element {
map.serialize_entry(key.as_str(), &Tagged(value))?;
}
map.end()
}
struct ElementVisitor;
impl<'de> Visitor<'de> for ElementVisitor {
type Value = PropertyMap;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a property map")
}
fn visit_map<A: MapAccess<'de>>(self, mut access: A) -> Result<PropertyMap, A::Error> {
let mut element = PropertyMap::with_capacity(access.size_hint().unwrap_or(0));
while let Some((key, TaggedOwned(value))) =
access.next_entry::<String, TaggedOwned>()?
{
element.insert(key, value);
}
Ok(element)
}
}
pub(super) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<PropertyMap, D::Error> {
deserializer.deserialize_map(ElementVisitor)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct CommitMetadata {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
principal: Option<String>,
#[cfg_attr(
feature = "serde",
serde(
default,
skip_serializing_if = "PropertyMap::is_empty",
with = "tagged_element"
)
)]
attributes: PropertyMap,
}
impl CommitMetadata {
pub fn new() -> Self {
Self::default()
}
pub fn with_principal(mut self, principal: impl Into<String>) -> Self {
self.principal = Some(principal.into());
self
}
pub fn with_attribute(mut self, key: &str, value: impl Into<ElementProperty>) -> Self {
self.attributes
.insert(CompactString::from(key), value.into());
self
}
pub fn principal(&self) -> Option<&str> {
self.principal.as_deref()
}
pub fn attributes(&self) -> &PropertyMap {
&self.attributes
}
pub(crate) fn merge_missing(&mut self, other: CommitMetadata) {
if self.principal.is_none() {
self.principal = other.principal;
}
for (key, value) in other.attributes {
self.attributes.0.entry(key).or_insert(value);
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct ChangeSet {
commit_seq: u64,
#[cfg_attr(feature = "serde", serde(default))]
committed_at: i64,
#[cfg_attr(feature = "serde", serde(default))]
node_sequence: u64,
#[cfg_attr(feature = "serde", serde(default))]
edge_sequence: u64,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "is_default_metadata")
)]
metadata: CommitMetadata,
mutations: Vec<Mutation>,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CommitStamp {
pub(crate) commit_seq: u64,
pub(crate) committed_at: i64,
pub(crate) node_sequence: u64,
pub(crate) edge_sequence: u64,
}
#[derive(Debug, Clone)]
pub struct ChangeSetBuilder {
stamp: CommitStamp,
metadata: CommitMetadata,
mutations: Vec<Mutation>,
}
impl ChangeSetBuilder {
pub fn with_sequences(mut self, node_sequence: u64, edge_sequence: u64) -> Self {
self.stamp.node_sequence = node_sequence;
self.stamp.edge_sequence = edge_sequence;
self
}
pub fn with_metadata(mut self, metadata: CommitMetadata) -> Self {
self.metadata = metadata;
self
}
pub fn push(&mut self, mutation: Mutation) -> &mut Self {
self.mutations.push(mutation);
self
}
pub fn build(mut self) -> Result<ChangeSet, crate::error::GraphError> {
let invalid = |reason: String| crate::error::GraphError::InvalidChangeSet { reason };
if self.stamp.committed_at < 0 {
return Err(invalid(format!(
"the commit time {} is before the Unix epoch",
self.stamp.committed_at
)));
}
if self.stamp.commit_seq == 0 && self.stamp.committed_at != 0 {
return Err(invalid(
"commit_seq 0 marks a change set that was never committed (a dry run); it has \
no commit time"
.to_string(),
));
}
for (position, mutation) in self.mutations.iter_mut().enumerate() {
if let Some(reason) = mutation.invalid_reason() {
return Err(invalid(format!("mutation {position}: {reason}")));
}
mutation.make_logical();
}
Ok(ChangeSet::new(self.stamp, self.metadata, self.mutations))
}
}
#[cfg(feature = "serde")]
fn is_default_metadata(metadata: &CommitMetadata) -> bool {
metadata.principal.is_none() && metadata.attributes.is_empty()
}
impl ChangeSet {
pub fn builder(commit_seq: u64, committed_at: i64) -> ChangeSetBuilder {
ChangeSetBuilder {
stamp: CommitStamp {
commit_seq,
committed_at,
node_sequence: 0,
edge_sequence: 0,
},
metadata: CommitMetadata::default(),
mutations: Vec::new(),
}
}
pub(crate) fn new(
stamp: CommitStamp,
metadata: CommitMetadata,
mutations: Vec<Mutation>,
) -> Self {
Self {
commit_seq: stamp.commit_seq,
committed_at: stamp.committed_at,
node_sequence: stamp.node_sequence,
edge_sequence: stamp.edge_sequence,
metadata,
mutations,
}
}
pub fn commit_seq(&self) -> u64 {
self.commit_seq
}
pub fn committed_at(&self) -> i64 {
self.committed_at
}
pub fn node_sequence(&self) -> u64 {
self.node_sequence
}
pub fn edge_sequence(&self) -> u64 {
self.edge_sequence
}
pub fn metadata(&self) -> &CommitMetadata {
&self.metadata
}
pub fn mutations(&self) -> &[Mutation] {
&self.mutations
}
pub fn into_mutations(self) -> Vec<Mutation> {
self.mutations
}
pub fn len(&self) -> usize {
self.mutations.len()
}
pub fn is_empty(&self) -> bool {
self.mutations.is_empty()
}
pub fn compact(&self) -> ChangeSet {
ChangeSet {
commit_seq: self.commit_seq,
committed_at: self.committed_at,
node_sequence: self.node_sequence,
edge_sequence: self.edge_sequence,
metadata: self.metadata.clone(),
mutations: compact_mutations(&self.mutations),
}
}
}
fn compact_mutations(mutations: &[Mutation]) -> Vec<Mutation> {
let mut out = Vec::new();
let mut segment = Compactor::default();
let mut schema: Option<(Box<GraphSchema>, Box<GraphSchema>)> = None;
type DefinitionImage = Option<Box<Definition>>;
let mut definitions: indexmap::IndexMap<
(DefinitionKind, String),
(DefinitionImage, DefinitionImage),
> = indexmap::IndexMap::new();
for mutation in mutations {
if let Mutation::SetDefinition {
kind,
name,
before,
after,
} = mutation
{
definitions
.entry((*kind, name.clone()))
.and_modify(|net| net.1 = after.clone())
.or_insert_with(|| (before.clone(), after.clone()));
continue;
}
if let Mutation::SetSchema { before, after } = mutation {
if schema.is_none() {
segment.finish(&mut out);
segment = Compactor::default();
}
schema = Some(match schema.take() {
Some((first, _)) => (first, after.clone()),
None => (before.clone(), after.clone()),
});
continue;
}
if let Some((before, after)) = schema.take()
&& before != after
{
out.push(Mutation::SetSchema { before, after });
}
segment.apply(mutation);
}
if let Some((before, after)) = schema.take()
&& before != after
{
out.push(Mutation::SetSchema { before, after });
}
segment.finish(&mut out);
for ((kind, name), (before, after)) in definitions {
if before != after {
out.push(Mutation::SetDefinition {
kind,
name,
before,
after,
});
}
}
out
}
#[derive(Debug)]
struct Net<S> {
existed: bool,
first_props: Vec<(String, Option<ElementProperty>)>,
last_props: Vec<(String, Option<ElementProperty>)>,
first_labels: Vec<(String, bool)>,
last_labels: Vec<(String, bool)>,
first_edge_label: Option<Option<String>>,
last_edge_label: Option<Option<String>>,
dropped: Option<S>,
current: Current<S>,
}
#[derive(Debug)]
enum Current<S> {
Original,
Gone,
New(S),
}
impl<S> Net<S> {
fn new(existed: bool) -> Self {
Self {
existed,
first_props: Vec::new(),
last_props: Vec::new(),
first_labels: Vec::new(),
last_labels: Vec::new(),
first_edge_label: None,
last_edge_label: None,
dropped: None,
current: if existed {
Current::Original
} else {
Current::Gone
},
}
}
fn changed_props(
&self,
) -> impl Iterator<Item = (&str, &Option<ElementProperty>, &Option<ElementProperty>)> {
self.last_props.iter().filter_map(|(key, last)| {
let first = lookup(&self.first_props, key)?;
(first != last).then_some((key.as_str(), first, last))
})
}
fn changed_edge_label(&self) -> Option<(&Option<String>, &str)> {
match (&self.first_edge_label, &self.last_edge_label) {
(Some(first), Some(Some(last))) if first.as_deref() != Some(last.as_str()) => {
Some((first, last.as_str()))
}
_ => None,
}
}
fn changed_labels(&self) -> impl Iterator<Item = (&str, bool)> {
self.last_labels.iter().filter_map(|(label, last)| {
let first = lookup(&self.first_labels, label)?;
(first != last).then_some((label.as_str(), *last))
})
}
}
fn lookup<'a, V>(entries: &'a [(String, V)], key: &str) -> Option<&'a V> {
entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
fn upsert<V>(entries: &mut Vec<(String, V)>, key: &str, value: V) {
match entries.iter_mut().find(|(k, _)| k == key) {
Some((_, slot)) => *slot = value,
None => entries.push((key.to_string(), value)),
}
}
fn insert_first<V>(entries: &mut Vec<(String, V)>, key: &str, value: V) {
if !entries.iter().any(|(k, _)| k == key) {
entries.push((key.to_string(), value));
}
}
fn set_in(properties: &mut PropertyMap, key: &str, value: Option<ElementProperty>) {
match value {
Some(value) => {
properties.insert(CompactString::from(key), value);
}
None => {
properties.shift_remove(key);
}
}
}
#[derive(Debug, Default)]
struct Compactor {
vertices: indexmap::IndexMap<String, Net<VertexState>>,
edges: indexmap::IndexMap<String, Net<EdgeState>>,
}
impl Compactor {
fn vertex(&mut self, id: &str, existed: bool) -> &mut Net<VertexState> {
self.vertices
.entry(id.to_string())
.or_insert_with(|| Net::new(existed))
}
fn edge(&mut self, id: &str, existed: bool) -> &mut Net<EdgeState> {
self.edges
.entry(id.to_string())
.or_insert_with(|| Net::new(existed))
}
fn apply(&mut self, mutation: &Mutation) {
match mutation {
Mutation::AddVertex {
id,
labels,
properties,
} => {
self.vertex(id, false).current = Current::New(VertexState {
labels: labels.clone(),
properties: properties.clone(),
});
}
Mutation::DropVertex { id, before } => {
let net = self.vertex(id, true);
if matches!(net.current, Current::Original) {
net.dropped = Some(before.clone());
}
net.current = Current::Gone;
}
Mutation::AddEdge {
id,
label,
out_id,
in_id,
properties,
} => {
self.edge(id, false).current = Current::New(EdgeState {
label: label.clone(),
out_id: out_id.clone(),
in_id: in_id.clone(),
properties: properties.clone(),
});
}
Mutation::DropEdge { id, before } => {
let net = self.edge(id, true);
if matches!(net.current, Current::Original) {
net.dropped = Some(before.clone());
}
net.current = Current::Gone;
}
Mutation::SetProperty {
element,
key,
before,
after,
} => self.property(element, key, before.clone(), Some(after.clone())),
Mutation::RemoveProperty {
element,
key,
before,
} => self.property(element, key, Some(before.clone()), None),
Mutation::AddLabel { id, label } => self.label(id, label, true),
Mutation::RemoveLabel { id, label } => self.label(id, label, false),
Mutation::SetEdgeLabel { id, before, after } => {
let net = self.edge(id, true);
match &mut net.current {
Current::Original => {
if net.first_edge_label.is_none() {
net.first_edge_label = Some(before.clone());
}
net.last_edge_label = Some(Some(after.clone()));
}
Current::New(state) => state.label = Some(after.clone()),
Current::Gone => {}
}
}
Mutation::SetSchema { .. } | Mutation::SetDefinition { .. } => {}
}
}
fn property(
&mut self,
element: &ElementRef,
key: &str,
before: Option<ElementProperty>,
after: Option<ElementProperty>,
) {
fn track<S>(
net: &mut Net<S>,
key: &str,
before: Option<ElementProperty>,
after: Option<ElementProperty>,
properties: impl FnOnce(&mut S) -> &mut PropertyMap,
) {
match &mut net.current {
Current::Original => {
insert_first(&mut net.first_props, key, before);
upsert(&mut net.last_props, key, after);
}
Current::New(state) => set_in(properties(state), key, after),
Current::Gone => {}
}
}
match element.kind {
ElementKind::Vertex => track(self.vertex(&element.id, true), key, before, after, |s| {
&mut s.properties
}),
ElementKind::Edge => track(self.edge(&element.id, true), key, before, after, |s| {
&mut s.properties
}),
}
}
fn label(&mut self, id: &str, label: &str, added: bool) {
let net = self.vertex(id, true);
match &mut net.current {
Current::Original => {
insert_first(&mut net.first_labels, label, !added);
upsert(&mut net.last_labels, label, added);
}
Current::New(state) => {
if added {
if !state.labels.iter().any(|l| l == label) {
state.labels.push(label.to_string());
}
} else {
state.labels.retain(|l| l != label);
}
}
Current::Gone => {}
}
}
fn finish(self, out: &mut Vec<Mutation>) {
for (id, net) in &self.edges {
if net.existed && !matches!(net.current, Current::Original) {
out.push(Mutation::DropEdge {
id: id.clone(),
before: original_edge(net),
});
}
}
for (id, net) in &self.vertices {
if net.existed && !matches!(net.current, Current::Original) {
out.push(Mutation::DropVertex {
id: id.clone(),
before: original_vertex(net),
});
}
}
for (id, net) in &self.vertices {
if let Current::New(state) = &net.current {
out.push(Mutation::AddVertex {
id: id.clone(),
labels: state.labels.clone(),
properties: state.properties.clone(),
});
}
}
for (id, net) in &self.vertices {
if !matches!(net.current, Current::Original) {
continue;
}
for (label, added) in net.changed_labels() {
out.push(if added {
Mutation::AddLabel {
id: id.clone(),
label: label.to_string(),
}
} else {
Mutation::RemoveLabel {
id: id.clone(),
label: label.to_string(),
}
});
}
push_property_changes(out, ElementRef::vertex(id.clone()), net);
}
for (id, net) in &self.edges {
if let Current::New(state) = &net.current {
out.push(Mutation::AddEdge {
id: id.clone(),
label: state.label.clone(),
out_id: state.out_id.clone(),
in_id: state.in_id.clone(),
properties: state.properties.clone(),
});
}
}
for (id, net) in &self.edges {
if matches!(net.current, Current::Original) {
if let Some((before, after)) = net.changed_edge_label() {
out.push(Mutation::SetEdgeLabel {
id: id.clone(),
before: before.clone(),
after: after.to_string(),
});
}
push_property_changes(out, ElementRef::edge(id.clone()), net);
}
}
}
}
fn push_property_changes<S>(out: &mut Vec<Mutation>, element: ElementRef, net: &Net<S>) {
for (key, first, last) in net.changed_props() {
out.push(match last {
Some(after) => Mutation::SetProperty {
element: element.clone(),
key: key.to_string(),
before: first.clone(),
after: after.clone(),
},
None => Mutation::RemoveProperty {
element: element.clone(),
key: key.to_string(),
before: first.clone().unwrap_or(ElementProperty::Null),
},
});
}
}
fn original_vertex(net: &Net<VertexState>) -> VertexState {
let mut state = net.dropped.clone().unwrap_or_default();
for (key, first) in &net.first_props {
set_in(&mut state.properties, key, first.clone());
}
for (label, present) in &net.first_labels {
if *present {
if !state.labels.iter().any(|l| l == label) {
state.labels.push(label.clone());
}
} else {
state.labels.retain(|l| l != label);
}
}
state
}
fn original_edge(net: &Net<EdgeState>) -> EdgeState {
let mut state = net.dropped.clone().unwrap_or(EdgeState {
label: None,
out_id: String::new(),
in_id: String::new(),
properties: PropertyMap::default(),
});
for (key, first) in &net.first_props {
set_in(&mut state.properties, key, first.clone());
}
if let Some(first) = &net.first_edge_label {
state.label = first.clone();
}
state
}