lock_db/id.rs
1//! Opaque identifiers for transactions and lockable resources.
2//!
3//! Both identifiers are thin newtypes over `u64`. lock-db does not assign or
4//! interpret them: the transaction layer decides what a transaction id means,
5//! and the storage layer decides how to map a database, table, page, or row to
6//! a single [`ResourceId`]. Keeping them opaque integers means the lock table
7//! never owns variable-length keys, so a lookup is a hash of one machine word
8//! with no allocation on the hot path.
9
10/// Identifies the transaction that owns a lock request.
11///
12/// The lock manager uses this only for equality and hashing: it tracks which
13/// requests belong together so a transaction can re-acquire, upgrade, or
14/// release its own locks. Reusing a retired id for a new transaction is safe as
15/// long as the previous transaction has released everything first.
16///
17/// # Examples
18///
19/// ```
20/// use lock_db::TxnId;
21///
22/// let t = TxnId::new(42);
23/// assert_eq!(t.get(), 42);
24/// assert_eq!(TxnId::from(42), t);
25/// ```
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28#[repr(transparent)]
29pub struct TxnId(u64);
30
31impl TxnId {
32 /// Wraps a raw transaction number.
33 #[inline]
34 #[must_use]
35 pub const fn new(id: u64) -> Self {
36 Self(id)
37 }
38
39 /// Returns the underlying number.
40 #[inline]
41 #[must_use]
42 pub const fn get(self) -> u64 {
43 self.0
44 }
45}
46
47impl From<u64> for TxnId {
48 #[inline]
49 fn from(id: u64) -> Self {
50 Self(id)
51 }
52}
53
54impl From<TxnId> for u64 {
55 #[inline]
56 fn from(id: TxnId) -> Self {
57 id.0
58 }
59}
60
61/// Identifies a lockable resource.
62///
63/// A resource is whatever the caller decides to protect with a lock: an entire
64/// database, a table, a page, or a single row. The caller is responsible for
65/// mapping its own object identity to a stable, collision-free `u64`. Two
66/// distinct resources that map to the same id will share a lock queue, which is
67/// a correctness bug in the caller's id scheme, not in lock-db.
68///
69/// # Examples
70///
71/// ```
72/// use lock_db::ResourceId;
73///
74/// let page = ResourceId::new(0xDEAD_BEEF);
75/// assert_eq!(page.get(), 0xDEAD_BEEF);
76/// ```
77#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79#[repr(transparent)]
80pub struct ResourceId(u64);
81
82impl ResourceId {
83 /// Wraps a raw resource number.
84 #[inline]
85 #[must_use]
86 pub const fn new(id: u64) -> Self {
87 Self(id)
88 }
89
90 /// Returns the underlying number.
91 #[inline]
92 #[must_use]
93 pub const fn get(self) -> u64 {
94 self.0
95 }
96}
97
98impl From<u64> for ResourceId {
99 #[inline]
100 fn from(id: u64) -> Self {
101 Self(id)
102 }
103}
104
105impl From<ResourceId> for u64 {
106 #[inline]
107 fn from(id: ResourceId) -> Self {
108 id.0
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::{ResourceId, TxnId};
115
116 #[test]
117 fn test_txn_roundtrip_through_u64() {
118 let t = TxnId::new(u64::MAX);
119 assert_eq!(t.get(), u64::MAX);
120 assert_eq!(u64::from(t), u64::MAX);
121 assert_eq!(TxnId::from(7), TxnId::new(7));
122 }
123
124 #[test]
125 fn test_resource_roundtrip_through_u64() {
126 let r = ResourceId::new(0);
127 assert_eq!(r.get(), 0);
128 assert_eq!(u64::from(r), 0);
129 assert_eq!(ResourceId::from(7), ResourceId::new(7));
130 }
131
132 #[test]
133 fn test_distinct_ids_are_unequal() {
134 assert_ne!(TxnId::new(1), TxnId::new(2));
135 assert_ne!(ResourceId::new(1), ResourceId::new(2));
136 }
137
138 #[test]
139 fn test_ids_are_ordered_by_value() {
140 assert!(TxnId::new(1) < TxnId::new(2));
141 assert!(ResourceId::new(1) < ResourceId::new(2));
142 }
143}