1use std::mem::size_of;
11
12use nodedb_mem::ScopedMemory;
13
14use super::types::{CsrIndex, Direction};
15use crate::GraphError;
16use crate::csr::LocalNodeId;
17use crate::csr::rebuild::journal::{CsrWriteOp, OpOutcome};
18
19pub(crate) struct DenseAdjacency {
21 pub(crate) offsets: Vec<u32>,
22 pub(crate) targets: Vec<u32>,
23 pub(crate) labels: Vec<u32>,
24 pub(crate) collections: Vec<u32>,
25}
26
27impl CsrIndex {
28 #[inline]
30 pub fn partition_tag(&self) -> u32 {
31 self.partition_tag
32 }
33
34 #[inline]
38 pub fn local(&self, id: u32) -> LocalNodeId {
39 LocalNodeId::new(id, self.partition_tag)
40 }
41
42 pub fn neighbors(
45 &self,
46 node: &str,
47 label_filter: &[&str],
48 direction: Direction,
49 ) -> Vec<(String, String)> {
50 let Some(&node_id) = self.node_to_id.get(node) else {
51 return Vec::new();
52 };
53 self.record_access(node_id);
54 let labels = self.label_filter(label_filter);
55
56 let mut result = Vec::new();
57
58 if matches!(direction, Direction::Out | Direction::Both) {
59 for (lid, dst) in self.dense_iter_out(node_id) {
60 if labels.keeps(lid) {
61 result.push((
62 self.id_to_label[lid as usize].clone(),
63 self.id_to_node[dst as usize].clone(),
64 ));
65 }
66 }
67 }
68 if matches!(direction, Direction::In | Direction::Both) {
69 for (lid, src) in self.dense_iter_in(node_id) {
70 if labels.keeps(lid) {
71 result.push((
72 self.id_to_label[lid as usize].clone(),
73 self.id_to_node[src as usize].clone(),
74 ));
75 }
76 }
77 }
78
79 result
80 }
81
82 pub fn add_node(&mut self, name: &str) -> Result<LocalNodeId, crate::GraphError> {
88 let result = self.ensure_node(name);
89 self.journal_record(
90 || CsrWriteOp::AddNode {
91 name: name.to_string(),
92 },
93 OpOutcome::of(&result),
94 );
95 Ok(LocalNodeId::new(result?, self.partition_tag))
96 }
97
98 pub fn node_count(&self) -> usize {
99 self.id_to_node.len()
100 }
101
102 pub fn contains_node(&self, node: &str) -> bool {
103 self.node_to_id.contains_key(node)
104 }
105
106 pub fn node_name(&self, id: LocalNodeId) -> &str {
108 &self.id_to_node[id.raw(self.partition_tag) as usize]
109 }
110
111 pub fn node_id(&self, name: &str) -> Option<LocalNodeId> {
113 self.node_to_id
114 .get(name)
115 .copied()
116 .map(|raw| LocalNodeId::new(raw, self.partition_tag))
117 }
118
119 pub fn label_name(&self, label_id: u32) -> &str {
121 &self.id_to_label[label_id as usize]
122 }
123
124 pub fn label_id(&self, name: &str) -> Option<u32> {
126 self.label_to_id.get(name).copied()
127 }
128
129 pub fn out_degree(&self, id: LocalNodeId) -> usize {
131 self.dense_iter_out(id.raw(self.partition_tag)).count()
132 }
133
134 pub fn in_degree(&self, id: LocalNodeId) -> usize {
136 self.dense_iter_in(id.raw(self.partition_tag)).count()
137 }
138
139 pub fn edge_count(&self) -> usize {
141 let n = self.id_to_node.len();
142 (0..n as u32).map(|i| self.out_degree(self.local(i))).sum()
143 }
144
145 pub(crate) fn build_dense(
154 edges: &[Vec<(u32, u32)>],
155 collections: &[Vec<u32>],
156 memory: &ScopedMemory,
157 ) -> Result<DenseAdjacency, GraphError> {
158 let n = edges.len();
159 let total: usize = edges.iter().map(|e| e.len()).sum();
160 let reserve_bytes = (n + 1 + 3 * total) * size_of::<u32>();
162 let _budget_guard = memory.reserve(reserve_bytes)?;
163 let mut offsets = Vec::with_capacity(n + 1);
164 let mut targets = Vec::with_capacity(total);
165 let mut labels = Vec::with_capacity(total);
166 let mut out_collections = Vec::with_capacity(total);
167
168 let mut offset = 0u32;
169 for (node, node_edges) in edges.iter().enumerate() {
170 offsets.push(offset);
171 let node_colls = collections.get(node);
172 for (k, &(lid, target)) in node_edges.iter().enumerate() {
173 targets.push(target);
174 labels.push(lid);
175 out_collections.push(node_colls.and_then(|c| c.get(k)).copied().unwrap_or(0));
176 }
177 offset += node_edges.len() as u32;
178 }
179 offsets.push(offset);
180
181 Ok(DenseAdjacency {
182 offsets,
183 targets,
184 labels,
185 collections: out_collections,
186 })
187 }
188
189 pub(crate) fn dense_has_edge(&self, src: u32, label: u32, dst: u32, collection: u32) -> bool {
194 for (lid, target, coll) in self.dense_out_edges(src) {
195 if lid == label && target == dst && coll == collection {
196 return true;
197 }
198 }
199 false
200 }
201
202 pub(crate) fn dense_out_edges(&self, node: u32) -> impl Iterator<Item = (u32, u32, u32)> + '_ {
205 let range = self
206 .out_offsets
207 .get(node as usize..)
208 .and_then(|offsets| offsets.first().zip(offsets.get(1)))
209 .map_or(0..0, |(&start, &end)| start as usize..end as usize);
210 range.map(move |i| {
211 (
212 self.out_labels[i],
213 self.out_targets[i],
214 self.out_collections.get(i).copied().unwrap_or(0),
215 )
216 })
217 }
218
219 pub(crate) fn dense_in_edges(&self, node: u32) -> impl Iterator<Item = (u32, u32, u32)> + '_ {
222 let range = self
223 .in_offsets
224 .get(node as usize..)
225 .and_then(|offsets| offsets.first().zip(offsets.get(1)))
226 .map_or(0..0, |(&start, &end)| start as usize..end as usize);
227 range.map(move |i| {
228 (
229 self.in_labels[i],
230 self.in_targets[i],
231 self.in_collections.get(i).copied().unwrap_or(0),
232 )
233 })
234 }
235
236 pub(crate) fn dense_iter_out(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
242 let dense = self
243 .dense_out_edges(node)
244 .filter(move |&(lid, dst, coll)| !self.deleted_edges.contains(&(node, lid, dst, coll)))
245 .map(|(lid, dst, _coll)| (lid, dst));
246 dense.chain(self.buffer_out_iter(node))
247 }
248
249 pub(crate) fn dense_iter_in(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
252 let dense = self
253 .dense_in_edges(node)
254 .filter(move |&(lid, src, coll)| !self.deleted_edges.contains(&(src, lid, node, coll)))
255 .map(|(lid, src, _coll)| (lid, src));
256 dense.chain(self.buffer_in_iter(node))
257 }
258
259 pub(crate) fn dense_iter_out_coll(&self, node: u32) -> Vec<(u32, u32, u32)> {
263 let mut out: Vec<(u32, u32, u32)> = self
264 .dense_out_edges(node)
265 .filter(|&(lid, dst, coll)| !self.deleted_edges.contains(&(node, lid, dst, coll)))
266 .collect();
267 let idx = node as usize;
268 if idx < self.buffer_out.len() {
269 for (k, &(lid, dst)) in self.buffer_out[idx].iter().enumerate() {
270 let coll = self
271 .buffer_out_collections
272 .get(idx)
273 .and_then(|c| c.get(k))
274 .copied()
275 .unwrap_or(0);
276 out.push((lid, dst, coll));
277 }
278 }
279 out
280 }
281
282 pub(crate) fn dense_iter_in_coll(&self, node: u32) -> Vec<(u32, u32, u32)> {
285 let mut out: Vec<(u32, u32, u32)> = self
286 .dense_in_edges(node)
287 .filter(|&(lid, src, coll)| !self.deleted_edges.contains(&(src, lid, node, coll)))
288 .collect();
289 let idx = node as usize;
290 if idx < self.buffer_in.len() {
291 for (k, &(lid, src)) in self.buffer_in[idx].iter().enumerate() {
292 let coll = self
293 .buffer_in_collections
294 .get(idx)
295 .and_then(|c| c.get(k))
296 .copied()
297 .unwrap_or(0);
298 out.push((lid, src, coll));
299 }
300 }
301 out
302 }
303
304 pub(crate) fn buffer_out_iter(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
306 self.buffer_out
307 .get(node as usize)
308 .map_or(&[][..], Vec::as_slice)
309 .iter()
310 .copied()
311 }
312
313 pub(crate) fn buffer_in_iter(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
315 self.buffer_in
316 .get(node as usize)
317 .map_or(&[][..], Vec::as_slice)
318 .iter()
319 .copied()
320 }
321
322 pub fn iter_out_edges(
325 &self,
326 node: LocalNodeId,
327 ) -> impl Iterator<Item = (u32, LocalNodeId)> + '_ {
328 let raw = node.raw(self.partition_tag);
329 let tag = self.partition_tag;
330 self.dense_iter_out(raw)
331 .map(move |(lid, dst)| (lid, LocalNodeId::new(dst, tag)))
332 }
333
334 pub fn iter_in_edges(
336 &self,
337 node: LocalNodeId,
338 ) -> impl Iterator<Item = (u32, LocalNodeId)> + '_ {
339 let raw = node.raw(self.partition_tag);
340 let tag = self.partition_tag;
341 self.dense_iter_in(raw)
342 .map(move |(lid, src)| (lid, LocalNodeId::new(src, tag)))
343 }
344
345 pub fn iter_out_edges_raw(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
356 self.dense_iter_out(node)
357 }
358
359 pub fn iter_in_edges_raw(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
361 self.dense_iter_in(node)
362 }
363
364 pub fn out_degree_raw(&self, node: u32) -> usize {
366 self.dense_iter_out(node).count()
367 }
368
369 pub fn in_degree_raw(&self, node: u32) -> usize {
371 self.dense_iter_in(node).count()
372 }
373
374 pub fn node_name_raw(&self, id: u32) -> &str {
376 &self.id_to_node[id as usize]
377 }
378
379 pub fn is_local_node(&self, id: u32) -> bool {
385 (id as usize) < self.id_to_node.len()
386 }
387
388 pub fn node_name_checked(&self, id: u32) -> Option<&str> {
393 self.id_to_node.get(id as usize).map(String::as_str)
394 }
395
396 pub fn node_id_raw(&self, name: &str) -> Option<u32> {
398 self.node_to_id.get(name).copied()
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::CsrIndex;
407 use crate::test_support::test_memory;
408
409 fn names(csr: &CsrIndex, node: &str) -> Vec<(String, String)> {
410 csr.iter_out_edges(csr.node_id(node).unwrap())
411 .map(|(label, target)| (csr.label_name(label).into(), csr.node_name(target).into()))
412 .collect()
413 }
414
415 #[test]
416 fn live_iterators_preserve_dense_then_buffer_order() {
417 let mut csr = CsrIndex::new(test_memory());
418 csr.add_edge("a", "FIRST", "b").unwrap();
419 assert_eq!(names(&csr, "a"), vec![("FIRST".into(), "b".into())]);
420 csr.compact().unwrap();
421 assert_eq!(names(&csr, "a"), vec![("FIRST".into(), "b".into())]);
422 csr.add_edge("a", "SECOND", "c").unwrap();
423 assert_eq!(
424 names(&csr, "a"),
425 vec![("FIRST".into(), "b".into()), ("SECOND".into(), "c".into())]
426 );
427 let a = csr.node_id_raw("a").unwrap();
428 assert_eq!(csr.iter_out_edges_raw(a).count(), 2);
429 for target in ["b", "c"] {
430 let tagged = csr.node_id(target).unwrap();
431 let inbound: Vec<_> = csr
432 .iter_in_edges(tagged)
433 .map(|(_, source)| csr.node_name(source))
434 .collect();
435 assert_eq!(inbound, vec!["a"]);
436 assert_eq!(
437 csr.iter_in_edges_raw(csr.node_id_raw(target).unwrap())
438 .count(),
439 1
440 );
441 }
442 assert_eq!(csr.iter_out_edges_raw(u32::MAX).count(), 0);
443 assert_eq!(csr.iter_in_edges_raw(u32::MAX).count(), 0);
444 }
445
446 #[test]
447 fn collection_tombstones_remove_only_the_matching_copy() {
448 let mut csr = CsrIndex::new(test_memory());
449 csr.add_edge_in_collection("a", "LINK", "b", "first")
450 .unwrap();
451 csr.add_edge_in_collection("a", "LINK", "b", "second")
452 .unwrap();
453 csr.compact().unwrap();
454 csr.remove_edge_in_collection("a", "LINK", "b", "first");
455 csr.add_edge("a", "STAGED", "c").unwrap();
456 csr.remove_edge("a", "STAGED", "c");
457 assert_eq!(names(&csr, "a"), vec![("LINK".into(), "b".into())]);
458 assert_eq!(csr.iter_in_edges(csr.node_id("b").unwrap()).count(), 1);
459 assert_eq!(csr.iter_in_edges(csr.node_id("c").unwrap()).count(), 0);
460 }
461}