1use super::types::CsrIndex;
12use crate::GraphError;
13use crate::csr::rebuild::journal::{CsrWriteOp, OpOutcome};
14
15impl CsrIndex {
16 pub fn edge_weight_in_collection(
19 &self,
20 src: &str,
21 label: &str,
22 dst: &str,
23 collection: &str,
24 ) -> Option<f64> {
25 let src_id = *self.node_to_id.get(src)?;
26 let dst_id = *self.node_to_id.get(dst)?;
27 let label_id = *self.label_to_id.get(label)?;
28 let coll_id = *self.collection_to_id.get(collection)?;
29 let idx = src_id as usize;
30
31 if let (Some(edges), Some(colls)) = (
32 self.buffer_out.get(idx),
33 self.buffer_out_collections.get(idx),
34 ) {
35 let position = edges
36 .iter()
37 .zip(colls.iter())
38 .position(|(&(l, d), &c)| l == label_id && d == dst_id && c == coll_id);
39 if let Some(k) = position {
40 let weight = if self.has_weights {
41 self.buffer_out_weights
42 .get(idx)
43 .and_then(|weights| weights.get(k))
44 .copied()
45 .unwrap_or(1.0)
46 } else {
47 1.0
48 };
49 return Some(weight);
50 }
51 }
52
53 if self
54 .deleted_edges
55 .contains(&(src_id, label_id, dst_id, coll_id))
56 || idx + 1 >= self.out_offsets.len()
57 {
58 return None;
59 }
60 let start = self.out_offsets[idx] as usize;
61 let end = self.out_offsets[idx + 1] as usize;
62 (start..end)
63 .find(|&i| {
64 self.out_labels[i] == label_id
65 && self.out_targets[i] == dst_id
66 && self.out_collections.get(i).copied().unwrap_or(0) == coll_id
67 })
68 .map(|i| self.out_edge_weight(i))
69 }
70
71 pub fn put_edge_in_collection(
77 &mut self,
78 src: &str,
79 label: &str,
80 dst: &str,
81 collection: &str,
82 weight: f64,
83 ) -> Result<Option<f64>, GraphError> {
84 let result = self.apply_put_edge(src, label, dst, collection, weight);
85 self.journal_record(
86 || CsrWriteOp::PutEdge {
87 src: src.to_string(),
88 label: label.to_string(),
89 dst: dst.to_string(),
90 collection: collection.to_string(),
91 weight,
92 },
93 OpOutcome::of(&result),
94 );
95 result
96 }
97
98 pub(crate) fn apply_put_edge(
100 &mut self,
101 src: &str,
102 label: &str,
103 dst: &str,
104 collection: &str,
105 weight: f64,
106 ) -> Result<Option<f64>, GraphError> {
107 let prior = self.edge_weight_in_collection(src, label, dst, collection);
108 match prior {
109 Some(current) if current == weight => return Ok(prior),
110 Some(_) => self.apply_remove_edge(src, label, dst, collection),
111 None => {}
112 }
113 let src_id = self.ensure_node(src)?;
114 let dst_id = self.ensure_node(dst)?;
115 let label_id = self.ensure_label(label)?;
116 let coll_id = self.ensure_collection(collection);
117 if weight != 1.0 && !self.has_weights {
118 self.enable_weights();
119 }
120 if !self.dense_has_edge(src_id, label_id, dst_id, coll_id) {
123 self.deleted_edges
124 .remove(&(src_id, label_id, dst_id, coll_id));
125 }
126 self.buffer_out[src_id as usize].push((label_id, dst_id));
127 self.buffer_in[dst_id as usize].push((label_id, src_id));
128 self.buffer_out_collections[src_id as usize].push(coll_id);
129 self.buffer_in_collections[dst_id as usize].push(coll_id);
130 if self.has_weights {
131 self.buffer_out_weights[src_id as usize].push(weight);
132 self.buffer_in_weights[dst_id as usize].push(weight);
133 }
134 Ok(prior)
135 }
136
137 pub fn restore_edge_in_collection(
140 &mut self,
141 src: &str,
142 label: &str,
143 dst: &str,
144 collection: &str,
145 prior: Option<f64>,
146 ) -> Result<(), GraphError> {
147 match prior {
148 Some(weight) => self
149 .put_edge_in_collection(src, label, dst, collection, weight)
150 .map(drop),
151 None => {
152 self.remove_edge_in_collection(src, label, dst, collection);
153 Ok(())
154 }
155 }
156 }
157
158 pub fn restore_node_surrogate(&mut self, node: &str, prior: u32) {
160 self.apply_restore_node_surrogate(node, prior);
161 self.journal_record(
162 || CsrWriteOp::RestoreNodeSurrogate {
163 node: node.to_string(),
164 prior,
165 },
166 OpOutcome::Applied,
167 );
168 }
169
170 pub(crate) fn apply_restore_node_surrogate(&mut self, node: &str, prior: u32) {
172 let Some(&id) = self.node_to_id.get(node) else {
173 return;
174 };
175 let Some(slot) = self.node_surrogates.get_mut(id as usize) else {
176 return;
177 };
178 let current = *slot;
179 if current == prior {
180 return;
181 }
182 *slot = prior;
183 if current != 0 && self.surrogate_to_local.get(¤t) == Some(&id) {
184 self.surrogate_to_local.remove(¤t);
185 }
186 if prior != 0 {
187 self.surrogate_to_local.insert(prior, id);
188 }
189 }
190
191 pub fn has_node_label_name(&self, label: &str) -> bool {
193 self.node_label_to_id.contains_key(label)
194 }
195
196 pub fn withdraw_newest_node(&mut self, node: &str) -> Result<(), GraphError> {
202 let result = self.apply_withdraw_newest_node(node);
203 self.journal_record(
204 || CsrWriteOp::WithdrawNewestNode {
205 node: node.to_string(),
206 },
207 OpOutcome::of(&result),
208 );
209 result
210 }
211
212 pub(crate) fn apply_withdraw_newest_node(&mut self, node: &str) -> Result<(), GraphError> {
214 let Some(&id) = self.node_to_id.get(node) else {
215 return Ok(());
216 };
217 let idx = id as usize;
218 let newest = idx + 1 == self.id_to_node.len();
219 let no_buffered_edge = self.buffer_out.get(idx).is_none_or(Vec::is_empty)
220 && self.buffer_in.get(idx).is_none_or(Vec::is_empty);
221 let empty_range = |offsets: &[u32]| match (offsets.get(idx), offsets.get(idx + 1)) {
222 (Some(start), Some(end)) => start == end,
223 _ => true,
224 };
225 let no_dense_edge =
226 empty_range(self.out_offsets.as_slice()) && empty_range(self.in_offsets.as_slice());
227 let unlabeled = self.node_label_bits.get(idx).copied().unwrap_or(0) == 0;
228 if !(newest && no_buffered_edge && no_dense_edge && unlabeled) {
229 return Err(GraphError::WithdrawRefused {
230 kind: "node",
231 name: node.to_string(),
232 });
233 }
234
235 self.node_to_id.remove(node);
236 self.id_to_node.pop();
237 if self.out_offsets.len() > idx + 1 {
239 self.out_offsets.pop();
240 }
241 if self.in_offsets.len() > idx + 1 {
242 self.in_offsets.pop();
243 }
244 self.buffer_out.truncate(idx);
245 self.buffer_in.truncate(idx);
246 self.buffer_out_weights.truncate(idx);
247 self.buffer_in_weights.truncate(idx);
248 self.buffer_out_collections.truncate(idx);
249 self.buffer_in_collections.truncate(idx);
250 self.node_label_bits.truncate(idx);
251 self.node_surrogates.truncate(idx);
252 self.surrogate_to_local.retain(|_, local| *local != id);
253 self.deleted_edges
255 .retain(|&(src, _, dst, _)| src != id && dst != id);
256 self.access_counts.truncate(idx);
257 Ok(())
258 }
259
260 pub fn withdraw_newest_node_label(&mut self, label: &str) -> Result<(), GraphError> {
265 let result = self.apply_withdraw_newest_node_label(label);
266 self.journal_record(
267 || CsrWriteOp::WithdrawNewestNodeLabel {
268 label: label.to_string(),
269 },
270 OpOutcome::of(&result),
271 );
272 result
273 }
274
275 pub(crate) fn apply_withdraw_newest_node_label(
277 &mut self,
278 label: &str,
279 ) -> Result<(), GraphError> {
280 let Some(&id) = self.node_label_to_id.get(label) else {
281 return Ok(());
282 };
283 let newest = usize::from(id) + 1 == self.node_label_names.len();
284 let bit = 1u64 << id;
285 let carried = self.node_label_bits.iter().any(|bits| bits & bit != 0);
286 if !newest || carried {
287 return Err(GraphError::WithdrawRefused {
288 kind: "node label",
289 name: label.to_string(),
290 });
291 }
292 self.node_label_to_id.remove(label);
293 self.node_label_names.pop();
294 Ok(())
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::csr::index::types::Direction;
302 use crate::test_support::test_memory;
303
304 #[test]
305 fn put_edge_replaces_the_weight_of_a_live_edge_and_reports_the_old_one() {
306 let mut csr = CsrIndex::new(test_memory());
307 assert_eq!(
308 csr.put_edge_in_collection("a", "L", "b", "c", 2.5)
309 .expect("first put"),
310 None
311 );
312 assert_eq!(
313 csr.put_edge_in_collection("a", "L", "b", "c", 9.0)
314 .expect("second put"),
315 Some(2.5)
316 );
317 assert_eq!(csr.edge_weight_in_collection("a", "L", "b", "c"), Some(9.0));
318 assert_eq!(csr.neighbors("a", &[], Direction::Out).len(), 1);
319 }
320
321 #[test]
322 fn put_edge_replaces_the_weight_of_a_compacted_edge() {
323 let mut csr = CsrIndex::new(test_memory());
324 csr.put_edge_in_collection("a", "L", "b", "c", 2.5)
325 .expect("put");
326 csr.compact().expect("compact");
327 assert_eq!(
328 csr.put_edge_in_collection("a", "L", "b", "c", 9.0)
329 .expect("reweigh"),
330 Some(2.5)
331 );
332 assert_eq!(csr.edge_weight_in_collection("a", "L", "b", "c"), Some(9.0));
333 assert_eq!(csr.neighbors("a", &[], Direction::Out).len(), 1);
334 csr.compact().expect("compact again");
335 assert_eq!(csr.edge_weight_in_collection("a", "L", "b", "c"), Some(9.0));
336 assert_eq!(csr.neighbors("a", &[], Direction::Out).len(), 1);
337 }
338
339 #[test]
340 fn restoring_an_edge_brings_back_a_deleted_compacted_edge() {
341 let mut csr = CsrIndex::new(test_memory());
342 csr.put_edge_in_collection("a", "L", "b", "c", 1.0)
343 .expect("put");
344 csr.compact().expect("compact");
345 csr.remove_edge_in_collection("a", "L", "b", "c");
346 assert_eq!(csr.edge_weight_in_collection("a", "L", "b", "c"), None);
347
348 csr.restore_edge_in_collection("a", "L", "b", "c", Some(1.0))
349 .expect("restore");
350 assert_eq!(csr.edge_weight_in_collection("a", "L", "b", "c"), Some(1.0));
351 assert_eq!(csr.neighbors("a", &[], Direction::Out).len(), 1);
352 }
353
354 #[test]
355 fn re_adding_a_deleted_compacted_edge_brings_it_back() {
356 let mut csr = CsrIndex::new(test_memory());
357 csr.add_edge_in_collection("a", "L", "b", "c")
358 .expect("edge");
359 csr.compact().expect("compact");
360 csr.remove_edge_in_collection("a", "L", "b", "c");
361 csr.add_edge_in_collection("a", "L", "b", "c")
362 .expect("re-add");
363 assert_eq!(csr.neighbors("a", &[], Direction::Out).len(), 1);
364 }
365
366 #[test]
367 fn withdrawing_the_newest_isolated_node_removes_it() {
368 let mut csr = CsrIndex::new(test_memory());
369 csr.add_edge("a", "L", "b").expect("edge");
370 csr.put_edge_in_collection("b", "L", "z", "c", 1.0)
371 .expect("edge to z");
372 csr.remove_edge_in_collection("b", "L", "z", "c");
373 csr.set_node_surrogate("z", nodedb_types::Surrogate::new(42));
374
375 csr.withdraw_newest_node("z").expect("withdraw");
376
377 assert!(!csr.contains_node("z"));
378 assert_eq!(csr.node_count(), 2);
379 assert_eq!(
380 csr.node_id_for_surrogate(nodedb_types::Surrogate::new(42)),
381 None
382 );
383 csr.add_edge("a", "L", "y")
384 .expect("a later node takes the freed id");
385 csr.compact().expect("compact");
386 assert_eq!(csr.neighbors("a", &[], Direction::Out).len(), 2);
387 }
388
389 #[test]
390 fn withdrawing_a_node_that_is_not_the_newest_is_refused() {
391 let mut csr = CsrIndex::new(test_memory());
392 csr.add_node_label("x", "Person").expect("label x");
393 csr.remove_node_label("x", "Person");
394 csr.add_edge("a", "L", "b").expect("edge");
395 assert!(matches!(
396 csr.withdraw_newest_node("x"),
397 Err(GraphError::WithdrawRefused { .. })
398 ));
399 assert!(csr.contains_node("x"));
400 }
401
402 #[test]
403 fn withdrawing_a_node_with_an_edge_is_refused() {
404 let mut csr = CsrIndex::new(test_memory());
405 csr.add_edge("a", "L", "b").expect("edge");
406 assert!(matches!(
407 csr.withdraw_newest_node("b"),
408 Err(GraphError::WithdrawRefused { .. })
409 ));
410 }
411
412 #[test]
413 fn withdrawing_the_newest_unused_node_label_frees_its_slot() {
414 let mut csr = CsrIndex::new(test_memory());
415 csr.add_node_label("x", "Person").expect("label");
416 csr.remove_node_label("x", "Person");
417 csr.withdraw_newest_node_label("Person").expect("withdraw");
418 assert!(!csr.has_node_label_name("Person"));
419 }
420
421 #[test]
422 fn withdrawing_a_carried_node_label_is_refused() {
423 let mut csr = CsrIndex::new(test_memory());
424 csr.add_node_label("x", "Person").expect("label");
425 assert!(matches!(
426 csr.withdraw_newest_node_label("Person"),
427 Err(GraphError::WithdrawRefused { .. })
428 ));
429 }
430
431 #[test]
432 fn restoring_a_surrogate_unbinds_the_one_a_write_set() {
433 let mut csr = CsrIndex::new(test_memory());
434 csr.add_edge("a", "L", "b").expect("edge");
435 csr.set_node_surrogate("a", nodedb_types::Surrogate::new(5));
436 csr.restore_node_surrogate("a", 0);
437 assert_eq!(csr.node_surrogate("a"), None);
438 assert_eq!(
439 csr.node_id_for_surrogate(nodedb_types::Surrogate::new(5)),
440 None
441 );
442 }
443}