Skip to main content

grafeo_core/graph/lpg/
node.rs

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