Skip to main content

noxu_txn/
lock_type.rs

1//! Lock type definitions and conflict/upgrade matrices.
2//!
3
4use crate::{LockConflict, LockUpgrade};
5
6/// Lock types used in the transaction system.
7///
8/// Noxu DB uses hierarchical locking with five primary lock types.
9/// The conflict and upgrade matrices define how locks interact.
10///
11///
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
13pub enum LockType {
14    /// Basic read lock on a record.
15    ///
16    /// Allows multiple concurrent readers but blocks writers.
17    Read,
18
19    /// Write lock on a record (exclusive).
20    ///
21    /// Blocks all other lockers from reading or writing this record.
22    Write,
23
24    /// Range read lock  -  locks a range to prevent phantom inserts.
25    ///
26    /// Similar to Read but also prevents inserts into the range.
27    RangeRead,
28
29    /// Range write lock  -  combines range and write locking.
30    ///
31    /// Blocks all access and prevents inserts into the range.
32    RangeWrite,
33
34    /// Range insert lock  -  used when inserting into a range.
35    ///
36    /// Conflicts with range locks to trigger restart on phantom detection.
37    RangeInsert,
38
39    /// No lock requested (dirty read).
40    ///
41    /// Special type indicating no locking should be performed.
42    None,
43
44    /// Restart marker  -  not a real lock type.
45    ///
46    /// Used internally to indicate a range restart is needed.
47    Restart,
48}
49
50impl LockType {
51    /// Returns true if this is a write lock that modifies data.
52    ///
53    /// Note: Per implementation, RangeInsert is NOT considered a write lock
54    /// for the purposes of transaction commit/abort. Only Write and RangeWrite
55    /// require undo information.
56    #[inline]
57    pub fn is_write_lock(self) -> bool {
58        matches!(self, LockType::Write | LockType::RangeWrite)
59    }
60
61    /// Returns true if this lock type causes a restart when upgraded.
62    ///
63    /// RangeRead and RangeWrite cause restarts in certain upgrade scenarios.
64    #[inline]
65    pub fn causes_restart(self) -> bool {
66        matches!(self, LockType::RangeRead | LockType::RangeWrite)
67    }
68
69    /// Returns the conflict status between a held lock and a requested lock.
70    ///
71    /// This implements the 5x5 conflict matrix from the:
72    /// ```text
73    ///              READ    WRITE   RANGE_R  RANGE_W  RANGE_I
74    /// READ        ALLOW   BLOCK   ALLOW    BLOCK    ALLOW
75    /// WRITE       BLOCK   BLOCK   BLOCK    BLOCK    ALLOW
76    /// RANGE_READ  ALLOW   BLOCK   ALLOW    BLOCK    BLOCK
77    /// RANGE_WRITE BLOCK   BLOCK   BLOCK    BLOCK    BLOCK
78    /// RANGE_INS   ALLOW   ALLOW   RESTART  RESTART  ALLOW
79    /// ```
80    pub fn get_conflict(self, requested: LockType) -> LockConflict {
81        use LockConflict::*;
82        use LockType::*;
83
84        match (self, requested) {
85            // READ row
86            (Read, Read) => Allow,
87            (Read, Write) => Block,
88            (Read, RangeRead) => Allow,
89            (Read, RangeWrite) => Block,
90            (Read, RangeInsert) => Allow,
91
92            // WRITE row
93            (Write, Read) => Block,
94            (Write, Write) => Block,
95            (Write, RangeRead) => Block,
96            (Write, RangeWrite) => Block,
97            (Write, RangeInsert) => Allow,
98
99            // RANGE_READ row
100            (RangeRead, Read) => Allow,
101            (RangeRead, Write) => Block,
102            (RangeRead, RangeRead) => Allow,
103            (RangeRead, RangeWrite) => Block,
104            (RangeRead, RangeInsert) => Block,
105
106            // RANGE_WRITE row
107            (RangeWrite, Read) => Block,
108            (RangeWrite, Write) => Block,
109            (RangeWrite, RangeRead) => Block,
110            (RangeWrite, RangeWrite) => Block,
111            (RangeWrite, RangeInsert) => Block,
112
113            // RANGE_INSERT row
114            (RangeInsert, Read) => Allow,
115            (RangeInsert, Write) => Allow,
116            (RangeInsert, RangeRead) => LockConflict::Restart,
117            (RangeInsert, RangeWrite) => LockConflict::Restart,
118            (RangeInsert, RangeInsert) => Allow,
119
120            // None and Restart are not used in conflict checking
121            _ => Allow,
122        }
123    }
124
125    /// Returns the upgrade result when a locker holds this lock and requests another.
126    ///
127    /// This implements the 5x5 upgrade matrix from the:
128    /// ```text
129    ///              READ           WRITE              RANGE_R            RANGE_W              RANGE_I
130    /// READ        EXISTING       WRITE_PROMOTE      RANGE_READ_IMMED   RANGE_WRITE_PROMOTE  ILLEGAL
131    /// WRITE       EXISTING       EXISTING           RANGE_WRITE_IMMED  RANGE_WRITE_IMMED    ILLEGAL
132    /// RANGE_READ  EXISTING       RANGE_WRITE_PROM   EXISTING           RANGE_WRITE_PROMOTE  ILLEGAL
133    /// RANGE_WRITE EXISTING       EXISTING           EXISTING           EXISTING             ILLEGAL
134    /// RANGE_INS   ILLEGAL        ILLEGAL            ILLEGAL            ILLEGAL              EXISTING
135    /// ```
136    pub fn get_upgrade(self, requested: LockType) -> LockUpgrade {
137        use LockType::*;
138        use LockUpgrade::*;
139
140        match (self, requested) {
141            // READ row
142            (Read, Read) => Existing,
143            (Read, Write) => WritePromote,
144            (Read, RangeRead) => RangeReadImmed,
145            (Read, RangeWrite) => RangeWritePromote,
146            (Read, RangeInsert) => Illegal,
147
148            // WRITE row
149            (Write, Read) => Existing,
150            (Write, Write) => Existing,
151            (Write, RangeRead) => RangeWriteImmed,
152            (Write, RangeWrite) => RangeWriteImmed,
153            (Write, RangeInsert) => Illegal,
154
155            // RANGE_READ row
156            (RangeRead, Read) => Existing,
157            (RangeRead, Write) => RangeWritePromote,
158            (RangeRead, RangeRead) => Existing,
159            (RangeRead, RangeWrite) => RangeWritePromote,
160            (RangeRead, RangeInsert) => Illegal,
161
162            // RANGE_WRITE row
163            (RangeWrite, Read) => Existing,
164            (RangeWrite, Write) => Existing,
165            (RangeWrite, RangeRead) => Existing,
166            (RangeWrite, RangeWrite) => Existing,
167            (RangeWrite, RangeInsert) => Illegal,
168
169            // RANGE_INSERT row
170            (RangeInsert, Read) => Illegal,
171            (RangeInsert, Write) => Illegal,
172            (RangeInsert, RangeRead) => Illegal,
173            (RangeInsert, RangeWrite) => Illegal,
174            (RangeInsert, RangeInsert) => Existing,
175
176            // None and Restart upgrades
177            (None, _) => Illegal,
178            (_, None) => Existing,
179            (Restart, _) => Illegal,
180            (_, Restart) => Illegal,
181        }
182    }
183
184    /// Returns the array index for this lock type (0-4 for the 5 main types).
185    ///
186    /// Used for indexing into statistics arrays.
187    #[inline]
188    pub fn index(self) -> usize {
189        match self {
190            LockType::Read => 0,
191            LockType::Write => 1,
192            LockType::RangeRead => 2,
193            LockType::RangeWrite => 3,
194            LockType::RangeInsert => 4,
195            LockType::None => 5,
196            LockType::Restart => 6,
197        }
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_is_write_lock() {
207        assert!(!LockType::Read.is_write_lock());
208        assert!(LockType::Write.is_write_lock());
209        assert!(!LockType::RangeRead.is_write_lock());
210        assert!(LockType::RangeWrite.is_write_lock());
211        assert!(!LockType::RangeInsert.is_write_lock()); // Important: NOT a write lock per 
212        assert!(!LockType::None.is_write_lock());
213        assert!(!LockType::Restart.is_write_lock());
214    }
215
216    #[test]
217    fn test_causes_restart() {
218        assert!(!LockType::Read.causes_restart());
219        assert!(!LockType::Write.causes_restart());
220        assert!(LockType::RangeRead.causes_restart());
221        assert!(LockType::RangeWrite.causes_restart());
222        assert!(!LockType::RangeInsert.causes_restart());
223        assert!(!LockType::None.causes_restart());
224        assert!(!LockType::Restart.causes_restart());
225    }
226
227    #[test]
228    fn test_index() {
229        assert_eq!(LockType::Read.index(), 0);
230        assert_eq!(LockType::Write.index(), 1);
231        assert_eq!(LockType::RangeRead.index(), 2);
232        assert_eq!(LockType::RangeWrite.index(), 3);
233        assert_eq!(LockType::RangeInsert.index(), 4);
234        assert_eq!(LockType::None.index(), 5);
235        assert_eq!(LockType::Restart.index(), 6);
236    }
237
238    // Test all 25 entries of the conflict matrix
239    #[test]
240    fn test_conflict_matrix_read_row() {
241        assert_eq!(
242            LockType::Read.get_conflict(LockType::Read),
243            LockConflict::Allow
244        );
245        assert_eq!(
246            LockType::Read.get_conflict(LockType::Write),
247            LockConflict::Block
248        );
249        assert_eq!(
250            LockType::Read.get_conflict(LockType::RangeRead),
251            LockConflict::Allow
252        );
253        assert_eq!(
254            LockType::Read.get_conflict(LockType::RangeWrite),
255            LockConflict::Block
256        );
257        assert_eq!(
258            LockType::Read.get_conflict(LockType::RangeInsert),
259            LockConflict::Allow
260        );
261    }
262
263    #[test]
264    fn test_conflict_matrix_write_row() {
265        assert_eq!(
266            LockType::Write.get_conflict(LockType::Read),
267            LockConflict::Block
268        );
269        assert_eq!(
270            LockType::Write.get_conflict(LockType::Write),
271            LockConflict::Block
272        );
273        assert_eq!(
274            LockType::Write.get_conflict(LockType::RangeRead),
275            LockConflict::Block
276        );
277        assert_eq!(
278            LockType::Write.get_conflict(LockType::RangeWrite),
279            LockConflict::Block
280        );
281        assert_eq!(
282            LockType::Write.get_conflict(LockType::RangeInsert),
283            LockConflict::Allow
284        );
285    }
286
287    #[test]
288    fn test_conflict_matrix_range_read_row() {
289        assert_eq!(
290            LockType::RangeRead.get_conflict(LockType::Read),
291            LockConflict::Allow
292        );
293        assert_eq!(
294            LockType::RangeRead.get_conflict(LockType::Write),
295            LockConflict::Block
296        );
297        assert_eq!(
298            LockType::RangeRead.get_conflict(LockType::RangeRead),
299            LockConflict::Allow
300        );
301        assert_eq!(
302            LockType::RangeRead.get_conflict(LockType::RangeWrite),
303            LockConflict::Block
304        );
305        assert_eq!(
306            LockType::RangeRead.get_conflict(LockType::RangeInsert),
307            LockConflict::Block
308        );
309    }
310
311    #[test]
312    fn test_conflict_matrix_range_write_row() {
313        assert_eq!(
314            LockType::RangeWrite.get_conflict(LockType::Read),
315            LockConflict::Block
316        );
317        assert_eq!(
318            LockType::RangeWrite.get_conflict(LockType::Write),
319            LockConflict::Block
320        );
321        assert_eq!(
322            LockType::RangeWrite.get_conflict(LockType::RangeRead),
323            LockConflict::Block
324        );
325        assert_eq!(
326            LockType::RangeWrite.get_conflict(LockType::RangeWrite),
327            LockConflict::Block
328        );
329        assert_eq!(
330            LockType::RangeWrite.get_conflict(LockType::RangeInsert),
331            LockConflict::Block
332        );
333    }
334
335    #[test]
336    fn test_conflict_matrix_range_insert_row() {
337        assert_eq!(
338            LockType::RangeInsert.get_conflict(LockType::Read),
339            LockConflict::Allow
340        );
341        assert_eq!(
342            LockType::RangeInsert.get_conflict(LockType::Write),
343            LockConflict::Allow
344        );
345        assert_eq!(
346            LockType::RangeInsert.get_conflict(LockType::RangeRead),
347            LockConflict::Restart
348        );
349        assert_eq!(
350            LockType::RangeInsert.get_conflict(LockType::RangeWrite),
351            LockConflict::Restart
352        );
353        assert_eq!(
354            LockType::RangeInsert.get_conflict(LockType::RangeInsert),
355            LockConflict::Allow
356        );
357    }
358
359    // Test all 25 entries of the upgrade matrix
360    #[test]
361    fn test_upgrade_matrix_read_row() {
362        assert_eq!(
363            LockType::Read.get_upgrade(LockType::Read),
364            LockUpgrade::Existing
365        );
366        assert_eq!(
367            LockType::Read.get_upgrade(LockType::Write),
368            LockUpgrade::WritePromote
369        );
370        assert_eq!(
371            LockType::Read.get_upgrade(LockType::RangeRead),
372            LockUpgrade::RangeReadImmed
373        );
374        assert_eq!(
375            LockType::Read.get_upgrade(LockType::RangeWrite),
376            LockUpgrade::RangeWritePromote
377        );
378        assert_eq!(
379            LockType::Read.get_upgrade(LockType::RangeInsert),
380            LockUpgrade::Illegal
381        );
382    }
383
384    #[test]
385    fn test_upgrade_matrix_write_row() {
386        assert_eq!(
387            LockType::Write.get_upgrade(LockType::Read),
388            LockUpgrade::Existing
389        );
390        assert_eq!(
391            LockType::Write.get_upgrade(LockType::Write),
392            LockUpgrade::Existing
393        );
394        assert_eq!(
395            LockType::Write.get_upgrade(LockType::RangeRead),
396            LockUpgrade::RangeWriteImmed
397        );
398        assert_eq!(
399            LockType::Write.get_upgrade(LockType::RangeWrite),
400            LockUpgrade::RangeWriteImmed
401        );
402        assert_eq!(
403            LockType::Write.get_upgrade(LockType::RangeInsert),
404            LockUpgrade::Illegal
405        );
406    }
407
408    #[test]
409    fn test_upgrade_matrix_range_read_row() {
410        assert_eq!(
411            LockType::RangeRead.get_upgrade(LockType::Read),
412            LockUpgrade::Existing
413        );
414        assert_eq!(
415            LockType::RangeRead.get_upgrade(LockType::Write),
416            LockUpgrade::RangeWritePromote
417        );
418        assert_eq!(
419            LockType::RangeRead.get_upgrade(LockType::RangeRead),
420            LockUpgrade::Existing
421        );
422        assert_eq!(
423            LockType::RangeRead.get_upgrade(LockType::RangeWrite),
424            LockUpgrade::RangeWritePromote
425        );
426        assert_eq!(
427            LockType::RangeRead.get_upgrade(LockType::RangeInsert),
428            LockUpgrade::Illegal
429        );
430    }
431
432    #[test]
433    fn test_upgrade_matrix_range_write_row() {
434        assert_eq!(
435            LockType::RangeWrite.get_upgrade(LockType::Read),
436            LockUpgrade::Existing
437        );
438        assert_eq!(
439            LockType::RangeWrite.get_upgrade(LockType::Write),
440            LockUpgrade::Existing
441        );
442        assert_eq!(
443            LockType::RangeWrite.get_upgrade(LockType::RangeRead),
444            LockUpgrade::Existing
445        );
446        assert_eq!(
447            LockType::RangeWrite.get_upgrade(LockType::RangeWrite),
448            LockUpgrade::Existing
449        );
450        assert_eq!(
451            LockType::RangeWrite.get_upgrade(LockType::RangeInsert),
452            LockUpgrade::Illegal
453        );
454    }
455
456    #[test]
457    fn test_upgrade_matrix_range_insert_row() {
458        assert_eq!(
459            LockType::RangeInsert.get_upgrade(LockType::Read),
460            LockUpgrade::Illegal
461        );
462        assert_eq!(
463            LockType::RangeInsert.get_upgrade(LockType::Write),
464            LockUpgrade::Illegal
465        );
466        assert_eq!(
467            LockType::RangeInsert.get_upgrade(LockType::RangeRead),
468            LockUpgrade::Illegal
469        );
470        assert_eq!(
471            LockType::RangeInsert.get_upgrade(LockType::RangeWrite),
472            LockUpgrade::Illegal
473        );
474        assert_eq!(
475            LockType::RangeInsert.get_upgrade(LockType::RangeInsert),
476            LockUpgrade::Existing
477        );
478    }
479
480    #[test]
481    fn test_upgrade_matrix_none() {
482        // None as held lock is illegal
483        assert_eq!(
484            LockType::None.get_upgrade(LockType::Read),
485            LockUpgrade::Illegal
486        );
487        assert_eq!(
488            LockType::None.get_upgrade(LockType::Write),
489            LockUpgrade::Illegal
490        );
491
492        // None as requested lock is always existing (no upgrade needed)
493        assert_eq!(
494            LockType::Read.get_upgrade(LockType::None),
495            LockUpgrade::Existing
496        );
497        assert_eq!(
498            LockType::Write.get_upgrade(LockType::None),
499            LockUpgrade::Existing
500        );
501    }
502}