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