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