From 12808c4ff317b7b0f7ccc77c1bc6514b8aa5b851 Mon Sep 17 00:00:00 2001 From: Zachary Hamm Date: Mon, 12 Aug 2024 11:54:56 -0500 Subject: [PATCH 1/3] fix(dal): correct exclusive edges --- .../graph/correct_transforms.rs | 6 ++ lib/dal/src/workspace_snapshot/node_weight.rs | 75 ++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/lib/dal/src/workspace_snapshot/graph/correct_transforms.rs b/lib/dal/src/workspace_snapshot/graph/correct_transforms.rs index 8d19c576b2..165cf1f5b0 100644 --- a/lib/dal/src/workspace_snapshot/graph/correct_transforms.rs +++ b/lib/dal/src/workspace_snapshot/graph/correct_transforms.rs @@ -10,6 +10,7 @@ pub fn correct_transforms( ) -> CorrectTransformsResult> { let mut new_nodes = HashMap::new(); let mut nodes_to_interrogate = HashSet::new(); + let mut nodes_to_correct_exclusive_outgoing = HashSet::new(); for update in &updates { match update { Update::NewEdge { @@ -19,6 +20,7 @@ pub fn correct_transforms( } => { nodes_to_interrogate.insert(source.id); nodes_to_interrogate.insert(destination.id); + nodes_to_correct_exclusive_outgoing.insert(source.id); } Update::RemoveEdge { source, @@ -49,6 +51,10 @@ pub fn correct_transforms( Some(node_index) => graph.get_node_weight_opt(node_index), None => new_nodes.get(&node_to_interrogate.into()), } { + if nodes_to_correct_exclusive_outgoing.contains(&node_to_interrogate) { + updates = node_weight.correct_exclusive_outgoing_edges(graph, updates); + } + updates = node_weight.correct_transforms(graph, updates)?; } } diff --git a/lib/dal/src/workspace_snapshot/node_weight.rs b/lib/dal/src/workspace_snapshot/node_weight.rs index ddcda8d2e1..4bf4cd555b 100644 --- a/lib/dal/src/workspace_snapshot/node_weight.rs +++ b/lib/dal/src/workspace_snapshot/node_weight.rs @@ -1,5 +1,9 @@ -use std::num::TryFromIntError; +use std::{ + collections::{HashMap, HashSet}, + num::TryFromIntError, +}; +use petgraph::{visit::EdgeRef, Direction::Outgoing}; use serde::{Deserialize, Serialize}; use si_events::{merkle_tree_hash::MerkleTreeHash, ulid::Ulid, ContentHash, EncryptedSecretKey}; use strum::EnumDiscriminants; @@ -33,8 +37,8 @@ pub use func_node_weight::FuncNodeWeight; pub use ordering_node_weight::OrderingNodeWeight; pub use prop_node_weight::PropNodeWeight; -use super::graph::deprecated::v1::DeprecatedNodeWeightV1; use super::graph::detect_updates::Update; +use super::{graph::deprecated::v1::DeprecatedNodeWeightV1, NodeInformation}; pub mod action_node_weight; pub mod action_prototype_node_weight; @@ -662,6 +666,73 @@ impl NodeWeight { encrypted_secret_key, )) } + + pub fn correct_exclusive_outgoing_edges( + &self, + graph: &WorkspaceSnapshotGraphV2, + mut updates: Vec, + ) -> Vec { + let exclusive_edge_kinds = self.exclusive_outgoing_edges(); + let mut new_edge_map = HashMap::new(); + let mut removal_set = HashSet::new(); + + for update in &updates { + match update { + Update::NewEdge { + source, + destination, + edge_weight, + } if source.id == self.id().into() + && exclusive_edge_kinds.contains(&edge_weight.kind().into()) => + { + let kind: EdgeWeightKindDiscriminants = edge_weight.kind().into(); + new_edge_map.insert(kind, destination.id); + removal_set.remove(&(kind, destination.id)); + } + Update::RemoveEdge { + source, + destination, + edge_kind, + } if source.id == self.id().into() && exclusive_edge_kinds.contains(edge_kind) => { + removal_set.insert((*edge_kind, destination.id)); + } + _ => {} + } + } + + if let Some(node_idx) = graph.get_node_index_by_id_opt(self.id()) { + updates.extend( + graph + .edges_directed(node_idx, Outgoing) + .filter_map(|edge_ref| { + let destination_weight = graph.get_node_weight_opt(edge_ref.target()); + let edge_kind = edge_ref.weight().kind().into(); + + destination_weight.and_then(|weight| { + let destination: NodeInformation = weight.into(); + + if removal_set.contains(&(edge_kind, destination.id)) { + // prevent adding unnecessary extra remove edge + // updates + None + } else { + new_edge_map.get(&edge_kind).and_then(|new_edge_dest_id| { + (new_edge_dest_id != &destination.id).then_some( + Update::RemoveEdge { + source: self.into(), + destination, + edge_kind, + }, + ) + }) + } + }) + }), + ) + } + + updates + } } impl From for NodeWeight { From 44392a3deab8132a520070990e6e3da720fe436b Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Tue, 13 Aug 2024 09:11:56 -0700 Subject: [PATCH 2/3] feat(dal): correct_exclusive_outgoing_edge trait impl for all node weights Co-Authored-By: Zachary Hamm --- .../graph/correct_transforms.rs | 7 +- lib/dal/src/workspace_snapshot/graph/tests.rs | 2 +- .../graph/tests/attribute_value_build_view.rs | 1844 ----------------- .../graph/tests/exclusive_outgoing_edges.rs | 167 ++ lib/dal/src/workspace_snapshot/node_weight.rs | 102 +- .../workspace_snapshot/node_weight/traits.rs | 4 + .../traits/correct_exclusive_outgoing_edge.rs | 105 + 7 files changed, 305 insertions(+), 1926 deletions(-) delete mode 100644 lib/dal/src/workspace_snapshot/graph/tests/attribute_value_build_view.rs create mode 100644 lib/dal/src/workspace_snapshot/graph/tests/exclusive_outgoing_edges.rs create mode 100644 lib/dal/src/workspace_snapshot/node_weight/traits/correct_exclusive_outgoing_edge.rs diff --git a/lib/dal/src/workspace_snapshot/graph/correct_transforms.rs b/lib/dal/src/workspace_snapshot/graph/correct_transforms.rs index 165cf1f5b0..6b4ed3bb81 100644 --- a/lib/dal/src/workspace_snapshot/graph/correct_transforms.rs +++ b/lib/dal/src/workspace_snapshot/graph/correct_transforms.rs @@ -10,7 +10,7 @@ pub fn correct_transforms( ) -> CorrectTransformsResult> { let mut new_nodes = HashMap::new(); let mut nodes_to_interrogate = HashSet::new(); - let mut nodes_to_correct_exclusive_outgoing = HashSet::new(); + for update in &updates { match update { Update::NewEdge { @@ -20,7 +20,6 @@ pub fn correct_transforms( } => { nodes_to_interrogate.insert(source.id); nodes_to_interrogate.insert(destination.id); - nodes_to_correct_exclusive_outgoing.insert(source.id); } Update::RemoveEdge { source, @@ -51,10 +50,6 @@ pub fn correct_transforms( Some(node_index) => graph.get_node_weight_opt(node_index), None => new_nodes.get(&node_to_interrogate.into()), } { - if nodes_to_correct_exclusive_outgoing.contains(&node_to_interrogate) { - updates = node_weight.correct_exclusive_outgoing_edges(graph, updates); - } - updates = node_weight.correct_transforms(graph, updates)?; } } diff --git a/lib/dal/src/workspace_snapshot/graph/tests.rs b/lib/dal/src/workspace_snapshot/graph/tests.rs index ba41cf7321..9301e0168f 100644 --- a/lib/dal/src/workspace_snapshot/graph/tests.rs +++ b/lib/dal/src/workspace_snapshot/graph/tests.rs @@ -7,8 +7,8 @@ use crate::{ WorkspaceSnapshotGraphV2, }; -mod attribute_value_build_view; mod detect_updates; +mod exclusive_outgoing_edges; mod rebase; #[allow(dead_code)] diff --git a/lib/dal/src/workspace_snapshot/graph/tests/attribute_value_build_view.rs b/lib/dal/src/workspace_snapshot/graph/tests/attribute_value_build_view.rs deleted file mode 100644 index 2e119550c9..0000000000 --- a/lib/dal/src/workspace_snapshot/graph/tests/attribute_value_build_view.rs +++ /dev/null @@ -1,1844 +0,0 @@ -#[allow(clippy::panic)] -#[cfg(test)] -mod test { - #[tokio::test] - #[cfg(ignore)] - async fn attribute_value_build_view() { - let change_set = ChangeSet::new_local().expect("Unable to create ChangeSet"); - let change_set = &change_set; - let mut graph = WorkspaceSnapshotGraph::new(change_set) - .expect("Unable to create WorkspaceSnapshotGraph"); - let mut content_store = content_store::LocalStore::default(); - - let schema_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let schema_content_hash = content_store - .add(&serde_json::json!("Schema A")) - .expect("Unable to add to content store"); - let schema_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - schema_id, - ContentAddress::Schema(schema_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add schema"); - graph - .add_edge( - graph.root_index, - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - schema_node_index, - ) - .expect("Unable to add root -> schema edge"); - - let schema_variant_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let schema_variant_content_hash = content_store - .add(&serde_json::json!("Schema Variant A")) - .expect("Unable to add to content store"); - let schema_variant_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - schema_variant_id, - ContentAddress::SchemaVariant(schema_variant_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add schema variant"); - graph - .add_edge( - graph - .get_node_index_by_id(schema_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - schema_variant_node_index, - ) - .expect("Unable to add schema -> schema variant edge"); - - let root_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let root_prop_content_hash = content_store - .add(&serde_json::json!("Root prop")) - .expect("Unable to add to content store"); - let root_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - root_prop_id, - PropKind::Object, - "root", - root_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add root prop"); - graph - .add_edge( - graph - .get_node_index_by_id(schema_variant_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - root_prop_node_index, - ) - .expect("Unable to add schema variant -> root prop edge"); - - let si_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let si_prop_content_hash = content_store - .add(&serde_json::json!("SI Prop Content")) - .expect("Unable to add to content store"); - let si_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - si_prop_id, - PropKind::Object, - "si", - si_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add si prop"); - graph - .add_edge( - graph - .get_node_index_by_id(root_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - si_prop_node_index, - ) - .expect("Unable to add root prop -> si prop edge"); - - let name_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let name_prop_content_hash = content_store - .add(&serde_json::json!("Name Prop Content")) - .expect("Unable to add to content store"); - let name_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - name_prop_id, - PropKind::Object, - "name", - name_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add name prop"); - graph - .add_edge( - graph - .get_node_index_by_id(si_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - name_prop_node_index, - ) - .expect("Unable to add si prop -> name prop edge"); - - let component_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let component_content_hash = content_store - .add(&serde_json::json!("Component Content")) - .expect("Unable to add to content store"); - let component_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - component_id, - ContentAddress::Component(component_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add component"); - graph - .add_edge( - graph.root_index, - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - component_node_index, - ) - .expect("Unable to add root -> component edge"); - graph - .add_edge( - graph - .get_node_index_by_id(component_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(schema_variant_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add component -> schema variant edge"); - - let root_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let root_av_content_hash = content_store - .add(&serde_json::json!({})) - .expect("Unable to add to content store"); - let root_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - root_av_id, - ContentAddress::AttributeValue(root_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add root av"); - graph - .add_edge( - graph - .get_node_index_by_id(component_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - root_av_node_index, - ) - .expect("Unable to add component -> root av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(root_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add root av -> root prop edge"); - - let si_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let si_av_content_hash = content_store - .add(&serde_json::json!({})) - .expect("Unable to add to content store"); - let si_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - si_av_id, - ContentAddress::AttributeValue(si_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add si av"); - graph - .add_edge( - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - si_av_node_index, - ) - .expect("Unable to add root av -> si av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(si_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(si_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add si av -> si prop edge"); - - let name_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let name_av_content_hash = content_store - .add(&serde_json::json!("component name")) - .expect("Unable to add to content store"); - let name_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - name_av_id, - ContentAddress::AttributeValue(name_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add name av"); - graph - .add_edge( - graph - .get_node_index_by_id(si_av_id) - .expect("Unable to get NodeWeight"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - name_av_node_index, - ) - .expect("Unable to add si av -> name av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(name_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(name_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to create name av -> name prop edge"); - - graph.cleanup(); - graph.dot(); - - assert_eq!( - serde_json::json![{"si": {"name": "component name"}}], - graph - .attribute_value_view( - &mut content_store, - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - ) - .await - .expect("Unable to generate attribute value view"), - ); - } - - #[tokio::test] - #[cfg(ignore)] - async fn attribute_value_build_view_unordered_object() { - let change_set = ChangeSet::new_local().expect("Unable to create ChangeSet"); - let change_set = &change_set; - let mut graph = WorkspaceSnapshotGraph::new(change_set) - .expect("Unable to create WorkspaceSnapshotGraph"); - let mut content_store = content_store::LocalStore::default(); - - let schema_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let schema_content_hash = content_store - .add(&serde_json::json!("Schema A")) - .expect("Unable to add to content store"); - let schema_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - schema_id, - ContentAddress::Schema(schema_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add schema"); - graph - .add_edge( - graph.root_index, - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - schema_node_index, - ) - .expect("Unable to add root -> schema edge"); - - let schema_variant_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let schema_variant_content_hash = content_store - .add(&serde_json::json!("Schema Variant A")) - .expect("Unable to add to content store"); - let schema_variant_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - schema_variant_id, - ContentAddress::SchemaVariant(schema_variant_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add schema variant"); - graph - .add_edge( - graph - .get_node_index_by_id(schema_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - schema_variant_node_index, - ) - .expect("Unable to add schema -> schema variant edge"); - - let root_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let root_prop_content_hash = content_store - .add(&serde_json::json!("Root prop")) - .expect("Unable to add to content store"); - let root_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - root_prop_id, - PropKind::Object, - "root", - root_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add root prop"); - graph - .add_edge( - graph - .get_node_index_by_id(schema_variant_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - root_prop_node_index, - ) - .expect("Unable to add schema variant -> root prop edge"); - - let si_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let si_prop_content_hash = content_store - .add(&serde_json::json!("SI Prop Content")) - .expect("Unable to add to content store"); - let si_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - si_prop_id, - PropKind::Object, - "si", - si_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add si prop"); - graph - .add_edge( - graph - .get_node_index_by_id(root_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - si_prop_node_index, - ) - .expect("Unable to add root prop -> si prop edge"); - - let name_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let name_prop_content_hash = content_store - .add(&serde_json::json!("Name Prop Content")) - .expect("Unable to add to content store"); - let name_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - name_prop_id, - PropKind::Object, - "name", - name_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add name prop"); - graph - .add_edge( - graph - .get_node_index_by_id(si_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - name_prop_node_index, - ) - .expect("Unable to add si prop -> name prop edge"); - - let description_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let description_prop_content_hash = content_store - .add(&serde_json::json!("Description Prop Content")) - .expect("Unable to add to content store"); - let description_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - description_prop_id, - PropKind::String, - "description", - description_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add description prop"); - graph - .add_edge( - graph - .get_node_index_by_id(si_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - description_prop_node_index, - ) - .expect("Unable to add si prop -> description prop edge"); - - let component_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let component_content_hash = content_store - .add(&serde_json::json!("Component Content")) - .expect("Unable to add to content store"); - let component_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - component_id, - ContentAddress::Component(component_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add component"); - graph - .add_edge( - graph.root_index, - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - component_node_index, - ) - .expect("Unable to add root -> component edge"); - graph - .add_edge( - graph - .get_node_index_by_id(component_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(schema_variant_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add component -> schema variant edge"); - - let root_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let root_av_content_hash = content_store - .add(&serde_json::json!({})) - .expect("Unable to add to content store"); - let root_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - root_av_id, - ContentAddress::AttributeValue(root_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add root av"); - graph - .add_edge( - graph - .get_node_index_by_id(component_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - root_av_node_index, - ) - .expect("Unable to add component -> root av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(root_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add root av -> root prop edge"); - - let si_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let si_av_content_hash = content_store - .add(&serde_json::json!({})) - .expect("Unable to add to content store"); - let si_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - si_av_id, - ContentAddress::AttributeValue(si_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add si av"); - graph - .add_edge( - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - si_av_node_index, - ) - .expect("Unable to add root av -> si av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(si_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(si_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add si av -> si prop edge"); - - let name_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let name_av_content_hash = content_store - .add(&serde_json::json!("component name")) - .expect("Unable to add to content store"); - let name_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - name_av_id, - ContentAddress::AttributeValue(name_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add name av"); - graph - .add_edge( - graph - .get_node_index_by_id(si_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - name_av_node_index, - ) - .expect("Unable to add si av -> name av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(name_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(name_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to create name av -> name prop edge"); - - let description_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let description_av_content_hash = content_store - .add(&serde_json::json!("Component description")) - .expect("Unable to add to content store"); - let description_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - description_av_id, - ContentAddress::AttributeValue(description_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add description av"); - graph - .add_edge( - graph - .get_node_index_by_id(si_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - description_av_node_index, - ) - .expect("Unable to add si av -> description av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(description_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(description_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add description av -> description prop edge"); - - graph.cleanup(); - graph.dot(); - - assert_eq!( - serde_json::json![{ - "si": { - "description": "Component description", - "name": "component name", - } - }], - graph - .attribute_value_view( - &mut content_store, - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - ) - .await - .expect("Unable to generate attribute value view"), - ); - } - - #[tokio::test] - #[cfg(ignore)] - async fn attribute_value_build_view_ordered_array() { - let change_set = ChangeSet::new_local().expect("Unable to create ChangeSet"); - let change_set = &change_set; - let mut graph = WorkspaceSnapshotGraph::new(change_set) - .expect("Unable to create WorkspaceSnapshotGraph"); - let mut content_store = content_store::LocalStore::default(); - - let schema_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let schema_content_hash = content_store - .add(&serde_json::json!("Schema A")) - .expect("Unable to add to content store"); - let schema_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - schema_id, - ContentAddress::Schema(schema_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add schema"); - graph - .add_edge( - graph.root_index, - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - schema_node_index, - ) - .expect("Unable to add root -> schema edge"); - - let schema_variant_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let schema_variant_content_hash = content_store - .add(&serde_json::json!("Schema Variant A")) - .expect("Unable to add to content store"); - let schema_variant_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - schema_variant_id, - ContentAddress::SchemaVariant(schema_variant_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add schema variant"); - graph - .add_edge( - graph - .get_node_index_by_id(schema_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - schema_variant_node_index, - ) - .expect("Unable to add schema -> schema variant edge"); - - let root_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let root_prop_content_hash = content_store - .add(&serde_json::json!("Root prop")) - .expect("Unable to add to content store"); - let root_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - root_prop_id, - PropKind::Object, - "root", - root_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add root prop"); - graph - .add_edge( - graph - .get_node_index_by_id(schema_variant_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - root_prop_node_index, - ) - .expect("Unable to add schema variant -> root prop edge"); - - let domain_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let domain_prop_content_hash = content_store - .add(&serde_json::json!("domain Prop Content")) - .expect("Unable to add to content store"); - let domain_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - domain_prop_id, - PropKind::Object, - "domain", - domain_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add domain prop"); - graph - .add_edge( - graph - .get_node_index_by_id(root_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - domain_prop_node_index, - ) - .expect("Unable to add root prop -> domain prop edge"); - - let ports_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let ports_prop_content_hash = content_store - .add(&serde_json::json!("ports Prop Content")) - .expect("Unable to add to content store"); - let ports_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - ports_prop_id, - PropKind::Array, - "ports", - ports_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add ports prop"); - graph - .add_edge( - graph - .get_node_index_by_id(domain_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - ports_prop_node_index, - ) - .expect("Unable to add domain prop -> ports prop edge"); - - let port_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let port_prop_content_hash = content_store - .add(&serde_json::json!("port Prop Content")) - .expect("Unable to add to content store"); - let port_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - port_prop_id, - PropKind::String, - "port", - port_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add port prop"); - graph - .add_edge( - graph - .get_node_index_by_id(ports_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - port_prop_node_index, - ) - .expect("Unable to add ports prop -> port prop edge"); - - let component_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let component_content_hash = content_store - .add(&serde_json::json!("Component Content")) - .expect("Unable to add to content store"); - let component_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - component_id, - ContentAddress::Component(component_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add component"); - graph - .add_edge( - graph.root_index, - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - component_node_index, - ) - .expect("Unable to add root -> component edge"); - graph - .add_edge( - graph - .get_node_index_by_id(component_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(schema_variant_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add component -> schema variant edge"); - - let root_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let root_av_content_hash = content_store - .add(&serde_json::json!({})) - .expect("Unable to add to content store"); - let root_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - root_av_id, - ContentAddress::AttributeValue(root_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add root av"); - graph - .add_edge( - graph - .get_node_index_by_id(component_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - root_av_node_index, - ) - .expect("Unable to add component -> root av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(root_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add root av -> root prop edge"); - - let domain_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let domain_av_content_hash = content_store - .add(&serde_json::json!({})) - .expect("Unable to add to content store"); - let domain_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - domain_av_id, - ContentAddress::AttributeValue(domain_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add domain av"); - graph - .add_edge( - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - domain_av_node_index, - ) - .expect("Unable to add root av -> domain av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(domain_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(domain_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add domain av -> domain prop edge"); - - let ports_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let ports_av_content_hash = content_store - .add(&serde_json::json!([])) - .expect("Unable to add to content store"); - let ports_av_node_index = graph - .add_ordered_node( - change_set, - NodeWeight::new_content( - change_set, - ports_av_id, - ContentAddress::AttributeValue(ports_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add ports av"); - graph - .add_edge( - graph - .get_node_index_by_id(domain_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - ports_av_node_index, - ) - .expect("Unable to add domain av -> ports av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(ports_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(ports_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to create ports av -> ports prop edge"); - - let port1_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let port1_av_content_hash = content_store - .add(&serde_json::json!("Port 1")) - .expect("Unable to add to content store"); - let port1_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - port1_av_id, - ContentAddress::AttributeValue(port1_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add port 1 av"); - graph - .add_ordered_edge( - change_set, - graph - .get_node_index_by_id(ports_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - port1_av_node_index, - ) - .expect("Unable to add ports av -> port 1 av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(port1_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(port_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add port 1 av -> port prop edge"); - - let port2_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let port2_av_content_hash = content_store - .add(&serde_json::json!("Port 2")) - .expect("Unable to add to content store"); - let port2_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - port2_av_id, - ContentAddress::AttributeValue(port2_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add port 2 av"); - graph - .add_ordered_edge( - change_set, - graph - .get_node_index_by_id(ports_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - port2_av_node_index, - ) - .expect("Unable to add ports av -> port 2 av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(port2_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(port_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add port 2 av -> port prop edge"); - - let port3_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let port3_av_content_hash = content_store - .add(&serde_json::json!("Port 3")) - .expect("Unable to add to content store"); - let port3_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - port3_av_id, - ContentAddress::AttributeValue(port3_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add port 3 av"); - graph - .add_ordered_edge( - change_set, - graph - .get_node_index_by_id(ports_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - port3_av_node_index, - ) - .expect("Unable to add ports av -> port 3 av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(port3_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(port_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add port 3 av -> port prop edge"); - - let port4_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let port4_av_content_hash = content_store - .add(&serde_json::json!("Port 4")) - .expect("Unable to add to content store"); - let port4_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - port4_av_id, - ContentAddress::AttributeValue(port4_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add port 4 av"); - graph - .add_ordered_edge( - change_set, - graph - .get_node_index_by_id(ports_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - port4_av_node_index, - ) - .expect("Unable to add ports av -> port 4 av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(port4_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(port_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add port 4 av -> port prop edge"); - - graph.cleanup(); - graph.dot(); - - assert_eq!( - serde_json::json![{ - "domain": { - "ports": [ - "Port 1", - "Port 2", - "Port 3", - "Port 4", - ], - } - }], - graph - .attribute_value_view( - &mut content_store, - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - ) - .await - .expect("Unable to generate attribute value view"), - ); - - let new_order = vec![port3_av_id, port1_av_id, port4_av_id, port2_av_id]; - graph - .update_order(change_set, ports_av_id, new_order) - .expect("Unable to update order of ports attribute value's children"); - assert_eq!( - serde_json::json![{ - "domain": { - "ports": [ - "Port 3", - "Port 1", - "Port 4", - "Port 2", - ] - } - }], - graph - .attribute_value_view( - &mut content_store, - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - ) - .await - .expect("Unable to generate attribute value view"), - ); - - let port5_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let port5_av_content_hash = content_store - .add(&serde_json::json!("Port 5")) - .expect("Unable to add to content store"); - let port5_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - port5_av_id, - ContentAddress::AttributeValue(port5_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add port 5 av"); - graph - .add_ordered_edge( - change_set, - graph - .get_node_index_by_id(ports_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - port5_av_node_index, - ) - .expect("Unable to add ports av -> port 5 av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(port5_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(port_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add port 5 av -> port prop edge"); - - assert_eq!( - serde_json::json![{ - "domain": { - "ports": [ - "Port 3", - "Port 1", - "Port 4", - "Port 2", - "Port 5", - ] - } - }], - graph - .attribute_value_view( - &mut content_store, - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - ) - .await - .expect("Unable to generate attribute value view"), - ); - } - - #[tokio::test] - #[cfg(ignore)] - async fn attribute_value_build_view_ordered_map() { - let change_set = ChangeSet::new_local().expect("Unable to create ChangeSet"); - let change_set = &change_set; - let mut graph = WorkspaceSnapshotGraph::new(change_set) - .expect("Unable to create WorkspaceSnapshotGraph"); - let mut content_store = content_store::LocalStore::default(); - - let schema_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let schema_content_hash = content_store - .add(&serde_json::json!("Schema A")) - .expect("Unable to add to content store"); - let schema_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - schema_id, - ContentAddress::Schema(schema_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add schema"); - graph - .add_edge( - graph.root_index, - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - schema_node_index, - ) - .expect("Unable to add root -> schema edge"); - - let schema_variant_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let schema_variant_content_hash = content_store - .add(&serde_json::json!("Schema Variant A")) - .expect("Unable to add to content store"); - let schema_variant_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - schema_variant_id, - ContentAddress::SchemaVariant(schema_variant_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add schema variant"); - graph - .add_edge( - graph - .get_node_index_by_id(schema_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - schema_variant_node_index, - ) - .expect("Unable to add schema -> schema variant edge"); - - let root_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let root_prop_content_hash = content_store - .add(&serde_json::json!("Root prop")) - .expect("Unable to add to content store"); - let root_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - root_prop_id, - PropKind::Object, - "root", - root_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add root prop"); - graph - .add_edge( - graph - .get_node_index_by_id(schema_variant_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - root_prop_node_index, - ) - .expect("Unable to add schema variant -> root prop edge"); - - let domain_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let domain_prop_content_hash = content_store - .add(&serde_json::json!("domain Prop Content")) - .expect("Unable to add to content store"); - let domain_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - domain_prop_id, - PropKind::Object, - "domain", - domain_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add domain prop"); - graph - .add_edge( - graph - .get_node_index_by_id(root_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - domain_prop_node_index, - ) - .expect("Unable to add root prop -> domain prop edge"); - - let environment_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let environment_prop_content_hash = content_store - .add(&serde_json::json!("environment Prop Content")) - .expect("Unable to add to content store"); - let environment_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - environment_prop_id, - PropKind::Array, - "environment", - environment_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add environment prop"); - graph - .add_edge( - graph - .get_node_index_by_id(domain_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - environment_prop_node_index, - ) - .expect("Unable to add domain prop -> environment prop edge"); - - let env_var_prop_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let env_var_prop_content_hash = content_store - .add(&serde_json::json!("port Prop Content")) - .expect("Unable to add to content store"); - let env_var_prop_node_index = graph - .add_node( - NodeWeight::new_prop( - change_set, - env_var_prop_id, - PropKind::String, - "port", - env_var_prop_content_hash, - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add env var prop"); - graph - .add_edge( - graph - .get_node_index_by_id(environment_prop_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - env_var_prop_node_index, - ) - .expect("Unable to add environment prop -> env var prop edge"); - - let component_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let component_content_hash = content_store - .add(&serde_json::json!("Component Content")) - .expect("Unable to add to content store"); - let component_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - component_id, - ContentAddress::Component(component_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add component"); - graph - .add_edge( - graph.root_index, - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - component_node_index, - ) - .expect("Unable to add root -> component edge"); - graph - .add_edge( - graph - .get_node_index_by_id(component_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(schema_variant_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add component -> schema variant edge"); - - let root_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let root_av_content_hash = content_store - .add(&serde_json::json!({})) - .expect("Unable to add to content store"); - let root_av_node_index = graph - .add_node( - NodeWeight::new_attribute_value(change_set, root_av_id, None, None, None) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add root av"); - graph - .add_edge( - graph - .get_node_index_by_id(component_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::new_use()) - .expect("Unable to create EdgeWeight"), - root_av_node_index, - ) - .expect("Unable to add component -> root av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(root_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add root av -> root prop edge"); - - let domain_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let domain_av_content_hash = content_store - .add(&serde_json::json!({})) - .expect("Unable to add to content store"); - let domain_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - domain_av_id, - ContentAddress::AttributeValue(domain_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add domain av"); - graph - .add_edge( - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - domain_av_node_index, - ) - .expect("Unable to add root av -> domain av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(domain_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(domain_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add domain av -> domain prop edge"); - - let envrionment_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let ports_av_content_hash = content_store - .add(&serde_json::json!({})) - .expect("Unable to add to content store"); - let environment_av_node_index = graph - .add_ordered_node( - change_set, - NodeWeight::new_content( - change_set, - envrionment_av_id, - ContentAddress::AttributeValue(ports_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add environment av"); - graph - .add_edge( - graph - .get_node_index_by_id(domain_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Contain(None)) - .expect("Unable to create EdgeWeight"), - environment_av_node_index, - ) - .expect("Unable to add domain av -> environment av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(envrionment_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(environment_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to create environment av -> environment prop edge"); - - let env_var1_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let env_var1_av_content_hash = content_store - .add(&serde_json::json!("1111")) - .expect("Unable to add to content store"); - let port1_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - env_var1_av_id, - ContentAddress::AttributeValue(env_var1_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add env_var 1 av"); - graph - .add_ordered_edge( - change_set, - graph - .get_node_index_by_id(envrionment_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new( - change_set, - EdgeWeightKind::Contain(Some("PORT_1".to_string())), - ) - .expect("Unable to create EdgeWeight"), - port1_av_node_index, - ) - .expect("Unable to add environment av -> env var 1 av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(env_var1_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(env_var_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add env var 1 av -> env var prop edge"); - - let env_var2_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let env_var2_av_content_hash = content_store - .add(&serde_json::json!("2222")) - .expect("Unable to add to content store"); - let env_var2_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - env_var2_av_id, - ContentAddress::AttributeValue(env_var2_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add env var 2 av"); - graph - .add_ordered_edge( - change_set, - graph - .get_node_index_by_id(envrionment_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new( - change_set, - EdgeWeightKind::Contain(Some("PORT_2".to_string())), - ) - .expect("Unable to create EdgeWeight"), - env_var2_av_node_index, - ) - .expect("Unable to add environment av -> env var 2 av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(env_var2_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(env_var_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add env var 2 av -> env var prop edge"); - - let env_var3_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let env_var3_av_content_hash = content_store - .add(&serde_json::json!("3333")) - .expect("Unable to add to content store"); - let port3_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - env_var3_av_id, - ContentAddress::AttributeValue(env_var3_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add env var 3 av"); - graph - .add_ordered_edge( - change_set, - graph - .get_node_index_by_id(envrionment_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new( - change_set, - EdgeWeightKind::Contain(Some("PORT_3".to_string())), - ) - .expect("Unable to create EdgeWeight"), - port3_av_node_index, - ) - .expect("Unable to add environment av -> env var 3 av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(env_var3_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(env_var_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add env var 3 av -> env var prop edge"); - - let env_var4_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let env_var4_av_content_hash = content_store - .add(&serde_json::json!("4444")) - .expect("Unable to add to content store"); - let env_var4_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - env_var4_av_id, - ContentAddress::AttributeValue(env_var4_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add env var 4 av"); - graph - .add_ordered_edge( - change_set, - graph - .get_node_index_by_id(envrionment_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new( - change_set, - EdgeWeightKind::Contain(Some("PORT_4".to_string())), - ) - .expect("Unable to create EdgeWeight"), - env_var4_av_node_index, - ) - .expect("Unable to add environment av -> env var 4 av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(env_var4_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(env_var_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add env var 4 av -> env var prop edge"); - - graph.cleanup(); - graph.dot(); - - assert_eq!( - serde_json::json![{ - "domain": { - "environment": { - "PORT_1": "1111", - "PORT_2": "2222", - "PORT_3": "3333", - "PORT_4": "4444", - }, - } - }], - graph - .attribute_value_view( - &mut content_store, - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - ) - .await - .expect("Unable to generate attribute value view"), - ); - - let new_order = vec![ - env_var3_av_id, - env_var1_av_id, - env_var4_av_id, - env_var2_av_id, - ]; - graph - .update_order(change_set, envrionment_av_id, new_order) - .expect("Unable to update order of environment attribute value's children"); - assert_eq!( - serde_json::json![{ - "domain": { - "environment": { - "PORT_3": "3333", - "PORT_1": "1111", - "PORT_4": "4444", - "PORT_2": "2222", - }, - } - }], - graph - .attribute_value_view( - &mut content_store, - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - ) - .await - .expect("Unable to generate attribute value view"), - ); - - let env_var5_av_id = change_set.generate_ulid().expect("Unable to generate Ulid"); - let env_var5_av_content_hash = content_store - .add(&serde_json::json!("5555")) - .expect("Unable to add to content store"); - let env_var5_av_node_index = graph - .add_node( - NodeWeight::new_content( - change_set, - env_var5_av_id, - ContentAddress::AttributeValue(env_var5_av_content_hash), - ) - .expect("Unable to create NodeWeight"), - ) - .expect("Unable to add env var 5 av"); - graph - .add_ordered_edge( - change_set, - graph - .get_node_index_by_id(envrionment_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new( - change_set, - EdgeWeightKind::Contain(Some("PORT_5".to_string())), - ) - .expect("Unable to create EdgeWeight"), - env_var5_av_node_index, - ) - .expect("Unable to add environment av -> env var 5 av edge"); - graph - .add_edge( - graph - .get_node_index_by_id(env_var5_av_id) - .expect("Unable to get NodeIndex"), - EdgeWeight::new(change_set, EdgeWeightKind::Prop) - .expect("Unable to create EdgeWeight"), - graph - .get_node_index_by_id(env_var_prop_id) - .expect("Unable to get NodeIndex"), - ) - .expect("Unable to add env var 5 av -> env var prop edge"); - - assert_eq!( - serde_json::json![{ - "domain": { - "environment": { - "PORT_3": "3333", - "PORT_1": "1111", - "PORT_4": "4444", - "PORT_2": "2222", - "PORT_5": "5555", - }, - } - }], - graph - .attribute_value_view( - &mut content_store, - graph - .get_node_index_by_id(root_av_id) - .expect("Unable to get NodeIndex"), - ) - .await - .expect("Unable to generate attribute value view"), - ); - } -} diff --git a/lib/dal/src/workspace_snapshot/graph/tests/exclusive_outgoing_edges.rs b/lib/dal/src/workspace_snapshot/graph/tests/exclusive_outgoing_edges.rs new file mode 100644 index 0000000000..c2e65772f6 --- /dev/null +++ b/lib/dal/src/workspace_snapshot/graph/tests/exclusive_outgoing_edges.rs @@ -0,0 +1,167 @@ +#[allow(clippy::panic)] +#[allow(clippy::panic_in_result_fn)] +#[cfg(test)] +mod test { + use si_events::ContentHash; + + use crate::{ + workspace_snapshot::{ + content_address::ContentAddress, + graph::{detect_updates::Update, WorkspaceSnapshotGraphResult}, + node_weight::{traits::CorrectExclusiveOutgoingEdge, NodeWeight}, + }, + EdgeWeight, EdgeWeightKind, EdgeWeightKindDiscriminants, WorkspaceSnapshotGraphV2, + }; + + #[test] + fn correct_exclusive_outgoing_edges() -> WorkspaceSnapshotGraphResult<()> { + let mut graph = WorkspaceSnapshotGraphV2::new()?; + + let schema_variant_1_id = graph.generate_ulid()?; + let schema_variant_2_id = graph.generate_ulid()?; + + let sv_1 = NodeWeight::new_content( + schema_variant_1_id, + schema_variant_1_id, + ContentAddress::SchemaVariant(ContentHash::new( + &schema_variant_1_id.inner().to_bytes(), + )), + ); + + let sv_2 = NodeWeight::new_content( + schema_variant_2_id, + schema_variant_2_id, + ContentAddress::SchemaVariant(ContentHash::new( + &schema_variant_2_id.inner().to_bytes(), + )), + ); + + let component_id = graph.generate_ulid()?; + let component = NodeWeight::new_component( + component_id, + component_id, + ContentHash::new(&component_id.inner().to_bytes()), + ); + + let sv_1_idx = graph.add_node(sv_1.clone())?; + + graph.add_edge( + graph.root(), + EdgeWeight::new(EdgeWeightKind::new_use()), + sv_1_idx, + )?; + + let component_idx = graph.add_node(component.clone())?; + + graph.add_edge( + graph.root_index, + EdgeWeight::new(EdgeWeightKind::new_use()), + component_idx, + )?; + + let root_node = graph.get_node_weight(graph.root())?.to_owned(); + + let updates = vec![ + Update::NewNode { + node_weight: sv_2.clone(), + }, + Update::NewEdge { + source: (&root_node).into(), + destination: (&sv_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + ]; + let new_updates = component.correct_exclusive_outgoing_edges(&graph, updates.clone()); + // There are no exclusive edges here so we should not produce an update + assert_eq!( + new_updates, updates, + "no exclusive edges, therefore no new updates" + ); + + let updates = vec![ + Update::NewNode { + node_weight: sv_2.clone(), + }, + Update::NewEdge { + source: (&root_node).into(), + destination: (&sv_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + Update::NewEdge { + source: (&component).into(), + destination: (&sv_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + ]; + + let new_updates = component.correct_exclusive_outgoing_edges(&graph, updates.clone()); + // nothing should change here, either, since we haven't added the exclusive edge + assert_eq!( + new_updates, updates, + "no exclusive edge already in the graph, so no new updates" + ); + + // this is the "exclusive" edge + graph.add_edge( + graph.get_node_index_by_id(component_id)?, + EdgeWeight::new(EdgeWeightKind::new_use()), + graph.get_node_index_by_id(schema_variant_1_id)?, + )?; + + let updates = vec![ + Update::NewNode { + node_weight: sv_2.clone(), + }, + Update::NewEdge { + source: (&root_node).into(), + destination: (&sv_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + Update::NewEdge { + source: (&component).into(), + destination: (&sv_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + ]; + + let new_updates = component.correct_exclusive_outgoing_edges(&graph, updates.clone()); + let mut expected_updates = updates.clone(); + let expected_remove_edge_update = Update::RemoveEdge { + source: (&component).into(), + destination: (&sv_1).into(), + edge_kind: EdgeWeightKindDiscriminants::Use, + }; + expected_updates.push(expected_remove_edge_update.clone()); + + assert_eq!( + expected_updates, new_updates, + "has a new remove edge update" + ); + + let updates = vec![ + Update::NewNode { + node_weight: sv_2.clone(), + }, + Update::NewEdge { + source: (&root_node).into(), + destination: (&sv_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + Update::NewEdge { + source: (&component).into(), + destination: (&sv_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + expected_remove_edge_update.clone(), + ]; + + let new_updates = component.correct_exclusive_outgoing_edges(&graph, updates.clone()); + // with the remove edge already there, there should be no change + assert_eq!( + updates, new_updates, + "remove edge in the set of updates, so no new update" + ); + + Ok(()) + } +} diff --git a/lib/dal/src/workspace_snapshot/node_weight.rs b/lib/dal/src/workspace_snapshot/node_weight.rs index 4bf4cd555b..c0a4d5a615 100644 --- a/lib/dal/src/workspace_snapshot/node_weight.rs +++ b/lib/dal/src/workspace_snapshot/node_weight.rs @@ -1,14 +1,10 @@ -use std::{ - collections::{HashMap, HashSet}, - num::TryFromIntError, -}; +use std::num::TryFromIntError; -use petgraph::{visit::EdgeRef, Direction::Outgoing}; use serde::{Deserialize, Serialize}; use si_events::{merkle_tree_hash::MerkleTreeHash, ulid::Ulid, ContentHash, EncryptedSecretKey}; use strum::EnumDiscriminants; use thiserror::Error; -use traits::{CorrectTransforms, CorrectTransformsResult}; +use traits::{CorrectExclusiveOutgoingEdge, CorrectTransforms, CorrectTransformsResult}; use crate::{ action::prototype::ActionKind, @@ -37,8 +33,8 @@ pub use func_node_weight::FuncNodeWeight; pub use ordering_node_weight::OrderingNodeWeight; pub use prop_node_weight::PropNodeWeight; +use super::graph::deprecated::v1::DeprecatedNodeWeightV1; use super::graph::detect_updates::Update; -use super::{graph::deprecated::v1::DeprecatedNodeWeightV1, NodeInformation}; pub mod action_node_weight; pub mod action_prototype_node_weight; @@ -666,73 +662,6 @@ impl NodeWeight { encrypted_secret_key, )) } - - pub fn correct_exclusive_outgoing_edges( - &self, - graph: &WorkspaceSnapshotGraphV2, - mut updates: Vec, - ) -> Vec { - let exclusive_edge_kinds = self.exclusive_outgoing_edges(); - let mut new_edge_map = HashMap::new(); - let mut removal_set = HashSet::new(); - - for update in &updates { - match update { - Update::NewEdge { - source, - destination, - edge_weight, - } if source.id == self.id().into() - && exclusive_edge_kinds.contains(&edge_weight.kind().into()) => - { - let kind: EdgeWeightKindDiscriminants = edge_weight.kind().into(); - new_edge_map.insert(kind, destination.id); - removal_set.remove(&(kind, destination.id)); - } - Update::RemoveEdge { - source, - destination, - edge_kind, - } if source.id == self.id().into() && exclusive_edge_kinds.contains(edge_kind) => { - removal_set.insert((*edge_kind, destination.id)); - } - _ => {} - } - } - - if let Some(node_idx) = graph.get_node_index_by_id_opt(self.id()) { - updates.extend( - graph - .edges_directed(node_idx, Outgoing) - .filter_map(|edge_ref| { - let destination_weight = graph.get_node_weight_opt(edge_ref.target()); - let edge_kind = edge_ref.weight().kind().into(); - - destination_weight.and_then(|weight| { - let destination: NodeInformation = weight.into(); - - if removal_set.contains(&(edge_kind, destination.id)) { - // prevent adding unnecessary extra remove edge - // updates - None - } else { - new_edge_map.get(&edge_kind).and_then(|new_edge_dest_id| { - (new_edge_dest_id != &destination.id).then_some( - Update::RemoveEdge { - source: self.into(), - destination, - edge_kind, - }, - ) - }) - } - }) - }), - ) - } - - updates - } } impl From for NodeWeight { @@ -765,7 +694,7 @@ impl CorrectTransforms for NodeWeight { workspace_snapshot_graph: &WorkspaceSnapshotGraphV2, updates: Vec, ) -> CorrectTransformsResult> { - match self { + let updates = match self { NodeWeight::Action(weight) => { weight.correct_transforms(workspace_snapshot_graph, updates) } @@ -805,6 +734,29 @@ impl CorrectTransforms for NodeWeight { NodeWeight::Secret(weight) => { weight.correct_transforms(workspace_snapshot_graph, updates) } + }?; + + Ok(self.correct_exclusive_outgoing_edges(workspace_snapshot_graph, updates)) + } +} + +impl CorrectExclusiveOutgoingEdge for NodeWeight { + fn exclusive_outgoing_edges(&self) -> Vec { + match self { + NodeWeight::Action(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::ActionPrototype(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::AttributePrototypeArgument(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::AttributeValue(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::Category(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::Component(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::Content(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::DependentValueRoot(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::Func(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::FuncArgument(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::Ordering(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::Prop(weight) => weight.exclusive_outgoing_edges(), + NodeWeight::Secret(weight) => weight.exclusive_outgoing_edges(), } + .to_vec() } } diff --git a/lib/dal/src/workspace_snapshot/node_weight/traits.rs b/lib/dal/src/workspace_snapshot/node_weight/traits.rs index 515e6c2dc8..ba8615a7eb 100644 --- a/lib/dal/src/workspace_snapshot/node_weight/traits.rs +++ b/lib/dal/src/workspace_snapshot/node_weight/traits.rs @@ -3,6 +3,10 @@ use thiserror::Error; use super::NodeWeightDiscriminants; +pub mod correct_exclusive_outgoing_edge; + +pub use correct_exclusive_outgoing_edge::CorrectExclusiveOutgoingEdge; + #[derive(Debug, Error)] pub enum CorrectTransformsError { #[error("expected a node weight of kind {0} but got another, or none")] diff --git a/lib/dal/src/workspace_snapshot/node_weight/traits/correct_exclusive_outgoing_edge.rs b/lib/dal/src/workspace_snapshot/node_weight/traits/correct_exclusive_outgoing_edge.rs new file mode 100644 index 0000000000..884d7fcb94 --- /dev/null +++ b/lib/dal/src/workspace_snapshot/node_weight/traits/correct_exclusive_outgoing_edge.rs @@ -0,0 +1,105 @@ +use std::collections::{HashMap, HashSet}; + +use petgraph::prelude::*; + +use crate::{ + workspace_snapshot::{ + edge_weight::EdgeWeightKindDiscriminants, graph::detect_updates::Update, NodeInformation, + }, + WorkspaceSnapshotGraphV2, +}; + +pub trait CorrectExclusiveOutgoingEdge +where + NodeInformation: for<'a> From<&'a Self>, +{ + fn exclusive_outgoing_edges(&self) -> Vec; + + /// If a set of updates will produce a new outgoing edge that a node weight + /// considers "exclusive" (that is, there can only be one of these kinds of + /// outgoing edges for this node), then we need to ensure we don't add that + /// new edge to a graph that already has an edge. + /// + /// We assume that the set of updates passed in was correct from the start + /// (that is, the set of updates does not itself include more than one net + /// outgoing NewEdge update for the exclusive edge) + fn correct_exclusive_outgoing_edges( + &self, + graph: &WorkspaceSnapshotGraphV2, + mut updates: Vec, + ) -> Vec { + let exclusive_edge_kinds = self.exclusive_outgoing_edges(); + if exclusive_edge_kinds.is_empty() { + return updates; + } + + let mut removal_set = HashSet::new(); + let mut new_edge_map = HashMap::new(); + + let source_node_information: NodeInformation = self.into(); + let source_id = source_node_information.id; + + for update in &updates { + match update { + Update::NewEdge { + source, + destination, + edge_weight, + } if source.id == source_id + && exclusive_edge_kinds.contains(&edge_weight.kind().into()) => + { + let kind: EdgeWeightKindDiscriminants = edge_weight.kind().into(); + // last new edge for an exclusive wins + new_edge_map.insert(kind, destination.id); + removal_set.remove(&(kind, destination.id)); + } + Update::RemoveEdge { + source, + destination, + edge_kind, + } if source.id == source_id && exclusive_edge_kinds.contains(edge_kind) => { + removal_set.insert((*edge_kind, destination.id)); + } + _ => {} + } + } + + if let Some(node_idx) = graph.get_node_index_by_id_opt(source_id) { + updates.extend( + graph + .edges_directed(node_idx, Outgoing) + .filter_map(|edge_ref| { + let destination_weight = graph.get_node_weight_opt(edge_ref.target()); + let edge_kind = edge_ref.weight().kind().into(); + + new_edge_map + .get(&edge_kind) + .and_then(|&new_edge_destination_id| { + destination_weight.and_then(|weight| { + let destination: NodeInformation = weight.into(); + + // If this is not the last NewEdge for this kind, + // and net removals is < 1, then we need to remove + // this edge since it will still be in the graph + // otherwise. + if destination.id != new_edge_destination_id + && !removal_set.contains(&(edge_kind, destination.id)) + { + removal_set.insert((edge_kind, destination.id)); + Some(Update::RemoveEdge { + source: source_node_information, + destination, + edge_kind, + }) + } else { + None + } + }) + }) + }), + ); + } + + updates + } +} From 652c4f6dfdafdbe1fc0d625a6e6319281e0ed2e7 Mon Sep 17 00:00:00 2001 From: Zachary Hamm Date: Wed, 14 Aug 2024 12:07:57 -0500 Subject: [PATCH 3/3] feat(dal): special case exclusive edges for actions --- .../graph/tests/exclusive_outgoing_edges.rs | 185 +++++++++++++++++- .../node_weight/action_node_weight.rs | 124 +++++++++++- .../node_weight/secret_node_weight.rs | 2 +- .../traits/correct_exclusive_outgoing_edge.rs | 7 +- 4 files changed, 306 insertions(+), 12 deletions(-) diff --git a/lib/dal/src/workspace_snapshot/graph/tests/exclusive_outgoing_edges.rs b/lib/dal/src/workspace_snapshot/graph/tests/exclusive_outgoing_edges.rs index c2e65772f6..e9a84c6d3f 100644 --- a/lib/dal/src/workspace_snapshot/graph/tests/exclusive_outgoing_edges.rs +++ b/lib/dal/src/workspace_snapshot/graph/tests/exclusive_outgoing_edges.rs @@ -2,15 +2,20 @@ #[allow(clippy::panic_in_result_fn)] #[cfg(test)] mod test { - use si_events::ContentHash; + use si_events::{ulid::Ulid, ContentHash}; use crate::{ + action::prototype::ActionKind, workspace_snapshot::{ content_address::ContentAddress, graph::{detect_updates::Update, WorkspaceSnapshotGraphResult}, - node_weight::{traits::CorrectExclusiveOutgoingEdge, NodeWeight}, + node_weight::{ + traits::{CorrectExclusiveOutgoingEdge, CorrectTransforms}, + NodeWeight, + }, }, - EdgeWeight, EdgeWeightKind, EdgeWeightKindDiscriminants, WorkspaceSnapshotGraphV2, + EdgeWeight, EdgeWeightKind, EdgeWeightKindDiscriminants, NodeWeightDiscriminants, + WorkspaceSnapshotGraphV2, }; #[test] @@ -164,4 +169,178 @@ mod test { Ok(()) } + + #[test] + fn correct_exclusive_outgoing_action_edges() -> WorkspaceSnapshotGraphResult<()> { + let mut graph = WorkspaceSnapshotGraphV2::new()?; + + let action_id = graph.generate_ulid()?; + let prototype_1_id = graph.generate_ulid()?; + let prototype_2_id = graph.generate_ulid()?; + let component_1_id = graph.generate_ulid()?; + let component_2_id = graph.generate_ulid()?; + + let action = NodeWeight::new_action(Ulid::new().into(), action_id, action_id); + graph.add_node(action.clone())?; + + let prototype_1 = NodeWeight::new_action_prototype( + prototype_1_id, + prototype_1_id, + ActionKind::Create, + "create".into(), + None, + ); + + let prototype_2 = NodeWeight::new_action_prototype( + prototype_2_id, + prototype_2_id, + ActionKind::Create, + "create 2".into(), + None, + ); + + let component_1 = NodeWeight::new_component( + component_1_id, + component_1_id, + ContentHash::new(&component_1_id.inner().to_bytes()), + ); + + let component_2 = NodeWeight::new_component( + component_2_id, + component_2_id, + ContentHash::new(&component_2_id.inner().to_bytes()), + ); + + let prototype_1_idx = graph.add_node(prototype_1.clone())?; + + graph.add_edge( + graph.root(), + EdgeWeight::new(EdgeWeightKind::new_use()), + graph.get_node_index_by_id(action.id())?, + )?; + + graph.add_edge( + graph.get_node_index_by_id(action.id())?, + EdgeWeight::new(EdgeWeightKind::new_use()), + prototype_1_idx, + )?; + + let root_node = graph.get_node_weight(graph.root())?.to_owned(); + + let updates = vec![ + Update::NewNode { + node_weight: prototype_2.clone(), + }, + Update::NewEdge { + source: (&root_node).into(), + destination: (&prototype_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + ]; + let new_updates = action + .correct_transforms(&graph, updates.clone()) + .expect("correct transforms"); + // There are no exclusive edges here so we should not produce an update + assert_eq!( + new_updates, updates, + "no exclusive edges, therefore no new updates" + ); + let updates = vec![ + Update::NewNode { + node_weight: prototype_2.clone(), + }, + Update::NewEdge { + source: (&action).into(), + destination: (&prototype_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + ]; + let new_updates = action + .correct_transforms(&graph, updates.clone()) + .expect("correct transforms"); + let new_remove_edge = Update::RemoveEdge { + source: (&action).into(), + destination: (&prototype_1).into(), + edge_kind: EdgeWeightKindDiscriminants::Use, + }; + assert_eq!( + new_remove_edge, + new_updates + .last() + .expect("there should be a last one!") + .to_owned() + ); + + let updates = vec![ + Update::NewNode { + node_weight: component_1.clone(), + }, + Update::NewEdge { + source: (&action).into(), + destination: (&component_1).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + ]; + let new_updates = action + .correct_transforms(&graph, updates.clone()) + .expect("correct transforms"); + // There are no exclusive edges here so we should not produce an update + assert_eq!( + new_updates, updates, + "no conflict with the component edges, therefore no new updates" + ); + graph.perform_updates(&new_updates)?; + + let updates = vec![ + Update::NewNode { + node_weight: component_2.clone(), + }, + Update::NewNode { + node_weight: prototype_2.clone(), + }, + Update::NewEdge { + source: (&action).into(), + destination: (&component_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + Update::NewEdge { + source: (&action).into(), + destination: (&prototype_2).into(), + edge_weight: EdgeWeight::new(EdgeWeightKind::new_use()), + }, + ]; + + let mut new_updates = action + .correct_transforms(&graph, updates.clone()) + .expect("correct transforms"); + let new_remove_edge_prototype = Update::RemoveEdge { + source: (&action).into(), + destination: (&prototype_1).into(), + edge_kind: EdgeWeightKindDiscriminants::Use, + }; + let new_remove_edge_component = Update::RemoveEdge { + source: (&action).into(), + destination: (&component_1).into(), + edge_kind: EdgeWeightKindDiscriminants::Use, + }; + + assert_eq!(2, new_updates.len() - updates.len()); + + let new_update_1 = new_updates.pop().expect("should exist"); + let new_update_2 = new_updates.pop().expect("should exist"); + + for new_update in [new_update_1, new_update_2] { + assert!(matches!(new_update, Update::RemoveEdge { .. })); + if let Update::RemoveEdge { destination, .. } = &new_update { + if destination.node_weight_kind == NodeWeightDiscriminants::Component { + assert_eq!(new_update, new_remove_edge_component); + } else { + assert_eq!(new_update, new_remove_edge_prototype); + } + } else { + unreachable!("we already asserted this above"); + } + } + Ok(()) + } } diff --git a/lib/dal/src/workspace_snapshot/node_weight/action_node_weight.rs b/lib/dal/src/workspace_snapshot/node_weight/action_node_weight.rs index 0603cfe31b..c4da378b96 100644 --- a/lib/dal/src/workspace_snapshot/node_weight/action_node_weight.rs +++ b/lib/dal/src/workspace_snapshot/node_weight/action_node_weight.rs @@ -1,13 +1,21 @@ +use std::collections::HashSet; + +use petgraph::prelude::*; use serde::{Deserialize, Serialize}; use si_events::{merkle_tree_hash::MerkleTreeHash, ulid::Ulid, ContentHash}; use crate::{ action::ActionState, - workspace_snapshot::graph::{deprecated::v1::DeprecatedActionNodeWeightV1, LineageId}, - workspace_snapshot::node_weight::traits::CorrectTransforms, - ChangeSetId, EdgeWeightKindDiscriminants, + workspace_snapshot::{ + graph::{deprecated::v1::DeprecatedActionNodeWeightV1, detect_updates::Update, LineageId}, + node_weight::{traits::CorrectTransforms, NodeWeight}, + NodeId, NodeInformation, + }, + ChangeSetId, EdgeWeightKindDiscriminants, NodeWeightDiscriminants, WorkspaceSnapshotGraphV2, }; +use super::traits::CorrectTransformsResult; + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct ActionNodeWeight { pub id: Ulid, @@ -74,7 +82,7 @@ impl ActionNodeWeight { } pub const fn exclusive_outgoing_edges(&self) -> &[EdgeWeightKindDiscriminants] { - &[EdgeWeightKindDiscriminants::Use] + &[] } } @@ -90,4 +98,110 @@ impl From for ActionNodeWeight { } } -impl CorrectTransforms for ActionNodeWeight {} +impl From<&ActionNodeWeight> for NodeInformation { + fn from(value: &ActionNodeWeight) -> Self { + Self { + node_weight_kind: NodeWeightDiscriminants::Action, + id: value.id.into(), + } + } +} + +impl CorrectTransforms for ActionNodeWeight { + fn correct_transforms( + &self, + graph: &WorkspaceSnapshotGraphV2, + mut updates: Vec, + ) -> CorrectTransformsResult> { + // An action's Use edge should be exclusive for both the component and + // the prototype. The generic exclusive edge logic assumes there can be + // one and only one edge of the given kind, so we have to do a custom + // implementation here for actions, since they should have just one use + // edge to a component and one use edge to a prototype. This is simpler + // than rewriting all the graphs to have distinct edge kinds for action + // prototypes and/or the components the action is for. + + struct ActionUseTargets { + component: Option, + prototype: Option, + } + + let mut removal_set = HashSet::new(); + let mut new_action_use_targets = ActionUseTargets { + component: None, + prototype: None, + }; + + for update in &updates { + match update { + Update::NewEdge { + source, + destination, + edge_weight, + } if source.id == self.id().into() + && EdgeWeightKindDiscriminants::Use == edge_weight.kind().into() => + { + removal_set.remove(&destination.id); + match destination.node_weight_kind { + NodeWeightDiscriminants::ActionPrototype => { + new_action_use_targets.prototype = Some(destination.id); + } + NodeWeightDiscriminants::Component => { + new_action_use_targets.component = Some(destination.id); + } + // If there's a use to some other thing, ignore it. + // Maybe some more functionality was added. What we care + // about is component and prototype targets + _ => {} + } + } + Update::RemoveEdge { + source, + destination, + edge_kind, + } if source.id == self.id().into() + && EdgeWeightKindDiscriminants::Use == *edge_kind => + { + removal_set.insert(destination.id); + } + _ => {} + } + } + + if let Some(node_idx) = graph.get_node_index_by_id_opt(self.id) { + updates.extend( + graph + .edges_directed(node_idx, Outgoing) + .filter(|edge_ref| { + EdgeWeightKindDiscriminants::Use == edge_ref.weight().kind().into() + }) + .filter_map(|edge_ref| { + graph.get_node_weight_opt(edge_ref.target()).and_then( + |destination_weight| { + let should_remove = match destination_weight { + NodeWeight::ActionPrototype(_) => { + new_action_use_targets.prototype + } + NodeWeight::Component(_) => new_action_use_targets.component, + _ => None, + } + .map(|new_destination_id| { + new_destination_id != destination_weight.id().into() + && !removal_set.contains(&destination_weight.id().into()) + }) + .unwrap_or(false); + + should_remove.then_some(Update::RemoveEdge { + source: self.into(), + destination: destination_weight.into(), + edge_kind: EdgeWeightKindDiscriminants::Use, + }) + }, + ) + }), + ) + } + + Ok(updates) + } +} diff --git a/lib/dal/src/workspace_snapshot/node_weight/secret_node_weight.rs b/lib/dal/src/workspace_snapshot/node_weight/secret_node_weight.rs index 721309aaf7..cd2c47f3bf 100644 --- a/lib/dal/src/workspace_snapshot/node_weight/secret_node_weight.rs +++ b/lib/dal/src/workspace_snapshot/node_weight/secret_node_weight.rs @@ -100,7 +100,7 @@ impl SecretNodeWeight { } pub const fn exclusive_outgoing_edges(&self) -> &[EdgeWeightKindDiscriminants] { - &[EdgeWeightKindDiscriminants::Use] + &[] } } diff --git a/lib/dal/src/workspace_snapshot/node_weight/traits/correct_exclusive_outgoing_edge.rs b/lib/dal/src/workspace_snapshot/node_weight/traits/correct_exclusive_outgoing_edge.rs index 884d7fcb94..12fd5af1c9 100644 --- a/lib/dal/src/workspace_snapshot/node_weight/traits/correct_exclusive_outgoing_edge.rs +++ b/lib/dal/src/workspace_snapshot/node_weight/traits/correct_exclusive_outgoing_edge.rs @@ -78,9 +78,10 @@ where destination_weight.and_then(|weight| { let destination: NodeInformation = weight.into(); - // If this is not the last NewEdge for this kind, - // and net removals is < 1, then we need to remove - // this edge since it will still be in the graph + // If this is not the last NewEdge for this + // kind, and the edge will not already be + // removed, then we need to remove this edge + // since it will still be in the graph // otherwise. if destination.id != new_edge_destination_id && !removal_set.contains(&(edge_kind, destination.id))