diff --git a/python/tests/test_graphdb/test_graphdb.py b/python/tests/test_graphdb/test_graphdb.py index bed24790c..071803ab9 100644 --- a/python/tests/test_graphdb/test_graphdb.py +++ b/python/tests/test_graphdb/test_graphdb.py @@ -3047,6 +3047,16 @@ def test_create_node_graph_with_deletion(): assert "Node already exists" in str(excinfo.value) +def test_edge_layer_properties(): + g = Graph() + g.add_edge(1, "A", "B", properties={"greeting": "howdy"}, layer="layer 1") + g.add_edge(2, "A", "B", properties={"greeting": "hello"}, layer="layer 2") + g.add_edge(3, "A", "B", properties={"greeting": "ola"}, layer="layer 2") + g.add_edge(3, "A", "B", properties={"greeting": "namaste"}, layer="layer 3") + + assert g.edge("A", "B").properties == {"greeting": "namaste"} + + @fixture def datadir(tmpdir, request): filename = request.module.__file__ diff --git a/python/tests/test_repr.py b/python/tests/test_repr.py index 5978e214a..703aabc20 100644 --- a/python/tests/test_repr.py +++ b/python/tests/test_repr.py @@ -45,7 +45,7 @@ def test_layers_and_props(self): G = Graph() G.add_edge(1, "A", "B", layer="layer 1", properties={"greeting": "howdy"}) G.add_edge(2, "A", "B", layer="layer 2", properties={"greeting": "yo"}) - expected_out = "Edge(source=A, target=B, earliest_time=1, latest_time=2, properties={greeting: yo, greeting: yo}, layer(s)=[layer 1, layer 2])\n" + expected_out = "Edge(source=A, target=B, earliest_time=1, latest_time=2, properties={greeting: yo}, layer(s)=[layer 1, layer 2])\n" with patch("sys.stdout", new=StringIO()) as fake_out: print(G.edge("A", "B")) self.assertEqual(fake_out.getvalue(), expected_out) @@ -60,7 +60,7 @@ def layers_and_non_layers(self): G = Graph() G.add_edge(1, "A", "B", layer="layer 1", properties={"greeting": "howdy"}) G.add_edge(2, "A", "B", properties={"greeting": "yo"}) - expected_out = "Edge(source=A, target=B, earliest_time=1, latest_time=2, properties={greeting: yo, greeting: yo}, layer(s)=[_default, layer 1])\n" + expected_out = "Edge(source=A, target=B, earliest_time=1, latest_time=2, properties={greeting: yo}, layer(s)=[_default, layer 1])\n" with patch("sys.stdout", new=StringIO()) as fake_out: print(G.edge("A", "B")) self.assertEqual(fake_out.getvalue(), expected_out) @@ -72,7 +72,7 @@ def test_persistent_graph(self): G.delete_edge(5, "A", "B", layer="layer 1") G.add_edge(2, "A", "B", layer="layer 2", properties={"greeting": "yo"}) G.delete_edge(6, "A", "B", layer="layer 2") - expected_out = "Edge(source=A, target=B, earliest_time=1, latest_time=6, properties={greeting: yo, greeting: yo}, layer(s)=[layer 1, layer 2])\n" + expected_out = "Edge(source=A, target=B, earliest_time=1, latest_time=6, properties={greeting: yo}, layer(s)=[layer 1, layer 2])\n" with patch("sys.stdout", new=StringIO()) as fake_out: print(G.edge("A", "B")) self.assertEqual(fake_out.getvalue(), expected_out) diff --git a/raphtory/src/core/storage/raw_edges.rs b/raphtory/src/core/storage/raw_edges.rs index 84dbbc7c8..f3fae12a3 100644 --- a/raphtory/src/core/storage/raw_edges.rs +++ b/raphtory/src/core/storage/raw_edges.rs @@ -6,6 +6,7 @@ use crate::{ }, db::api::storage::graph::edges::edge_storage_ops::{EdgeStorageOps, MemEdge}, }; +use itertools::Itertools; use lock_api::ArcRwLockReadGuard; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use raphtory_api::core::{entities::EID, storage::timeindex::TimeIndexEntry}; @@ -292,7 +293,9 @@ impl<'a> EdgeRGuard<'a> { Box::new( self.guard .props_iter(self.offset) - .flat_map(|(_, layer)| layer.temporal_prop_ids()), + .map(|(_, layer)| layer.temporal_prop_ids()) + .kmerge() + .dedup(), ) } } diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index 3c4028e56..3b28576ee 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -779,4 +779,25 @@ mod test_materialize { g.add_edge(0, 1, 2, props_0.clone(), None).unwrap(); assert!(g.add_edge(1, 1, 2, props_1.clone(), None).is_err()); } + + #[test] + fn test_edge_layer_properties() { + let g = Graph::new(); + g.add_edge(1, "A", "B", [("greeting", "howdy")], Some("layer 1")) + .unwrap(); + g.add_edge(2, "A", "B", [("greeting", "ola")], Some("layer 2")) + .unwrap(); + g.add_edge(2, "A", "B", [("greeting", "hello")], Some("layer 2")) + .unwrap(); + g.add_edge(3, "A", "B", [("greeting", "namaste")], Some("layer 3")) + .unwrap(); + + let edge_ab = g.edge("A", "B").unwrap(); + let props = edge_ab + .properties() + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect::>(); + assert_eq!(props, vec![("greeting".to_string(), "namaste".to_string())]); + } } diff --git a/raphtory/src/python/types/macros/trait_impl/edgeviewops.rs b/raphtory/src/python/types/macros/trait_impl/edgeviewops.rs index 278777b82..34324e033 100644 --- a/raphtory/src/python/types/macros/trait_impl/edgeviewops.rs +++ b/raphtory/src/python/types/macros/trait_impl/edgeviewops.rs @@ -32,13 +32,14 @@ macro_rules! impl_edgeviewops { self.$field.nbr() } - /// Explodes an edge and returns all instances it had been updated as seperate edges + /// Explodes returns an edge object for each update within the original edge. fn explode( &self, ) -> <$base_type as $crate::db::api::view::EdgeViewOps<'static>>::Exploded { self.$field.explode() } + /// Explode layers returns an edge object for each layer within the original edge. These new edge object contains only updates from respective layers. fn explode_layers( &self, ) -> <$base_type as $crate::db::api::view::EdgeViewOps<'static>>::Exploded {