1use std::mem::size_of;
11use std::sync::Arc;
12
13use nodedb_mem::{EngineId, MemoryGovernor};
14
15use super::types::{CsrIndex, Direction};
16use crate::GraphError;
17use crate::csr::LocalNodeId;
18
19pub(crate) struct DenseAdjacency {
21 pub(crate) offsets: Vec<u32>,
22 pub(crate) targets: Vec<u32>,
23 pub(crate) labels: Vec<u32>,
24}
25
26impl CsrIndex {
27 #[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(
44 &self,
45 node: &str,
46 label_filter: Option<&str>,
47 direction: Direction,
48 ) -> Vec<(String, String)> {
49 let Some(&node_id) = self.node_to_id.get(node) else {
50 return Vec::new();
51 };
52 self.record_access(node_id);
53 let label_id = label_filter.and_then(|l| self.label_to_id.get(l).copied());
54
55 let mut result = Vec::new();
56
57 if matches!(direction, Direction::Out | Direction::Both) {
58 for (lid, dst) in self.dense_iter_out(node_id) {
59 if label_id.is_none_or(|f| f == lid) {
60 result.push((
61 self.id_to_label[lid as usize].clone(),
62 self.id_to_node[dst as usize].clone(),
63 ));
64 }
65 }
66 }
67 if matches!(direction, Direction::In | Direction::Both) {
68 for (lid, src) in self.dense_iter_in(node_id) {
69 if label_id.is_none_or(|f| f == lid) {
70 result.push((
71 self.id_to_label[lid as usize].clone(),
72 self.id_to_node[src as usize].clone(),
73 ));
74 }
75 }
76 }
77
78 result
79 }
80
81 pub fn neighbors_multi(
83 &self,
84 node: &str,
85 label_filters: &[&str],
86 direction: Direction,
87 ) -> Vec<(String, String)> {
88 let Some(&node_id) = self.node_to_id.get(node) else {
89 return Vec::new();
90 };
91 self.record_access(node_id);
92 let label_ids: Vec<u32> = label_filters
93 .iter()
94 .filter_map(|l| self.label_to_id.get(*l).copied())
95 .collect();
96 let match_label = |lid: u32| label_ids.is_empty() || label_ids.contains(&lid);
97
98 let mut result = Vec::new();
99
100 if matches!(direction, Direction::Out | Direction::Both) {
101 for (lid, dst) in self.dense_iter_out(node_id) {
102 if match_label(lid) {
103 result.push((
104 self.id_to_label[lid as usize].clone(),
105 self.id_to_node[dst as usize].clone(),
106 ));
107 }
108 }
109 }
110 if matches!(direction, Direction::In | Direction::Both) {
111 for (lid, src) in self.dense_iter_in(node_id) {
112 if match_label(lid) {
113 result.push((
114 self.id_to_label[lid as usize].clone(),
115 self.id_to_node[src as usize].clone(),
116 ));
117 }
118 }
119 }
120
121 result
122 }
123
124 pub fn add_node(&mut self, name: &str) -> Result<LocalNodeId, crate::GraphError> {
130 let raw = self.ensure_node(name)?;
131 Ok(LocalNodeId::new(raw, self.partition_tag))
132 }
133
134 pub fn node_count(&self) -> usize {
135 self.id_to_node.len()
136 }
137
138 pub fn contains_node(&self, node: &str) -> bool {
139 self.node_to_id.contains_key(node)
140 }
141
142 pub fn node_name(&self, id: LocalNodeId) -> &str {
144 &self.id_to_node[id.raw(self.partition_tag) as usize]
145 }
146
147 pub fn node_id(&self, name: &str) -> Option<LocalNodeId> {
149 self.node_to_id
150 .get(name)
151 .copied()
152 .map(|raw| LocalNodeId::new(raw, self.partition_tag))
153 }
154
155 pub fn label_name(&self, label_id: u32) -> &str {
157 &self.id_to_label[label_id as usize]
158 }
159
160 pub fn label_id(&self, name: &str) -> Option<u32> {
162 self.label_to_id.get(name).copied()
163 }
164
165 pub fn out_degree(&self, id: LocalNodeId) -> usize {
167 self.dense_iter_out(id.raw(self.partition_tag)).count()
168 }
169
170 pub fn in_degree(&self, id: LocalNodeId) -> usize {
172 self.dense_iter_in(id.raw(self.partition_tag)).count()
173 }
174
175 pub fn edge_count(&self) -> usize {
177 let n = self.id_to_node.len();
178 (0..n as u32).map(|i| self.out_degree(self.local(i))).sum()
179 }
180
181 pub(crate) fn build_dense(
190 edges: &[Vec<(u32, u32)>],
191 governor: Option<&Arc<MemoryGovernor>>,
192 ) -> Result<DenseAdjacency, GraphError> {
193 let n = edges.len();
194 let total: usize = edges.iter().map(|e| e.len()).sum();
195 let reserve_bytes = (n + 1 + 2 * total) * size_of::<u32>();
197 let _budget_guard = governor
198 .map(|g| g.reserve(EngineId::Graph, reserve_bytes))
199 .transpose()?;
200 let mut offsets = Vec::with_capacity(n + 1);
201 let mut targets = Vec::with_capacity(total);
202 let mut labels = Vec::with_capacity(total);
203
204 let mut offset = 0u32;
205 for node_edges in edges {
206 offsets.push(offset);
207 for &(lid, target) in node_edges {
208 targets.push(target);
209 labels.push(lid);
210 }
211 offset += node_edges.len() as u32;
212 }
213 offsets.push(offset);
214
215 Ok(DenseAdjacency {
216 offsets,
217 targets,
218 labels,
219 })
220 }
221
222 pub(crate) fn dense_has_edge(&self, src: u32, label: u32, dst: u32) -> bool {
224 for (lid, target) in self.dense_out_edges(src) {
225 if lid == label && target == dst {
226 return true;
227 }
228 }
229 false
230 }
231
232 pub(crate) fn dense_out_edges(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
234 let idx = node as usize;
235 if idx + 1 >= self.out_offsets.len() {
236 return Vec::new().into_iter();
237 }
238 let start = self.out_offsets[idx] as usize;
239 let end = self.out_offsets[idx + 1] as usize;
240 (start..end)
241 .map(move |i| (self.out_labels[i], self.out_targets[i]))
242 .collect::<Vec<_>>()
243 .into_iter()
244 }
245
246 pub(crate) fn dense_in_edges(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
248 let idx = node as usize;
249 if idx + 1 >= self.in_offsets.len() {
250 return Vec::new().into_iter();
251 }
252 let start = self.in_offsets[idx] as usize;
253 let end = self.in_offsets[idx + 1] as usize;
254 (start..end)
255 .map(move |i| (self.in_labels[i], self.in_targets[i]))
256 .collect::<Vec<_>>()
257 .into_iter()
258 }
259
260 pub(crate) fn dense_iter_out(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
264 let dense = self
265 .dense_out_edges(node)
266 .filter(move |&(lid, dst)| !self.deleted_edges.contains(&(node, lid, dst)));
267 dense.chain(self.buffer_out_iter(node))
268 }
269
270 pub(crate) fn dense_iter_in(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
272 let dense = self
273 .dense_in_edges(node)
274 .filter(move |&(lid, src)| !self.deleted_edges.contains(&(src, lid, node)));
275 dense.chain(self.buffer_in_iter(node))
276 }
277
278 pub(crate) fn buffer_out_iter(&self, node: u32) -> std::vec::IntoIter<(u32, u32)> {
280 let idx = node as usize;
281 if idx < self.buffer_out.len() {
282 self.buffer_out[idx].clone().into_iter()
283 } else {
284 Vec::new().into_iter()
285 }
286 }
287
288 pub(crate) fn buffer_in_iter(&self, node: u32) -> std::vec::IntoIter<(u32, u32)> {
290 let idx = node as usize;
291 if idx < self.buffer_in.len() {
292 self.buffer_in[idx].clone().into_iter()
293 } else {
294 Vec::new().into_iter()
295 }
296 }
297
298 pub fn iter_out_edges(
301 &self,
302 node: LocalNodeId,
303 ) -> impl Iterator<Item = (u32, LocalNodeId)> + '_ {
304 let raw = node.raw(self.partition_tag);
305 let tag = self.partition_tag;
306 self.dense_iter_out(raw)
307 .map(move |(lid, dst)| (lid, LocalNodeId::new(dst, tag)))
308 }
309
310 pub fn iter_in_edges(
312 &self,
313 node: LocalNodeId,
314 ) -> impl Iterator<Item = (u32, LocalNodeId)> + '_ {
315 let raw = node.raw(self.partition_tag);
316 let tag = self.partition_tag;
317 self.dense_iter_in(raw)
318 .map(move |(lid, src)| (lid, LocalNodeId::new(src, tag)))
319 }
320
321 pub fn iter_out_edges_raw(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
332 self.dense_iter_out(node)
333 }
334
335 pub fn iter_in_edges_raw(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
337 self.dense_iter_in(node)
338 }
339
340 pub fn out_degree_raw(&self, node: u32) -> usize {
342 self.dense_iter_out(node).count()
343 }
344
345 pub fn in_degree_raw(&self, node: u32) -> usize {
347 self.dense_iter_in(node).count()
348 }
349
350 pub fn node_name_raw(&self, id: u32) -> &str {
352 &self.id_to_node[id as usize]
353 }
354
355 pub fn node_id_raw(&self, name: &str) -> Option<u32> {
357 self.node_to_id.get(name).copied()
358 }
359}