Skip to main content

grafeo_common/types/
id.rs

1//! Identifier types for graph elements and transactions.
2//!
3//! These are the handles you use to reference nodes, edges, and transactions.
4//! They're all thin wrappers around integers - cheap to copy and compare.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Identifies a node in the graph.
10///
11/// You get these back when you create nodes, and use them to look up or
12/// connect nodes later. Just a `u64` under the hood - cheap to copy.
13///
14/// # Examples
15///
16/// ```
17/// use grafeo_common::types::NodeId;
18///
19/// let id = NodeId::new(42);
20/// assert!(id.is_valid());
21/// assert_eq!(id.as_u64(), 42);
22/// ```
23#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
24#[repr(transparent)]
25pub struct NodeId(pub u64);
26
27impl NodeId {
28    /// The invalid/null node ID.
29    pub const INVALID: Self = Self(u64::MAX);
30
31    /// Creates a new NodeId from a raw u64 value.
32    #[inline]
33    #[must_use]
34    pub const fn new(id: u64) -> Self {
35        Self(id)
36    }
37
38    /// Returns the raw u64 value.
39    #[inline]
40    #[must_use]
41    pub const fn as_u64(&self) -> u64 {
42        self.0
43    }
44
45    /// Checks if this is a valid node ID.
46    #[inline]
47    #[must_use]
48    pub const fn is_valid(&self) -> bool {
49        self.0 != u64::MAX
50    }
51}
52
53impl fmt::Debug for NodeId {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        if self.is_valid() {
56            write!(f, "NodeId({})", self.0)
57        } else {
58            write!(f, "NodeId(INVALID)")
59        }
60    }
61}
62
63impl fmt::Display for NodeId {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        write!(f, "{}", self.0)
66    }
67}
68
69impl From<u64> for NodeId {
70    fn from(id: u64) -> Self {
71        Self(id)
72    }
73}
74
75impl From<NodeId> for u64 {
76    fn from(id: NodeId) -> Self {
77        id.0
78    }
79}
80
81/// Identifies an edge (relationship) in the graph.
82///
83/// Like [`NodeId`], just a `u64` wrapper. You get these when creating edges
84/// and use them to look up or modify relationships later.
85#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
86#[repr(transparent)]
87pub struct EdgeId(pub u64);
88
89impl EdgeId {
90    /// The invalid/null edge ID.
91    pub const INVALID: Self = Self(u64::MAX);
92
93    /// Creates a new EdgeId from a raw u64 value.
94    #[inline]
95    #[must_use]
96    pub const fn new(id: u64) -> Self {
97        Self(id)
98    }
99
100    /// Returns the raw u64 value.
101    #[inline]
102    #[must_use]
103    pub const fn as_u64(&self) -> u64 {
104        self.0
105    }
106
107    /// Checks if this is a valid edge ID.
108    #[inline]
109    #[must_use]
110    pub const fn is_valid(&self) -> bool {
111        self.0 != u64::MAX
112    }
113}
114
115impl fmt::Debug for EdgeId {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        if self.is_valid() {
118            write!(f, "EdgeId({})", self.0)
119        } else {
120            write!(f, "EdgeId(INVALID)")
121        }
122    }
123}
124
125impl fmt::Display for EdgeId {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        write!(f, "{}", self.0)
128    }
129}
130
131impl From<u64> for EdgeId {
132    fn from(id: u64) -> Self {
133        Self(id)
134    }
135}
136
137impl From<EdgeId> for u64 {
138    fn from(id: EdgeId) -> Self {
139        id.0
140    }
141}
142
143/// Identifies a transaction for MVCC versioning.
144///
145/// Each transaction gets a unique, monotonically increasing ID. This is how
146/// Grafeo knows which versions of data each transaction should see.
147#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
148#[repr(transparent)]
149pub struct TransactionId(pub u64);
150
151impl TransactionId {
152    /// The invalid/null transaction ID (sentinel value, same as other ID types).
153    pub const INVALID: Self = Self(u64::MAX);
154
155    /// The system transaction ID used for non-transactional operations.
156    /// System transactions are always visible and committed.
157    pub const SYSTEM: Self = Self(1);
158
159    /// Creates a new TransactionId from a raw u64 value.
160    #[inline]
161    #[must_use]
162    pub const fn new(id: u64) -> Self {
163        Self(id)
164    }
165
166    /// Returns the raw u64 value.
167    #[inline]
168    #[must_use]
169    pub const fn as_u64(&self) -> u64 {
170        self.0
171    }
172
173    /// Returns the next transaction ID.
174    #[inline]
175    #[must_use]
176    pub const fn next(&self) -> Self {
177        Self(self.0 + 1)
178    }
179
180    /// Checks if this is a valid transaction ID.
181    #[inline]
182    #[must_use]
183    pub const fn is_valid(&self) -> bool {
184        self.0 != u64::MAX
185    }
186}
187
188impl fmt::Debug for TransactionId {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        if self.is_valid() {
191            write!(f, "TransactionId({})", self.0)
192        } else {
193            write!(f, "TransactionId(INVALID)")
194        }
195    }
196}
197
198impl fmt::Display for TransactionId {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        write!(f, "{}", self.0)
201    }
202}
203
204impl From<u64> for TransactionId {
205    fn from(id: u64) -> Self {
206        Self(id)
207    }
208}
209
210impl From<TransactionId> for u64 {
211    fn from(id: TransactionId) -> Self {
212        id.0
213    }
214}
215
216/// Identifies an epoch for memory management.
217///
218/// Think of epochs like garbage collection generations. When all readers from
219/// an old epoch finish, we can reclaim that memory. You usually don't interact
220/// with epochs directly - they're managed by the transaction system.
221#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
222#[repr(transparent)]
223pub struct EpochId(pub u64);
224
225impl EpochId {
226    /// The initial epoch (epoch 0).
227    pub const INITIAL: Self = Self(0);
228
229    /// Sentinel epoch for uncommitted transactional versions.
230    ///
231    /// Versions created inside an active (not yet committed) transaction use
232    /// this value as their `created_epoch`. Because `PENDING > any_real_epoch`,
233    /// `is_visible_at(real_epoch)` returns false, preventing dirty reads.
234    /// On commit, the session finalizes these to the actual commit epoch.
235    pub const PENDING: Self = Self(u64::MAX);
236
237    /// Creates a new EpochId from a raw u64 value.
238    #[inline]
239    #[must_use]
240    pub const fn new(id: u64) -> Self {
241        Self(id)
242    }
243
244    /// Returns the raw u64 value.
245    #[inline]
246    #[must_use]
247    pub const fn as_u64(&self) -> u64 {
248        self.0
249    }
250
251    /// Returns the next epoch ID.
252    #[inline]
253    #[must_use]
254    pub const fn next(&self) -> Self {
255        Self(self.0 + 1)
256    }
257
258    /// Checks if this epoch is visible at the given epoch.
259    ///
260    /// An epoch is visible if it was created before or at the viewing epoch.
261    #[inline]
262    #[must_use]
263    pub const fn is_visible_at(&self, viewing_epoch: Self) -> bool {
264        self.0 <= viewing_epoch.0
265    }
266}
267
268impl fmt::Debug for EpochId {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        write!(f, "EpochId({})", self.0)
271    }
272}
273
274impl fmt::Display for EpochId {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        write!(f, "{}", self.0)
277    }
278}
279
280impl From<u64> for EpochId {
281    fn from(id: u64) -> Self {
282        Self(id)
283    }
284}
285
286impl From<EpochId> for u64 {
287    fn from(id: EpochId) -> Self {
288        id.0
289    }
290}
291
292/// Unique identifier for a label in the catalog.
293///
294/// Labels are strings assigned to nodes to categorize them.
295/// The catalog assigns unique IDs for efficient storage and comparison.
296#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
297#[repr(transparent)]
298pub struct LabelId(pub u32);
299
300impl LabelId {
301    /// The invalid/null label ID.
302    pub const INVALID: Self = Self(u32::MAX);
303
304    /// Creates a new LabelId from a raw u32 value.
305    #[inline]
306    #[must_use]
307    pub const fn new(id: u32) -> Self {
308        Self(id)
309    }
310
311    /// Returns the raw u32 value.
312    #[inline]
313    #[must_use]
314    pub const fn as_u32(&self) -> u32 {
315        self.0
316    }
317
318    /// Checks if this is a valid label ID.
319    #[inline]
320    #[must_use]
321    pub const fn is_valid(&self) -> bool {
322        self.0 != u32::MAX
323    }
324}
325
326impl fmt::Debug for LabelId {
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328        if self.is_valid() {
329            write!(f, "LabelId({})", self.0)
330        } else {
331            write!(f, "LabelId(INVALID)")
332        }
333    }
334}
335
336impl fmt::Display for LabelId {
337    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338        write!(f, "{}", self.0)
339    }
340}
341
342impl From<u32> for LabelId {
343    fn from(id: u32) -> Self {
344        Self(id)
345    }
346}
347
348impl From<LabelId> for u32 {
349    fn from(id: LabelId) -> Self {
350        id.0
351    }
352}
353
354/// Unique identifier for a property key in the catalog.
355///
356/// Property keys are strings used as names for node/edge properties.
357/// The catalog assigns unique IDs for efficient storage.
358#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
359#[repr(transparent)]
360pub struct PropertyKeyId(pub u32);
361
362impl PropertyKeyId {
363    /// The invalid/null property key ID.
364    pub const INVALID: Self = Self(u32::MAX);
365
366    /// Creates a new PropertyKeyId from a raw u32 value.
367    #[inline]
368    #[must_use]
369    pub const fn new(id: u32) -> Self {
370        Self(id)
371    }
372
373    /// Returns the raw u32 value.
374    #[inline]
375    #[must_use]
376    pub const fn as_u32(&self) -> u32 {
377        self.0
378    }
379
380    /// Checks if this is a valid property key ID.
381    #[inline]
382    #[must_use]
383    pub const fn is_valid(&self) -> bool {
384        self.0 != u32::MAX
385    }
386}
387
388impl fmt::Debug for PropertyKeyId {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        if self.is_valid() {
391            write!(f, "PropertyKeyId({})", self.0)
392        } else {
393            write!(f, "PropertyKeyId(INVALID)")
394        }
395    }
396}
397
398impl fmt::Display for PropertyKeyId {
399    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400        write!(f, "{}", self.0)
401    }
402}
403
404impl From<u32> for PropertyKeyId {
405    fn from(id: u32) -> Self {
406        Self(id)
407    }
408}
409
410impl From<PropertyKeyId> for u32 {
411    fn from(id: PropertyKeyId) -> Self {
412        id.0
413    }
414}
415
416/// Unique identifier for an edge type in the catalog.
417///
418/// Edge types are strings that categorize relationships between nodes.
419/// The catalog assigns unique IDs for efficient storage.
420#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
421#[repr(transparent)]
422pub struct EdgeTypeId(pub u32);
423
424impl EdgeTypeId {
425    /// The invalid/null edge type ID.
426    pub const INVALID: Self = Self(u32::MAX);
427
428    /// Creates a new EdgeTypeId from a raw u32 value.
429    #[inline]
430    #[must_use]
431    pub const fn new(id: u32) -> Self {
432        Self(id)
433    }
434
435    /// Returns the raw u32 value.
436    #[inline]
437    #[must_use]
438    pub const fn as_u32(&self) -> u32 {
439        self.0
440    }
441
442    /// Checks if this is a valid edge type ID.
443    #[inline]
444    #[must_use]
445    pub const fn is_valid(&self) -> bool {
446        self.0 != u32::MAX
447    }
448}
449
450impl fmt::Debug for EdgeTypeId {
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        if self.is_valid() {
453            write!(f, "EdgeTypeId({})", self.0)
454        } else {
455            write!(f, "EdgeTypeId(INVALID)")
456        }
457    }
458}
459
460impl fmt::Display for EdgeTypeId {
461    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462        write!(f, "{}", self.0)
463    }
464}
465
466impl From<u32> for EdgeTypeId {
467    fn from(id: u32) -> Self {
468        Self(id)
469    }
470}
471
472impl From<EdgeTypeId> for u32 {
473    fn from(id: EdgeTypeId) -> Self {
474        id.0
475    }
476}
477
478/// Unique identifier for an index in the catalog.
479#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
480#[repr(transparent)]
481pub struct IndexId(pub u32);
482
483impl IndexId {
484    /// The invalid/null index ID.
485    pub const INVALID: Self = Self(u32::MAX);
486
487    /// Creates a new IndexId from a raw u32 value.
488    #[inline]
489    #[must_use]
490    pub const fn new(id: u32) -> Self {
491        Self(id)
492    }
493
494    /// Returns the raw u32 value.
495    #[inline]
496    #[must_use]
497    pub const fn as_u32(&self) -> u32 {
498        self.0
499    }
500
501    /// Checks if this is a valid index ID.
502    #[inline]
503    #[must_use]
504    pub const fn is_valid(&self) -> bool {
505        self.0 != u32::MAX
506    }
507}
508
509impl fmt::Debug for IndexId {
510    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511        if self.is_valid() {
512            write!(f, "IndexId({})", self.0)
513        } else {
514            write!(f, "IndexId(INVALID)")
515        }
516    }
517}
518
519impl fmt::Display for IndexId {
520    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521        write!(f, "{}", self.0)
522    }
523}
524
525impl From<u32> for IndexId {
526    fn from(id: u32) -> Self {
527        Self(id)
528    }
529}
530
531impl From<IndexId> for u32 {
532    fn from(id: IndexId) -> Self {
533        id.0
534    }
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540
541    #[test]
542    fn test_node_id_basic() {
543        let id = NodeId::new(42);
544        assert_eq!(id.as_u64(), 42);
545        assert!(id.is_valid());
546        assert!(!NodeId::INVALID.is_valid());
547    }
548
549    #[test]
550    fn test_node_id_ordering() {
551        let id1 = NodeId::new(1);
552        let id2 = NodeId::new(2);
553        assert!(id1 < id2);
554    }
555
556    #[test]
557    fn test_edge_id_basic() {
558        let id = EdgeId::new(100);
559        assert_eq!(id.as_u64(), 100);
560        assert!(id.is_valid());
561        assert!(!EdgeId::INVALID.is_valid());
562    }
563
564    #[test]
565    fn test_tx_id_basic() {
566        let id = TransactionId::new(1);
567        assert!(id.is_valid());
568        assert!(!TransactionId::INVALID.is_valid());
569        assert_eq!(id.next(), TransactionId::new(2));
570    }
571
572    #[test]
573    fn test_epoch_visibility() {
574        let e1 = EpochId::new(1);
575        let e2 = EpochId::new(2);
576        let e3 = EpochId::new(3);
577
578        // e1 is visible at e2 and e3
579        assert!(e1.is_visible_at(e2));
580        assert!(e1.is_visible_at(e3));
581
582        // e2 is visible at e2 and e3, but not e1
583        assert!(!e2.is_visible_at(e1));
584        assert!(e2.is_visible_at(e2));
585        assert!(e2.is_visible_at(e3));
586
587        // e3 is only visible at e3
588        assert!(!e3.is_visible_at(e1));
589        assert!(!e3.is_visible_at(e2));
590        assert!(e3.is_visible_at(e3));
591    }
592
593    #[test]
594    fn test_epoch_next() {
595        let e = EpochId::INITIAL;
596        assert_eq!(e.next(), EpochId::new(1));
597        assert_eq!(e.next().next(), EpochId::new(2));
598    }
599
600    #[test]
601    fn test_conversions() {
602        // NodeId
603        let node_id: NodeId = 42u64.into();
604        let raw: u64 = node_id.into();
605        assert_eq!(raw, 42);
606
607        // EdgeId
608        let edge_id: EdgeId = 100u64.into();
609        let raw: u64 = edge_id.into();
610        assert_eq!(raw, 100);
611
612        // TransactionId
613        let tx_id: TransactionId = 1u64.into();
614        let raw: u64 = tx_id.into();
615        assert_eq!(raw, 1);
616
617        // EpochId
618        let epoch_id: EpochId = 5u64.into();
619        let raw: u64 = epoch_id.into();
620        assert_eq!(raw, 5);
621    }
622}