Skip to main content

reddb_server/storage/primitives/
ids.rs

1//! Unified ID Types for Storage Layer
2//!
3//! This module provides type-safe ID wrappers for all storage concepts.
4//! Using newtype wrappers prevents accidental mixing of semantically
5//! different IDs (e.g., using a NodeId where a VectorId was expected).
6//!
7//! # Design Rationale
8//!
9//! Previously, IDs were scattered across modules as simple type aliases:
10//! - `NodeId` was defined in both btree/node.rs and engine/hnsw.rs
11//! - `TxnId` was duplicated 5 times across transaction modules
12//! - `VectorId` existed in both deprecated vector/ and engine/vector_store.rs
13//!
14//! This centralization provides:
15//! 1. Single source of truth
16//! 2. Type safety (cannot accidentally mix ID types)
17//! 3. Easy to add traits uniformly (Display, Hash, etc.)
18//!
19//! The wrappers support arithmetic operations for practical usage while
20//! maintaining type distinction between different ID domains.
21
22use std::fmt;
23use std::ops::{Add, AddAssign, Sub, SubAssign};
24use std::sync::atomic::{AtomicU64, Ordering};
25
26// ============================================================================
27// B+ Tree IDs
28// ============================================================================
29
30/// Node ID for B+ tree nodes (internal and leaf nodes).
31///
32/// B+ tree nodes form a hierarchical structure with internal nodes
33/// containing keys and child pointers, and leaf nodes containing
34/// the actual key-value pairs.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
36#[repr(transparent)]
37pub struct BTreeNodeId(pub u64);
38
39impl BTreeNodeId {
40    /// Create a new BTreeNodeId
41    pub const fn new(id: u64) -> Self {
42        Self(id)
43    }
44
45    /// Get the raw u64 value
46    pub const fn get(self) -> u64 {
47        self.0
48    }
49}
50
51impl fmt::Display for BTreeNodeId {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(f, "btree:{}", self.0)
54    }
55}
56
57impl From<u64> for BTreeNodeId {
58    fn from(id: u64) -> Self {
59        Self(id)
60    }
61}
62
63impl From<BTreeNodeId> for u64 {
64    fn from(id: BTreeNodeId) -> Self {
65        id.0
66    }
67}
68
69/// Global counter for B+ tree node IDs
70static BTREE_NODE_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
71
72/// Generate next B+ tree node ID
73pub fn next_btree_node_id() -> BTreeNodeId {
74    BTreeNodeId(BTREE_NODE_ID_COUNTER.fetch_add(1, Ordering::SeqCst))
75}
76
77// ============================================================================
78// HNSW Graph IDs
79// ============================================================================
80
81/// Node ID for HNSW (Hierarchical Navigable Small World) graph nodes.
82///
83/// HNSW nodes represent vectors in the approximate nearest neighbor
84/// search structure. Each node exists in one or more layers of the
85/// hierarchical graph.
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
87#[repr(transparent)]
88pub struct HnswNodeId(pub u64);
89
90impl HnswNodeId {
91    /// Create a new HnswNodeId
92    pub const fn new(id: u64) -> Self {
93        Self(id)
94    }
95
96    /// Get the raw u64 value
97    pub const fn get(self) -> u64 {
98        self.0
99    }
100}
101
102impl fmt::Display for HnswNodeId {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        write!(f, "hnsw:{}", self.0)
105    }
106}
107
108impl From<u64> for HnswNodeId {
109    fn from(id: u64) -> Self {
110        Self(id)
111    }
112}
113
114impl From<HnswNodeId> for u64 {
115    fn from(id: HnswNodeId) -> Self {
116        id.0
117    }
118}
119
120// ============================================================================
121// Transaction IDs
122// ============================================================================
123
124/// Transaction ID for MVCC (Multi-Version Concurrency Control).
125///
126/// Transaction IDs are monotonically increasing and used to:
127/// - Identify which transaction created/modified a version
128/// - Determine version visibility during reads
129/// - Coordinate distributed transactions
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
131#[repr(transparent)]
132pub struct TxnId(pub u64);
133
134impl TxnId {
135    /// Create a new TxnId
136    pub const fn new(id: u64) -> Self {
137        Self(id)
138    }
139
140    /// The null/initial transaction ID
141    pub const ZERO: TxnId = TxnId(0);
142
143    /// Get the raw u64 value
144    pub const fn get(self) -> u64 {
145        self.0
146    }
147
148    /// Check if this is the null/initial transaction
149    pub const fn is_zero(self) -> bool {
150        self.0 == 0
151    }
152}
153
154impl fmt::Display for TxnId {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        write!(f, "txn:{}", self.0)
157    }
158}
159
160impl From<u64> for TxnId {
161    fn from(id: u64) -> Self {
162        Self(id)
163    }
164}
165
166impl From<TxnId> for u64 {
167    fn from(id: TxnId) -> Self {
168        id.0
169    }
170}
171
172// Arithmetic operations for TxnId
173impl Add<u64> for TxnId {
174    type Output = TxnId;
175    fn add(self, rhs: u64) -> TxnId {
176        TxnId(self.0 + rhs)
177    }
178}
179
180impl AddAssign<u64> for TxnId {
181    fn add_assign(&mut self, rhs: u64) {
182        self.0 += rhs;
183    }
184}
185
186impl Sub<u64> for TxnId {
187    type Output = TxnId;
188    fn sub(self, rhs: u64) -> TxnId {
189        TxnId(self.0 - rhs)
190    }
191}
192
193/// Global counter for transaction IDs
194static TXN_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
195
196/// Generate next transaction ID
197pub fn next_txn_id() -> TxnId {
198    TxnId(TXN_ID_COUNTER.fetch_add(1, Ordering::SeqCst))
199}
200
201// ============================================================================
202// Vector IDs
203// ============================================================================
204
205/// Vector ID for vector storage and similarity search.
206///
207/// Each vector in the storage engine has a unique VectorId that
208/// persists across index rebuilds and can be used to retrieve
209/// the original vector data.
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
211#[repr(transparent)]
212pub struct VectorId(pub u64);
213
214impl VectorId {
215    /// Create a new VectorId
216    pub const fn new(id: u64) -> Self {
217        Self(id)
218    }
219
220    /// Get the raw u64 value
221    pub const fn get(self) -> u64 {
222        self.0
223    }
224}
225
226impl fmt::Display for VectorId {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        write!(f, "vec:{}", self.0)
229    }
230}
231
232impl From<u64> for VectorId {
233    fn from(id: u64) -> Self {
234        Self(id)
235    }
236}
237
238impl From<VectorId> for u64 {
239    fn from(id: VectorId) -> Self {
240        id.0
241    }
242}
243
244// ============================================================================
245// Segment IDs
246// ============================================================================
247
248/// Segment ID for data segment identification.
249///
250/// Segments are logical partitions of data used for:
251/// - Organizing related records together
252/// - Enabling targeted queries
253/// - Supporting segment-level operations (compaction, gc)
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
255#[repr(transparent)]
256pub struct SegmentId(pub u64);
257
258impl SegmentId {
259    /// Create a new SegmentId
260    pub const fn new(id: u64) -> Self {
261        Self(id)
262    }
263
264    /// Get the raw u64 value
265    pub const fn get(self) -> u64 {
266        self.0
267    }
268}
269
270impl fmt::Display for SegmentId {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        write!(f, "seg:{}", self.0)
273    }
274}
275
276impl From<u64> for SegmentId {
277    fn from(id: u64) -> Self {
278        Self(id)
279    }
280}
281
282impl From<SegmentId> for u64 {
283    fn from(id: SegmentId) -> Self {
284        id.0
285    }
286}
287
288// ============================================================================
289// Page IDs
290// ============================================================================
291
292/// Page ID for storage page identification.
293///
294/// Pages are the fundamental unit of storage I/O. Each page has
295/// a fixed size (typically 4KB or 8KB) and contains records or
296/// index data.
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
298#[repr(transparent)]
299pub struct PageId(pub u64);
300
301impl PageId {
302    /// Create a new PageId
303    pub const fn new(id: u64) -> Self {
304        Self(id)
305    }
306
307    /// The null page ID (no page)
308    pub const NULL: PageId = PageId(0);
309
310    /// Get the raw u64 value
311    pub const fn get(self) -> u64 {
312        self.0
313    }
314
315    /// Check if this is the null page
316    pub const fn is_null(self) -> bool {
317        self.0 == 0
318    }
319}
320
321impl fmt::Display for PageId {
322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323        write!(f, "page:{}", self.0)
324    }
325}
326
327impl From<u64> for PageId {
328    fn from(id: u64) -> Self {
329        Self(id)
330    }
331}
332
333impl From<PageId> for u64 {
334    fn from(id: PageId) -> Self {
335        id.0
336    }
337}
338
339// ============================================================================
340// Entity IDs (graph/relational)
341// ============================================================================
342
343/// Entity ID for graph nodes and relational entities.
344///
345/// Used in the graph storage layer to identify vertices
346/// and their relationships.
347#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
348#[repr(transparent)]
349pub struct EntityId(pub u64);
350
351impl EntityId {
352    /// Create a new EntityId
353    pub const fn new(id: u64) -> Self {
354        Self(id)
355    }
356
357    /// Get the raw u64 value
358    pub const fn get(self) -> u64 {
359        self.0
360    }
361}
362
363impl fmt::Display for EntityId {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        write!(f, "entity:{}", self.0)
366    }
367}
368
369impl From<u64> for EntityId {
370    fn from(id: u64) -> Self {
371        Self(id)
372    }
373}
374
375impl From<EntityId> for u64 {
376    fn from(id: EntityId) -> Self {
377        id.0
378    }
379}
380
381// ============================================================================
382// Timestamp (logical clock)
383// ============================================================================
384
385/// Logical timestamp for MVCC versioning.
386///
387/// Timestamps are monotonically increasing values used to
388/// order events and determine version visibility.
389#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
390#[repr(transparent)]
391pub struct Timestamp(pub u64);
392
393impl Timestamp {
394    /// Create a new Timestamp
395    pub const fn new(ts: u64) -> Self {
396        Self(ts)
397    }
398
399    /// The epoch (time zero)
400    pub const EPOCH: Timestamp = Timestamp(0);
401
402    /// Maximum timestamp
403    pub const MAX: Timestamp = Timestamp(u64::MAX);
404
405    /// Get the raw u64 value
406    pub const fn get(self) -> u64 {
407        self.0
408    }
409
410    /// Check if this is the epoch
411    pub const fn is_epoch(self) -> bool {
412        self.0 == 0
413    }
414}
415
416impl fmt::Display for Timestamp {
417    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418        write!(f, "ts:{}", self.0)
419    }
420}
421
422impl From<u64> for Timestamp {
423    fn from(ts: u64) -> Self {
424        Self(ts)
425    }
426}
427
428impl From<Timestamp> for u64 {
429    fn from(ts: Timestamp) -> Self {
430        ts.0
431    }
432}
433
434// Arithmetic operations for Timestamp
435impl Add<u64> for Timestamp {
436    type Output = Timestamp;
437    fn add(self, rhs: u64) -> Timestamp {
438        Timestamp(self.0 + rhs)
439    }
440}
441
442impl Add<Timestamp> for Timestamp {
443    type Output = Timestamp;
444    fn add(self, rhs: Timestamp) -> Timestamp {
445        Timestamp(self.0 + rhs.0)
446    }
447}
448
449impl AddAssign<u64> for Timestamp {
450    fn add_assign(&mut self, rhs: u64) {
451        self.0 += rhs;
452    }
453}
454
455impl Sub<u64> for Timestamp {
456    type Output = Timestamp;
457    fn sub(self, rhs: u64) -> Timestamp {
458        Timestamp(self.0 - rhs)
459    }
460}
461
462impl Sub<Timestamp> for Timestamp {
463    type Output = Timestamp;
464    fn sub(self, rhs: Timestamp) -> Timestamp {
465        Timestamp(self.0 - rhs.0)
466    }
467}
468
469impl SubAssign<u64> for Timestamp {
470    fn sub_assign(&mut self, rhs: u64) {
471        self.0 -= rhs;
472    }
473}
474
475impl Timestamp {
476    /// Saturating subtraction
477    pub const fn saturating_sub(self, rhs: Self) -> Self {
478        Timestamp(self.0.saturating_sub(rhs.0))
479    }
480
481    /// Minimum of two timestamps
482    pub fn min(self, other: Self) -> Self {
483        Timestamp(self.0.min(other.0))
484    }
485
486    /// Maximum of two timestamps
487    pub fn max(self, other: Self) -> Self {
488        Timestamp(self.0.max(other.0))
489    }
490}
491
492/// Global logical timestamp counter
493static GLOBAL_TIMESTAMP: AtomicU64 = AtomicU64::new(1);
494
495/// Get next timestamp
496pub fn next_timestamp() -> Timestamp {
497    Timestamp(GLOBAL_TIMESTAMP.fetch_add(1, Ordering::SeqCst))
498}
499
500/// Get current timestamp without incrementing
501pub fn current_timestamp() -> Timestamp {
502    Timestamp(GLOBAL_TIMESTAMP.load(Ordering::SeqCst))
503}
504
505// ============================================================================
506// Tests
507// ============================================================================
508
509#[cfg(test)]
510mod tests {
511    use super::*;
512
513    #[test]
514    fn test_btree_node_id() {
515        let id1 = BTreeNodeId::new(42);
516        let id2 = BTreeNodeId::from(42u64);
517        assert_eq!(id1, id2);
518        assert_eq!(id1.get(), 42);
519        assert_eq!(format!("{}", id1), "btree:42");
520    }
521
522    #[test]
523    fn test_hnsw_node_id() {
524        let id = HnswNodeId::new(100);
525        assert_eq!(id.get(), 100);
526        assert_eq!(format!("{}", id), "hnsw:100");
527    }
528
529    #[test]
530    fn test_txn_id() {
531        assert!(TxnId::ZERO.is_zero());
532        let id = TxnId::new(5);
533        assert!(!id.is_zero());
534        assert_eq!(format!("{}", id), "txn:5");
535    }
536
537    #[test]
538    fn test_vector_id() {
539        let id = VectorId::new(999);
540        assert_eq!(id.get(), 999);
541        assert_eq!(u64::from(id), 999);
542    }
543
544    #[test]
545    fn test_page_id() {
546        assert!(PageId::NULL.is_null());
547        let id = PageId::new(1);
548        assert!(!id.is_null());
549    }
550
551    #[test]
552    fn test_timestamp() {
553        assert!(Timestamp::EPOCH.is_epoch());
554        let ts = Timestamp::new(100);
555        assert!(!ts.is_epoch());
556        assert!(ts < Timestamp::MAX);
557    }
558
559    #[test]
560    fn test_id_generation() {
561        let id1 = next_btree_node_id();
562        let id2 = next_btree_node_id();
563        assert!(id2.get() > id1.get());
564
565        let txn1 = next_txn_id();
566        let txn2 = next_txn_id();
567        assert!(txn2.get() > txn1.get());
568
569        let ts1 = next_timestamp();
570        let ts2 = next_timestamp();
571        assert!(ts2.get() > ts1.get());
572    }
573
574    #[test]
575    fn test_type_safety() {
576        // These types should NOT be interchangeable at compile time
577        // (This is a conceptual test - actual type errors are compile-time)
578        let btree_id = BTreeNodeId::new(1);
579        let hnsw_id = HnswNodeId::new(1);
580        let txn_id = TxnId::new(1);
581
582        // They have the same underlying value but are different types
583        assert_eq!(btree_id.get(), hnsw_id.get());
584        assert_eq!(btree_id.get(), txn_id.get());
585
586        // But they format differently, showing their semantic meaning
587        assert_ne!(format!("{}", btree_id), format!("{}", hnsw_id));
588        assert_ne!(format!("{}", btree_id), format!("{}", txn_id));
589    }
590}