Skip to main content

nodedb_graph/csr/index/
lookup.rs

1//! Read-side queries: neighbor lookup, counters, degree, iterators,
2//! dense-array helpers, and the `add_node` / `build_dense` utilities.
3
4use super::types::{CsrIndex, Direction};
5
6impl CsrIndex {
7    /// Get immediate neighbors.
8    pub fn neighbors(
9        &self,
10        node: &str,
11        label_filter: Option<&str>,
12        direction: Direction,
13    ) -> Vec<(String, String)> {
14        let Some(&node_id) = self.node_to_id.get(node) else {
15            return Vec::new();
16        };
17        self.record_access(node_id);
18        let label_id = label_filter.and_then(|l| self.label_to_id.get(l).copied());
19
20        let mut result = Vec::new();
21
22        if matches!(direction, Direction::Out | Direction::Both) {
23            for (lid, dst) in self.iter_out_edges(node_id) {
24                if label_id.is_none_or(|f| f == lid) {
25                    result.push((
26                        self.id_to_label[lid as usize].clone(),
27                        self.id_to_node[dst as usize].clone(),
28                    ));
29                }
30            }
31        }
32        if matches!(direction, Direction::In | Direction::Both) {
33            for (lid, src) in self.iter_in_edges(node_id) {
34                if label_id.is_none_or(|f| f == lid) {
35                    result.push((
36                        self.id_to_label[lid as usize].clone(),
37                        self.id_to_node[src as usize].clone(),
38                    ));
39                }
40            }
41        }
42
43        result
44    }
45
46    /// Get neighbors with multi-label filter. Empty labels = all edges.
47    pub fn neighbors_multi(
48        &self,
49        node: &str,
50        label_filters: &[&str],
51        direction: Direction,
52    ) -> Vec<(String, String)> {
53        let Some(&node_id) = self.node_to_id.get(node) else {
54            return Vec::new();
55        };
56        self.record_access(node_id);
57        let label_ids: Vec<u32> = label_filters
58            .iter()
59            .filter_map(|l| self.label_to_id.get(*l).copied())
60            .collect();
61        let match_label = |lid: u32| label_ids.is_empty() || label_ids.contains(&lid);
62
63        let mut result = Vec::new();
64
65        if matches!(direction, Direction::Out | Direction::Both) {
66            for (lid, dst) in self.iter_out_edges(node_id) {
67                if match_label(lid) {
68                    result.push((
69                        self.id_to_label[lid as usize].clone(),
70                        self.id_to_node[dst as usize].clone(),
71                    ));
72                }
73            }
74        }
75        if matches!(direction, Direction::In | Direction::Both) {
76            for (lid, src) in self.iter_in_edges(node_id) {
77                if match_label(lid) {
78                    result.push((
79                        self.id_to_label[lid as usize].clone(),
80                        self.id_to_node[src as usize].clone(),
81                    ));
82                }
83            }
84        }
85
86        result
87    }
88
89    /// Add a node without any edges (used for isolated/dangling nodes).
90    /// Returns the dense node ID. Idempotent — returns existing ID if present.
91    pub fn add_node(&mut self, name: &str) -> u32 {
92        self.ensure_node(name)
93    }
94
95    pub fn node_count(&self) -> usize {
96        self.id_to_node.len()
97    }
98
99    pub fn contains_node(&self, node: &str) -> bool {
100        self.node_to_id.contains_key(node)
101    }
102
103    /// Get the string node ID for a dense node index.
104    pub fn node_name(&self, dense_id: u32) -> &str {
105        &self.id_to_node[dense_id as usize]
106    }
107
108    /// Look up the dense node ID for a string node ID.
109    pub fn node_id(&self, name: &str) -> Option<u32> {
110        self.node_to_id.get(name).copied()
111    }
112
113    /// Get the string label for a dense label index.
114    pub fn label_name(&self, label_id: u32) -> &str {
115        &self.id_to_label[label_id as usize]
116    }
117
118    /// Look up the dense label ID for a string label.
119    pub fn label_id(&self, name: &str) -> Option<u32> {
120        self.label_to_id.get(name).copied()
121    }
122
123    /// Out-degree of a node (including buffer, excluding deleted).
124    pub fn out_degree(&self, node_id: u32) -> usize {
125        self.iter_out_edges(node_id).count()
126    }
127
128    /// In-degree of a node.
129    pub fn in_degree(&self, node_id: u32) -> usize {
130        self.iter_in_edges(node_id).count()
131    }
132
133    /// Total edge count (dense + buffer - deleted). O(V).
134    pub fn edge_count(&self) -> usize {
135        let n = self.id_to_node.len();
136        (0..n).map(|i| self.out_degree(i as u32)).sum()
137    }
138
139    // ── Internal helpers ──
140
141    /// Build contiguous offset/target/label arrays from per-node edge lists.
142    pub(crate) fn build_dense(edges: &[Vec<(u32, u32)>]) -> (Vec<u32>, Vec<u32>, Vec<u32>) {
143        let n = edges.len();
144        let total: usize = edges.iter().map(|e| e.len()).sum();
145        let mut offsets = Vec::with_capacity(n + 1);
146        let mut targets = Vec::with_capacity(total);
147        let mut labels = Vec::with_capacity(total);
148
149        let mut offset = 0u32;
150        for node_edges in edges {
151            offsets.push(offset);
152            for &(lid, target) in node_edges {
153                targets.push(target);
154                labels.push(lid);
155            }
156            offset += node_edges.len() as u32;
157        }
158        offsets.push(offset);
159
160        (offsets, targets, labels)
161    }
162
163    /// Check if a specific edge exists in the dense CSR.
164    pub(crate) fn dense_has_edge(&self, src: u32, label: u32, dst: u32) -> bool {
165        for (lid, target) in self.dense_out_edges(src) {
166            if lid == label && target == dst {
167                return true;
168            }
169        }
170        false
171    }
172
173    /// Iterate dense outbound edges for a node.
174    pub(crate) fn dense_out_edges(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
175        let idx = node as usize;
176        if idx + 1 >= self.out_offsets.len() {
177            return Vec::new().into_iter();
178        }
179        let start = self.out_offsets[idx] as usize;
180        let end = self.out_offsets[idx + 1] as usize;
181        (start..end)
182            .map(move |i| (self.out_labels[i], self.out_targets[i]))
183            .collect::<Vec<_>>()
184            .into_iter()
185    }
186
187    /// Iterate dense inbound edges for a node.
188    pub(crate) fn dense_in_edges(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
189        let idx = node as usize;
190        if idx + 1 >= self.in_offsets.len() {
191            return Vec::new().into_iter();
192        }
193        let start = self.in_offsets[idx] as usize;
194        let end = self.in_offsets[idx + 1] as usize;
195        (start..end)
196            .map(move |i| (self.in_labels[i], self.in_targets[i]))
197            .collect::<Vec<_>>()
198            .into_iter()
199    }
200
201    /// Iterate all outbound edges for a node (dense + buffer, minus deleted).
202    pub fn iter_out_edges(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
203        let idx = node as usize;
204        let dense = self
205            .dense_out_edges(node)
206            .filter(move |&(lid, dst)| !self.deleted_edges.contains(&(node, lid, dst)));
207        let buffer = if idx < self.buffer_out.len() {
208            self.buffer_out[idx].to_vec()
209        } else {
210            Vec::new()
211        };
212        dense.chain(buffer)
213    }
214
215    /// Iterate all inbound edges for a node (dense + buffer, minus deleted).
216    pub fn iter_in_edges(&self, node: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
217        let idx = node as usize;
218        let dense = self
219            .dense_in_edges(node)
220            .filter(move |&(lid, src)| !self.deleted_edges.contains(&(src, lid, node)));
221        let buffer = if idx < self.buffer_in.len() {
222            self.buffer_in[idx].to_vec()
223        } else {
224            Vec::new()
225        };
226        dense.chain(buffer)
227    }
228}