1use std::collections::{BTreeMap, HashMap, VecDeque};
26
27#[inline]
33fn xorshift64(state: &mut u64) -> u64 {
34 let mut x = *state;
35 x ^= x << 13;
36 x ^= x >> 7;
37 x ^= x << 17;
38 *state = x;
39 x
40}
41
42#[inline]
44fn fnv1a_64(data: &[u8]) -> u64 {
45 let mut h: u64 = 14_695_981_039_346_656_037;
46 for &b in data {
47 h ^= b as u64;
48 h = h.wrapping_mul(1_099_511_628_211);
49 }
50 h
51}
52
53fn gen_id(seed: &mut u64, salt: u64) -> [u8; 16] {
55 let a = xorshift64(seed);
56 let b = fnv1a_64(&(a ^ salt).to_le_bytes());
57 let mut out = [0u8; 16];
58 out[..8].copy_from_slice(&a.to_le_bytes());
59 out[8..].copy_from_slice(&b.to_le_bytes());
60 out
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
69pub struct NodeId(pub [u8; 16]);
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
73pub struct EdgeId(pub [u8; 16]);
74
75#[derive(Debug, Clone)]
81pub struct TkgNode {
82 pub id: NodeId,
83 pub label: String,
84 pub properties: HashMap<String, String>,
85 pub created_at: u64,
86 pub deleted_at: Option<u64>,
87}
88
89impl TkgNode {
90 pub fn alive_at(&self, t: u64) -> bool {
92 self.created_at <= t && self.deleted_at.is_none_or(|d| t < d)
93 }
94}
95
96#[derive(Debug, Clone)]
98pub struct TkgEdge {
99 pub id: EdgeId,
100 pub src: NodeId,
101 pub dst: NodeId,
102 pub relation: String,
103 pub weight: f64,
104 pub valid_from: u64,
105 pub valid_until: Option<u64>,
106}
107
108impl TkgEdge {
109 pub fn valid_at(&self, t: u64) -> bool {
111 self.valid_from <= t && self.valid_until.is_none_or(|v| t < v)
112 }
113}
114
115#[derive(Debug, Clone)]
121pub enum TkgEvent {
122 NodeAdded(NodeId),
123 NodeDeleted(NodeId),
124 EdgeAdded(EdgeId),
125 EdgeDeleted(EdgeId),
126 PropertyChanged {
127 node_id: NodeId,
128 key: String,
129 old_val: Option<String>,
130 new_val: String,
131 },
132}
133
134#[derive(Debug, Clone)]
140pub enum TkgQuery {
141 NodesAt(u64),
143 EdgesAt(u64),
145 NodeHistory(NodeId),
147 EdgesBetween {
149 src: NodeId,
150 dst: NodeId,
151 from_t: u64,
152 to_t: u64,
153 },
154 PropertyAt {
156 node_id: NodeId,
157 key: String,
158 at_t: u64,
159 },
160}
161
162#[derive(Debug, Clone)]
164pub enum TkgQueryResult {
165 Nodes(Vec<TkgNode>),
166 Edges(Vec<TkgEdge>),
167 Events(Vec<(u64, TkgEvent)>),
168 Property(Option<String>),
169 Empty,
170}
171
172#[derive(Debug, Clone)]
178pub struct TkgSnapshot {
179 pub at: u64,
180 pub nodes: Vec<TkgNode>,
181 pub edges: Vec<TkgEdge>,
182}
183
184#[derive(Debug, Clone, Default)]
190pub struct TkgGraphStats {
191 pub total_nodes: usize,
192 pub live_nodes: usize,
193 pub total_edges: usize,
194 pub live_edges: usize,
195 pub total_events: usize,
196 pub first_timestamp: Option<u64>,
197 pub last_timestamp: Option<u64>,
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub enum TkgMergePolicy {
207 KeepMine,
209 KeepOther,
211 UnionAll,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
221pub enum TkgError {
222 #[error("node {0:?} not found")]
223 NodeNotFound(NodeId),
224 #[error("edge {0:?} not found")]
225 EdgeNotFound(EdgeId),
226 #[error("node {0:?} already deleted")]
227 NodeAlreadyDeleted(NodeId),
228 #[error("edge {0:?} already deleted")]
229 EdgeAlreadyDeleted(EdgeId),
230 #[error("source node {0:?} does not exist at the given timestamp")]
231 SourceNodeInvalid(NodeId),
232 #[error("destination node {0:?} does not exist at the given timestamp")]
233 DestNodeInvalid(NodeId),
234}
235
236#[derive(Debug, Clone)]
245pub struct TemporalKnowledgeGraph {
246 nodes: HashMap<NodeId, TkgNode>,
247 edges: HashMap<EdgeId, TkgEdge>,
248 timeline: BTreeMap<u64, Vec<TkgEvent>>,
250 rng_state: u64,
252 id_counter: u64,
254}
255
256impl Default for TemporalKnowledgeGraph {
257 fn default() -> Self {
258 Self::new()
259 }
260}
261
262impl TemporalKnowledgeGraph {
263 pub fn new() -> Self {
265 Self {
266 nodes: HashMap::new(),
267 edges: HashMap::new(),
268 timeline: BTreeMap::new(),
269 rng_state: 0xcafe_babe_dead_beef,
270 id_counter: 0,
271 }
272 }
273
274 fn next_node_id(&mut self) -> NodeId {
277 self.id_counter = self.id_counter.wrapping_add(1);
278 NodeId(gen_id(&mut self.rng_state, self.id_counter))
279 }
280
281 fn next_edge_id(&mut self) -> EdgeId {
282 self.id_counter = self.id_counter.wrapping_add(1);
283 let raw = gen_id(&mut self.rng_state, self.id_counter ^ 0xFFFF_FFFF);
284 EdgeId(raw)
285 }
286
287 fn push_event(&mut self, ts: u64, event: TkgEvent) {
290 self.timeline.entry(ts).or_default().push(event);
291 }
292
293 pub fn add_node(&mut self, label: String, ts: u64) -> Result<NodeId, TkgError> {
299 let id = self.next_node_id();
300 let node = TkgNode {
301 id,
302 label,
303 properties: HashMap::new(),
304 created_at: ts,
305 deleted_at: None,
306 };
307 self.nodes.insert(id, node);
308 self.push_event(ts, TkgEvent::NodeAdded(id));
309 Ok(id)
310 }
311
312 pub fn delete_node(&mut self, id: NodeId, ts: u64) -> Result<(), TkgError> {
314 let node = self.nodes.get_mut(&id).ok_or(TkgError::NodeNotFound(id))?;
315 if node.deleted_at.is_some() {
316 return Err(TkgError::NodeAlreadyDeleted(id));
317 }
318 node.deleted_at = Some(ts);
319 self.push_event(ts, TkgEvent::NodeDeleted(id));
320 Ok(())
321 }
322
323 pub fn add_edge(
328 &mut self,
329 src: NodeId,
330 dst: NodeId,
331 relation: String,
332 weight: f64,
333 valid_from: u64,
334 ) -> Result<EdgeId, TkgError> {
335 {
337 let src_node = self
338 .nodes
339 .get(&src)
340 .ok_or(TkgError::SourceNodeInvalid(src))?;
341 if !src_node.alive_at(valid_from) {
342 return Err(TkgError::SourceNodeInvalid(src));
343 }
344 }
345 {
347 let dst_node = self.nodes.get(&dst).ok_or(TkgError::DestNodeInvalid(dst))?;
348 if !dst_node.alive_at(valid_from) {
349 return Err(TkgError::DestNodeInvalid(dst));
350 }
351 }
352
353 let id = self.next_edge_id();
354 let edge = TkgEdge {
355 id,
356 src,
357 dst,
358 relation,
359 weight,
360 valid_from,
361 valid_until: None,
362 };
363 self.edges.insert(id, edge);
364 self.push_event(valid_from, TkgEvent::EdgeAdded(id));
365 Ok(id)
366 }
367
368 pub fn delete_edge(&mut self, id: EdgeId, ts: u64) -> Result<(), TkgError> {
370 let edge = self.edges.get_mut(&id).ok_or(TkgError::EdgeNotFound(id))?;
371 if edge.valid_until.is_some() {
372 return Err(TkgError::EdgeAlreadyDeleted(id));
373 }
374 edge.valid_until = Some(ts);
375 self.push_event(ts, TkgEvent::EdgeDeleted(id));
376 Ok(())
377 }
378
379 pub fn set_property(
381 &mut self,
382 id: NodeId,
383 key: String,
384 value: String,
385 ts: u64,
386 ) -> Result<(), TkgError> {
387 let node = self.nodes.get_mut(&id).ok_or(TkgError::NodeNotFound(id))?;
388 let old_val = node.properties.get(&key).cloned();
389 node.properties.insert(key.clone(), value.clone());
390 self.push_event(
391 ts,
392 TkgEvent::PropertyChanged {
393 node_id: id,
394 key,
395 old_val,
396 new_val: value,
397 },
398 );
399 Ok(())
400 }
401
402 pub fn query(&self, q: TkgQuery) -> TkgQueryResult {
406 match q {
407 TkgQuery::NodesAt(t) => {
408 let nodes = self
409 .nodes
410 .values()
411 .filter(|n| n.alive_at(t))
412 .cloned()
413 .collect();
414 TkgQueryResult::Nodes(nodes)
415 }
416
417 TkgQuery::EdgesAt(t) => {
418 let edges = self
419 .edges
420 .values()
421 .filter(|e| e.valid_at(t))
422 .cloned()
423 .collect();
424 TkgQueryResult::Edges(edges)
425 }
426
427 TkgQuery::NodeHistory(node_id) => {
428 let mut events: Vec<(u64, TkgEvent)> = Vec::new();
429 for (&ts, evs) in &self.timeline {
430 for ev in evs {
431 let relevant = match ev {
432 TkgEvent::NodeAdded(id) | TkgEvent::NodeDeleted(id) => *id == node_id,
433 TkgEvent::PropertyChanged { node_id: nid, .. } => *nid == node_id,
434 _ => false,
435 };
436 if relevant {
437 events.push((ts, ev.clone()));
438 }
439 }
440 }
441 TkgQueryResult::Events(events)
442 }
443
444 TkgQuery::EdgesBetween {
445 src,
446 dst,
447 from_t,
448 to_t,
449 } => {
450 let edges = self
451 .edges
452 .values()
453 .filter(|e| {
454 e.src == src
455 && e.dst == dst
456 && e.valid_from <= to_t
457 && e.valid_until.is_none_or(|u| u >= from_t)
458 })
459 .cloned()
460 .collect();
461 TkgQueryResult::Edges(edges)
462 }
463
464 TkgQuery::PropertyAt { node_id, key, at_t } => {
465 let mut current: Option<String> = None;
467 for (&ts, evs) in &self.timeline {
468 if ts > at_t {
469 break;
470 }
471 for ev in evs {
472 if let TkgEvent::PropertyChanged {
473 node_id: nid,
474 key: k,
475 new_val,
476 ..
477 } = ev
478 {
479 if *nid == node_id && k == &key {
480 current = Some(new_val.clone());
481 }
482 }
483 }
484 }
485 TkgQueryResult::Property(current)
486 }
487 }
488 }
489
490 pub fn snapshot_at(&self, t: u64) -> TkgSnapshot {
494 let nodes: Vec<TkgNode> = self
495 .nodes
496 .values()
497 .filter(|n| n.alive_at(t))
498 .cloned()
499 .collect();
500 let edges: Vec<TkgEdge> = self
501 .edges
502 .values()
503 .filter(|e| e.valid_at(t))
504 .cloned()
505 .collect();
506 TkgSnapshot {
507 at: t,
508 nodes,
509 edges,
510 }
511 }
512
513 pub fn temporal_path(&self, src: NodeId, dst: NodeId, at_t: u64) -> Option<Vec<NodeId>> {
519 let src_node = self.nodes.get(&src)?;
521 if !src_node.alive_at(at_t) {
522 return None;
523 }
524 let dst_node = self.nodes.get(&dst)?;
525 if !dst_node.alive_at(at_t) {
526 return None;
527 }
528
529 if src == dst {
530 return Some(vec![src]);
531 }
532
533 let mut adj: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
535 for edge in self.edges.values() {
536 if edge.valid_at(at_t) {
537 adj.entry(edge.src).or_default().push(edge.dst);
538 }
539 }
540
541 let mut visited: HashMap<NodeId, NodeId> = HashMap::new(); let mut queue: VecDeque<NodeId> = VecDeque::new();
544 queue.push_back(src);
545 visited.insert(src, src); 'bfs: while let Some(cur) = queue.pop_front() {
548 if let Some(neighbours) = adj.get(&cur) {
549 for &next in neighbours {
550 if visited.contains_key(&next) {
551 continue;
552 }
553 visited.insert(next, cur);
554 if next == dst {
555 break 'bfs;
556 }
557 queue.push_back(next);
558 }
559 }
560 }
561
562 if !visited.contains_key(&dst) {
563 return None;
564 }
565
566 let mut path = Vec::new();
568 let mut cur = dst;
569 loop {
570 path.push(cur);
571 let parent = *visited.get(&cur)?;
572 if parent == cur {
573 break; }
575 cur = parent;
576 }
577 path.reverse();
578 Some(path)
579 }
580
581 pub fn merge_graphs(&mut self, other: &Self, policy: TkgMergePolicy) {
589 for (&id, other_node) in &other.nodes {
591 match policy {
592 TkgMergePolicy::KeepMine => {
593 self.nodes.entry(id).or_insert_with(|| other_node.clone());
594 }
595 TkgMergePolicy::KeepOther => {
596 self.nodes.insert(id, other_node.clone());
597 }
598 TkgMergePolicy::UnionAll => {
599 let entry = self.nodes.entry(id).or_insert_with(|| other_node.clone());
600 for (k, v) in &other_node.properties {
602 entry
603 .properties
604 .entry(k.clone())
605 .or_insert_with(|| v.clone());
606 }
607 if let (Some(mine_d), Some(other_d)) = (entry.deleted_at, other_node.deleted_at)
609 {
610 entry.deleted_at = Some(mine_d.max(other_d));
611 } else if other_node.deleted_at.is_some() {
612 entry.deleted_at = other_node.deleted_at;
613 }
614 }
615 }
616 }
617
618 for (&id, other_edge) in &other.edges {
620 match policy {
621 TkgMergePolicy::KeepMine => {
622 self.edges.entry(id).or_insert_with(|| other_edge.clone());
623 }
624 TkgMergePolicy::KeepOther | TkgMergePolicy::UnionAll => {
625 self.edges.insert(id, other_edge.clone());
626 }
627 }
628 }
629
630 for (&ts, other_evs) in &other.timeline {
632 let entry = self.timeline.entry(ts).or_default();
633 for ev in other_evs {
634 entry.push(ev.clone());
635 }
636 }
637 }
638
639 pub fn stats(&self) -> TkgGraphStats {
643 let total_events: usize = self.timeline.values().map(|v| v.len()).sum();
644 TkgGraphStats {
645 total_nodes: self.nodes.len(),
646 live_nodes: self
647 .nodes
648 .values()
649 .filter(|n| n.deleted_at.is_none())
650 .count(),
651 total_edges: self.edges.len(),
652 live_edges: self
653 .edges
654 .values()
655 .filter(|e| e.valid_until.is_none())
656 .count(),
657 total_events,
658 first_timestamp: self.timeline.keys().next().copied(),
659 last_timestamp: self.timeline.keys().next_back().copied(),
660 }
661 }
662
663 pub fn get_node(&self, id: NodeId) -> Option<&TkgNode> {
667 self.nodes.get(&id)
668 }
669
670 pub fn get_edge(&self, id: EdgeId) -> Option<&TkgEdge> {
672 self.edges.get(&id)
673 }
674
675 pub fn events_at(&self, ts: u64) -> &[TkgEvent] {
677 self.timeline.get(&ts).map(|v| v.as_slice()).unwrap_or(&[])
678 }
679
680 pub fn timeline_iter(&self) -> impl Iterator<Item = (u64, &[TkgEvent])> {
682 self.timeline.iter().map(|(&ts, evs)| (ts, evs.as_slice()))
683 }
684
685 pub fn node_count(&self) -> usize {
687 self.nodes.len()
688 }
689
690 pub fn edge_count(&self) -> usize {
692 self.edges.len()
693 }
694}
695
696#[cfg(test)]
701mod tests {
702 use super::*;
703
704 fn make_graph() -> (TemporalKnowledgeGraph, NodeId, NodeId) {
707 let mut g = TemporalKnowledgeGraph::new();
708 let a = g
709 .add_node("Alice".to_string(), 0)
710 .expect("test setup: add_node Alice should succeed");
711 let b = g
712 .add_node("Bob".to_string(), 0)
713 .expect("test setup: add_node Bob should succeed");
714 (g, a, b)
715 }
716
717 #[test]
720 fn test_xorshift64_nonzero() {
721 let mut state = 1u64;
722 let v = xorshift64(&mut state);
723 assert_ne!(v, 1);
724 }
725
726 #[test]
727 fn test_xorshift64_deterministic() {
728 let mut s1 = 42u64;
729 let mut s2 = 42u64;
730 assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
731 }
732
733 #[test]
734 fn test_fnv1a_empty() {
735 let h = fnv1a_64(&[]);
736 assert_eq!(h, 14_695_981_039_346_656_037u64);
737 }
738
739 #[test]
740 fn test_fnv1a_deterministic() {
741 let a = fnv1a_64(b"hello");
742 let b = fnv1a_64(b"hello");
743 assert_eq!(a, b);
744 }
745
746 #[test]
747 fn test_fnv1a_different_inputs() {
748 assert_ne!(fnv1a_64(b"foo"), fnv1a_64(b"bar"));
749 }
750
751 #[test]
752 fn test_gen_id_unique() {
753 let mut state = 0x1234_5678u64;
754 let id1 = gen_id(&mut state, 1);
755 let id2 = gen_id(&mut state, 2);
756 assert_ne!(id1, id2);
757 }
758
759 #[test]
762 fn test_add_node_returns_unique_ids() {
763 let mut g = TemporalKnowledgeGraph::new();
764 let a = g
765 .add_node("A".to_string(), 0)
766 .expect("test setup: add_node A should succeed");
767 let b = g
768 .add_node("B".to_string(), 0)
769 .expect("test setup: add_node B should succeed");
770 assert_ne!(a, b);
771 }
772
773 #[test]
774 fn test_add_node_stored() {
775 let mut g = TemporalKnowledgeGraph::new();
776 let id = g
777 .add_node("X".to_string(), 5)
778 .expect("test setup: add_node X should succeed");
779 let node = g
780 .get_node(id)
781 .expect("test setup: node must exist after add_node succeeded");
782 assert_eq!(node.label, "X");
783 assert_eq!(node.created_at, 5);
784 assert!(node.deleted_at.is_none());
785 }
786
787 #[test]
788 fn test_delete_node_sets_deleted_at() {
789 let (mut g, a, _) = make_graph();
790 g.delete_node(a, 10)
791 .expect("test setup: delete_node should succeed");
792 let node = g
793 .get_node(a)
794 .expect("test setup: node must exist after add_node succeeded");
795 assert_eq!(node.deleted_at, Some(10));
796 }
797
798 #[test]
799 fn test_delete_node_not_found() {
800 let mut g = TemporalKnowledgeGraph::new();
801 let fake = NodeId([0u8; 16]);
802 assert!(matches!(
803 g.delete_node(fake, 0),
804 Err(TkgError::NodeNotFound(_))
805 ));
806 }
807
808 #[test]
809 fn test_delete_node_twice_errors() {
810 let (mut g, a, _) = make_graph();
811 g.delete_node(a, 5)
812 .expect("test setup: first deletion of node a must succeed");
813 assert!(matches!(
814 g.delete_node(a, 10),
815 Err(TkgError::NodeAlreadyDeleted(_))
816 ));
817 }
818
819 #[test]
820 fn test_node_alive_at() {
821 let node = TkgNode {
822 id: NodeId([0; 16]),
823 label: "x".to_string(),
824 properties: HashMap::new(),
825 created_at: 5,
826 deleted_at: Some(15),
827 };
828 assert!(!node.alive_at(4));
829 assert!(node.alive_at(5));
830 assert!(node.alive_at(14));
831 assert!(!node.alive_at(15));
832 }
833
834 #[test]
835 fn test_node_alive_no_deletion() {
836 let node = TkgNode {
837 id: NodeId([0; 16]),
838 label: "x".to_string(),
839 properties: HashMap::new(),
840 created_at: 0,
841 deleted_at: None,
842 };
843 assert!(node.alive_at(u64::MAX));
844 }
845
846 #[test]
849 fn test_add_edge_stored() {
850 let (mut g, a, b) = make_graph();
851 let eid = g
852 .add_edge(a, b, "knows".to_string(), 0.9, 0)
853 .expect("test setup: add edge a→b with relation 'knows'");
854 let edge = g
855 .get_edge(eid)
856 .expect("test setup: get edge by id after successful add");
857 assert_eq!(edge.src, a);
858 assert_eq!(edge.dst, b);
859 assert_eq!(edge.relation, "knows");
860 assert!((edge.weight - 0.9).abs() < 1e-10);
861 }
862
863 #[test]
864 fn test_add_edge_invalid_src() {
865 let mut g = TemporalKnowledgeGraph::new();
866 let fake = NodeId([0u8; 16]);
867 let b = g
868 .add_node("B".to_string(), 0)
869 .expect("test setup: add node B to graph");
870 assert!(matches!(
871 g.add_edge(fake, b, "r".to_string(), 1.0, 0),
872 Err(TkgError::SourceNodeInvalid(_))
873 ));
874 }
875
876 #[test]
877 fn test_add_edge_invalid_dst() {
878 let mut g = TemporalKnowledgeGraph::new();
879 let a = g
880 .add_node("A".to_string(), 0)
881 .expect("test setup: add node A to graph");
882 let fake = NodeId([0u8; 16]);
883 assert!(matches!(
884 g.add_edge(a, fake, "r".to_string(), 1.0, 0),
885 Err(TkgError::DestNodeInvalid(_))
886 ));
887 }
888
889 #[test]
890 fn test_add_edge_on_deleted_src_fails() {
891 let (mut g, a, b) = make_graph();
892 g.delete_node(a, 5)
893 .expect("test setup: delete node a before attempting edge add");
894 assert!(matches!(
895 g.add_edge(a, b, "r".to_string(), 1.0, 10),
896 Err(TkgError::SourceNodeInvalid(_))
897 ));
898 }
899
900 #[test]
901 fn test_delete_edge_sets_valid_until() {
902 let (mut g, a, b) = make_graph();
903 let eid = g
904 .add_edge(a, b, "r".to_string(), 1.0, 0)
905 .expect("test setup: add edge a→b with relation 'r'");
906 g.delete_edge(eid, 20)
907 .expect("test setup: delete edge at timestamp 20");
908 let edge = g
909 .get_edge(eid)
910 .expect("test setup: get edge after deletion to inspect valid_until");
911 assert_eq!(edge.valid_until, Some(20));
912 }
913
914 #[test]
915 fn test_delete_edge_not_found() {
916 let mut g = TemporalKnowledgeGraph::new();
917 let fake = EdgeId([0u8; 16]);
918 assert!(matches!(
919 g.delete_edge(fake, 0),
920 Err(TkgError::EdgeNotFound(_))
921 ));
922 }
923
924 #[test]
925 fn test_delete_edge_twice_errors() {
926 let (mut g, a, b) = make_graph();
927 let eid = g
928 .add_edge(a, b, "r".to_string(), 1.0, 0)
929 .expect("test setup: add edge for double-delete test");
930 g.delete_edge(eid, 5)
931 .expect("test setup: first deletion of edge must succeed");
932 assert!(matches!(
933 g.delete_edge(eid, 10),
934 Err(TkgError::EdgeAlreadyDeleted(_))
935 ));
936 }
937
938 #[test]
939 fn test_edge_valid_at() {
940 let edge = TkgEdge {
941 id: EdgeId([0; 16]),
942 src: NodeId([0; 16]),
943 dst: NodeId([1; 16]),
944 relation: "r".to_string(),
945 weight: 1.0,
946 valid_from: 10,
947 valid_until: Some(20),
948 };
949 assert!(!edge.valid_at(9));
950 assert!(edge.valid_at(10));
951 assert!(edge.valid_at(19));
952 assert!(!edge.valid_at(20));
953 }
954
955 #[test]
958 fn test_set_property_stores_value() {
959 let (mut g, a, _) = make_graph();
960 g.set_property(a, "age".to_string(), "30".to_string(), 1)
961 .expect("test setup: set property 'age' to '30' on node a");
962 let node = g
963 .get_node(a)
964 .expect("test setup: get node a after setting property");
965 assert_eq!(node.properties.get("age").map(String::as_str), Some("30"));
966 }
967
968 #[test]
969 fn test_set_property_overwrite() {
970 let (mut g, a, _) = make_graph();
971 g.set_property(a, "age".to_string(), "30".to_string(), 1)
972 .expect("test setup: set initial property 'age' to '30'");
973 g.set_property(a, "age".to_string(), "31".to_string(), 2)
974 .expect("test setup: overwrite property 'age' to '31'");
975 let node = g
976 .get_node(a)
977 .expect("test setup: get node a after property overwrite");
978 assert_eq!(node.properties.get("age").map(String::as_str), Some("31"));
979 }
980
981 #[test]
982 fn test_set_property_node_not_found() {
983 let mut g = TemporalKnowledgeGraph::new();
984 let fake = NodeId([0u8; 16]);
985 assert!(matches!(
986 g.set_property(fake, "k".to_string(), "v".to_string(), 0),
987 Err(TkgError::NodeNotFound(_))
988 ));
989 }
990
991 #[test]
994 fn test_events_at_returns_events() {
995 let (g, a, _) = make_graph();
996 let evs = g.events_at(0);
997 assert_eq!(evs.len(), 2);
998 let has_a = evs
999 .iter()
1000 .any(|e| matches!(e, TkgEvent::NodeAdded(id) if *id == a));
1001 assert!(has_a);
1002 }
1003
1004 #[test]
1005 fn test_events_at_empty_timestamp() {
1006 let g = TemporalKnowledgeGraph::new();
1007 assert!(g.events_at(999).is_empty());
1008 }
1009
1010 #[test]
1011 fn test_timeline_iter_ordered() {
1012 let mut g = TemporalKnowledgeGraph::new();
1013 g.add_node("A".to_string(), 10)
1014 .expect("test setup: add node A at timestamp 10");
1015 g.add_node("B".to_string(), 5)
1016 .expect("test setup: add node B at timestamp 5");
1017 g.add_node("C".to_string(), 20)
1018 .expect("test setup: add node C at timestamp 20");
1019 let ts: Vec<u64> = g.timeline_iter().map(|(t, _)| t).collect();
1020 assert_eq!(ts, vec![5, 10, 20]);
1021 }
1022
1023 #[test]
1026 fn test_query_nodes_at() {
1027 let (mut g, a, _) = make_graph();
1028 g.delete_node(a, 5)
1029 .expect("test setup: delete node a at timestamp 5");
1030 if let TkgQueryResult::Nodes(nodes) = g.query(TkgQuery::NodesAt(3)) {
1031 assert_eq!(nodes.len(), 2);
1032 } else {
1033 panic!("wrong variant");
1034 }
1035 if let TkgQueryResult::Nodes(nodes) = g.query(TkgQuery::NodesAt(10)) {
1036 assert_eq!(nodes.len(), 1);
1037 } else {
1038 panic!("wrong variant");
1039 }
1040 }
1041
1042 #[test]
1043 fn test_query_edges_at() {
1044 let (mut g, a, b) = make_graph();
1045 let eid = g
1046 .add_edge(a, b, "r".to_string(), 1.0, 0)
1047 .expect("test setup: add edge a→b at timestamp 0");
1048 g.delete_edge(eid, 10)
1049 .expect("test setup: delete edge at timestamp 10");
1050 if let TkgQueryResult::Edges(edges) = g.query(TkgQuery::EdgesAt(5)) {
1051 assert_eq!(edges.len(), 1);
1052 } else {
1053 panic!("wrong variant");
1054 }
1055 if let TkgQueryResult::Edges(edges) = g.query(TkgQuery::EdgesAt(15)) {
1056 assert_eq!(edges.len(), 0);
1057 } else {
1058 panic!("wrong variant");
1059 }
1060 }
1061
1062 #[test]
1063 fn test_query_node_history() {
1064 let (mut g, a, _) = make_graph();
1065 g.set_property(a, "x".to_string(), "1".to_string(), 2)
1066 .expect("test setup: set property 'x' to '1' at timestamp 2");
1067 g.set_property(a, "x".to_string(), "2".to_string(), 4)
1068 .expect("test setup: set property 'x' to '2' at timestamp 4");
1069 g.delete_node(a, 6)
1070 .expect("test setup: delete node a at timestamp 6 to generate delete event");
1071 if let TkgQueryResult::Events(events) = g.query(TkgQuery::NodeHistory(a)) {
1072 assert_eq!(events.len(), 4);
1074 } else {
1075 panic!("wrong variant");
1076 }
1077 }
1078
1079 #[test]
1080 fn test_query_edges_between() {
1081 let (mut g, a, b) = make_graph();
1082 g.add_edge(a, b, "r".to_string(), 1.0, 5)
1083 .expect("test setup: add edge a→b with relation 'r' at timestamp 5");
1084 g.add_edge(a, b, "s".to_string(), 0.5, 15)
1085 .expect("test setup: add edge a→b with relation 's' at timestamp 15");
1086 if let TkgQueryResult::Edges(edges) = g.query(TkgQuery::EdgesBetween {
1087 src: a,
1088 dst: b,
1089 from_t: 0,
1090 to_t: 10,
1091 }) {
1092 assert_eq!(edges.len(), 1);
1093 } else {
1094 panic!("wrong variant");
1095 }
1096 }
1097
1098 #[test]
1099 fn test_query_edges_between_full_range() {
1100 let (mut g, a, b) = make_graph();
1101 g.add_edge(a, b, "r".to_string(), 1.0, 0)
1102 .expect("test setup: add first edge a→b with relation 'r'");
1103 g.add_edge(a, b, "s".to_string(), 0.5, 0)
1104 .expect("test setup: add second edge a→b with relation 's'");
1105 if let TkgQueryResult::Edges(edges) = g.query(TkgQuery::EdgesBetween {
1106 src: a,
1107 dst: b,
1108 from_t: 0,
1109 to_t: u64::MAX,
1110 }) {
1111 assert_eq!(edges.len(), 2);
1112 } else {
1113 panic!("wrong variant");
1114 }
1115 }
1116
1117 #[test]
1118 fn test_query_property_at_returns_correct_version() {
1119 let (mut g, a, _) = make_graph();
1120 g.set_property(a, "k".to_string(), "v1".to_string(), 5)
1121 .expect("test setup: set property 'k' to 'v1' at timestamp 5");
1122 g.set_property(a, "k".to_string(), "v2".to_string(), 10)
1123 .expect("test setup: set property 'k' to 'v2' at timestamp 10");
1124 if let TkgQueryResult::Property(Some(val)) = g.query(TkgQuery::PropertyAt {
1125 node_id: a,
1126 key: "k".to_string(),
1127 at_t: 7,
1128 }) {
1129 assert_eq!(val, "v1");
1130 } else {
1131 panic!("expected Some(v1)");
1132 }
1133 if let TkgQueryResult::Property(Some(val)) = g.query(TkgQuery::PropertyAt {
1134 node_id: a,
1135 key: "k".to_string(),
1136 at_t: 10,
1137 }) {
1138 assert_eq!(val, "v2");
1139 } else {
1140 panic!("expected Some(v2)");
1141 }
1142 }
1143
1144 #[test]
1145 fn test_query_property_at_none_before_set() {
1146 let (mut g, a, _) = make_graph();
1147 g.set_property(a, "k".to_string(), "v".to_string(), 10)
1148 .expect("test setup: set property 'k' to 'v' at timestamp 10");
1149 if let TkgQueryResult::Property(val) = g.query(TkgQuery::PropertyAt {
1150 node_id: a,
1151 key: "k".to_string(),
1152 at_t: 5,
1153 }) {
1154 assert!(val.is_none());
1155 } else {
1156 panic!("wrong variant");
1157 }
1158 }
1159
1160 #[test]
1163 fn test_snapshot_at_basic() {
1164 let (mut g, a, b) = make_graph();
1165 g.add_edge(a, b, "r".to_string(), 1.0, 0)
1166 .expect("test setup: add edge a→b for basic snapshot test");
1167 let snap = g.snapshot_at(5);
1168 assert_eq!(snap.at, 5);
1169 assert_eq!(snap.nodes.len(), 2);
1170 assert_eq!(snap.edges.len(), 1);
1171 }
1172
1173 #[test]
1174 fn test_snapshot_excludes_deleted_nodes() {
1175 let (mut g, a, _) = make_graph();
1176 g.delete_node(a, 3)
1177 .expect("test setup: delete node a at timestamp 3 before snapshot at 5");
1178 let snap = g.snapshot_at(5);
1179 assert_eq!(snap.nodes.len(), 1);
1180 }
1181
1182 #[test]
1183 fn test_snapshot_excludes_expired_edges() {
1184 let (mut g, a, b) = make_graph();
1185 let eid = g
1186 .add_edge(a, b, "r".to_string(), 1.0, 0)
1187 .expect("test setup: add edge for snapshot expired-edge test");
1188 g.delete_edge(eid, 5)
1189 .expect("test setup: delete edge at timestamp 5 before snapshot at 10");
1190 let snap = g.snapshot_at(10);
1191 assert_eq!(snap.edges.len(), 0);
1192 }
1193
1194 #[test]
1195 fn test_snapshot_empty_graph() {
1196 let g = TemporalKnowledgeGraph::new();
1197 let snap = g.snapshot_at(100);
1198 assert!(snap.nodes.is_empty());
1199 assert!(snap.edges.is_empty());
1200 }
1201
1202 #[test]
1205 fn test_temporal_path_direct() {
1206 let (mut g, a, b) = make_graph();
1207 g.add_edge(a, b, "r".to_string(), 1.0, 0)
1208 .expect("test setup: add direct edge a→b for path test");
1209 let path = g
1210 .temporal_path(a, b, 5)
1211 .expect("test setup: direct path from a to b must exist at timestamp 5");
1212 assert_eq!(path, vec![a, b]);
1213 }
1214
1215 #[test]
1216 fn test_temporal_path_self() {
1217 let (g, a, _) = make_graph();
1218 let path = g
1219 .temporal_path(a, a, 0)
1220 .expect("test setup: self-path from a to a must always exist");
1221 assert_eq!(path, vec![a]);
1222 }
1223
1224 #[test]
1225 fn test_temporal_path_multi_hop() {
1226 let mut g = TemporalKnowledgeGraph::new();
1227 let a = g
1228 .add_node("A".to_string(), 0)
1229 .expect("test setup: add node A for multi-hop path test");
1230 let b = g
1231 .add_node("B".to_string(), 0)
1232 .expect("test setup: add node B for multi-hop path test");
1233 let c = g
1234 .add_node("C".to_string(), 0)
1235 .expect("test setup: add node C for multi-hop path test");
1236 g.add_edge(a, b, "r".to_string(), 1.0, 0)
1237 .expect("test setup: add edge A→B for multi-hop path");
1238 g.add_edge(b, c, "r".to_string(), 1.0, 0)
1239 .expect("test setup: add edge B→C for multi-hop path");
1240 let path = g
1241 .temporal_path(a, c, 5)
1242 .expect("test setup: multi-hop path A→B→C must exist at timestamp 5");
1243 assert_eq!(path, vec![a, b, c]);
1244 }
1245
1246 #[test]
1247 fn test_temporal_path_no_path() {
1248 let (g, a, b) = make_graph();
1249 assert!(g.temporal_path(a, b, 5).is_none());
1251 }
1252
1253 #[test]
1254 fn test_temporal_path_expired_edge() {
1255 let (mut g, a, b) = make_graph();
1256 let eid = g
1257 .add_edge(a, b, "r".to_string(), 1.0, 0)
1258 .expect("test setup: add edge that will be expired for path test");
1259 g.delete_edge(eid, 5)
1260 .expect("test setup: delete edge at timestamp 5 to expire it");
1261 assert!(g.temporal_path(a, b, 3).is_some());
1263 assert!(g.temporal_path(a, b, 10).is_none());
1264 }
1265
1266 #[test]
1267 fn test_temporal_path_deleted_node() {
1268 let (mut g, a, b) = make_graph();
1269 g.add_edge(a, b, "r".to_string(), 1.0, 0)
1270 .expect("test setup: add edge a→b before deleting node b");
1271 g.delete_node(b, 5)
1272 .expect("test setup: delete destination node b at timestamp 5");
1273 assert!(g.temporal_path(a, b, 10).is_none());
1275 }
1276
1277 #[test]
1278 fn test_temporal_path_unknown_src() {
1279 let g = TemporalKnowledgeGraph::new();
1280 let fake = NodeId([0u8; 16]);
1281 let fake2 = NodeId([1u8; 16]);
1282 assert!(g.temporal_path(fake, fake2, 0).is_none());
1283 }
1284
1285 #[test]
1288 fn test_merge_keep_mine() {
1289 let (mut g1, a, _) = make_graph();
1290 g1.set_property(a, "x".to_string(), "mine".to_string(), 1)
1291 .expect("test setup: set property 'x' to 'mine' on node a in g1");
1292
1293 let mut g2 = TemporalKnowledgeGraph::new();
1294 g2.nodes.insert(
1297 a,
1298 TkgNode {
1299 id: a,
1300 label: "Alice-other".to_string(),
1301 properties: {
1302 let mut m = HashMap::new();
1303 m.insert("x".to_string(), "other".to_string());
1304 m
1305 },
1306 created_at: 0,
1307 deleted_at: None,
1308 },
1309 );
1310
1311 g1.merge_graphs(&g2, TkgMergePolicy::KeepMine);
1312 assert_eq!(
1313 g1.get_node(a)
1314 .expect("test setup: get node a from g1 after KeepMine merge")
1315 .properties
1316 .get("x")
1317 .map(String::as_str),
1318 Some("mine")
1319 );
1320 }
1321
1322 #[test]
1323 fn test_merge_keep_other() {
1324 let (mut g1, a, _) = make_graph();
1325 g1.set_property(a, "x".to_string(), "mine".to_string(), 1)
1326 .expect("test setup: set property 'x' to 'mine' on node a before KeepOther merge");
1327
1328 let mut g2 = TemporalKnowledgeGraph::new();
1329 g2.nodes.insert(
1330 a,
1331 TkgNode {
1332 id: a,
1333 label: "Alice-other".to_string(),
1334 properties: {
1335 let mut m = HashMap::new();
1336 m.insert("x".to_string(), "other".to_string());
1337 m
1338 },
1339 created_at: 0,
1340 deleted_at: None,
1341 },
1342 );
1343
1344 g1.merge_graphs(&g2, TkgMergePolicy::KeepOther);
1345 assert_eq!(
1346 g1.get_node(a)
1347 .expect("test setup: get node a from g1 after KeepOther merge")
1348 .properties
1349 .get("x")
1350 .map(String::as_str),
1351 Some("other")
1352 );
1353 }
1354
1355 #[test]
1356 fn test_merge_union_all_new_node() {
1357 let (mut g1, _, _) = make_graph();
1358 let mut g2 = TemporalKnowledgeGraph::new();
1359 let c = g2
1360 .add_node("Charlie".to_string(), 0)
1361 .expect("test setup: add Charlie node to g2 for union merge test");
1362
1363 g1.merge_graphs(&g2, TkgMergePolicy::UnionAll);
1364 assert!(g1.get_node(c).is_some());
1365 }
1366
1367 #[test]
1368 fn test_merge_timeline_combined() {
1369 let mut g1 = TemporalKnowledgeGraph::new();
1370 g1.add_node("A".to_string(), 5)
1371 .expect("test setup: add node A at timestamp 5 in g1");
1372
1373 let mut g2 = TemporalKnowledgeGraph::new();
1374 g2.add_node("B".to_string(), 10)
1375 .expect("test setup: add node B at timestamp 10 in g2");
1376
1377 g1.merge_graphs(&g2, TkgMergePolicy::UnionAll);
1378 assert!(!g1.events_at(5).is_empty());
1379 assert!(!g1.events_at(10).is_empty());
1380 }
1381
1382 #[test]
1383 fn test_merge_edges_keep_mine() {
1384 let (mut g1, a, b) = make_graph();
1385 let eid = g1
1386 .add_edge(a, b, "mine".to_string(), 1.0, 0)
1387 .expect("test setup: add edge with relation 'mine' in g1 before merge");
1388
1389 let mut g2 = TemporalKnowledgeGraph::new();
1390 g2.edges.insert(
1391 eid,
1392 TkgEdge {
1393 id: eid,
1394 src: a,
1395 dst: b,
1396 relation: "other".to_string(),
1397 weight: 0.5,
1398 valid_from: 0,
1399 valid_until: None,
1400 },
1401 );
1402
1403 g1.merge_graphs(&g2, TkgMergePolicy::KeepMine);
1404 assert_eq!(
1405 g1.get_edge(eid)
1406 .expect("test setup: get edge from g1 after KeepMine merge")
1407 .relation,
1408 "mine"
1409 );
1410 }
1411
1412 #[test]
1415 fn test_stats_basic() {
1416 let (mut g, a, b) = make_graph();
1417 g.add_edge(a, b, "r".to_string(), 1.0, 0)
1418 .expect("test setup: add edge a→b for stats test");
1419 let s = g.stats();
1420 assert_eq!(s.total_nodes, 2);
1421 assert_eq!(s.live_nodes, 2);
1422 assert_eq!(s.total_edges, 1);
1423 assert_eq!(s.live_edges, 1);
1424 }
1425
1426 #[test]
1427 fn test_stats_after_deletion() {
1428 let (mut g, a, b) = make_graph();
1429 let eid = g
1430 .add_edge(a, b, "r".to_string(), 1.0, 0)
1431 .expect("test setup: add edge for stats-after-deletion test");
1432 g.delete_node(a, 5)
1433 .expect("test setup: delete node a at timestamp 5 for stats test");
1434 g.delete_edge(eid, 5)
1435 .expect("test setup: delete edge at timestamp 5 for stats test");
1436 let s = g.stats();
1437 assert_eq!(s.live_nodes, 1);
1438 assert_eq!(s.live_edges, 0);
1439 }
1440
1441 #[test]
1442 fn test_stats_timestamps() {
1443 let mut g = TemporalKnowledgeGraph::new();
1444 g.add_node("A".to_string(), 3)
1445 .expect("test setup: add node A at timestamp 3");
1446 g.add_node("B".to_string(), 7)
1447 .expect("test setup: add node B at timestamp 7");
1448 let s = g.stats();
1449 assert_eq!(s.first_timestamp, Some(3));
1450 assert_eq!(s.last_timestamp, Some(7));
1451 }
1452
1453 #[test]
1454 fn test_stats_empty_graph() {
1455 let g = TemporalKnowledgeGraph::new();
1456 let s = g.stats();
1457 assert_eq!(s.total_nodes, 0);
1458 assert_eq!(s.total_events, 0);
1459 assert!(s.first_timestamp.is_none());
1460 }
1461
1462 #[test]
1465 fn test_node_count() {
1466 let (g, _, _) = make_graph();
1467 assert_eq!(g.node_count(), 2);
1468 }
1469
1470 #[test]
1471 fn test_edge_count() {
1472 let (mut g, a, b) = make_graph();
1473 g.add_edge(a, b, "r".to_string(), 1.0, 0)
1474 .expect("test setup: add single edge for edge count test");
1475 assert_eq!(g.edge_count(), 1);
1476 }
1477
1478 #[test]
1481 fn test_default_empty() {
1482 let g: TemporalKnowledgeGraph = Default::default();
1483 assert_eq!(g.node_count(), 0);
1484 assert_eq!(g.edge_count(), 0);
1485 }
1486
1487 #[test]
1488 fn test_clone_independent() {
1489 let (mut g, a, b) = make_graph();
1490 let mut g2 = g.clone();
1491 g.add_edge(a, b, "r".to_string(), 1.0, 0)
1492 .expect("test setup: add edge a→b for clone test");
1493 assert_eq!(g.edge_count(), 1);
1494 assert_eq!(g2.edge_count(), 0);
1495 g2.delete_node(a, 99)
1497 .expect("test setup: delete node a in cloned graph to verify independence");
1498 assert!(g
1499 .get_node(a)
1500 .expect("test setup: get node a from original graph after clone modification")
1501 .deleted_at
1502 .is_none());
1503 }
1504
1505 #[test]
1508 fn test_error_display_node_not_found() {
1509 let e = TkgError::NodeNotFound(NodeId([0u8; 16]));
1510 assert!(e.to_string().contains("not found"));
1511 }
1512
1513 #[test]
1514 fn test_error_display_edge_not_found() {
1515 let e = TkgError::EdgeNotFound(EdgeId([0u8; 16]));
1516 assert!(e.to_string().contains("not found"));
1517 }
1518
1519 #[test]
1522 fn test_multiple_edges_same_pair() {
1523 let (mut g, a, b) = make_graph();
1524 let e1 = g
1525 .add_edge(a, b, "r1".to_string(), 1.0, 0)
1526 .expect("test setup: add first edge with relation 'r1'");
1527 let e2 = g
1528 .add_edge(a, b, "r2".to_string(), 0.5, 0)
1529 .expect("test setup: add second edge with relation 'r2'");
1530 assert_ne!(e1, e2);
1531 assert_eq!(g.edge_count(), 2);
1532 }
1533
1534 #[test]
1537 fn test_temporal_path_long_chain() {
1538 let mut g = TemporalKnowledgeGraph::new();
1539 let n = 20usize;
1540 let mut ids = Vec::with_capacity(n);
1541 for i in 0..n {
1542 ids.push(
1543 g.add_node(format!("N{i}"), 0)
1544 .expect("test setup: add node N{i} in long-chain test"),
1545 );
1546 }
1547 for i in 0..n - 1 {
1548 g.add_edge(ids[i], ids[i + 1], "next".to_string(), 1.0, 0)
1549 .expect("test setup: add chain edge from node i to node i+1");
1550 }
1551 let path = g
1552 .temporal_path(ids[0], ids[n - 1], 0)
1553 .expect("test setup: long chain path from first to last node must exist");
1554 assert_eq!(path.len(), n);
1555 assert_eq!(path[0], ids[0]);
1556 assert_eq!(path[n - 1], ids[n - 1]);
1557 }
1558
1559 #[test]
1562 fn test_node_id_ordering() {
1563 let a = NodeId([0u8; 16]);
1564 let b = NodeId([1u8; 16]);
1565 assert!(a < b);
1566 }
1567
1568 #[test]
1569 fn test_edge_id_equality() {
1570 let a = EdgeId([42u8; 16]);
1571 let b = EdgeId([42u8; 16]);
1572 assert_eq!(a, b);
1573 }
1574}