velesdb_core/collection/graph/edge_persistence.rs
1//! `EdgeStore` persistence (serialization/deserialization) methods.
2//!
3//! Extracted from `edge.rs` to reduce NLOC below the 500 threshold.
4
5use super::csr_snapshot::{CsrSnapshot, SnapshotBuilder};
6use super::edge::EdgeStore;
7use super::helpers::PostcardPersistence;
8use super::label_table::LabelTable;
9
10// ---------------------------------------------------------------------------
11// CSR snapshot methods (G1: zero-copy BFS)
12// ---------------------------------------------------------------------------
13impl EdgeStore {
14 /// Builds a CSR snapshot from the current outgoing index.
15 ///
16 /// This pre-computes contiguous arrays of target IDs, edge IDs, and
17 /// interned labels for all source nodes. After calling this, BFS
18 /// traversal uses `with_neighbors()` for zero-copy `&[u64]` access
19 /// instead of cloning full `GraphEdge` objects.
20 ///
21 /// # When to call
22 ///
23 /// - After loading from disk (graph is ready for reads)
24 /// - After a batch of mutations, before a read-heavy phase
25 ///
26 /// The snapshot is automatically invalidated by any write operation.
27 pub fn build_read_snapshot(&mut self) {
28 let label_table = LabelTable::new();
29 self.csr_snapshot = Some(SnapshotBuilder::build(self, &label_table));
30 }
31
32 /// Returns a reference to the CSR snapshot, if built.
33 #[must_use]
34 #[inline]
35 pub fn csr_snapshot(&self) -> Option<&CsrSnapshot> {
36 self.csr_snapshot.as_ref()
37 }
38
39 /// Returns `true` if a CSR snapshot is available for zero-copy reads.
40 #[must_use]
41 #[inline]
42 pub fn has_csr_snapshot(&self) -> bool {
43 self.csr_snapshot.is_some()
44 }
45
46 /// Provides zero-copy access to neighbor target IDs via a callback.
47 #[inline]
48 pub fn with_neighbors<F, R>(&self, source_id: u64, f: F) -> R
49 where
50 F: FnOnce(&[u64]) -> R,
51 {
52 if let Some(snapshot) = &self.csr_snapshot {
53 f(snapshot.neighbors(source_id))
54 } else {
55 let ids: Vec<u64> = self
56 .get_outgoing(source_id)
57 .iter()
58 .map(|e| e.target())
59 .collect();
60 f(&ids)
61 }
62 }
63
64 /// Provides zero-copy access to `(target_id, edge_id)` pairs via callback.
65 #[inline]
66 pub fn with_neighbor_edges<F, R>(&self, source_id: u64, f: F) -> R
67 where
68 F: FnOnce(&[u64], &[u64]) -> R,
69 {
70 if let Some(snapshot) = &self.csr_snapshot {
71 f(snapshot.neighbors(source_id), snapshot.edge_ids(source_id))
72 } else {
73 let edges = self.get_outgoing(source_id);
74 let targets: Vec<u64> = edges.iter().map(|e| e.target()).collect();
75 let eids: Vec<u64> = edges.iter().map(|e| e.id()).collect();
76 f(&targets, &eids)
77 }
78 }
79}
80
81impl PostcardPersistence for EdgeStore {}
82
83// Inherent persistence methods that delegate to `PostcardPersistence`.
84impl EdgeStore {
85 /// Serializes the edge store to bytes using `postcard`.
86 ///
87 /// # Errors
88 /// Returns an error if serialization fails.
89 pub fn to_bytes(&self) -> std::result::Result<Vec<u8>, postcard::Error> {
90 <Self as PostcardPersistence>::to_bytes(self)
91 }
92
93 /// Deserializes an edge store from bytes.
94 ///
95 /// # Errors
96 /// Returns an error if deserialization fails (e.g., corrupted data).
97 pub fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, postcard::Error> {
98 <Self as PostcardPersistence>::from_bytes(bytes)
99 }
100
101 /// Saves the edge store to a file.
102 ///
103 /// # Errors
104 /// Returns an error if serialization or file I/O fails.
105 pub fn save_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
106 <Self as PostcardPersistence>::save_to_file(self, path)
107 }
108
109 /// Loads an edge store from a file.
110 ///
111 /// Automatically builds a CSR snapshot after loading for zero-copy
112 /// BFS traversal (G1).
113 ///
114 /// # Errors
115 /// Returns an error if file I/O or deserialization fails.
116 pub fn load_from_file(path: &std::path::Path) -> std::io::Result<Self> {
117 let mut store = <Self as PostcardPersistence>::load_from_file(path)?;
118 store.build_read_snapshot();
119 Ok(store)
120 }
121}