Skip to main content

graphos_common/types/
id.rs

1//! Identifier types for graph elements and transactions.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Unique identifier for a node in the graph.
7///
8/// Internally represented as a `u64`, allowing for up to 2^64 nodes.
9/// NodeIds are assigned sequentially and are never reused within an epoch.
10#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
11#[repr(transparent)]
12pub struct NodeId(pub u64);
13
14impl NodeId {
15    /// The invalid/null node ID.
16    pub const INVALID: Self = Self(u64::MAX);
17
18    /// Creates a new NodeId from a raw u64 value.
19    #[inline]
20    #[must_use]
21    pub const fn new(id: u64) -> Self {
22        Self(id)
23    }
24
25    /// Returns the raw u64 value.
26    #[inline]
27    #[must_use]
28    pub const fn as_u64(self) -> u64 {
29        self.0
30    }
31
32    /// Checks if this is a valid node ID.
33    #[inline]
34    #[must_use]
35    pub const fn is_valid(self) -> bool {
36        self.0 != u64::MAX
37    }
38}
39
40impl fmt::Debug for NodeId {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        if self.is_valid() {
43            write!(f, "NodeId({})", self.0)
44        } else {
45            write!(f, "NodeId(INVALID)")
46        }
47    }
48}
49
50impl fmt::Display for NodeId {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        write!(f, "{}", self.0)
53    }
54}
55
56impl From<u64> for NodeId {
57    fn from(id: u64) -> Self {
58        Self(id)
59    }
60}
61
62impl From<NodeId> for u64 {
63    fn from(id: NodeId) -> Self {
64        id.0
65    }
66}
67
68/// Unique identifier for an edge in the graph.
69///
70/// Internally represented as a `u64`, allowing for up to 2^64 edges.
71#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
72#[repr(transparent)]
73pub struct EdgeId(pub u64);
74
75impl EdgeId {
76    /// The invalid/null edge ID.
77    pub const INVALID: Self = Self(u64::MAX);
78
79    /// Creates a new EdgeId from a raw u64 value.
80    #[inline]
81    #[must_use]
82    pub const fn new(id: u64) -> Self {
83        Self(id)
84    }
85
86    /// Returns the raw u64 value.
87    #[inline]
88    #[must_use]
89    pub const fn as_u64(self) -> u64 {
90        self.0
91    }
92
93    /// Checks if this is a valid edge ID.
94    #[inline]
95    #[must_use]
96    pub const fn is_valid(self) -> bool {
97        self.0 != u64::MAX
98    }
99}
100
101impl fmt::Debug for EdgeId {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        if self.is_valid() {
104            write!(f, "EdgeId({})", self.0)
105        } else {
106            write!(f, "EdgeId(INVALID)")
107        }
108    }
109}
110
111impl fmt::Display for EdgeId {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "{}", self.0)
114    }
115}
116
117impl From<u64> for EdgeId {
118    fn from(id: u64) -> Self {
119        Self(id)
120    }
121}
122
123impl From<EdgeId> for u64 {
124    fn from(id: EdgeId) -> Self {
125        id.0
126    }
127}
128
129/// Unique identifier for a transaction.
130///
131/// Transaction IDs are monotonically increasing and used for MVCC versioning.
132#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
133#[repr(transparent)]
134pub struct TxId(pub u64);
135
136impl TxId {
137    /// The invalid/null transaction ID.
138    pub const INVALID: Self = Self(0);
139
140    /// Creates a new TxId from a raw u64 value.
141    #[inline]
142    #[must_use]
143    pub const fn new(id: u64) -> Self {
144        Self(id)
145    }
146
147    /// Returns the raw u64 value.
148    #[inline]
149    #[must_use]
150    pub const fn as_u64(self) -> u64 {
151        self.0
152    }
153
154    /// Returns the next transaction ID.
155    #[inline]
156    #[must_use]
157    pub const fn next(self) -> Self {
158        Self(self.0 + 1)
159    }
160
161    /// Checks if this is a valid transaction ID.
162    #[inline]
163    #[must_use]
164    pub const fn is_valid(self) -> bool {
165        self.0 != 0
166    }
167}
168
169impl fmt::Debug for TxId {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        if self.is_valid() {
172            write!(f, "TxId({})", self.0)
173        } else {
174            write!(f, "TxId(INVALID)")
175        }
176    }
177}
178
179impl fmt::Display for TxId {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        write!(f, "{}", self.0)
182    }
183}
184
185impl From<u64> for TxId {
186    fn from(id: u64) -> Self {
187        Self(id)
188    }
189}
190
191impl From<TxId> for u64 {
192    fn from(id: TxId) -> Self {
193        id.0
194    }
195}
196
197/// Unique identifier for an epoch in the arena allocator.
198///
199/// Epochs are used for structural sharing in MVCC. Each write transaction
200/// creates a new epoch, and old epochs can be garbage collected when no
201/// readers reference them.
202#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
203#[repr(transparent)]
204pub struct EpochId(pub u64);
205
206impl EpochId {
207    /// The initial epoch (epoch 0).
208    pub const INITIAL: Self = Self(0);
209
210    /// Creates a new EpochId from a raw u64 value.
211    #[inline]
212    #[must_use]
213    pub const fn new(id: u64) -> Self {
214        Self(id)
215    }
216
217    /// Returns the raw u64 value.
218    #[inline]
219    #[must_use]
220    pub const fn as_u64(self) -> u64 {
221        self.0
222    }
223
224    /// Returns the next epoch ID.
225    #[inline]
226    #[must_use]
227    pub const fn next(self) -> Self {
228        Self(self.0 + 1)
229    }
230
231    /// Checks if this epoch is visible at the given epoch.
232    ///
233    /// An epoch is visible if it was created before or at the viewing epoch.
234    #[inline]
235    #[must_use]
236    pub const fn is_visible_at(self, viewing_epoch: Self) -> bool {
237        self.0 <= viewing_epoch.0
238    }
239}
240
241impl fmt::Debug for EpochId {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        write!(f, "EpochId({})", self.0)
244    }
245}
246
247impl fmt::Display for EpochId {
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        write!(f, "{}", self.0)
250    }
251}
252
253impl From<u64> for EpochId {
254    fn from(id: u64) -> Self {
255        Self(id)
256    }
257}
258
259impl From<EpochId> for u64 {
260    fn from(id: EpochId) -> Self {
261        id.0
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn test_node_id_basic() {
271        let id = NodeId::new(42);
272        assert_eq!(id.as_u64(), 42);
273        assert!(id.is_valid());
274        assert!(!NodeId::INVALID.is_valid());
275    }
276
277    #[test]
278    fn test_node_id_ordering() {
279        let id1 = NodeId::new(1);
280        let id2 = NodeId::new(2);
281        assert!(id1 < id2);
282    }
283
284    #[test]
285    fn test_edge_id_basic() {
286        let id = EdgeId::new(100);
287        assert_eq!(id.as_u64(), 100);
288        assert!(id.is_valid());
289        assert!(!EdgeId::INVALID.is_valid());
290    }
291
292    #[test]
293    fn test_tx_id_basic() {
294        let id = TxId::new(1);
295        assert!(id.is_valid());
296        assert!(!TxId::INVALID.is_valid());
297        assert_eq!(id.next(), TxId::new(2));
298    }
299
300    #[test]
301    fn test_epoch_visibility() {
302        let e1 = EpochId::new(1);
303        let e2 = EpochId::new(2);
304        let e3 = EpochId::new(3);
305
306        // e1 is visible at e2 and e3
307        assert!(e1.is_visible_at(e2));
308        assert!(e1.is_visible_at(e3));
309
310        // e2 is visible at e2 and e3, but not e1
311        assert!(!e2.is_visible_at(e1));
312        assert!(e2.is_visible_at(e2));
313        assert!(e2.is_visible_at(e3));
314
315        // e3 is only visible at e3
316        assert!(!e3.is_visible_at(e1));
317        assert!(!e3.is_visible_at(e2));
318        assert!(e3.is_visible_at(e3));
319    }
320
321    #[test]
322    fn test_epoch_next() {
323        let e = EpochId::INITIAL;
324        assert_eq!(e.next(), EpochId::new(1));
325        assert_eq!(e.next().next(), EpochId::new(2));
326    }
327
328    #[test]
329    fn test_conversions() {
330        // NodeId
331        let node_id: NodeId = 42u64.into();
332        let raw: u64 = node_id.into();
333        assert_eq!(raw, 42);
334
335        // EdgeId
336        let edge_id: EdgeId = 100u64.into();
337        let raw: u64 = edge_id.into();
338        assert_eq!(raw, 100);
339
340        // TxId
341        let tx_id: TxId = 1u64.into();
342        let raw: u64 = tx_id.into();
343        assert_eq!(raw, 1);
344
345        // EpochId
346        let epoch_id: EpochId = 5u64.into();
347        let raw: u64 = epoch_id.into();
348        assert_eq!(raw, 5);
349    }
350}