From 335186dfe57d812f0c70ef5c132ce60d6e6c384e Mon Sep 17 00:00:00 2001 From: Shivam Kapoor <4599890+iamsmkr@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:03:37 +0000 Subject: [PATCH 1/6] fix issue with secondary index not overwriting the prop --- raphtory/src/core/entities/properties/tcell.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/raphtory/src/core/entities/properties/tcell.rs b/raphtory/src/core/entities/properties/tcell.rs index d5eafdf3f1..bee6200d0e 100644 --- a/raphtory/src/core/entities/properties/tcell.rs +++ b/raphtory/src/core/entities/properties/tcell.rs @@ -27,7 +27,7 @@ impl TCell { TCell::Empty => { *self = TCell::TCell1(t, value); } - TCell::TCell1(t0, _) => { + TCell::TCell1(t0, v) => { if &t != t0 { if let TCell::TCell1(t0, value0) = std::mem::take(self) { let mut svm = SVM::new(); @@ -35,6 +35,8 @@ impl TCell { svm.insert(t0, value0); *self = TCell::TCellCap(svm) } + } else { + *v = value } } TCell::TCellCap(svm) => { From 6e206d8d53992ef42af048160b111e4f5559a7a7 Mon Sep 17 00:00:00 2001 From: Shivam Kapoor <4599890+iamsmkr@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:04:15 +0000 Subject: [PATCH 2/6] fix the issue with time secondary index tuple not working for properties addition --- raphtory/src/db/api/mutation/property_addition_ops.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raphtory/src/db/api/mutation/property_addition_ops.rs b/raphtory/src/db/api/mutation/property_addition_ops.rs index 7aec6632f6..8160244a54 100644 --- a/raphtory/src/db/api/mutation/property_addition_ops.rs +++ b/raphtory/src/db/api/mutation/property_addition_ops.rs @@ -1,5 +1,5 @@ use crate::{ - core::utils::{errors::GraphError, time::TryIntoTime}, + core::utils::{errors::GraphError}, db::api::mutation::{ internal::{InternalAdditionOps, InternalPropertyAdditionOps}, TryIntoInputTime, @@ -9,7 +9,7 @@ use crate::{ use super::{time_from_input, CollectProperties}; pub trait PropertyAdditionOps { - fn add_properties( + fn add_properties( &self, t: T, props: PI, From e59fb9429f8b2b289b6d9a7871f8ae506e2384a6 Mon Sep 17 00:00:00 2001 From: Shivam Kapoor <4599890+iamsmkr@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:05:01 +0000 Subject: [PATCH 3/6] add tests for add node secondary index --- raphtory/src/db/api/view/graph.rs | 94 +++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index 3b28576ee0..673fca180d 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -64,14 +64,14 @@ pub trait GraphViewOps<'graph>: BoxableGraphView + Sized + Clone + 'graph { /// Graph - Returns clone of the graph fn materialize(&self) -> Result; - fn subgraph, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph; + fn subgraph, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph; - fn subgraph_node_types, V: Borrow>( + fn subgraph_node_types, V: Borrow>( &self, nodes_types: I, ) -> TypeFilteredSubgraph; - fn exclude_nodes, V: AsNodeRef>( + fn exclude_nodes, V: AsNodeRef>( &self, nodes: I, ) -> NodeSubgraph; @@ -336,7 +336,7 @@ impl<'graph, G: BoxableGraphView + Sized + Clone + 'graph> GraphViewOps<'graph> Ok(self.new_base_graph(g.into())) } - fn subgraph, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { + fn subgraph, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { let _layer_ids = self.layer_ids(); let nodes: FxHashSet = nodes .into_iter() @@ -345,7 +345,7 @@ impl<'graph, G: BoxableGraphView + Sized + Clone + 'graph> GraphViewOps<'graph> NodeSubgraph::new(self.clone(), nodes) } - fn subgraph_node_types, V: Borrow>( + fn subgraph_node_types, V: Borrow>( &self, nodes_types: I, ) -> TypeFilteredSubgraph { @@ -357,7 +357,7 @@ impl<'graph, G: BoxableGraphView + Sized + Clone + 'graph> GraphViewOps<'graph> TypeFilteredSubgraph::new(self.clone(), r) } - fn exclude_nodes, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { + fn exclude_nodes, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { let _layer_ids = self.layer_ids(); let nodes_to_exclude: FxHashSet = nodes @@ -604,8 +604,90 @@ impl<'graph, G: GraphViewOps<'graph> + 'graph> OneHopFilter<'graph> for G { #[cfg(test)] mod test_exploded_edges { + use itertools::Itertools; use crate::{prelude::*, test_storage}; + #[test] + fn test_properties_ordered_by_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_node(0, 0, [("prop", "1")], None).unwrap(); + graph.add_node(0, 0, [("prop", "2")], None).unwrap(); + graph.add_node(0, 0, [("prop", "3")], None).unwrap(); + + let props = graph.node("0") + .map(|node| { + node.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }).unwrap(); + + assert_eq!(props, vec!["1".to_string(), "2".to_string(), "3".to_string()]); + } + + #[test] + fn test_properties_ordered_by_custom_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_node((0, 3), 0, [("prop", "1")], None).unwrap(); + graph.add_node((0, 2), 0, [("prop", "2")], None).unwrap(); + graph.add_node((0, 1), 0, [("prop", "3")], None).unwrap(); + + let props = graph.node("0") + .map(|node| { + node.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }).unwrap(); + + assert_eq!(props, vec!["3".to_string(), "2".to_string(), "1".to_string()]); + } + + #[test] + fn test_properties_overwritten_for_same_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_node((0, 1), 0, [("prop", "1")], None).unwrap(); + graph.add_node((0, 1), 0, [("prop", "2")], None).unwrap(); + graph.add_node((0, 1), 0, [("prop", "3")], None).unwrap(); + + let props = graph.node(0) + .map(|node| { + node.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }).unwrap(); + + assert_eq!(props, vec!["3".to_string()]); + + let graph: Graph = Graph::new(); + graph.add_node((0, 1), 0, [("prop", "1")], None).unwrap(); + graph.add_node((0, 2), 0, [("prop", "2")], None).unwrap(); + graph.add_node((0, 2), 0, [("prop", "3")], None).unwrap(); + + let props = graph.node(0) + .map(|node| { + node.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }).unwrap(); + + assert_eq!(props, vec!["1".to_string(), "3".to_string()]); + } + #[test] fn test_exploded_edges() { let graph: Graph = Graph::new(); From fdc31d3efad2a17510f1209de1b43897d142fcdb Mon Sep 17 00:00:00 2001 From: Shivam Kapoor <4599890+iamsmkr@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:27:00 +0000 Subject: [PATCH 4/6] add more tests --- .../db/api/mutation/property_addition_ops.rs | 2 +- raphtory/src/db/api/view/graph.rs | 485 +++++++++++++++++- 2 files changed, 460 insertions(+), 27 deletions(-) diff --git a/raphtory/src/db/api/mutation/property_addition_ops.rs b/raphtory/src/db/api/mutation/property_addition_ops.rs index 8160244a54..930a8074e4 100644 --- a/raphtory/src/db/api/mutation/property_addition_ops.rs +++ b/raphtory/src/db/api/mutation/property_addition_ops.rs @@ -1,5 +1,5 @@ use crate::{ - core::utils::{errors::GraphError}, + core::utils::errors::GraphError, db::api::mutation::{ internal::{InternalAdditionOps, InternalPropertyAdditionOps}, TryIntoInputTime, diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index 673fca180d..48c867f410 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -64,14 +64,14 @@ pub trait GraphViewOps<'graph>: BoxableGraphView + Sized + Clone + 'graph { /// Graph - Returns clone of the graph fn materialize(&self) -> Result; - fn subgraph, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph; + fn subgraph, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph; - fn subgraph_node_types, V: Borrow>( + fn subgraph_node_types, V: Borrow>( &self, nodes_types: I, ) -> TypeFilteredSubgraph; - fn exclude_nodes, V: AsNodeRef>( + fn exclude_nodes, V: AsNodeRef>( &self, nodes: I, ) -> NodeSubgraph; @@ -336,7 +336,7 @@ impl<'graph, G: BoxableGraphView + Sized + Clone + 'graph> GraphViewOps<'graph> Ok(self.new_base_graph(g.into())) } - fn subgraph, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { + fn subgraph, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { let _layer_ids = self.layer_ids(); let nodes: FxHashSet = nodes .into_iter() @@ -345,7 +345,7 @@ impl<'graph, G: BoxableGraphView + Sized + Clone + 'graph> GraphViewOps<'graph> NodeSubgraph::new(self.clone(), nodes) } - fn subgraph_node_types, V: Borrow>( + fn subgraph_node_types, V: Borrow>( &self, nodes_types: I, ) -> TypeFilteredSubgraph { @@ -357,7 +357,7 @@ impl<'graph, G: BoxableGraphView + Sized + Clone + 'graph> GraphViewOps<'graph> TypeFilteredSubgraph::new(self.clone(), r) } - fn exclude_nodes, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { + fn exclude_nodes, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { let _layer_ids = self.layer_ids(); let nodes_to_exclude: FxHashSet = nodes @@ -604,17 +604,18 @@ impl<'graph, G: GraphViewOps<'graph> + 'graph> OneHopFilter<'graph> for G { #[cfg(test)] mod test_exploded_edges { - use itertools::Itertools; use crate::{prelude::*, test_storage}; + use itertools::Itertools; #[test] - fn test_properties_ordered_by_secondary_index() { + fn test_add_node_properties_ordered_by_secondary_index() { let graph: Graph = Graph::new(); - graph.add_node(0, 0, [("prop", "1")], None).unwrap(); - graph.add_node(0, 0, [("prop", "2")], None).unwrap(); - graph.add_node(0, 0, [("prop", "3")], None).unwrap(); + graph.add_node((0, 3), 0, [("prop", "1")], None).unwrap(); + graph.add_node((0, 2), 0, [("prop", "2")], None).unwrap(); + graph.add_node((0, 1), 0, [("prop", "3")], None).unwrap(); - let props = graph.node("0") + let props = graph + .node("0") .map(|node| { node.properties() .temporal() @@ -623,19 +624,67 @@ mod test_exploded_edges { .values() .map(|x| x.to_string()) .collect_vec() - }).unwrap(); + }) + .unwrap(); - assert_eq!(props, vec!["1".to_string(), "2".to_string(), "3".to_string()]); + assert_eq!( + props, + vec!["3".to_string(), "2".to_string(), "1".to_string()] + ); } #[test] - fn test_properties_ordered_by_custom_secondary_index() { + fn test_add_node_properties_overwritten_for_same_secondary_index() { let graph: Graph = Graph::new(); - graph.add_node((0, 3), 0, [("prop", "1")], None).unwrap(); + graph.add_node((0, 1), 0, [("prop", "1")], None).unwrap(); + graph.add_node((0, 1), 0, [("prop", "2")], None).unwrap(); + graph.add_node((0, 1), 0, [("prop", "3")], None).unwrap(); + + let props = graph + .node(0) + .map(|node| { + node.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); + + assert_eq!(props, vec!["3".to_string()]); + + let graph: Graph = Graph::new(); + graph.add_node((0, 1), 0, [("prop", "1")], None).unwrap(); + graph.add_node((0, 2), 0, [("prop", "2")], None).unwrap(); + graph.add_node((0, 2), 0, [("prop", "3")], None).unwrap(); + + let props = graph + .node(0) + .map(|node| { + node.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); + + assert_eq!(props, vec!["1".to_string(), "3".to_string()]); + } + + #[test] + fn test_create_node_properties_ordered_by_secondary_index() { + let graph: Graph = Graph::new(); + graph.create_node((0, 3), 0, [("prop", "1")], None).unwrap(); graph.add_node((0, 2), 0, [("prop", "2")], None).unwrap(); graph.add_node((0, 1), 0, [("prop", "3")], None).unwrap(); - let props = graph.node("0") + let props = graph + .node("0") .map(|node| { node.properties() .temporal() @@ -644,19 +693,24 @@ mod test_exploded_edges { .values() .map(|x| x.to_string()) .collect_vec() - }).unwrap(); + }) + .unwrap(); - assert_eq!(props, vec!["3".to_string(), "2".to_string(), "1".to_string()]); + assert_eq!( + props, + vec!["3".to_string(), "2".to_string(), "1".to_string()] + ); } #[test] - fn test_properties_overwritten_for_same_secondary_index() { + fn test_create_node_properties_overwritten_for_same_secondary_index() { let graph: Graph = Graph::new(); - graph.add_node((0, 1), 0, [("prop", "1")], None).unwrap(); + graph.create_node((0, 1), 0, [("prop", "1")], None).unwrap(); graph.add_node((0, 1), 0, [("prop", "2")], None).unwrap(); graph.add_node((0, 1), 0, [("prop", "3")], None).unwrap(); - let props = graph.node(0) + let props = graph + .node(0) .map(|node| { node.properties() .temporal() @@ -665,16 +719,195 @@ mod test_exploded_edges { .values() .map(|x| x.to_string()) .collect_vec() - }).unwrap(); + }) + .unwrap(); assert_eq!(props, vec!["3".to_string()]); let graph: Graph = Graph::new(); - graph.add_node((0, 1), 0, [("prop", "1")], None).unwrap(); + graph.create_node((0, 1), 0, [("prop", "1")], None).unwrap(); graph.add_node((0, 2), 0, [("prop", "2")], None).unwrap(); graph.add_node((0, 2), 0, [("prop", "3")], None).unwrap(); - let props = graph.node(0) + let props = graph + .node(0) + .map(|node| { + node.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); + + assert_eq!(props, vec!["1".to_string(), "3".to_string()]); + } + + #[test] + fn test_add_edge_properties_ordered_by_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_edge((0, 3), 0, 1, [("prop", "1")], None).unwrap(); + graph.add_edge((0, 2), 0, 1, [("prop", "2")], None).unwrap(); + graph.add_edge((0, 1), 0, 1, [("prop", "3")], None).unwrap(); + + let props = graph + .edge(0, 1) + .map(|edge| { + edge.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); + + assert_eq!( + props, + vec!["3".to_string(), "2".to_string(), "1".to_string()] + ); + } + + #[test] + fn test_add_edge_properties_overwritten_for_same_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_edge((0, 1), 0, 1, [("prop", "1")], None).unwrap(); + graph.add_edge((0, 1), 0, 1, [("prop", "2")], None).unwrap(); + graph.add_edge((0, 1), 0, 1, [("prop", "3")], None).unwrap(); + + let props = graph + .edge(0, 1) + .map(|edge| { + edge.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); + + assert_eq!(props, vec!["3".to_string()]); + + let graph: Graph = Graph::new(); + graph.add_edge((0, 1), 0, 1, [("prop", "1")], None).unwrap(); + graph.add_edge((0, 2), 0, 1, [("prop", "2")], None).unwrap(); + graph.add_edge((0, 2), 0, 1, [("prop", "3")], None).unwrap(); + + let props = graph + .edge(0, 1) + .map(|edge| { + edge.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); + + assert_eq!(props, vec!["1".to_string(), "3".to_string()]); + } + + #[test] + fn test_add_properties_properties_ordered_by_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + + graph.add_properties((0, 3), [("prop", "1")]).unwrap(); + graph.add_properties((0, 2), [("prop", "2")]).unwrap(); + graph.add_properties((0, 1), [("prop", "3")]).unwrap(); + + let props = graph + .properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec(); + + assert_eq!( + props, + vec!["3".to_string(), "2".to_string(), "1".to_string()] + ); + } + + #[test] + fn test_add_properties_properties_overwritten_for_same_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_edge((0, 1), 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge((0, 1), 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge((0, 1), 0, 1, NO_PROPS, None).unwrap(); + + graph.add_properties((0, 1), [("prop", "1")]).unwrap(); + graph.add_properties((0, 1), [("prop", "2")]).unwrap(); + graph.add_properties((0, 1), [("prop", "3")]).unwrap(); + + let props = graph + .properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec(); + + assert_eq!(props, vec!["3".to_string()]); + + let graph: Graph = Graph::new(); + graph.add_edge((0, 1), 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge((0, 2), 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge((0, 2), 0, 1, NO_PROPS, None).unwrap(); + + graph.add_properties((0, 1), [("prop", "1")]).unwrap(); + graph.add_properties((0, 2), [("prop", "2")]).unwrap(); + graph.add_properties((0, 2), [("prop", "3")]).unwrap(); + + let props = graph + .properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec(); + + assert_eq!(props, vec!["1".to_string(), "3".to_string()]); + } + + #[test] + fn test_node_add_updates_properties_ordered_by_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + + graph + .node(0) + .unwrap() + .add_updates((0, 3), [("prop", "1")]) + .unwrap(); + graph + .node(0) + .unwrap() + .add_updates((0, 2), [("prop", "2")]) + .unwrap(); + graph + .node(0) + .unwrap() + .add_updates((0, 1), [("prop", "3")]) + .unwrap(); + + let props = graph + .node("0") .map(|node| { node.properties() .temporal() @@ -683,7 +916,207 @@ mod test_exploded_edges { .values() .map(|x| x.to_string()) .collect_vec() - }).unwrap(); + }) + .unwrap(); + + assert_eq!( + props, + vec!["3".to_string(), "2".to_string(), "1".to_string()] + ); + } + + #[test] + fn test_node_add_updates_properties_overwritten_for_same_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + + graph + .node(0) + .unwrap() + .add_updates((0, 1), [("prop", "1")]) + .unwrap(); + graph + .node(0) + .unwrap() + .add_updates((0, 1), [("prop", "2")]) + .unwrap(); + graph + .node(0) + .unwrap() + .add_updates((0, 1), [("prop", "3")]) + .unwrap(); + + let props = graph + .node("0") + .map(|node| { + node.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); + + assert_eq!(props, vec!["3".to_string()]); + + let graph: Graph = Graph::new(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + graph.add_node(0, 0, NO_PROPS, None).unwrap(); + + graph + .node(0) + .unwrap() + .add_updates((0, 1), [("prop", "1")]) + .unwrap(); + graph + .node(0) + .unwrap() + .add_updates((0, 2), [("prop", "2")]) + .unwrap(); + graph + .node(0) + .unwrap() + .add_updates((0, 2), [("prop", "3")]) + .unwrap(); + + let props = graph + .node("0") + .map(|node| { + node.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); + + assert_eq!(props, vec!["1".to_string(), "3".to_string()]); + } + + #[test] + fn test_edge_add_updates_properties_ordered_by_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + + graph + .edge(0, 1) + .unwrap() + .add_updates((0, 3), [("prop", "1")], None) + .unwrap(); + graph + .edge(0, 1) + .unwrap() + .add_updates((0, 2), [("prop", "2")], None) + .unwrap(); + graph + .edge(0, 1) + .unwrap() + .add_updates((0, 1), [("prop", "3")], None) + .unwrap(); + + let props = graph + .edge(0, 1) + .map(|edge| { + edge.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); + + assert_eq!( + props, + vec!["3".to_string(), "2".to_string(), "1".to_string()] + ); + } + + #[test] + fn test_edge_add_updates_properties_overwritten_for_same_secondary_index() { + let graph: Graph = Graph::new(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + + graph + .edge(0, 1) + .unwrap() + .add_updates((0, 1), [("prop", "1")], None) + .unwrap(); + graph + .edge(0, 1) + .unwrap() + .add_updates((0, 1), [("prop", "2")], None) + .unwrap(); + graph + .edge(0, 1) + .unwrap() + .add_updates((0, 1), [("prop", "3")], None) + .unwrap(); + + let props = graph + .edge(0, 1) + .map(|edge| { + edge.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); + + assert_eq!(props, vec!["3".to_string()]); + + let graph: Graph = Graph::new(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); + + graph + .edge(0, 1) + .unwrap() + .add_updates((0, 1), [("prop", "1")], None) + .unwrap(); + graph + .edge(0, 1) + .unwrap() + .add_updates((0, 2), [("prop", "2")], None) + .unwrap(); + graph + .edge(0, 1) + .unwrap() + .add_updates((0, 2), [("prop", "3")], None) + .unwrap(); + + let props = graph + .edge(0, 1) + .map(|edge| { + edge.properties() + .temporal() + .get("prop") + .unwrap() + .values() + .map(|x| x.to_string()) + .collect_vec() + }) + .unwrap(); assert_eq!(props, vec!["1".to_string(), "3".to_string()]); } From fbebc01b1456490b705b4b9eb3de90facbae49be Mon Sep 17 00:00:00 2001 From: Shivam Kapoor <4599890+iamsmkr@users.noreply.github.com> Date: Thu, 28 Nov 2024 13:58:43 +0000 Subject: [PATCH 5/6] impl py secondary index --- raphtory/src/python/graph/edge.rs | 27 +++++-- raphtory/src/python/graph/graph.rs | 77 +++++++++++++++---- .../src/python/graph/graph_with_deletions.rs | 77 +++++++++++++++---- raphtory/src/python/graph/node.rs | 21 ++++- 4 files changed, 165 insertions(+), 37 deletions(-) diff --git a/raphtory/src/python/graph/edge.rs b/raphtory/src/python/graph/edge.rs index 277b528375..dd280eaf1b 100644 --- a/raphtory/src/python/graph/edge.rs +++ b/raphtory/src/python/graph/edge.rs @@ -385,18 +385,33 @@ impl PyMutableEdge { /// This function allows for the addition of property updates to an edge within the graph. The updates are time-stamped, meaning they are applied at the specified time. /// /// Parameters: - /// t (TimeInput): The timestamp at which the updates should be applied. - /// properties (PropInput, optional): A dictionary of properties to update. - /// layer (str, optional): The layer you want these properties to be added on to. - #[pyo3(signature = (t, properties=None, layer=None))] + /// t (TimeInput): The timestamp at which the updates should be applied. + /// properties (PropInput, optional): A dictionary of properties to update. + /// layer (str, optional): The layer you want these properties to be added on to. + /// secondary_index (int, optional): The optional integer which will be used as a secondary index + /// + /// Returns: + /// None: This function does not return a value, if the operation is successful. + /// + /// Raises: + /// GraphError: If the operation fails. + #[pyo3(signature = (t, properties=None, layer=None, secondary_index=None))] fn add_updates( &self, t: PyTime, properties: Option>, layer: Option<&str>, + secondary_index: Option, ) -> Result<(), GraphError> { - self.edge - .add_updates(t, properties.unwrap_or_default(), layer) + match secondary_index { + None => self + .edge + .add_updates(t, properties.unwrap_or_default(), layer), + Some(secondary_index) => { + self.edge + .add_updates((t, secondary_index), properties.unwrap_or_default(), layer) + } + } } /// Mark the edge as deleted at the specified time. diff --git a/raphtory/src/python/graph/graph.rs b/raphtory/src/python/graph/graph.rs index d7b7495c7b..9f0de6f68e 100644 --- a/raphtory/src/python/graph/graph.rs +++ b/raphtory/src/python/graph/graph.rs @@ -140,7 +140,7 @@ impl PyGraphEncoder { #[pymethods] impl PyGraph { #[new] - #[pyo3(signature=(num_shards=None))] + #[pyo3(signature = (num_shards = None))] pub fn py_new(num_shards: Option) -> (Self, PyGraphView) { let graph = match num_shards { None => Graph::new(), @@ -177,22 +177,35 @@ impl PyGraph { /// id (str|int): The id of the node. /// properties (PropInput, optional): The properties of the node. /// node_type (str, optional): The optional string which will be used as a node type + /// secondary_index (int, optional): The optional integer which will be used as a secondary index /// /// Returns: /// MutableNode: The added node. /// /// Raises: /// GraphError: If the operation fails. - #[pyo3(signature = (timestamp, id, properties = None, node_type = None))] + #[pyo3( + signature = (timestamp, id, properties = None, node_type = None, secondary_index = None) + )] pub fn add_node( &self, timestamp: PyTime, id: GID, properties: Option>, node_type: Option<&str>, + secondary_index: Option, ) -> Result, GraphError> { - self.graph - .add_node(timestamp, id, properties.unwrap_or_default(), node_type) + match secondary_index { + None => self + .graph + .add_node(timestamp, id, properties.unwrap_or_default(), node_type), + Some(secondary_index) => self.graph.add_node( + (timestamp, secondary_index), + id, + properties.unwrap_or_default(), + node_type, + ), + } } /// Creates a new node with the given id and properties to the graph. It fails if the node already exists. @@ -202,22 +215,34 @@ impl PyGraph { /// id (str|int): The id of the node. /// properties (PropInput, optional): The properties of the node. /// node_type (str, optional): The optional string which will be used as a node type + /// secondary_index (int, optional): The optional integer which will be used as a secondary index /// /// Returns: /// MutableNode: The created node. /// /// Raises: /// GraphError: If the operation fails. - #[pyo3(signature = (timestamp, id, properties = None, node_type = None))] + #[pyo3(signature = (timestamp, id, properties = None, node_type = None, secondary_index = None))] pub fn create_node( &self, timestamp: PyTime, id: GID, properties: Option>, node_type: Option<&str>, + secondary_index: Option, ) -> Result, GraphError> { - self.graph - .create_node(timestamp, id, properties.unwrap_or_default(), node_type) + match secondary_index { + None => { + self.graph + .create_node(timestamp, id, properties.unwrap_or_default(), node_type) + } + Some(secondary_index) => self.graph.create_node( + (timestamp, secondary_index), + id, + properties.unwrap_or_default(), + node_type, + ), + } } /// Adds properties to the graph. @@ -225,18 +250,26 @@ impl PyGraph { /// Arguments: /// timestamp (TimeInput): The timestamp of the temporal property. /// properties (PropInput): The temporal properties of the graph. + /// secondary_index (int, optional): The optional integer which will be used as a secondary index /// /// Returns: /// None: This function does not return a value, if the operation is successful. /// /// Raises: /// GraphError: If the operation fails. + #[pyo3(signature = (timestamp, properties, secondary_index = None))] pub fn add_property( &self, timestamp: PyTime, properties: HashMap, + secondary_index: Option, ) -> Result<(), GraphError> { - self.graph.add_properties(timestamp, properties) + match secondary_index { + None => self.graph.add_properties(timestamp, properties), + Some(secondary_index) => self + .graph + .add_properties((timestamp, secondary_index), properties), + } } /// Adds static properties to the graph. @@ -281,13 +314,14 @@ impl PyGraph { /// dst (str|int): The id of the destination node. /// properties (PropInput, optional): The properties of the edge, as a dict of string and properties. /// layer (str, optional): The layer of the edge. + /// secondary_index (int, optional): The optional integer which will be used as a secondary index /// /// Returns: /// MutableEdge: The added edge. /// /// Raises: /// GraphError: If the operation fails. - #[pyo3(signature = (timestamp, src, dst, properties = None, layer = None))] + #[pyo3(signature = (timestamp, src, dst, properties = None, layer = None, secondary_index = None))] pub fn add_edge( &self, timestamp: PyTime, @@ -295,9 +329,20 @@ impl PyGraph { dst: GID, properties: Option>, layer: Option<&str>, + secondary_index: Option, ) -> Result, GraphError> { - self.graph - .add_edge(timestamp, src, dst, properties.unwrap_or_default(), layer) + match secondary_index { + None => self + .graph + .add_edge(timestamp, src, dst, properties.unwrap_or_default(), layer), + Some(secondary_index) => self.graph.add_edge( + (timestamp, secondary_index), + src, + dst, + properties.unwrap_or_default(), + layer, + ), + } } /// Import a single node into the graph. @@ -557,7 +602,7 @@ impl PyGraph { /// Raises: /// GraphError: If the operation fails. #[pyo3( - signature = (df,time, id, node_type = None, node_type_col = None, properties = None, constant_properties = None, shared_constant_properties = None) + signature = (df, time, id, node_type = None, node_type_col = None, properties = None, constant_properties = None, shared_constant_properties = None) )] fn load_nodes_from_pandas<'py>( &self, @@ -744,7 +789,9 @@ impl PyGraph { /// /// Raises: /// GraphError: If the operation fails. - #[pyo3(signature = (df, id, node_type=None, node_type_col=None, constant_properties = None, shared_constant_properties = None))] + #[pyo3( + signature = (df, id, node_type = None, node_type_col = None, constant_properties = None, shared_constant_properties = None) + )] fn load_node_props_from_pandas( &self, df: &Bound, @@ -781,7 +828,9 @@ impl PyGraph { /// /// Raises: /// GraphError: If the operation fails. - #[pyo3(signature = (parquet_path, id, node_type=None,node_type_col=None, constant_properties = None, shared_constant_properties = None))] + #[pyo3( + signature = (parquet_path, id, node_type = None, node_type_col = None, constant_properties = None, shared_constant_properties = None) + )] fn load_node_props_from_parquet( &self, parquet_path: PathBuf, diff --git a/raphtory/src/python/graph/graph_with_deletions.rs b/raphtory/src/python/graph/graph_with_deletions.rs index 1ea4aeb186..36dcee8529 100644 --- a/raphtory/src/python/graph/graph_with_deletions.rs +++ b/raphtory/src/python/graph/graph_with_deletions.rs @@ -116,22 +116,33 @@ impl PyPersistentGraph { /// id (str | int): The id of the node. /// properties (dict): The properties of the node. /// node_type (str) : The optional string which will be used as a node type + /// secondary_index (int, optional): The optional integer which will be used as a secondary index /// /// Returns: /// None: This function does not return a value, if the operation is successful. /// /// Raises: /// GraphError: If the operation fails. - #[pyo3(signature = (timestamp, id, properties = None, node_type = None))] + #[pyo3(signature = (timestamp, id, properties = None, node_type = None, secondary_index = None))] pub fn add_node( &self, timestamp: PyTime, id: GID, properties: Option>, node_type: Option<&str>, + secondary_index: Option, ) -> Result, GraphError> { - self.graph - .add_node(timestamp, id, properties.unwrap_or_default(), node_type) + match secondary_index { + None => self + .graph + .add_node(timestamp, id, properties.unwrap_or_default(), node_type), + Some(secondary_index) => self.graph.add_node( + (timestamp, secondary_index), + id, + properties.unwrap_or_default(), + node_type, + ), + } } /// Creates a new node with the given id and properties to the graph. It fails if the node already exists. @@ -141,22 +152,34 @@ impl PyPersistentGraph { /// id (str | int): The id of the node. /// properties (dict): The properties of the node. /// node_type (str) : The optional string which will be used as a node type + /// secondary_index (int, optional): The optional integer which will be used as a secondary index /// /// Returns: /// MutableNode /// /// Raises: /// GraphError: If the operation fails. - #[pyo3(signature = (timestamp, id, properties = None, node_type = None))] + #[pyo3(signature = (timestamp, id, properties = None, node_type = None, secondary_index = None))] pub fn create_node( &self, timestamp: PyTime, id: GID, properties: Option>, node_type: Option<&str>, + secondary_index: Option, ) -> Result, GraphError> { - self.graph - .create_node(timestamp, id, properties.unwrap_or_default(), node_type) + match secondary_index { + None => { + self.graph + .create_node(timestamp, id, properties.unwrap_or_default(), node_type) + } + Some(secondary_index) => self.graph.create_node( + (timestamp, secondary_index), + id, + properties.unwrap_or_default(), + node_type, + ), + } } /// Adds properties to the graph. @@ -164,18 +187,26 @@ impl PyPersistentGraph { /// Arguments: /// timestamp (TimeInput): The timestamp of the temporal property. /// properties (dict): The temporal properties of the graph. + /// secondary_index (int, optional): The optional integer which will be used as a secondary index /// /// Returns: /// None: This function does not return a value, if the operation is successful. /// /// Raises: /// GraphError: If the operation fails. - pub fn add_property( + #[pyo3(signature = (timestamp, properties, secondary_index = None))] + pub fn add_properties( &self, timestamp: PyTime, properties: HashMap, + secondary_index: Option, ) -> Result<(), GraphError> { - self.graph.add_properties(timestamp, properties) + match secondary_index { + None => self.graph.add_properties(timestamp, properties), + Some(secondary_index) => self + .graph + .add_properties((timestamp, secondary_index), properties), + } } /// Adds static properties to the graph. @@ -220,13 +251,14 @@ impl PyPersistentGraph { /// dst (str | int): The id of the destination node. /// properties (dict): The properties of the edge, as a dict of string and properties /// layer (str): The layer of the edge. + /// secondary_index (int, optional): The optional integer which will be used as a secondary index /// /// Returns: /// None: This function does not return a value, if the operation is successful. /// /// Raises: /// GraphError: If the operation fails. - #[pyo3(signature = (timestamp, src, dst, properties = None, layer = None))] + #[pyo3(signature = (timestamp, src, dst, properties = None, layer = None, secondary_index = None))] pub fn add_edge( &self, timestamp: PyTime, @@ -234,9 +266,20 @@ impl PyPersistentGraph { dst: GID, properties: Option>, layer: Option<&str>, + secondary_index: Option, ) -> Result, GraphError> { - self.graph - .add_edge(timestamp, src, dst, properties.unwrap_or_default(), layer) + match secondary_index { + None => self + .graph + .add_edge(timestamp, src, dst, properties.unwrap_or_default(), layer), + Some(secondary_index) => self.graph.add_edge( + (timestamp, secondary_index), + src, + dst, + properties.unwrap_or_default(), + layer, + ), + } } /// Deletes an edge given the timestamp, src and dst nodes and layer (optional) @@ -246,21 +289,29 @@ impl PyPersistentGraph { /// src (str | int): The id of the source node. /// dst (str | int): The id of the destination node. /// layer (str): The layer of the edge. (optional) + /// secondary_index (int, optional): The optional integer which will be used as a secondary index /// /// Returns: /// The deleted edge /// /// Raises: /// GraphError: If the operation fails. - #[pyo3(signature = (timestamp, src, dst, layer=None))] + #[pyo3(signature = (timestamp, src, dst, layer=None, secondary_index = None))] pub fn delete_edge( &self, timestamp: PyTime, src: GID, dst: GID, layer: Option<&str>, + secondary_index: Option, ) -> Result, GraphError> { - self.graph.delete_edge(timestamp, src, dst, layer) + match secondary_index { + None => self.graph.delete_edge(timestamp, src, dst, layer), + Some(secondary_index) => { + self.graph + .delete_edge((timestamp, secondary_index), src, dst, layer) + } + } } //FIXME: This is reimplemented here to get mutable views. If we switch the underlying graph to enum dispatch, this won't be necessary! diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index 9bd3201997..f7cc7aa16a 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -371,15 +371,28 @@ impl PyMutableNode { /// This function allows for the addition of property updates to a node within the graph. The updates are time-stamped, meaning they are applied at the specified time. /// /// Parameters: - /// t (TimeInput): The timestamp at which the updates should be applied. - /// properties (PropInput): A dictionary of properties to update. Each key is a string representing the property name, and each value is of type Prop representing the property value. If None, no properties are updated. - #[pyo3(signature = (t, properties=None))] + /// t (TimeInput): The timestamp at which the updates should be applied. + /// properties (PropInput): A dictionary of properties to update. Each key is a string representing the property name, and each value is of type Prop representing the property value. If None, no properties are updated. + /// secondary_index (int, optional): The optional integer which will be used as a secondary index + /// + /// Returns: + /// None: This function does not return a value, if the operation is successful. + /// + /// Raises: + /// GraphError: If the operation fails. + #[pyo3(signature = (t, properties=None, secondary_index=None))] pub fn add_updates( &self, t: PyTime, properties: Option>, + secondary_index: Option, ) -> Result<(), GraphError> { - self.node.add_updates(t, properties.unwrap_or_default()) + match secondary_index { + None => self.node.add_updates(t, properties.unwrap_or_default()), + Some(secondary_index) => self + .node + .add_updates((t, secondary_index), properties.unwrap_or_default()), + } } /// Add constant properties to a node in the graph. From fa14f59740a67f488a53db5fe86f6c9d2e9f51a9 Mon Sep 17 00:00:00 2001 From: Shivam Kapoor <4599890+iamsmkr@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:35:35 +0000 Subject: [PATCH 6/6] add and fix tests --- python/tests/test_graphdb/test_graphdb.py | 170 +++++++++++++++++- .../src/core/entities/properties/tcell.rs | 5 +- .../src/core/entities/properties/tprop.rs | 2 +- raphtory/src/db/api/view/graph.rs | 14 -- raphtory/src/python/graph/graph.rs | 2 +- 5 files changed, 169 insertions(+), 24 deletions(-) diff --git a/python/tests/test_graphdb/test_graphdb.py b/python/tests/test_graphdb/test_graphdb.py index 071803ab92..41921c73b0 100644 --- a/python/tests/test_graphdb/test_graphdb.py +++ b/python/tests/test_graphdb/test_graphdb.py @@ -457,10 +457,10 @@ def test_graph_properties(): assert g.properties["prop 5"] == {"x": 1, "y": "ok"} props = {"prop 4": 11, "prop 5": "world", "prop 6": False} - g.add_property(1, props) + g.add_properties(1, props) props = {"prop 6": True} - g.add_property(2, props) + g.add_properties(2, props) def history_test(key, value): if value is None: @@ -2054,7 +2054,7 @@ def check(g): assert mg.nodes.collect()[0].name == "1" props = {"prop 4": 11, "prop 5": "world", "prop 6": False} - mg.add_property(1, props) + mg.add_properties(1, props) props = {"prop 1": 1, "prop 2": "hi", "prop 3": True} mg.add_constant_properties(props) @@ -2755,10 +2755,10 @@ def check(g): def test_unique_temporal_properties(): g = Graph() - g.add_property(1, {"name": "tarzan"}) - g.add_property(2, {"name": "tarzan2"}) - g.add_property(3, {"name": "tarzan2"}) - g.add_property(2, {"salary": "1000"}) + g.add_properties(1, {"name": "tarzan"}) + g.add_properties(2, {"name": "tarzan2"}) + g.add_properties(3, {"name": "tarzan2"}) + g.add_properties(2, {"salary": "1000"}) g.add_constant_properties({"type": "character"}) g.add_edge(1, 1, 2, properties={"status": "open"}) g.add_edge(2, 1, 2, properties={"status": "open"}) @@ -3057,6 +3057,162 @@ def test_edge_layer_properties(): assert g.edge("A", "B").properties == {"greeting": "namaste"} +def test_add_node_properties_ordered_by_secondary_index(): + g = Graph() + g.add_node(1, "A", properties={"prop": 1}, secondary_index=3) + g.add_node(1, "A", properties={"prop": 2}, secondary_index=2) + g.add_node(1, "A", properties={"prop": 3}, secondary_index=1) + + assert g.node("A").properties.temporal.get("prop").items() == [(1, 3), (1, 2), (1, 1)] + + +def test_add_node_properties_overwritten_for_same_secondary_index(): + g = Graph() + g.add_node(1, "A", properties={"prop": 1}, secondary_index=1) + g.add_node(1, "A", properties={"prop": 2}, secondary_index=1) + g.add_node(1, "A", properties={"prop": 3}, secondary_index=1) + + assert g.node("A").properties.temporal.get("prop").items() == [(1, 3)] + + g = Graph() + g.add_node(1, "A", properties={"prop": 1}, secondary_index=1) + g.add_node(1, "A", properties={"prop": 2}, secondary_index=2) + g.add_node(1, "A", properties={"prop": 3}, secondary_index=2) + + assert g.node("A").properties.temporal.get("prop").items() == [(1, 1), (1, 3)] + + +def test_create_node_properties_ordered_by_secondary_index(): + g = Graph() + g.create_node(1, "A", properties={"prop": 1}, secondary_index=3) + g.add_node(1, "A", properties={"prop": 2}, secondary_index=2) + g.add_node(1, "A", properties={"prop": 3}, secondary_index=1) + + assert g.node("A").properties.temporal.get("prop").items() == [(1, 3), (1, 2), (1, 1)] + + +def test_create_node_properties_overwritten_for_same_secondary_index(): + g = Graph() + g.create_node(1, "A", properties={"prop": 1}, secondary_index=1) + g.add_node(1, "A", properties={"prop": 2}, secondary_index=1) + g.add_node(1, "A", properties={"prop": 3}, secondary_index=1) + + assert g.node("A").properties.temporal.get("prop").items() == [(1, 3)] + + g = Graph() + g.create_node(1, "A", properties={"prop": 1}, secondary_index=1) + g.add_node(1, "A", properties={"prop": 2}, secondary_index=2) + g.add_node(1, "A", properties={"prop": 3}, secondary_index=2) + + assert g.node("A").properties.temporal.get("prop").items() == [(1, 1), (1, 3)] + + +def test_add_edge_properties_ordered_by_secondary_index(): + g = Graph() + g.add_edge(1, "A", "B", properties={"prop": 1}, secondary_index=3) + g.add_edge(1, "A", "B", properties={"prop": 2}, secondary_index=2) + g.add_edge(1, "A", "B", properties={"prop": 3}, secondary_index=1) + + assert g.edge("A", "B").properties.temporal.get("prop").items() == [(1, 3), (1, 2), (1, 1)] + + +def test_add_edge_properties_overwritten_for_same_secondary_index(): + g = Graph() + g.add_edge(1, "A", "B", properties={"prop": 1}, secondary_index=1) + g.add_edge(1, "A", "B", properties={"prop": 2}, secondary_index=1) + g.add_edge(1, "A", "B", properties={"prop": 3}, secondary_index=1) + + assert g.edge("A", "B").properties.temporal.get("prop").items() == [(1, 3)] + + g = Graph() + g.add_edge(1, "A", "B", properties={"prop": 1}, secondary_index=1) + g.add_edge(1, "A", "B", properties={"prop": 2}, secondary_index=2) + g.add_edge(1, "A", "B", properties={"prop": 3}, secondary_index=2) + + assert g.edge("A", "B").properties.temporal.get("prop").items() == [(1, 1), (1, 3)] + + +def test_add_properties_properties_ordered_by_secondary_index(): + g = Graph() + g.add_properties(1, properties={"prop": 1}, secondary_index=3) + g.add_properties(1, properties={"prop": 2}, secondary_index=2) + g.add_properties(1, properties={"prop": 3}, secondary_index=1) + + assert g.properties.temporal.get("prop").items() == [(1, 3), (1, 2), (1, 1)] + + +def test_add_properties_properties_overwritten_for_same_secondary_index(): + g = Graph() + g.add_properties(1, properties={"prop": 1}, secondary_index=1) + g.add_properties(1, properties={"prop": 2}, secondary_index=1) + g.add_properties(1, properties={"prop": 3}, secondary_index=1) + + assert g.properties.temporal.get("prop").items() == [(1, 3)] + + g = Graph() + g.add_properties(1, properties={"prop": 1}, secondary_index=1) + g.add_properties(1, properties={"prop": 2}, secondary_index=2) + g.add_properties(1, properties={"prop": 3}, secondary_index=2) + + assert g.properties.temporal.get("prop").items() == [(1, 1), (1, 3)] + + +def test_node_add_updates_properties_ordered_by_secondary_index(): + g = Graph() + g.add_node(1, "A") + g.node("A").add_updates(1, properties={"prop": 1}, secondary_index=3) + g.node("A").add_updates(1, properties={"prop": 2}, secondary_index=2) + g.node("A").add_updates(1, properties={"prop": 3}, secondary_index=1) + + assert g.node("A").properties.temporal.get("prop").items() == [(1, 3), (1, 2), (1, 1)] + + +def test_node_add_updates_properties_overwritten_for_same_secondary_index(): + g = Graph() + g.add_node(1, "A") + g.node("A").add_updates(1, properties={"prop": 1}, secondary_index=1) + g.node("A").add_updates(1, properties={"prop": 2}, secondary_index=1) + g.node("A").add_updates(1, properties={"prop": 3}, secondary_index=1) + + assert g.node("A").properties.temporal.get("prop").items() == [(1, 3)] + + g = Graph() + g.add_node(1, "A") + g.node("A").add_updates(1, properties={"prop": 1}, secondary_index=1) + g.node("A").add_updates(1, properties={"prop": 2}, secondary_index=2) + g.node("A").add_updates(1, properties={"prop": 3}, secondary_index=2) + + assert g.node("A").properties.temporal.get("prop").items() == [(1, 1), (1, 3)] + + +def test_edge_add_updates_properties_ordered_by_secondary_index(): + g = Graph() + g.add_edge(1, "A", "B") + g.edge("A", "B").add_updates(1, properties={"prop": 1}, secondary_index=3) + g.edge("A", "B").add_updates(1, properties={"prop": 2}, secondary_index=2) + g.edge("A", "B").add_updates(1, properties={"prop": 3}, secondary_index=1) + + assert g.edge("A", "B").properties.temporal.get("prop").items() == [(1, 3), (1, 2), (1, 1)] + + +def test_edge_add_updates_properties_overwritten_for_same_secondary_index(): + g = Graph() + g.add_edge(1, "A", "B") + g.edge("A", "B").add_updates(1, properties={"prop": 1}, secondary_index=1) + g.edge("A", "B").add_updates(1, properties={"prop": 2}, secondary_index=1) + g.edge("A", "B").add_updates(1, properties={"prop": 3}, secondary_index=1) + + assert g.edge("A", "B").properties.temporal.get("prop").items() == [(1, 3)] + + g = Graph() + g.add_edge(1, "A", "B") + g.edge("A", "B").add_updates(1, properties={"prop": 1}, secondary_index=1) + g.edge("A", "B").add_updates(1, properties={"prop": 2}, secondary_index=2) + g.edge("A", "B").add_updates(1, properties={"prop": 3}, secondary_index=2) + + assert g.edge("A", "B").properties.temporal.get("prop").items() == [(1, 1), (1, 3)] + + @fixture def datadir(tmpdir, request): filename = request.module.__file__ diff --git a/raphtory/src/core/entities/properties/tcell.rs b/raphtory/src/core/entities/properties/tcell.rs index bee6200d0e..1945e6ce9e 100644 --- a/raphtory/src/core/entities/properties/tcell.rs +++ b/raphtory/src/core/entities/properties/tcell.rs @@ -185,7 +185,10 @@ mod tcell_tests { let mut tcell = TCell::new(TimeIndexEntry::start(1), "Pometry"); tcell.set(TimeIndexEntry::start(1), "Pometry Inc."); - assert_eq!(tcell.iter_t().collect::>(), vec![(1, &"Pometry")]); + assert_eq!( + tcell.iter_t().collect::>(), + vec![(1, &"Pometry Inc.")] + ); } #[test] diff --git a/raphtory/src/core/entities/properties/tprop.rs b/raphtory/src/core/entities/properties/tprop.rs index 5e732027d9..39b993f7b5 100644 --- a/raphtory/src/core/entities/properties/tprop.rs +++ b/raphtory/src/core/entities/properties/tprop.rs @@ -430,7 +430,7 @@ mod tprop_tests { assert_eq!( tprop.iter_t().collect::>(), - vec![(1, "Pometry".into())] + vec![(1, "Pometry Inc.".into())] ); } diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index 48c867f410..377ab5d406 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -817,10 +817,6 @@ mod test_exploded_edges { #[test] fn test_add_properties_properties_ordered_by_secondary_index() { let graph: Graph = Graph::new(); - graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); - graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); - graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); - graph.add_properties((0, 3), [("prop", "1")]).unwrap(); graph.add_properties((0, 2), [("prop", "2")]).unwrap(); graph.add_properties((0, 1), [("prop", "3")]).unwrap(); @@ -843,10 +839,6 @@ mod test_exploded_edges { #[test] fn test_add_properties_properties_overwritten_for_same_secondary_index() { let graph: Graph = Graph::new(); - graph.add_edge((0, 1), 0, 1, NO_PROPS, None).unwrap(); - graph.add_edge((0, 1), 0, 1, NO_PROPS, None).unwrap(); - graph.add_edge((0, 1), 0, 1, NO_PROPS, None).unwrap(); - graph.add_properties((0, 1), [("prop", "1")]).unwrap(); graph.add_properties((0, 1), [("prop", "2")]).unwrap(); graph.add_properties((0, 1), [("prop", "3")]).unwrap(); @@ -887,8 +879,6 @@ mod test_exploded_edges { fn test_node_add_updates_properties_ordered_by_secondary_index() { let graph: Graph = Graph::new(); graph.add_node(0, 0, NO_PROPS, None).unwrap(); - graph.add_node(0, 0, NO_PROPS, None).unwrap(); - graph.add_node(0, 0, NO_PROPS, None).unwrap(); graph .node(0) @@ -1008,8 +998,6 @@ mod test_exploded_edges { fn test_edge_add_updates_properties_ordered_by_secondary_index() { let graph: Graph = Graph::new(); graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); - graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); - graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); graph .edge(0, 1) @@ -1050,8 +1038,6 @@ mod test_exploded_edges { fn test_edge_add_updates_properties_overwritten_for_same_secondary_index() { let graph: Graph = Graph::new(); graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); - graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); - graph.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); graph .edge(0, 1) diff --git a/raphtory/src/python/graph/graph.rs b/raphtory/src/python/graph/graph.rs index 9f0de6f68e..defa39cd65 100644 --- a/raphtory/src/python/graph/graph.rs +++ b/raphtory/src/python/graph/graph.rs @@ -258,7 +258,7 @@ impl PyGraph { /// Raises: /// GraphError: If the operation fails. #[pyo3(signature = (timestamp, properties, secondary_index = None))] - pub fn add_property( + pub fn add_properties( &self, timestamp: PyTime, properties: HashMap,