Skip to main content

grafeo_core/graph/lpg/store/
schema.rs

1//! Schema, label, edge-type, and property-key methods for [`LpgStore`].
2
3use super::LpgStore;
4use grafeo_common::types::NodeId;
5use grafeo_common::utils::hash::FxHashMap;
6
7impl LpgStore {
8    /// Adds a label to a node.
9    ///
10    /// Returns true if the label was added, false if the node doesn't exist
11    /// or already has the label.
12    #[cfg(not(feature = "tiered-storage"))]
13    pub fn add_label(&self, node_id: NodeId, label: &str) -> bool {
14        let epoch = self.current_epoch();
15
16        // Check if node exists
17        let nodes = self.nodes.read();
18        if let Some(chain) = nodes.get(&node_id) {
19            if chain.visible_at(epoch).map_or(true, |r| r.is_deleted()) {
20                return false;
21            }
22        } else {
23            return false;
24        }
25        drop(nodes);
26
27        // Get or create label ID
28        let label_id = self.get_or_create_label_id(label);
29
30        // Add to node_labels map
31        let mut node_labels = self.node_labels.write();
32        let label_set = node_labels.entry(node_id).or_default();
33
34        if label_set.contains(&label_id) {
35            return false; // Already has this label
36        }
37
38        label_set.insert(label_id);
39        drop(node_labels);
40
41        // Add to label_index
42        let mut index = self.label_index.write();
43        if (label_id as usize) >= index.len() {
44            index.resize(label_id as usize + 1, FxHashMap::default());
45        }
46        index[label_id as usize].insert(node_id, ());
47
48        // Update label count in node record
49        if let Some(chain) = self.nodes.write().get_mut(&node_id)
50            && let Some(record) = chain.latest_mut()
51        {
52            let count = self.node_labels.read().get(&node_id).map_or(0, |s| s.len());
53            record.set_label_count(count as u16);
54        }
55
56        true
57    }
58
59    /// Adds a label to a node.
60    /// (Tiered storage version)
61    #[cfg(feature = "tiered-storage")]
62    pub fn add_label(&self, node_id: NodeId, label: &str) -> bool {
63        let epoch = self.current_epoch();
64
65        // Check if node exists
66        let versions = self.node_versions.read();
67        if let Some(index) = versions.get(&node_id) {
68            if let Some(vref) = index.visible_at(epoch) {
69                if let Some(record) = self.read_node_record(&vref) {
70                    if record.is_deleted() {
71                        return false;
72                    }
73                } else {
74                    return false;
75                }
76            } else {
77                return false;
78            }
79        } else {
80            return false;
81        }
82        drop(versions);
83
84        // Get or create label ID
85        let label_id = self.get_or_create_label_id(label);
86
87        // Add to node_labels map
88        let mut node_labels = self.node_labels.write();
89        let label_set = node_labels.entry(node_id).or_default();
90
91        if label_set.contains(&label_id) {
92            return false; // Already has this label
93        }
94
95        label_set.insert(label_id);
96        drop(node_labels);
97
98        // Add to label_index
99        let mut index = self.label_index.write();
100        if (label_id as usize) >= index.len() {
101            index.resize(label_id as usize + 1, FxHashMap::default());
102        }
103        index[label_id as usize].insert(node_id, ());
104
105        // Note: label_count in record is not updated for tiered storage.
106        // The record is immutable once allocated in the arena.
107
108        true
109    }
110
111    /// Removes a label from a node.
112    ///
113    /// Returns true if the label was removed, false if the node doesn't exist
114    /// or doesn't have the label.
115    #[cfg(not(feature = "tiered-storage"))]
116    pub fn remove_label(&self, node_id: NodeId, label: &str) -> bool {
117        let epoch = self.current_epoch();
118
119        // Check if node exists
120        let nodes = self.nodes.read();
121        if let Some(chain) = nodes.get(&node_id) {
122            if chain.visible_at(epoch).map_or(true, |r| r.is_deleted()) {
123                return false;
124            }
125        } else {
126            return false;
127        }
128        drop(nodes);
129
130        // Get label ID
131        let label_id = {
132            let label_ids = self.label_to_id.read();
133            match label_ids.get(label) {
134                Some(&id) => id,
135                None => return false, // Label doesn't exist
136            }
137        };
138
139        // Remove from node_labels map
140        let mut node_labels = self.node_labels.write();
141        if let Some(label_set) = node_labels.get_mut(&node_id) {
142            if !label_set.remove(&label_id) {
143                return false; // Node doesn't have this label
144            }
145        } else {
146            return false;
147        }
148        drop(node_labels);
149
150        // Remove from label_index
151        let mut index = self.label_index.write();
152        if (label_id as usize) < index.len() {
153            index[label_id as usize].remove(&node_id);
154        }
155
156        // Update label count in node record
157        if let Some(chain) = self.nodes.write().get_mut(&node_id)
158            && let Some(record) = chain.latest_mut()
159        {
160            let count = self.node_labels.read().get(&node_id).map_or(0, |s| s.len());
161            record.set_label_count(count as u16);
162        }
163
164        true
165    }
166
167    /// Removes a label from a node.
168    /// (Tiered storage version)
169    #[cfg(feature = "tiered-storage")]
170    pub fn remove_label(&self, node_id: NodeId, label: &str) -> bool {
171        let epoch = self.current_epoch();
172
173        // Check if node exists
174        let versions = self.node_versions.read();
175        if let Some(index) = versions.get(&node_id) {
176            if let Some(vref) = index.visible_at(epoch) {
177                if let Some(record) = self.read_node_record(&vref) {
178                    if record.is_deleted() {
179                        return false;
180                    }
181                } else {
182                    return false;
183                }
184            } else {
185                return false;
186            }
187        } else {
188            return false;
189        }
190        drop(versions);
191
192        // Get label ID
193        let label_id = {
194            let label_ids = self.label_to_id.read();
195            match label_ids.get(label) {
196                Some(&id) => id,
197                None => return false, // Label doesn't exist
198            }
199        };
200
201        // Remove from node_labels map
202        let mut node_labels = self.node_labels.write();
203        if let Some(label_set) = node_labels.get_mut(&node_id) {
204            if !label_set.remove(&label_id) {
205                return false; // Node doesn't have this label
206            }
207        } else {
208            return false;
209        }
210        drop(node_labels);
211
212        // Remove from label_index
213        let mut index = self.label_index.write();
214        if (label_id as usize) < index.len() {
215            index[label_id as usize].remove(&node_id);
216        }
217
218        // Note: label_count in record is not updated for tiered storage.
219
220        true
221    }
222
223    /// Returns all nodes with a specific label.
224    ///
225    /// Uses the label index for O(1) lookup per label. Returns a snapshot -
226    /// concurrent modifications won't affect the returned vector. Results are
227    /// sorted by NodeId for deterministic iteration order.
228    pub fn nodes_by_label(&self, label: &str) -> Vec<NodeId> {
229        let label_to_id = self.label_to_id.read();
230        if let Some(&label_id) = label_to_id.get(label) {
231            let index = self.label_index.read();
232            if let Some(set) = index.get(label_id as usize) {
233                let mut ids: Vec<NodeId> = set.keys().copied().collect();
234                ids.sort_unstable();
235                return ids;
236            }
237        }
238        Vec::new()
239    }
240
241    /// Returns the number of distinct labels in the store.
242    #[must_use]
243    pub fn label_count(&self) -> usize {
244        self.id_to_label.read().len()
245    }
246
247    /// Returns the number of distinct property keys in the store.
248    ///
249    /// This counts unique property keys across both nodes and edges.
250    #[must_use]
251    pub fn property_key_count(&self) -> usize {
252        let node_keys = self.node_properties.column_count();
253        let edge_keys = self.edge_properties.column_count();
254        // Note: This may count some keys twice if the same key is used
255        // for both nodes and edges. A more precise count would require
256        // tracking unique keys across both storages.
257        node_keys + edge_keys
258    }
259
260    /// Returns the number of distinct edge types in the store.
261    #[must_use]
262    pub fn edge_type_count(&self) -> usize {
263        self.id_to_edge_type.read().len()
264    }
265
266    /// Returns all label names in the database.
267    pub fn all_labels(&self) -> Vec<String> {
268        self.id_to_label
269            .read()
270            .iter()
271            .map(|s| s.to_string())
272            .collect()
273    }
274
275    /// Returns all edge type names in the database.
276    pub fn all_edge_types(&self) -> Vec<String> {
277        self.id_to_edge_type
278            .read()
279            .iter()
280            .map(|s| s.to_string())
281            .collect()
282    }
283
284    /// Returns all property keys used in the database.
285    pub fn all_property_keys(&self) -> Vec<String> {
286        let mut keys = std::collections::HashSet::new();
287        for key in self.node_properties.keys() {
288            keys.insert(key.to_string());
289        }
290        for key in self.edge_properties.keys() {
291            keys.insert(key.to_string());
292        }
293        keys.into_iter().collect()
294    }
295}