Skip to main content

nodedb_graph/csr/index/
interning.rs

1//! Node / label string↔id interning and the node-label bitset.
2
3use std::collections::hash_map::Entry;
4
5use super::types::CsrIndex;
6
7impl CsrIndex {
8    /// Get or create a dense ID for a node.
9    pub(crate) fn ensure_node(&mut self, node: &str) -> u32 {
10        match self.node_to_id.entry(node.to_string()) {
11            Entry::Occupied(e) => *e.get(),
12            Entry::Vacant(e) => {
13                let id = self.id_to_node.len() as u32;
14                e.insert(id);
15                self.id_to_node.push(node.to_string());
16                // Extend dense offsets (new node has 0 edges in dense part).
17                self.out_offsets
18                    .push(*self.out_offsets.last().unwrap_or(&0));
19                self.in_offsets.push(*self.in_offsets.last().unwrap_or(&0));
20                // Extend buffer and access tracking.
21                self.buffer_out.push(Vec::new());
22                self.buffer_in.push(Vec::new());
23                self.buffer_out_weights.push(Vec::new());
24                self.buffer_in_weights.push(Vec::new());
25                self.node_label_bits.push(0);
26                self.access_counts.push(std::cell::Cell::new(0));
27                id
28            }
29        }
30    }
31
32    /// Get or create a dense ID for a label.
33    ///
34    /// Returns `Err(GraphError::LabelOverflow)` if the id space is
35    /// exhausted (`MAX_EDGE_LABELS` distinct labels already interned).
36    /// The DSL ingress layer enforces a far tighter user-facing cap,
37    /// so this branch is effectively unreachable in practice — but the
38    /// Result is propagated so the ceiling failure mode is typed and
39    /// loud rather than a silent cast.
40    pub(crate) fn ensure_label(&mut self, label: &str) -> Result<u32, crate::GraphError> {
41        match self.label_to_id.entry(label.to_string()) {
42            Entry::Occupied(e) => Ok(*e.get()),
43            Entry::Vacant(e) => {
44                let len = self.id_to_label.len();
45                if len >= crate::MAX_EDGE_LABELS {
46                    return Err(crate::GraphError::LabelOverflow { used: len });
47                }
48                let id = len as u32;
49                e.insert(id);
50                self.id_to_label.push(label.to_string());
51                Ok(id)
52            }
53        }
54    }
55
56    // ── Node label methods ──
57
58    /// Get or create a node label ID (max 64 labels).
59    fn ensure_node_label(&mut self, label: &str) -> Option<u8> {
60        if let Some(&id) = self.node_label_to_id.get(label) {
61            return Some(id);
62        }
63        let id = self.node_label_names.len();
64        if id >= 64 {
65            return None; // bitset limit
66        }
67        let id = id as u8;
68        self.node_label_to_id.insert(label.to_string(), id);
69        self.node_label_names.push(label.to_string());
70        Some(id)
71    }
72
73    /// Add a label to a node. Returns false if the 64-label limit is hit.
74    pub fn add_node_label(&mut self, node: &str, label: &str) -> bool {
75        let node_id = self.ensure_node(node);
76        let Some(label_id) = self.ensure_node_label(label) else {
77            return false;
78        };
79        self.node_label_bits[node_id as usize] |= 1u64 << label_id;
80        true
81    }
82
83    /// Remove a label from a node.
84    pub fn remove_node_label(&mut self, node: &str, label: &str) {
85        let Some(&node_id) = self.node_to_id.get(node) else {
86            return;
87        };
88        let Some(&label_id) = self.node_label_to_id.get(label) else {
89            return;
90        };
91        self.node_label_bits[node_id as usize] &= !(1u64 << label_id);
92    }
93
94    /// Check if a node has a specific label.
95    pub fn node_has_label(&self, node_id: u32, label: &str) -> bool {
96        let Some(&label_id) = self.node_label_to_id.get(label) else {
97            return false;
98        };
99        let bits = self
100            .node_label_bits
101            .get(node_id as usize)
102            .copied()
103            .unwrap_or(0);
104        bits & (1u64 << label_id) != 0
105    }
106
107    /// Get all label names for a node.
108    pub fn node_labels(&self, node_id: u32) -> Vec<&str> {
109        let bits = self
110            .node_label_bits
111            .get(node_id as usize)
112            .copied()
113            .unwrap_or(0);
114        if bits == 0 {
115            return Vec::new();
116        }
117        let mut labels = Vec::new();
118        for (i, name) in self.node_label_names.iter().enumerate() {
119            if bits & (1u64 << i) != 0 {
120                labels.push(name.as_str());
121            }
122        }
123        labels
124    }
125}