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