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