1use crate::database::{Database, ExternalTriggerBridge, TableHandle};
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) fn allocate_txn_id(allocator: &PlMutex<u64>) -> Result<u64> {
20 let mut next = allocator.lock();
21 let id = *next;
22 if id == crate::wal::SYSTEM_TXN_ID || id & u32::MAX as u64 == 0 {
23 return Err(MongrelError::Full(
24 "per-open transaction id namespace exhausted; reopen the database".into(),
25 ));
26 }
27 *next = id.checked_add(1).ok_or_else(|| {
28 MongrelError::Full(
29 "per-open transaction id namespace exhausted; reopen the database".into(),
30 )
31 })?;
32 Ok(id)
33}
34
35pub(crate) enum Staged {
37 Put(Vec<(u16, Value)>),
38 Delete(RowId),
39 Update {
43 row_id: RowId,
44 new_row: Vec<(u16, Value)>,
45 changed_columns: Vec<u16>,
46 },
47 Truncate,
48}
49
50#[derive(Debug, Clone)]
51pub struct OwnedRow {
52 pub columns: Vec<(u16, Value)>,
53}
54
55#[derive(Debug, Clone)]
56pub struct PutResult {
57 pub auto_inc: Option<i64>,
58 pub row: OwnedRow,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum UpsertActionKind {
63 Inserted,
64 Updated,
65 Unchanged,
66}
67
68#[derive(Debug, Clone)]
69pub enum UpsertAction {
70 DoNothing,
71 DoUpdate(Vec<(u16, Value)>),
72}
73
74#[derive(Debug, Clone)]
75pub struct UpsertResult {
76 pub action: UpsertActionKind,
77 pub row: OwnedRow,
78 pub auto_inc: Option<i64>,
79}
80
81pub struct Transaction<'db> {
84 db: &'db Database,
85 txn_id: u64,
86 allocation_error: Option<String>,
87 read: Snapshot,
88 staging: Vec<(u64 , Staged)>,
89 external_states: Vec<(String, Vec<u8>)>,
90 materialized_view_updates: Vec<crate::catalog::MaterializedViewEntry>,
91 principal: Option<crate::auth::Principal>,
92 principal_catalog_bound: bool,
93 external_trigger_bridge: Option<&'db dyn ExternalTriggerBridge>,
94 _active: Option<ActiveTxnGuard<'db>>,
95}
96
97impl<'db> Transaction<'db> {
98 pub(crate) fn new(db: &'db Database, txn_id: Result<u64>, read: Snapshot) -> Self {
99 let guard = db.register_active(read.epoch);
100 let (txn_id, allocation_error) = match txn_id {
101 Ok(txn_id) => (txn_id, None),
102 Err(MongrelError::Full(message)) => (crate::wal::SYSTEM_TXN_ID, Some(message)),
103 Err(error) => (crate::wal::SYSTEM_TXN_ID, Some(error.to_string())),
104 };
105 Self {
106 db,
107 txn_id,
108 allocation_error,
109 read,
110 staging: Vec::new(),
111 external_states: Vec::new(),
112 materialized_view_updates: Vec::new(),
113 principal: None,
114 principal_catalog_bound: false,
115 external_trigger_bridge: None,
116 _active: Some(guard),
117 }
118 }
119
120 pub(crate) fn with_external_trigger_bridge(
121 mut self,
122 bridge: &'db dyn ExternalTriggerBridge,
123 ) -> Self {
124 self.external_trigger_bridge = Some(bridge);
125 self
126 }
127
128 pub(crate) fn with_principal(
129 mut self,
130 principal: Option<crate::auth::Principal>,
131 catalog_bound: bool,
132 ) -> Self {
133 self.principal = principal;
134 self.principal_catalog_bound = catalog_bound;
135 self
136 }
137
138 pub fn read_snapshot(&self) -> Snapshot {
139 self.read
140 }
141
142 pub fn txn_id(&self) -> u64 {
145 self.txn_id
146 }
147
148 pub fn put(&mut self, table: &str, mut cells: Vec<(u16, Value)>) -> Result<Option<i64>> {
155 self.require_columns(table, crate::auth::ColumnOperation::Insert, &cells)?;
156 let id = self.db.table_id(table)?;
157 let handle = self.db.table(table)?;
158 let mut t = handle.lock();
159 let assigned = t.fill_auto_inc(&mut cells)?;
160 t.apply_defaults(&mut cells)?;
161 drop(t);
162 self.staging.push((id, Staged::Put(cells)));
163 Ok(assigned)
164 }
165
166 #[doc(hidden)]
168 pub fn put_building(
169 &mut self,
170 table: &str,
171 mut cells: Vec<(u16, Value)>,
172 ) -> Result<Option<i64>> {
173 self.db
174 .require_for(self.principal.as_ref(), &crate::auth::Permission::Ddl)?;
175 let id = self.db.building_table_id(table)?;
176 let handle = self.db.table_by_id(id)?;
177 let mut target = handle.lock();
178 let assigned = target.fill_auto_inc(&mut cells)?;
179 target.apply_defaults(&mut cells)?;
180 let primary_key_column = target
181 .schema()
182 .primary_key()
183 .map(|column| column.id)
184 .ok_or_else(|| MongrelError::Schema("CTAS build table has no primary key".into()))?;
185 let primary_key = cells
186 .iter()
187 .find(|(column, _)| *column == primary_key_column)
188 .map(|(_, value)| value)
189 .ok_or_else(|| MongrelError::InvalidArgument("CTAS primary key is missing".into()))?;
190 if matches!(primary_key, Value::Null) {
191 return Err(MongrelError::InvalidArgument(
192 "CTAS primary key cannot be NULL".into(),
193 ));
194 }
195 let primary_key = primary_key.encode_key();
196 let replacing = self
197 .staging
198 .iter()
199 .any(|(table_id, staged)| *table_id == id && matches!(staged, Staged::Truncate));
200 if !replacing && target.lookup_pk(&primary_key).is_some() {
201 return Err(MongrelError::InvalidArgument(
202 "duplicate CTAS primary key".into(),
203 ));
204 }
205 drop(target);
206 if self.staging.iter().any(|(staged_table, staged)| {
207 if *staged_table != id {
208 return false;
209 }
210 let Staged::Put(staged_cells) = staged else {
211 return false;
212 };
213 staged_cells
214 .iter()
215 .find(|(column, _)| *column == primary_key_column)
216 .is_some_and(|(_, value)| value.encode_key() == primary_key)
217 }) {
218 return Err(MongrelError::InvalidArgument(
219 "duplicate CTAS primary key".into(),
220 ));
221 }
222 self.staging.push((id, Staged::Put(cells)));
223 Ok(assigned)
224 }
225
226 #[doc(hidden)]
228 pub fn truncate_building(&mut self, table: &str) -> Result<()> {
229 self.db
230 .require_for(self.principal.as_ref(), &crate::auth::Permission::Ddl)?;
231 let id = self.db.building_table_id(table)?;
232 if self.staging.iter().any(|(table_id, _)| *table_id == id) {
233 return Err(MongrelError::InvalidArgument(
234 "building-table truncate must be staged before replacement rows".into(),
235 ));
236 }
237 self.staging.push((id, Staged::Truncate));
238 Ok(())
239 }
240
241 pub fn put_returning(
242 &mut self,
243 table: &str,
244 mut cells: Vec<(u16, Value)>,
245 ) -> Result<PutResult> {
246 self.require_columns(table, crate::auth::ColumnOperation::Insert, &cells)?;
247 let id = self.db.table_id(table)?;
248 let handle = self.db.table(table)?;
249 let mut t = handle.lock();
250 let assigned = t.fill_auto_inc(&mut cells)?;
251 t.apply_defaults(&mut cells)?;
252 drop(t);
253 let row = owned_row_from_cells(&cells);
254 self.staging.push((id, Staged::Put(cells)));
255 Ok(PutResult {
256 auto_inc: assigned,
257 row,
258 })
259 }
260
261 #[doc(hidden)]
264 pub fn put_returning_bound(
265 &mut self,
266 table: &str,
267 expected_table_id: u64,
268 expected_schema_id: u64,
269 mut cells: Vec<(u16, Value)>,
270 ) -> Result<PutResult> {
271 self.require_columns(table, crate::auth::ColumnOperation::Insert, &cells)?;
272 let handle = self.bound_table(table, expected_table_id, expected_schema_id)?;
273 let mut target = handle.lock();
274 let assigned = target.fill_auto_inc(&mut cells)?;
275 target.apply_defaults(&mut cells)?;
276 drop(target);
277 let row = owned_row_from_cells(&cells);
278 self.staging.push((expected_table_id, Staged::Put(cells)));
279 Ok(PutResult {
280 auto_inc: assigned,
281 row,
282 })
283 }
284
285 pub fn put_batch(
291 &mut self,
292 table: &str,
293 rows: Vec<Vec<(u16, Value)>>,
294 ) -> Result<Vec<Option<i64>>> {
295 if !rows.is_empty() {
296 let mut columns = rows
297 .iter()
298 .flat_map(|cells| cells.iter().map(|(column, _)| *column))
299 .collect::<Vec<_>>();
300 columns.sort_unstable();
301 columns.dedup();
302 self.db.require_columns_for(
303 table,
304 crate::auth::ColumnOperation::Insert,
305 &columns,
306 self.principal.as_ref(),
307 )?;
308 }
309 let id = self.db.table_id(table)?;
310 let handle = self.db.table(table)?;
311 let mut t = handle.lock();
312 let mut assigned = Vec::with_capacity(rows.len());
313 for mut cells in rows {
314 let a = t.fill_auto_inc(&mut cells)?;
315 t.apply_defaults(&mut cells)?;
316 assigned.push(a);
317 self.staging.push((id, Staged::Put(cells)));
318 }
319 drop(t);
320 Ok(assigned)
321 }
322
323 pub fn delete(&mut self, table: &str, row_id: RowId) -> Result<()> {
325 self.delete_batch(table, vec![row_id])
326 }
327
328 #[doc(hidden)]
331 pub fn delete_bound(
332 &mut self,
333 table: &str,
334 expected_table_id: u64,
335 expected_schema_id: u64,
336 row_id: RowId,
337 ) -> Result<()> {
338 self.require_delete(table)?;
339 self.bound_table(table, expected_table_id, expected_schema_id)?;
340 self.reject_after_truncate(expected_table_id)?;
341 self.staging
342 .push((expected_table_id, Staged::Delete(row_id)));
343 Ok(())
344 }
345
346 #[doc(hidden)]
349 pub fn delete_by_pk_bound(
350 &mut self,
351 table: &str,
352 expected_table_id: u64,
353 expected_schema_id: u64,
354 key: &Value,
355 ) -> Result<bool> {
356 self.require_delete(table)?;
357 let handle = self.bound_table(table, expected_table_id, expected_schema_id)?;
358 self.reject_after_truncate(expected_table_id)?;
359 let row_id = {
360 let mut target = handle.lock();
361 target.ensure_indexes_complete()?;
362 target.lookup_pk(&key.encode_key())
363 };
364 let Some(row_id) = row_id else {
365 return Ok(false);
366 };
367 self.staging
368 .push((expected_table_id, Staged::Delete(row_id)));
369 Ok(true)
370 }
371
372 pub fn delete_batch(&mut self, table: &str, row_ids: Vec<RowId>) -> Result<()> {
374 self.require_delete(table)?;
375 let id = self.db.table_id(table)?;
376 self.reject_after_truncate(id)?;
377 self.staging.extend(
378 row_ids
379 .into_iter()
380 .map(|row_id| (id, Staged::Delete(row_id))),
381 );
382 Ok(())
383 }
384
385 pub fn put_external_state(&mut self, table: &str, state: Vec<u8>) -> Result<()> {
388 if self.db.external_table(table).is_none() {
389 return Err(MongrelError::NotFound(format!(
390 "external table {table:?} not found"
391 )));
392 }
393 self.external_states.push((table.to_string(), state));
394 Ok(())
395 }
396
397 pub fn set_materialized_view_definition(
401 &mut self,
402 definition: crate::catalog::MaterializedViewEntry,
403 ) -> Result<()> {
404 self.db
405 .require_for(self.principal.as_ref(), &crate::auth::Permission::Ddl)?;
406 if self.db.table_id(&definition.name).is_err() {
407 return Err(MongrelError::NotFound(format!(
408 "materialized view table {:?} not found",
409 definition.name
410 )));
411 }
412 self.materialized_view_updates
413 .retain(|current| current.name != definition.name);
414 self.materialized_view_updates.push(definition);
415 Ok(())
416 }
417
418 pub fn delete_many(&mut self, table: &str, row_ids: Vec<RowId>) -> Result<Vec<OwnedRow>> {
419 self.require_delete(table)?;
420 let id = self.db.table_id(table)?;
421 self.reject_after_truncate(id)?;
422 let snap = self.read;
423 let handle = self.db.table(table)?;
424 let t = handle.lock();
425 let mut pre_images = Vec::with_capacity(row_ids.len());
426 for row_id in &row_ids {
427 if let Some(row) = t.get(*row_id, snap) {
428 pre_images.push(owned_row_from_map(row.columns));
429 }
430 }
431 drop(t);
432 for row_id in row_ids {
433 self.staging.push((id, Staged::Delete(row_id)));
434 }
435 Ok(pre_images)
436 }
437
438 pub fn update_many(
439 &mut self,
440 table: &str,
441 updates: Vec<(RowId, Vec<(u16, Value)>)>,
442 ) -> Result<Vec<OwnedRow>> {
443 if !updates.is_empty() {
444 let mut columns = updates
445 .iter()
446 .flat_map(|(_, cells)| cells.iter().map(|(column, _)| *column))
447 .collect::<Vec<_>>();
448 columns.sort_unstable();
449 columns.dedup();
450 self.db.require_columns_for(
451 table,
452 crate::auth::ColumnOperation::Update,
453 &columns,
454 self.principal.as_ref(),
455 )?;
456 }
457 let id = self.db.table_id(table)?;
458 self.reject_after_truncate(id)?;
459 let snap = self.read;
460 let handle = self.db.table(table)?;
461 let t = handle.lock();
462 let mut post_images = Vec::with_capacity(updates.len());
463 let mut staged = Vec::with_capacity(updates.len());
464 for (old_id, new_cells) in updates {
465 let changed_columns = changed_columns(&new_cells);
466 let old_row = t
467 .get(old_id, snap)
468 .ok_or_else(|| MongrelError::NotFound(format!("row {old_id:?} not found")))?;
469 let merged = merge_cells(old_row.columns.into_iter().collect(), new_cells);
470 post_images.push(owned_row_from_cells(&merged));
471 staged.push((
472 id,
473 Staged::Update {
474 row_id: old_id,
475 new_row: merged,
476 changed_columns,
477 },
478 ));
479 }
480 drop(t);
481 self.staging.extend(staged);
482 Ok(post_images)
483 }
484
485 pub fn upsert(
486 &mut self,
487 table: &str,
488 mut insert_cells: Vec<(u16, Value)>,
489 action: UpsertAction,
490 ) -> Result<UpsertResult> {
491 self.require_columns(table, crate::auth::ColumnOperation::Insert, &insert_cells)?;
495 let id = self.db.table_id(table)?;
496 self.reject_after_truncate(id)?;
497 match (self.existing_pk_row(table, &insert_cells)?, action) {
498 (None, _) => {
499 let handle = self.db.table(table)?;
500 let mut t = handle.lock();
501 let assigned = t.fill_auto_inc(&mut insert_cells)?;
502 t.apply_defaults(&mut insert_cells)?;
503 drop(t);
504 let row = owned_row_from_cells(&insert_cells);
505 self.staging.push((id, Staged::Put(insert_cells)));
506 Ok(UpsertResult {
507 action: UpsertActionKind::Inserted,
508 row,
509 auto_inc: assigned,
510 })
511 }
512 (Some((_old_id, old_row)), UpsertAction::DoNothing) => Ok(UpsertResult {
513 action: UpsertActionKind::Unchanged,
514 row: old_row,
515 auto_inc: None,
516 }),
517 (Some((old_id, old_row)), UpsertAction::DoUpdate(update_cells)) => {
518 self.require_columns(table, crate::auth::ColumnOperation::Update, &update_cells)?;
520 let changed_columns = changed_columns(&update_cells);
521 let merged = merge_cells(old_row.columns.clone(), update_cells);
522 if columns_equal(&old_row.columns, &merged) {
523 return Ok(UpsertResult {
524 action: UpsertActionKind::Unchanged,
525 row: old_row,
526 auto_inc: None,
527 });
528 }
529 let row = owned_row_from_cells(&merged);
530 self.staging.push((
531 id,
532 Staged::Update {
533 row_id: old_id,
534 new_row: merged,
535 changed_columns,
536 },
537 ));
538 Ok(UpsertResult {
539 action: UpsertActionKind::Updated,
540 row,
541 auto_inc: None,
542 })
543 }
544 }
545 }
546
547 #[doc(hidden)]
550 pub fn upsert_bound(
551 &mut self,
552 table: &str,
553 expected_table_id: u64,
554 expected_schema_id: u64,
555 mut insert_cells: Vec<(u16, Value)>,
556 action: UpsertAction,
557 ) -> Result<UpsertResult> {
558 self.require_columns(table, crate::auth::ColumnOperation::Insert, &insert_cells)?;
559 let handle = self.bound_table(table, expected_table_id, expected_schema_id)?;
560 self.reject_after_truncate(expected_table_id)?;
561 match (self.existing_pk_row_in(&handle, &insert_cells)?, action) {
562 (None, _) => {
563 let mut target = handle.lock();
564 let assigned = target.fill_auto_inc(&mut insert_cells)?;
565 target.apply_defaults(&mut insert_cells)?;
566 drop(target);
567 let row = owned_row_from_cells(&insert_cells);
568 self.staging
569 .push((expected_table_id, Staged::Put(insert_cells)));
570 Ok(UpsertResult {
571 action: UpsertActionKind::Inserted,
572 row,
573 auto_inc: assigned,
574 })
575 }
576 (Some((_old_id, old_row)), UpsertAction::DoNothing) => Ok(UpsertResult {
577 action: UpsertActionKind::Unchanged,
578 row: old_row,
579 auto_inc: None,
580 }),
581 (Some((old_id, old_row)), UpsertAction::DoUpdate(update_cells)) => {
582 self.require_columns(table, crate::auth::ColumnOperation::Update, &update_cells)?;
583 let changed_columns = changed_columns(&update_cells);
584 let merged = merge_cells(old_row.columns.clone(), update_cells);
585 if columns_equal(&old_row.columns, &merged) {
586 return Ok(UpsertResult {
587 action: UpsertActionKind::Unchanged,
588 row: old_row,
589 auto_inc: None,
590 });
591 }
592 let row = owned_row_from_cells(&merged);
593 self.staging.push((
594 expected_table_id,
595 Staged::Update {
596 row_id: old_id,
597 new_row: merged,
598 changed_columns,
599 },
600 ));
601 Ok(UpsertResult {
602 action: UpsertActionKind::Updated,
603 row,
604 auto_inc: None,
605 })
606 }
607 }
608 }
609
610 pub fn truncate(&mut self, table: &str) -> Result<()> {
611 self.db
612 .require_for(self.principal.as_ref(), &crate::auth::Permission::Admin)?;
613 let id = self.db.table_id(table)?;
614 for (table_id, op) in &self.staging {
615 if *table_id == id && !matches!(op, Staged::Truncate) {
616 return Err(MongrelError::InvalidArgument(
617 "truncate cannot be combined with other writes on the same table".into(),
618 ));
619 }
620 }
621 self.staging.push((id, Staged::Truncate));
622 Ok(())
623 }
624
625 fn reject_after_truncate(&self, table_id: u64) -> Result<()> {
626 if self
627 .staging
628 .iter()
629 .any(|(tid, op)| *tid == table_id && matches!(op, Staged::Truncate))
630 {
631 return Err(MongrelError::InvalidArgument(
632 "truncate cannot be combined with other writes on the same table".into(),
633 ));
634 }
635 Ok(())
636 }
637
638 fn require_columns(
639 &self,
640 table: &str,
641 operation: crate::auth::ColumnOperation,
642 cells: &[(u16, Value)],
643 ) -> Result<()> {
644 let columns = cells.iter().map(|(column, _)| *column).collect::<Vec<_>>();
645 self.db
646 .require_columns_for(table, operation, &columns, self.principal.as_ref())
647 }
648
649 fn require_delete(&self, table: &str) -> Result<()> {
650 self.db.require_for(
651 self.principal.as_ref(),
652 &crate::auth::Permission::Delete {
653 table: table.to_string(),
654 },
655 )
656 }
657
658 fn bound_table(
659 &self,
660 table: &str,
661 expected_table_id: u64,
662 expected_schema_id: u64,
663 ) -> Result<TableHandle> {
664 let current = self.db.table_identity(table)?;
665 if current != (expected_table_id, expected_schema_id) {
666 return Err(MongrelError::Conflict(format!(
667 "table {table:?} changed after request authorization"
668 )));
669 }
670 self.db.table_by_id(expected_table_id)
671 }
672
673 fn existing_pk_row(
674 &self,
675 table: &str,
676 cells: &[(u16, Value)],
677 ) -> Result<Option<(RowId, OwnedRow)>> {
678 let handle = self.db.table(table)?;
679 self.existing_pk_row_in(&handle, cells)
680 }
681
682 fn existing_pk_row_in(
683 &self,
684 handle: &TableHandle,
685 cells: &[(u16, Value)],
686 ) -> Result<Option<(RowId, OwnedRow)>> {
687 let target = handle.lock();
688 let Some(pk_col) = target.schema().primary_key() else {
689 return Ok(None);
690 };
691 let Some((_, pk_value)) = cells.iter().find(|(id, _)| *id == pk_col.id) else {
692 return Ok(None);
693 };
694 if matches!(pk_value, Value::Null) {
695 return Ok(None);
696 }
697 let Some(row_id) = target.lookup_pk(&pk_value.encode_key()) else {
698 return Ok(None);
699 };
700 Ok(target
701 .get(row_id, self.read)
702 .map(|row| (row_id, owned_row_from_map(row.columns))))
703 }
704
705 pub fn commit(self) -> Result<Epoch> {
707 if let Some(message) = self.allocation_error {
708 return Err(MongrelError::Full(message));
709 }
710 self.db
711 .commit_transaction_with_external_states(
712 self.txn_id,
713 self.read.epoch,
714 self.staging,
715 self.external_states,
716 self.materialized_view_updates,
717 self.principal,
718 self.principal_catalog_bound,
719 self.external_trigger_bridge,
720 )
721 .map(|(epoch, _)| epoch)
722 }
723
724 pub fn commit_with_row_ids(self) -> Result<(Epoch, Vec<RowId>)> {
725 if let Some(message) = self.allocation_error {
726 return Err(MongrelError::Full(message));
727 }
728 self.db.commit_transaction_with_external_states(
729 self.txn_id,
730 self.read.epoch,
731 self.staging,
732 self.external_states,
733 self.materialized_view_updates,
734 self.principal,
735 self.principal_catalog_bound,
736 self.external_trigger_bridge,
737 )
738 }
739
740 pub fn commit_controlled<F>(
744 self,
745 control: &crate::ExecutionControl,
746 mut before_commit: F,
747 ) -> Result<Epoch>
748 where
749 F: FnMut() -> Result<()>,
750 {
751 if let Some(message) = self.allocation_error {
752 return Err(MongrelError::Full(message));
753 }
754 self.db
755 .commit_transaction_with_external_states_controlled(
756 self.txn_id,
757 self.read.epoch,
758 self.staging,
759 self.external_states,
760 self.materialized_view_updates,
761 self.principal,
762 self.principal_catalog_bound,
763 self.external_trigger_bridge,
764 control,
765 &mut before_commit,
766 )
767 .map(|(epoch, _)| epoch)
768 }
769
770 pub fn commit_controlled_with_row_ids<F>(
771 self,
772 control: &crate::ExecutionControl,
773 mut before_commit: F,
774 ) -> Result<(Epoch, Vec<RowId>)>
775 where
776 F: FnMut() -> Result<()>,
777 {
778 if let Some(message) = self.allocation_error {
779 return Err(MongrelError::Full(message));
780 }
781 self.db.commit_transaction_with_external_states_controlled(
782 self.txn_id,
783 self.read.epoch,
784 self.staging,
785 self.external_states,
786 self.materialized_view_updates,
787 self.principal,
788 self.principal_catalog_bound,
789 self.external_trigger_bridge,
790 control,
791 &mut before_commit,
792 )
793 }
794
795 pub fn rollback(self) {
797 }
799}
800
801fn owned_row_from_cells(cells: &[(u16, Value)]) -> OwnedRow {
802 let mut columns = cells.to_vec();
803 columns.sort_by_key(|(id, _)| *id);
804 OwnedRow { columns }
805}
806
807fn owned_row_from_map(columns: HashMap<u16, Value>) -> OwnedRow {
808 let mut columns: Vec<(u16, Value)> = columns.into_iter().collect();
809 columns.sort_by_key(|(id, _)| *id);
810 OwnedRow { columns }
811}
812
813fn merge_cells(mut base: Vec<(u16, Value)>, updates: Vec<(u16, Value)>) -> Vec<(u16, Value)> {
814 for (id, value) in updates {
815 base.retain(|(existing, _)| *existing != id);
816 base.push((id, value));
817 }
818 base.sort_by_key(|(id, _)| *id);
819 base
820}
821
822fn changed_columns(cells: &[(u16, Value)]) -> Vec<u16> {
823 let mut columns = cells.iter().map(|(column, _)| *column).collect::<Vec<_>>();
824 columns.sort_unstable();
825 columns.dedup();
826 columns
827}
828
829fn columns_equal(a: &[(u16, Value)], b: &[(u16, Value)]) -> bool {
830 if a.len() != b.len() {
831 return false;
832 }
833 let mut a: Vec<_> = a.iter().collect();
834 let mut b: Vec<_> = b.iter().collect();
835 a.sort_by_key(|(id, _)| *id);
836 b.sort_by_key(|(id, _)| *id);
837 a.iter()
838 .zip(b.iter())
839 .all(|((id_a, v_a), (id_b, v_b))| id_a == id_b && v_a == v_b)
840}
841
842pub(crate) enum StagedOp {
844 Put(Vec<crate::memtable::Row>),
845 Delete(Vec<RowId>),
846 Truncate,
847}
848
849use std::collections::{BTreeMap, HashMap};
852use std::hash::{Hash, Hasher};
853
854#[derive(Clone, Debug)]
857pub enum WriteKey {
858 Row { table_id: u64, row_id: u64 },
860 Unique {
862 table_id: u64,
863 index_id: u16,
864 key_hash: u64,
865 },
866 Table { table_id: u64 },
868}
869
870impl Hash for WriteKey {
871 fn hash<H: Hasher>(&self, state: &mut H) {
872 match self {
873 WriteKey::Row { table_id, row_id } => {
874 0u8.hash(state);
875 table_id.hash(state);
876 row_id.hash(state);
877 }
878 WriteKey::Unique {
879 table_id,
880 index_id,
881 key_hash,
882 } => {
883 1u8.hash(state);
884 table_id.hash(state);
885 index_id.hash(state);
886 key_hash.hash(state);
887 }
888 WriteKey::Table { table_id } => {
889 2u8.hash(state);
890 table_id.hash(state);
891 }
892 }
893 }
894}
895
896impl PartialEq for WriteKey {
897 fn eq(&self, other: &Self) -> bool {
898 match (self, other) {
899 (
900 WriteKey::Row {
901 table_id: a,
902 row_id: b,
903 },
904 WriteKey::Row {
905 table_id: c,
906 row_id: d,
907 },
908 ) => a == c && b == d,
909 (
910 WriteKey::Unique {
911 table_id: a,
912 index_id: b,
913 key_hash: c,
914 },
915 WriteKey::Unique {
916 table_id: d,
917 index_id: e,
918 key_hash: f,
919 },
920 ) => a == d && b == e && c == f,
921 (WriteKey::Table { table_id: a }, WriteKey::Table { table_id: b }) => a == b,
922 _ => false,
923 }
924 }
925}
926
927impl Eq for WriteKey {}
928
929const CONFLICT_SHARDS: usize = 16;
930
931pub struct ConflictIndex {
935 shards: [parking_lot::Mutex<HashMap<WriteKey, u64>>; CONFLICT_SHARDS],
936 table_truncate_epochs: parking_lot::Mutex<HashMap<u64, u64>>,
937 table_write_epochs: parking_lot::Mutex<HashMap<u64, u64>>,
938 version: std::sync::atomic::AtomicU64,
942}
943
944impl ConflictIndex {
945 pub fn new() -> Self {
946 Self {
947 shards: std::array::from_fn(|_| parking_lot::Mutex::new(HashMap::new())),
948 table_truncate_epochs: parking_lot::Mutex::new(HashMap::new()),
949 table_write_epochs: parking_lot::Mutex::new(HashMap::new()),
950 version: std::sync::atomic::AtomicU64::new(0),
951 }
952 }
953
954 pub fn version(&self) -> u64 {
958 self.version.load(std::sync::atomic::Ordering::Acquire)
959 }
960
961 fn shard(&self, key: &WriteKey) -> &parking_lot::Mutex<HashMap<WriteKey, u64>> {
962 let mut h = std::collections::hash_map::DefaultHasher::new();
963 key.hash(&mut h);
964 let idx = (h.finish() as usize) & (CONFLICT_SHARDS - 1);
965 &self.shards[idx]
966 }
967
968 pub fn conflicts(&self, keys: &[WriteKey], read_epoch: Epoch) -> bool {
971 for k in keys {
972 let s = self.shard(k);
973 if let Some(&ce) = s.lock().get(k) {
974 if ce > read_epoch.0 {
975 return true;
976 }
977 }
978 }
979 let truncates = self.table_truncate_epochs.lock();
980 let writes = self.table_write_epochs.lock();
981 for k in keys {
982 match k {
983 WriteKey::Row { table_id, .. } | WriteKey::Unique { table_id, .. } => {
984 if truncates.get(table_id).is_some_and(|&ce| ce > read_epoch.0) {
985 return true;
986 }
987 }
988 WriteKey::Table { table_id } => {
989 if writes.get(table_id).is_some_and(|&ce| ce > read_epoch.0) {
990 return true;
991 }
992 }
993 }
994 }
995 false
996 }
997
998 pub fn record(&self, keys: &[WriteKey], commit_epoch: Epoch) {
1000 for k in keys {
1001 let s = self.shard(k);
1002 s.lock().insert(k.clone(), commit_epoch.0);
1003 }
1004 let mut truncates = self.table_truncate_epochs.lock();
1005 let mut writes = self.table_write_epochs.lock();
1006 for k in keys {
1007 match k {
1008 WriteKey::Table { table_id } => {
1009 truncates
1010 .entry(*table_id)
1011 .and_modify(|ce| *ce = (*ce).max(commit_epoch.0))
1012 .or_insert(commit_epoch.0);
1013 }
1014 WriteKey::Row { table_id, .. } | WriteKey::Unique { table_id, .. } => {
1015 writes
1016 .entry(*table_id)
1017 .and_modify(|ce| *ce = (*ce).max(commit_epoch.0))
1018 .or_insert(commit_epoch.0);
1019 }
1020 }
1021 }
1022 self.version
1023 .fetch_add(1, std::sync::atomic::Ordering::Release);
1024 }
1025
1026 pub fn prune_below(&self, min_active: Epoch) {
1029 for s in &self.shards {
1030 s.lock().retain(|_, ce| *ce >= min_active.0);
1031 }
1032 self.table_truncate_epochs
1033 .lock()
1034 .retain(|_, ce| *ce >= min_active.0);
1035 self.table_write_epochs
1036 .lock()
1037 .retain(|_, ce| *ce >= min_active.0);
1038 }
1039}
1040
1041impl Default for ConflictIndex {
1042 fn default() -> Self {
1043 Self::new()
1044 }
1045}
1046
1047pub struct GroupCommit {
1057 inner: PlMutex<GroupState>,
1058 cv: Condvar,
1059}
1060
1061struct GroupState {
1062 durable_seq: u64,
1063 syncing: bool,
1064 poisoned: bool,
1065}
1066
1067impl GroupCommit {
1068 pub fn new(durable_seq: u64) -> Self {
1069 Self {
1070 inner: PlMutex::new(GroupState {
1071 durable_seq,
1072 syncing: false,
1073 poisoned: false,
1074 }),
1075 cv: Condvar::new(),
1076 }
1077 }
1078
1079 pub fn await_durable(&self, wal: &PlMutex<SharedWal>, commit_seq: u64) -> Result<()> {
1085 let mut st = self.inner.lock();
1086 loop {
1087 if st.poisoned {
1088 return Err(MongrelError::Other(
1089 "database poisoned by fsync error".into(),
1090 ));
1091 }
1092 if st.durable_seq >= commit_seq {
1093 return Ok(());
1094 }
1095 if st.syncing {
1096 self.cv.wait(&mut st);
1098 continue;
1099 }
1100 st.syncing = true;
1103 drop(st);
1104 std::thread::sleep(std::time::Duration::from_micros(50));
1106 let res = wal.lock().group_sync();
1107 st = self.inner.lock();
1108 st.syncing = false;
1109 match res {
1110 Ok(durable) => {
1111 if durable > st.durable_seq {
1112 st.durable_seq = durable;
1113 }
1114 self.cv.notify_all();
1115 }
1118 Err(e) => {
1119 st.poisoned = true;
1120 self.cv.notify_all();
1121 return Err(e);
1122 }
1123 }
1124 }
1125 }
1126}
1127
1128pub struct ActiveTxns {
1132 inner: parking_lot::Mutex<BTreeMap<u64, u64>>,
1133}
1134
1135impl ActiveTxns {
1136 pub fn new() -> Self {
1137 Self {
1138 inner: parking_lot::Mutex::new(BTreeMap::new()),
1139 }
1140 }
1141
1142 pub fn register(&self, read_epoch: Epoch) -> ActiveTxnGuard<'_> {
1145 let mut g = self.inner.lock();
1146 *g.entry(read_epoch.0).or_insert(0) += 1;
1147 ActiveTxnGuard {
1148 active: self,
1149 epoch: read_epoch.0,
1150 }
1151 }
1152
1153 pub fn min_read_epoch(&self) -> u64 {
1155 self.inner.lock().keys().next().copied().unwrap_or(u64::MAX)
1156 }
1157}
1158
1159impl Default for ActiveTxns {
1160 fn default() -> Self {
1161 Self::new()
1162 }
1163}
1164
1165pub struct ActiveTxnGuard<'a> {
1167 active: &'a ActiveTxns,
1168 epoch: u64,
1169}
1170
1171impl Drop for ActiveTxnGuard<'_> {
1172 fn drop(&mut self) {
1173 let mut g = self.active.inner.lock();
1174 if let Some(count) = g.get_mut(&self.epoch) {
1175 *count -= 1;
1176 if *count == 0 {
1177 g.remove(&self.epoch);
1178 }
1179 }
1180 }
1181}
1182
1183#[cfg(test)]
1184mod tests {
1185 use super::*;
1186
1187 #[test]
1188 fn shared_transaction_allocator_never_crosses_open_generation() {
1189 let allocator = PlMutex::new((7_u64 << 32) | u32::MAX as u64);
1190 assert_eq!(
1191 allocate_txn_id(&allocator).unwrap(),
1192 (7_u64 << 32) | u32::MAX as u64
1193 );
1194 assert!(matches!(
1195 allocate_txn_id(&allocator),
1196 Err(MongrelError::Full(_))
1197 ));
1198 }
1199
1200 #[test]
1201 fn conflict_index_first_committer_wins_and_prunes_safely() {
1202 let ci = ConflictIndex::new();
1203 let k = vec![WriteKey::Row {
1204 table_id: 1,
1205 row_id: 7,
1206 }];
1207 assert!(!ci.conflicts(&k, Epoch(5)));
1208 ci.record(&k, Epoch(6));
1209 assert!(ci.conflicts(&k, Epoch(5)));
1210 assert!(!ci.conflicts(&k, Epoch(6)));
1211 ci.prune_below(Epoch(7));
1212 assert!(!ci.conflicts(&k, Epoch(5)));
1213 }
1214
1215 #[test]
1216 fn conflict_index_table_scope_conflicts_both_directions() {
1217 let ci = ConflictIndex::new();
1218 ci.record(&[WriteKey::Table { table_id: 1 }], Epoch(6));
1219 assert!(ci.conflicts(
1220 &[WriteKey::Row {
1221 table_id: 1,
1222 row_id: 7,
1223 }],
1224 Epoch(5)
1225 ));
1226 assert!(ci.conflicts(
1227 &[WriteKey::Unique {
1228 table_id: 1,
1229 index_id: 0,
1230 key_hash: 42,
1231 }],
1232 Epoch(5)
1233 ));
1234 assert!(!ci.conflicts(
1235 &[WriteKey::Row {
1236 table_id: 2,
1237 row_id: 7,
1238 }],
1239 Epoch(5)
1240 ));
1241
1242 let ci = ConflictIndex::new();
1243 ci.record(
1244 &[WriteKey::Row {
1245 table_id: 1,
1246 row_id: 7,
1247 }],
1248 Epoch(6),
1249 );
1250 assert!(ci.conflicts(&[WriteKey::Table { table_id: 1 }], Epoch(5)));
1251 assert!(!ci.conflicts(&[WriteKey::Table { table_id: 2 }], Epoch(5)));
1252 }
1253
1254 #[test]
1255 fn writekey_eq_across_variants() {
1256 let r1 = WriteKey::Row {
1257 table_id: 1,
1258 row_id: 2,
1259 };
1260 let r2 = WriteKey::Row {
1261 table_id: 1,
1262 row_id: 2,
1263 };
1264 let r3 = WriteKey::Row {
1265 table_id: 1,
1266 row_id: 3,
1267 };
1268 assert_eq!(r1, r2);
1269 assert_ne!(r1, r3);
1270
1271 let u1 = WriteKey::Unique {
1272 table_id: 1,
1273 index_id: 0,
1274 key_hash: 42,
1275 };
1276 let u2 = WriteKey::Unique {
1277 table_id: 1,
1278 index_id: 0,
1279 key_hash: 42,
1280 };
1281 assert_eq!(u1, u2);
1282 assert_ne!(r1, u1);
1283
1284 let t1 = WriteKey::Table { table_id: 5 };
1285 let t2 = WriteKey::Table { table_id: 5 };
1286 assert_eq!(t1, t2);
1287 assert_ne!(t1, r1);
1288 }
1289
1290 #[test]
1291 fn active_txns_tracks_min_read_epoch() {
1292 let at = ActiveTxns::new();
1293 assert_eq!(at.min_read_epoch(), u64::MAX);
1294 let g1 = at.register(Epoch(5));
1295 assert_eq!(at.min_read_epoch(), 5);
1296 let g2 = at.register(Epoch(3));
1297 assert_eq!(at.min_read_epoch(), 3);
1298 drop(g2);
1299 assert_eq!(at.min_read_epoch(), 5);
1300 drop(g1);
1301 assert_eq!(at.min_read_epoch(), u64::MAX);
1302 }
1303
1304 #[test]
1305 fn active_txns_dedups_same_epoch() {
1306 let at = ActiveTxns::new();
1307 let g1 = at.register(Epoch(7));
1308 let g2 = at.register(Epoch(7));
1309 assert_eq!(at.min_read_epoch(), 7);
1310 drop(g1);
1311 assert_eq!(at.min_read_epoch(), 7);
1312 drop(g2);
1313 assert_eq!(at.min_read_epoch(), u64::MAX);
1314 }
1315}
1316
1317#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1328pub enum IsolationLevel {
1329 #[default]
1330 Snapshot,
1331 ReadCommitted,
1332 Serializable,
1333}