1use super::execution_context::current_connection_id;
22use super::*;
23
24struct DmlWalCapture(bool);
26
27impl DmlWalCapture {
28 fn begin() -> Self {
29 assert!(
30 !UnifiedStore::deferred_store_wal_capture_active(),
31 "DML WAL captures cannot nest"
32 );
33 UnifiedStore::begin_deferred_store_wal_capture();
34 Self(true)
35 }
36
37 fn finish(mut self) -> crate::storage::unified::DeferredStoreWalActions {
38 self.0 = false;
39 UnifiedStore::take_deferred_store_wal_capture()
40 }
41}
42
43impl Drop for DmlWalCapture {
44 fn drop(&mut self) {
45 if self.0 {
46 let _ = UnifiedStore::take_deferred_store_wal_capture();
47 }
48 }
49}
50
51impl RedDBRuntime {
52 pub(crate) fn rollback_connection(
53 &self,
54 connection_id: u64,
55 ) -> RedDBResult<Option<crate::storage::transaction::snapshot::TxnContext>> {
56 let undo = self.revive_pending_versioned_updates(connection_id);
60 self.revive_pending_tombstones(connection_id);
61 let context = self.inner.transaction_state.rollback(connection_id);
62 self.discard_pending_queue_dedup(connection_id);
63 self.discard_pending_kv_watch_events(connection_id);
64 self.discard_pending_queue_wakes(connection_id);
65 self.discard_pending_store_wal_actions(connection_id);
66 self.release_pending_claim_locks(connection_id);
67 self.discard_pending_vault_writes(connection_id)?;
68 undo?;
69 Ok(context)
70 }
71
72 pub(crate) fn record_pending_tombstone(
78 &self,
79 conn_id: u64,
80 collection: &str,
81 id: crate::storage::unified::entity::EntityId,
82 stamper_xid: crate::storage::transaction::snapshot::Xid,
83 previous_xmax: crate::storage::transaction::snapshot::Xid,
84 ) {
85 self.inner
86 .pending_tombstones
87 .write()
88 .entry(conn_id)
89 .or_default()
90 .push((collection.to_string(), id, stamper_xid, previous_xmax));
91 }
92
93 pub(crate) fn record_pending_versioned_update(
94 &self,
95 conn_id: u64,
96 collection: &str,
97 old_id: crate::storage::unified::entity::EntityId,
98 new_id: crate::storage::unified::entity::EntityId,
99 stamper_xid: crate::storage::transaction::snapshot::Xid,
100 previous_xmax: crate::storage::transaction::snapshot::Xid,
101 ) {
102 self.inner
103 .pending_versioned_updates
104 .write()
105 .entry(conn_id)
106 .or_default()
107 .push((
108 collection.to_string(),
109 old_id,
110 new_id,
111 stamper_xid,
112 previous_xmax,
113 ));
114 }
115
116 pub(crate) fn record_pending_queue_dedup(
117 &self,
118 conn_id: u64,
119 queue: &str,
120 dedup_key: &str,
121 metadata_id: crate::storage::unified::entity::EntityId,
122 ) {
123 self.inner
124 .pending_queue_dedup
125 .write()
126 .entry(conn_id)
127 .or_default()
128 .push((queue.to_string(), dedup_key.to_string(), metadata_id));
129 }
130
131 pub(crate) fn with_deferred_store_wal_if_transaction<T>(
132 &self,
133 f: impl FnOnce() -> RedDBResult<T>,
134 ) -> RedDBResult<T> {
135 let conn_id = current_connection_id();
136 if !self.inner.transaction_state.in_transaction(conn_id) {
137 return f();
138 }
139
140 let capture = DmlWalCapture::begin();
141 let result = f();
142 let captured = capture.finish();
143 match result {
144 Ok(value) => {
145 self.record_pending_store_wal_actions(conn_id, captured);
146 Ok(value)
147 }
148 Err(err) => Err(err),
149 }
150 }
151
152 pub(crate) fn with_deferred_store_wal_for_dml<T>(
153 &self,
154 capture_autocommit_events: bool,
155 f: impl FnOnce() -> RedDBResult<T>,
156 ) -> RedDBResult<T> {
157 let conn_id = current_connection_id();
158 if self.inner.transaction_state.in_transaction(conn_id) {
159 return self.with_deferred_store_wal_if_transaction(f);
160 }
161 self.drain_committed_writes_under_pressure(0)?;
162 if !capture_autocommit_events {
163 return f();
164 }
165
166 let deletions = super::delete_publication::DeletePublicationScope::begin();
167 let capture = DmlWalCapture::begin();
168 let result = f();
169 let captured = capture.finish();
170 if result.is_err() && deletions.has_pending() {
171 return result;
172 }
173 deletions.publish(self, || {
174 self.inner
175 .db
176 .store()
177 .append_deferred_store_wal_actions(captured)
178 .map_err(|err| RedDBError::Internal(err.to_string()))
179 })?;
180 result
181 }
182
183 pub(crate) fn insert_may_emit_events(&self, query: &InsertQuery) -> bool {
184 !query.suppress_events
185 && self.collection_has_event_subscriptions_for_operation(
186 &query.table,
187 crate::catalog::SubscriptionOperation::Insert,
188 )
189 }
190
191 pub(crate) fn update_may_emit_events(&self, query: &UpdateQuery) -> bool {
192 !query.suppress_events
193 && self.collection_has_event_subscriptions_for_operation(
194 &query.table,
195 crate::catalog::SubscriptionOperation::Update,
196 )
197 }
198
199 pub(crate) fn delete_may_emit_events(&self, query: &DeleteQuery) -> bool {
200 !query.suppress_events
201 && self.collection_has_event_subscriptions_for_operation(
202 &query.table,
203 crate::catalog::SubscriptionOperation::Delete,
204 )
205 }
206
207 fn collection_has_event_subscriptions_for_operation(
208 &self,
209 collection: &str,
210 operation: crate::catalog::SubscriptionOperation,
211 ) -> bool {
212 let Some(contract) = self.db().collection_contract_arc(collection) else {
213 return false;
214 };
215 contract.subscriptions.iter().any(|subscription| {
216 subscription.enabled
217 && (subscription.ops_filter.is_empty()
218 || subscription.ops_filter.contains(&operation))
219 })
220 }
221
222 pub(crate) fn record_pending_store_wal_actions(
223 &self,
224 conn_id: u64,
225 actions: crate::storage::unified::DeferredStoreWalActions,
226 ) {
227 if actions.is_empty() {
228 return;
229 }
230 let mut guard = self.inner.pending_store_wal_actions.write();
231 guard.entry(conn_id).or_default().extend(actions);
232 }
233
234 pub(crate) fn flush_pending_store_wal_actions(&self, conn_id: u64) -> RedDBResult<()> {
235 let Some(actions) = self
236 .inner
237 .pending_store_wal_actions
238 .write()
239 .remove(&conn_id)
240 else {
241 return Ok(());
242 };
243 self.inner
244 .db
245 .store()
246 .append_deferred_store_wal_actions(actions)
247 .map_err(|err| RedDBError::Internal(err.to_string()))
248 }
249
250 pub(crate) fn discard_pending_store_wal_actions(&self, conn_id: u64) {
251 self.inner
252 .pending_store_wal_actions
253 .write()
254 .remove(&conn_id);
255 }
256
257 fn xid_conflicts_with_snapshot(
258 &self,
259 xid: crate::storage::transaction::snapshot::Xid,
260 snapshot: &crate::storage::transaction::snapshot::Snapshot,
261 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
262 ) -> bool {
263 xid != 0
264 && !own_xids.contains(&xid)
265 && !self
266 .inner
267 .transaction_state
268 .snapshot_manager()
269 .is_aborted(xid)
270 && !self
271 .inner
272 .transaction_state
273 .snapshot_manager()
274 .is_active(xid)
275 && (xid > snapshot.xid || snapshot.in_progress.contains(&xid))
276 }
277
278 pub(super) fn conflict_error(
279 collection: &str,
280 logical_id: crate::storage::unified::entity::EntityId,
281 xid: crate::storage::transaction::snapshot::Xid,
282 ) -> RedDBError {
283 RedDBError::Query(format!(
284 "serialization conflict: table row {collection}/{} was modified by concurrent transaction {xid}",
285 logical_id.raw()
286 ))
287 }
288
289 fn check_logical_row_conflict(
290 &self,
291 collection: &str,
292 logical_id: crate::storage::unified::entity::EntityId,
293 excluded_ids: &[crate::storage::unified::entity::EntityId],
294 snapshot: &crate::storage::transaction::snapshot::Snapshot,
295 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
296 ) -> RedDBResult<()> {
297 let store = self.inner.db.store();
298 let Some(manager) = store.get_collection(collection) else {
299 return Ok(());
300 };
301
302 for candidate in manager.query_all(|_| true) {
303 if excluded_ids.contains(&candidate.id) || candidate.logical_id() != logical_id {
304 continue;
305 }
306 if self.xid_conflicts_with_snapshot(candidate.xmin, snapshot, own_xids) {
307 return Err(Self::conflict_error(collection, logical_id, candidate.xmin));
308 }
309 if self.xid_conflicts_with_snapshot(candidate.xmax, snapshot, own_xids) {
310 return Err(Self::conflict_error(collection, logical_id, candidate.xmax));
311 }
312 }
313 Ok(())
314 }
315
316 pub(crate) fn check_table_row_write_conflicts(
317 &self,
318 conn_id: u64,
319 snapshot: &crate::storage::transaction::snapshot::Snapshot,
320 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
321 isolation: crate::storage::transaction::IsolationLevel,
322 ) -> RedDBResult<()> {
323 let versioned_updates = self
324 .inner
325 .pending_versioned_updates
326 .read()
327 .get(&conn_id)
328 .cloned()
329 .unwrap_or_default();
330 let tombstones = self
331 .inner
332 .pending_tombstones
333 .read()
334 .get(&conn_id)
335 .cloned()
336 .unwrap_or_default();
337
338 let mut serializable_write_set = std::collections::HashSet::new();
339 let store = self.inner.db.store();
340 for (collection, old_id, new_id, xid, previous_xmax) in versioned_updates {
341 let Some(manager) = store.get_collection(&collection) else {
342 continue;
343 };
344 let Some(old) = manager.get(old_id) else {
345 continue;
346 };
347 let logical_id = old.logical_id();
348 serializable_write_set.insert((collection.clone(), logical_id));
349 if self.xid_conflicts_with_snapshot(previous_xmax, snapshot, own_xids) {
350 return Err(Self::conflict_error(&collection, logical_id, previous_xmax));
351 }
352 if old.xmax != xid && self.xid_conflicts_with_snapshot(old.xmax, snapshot, own_xids) {
353 return Err(Self::conflict_error(&collection, logical_id, old.xmax));
354 }
355 self.check_logical_row_conflict(
356 &collection,
357 logical_id,
358 &[old_id, new_id],
359 snapshot,
360 own_xids,
361 )?;
362 }
363
364 for (collection, id, xid, previous_xmax) in tombstones {
365 let Some(manager) = store.get_collection(&collection) else {
366 continue;
367 };
368 let Some(entity) = manager.get(id) else {
369 continue;
370 };
371 let logical_id = entity.logical_id();
372 serializable_write_set.insert((collection.clone(), logical_id));
373 if self.xid_conflicts_with_snapshot(previous_xmax, snapshot, own_xids) {
374 return Err(Self::conflict_error(&collection, logical_id, previous_xmax));
375 }
376 if entity.xmax != xid
377 && self.xid_conflicts_with_snapshot(entity.xmax, snapshot, own_xids)
378 {
379 return Err(Self::conflict_error(&collection, logical_id, entity.xmax));
380 }
381 self.check_logical_row_conflict(&collection, logical_id, &[id], snapshot, own_xids)?;
382 }
383
384 if isolation == crate::storage::transaction::IsolationLevel::Serializable
385 && self
386 .inner
387 .transaction_state
388 .snapshot_manager()
389 .serializable_commit_would_be_dangerous(
390 *own_xids.iter().min().unwrap_or(&0),
391 &serializable_write_set,
392 )
393 {
394 return Err(RedDBError::Query(
395 "serialization conflict: serializable transaction would complete rw-antidependency dangerous structure".to_string(),
396 ));
397 }
398
399 Ok(())
400 }
401
402 pub(crate) fn check_queue_dedup_write_conflicts(
403 &self,
404 conn_id: u64,
405 snapshot: &crate::storage::transaction::snapshot::Snapshot,
406 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
407 ) -> RedDBResult<()> {
408 let pending = self
409 .inner
410 .pending_queue_dedup
411 .read()
412 .get(&conn_id)
413 .cloned()
414 .unwrap_or_default();
415 if pending.is_empty() {
416 return Ok(());
417 }
418
419 let store = self.inner.db.store();
420 let Some(manager) = store.get_collection(crate::runtime::impl_queue::QUEUE_META_COLLECTION)
421 else {
422 return Ok(());
423 };
424
425 for (queue, dedup_key, metadata_id) in pending {
426 for candidate in manager.query_all(|entity| {
427 entity.data.as_row().is_some_and(|row| {
428 crate::runtime::impl_queue::row_text(row, "kind").as_deref()
429 == Some(crate::runtime::impl_queue::KIND_QUEUE_PUSH_DEDUP)
430 && crate::runtime::impl_queue::row_text(row, "queue").as_deref()
431 == Some(&queue)
432 && crate::runtime::impl_queue::row_text(row, "dedup_key").as_deref()
433 == Some(&dedup_key)
434 })
435 }) {
436 if candidate.id == metadata_id
437 || self
438 .inner
439 .transaction_state
440 .snapshot_manager()
441 .is_aborted(candidate.xmin)
442 {
443 continue;
444 }
445 if self.xid_conflicts_with_snapshot(candidate.xmin, snapshot, own_xids) {
446 return Err(RedDBError::Query(format!(
447 "serialization conflict: queue dedup key {queue}/{dedup_key} was modified by concurrent transaction {}",
448 candidate.xmin
449 )));
450 }
451 }
452 }
453 Ok(())
454 }
455
456 pub(crate) fn restore_pending_write_stamps(&self, conn_id: u64) {
457 let versioned_updates = self
458 .inner
459 .pending_versioned_updates
460 .read()
461 .get(&conn_id)
462 .cloned()
463 .unwrap_or_default();
464 let tombstones = self
465 .inner
466 .pending_tombstones
467 .read()
468 .get(&conn_id)
469 .cloned()
470 .unwrap_or_default();
471
472 let store = self.inner.db.store();
473 for (collection, old_id, _new_id, xid, _previous_xmax) in versioned_updates {
474 if let Some(manager) = store.get_collection(&collection) {
475 if let Some(mut entity) = manager.get(old_id) {
476 entity.set_xmax(xid);
477 let _ = manager.update(entity);
478 }
479 }
480 }
481 for (collection, id, xid, _previous_xmax) in tombstones {
482 if let Some(manager) = store.get_collection(&collection) {
483 if let Some(mut entity) = manager.get(id) {
484 entity.set_xmax(xid);
485 let _ = manager.update(entity);
486 }
487 }
488 }
489 }
490
491 pub(crate) fn finalize_pending_versioned_updates(&self, conn_id: u64) {
492 self.inner
493 .pending_versioned_updates
494 .write()
495 .remove(&conn_id);
496 }
497
498 pub(crate) fn finalize_pending_queue_dedup(&self, conn_id: u64) {
499 self.inner.pending_queue_dedup.write().remove(&conn_id);
500 }
501
502 pub(crate) fn discard_pending_queue_dedup(&self, conn_id: u64) {
503 let Some(pending) = self.inner.pending_queue_dedup.write().remove(&conn_id) else {
504 return;
505 };
506 let store = self.inner.db.store();
507 for (_queue, _dedup_key, metadata_id) in pending {
508 let _ = store.delete(
509 crate::runtime::impl_queue::QUEUE_META_COLLECTION,
510 metadata_id,
511 );
512 }
513 }
514
515 pub(crate) fn revive_pending_versioned_updates(&self, conn_id: u64) -> RedDBResult<()> {
516 let Some(pending) = self
517 .inner
518 .pending_versioned_updates
519 .write()
520 .remove(&conn_id)
521 else {
522 return Ok(());
523 };
524 for (collection, old_id, new_id, xid, previous_xmax) in pending.into_iter().rev() {
526 let topology_lock = self.index_store_ref().collection_topology_lock(&collection);
527 #[cfg(test)]
528 self.index_store_ref().mutation_test_hook(
529 &collection,
530 super::index_store::MutationTestPhase::BeforeUndoTopology,
531 );
532 self.revive_versioned_update(
533 &collection,
534 old_id,
535 new_id,
536 xid,
537 previous_xmax,
538 topology_lock.read(),
539 )?;
540 }
541 Ok(())
542 }
543
544 pub(crate) fn revive_versioned_update(
545 &self,
546 collection: &str,
547 old_id: crate::storage::EntityId,
548 new_id: crate::storage::EntityId,
549 xid: u64,
550 previous_xmax: u64,
551 _topology_guard: parking_lot::RwLockReadGuard<'_, ()>,
552 ) -> RedDBResult<()> {
553 let store = self.inner.db.store();
554 if let Some(new) = store.get(collection, new_id) {
555 let fields = crate::application::ports::entity_row_fields_snapshot(&new);
556 self.index_store_ref()
557 .index_entity_delete(collection, new_id, &fields)
558 .map_err(RedDBError::Internal)?;
559 }
560 store
561 .delete_batch(collection, &[new_id])
562 .map_err(|error| RedDBError::Internal(error.to_string()))?;
563 if let Some(manager) = store.get_collection(collection) {
564 if let Some(mut old) = manager.get(old_id) {
565 if old.xmax == xid {
566 old.set_xmax(previous_xmax);
567 manager
568 .update(old.clone())
569 .map_err(|error| RedDBError::Internal(error.to_string()))?;
570 store.context_index().index_entity(collection, &old);
571 let fields = crate::application::ports::entity_row_fields_snapshot(&old);
572 self.index_store_ref()
573 .index_entity_insert(collection, old_id, &fields)
574 .map_err(RedDBError::Internal)?;
575 }
576 }
577 }
578 Ok(())
579 }
580
581 pub(crate) fn finalize_pending_tombstones(&self, conn_id: u64) {
586 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
587 return;
588 };
589 if pending.is_empty() {
590 return;
591 }
592
593 let store = self.inner.db.store();
594 for (collection, id, _xid, _previous_xmax) in pending {
595 store.context_index().remove_entity(id);
596 self.cdc_emit(
597 crate::replication::cdc::ChangeOperation::Delete,
598 &collection,
599 id.raw(),
600 "entity",
601 );
602 }
603 }
604
605 pub(crate) fn revive_pending_tombstones(&self, conn_id: u64) {
612 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
613 return;
614 };
615
616 let store = self.inner.db.store();
617 for (collection, id, xid, previous_xmax) in pending {
618 let Some(manager) = store.get_collection(&collection) else {
619 continue;
620 };
621 if let Some(mut entity) = manager.get(id) {
622 if entity.xmax == xid {
623 entity.set_xmax(previous_xmax);
624 let _ = manager.update(entity);
625 }
626 }
627 }
628 }
629
630 pub fn queue_wait_registry(
632 &self,
633 ) -> std::sync::Arc<crate::runtime::queue_wait_registry::QueueWaitRegistry> {
634 self.inner.queue_wait_registry.clone()
635 }
636
637 pub(crate) fn record_queue_wake(&self, scope: &str, queue: &str) {
642 if self.current_xid().is_some() {
643 let conn_id = current_connection_id();
644 self.inner
645 .pending_queue_wakes
646 .write()
647 .entry(conn_id)
648 .or_default()
649 .push((scope.to_string(), queue.to_string()));
650 return;
651 }
652 self.inner.queue_wait_registry.notify(scope, queue);
653 }
654
655 pub(crate) fn finalize_pending_queue_wakes(&self, conn_id: u64) {
656 let Some(pending) = self.inner.pending_queue_wakes.write().remove(&conn_id) else {
657 return;
658 };
659 for (scope, queue) in pending {
660 self.inner.queue_wait_registry.notify(&scope, &queue);
661 }
662 }
663
664 pub(crate) fn discard_pending_queue_wakes(&self, conn_id: u64) {
665 self.inner.pending_queue_wakes.write().remove(&conn_id);
666 }
667
668 pub(crate) fn release_pending_claim_locks(&self, conn_id: u64) {
669 self.inner
670 .pending_claim_locks
671 .write()
672 .retain(|_, owner| *owner != conn_id);
673 }
674
675 pub(crate) fn finalize_pending_kv_watch_events(&self, conn_id: u64) {
676 let Some(pending) = self.inner.pending_kv_watch_events.write().remove(&conn_id) else {
677 return;
678 };
679 for (_, event) in pending {
680 self.cdc_emit_kv(
681 event.op,
682 &event.collection,
683 &event.key,
684 0,
685 event.before,
686 event.after,
687 );
688 }
689 }
690
691 pub(crate) fn discard_pending_kv_watch_events(&self, conn_id: u64) {
692 self.inner.pending_kv_watch_events.write().remove(&conn_id);
693 }
694
695 pub(crate) fn stamp_xmin_if_in_txn(
710 &self,
711 collection: &str,
712 id: crate::storage::unified::entity::EntityId,
713 ) {
714 let Some(xid) = self.current_xid() else {
715 return;
716 };
717 let store = self.inner.db.store();
718 let Some(manager) = store.get_collection(collection) else {
719 return;
720 };
721 if let Some(mut entity) = manager.get(id) {
722 entity.set_xmin(xid);
723 let _ = manager.update(entity);
724 }
725 }
726
727 pub(crate) fn revive_tombstones_since(&self, conn_id: u64, stamper_xid: u64) -> usize {
735 let mut guard = self.inner.pending_tombstones.write();
736 let Some(pending) = guard.get_mut(&conn_id) else {
737 return 0;
738 };
739
740 let store = self.inner.db.store();
741 let mut revived = 0usize;
742 pending.retain(|(collection, id, xid, previous_xmax)| {
743 if *xid < stamper_xid {
744 return true;
746 }
747 if let Some(manager) = store.get_collection(collection) {
748 if let Some(mut entity) = manager.get(*id) {
749 if entity.xmax == *xid {
750 entity.set_xmax(*previous_xmax);
751 let _ = manager.update(entity);
752 revived += 1;
753 }
754 }
755 }
756 false
757 });
758 if pending.is_empty() {
759 guard.remove(&conn_id);
760 }
761 revived
762 }
763
764 pub fn current_snapshot(&self) -> crate::storage::transaction::snapshot::Snapshot {
776 self.inner
777 .transaction_state
778 .current_snapshot(current_connection_id())
779 }
780
781 pub fn current_xid(&self) -> Option<crate::storage::transaction::snapshot::Xid> {
791 self.inner
792 .transaction_state
793 .writer_xid(current_connection_id())
794 }
795
796 pub(crate) fn own_transaction_xids(&self) -> std::collections::HashSet<u64> {
797 self.inner
798 .transaction_state
799 .own_xids(current_connection_id())
800 }
801
802 pub fn connection_in_transaction(&self, conn_id: u64) -> bool {
809 self.inner.transaction_state.in_transaction(conn_id)
810 }
811
812 pub fn snapshot_manager(&self) -> Arc<crate::storage::transaction::snapshot::SnapshotManager> {
815 self.inner.transaction_state.snapshot_manager()
816 }
817
818 pub(crate) fn mvcc_vacuum_cutoff_xid(&self) -> crate::storage::transaction::snapshot::Xid {
819 let manager = self.inner.transaction_state.snapshot_manager();
820 let next_xid = manager.peek_next_xid();
821 let mut cutoff = next_xid;
822 if let Some(oldest_required) = manager.oldest_required_xid() {
823 cutoff = cutoff.min(oldest_required);
824 }
825 let retention_xids = self.config_u64("runtime.mvcc.vacuum_retention_xids", 0);
826 if retention_xids > 0 {
827 cutoff = cutoff.min(next_xid.saturating_sub(retention_xids));
828 }
829 cutoff
830 }
831
832 pub fn current_txn_own_xids(
837 &self,
838 ) -> std::collections::HashSet<crate::storage::transaction::snapshot::Xid> {
839 self.inner
840 .transaction_state
841 .own_xids(current_connection_id())
842 }
843}