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