1use crate::database::{Database, ExternalTriggerBridge};
12use crate::epoch::{Epoch, Snapshot};
13use crate::error::{MongrelError, Result};
14use crate::memtable::Value;
15use crate::rowid::RowId;
16use crate::wal::SharedWal;
17use parking_lot::{Condvar, Mutex as PlMutex};
18
19pub(crate) enum Staged {
21 Put(Vec<(u16, Value)>),
22 Delete(RowId),
23 Truncate,
24}
25
26#[derive(Debug, Clone)]
27pub struct OwnedRow {
28 pub columns: Vec<(u16, Value)>,
29}
30
31#[derive(Debug, Clone)]
32pub struct PutResult {
33 pub auto_inc: Option<i64>,
34 pub row: OwnedRow,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum UpsertActionKind {
39 Inserted,
40 Updated,
41 Unchanged,
42}
43
44#[derive(Debug, Clone)]
45pub enum UpsertAction {
46 DoNothing,
47 DoUpdate(Vec<(u16, Value)>),
48}
49
50#[derive(Debug, Clone)]
51pub struct UpsertResult {
52 pub action: UpsertActionKind,
53 pub row: OwnedRow,
54 pub auto_inc: Option<i64>,
55}
56
57pub struct Transaction<'db> {
60 db: &'db Database,
61 txn_id: u64,
62 read: Snapshot,
63 staging: Vec<(u64 , Staged)>,
64 external_states: Vec<(String, Vec<u8>)>,
65 external_trigger_bridge: Option<&'db dyn ExternalTriggerBridge>,
66 _active: Option<ActiveTxnGuard<'db>>,
67}
68
69impl<'db> Transaction<'db> {
70 pub(crate) fn new(db: &'db Database, txn_id: u64, read: Snapshot) -> Self {
71 let guard = db.register_active(read.epoch);
72 Self {
73 db,
74 txn_id,
75 read,
76 staging: Vec::new(),
77 external_states: Vec::new(),
78 external_trigger_bridge: None,
79 _active: Some(guard),
80 }
81 }
82
83 pub(crate) fn with_external_trigger_bridge(
84 mut self,
85 bridge: &'db dyn ExternalTriggerBridge,
86 ) -> Self {
87 self.external_trigger_bridge = Some(bridge);
88 self
89 }
90
91 pub fn read_snapshot(&self) -> Snapshot {
92 self.read
93 }
94
95 pub fn txn_id(&self) -> u64 {
98 self.txn_id
99 }
100
101 pub fn put(&mut self, table: &str, mut cells: Vec<(u16, Value)>) -> Result<Option<i64>> {
108 self.db
109 .require_table(table, crate::auth_state::RequiredPermission::Insert)?;
110 let id = self.db.table_id(table)?;
111 self.reject_after_truncate(id)?;
112 let handle = self.db.table(table)?;
113 let mut t = handle.lock();
114 let assigned = t.fill_auto_inc(&mut cells)?;
115 t.apply_defaults(&mut cells)?;
116 drop(t);
117 self.staging.push((id, Staged::Put(cells)));
118 Ok(assigned)
119 }
120
121 pub fn put_returning(
122 &mut self,
123 table: &str,
124 mut cells: Vec<(u16, Value)>,
125 ) -> Result<PutResult> {
126 self.db
127 .require_table(table, crate::auth_state::RequiredPermission::Insert)?;
128 let id = self.db.table_id(table)?;
129 self.reject_after_truncate(id)?;
130 let handle = self.db.table(table)?;
131 let mut t = handle.lock();
132 let assigned = t.fill_auto_inc(&mut cells)?;
133 t.apply_defaults(&mut cells)?;
134 drop(t);
135 let row = owned_row_from_cells(&cells);
136 self.staging.push((id, Staged::Put(cells)));
137 Ok(PutResult {
138 auto_inc: assigned,
139 row,
140 })
141 }
142
143 pub fn put_batch(
149 &mut self,
150 table: &str,
151 rows: Vec<Vec<(u16, Value)>>,
152 ) -> Result<Vec<Option<i64>>> {
153 self.db
154 .require_table(table, crate::auth_state::RequiredPermission::Insert)?;
155 let id = self.db.table_id(table)?;
156 self.reject_after_truncate(id)?;
157 let handle = self.db.table(table)?;
158 let mut t = handle.lock();
159 let mut assigned = Vec::with_capacity(rows.len());
160 for mut cells in rows {
161 let a = t.fill_auto_inc(&mut cells)?;
162 t.apply_defaults(&mut cells)?;
163 assigned.push(a);
164 self.staging.push((id, Staged::Put(cells)));
165 }
166 drop(t);
167 Ok(assigned)
168 }
169
170 pub fn delete(&mut self, table: &str, row_id: RowId) -> Result<()> {
172 self.db
173 .require_table(table, crate::auth_state::RequiredPermission::Delete)?;
174 let id = self.db.table_id(table)?;
175 self.reject_after_truncate(id)?;
176 self.staging.push((id, Staged::Delete(row_id)));
177 Ok(())
178 }
179
180 pub fn put_external_state(&mut self, table: &str, state: Vec<u8>) -> Result<()> {
183 if self.db.external_table(table).is_none() {
184 return Err(MongrelError::NotFound(format!(
185 "external table {table:?} not found"
186 )));
187 }
188 self.external_states.push((table.to_string(), state));
189 Ok(())
190 }
191
192 pub fn delete_many(&mut self, table: &str, row_ids: Vec<RowId>) -> Result<Vec<OwnedRow>> {
193 self.db
194 .require_table(table, crate::auth_state::RequiredPermission::Delete)?;
195 let id = self.db.table_id(table)?;
196 self.reject_after_truncate(id)?;
197 let snap = self.read;
198 let handle = self.db.table(table)?;
199 let t = handle.lock();
200 let mut pre_images = Vec::with_capacity(row_ids.len());
201 for row_id in &row_ids {
202 if let Some(row) = t.get(*row_id, snap) {
203 pre_images.push(owned_row_from_map(row.columns));
204 }
205 }
206 drop(t);
207 for row_id in row_ids {
208 self.staging.push((id, Staged::Delete(row_id)));
209 }
210 Ok(pre_images)
211 }
212
213 pub fn update_many(
214 &mut self,
215 table: &str,
216 updates: Vec<(RowId, Vec<(u16, Value)>)>,
217 ) -> Result<Vec<OwnedRow>> {
218 self.db
219 .require_table(table, crate::auth_state::RequiredPermission::Update)?;
220 let id = self.db.table_id(table)?;
221 self.reject_after_truncate(id)?;
222 let snap = self.read;
223 let handle = self.db.table(table)?;
224 let t = handle.lock();
225 let mut post_images = Vec::with_capacity(updates.len());
226 let mut staged = Vec::with_capacity(updates.len() * 2);
227 for (old_id, new_cells) in updates {
228 let old_row = t
229 .get(old_id, snap)
230 .ok_or_else(|| MongrelError::NotFound(format!("row {old_id:?} not found")))?;
231 let merged = merge_cells(old_row.columns.into_iter().collect(), new_cells);
232 post_images.push(owned_row_from_cells(&merged));
233 staged.push((id, Staged::Delete(old_id)));
234 staged.push((id, Staged::Put(merged)));
235 }
236 drop(t);
237 self.staging.extend(staged);
238 Ok(post_images)
239 }
240
241 pub fn upsert(
242 &mut self,
243 table: &str,
244 mut insert_cells: Vec<(u16, Value)>,
245 action: UpsertAction,
246 ) -> Result<UpsertResult> {
247 self.db
251 .require_table(table, crate::auth_state::RequiredPermission::Insert)?;
252 let id = self.db.table_id(table)?;
253 self.reject_after_truncate(id)?;
254 match (self.existing_pk_row(table, &insert_cells)?, action) {
255 (None, _) => {
256 let handle = self.db.table(table)?;
257 let mut t = handle.lock();
258 let assigned = t.fill_auto_inc(&mut insert_cells)?;
259 t.apply_defaults(&mut insert_cells)?;
260 drop(t);
261 let row = owned_row_from_cells(&insert_cells);
262 self.staging.push((id, Staged::Put(insert_cells)));
263 Ok(UpsertResult {
264 action: UpsertActionKind::Inserted,
265 row,
266 auto_inc: assigned,
267 })
268 }
269 (Some((_old_id, old_row)), UpsertAction::DoNothing) => Ok(UpsertResult {
270 action: UpsertActionKind::Unchanged,
271 row: old_row,
272 auto_inc: None,
273 }),
274 (Some((old_id, old_row)), UpsertAction::DoUpdate(update_cells)) => {
275 self.db
277 .require_table(table, crate::auth_state::RequiredPermission::Update)?;
278 let merged = merge_cells(old_row.columns.clone(), update_cells);
279 if columns_equal(&old_row.columns, &merged) {
280 return Ok(UpsertResult {
281 action: UpsertActionKind::Unchanged,
282 row: old_row,
283 auto_inc: None,
284 });
285 }
286 let row = owned_row_from_cells(&merged);
287 self.staging.push((id, Staged::Delete(old_id)));
288 self.staging.push((id, Staged::Put(merged)));
289 Ok(UpsertResult {
290 action: UpsertActionKind::Updated,
291 row,
292 auto_inc: None,
293 })
294 }
295 }
296 }
297
298 pub fn truncate(&mut self, table: &str) -> Result<()> {
299 self.db
300 .require_table(table, crate::auth_state::RequiredPermission::Delete)?;
301 let id = self.db.table_id(table)?;
302 for (table_id, op) in &self.staging {
303 if *table_id == id && !matches!(op, Staged::Truncate) {
304 return Err(MongrelError::InvalidArgument(
305 "truncate cannot be combined with other writes on the same table".into(),
306 ));
307 }
308 }
309 self.staging.push((id, Staged::Truncate));
310 Ok(())
311 }
312
313 fn reject_after_truncate(&self, table_id: u64) -> Result<()> {
314 if self
315 .staging
316 .iter()
317 .any(|(tid, op)| *tid == table_id && matches!(op, Staged::Truncate))
318 {
319 return Err(MongrelError::InvalidArgument(
320 "truncate cannot be combined with other writes on the same table".into(),
321 ));
322 }
323 Ok(())
324 }
325
326 fn existing_pk_row(
327 &self,
328 table: &str,
329 cells: &[(u16, Value)],
330 ) -> Result<Option<(RowId, OwnedRow)>> {
331 let handle = self.db.table(table)?;
332 let t = handle.lock();
333 let Some(pk_col) = t.schema().primary_key() else {
334 return Ok(None);
335 };
336 let Some((_, pk_value)) = cells.iter().find(|(id, _)| *id == pk_col.id) else {
337 return Ok(None);
338 };
339 if matches!(pk_value, Value::Null) {
340 return Ok(None);
341 }
342 let Some(row_id) = t.lookup_pk(&pk_value.encode_key()) else {
343 return Ok(None);
344 };
345 Ok(t.get(row_id, self.read)
346 .map(|row| (row_id, owned_row_from_map(row.columns))))
347 }
348
349 pub fn commit(self) -> Result<Epoch> {
351 self.db.commit_transaction_with_external_states(
352 self.txn_id,
353 self.read.epoch,
354 self.staging,
355 self.external_states,
356 self.external_trigger_bridge,
357 )
358 }
359
360 pub fn rollback(self) {
362 }
364}
365
366fn owned_row_from_cells(cells: &[(u16, Value)]) -> OwnedRow {
367 let mut columns = cells.to_vec();
368 columns.sort_by_key(|(id, _)| *id);
369 OwnedRow { columns }
370}
371
372fn owned_row_from_map(columns: HashMap<u16, Value>) -> OwnedRow {
373 let mut columns: Vec<(u16, Value)> = columns.into_iter().collect();
374 columns.sort_by_key(|(id, _)| *id);
375 OwnedRow { columns }
376}
377
378fn merge_cells(mut base: Vec<(u16, Value)>, updates: Vec<(u16, Value)>) -> Vec<(u16, Value)> {
379 for (id, value) in updates {
380 base.retain(|(existing, _)| *existing != id);
381 base.push((id, value));
382 }
383 base.sort_by_key(|(id, _)| *id);
384 base
385}
386
387fn columns_equal(a: &[(u16, Value)], b: &[(u16, Value)]) -> bool {
388 if a.len() != b.len() {
389 return false;
390 }
391 let mut a: Vec<_> = a.iter().collect();
392 let mut b: Vec<_> = b.iter().collect();
393 a.sort_by_key(|(id, _)| *id);
394 b.sort_by_key(|(id, _)| *id);
395 a.iter()
396 .zip(b.iter())
397 .all(|((id_a, v_a), (id_b, v_b))| id_a == id_b && v_a == v_b)
398}
399
400pub(crate) enum StagedOp {
402 Put(crate::memtable::Row),
403 Delete(RowId),
404 Truncate,
405}
406
407use std::collections::{BTreeMap, HashMap};
410use std::hash::{Hash, Hasher};
411
412#[derive(Clone, Debug)]
415pub enum WriteKey {
416 Row { table_id: u64, row_id: u64 },
418 Unique {
420 table_id: u64,
421 index_id: u16,
422 key_hash: u64,
423 },
424 Table { table_id: u64 },
426}
427
428impl Hash for WriteKey {
429 fn hash<H: Hasher>(&self, state: &mut H) {
430 match self {
431 WriteKey::Row { table_id, row_id } => {
432 0u8.hash(state);
433 table_id.hash(state);
434 row_id.hash(state);
435 }
436 WriteKey::Unique {
437 table_id,
438 index_id,
439 key_hash,
440 } => {
441 1u8.hash(state);
442 table_id.hash(state);
443 index_id.hash(state);
444 key_hash.hash(state);
445 }
446 WriteKey::Table { table_id } => {
447 2u8.hash(state);
448 table_id.hash(state);
449 }
450 }
451 }
452}
453
454impl PartialEq for WriteKey {
455 fn eq(&self, other: &Self) -> bool {
456 match (self, other) {
457 (
458 WriteKey::Row {
459 table_id: a,
460 row_id: b,
461 },
462 WriteKey::Row {
463 table_id: c,
464 row_id: d,
465 },
466 ) => a == c && b == d,
467 (
468 WriteKey::Unique {
469 table_id: a,
470 index_id: b,
471 key_hash: c,
472 },
473 WriteKey::Unique {
474 table_id: d,
475 index_id: e,
476 key_hash: f,
477 },
478 ) => a == d && b == e && c == f,
479 (WriteKey::Table { table_id: a }, WriteKey::Table { table_id: b }) => a == b,
480 _ => false,
481 }
482 }
483}
484
485impl Eq for WriteKey {}
486
487const CONFLICT_SHARDS: usize = 16;
488
489pub struct ConflictIndex {
493 shards: [parking_lot::Mutex<HashMap<WriteKey, u64>>; CONFLICT_SHARDS],
494 table_truncate_epochs: parking_lot::Mutex<HashMap<u64, u64>>,
495 table_write_epochs: parking_lot::Mutex<HashMap<u64, u64>>,
496 version: std::sync::atomic::AtomicU64,
500}
501
502impl ConflictIndex {
503 pub fn new() -> Self {
504 Self {
505 shards: std::array::from_fn(|_| parking_lot::Mutex::new(HashMap::new())),
506 table_truncate_epochs: parking_lot::Mutex::new(HashMap::new()),
507 table_write_epochs: parking_lot::Mutex::new(HashMap::new()),
508 version: std::sync::atomic::AtomicU64::new(0),
509 }
510 }
511
512 pub fn version(&self) -> u64 {
516 self.version.load(std::sync::atomic::Ordering::Acquire)
517 }
518
519 fn shard(&self, key: &WriteKey) -> &parking_lot::Mutex<HashMap<WriteKey, u64>> {
520 let mut h = std::collections::hash_map::DefaultHasher::new();
521 key.hash(&mut h);
522 let idx = (h.finish() as usize) & (CONFLICT_SHARDS - 1);
523 &self.shards[idx]
524 }
525
526 pub fn conflicts(&self, keys: &[WriteKey], read_epoch: Epoch) -> bool {
529 for k in keys {
530 let s = self.shard(k);
531 if let Some(&ce) = s.lock().get(k) {
532 if ce > read_epoch.0 {
533 return true;
534 }
535 }
536 }
537 let truncates = self.table_truncate_epochs.lock();
538 let writes = self.table_write_epochs.lock();
539 for k in keys {
540 match k {
541 WriteKey::Row { table_id, .. } | WriteKey::Unique { table_id, .. } => {
542 if truncates.get(table_id).is_some_and(|&ce| ce > read_epoch.0) {
543 return true;
544 }
545 }
546 WriteKey::Table { table_id } => {
547 if writes.get(table_id).is_some_and(|&ce| ce > read_epoch.0) {
548 return true;
549 }
550 }
551 }
552 }
553 false
554 }
555
556 pub fn record(&self, keys: &[WriteKey], commit_epoch: Epoch) {
558 for k in keys {
559 let s = self.shard(k);
560 s.lock().insert(k.clone(), commit_epoch.0);
561 }
562 let mut truncates = self.table_truncate_epochs.lock();
563 let mut writes = self.table_write_epochs.lock();
564 for k in keys {
565 match k {
566 WriteKey::Table { table_id } => {
567 truncates
568 .entry(*table_id)
569 .and_modify(|ce| *ce = (*ce).max(commit_epoch.0))
570 .or_insert(commit_epoch.0);
571 }
572 WriteKey::Row { table_id, .. } | WriteKey::Unique { table_id, .. } => {
573 writes
574 .entry(*table_id)
575 .and_modify(|ce| *ce = (*ce).max(commit_epoch.0))
576 .or_insert(commit_epoch.0);
577 }
578 }
579 }
580 self.version
581 .fetch_add(1, std::sync::atomic::Ordering::Release);
582 }
583
584 pub fn prune_below(&self, min_active: Epoch) {
587 for s in &self.shards {
588 s.lock().retain(|_, ce| *ce >= min_active.0);
589 }
590 self.table_truncate_epochs
591 .lock()
592 .retain(|_, ce| *ce >= min_active.0);
593 self.table_write_epochs
594 .lock()
595 .retain(|_, ce| *ce >= min_active.0);
596 }
597}
598
599impl Default for ConflictIndex {
600 fn default() -> Self {
601 Self::new()
602 }
603}
604
605pub struct GroupCommit {
615 inner: PlMutex<GroupState>,
616 cv: Condvar,
617}
618
619struct GroupState {
620 durable_seq: u64,
621 syncing: bool,
622 poisoned: bool,
623}
624
625impl GroupCommit {
626 pub fn new(durable_seq: u64) -> Self {
627 Self {
628 inner: PlMutex::new(GroupState {
629 durable_seq,
630 syncing: false,
631 poisoned: false,
632 }),
633 cv: Condvar::new(),
634 }
635 }
636
637 pub fn await_durable(&self, wal: &PlMutex<SharedWal>, commit_seq: u64) -> Result<()> {
643 let mut st = self.inner.lock();
644 loop {
645 if st.poisoned {
646 return Err(MongrelError::Other(
647 "database poisoned by fsync error".into(),
648 ));
649 }
650 if st.durable_seq >= commit_seq {
651 return Ok(());
652 }
653 if st.syncing {
654 self.cv.wait(&mut st);
656 continue;
657 }
658 st.syncing = true;
661 drop(st);
662 let res = wal.lock().group_sync();
663 st = self.inner.lock();
664 st.syncing = false;
665 match res {
666 Ok(durable) => {
667 if durable > st.durable_seq {
668 st.durable_seq = durable;
669 }
670 self.cv.notify_all();
671 }
674 Err(e) => {
675 st.poisoned = true;
676 self.cv.notify_all();
677 return Err(e);
678 }
679 }
680 }
681 }
682}
683
684pub struct ActiveTxns {
688 inner: parking_lot::Mutex<BTreeMap<u64, u64>>,
689}
690
691impl ActiveTxns {
692 pub fn new() -> Self {
693 Self {
694 inner: parking_lot::Mutex::new(BTreeMap::new()),
695 }
696 }
697
698 pub fn register(&self, read_epoch: Epoch) -> ActiveTxnGuard<'_> {
701 let mut g = self.inner.lock();
702 *g.entry(read_epoch.0).or_insert(0) += 1;
703 ActiveTxnGuard {
704 active: self,
705 epoch: read_epoch.0,
706 }
707 }
708
709 pub fn min_read_epoch(&self) -> u64 {
711 self.inner.lock().keys().next().copied().unwrap_or(u64::MAX)
712 }
713}
714
715impl Default for ActiveTxns {
716 fn default() -> Self {
717 Self::new()
718 }
719}
720
721pub struct ActiveTxnGuard<'a> {
723 active: &'a ActiveTxns,
724 epoch: u64,
725}
726
727impl Drop for ActiveTxnGuard<'_> {
728 fn drop(&mut self) {
729 let mut g = self.active.inner.lock();
730 if let Some(count) = g.get_mut(&self.epoch) {
731 *count -= 1;
732 if *count == 0 {
733 g.remove(&self.epoch);
734 }
735 }
736 }
737}
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742
743 #[test]
744 fn conflict_index_first_committer_wins_and_prunes_safely() {
745 let ci = ConflictIndex::new();
746 let k = vec![WriteKey::Row {
747 table_id: 1,
748 row_id: 7,
749 }];
750 assert!(!ci.conflicts(&k, Epoch(5)));
751 ci.record(&k, Epoch(6));
752 assert!(ci.conflicts(&k, Epoch(5)));
753 assert!(!ci.conflicts(&k, Epoch(6)));
754 ci.prune_below(Epoch(7));
755 assert!(!ci.conflicts(&k, Epoch(5)));
756 }
757
758 #[test]
759 fn conflict_index_table_scope_conflicts_both_directions() {
760 let ci = ConflictIndex::new();
761 ci.record(&[WriteKey::Table { table_id: 1 }], Epoch(6));
762 assert!(ci.conflicts(
763 &[WriteKey::Row {
764 table_id: 1,
765 row_id: 7,
766 }],
767 Epoch(5)
768 ));
769 assert!(ci.conflicts(
770 &[WriteKey::Unique {
771 table_id: 1,
772 index_id: 0,
773 key_hash: 42,
774 }],
775 Epoch(5)
776 ));
777 assert!(!ci.conflicts(
778 &[WriteKey::Row {
779 table_id: 2,
780 row_id: 7,
781 }],
782 Epoch(5)
783 ));
784
785 let ci = ConflictIndex::new();
786 ci.record(
787 &[WriteKey::Row {
788 table_id: 1,
789 row_id: 7,
790 }],
791 Epoch(6),
792 );
793 assert!(ci.conflicts(&[WriteKey::Table { table_id: 1 }], Epoch(5)));
794 assert!(!ci.conflicts(&[WriteKey::Table { table_id: 2 }], Epoch(5)));
795 }
796
797 #[test]
798 fn writekey_eq_across_variants() {
799 let r1 = WriteKey::Row {
800 table_id: 1,
801 row_id: 2,
802 };
803 let r2 = WriteKey::Row {
804 table_id: 1,
805 row_id: 2,
806 };
807 let r3 = WriteKey::Row {
808 table_id: 1,
809 row_id: 3,
810 };
811 assert_eq!(r1, r2);
812 assert_ne!(r1, r3);
813
814 let u1 = WriteKey::Unique {
815 table_id: 1,
816 index_id: 0,
817 key_hash: 42,
818 };
819 let u2 = WriteKey::Unique {
820 table_id: 1,
821 index_id: 0,
822 key_hash: 42,
823 };
824 assert_eq!(u1, u2);
825 assert_ne!(r1, u1);
826
827 let t1 = WriteKey::Table { table_id: 5 };
828 let t2 = WriteKey::Table { table_id: 5 };
829 assert_eq!(t1, t2);
830 assert_ne!(t1, r1);
831 }
832
833 #[test]
834 fn active_txns_tracks_min_read_epoch() {
835 let at = ActiveTxns::new();
836 assert_eq!(at.min_read_epoch(), u64::MAX);
837 let g1 = at.register(Epoch(5));
838 assert_eq!(at.min_read_epoch(), 5);
839 let g2 = at.register(Epoch(3));
840 assert_eq!(at.min_read_epoch(), 3);
841 drop(g2);
842 assert_eq!(at.min_read_epoch(), 5);
843 drop(g1);
844 assert_eq!(at.min_read_epoch(), u64::MAX);
845 }
846
847 #[test]
848 fn active_txns_dedups_same_epoch() {
849 let at = ActiveTxns::new();
850 let g1 = at.register(Epoch(7));
851 let g2 = at.register(Epoch(7));
852 assert_eq!(at.min_read_epoch(), 7);
853 drop(g1);
854 assert_eq!(at.min_read_epoch(), 7);
855 drop(g2);
856 assert_eq!(at.min_read_epoch(), u64::MAX);
857 }
858}
859
860#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
871pub enum IsolationLevel {
872 #[default]
873 Snapshot,
874 ReadCommitted,
875 Serializable,
876}