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