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    /// The system transaction ID used for non-transactional operations.
141    /// System transactions are always visible and committed.
142    pub const SYSTEM: Self = Self(1);
143
144    /// Creates a new TxId from a raw u64 value.
145    #[inline]
146    #[must_use]
147    pub const fn new(id: u64) -> Self {
148        Self(id)
149    }
150
151    /// Returns the raw u64 value.
152    #[inline]
153    #[must_use]
154    pub const fn as_u64(&self) -> u64 {
155        self.0
156    }
157
158    /// Returns the next transaction ID.
159    #[inline]
160    #[must_use]
161    pub const fn next(&self) -> Self {
162        Self(self.0 + 1)
163    }
164
165    /// Checks if this is a valid transaction ID.
166    #[inline]
167    #[must_use]
168    pub const fn is_valid(&self) -> bool {
169        self.0 != 0
170    }
171}
172
173impl fmt::Debug for TxId {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        if self.is_valid() {
176            write!(f, "TxId({})", self.0)
177        } else {
178            write!(f, "TxId(INVALID)")
179        }
180    }
181}
182
183impl fmt::Display for TxId {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        write!(f, "{}", self.0)
186    }
187}
188
189impl From<u64> for TxId {
190    fn from(id: u64) -> Self {
191        Self(id)
192    }
193}
194
195impl From<TxId> for u64 {
196    fn from(id: TxId) -> Self {
197        id.0
198    }
199}
200
201/// Unique identifier for an epoch in the arena allocator.
202///
203/// Epochs are used for structural sharing in MVCC. Each write transaction
204/// creates a new epoch, and old epochs can be garbage collected when no
205/// readers reference them.
206#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
207#[repr(transparent)]
208pub struct EpochId(pub u64);
209
210impl EpochId {
211    /// The initial epoch (epoch 0).
212    pub const INITIAL: Self = Self(0);
213
214    /// Creates a new EpochId from a raw u64 value.
215    #[inline]
216    #[must_use]
217    pub const fn new(id: u64) -> Self {
218        Self(id)
219    }
220
221    /// Returns the raw u64 value.
222    #[inline]
223    #[must_use]
224    pub const fn as_u64(&self) -> u64 {
225        self.0
226    }
227
228    /// Returns the next epoch ID.
229    #[inline]
230    #[must_use]
231    pub const fn next(&self) -> Self {
232        Self(self.0 + 1)
233    }
234
235    /// Checks if this epoch is visible at the given epoch.
236    ///
237    /// An epoch is visible if it was created before or at the viewing epoch.
238    #[inline]
239    #[must_use]
240    pub const fn is_visible_at(&self, viewing_epoch: Self) -> bool {
241        self.0 <= viewing_epoch.0
242    }
243}
244
245impl fmt::Debug for EpochId {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        write!(f, "EpochId({})", self.0)
248    }
249}
250
251impl fmt::Display for EpochId {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        write!(f, "{}", self.0)
254    }
255}
256
257impl From<u64> for EpochId {
258    fn from(id: u64) -> Self {
259        Self(id)
260    }
261}
262
263impl From<EpochId> for u64 {
264    fn from(id: EpochId) -> Self {
265        id.0
266    }
267}
268
269/// Unique identifier for a label in the catalog.
270///
271/// Labels are strings assigned to nodes to categorize them.
272/// The catalog assigns unique IDs for efficient storage and comparison.
273#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
274#[repr(transparent)]
275pub struct LabelId(pub u32);
276
277impl LabelId {
278    /// The invalid/null label ID.
279    pub const INVALID: Self = Self(u32::MAX);
280
281    /// Creates a new LabelId from a raw u32 value.
282    #[inline]
283    #[must_use]
284    pub const fn new(id: u32) -> Self {
285        Self(id)
286    }
287
288    /// Returns the raw u32 value.
289    #[inline]
290    #[must_use]
291    pub const fn as_u32(&self) -> u32 {
292        self.0
293    }
294
295    /// Checks if this is a valid label ID.
296    #[inline]
297    #[must_use]
298    pub const fn is_valid(&self) -> bool {
299        self.0 != u32::MAX
300    }
301}
302
303impl fmt::Debug for LabelId {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        if self.is_valid() {
306            write!(f, "LabelId({})", self.0)
307        } else {
308            write!(f, "LabelId(INVALID)")
309        }
310    }
311}
312
313impl fmt::Display for LabelId {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        write!(f, "{}", self.0)
316    }
317}
318
319impl From<u32> for LabelId {
320    fn from(id: u32) -> Self {
321        Self(id)
322    }
323}
324
325impl From<LabelId> for u32 {
326    fn from(id: LabelId) -> Self {
327        id.0
328    }
329}
330
331/// Unique identifier for a property key in the catalog.
332///
333/// Property keys are strings used as names for node/edge properties.
334/// The catalog assigns unique IDs for efficient storage.
335#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
336#[repr(transparent)]
337pub struct PropertyKeyId(pub u32);
338
339impl PropertyKeyId {
340    /// The invalid/null property key ID.
341    pub const INVALID: Self = Self(u32::MAX);
342
343    /// Creates a new PropertyKeyId from a raw u32 value.
344    #[inline]
345    #[must_use]
346    pub const fn new(id: u32) -> Self {
347        Self(id)
348    }
349
350    /// Returns the raw u32 value.
351    #[inline]
352    #[must_use]
353    pub const fn as_u32(&self) -> u32 {
354        self.0
355    }
356
357    /// Checks if this is a valid property key ID.
358    #[inline]
359    #[must_use]
360    pub const fn is_valid(&self) -> bool {
361        self.0 != u32::MAX
362    }
363}
364
365impl fmt::Debug for PropertyKeyId {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        if self.is_valid() {
368            write!(f, "PropertyKeyId({})", self.0)
369        } else {
370            write!(f, "PropertyKeyId(INVALID)")
371        }
372    }
373}
374
375impl fmt::Display for PropertyKeyId {
376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377        write!(f, "{}", self.0)
378    }
379}
380
381impl From<u32> for PropertyKeyId {
382    fn from(id: u32) -> Self {
383        Self(id)
384    }
385}
386
387impl From<PropertyKeyId> for u32 {
388    fn from(id: PropertyKeyId) -> Self {
389        id.0
390    }
391}
392
393/// Unique identifier for an edge type in the catalog.
394///
395/// Edge types are strings that categorize relationships between nodes.
396/// The catalog assigns unique IDs for efficient storage.
397#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
398#[repr(transparent)]
399pub struct EdgeTypeId(pub u32);
400
401impl EdgeTypeId {
402    /// The invalid/null edge type ID.
403    pub const INVALID: Self = Self(u32::MAX);
404
405    /// Creates a new EdgeTypeId from a raw u32 value.
406    #[inline]
407    #[must_use]
408    pub const fn new(id: u32) -> Self {
409        Self(id)
410    }
411
412    /// Returns the raw u32 value.
413    #[inline]
414    #[must_use]
415    pub const fn as_u32(&self) -> u32 {
416        self.0
417    }
418
419    /// Checks if this is a valid edge type ID.
420    #[inline]
421    #[must_use]
422    pub const fn is_valid(&self) -> bool {
423        self.0 != u32::MAX
424    }
425}
426
427impl fmt::Debug for EdgeTypeId {
428    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429        if self.is_valid() {
430            write!(f, "EdgeTypeId({})", self.0)
431        } else {
432            write!(f, "EdgeTypeId(INVALID)")
433        }
434    }
435}
436
437impl fmt::Display for EdgeTypeId {
438    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439        write!(f, "{}", self.0)
440    }
441}
442
443impl From<u32> for EdgeTypeId {
444    fn from(id: u32) -> Self {
445        Self(id)
446    }
447}
448
449impl From<EdgeTypeId> for u32 {
450    fn from(id: EdgeTypeId) -> Self {
451        id.0
452    }
453}
454
455/// Unique identifier for an index in the catalog.
456#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
457#[repr(transparent)]
458pub struct IndexId(pub u32);
459
460impl IndexId {
461    /// The invalid/null index ID.
462    pub const INVALID: Self = Self(u32::MAX);
463
464    /// Creates a new IndexId from a raw u32 value.
465    #[inline]
466    #[must_use]
467    pub const fn new(id: u32) -> Self {
468        Self(id)
469    }
470
471    /// Returns the raw u32 value.
472    #[inline]
473    #[must_use]
474    pub const fn as_u32(&self) -> u32 {
475        self.0
476    }
477
478    /// Checks if this is a valid index ID.
479    #[inline]
480    #[must_use]
481    pub const fn is_valid(&self) -> bool {
482        self.0 != u32::MAX
483    }
484}
485
486impl fmt::Debug for IndexId {
487    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488        if self.is_valid() {
489            write!(f, "IndexId({})", self.0)
490        } else {
491            write!(f, "IndexId(INVALID)")
492        }
493    }
494}
495
496impl fmt::Display for IndexId {
497    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
498        write!(f, "{}", self.0)
499    }
500}
501
502impl From<u32> for IndexId {
503    fn from(id: u32) -> Self {
504        Self(id)
505    }
506}
507
508impl From<IndexId> for u32 {
509    fn from(id: IndexId) -> Self {
510        id.0
511    }
512}
513
514#[cfg(test)]
515mod tests {
516    use super::*;
517
518    #[test]
519    fn test_node_id_basic() {
520        let id = NodeId::new(42);
521        assert_eq!(id.as_u64(), 42);
522        assert!(id.is_valid());
523        assert!(!NodeId::INVALID.is_valid());
524    }
525
526    #[test]
527    fn test_node_id_ordering() {
528        let id1 = NodeId::new(1);
529        let id2 = NodeId::new(2);
530        assert!(id1 < id2);
531    }
532
533    #[test]
534    fn test_edge_id_basic() {
535        let id = EdgeId::new(100);
536        assert_eq!(id.as_u64(), 100);
537        assert!(id.is_valid());
538        assert!(!EdgeId::INVALID.is_valid());
539    }
540
541    #[test]
542    fn test_tx_id_basic() {
543        let id = TxId::new(1);
544        assert!(id.is_valid());
545        assert!(!TxId::INVALID.is_valid());
546        assert_eq!(id.next(), TxId::new(2));
547    }
548
549    #[test]
550    fn test_epoch_visibility() {
551        let e1 = EpochId::new(1);
552        let e2 = EpochId::new(2);
553        let e3 = EpochId::new(3);
554
555        // e1 is visible at e2 and e3
556        assert!(e1.is_visible_at(e2));
557        assert!(e1.is_visible_at(e3));
558
559        // e2 is visible at e2 and e3, but not e1
560        assert!(!e2.is_visible_at(e1));
561        assert!(e2.is_visible_at(e2));
562        assert!(e2.is_visible_at(e3));
563
564        // e3 is only visible at e3
565        assert!(!e3.is_visible_at(e1));
566        assert!(!e3.is_visible_at(e2));
567        assert!(e3.is_visible_at(e3));
568    }
569
570    #[test]
571    fn test_epoch_next() {
572        let e = EpochId::INITIAL;
573        assert_eq!(e.next(), EpochId::new(1));
574        assert_eq!(e.next().next(), EpochId::new(2));
575    }
576
577    #[test]
578    fn test_conversions() {
579        // NodeId
580        let node_id: NodeId = 42u64.into();
581        let raw: u64 = node_id.into();
582        assert_eq!(raw, 42);
583
584        // EdgeId
585        let edge_id: EdgeId = 100u64.into();
586        let raw: u64 = edge_id.into();
587        assert_eq!(raw, 100);
588
589        // TxId
590        let tx_id: TxId = 1u64.into();
591        let raw: u64 = tx_id.into();
592        assert_eq!(raw, 1);
593
594        // EpochId
595        let epoch_id: EpochId = 5u64.into();
596        let raw: u64 = epoch_id.into();
597        assert_eq!(raw, 5);
598    }
599}