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