Skip to main content

grafeo_core/graph/lpg/
node.rs

1//! Node types for the LPG model.
2//!
3//! Two representations here: [`Node`] is the friendly one with all the data,
4//! [`NodeRecord`] is the compact 32-byte struct for storage.
5
6use std::collections::BTreeMap;
7use std::sync::Arc;
8
9use grafeo_common::types::{EpochId, NodeId, PropertyKey, Value};
10use serde::{Deserialize, Serialize};
11
12/// A node with its labels and properties fully loaded.
13///
14/// This is what you get back from [`LpgStore::get_node()`](super::LpgStore::get_node).
15/// For bulk operations, the store works with [`NodeRecord`] internally.
16///
17/// # Example
18///
19/// ```
20/// use grafeo_core::graph::lpg::Node;
21/// use grafeo_common::types::NodeId;
22///
23/// let mut person = Node::new(NodeId::new(1));
24/// person.add_label("Person");
25/// person.set_property("name", "Alice");
26/// person.set_property("age", 30i64);
27///
28/// assert!(person.has_label("Person"));
29/// ```
30#[derive(Debug, Clone)]
31pub struct Node {
32    /// Unique identifier.
33    pub id: NodeId,
34    /// Labels attached to this node.
35    pub labels: Vec<Arc<str>>,
36    /// Properties stored on this node.
37    pub properties: BTreeMap<PropertyKey, Value>,
38}
39
40impl Node {
41    /// Creates a new node with the given ID.
42    #[must_use]
43    pub fn new(id: NodeId) -> Self {
44        Self {
45            id,
46            labels: Vec::new(),
47            properties: BTreeMap::new(),
48        }
49    }
50
51    /// Creates a new node with labels.
52    #[must_use]
53    pub fn with_labels(id: NodeId, labels: impl IntoIterator<Item = impl Into<Arc<str>>>) -> Self {
54        Self {
55            id,
56            labels: labels.into_iter().map(Into::into).collect(),
57            properties: BTreeMap::new(),
58        }
59    }
60
61    /// Adds a label to this node.
62    pub fn add_label(&mut self, label: impl Into<Arc<str>>) {
63        let label = label.into();
64        if !self.labels.iter().any(|l| l.as_ref() == label.as_ref()) {
65            self.labels.push(label);
66        }
67    }
68
69    /// Removes a label from this node.
70    pub fn remove_label(&mut self, label: &str) -> bool {
71        if let Some(pos) = self.labels.iter().position(|l| l.as_ref() == label) {
72            self.labels.remove(pos);
73            true
74        } else {
75            false
76        }
77    }
78
79    /// Checks if this node has the given label.
80    #[must_use]
81    pub fn has_label(&self, label: &str) -> bool {
82        self.labels.iter().any(|l| l.as_ref() == label)
83    }
84
85    /// Sets a property on this node.
86    pub fn set_property(&mut self, key: impl Into<PropertyKey>, value: impl Into<Value>) {
87        self.properties.insert(key.into(), value.into());
88    }
89
90    /// Gets a property from this node.
91    #[must_use]
92    pub fn get_property(&self, key: &str) -> Option<&Value> {
93        self.properties.get(&PropertyKey::new(key))
94    }
95
96    /// Removes a property from this node.
97    pub fn remove_property(&mut self, key: &str) -> Option<Value> {
98        self.properties.remove(&PropertyKey::new(key))
99    }
100}
101
102/// The compact storage format for a node - exactly 32 bytes.
103///
104/// You won't interact with this directly most of the time. It's what lives
105/// in memory for each node, with properties and labels stored separately.
106/// The 32-byte size means two records fit in a cache line.
107///
108/// Fields are ordered to minimize padding: u64s first, then u32, then u16s.
109#[repr(C)]
110#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
111pub struct NodeRecord {
112    /// Unique node identifier.
113    pub id: NodeId,
114    /// Epoch this record was created in.
115    pub epoch: EpochId,
116    /// Offset into the property arena.
117    pub props_offset: u32,
118    /// Number of labels on this node (labels stored externally).
119    pub label_count: u16,
120    /// Reserved for future use / alignment.
121    pub _reserved: u16,
122    /// Number of properties.
123    pub props_count: u16,
124    /// Flags (deleted, has_version, etc.).
125    pub flags: NodeFlags,
126    /// Padding to maintain 32-byte size.
127    pub _padding: u32,
128}
129
130impl NodeRecord {
131    /// Flag indicating the node is deleted.
132    pub const FLAG_DELETED: u16 = 1 << 0;
133    /// Flag indicating the node has version history.
134    pub const FLAG_HAS_VERSION: u16 = 1 << 1;
135
136    /// Creates a new node record.
137    #[must_use]
138    pub const fn new(id: NodeId, epoch: EpochId) -> Self {
139        Self {
140            id,
141            label_count: 0,
142            _reserved: 0,
143            props_offset: 0,
144            props_count: 0,
145            flags: NodeFlags(0),
146            epoch,
147            _padding: 0,
148        }
149    }
150
151    /// Checks if this node is deleted.
152    #[must_use]
153    pub const fn is_deleted(&self) -> bool {
154        self.flags.contains(Self::FLAG_DELETED)
155    }
156
157    /// Marks this node as deleted.
158    pub fn set_deleted(&mut self, deleted: bool) {
159        if deleted {
160            self.flags.set(Self::FLAG_DELETED);
161        } else {
162            self.flags.clear(Self::FLAG_DELETED);
163        }
164    }
165
166    /// Returns the number of labels on this node.
167    #[must_use]
168    pub const fn label_count(&self) -> u16 {
169        self.label_count
170    }
171
172    /// Sets the label count.
173    pub fn set_label_count(&mut self, count: u16) {
174        self.label_count = count;
175    }
176}
177
178/// Bit flags packed into a node record.
179///
180/// Check flags with [`contains()`](Self::contains), set with [`set()`](Self::set).
181#[repr(transparent)]
182#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
183pub struct NodeFlags(pub u16);
184
185impl NodeFlags {
186    /// Checks if a flag is set.
187    #[must_use]
188    pub const fn contains(&self, flag: u16) -> bool {
189        (self.0 & flag) != 0
190    }
191
192    /// Sets a flag.
193    pub fn set(&mut self, flag: u16) {
194        self.0 |= flag;
195    }
196
197    /// Clears a flag.
198    pub fn clear(&mut self, flag: u16) {
199        self.0 &= !flag;
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn test_node_record_size() {
209        // Ensure NodeRecord is exactly 32 bytes
210        assert_eq!(std::mem::size_of::<NodeRecord>(), 32);
211    }
212
213    #[test]
214    fn test_node_labels() {
215        let mut node = Node::new(NodeId::new(1));
216
217        node.add_label("Person");
218        assert!(node.has_label("Person"));
219        assert!(!node.has_label("Animal"));
220
221        node.add_label("Employee");
222        assert!(node.has_label("Employee"));
223
224        // Adding same label again should be idempotent
225        node.add_label("Person");
226        assert_eq!(node.labels.len(), 2);
227
228        // Remove label
229        assert!(node.remove_label("Person"));
230        assert!(!node.has_label("Person"));
231        assert!(!node.remove_label("NotExists"));
232    }
233
234    #[test]
235    fn test_node_properties() {
236        let mut node = Node::new(NodeId::new(1));
237
238        node.set_property("name", "Alice");
239        node.set_property("age", 30i64);
240
241        assert_eq!(
242            node.get_property("name").and_then(|v| v.as_str()),
243            Some("Alice")
244        );
245        assert_eq!(
246            node.get_property("age").and_then(|v| v.as_int64()),
247            Some(30)
248        );
249        assert!(node.get_property("missing").is_none());
250
251        let removed = node.remove_property("name");
252        assert!(removed.is_some());
253        assert!(node.get_property("name").is_none());
254    }
255
256    #[test]
257    fn test_node_record_flags() {
258        let mut record = NodeRecord::new(NodeId::new(1), EpochId::INITIAL);
259
260        assert!(!record.is_deleted());
261        record.set_deleted(true);
262        assert!(record.is_deleted());
263        record.set_deleted(false);
264        assert!(!record.is_deleted());
265    }
266
267    #[test]
268    fn test_node_record_label_count() {
269        let mut record = NodeRecord::new(NodeId::new(1), EpochId::INITIAL);
270
271        assert_eq!(record.label_count(), 0);
272        record.set_label_count(5);
273        assert_eq!(record.label_count(), 5);
274
275        // Can handle large label counts (no 64 limit)
276        record.set_label_count(1000);
277        assert_eq!(record.label_count(), 1000);
278    }
279}