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 logical_start = self
141 .inner
142 .pending_logical_records
143 .lock()
144 .get(&conn_id)
145 .map_or(0, |pending| pending.len());
146 let capture = DmlWalCapture::begin();
147 let result = f();
148 let captured = capture.finish();
149 match result {
150 Ok(value) => {
151 self.record_pending_store_wal_actions(conn_id, captured);
152 Ok(value)
153 }
154 Err(err) => {
155 if let Some(records) = self.inner.pending_logical_records.lock().get_mut(&conn_id) {
156 records.truncate(logical_start);
157 }
158 Err(err)
159 }
160 }
161 }
162
163 pub(crate) fn with_deferred_store_wal_for_dml<T>(
164 &self,
165 capture_autocommit_events: bool,
166 f: impl FnOnce() -> RedDBResult<T>,
167 ) -> RedDBResult<T> {
168 let conn_id = current_connection_id();
169 if self.inner.transaction_state.in_transaction(conn_id) {
170 return self.with_deferred_store_wal_if_transaction(f);
171 }
172 self.drain_committed_writes_under_pressure(0)?;
173 if !capture_autocommit_events {
174 return f();
175 }
176
177 let deletions = super::delete_publication::DeletePublicationScope::begin();
178 let capture = DmlWalCapture::begin();
179 let result = f();
180 let captured = capture.finish();
181 if result.is_err() && deletions.has_pending() {
182 return result;
183 }
184 deletions.publish(self, || {
185 self.inner
186 .db
187 .store()
188 .append_deferred_store_wal_actions(captured)
189 .map_err(|err| RedDBError::Internal(err.to_string()))
190 })?;
191 result
192 }
193
194 pub(crate) fn insert_may_emit_events(&self, query: &InsertQuery) -> bool {
195 !query.suppress_events
196 && self.collection_has_event_subscriptions_for_operation(
197 &query.table,
198 crate::catalog::SubscriptionOperation::Insert,
199 )
200 }
201
202 pub(crate) fn update_may_emit_events(&self, query: &UpdateQuery) -> bool {
203 !query.suppress_events
204 && self.collection_has_event_subscriptions_for_operation(
205 &query.table,
206 crate::catalog::SubscriptionOperation::Update,
207 )
208 }
209
210 pub(crate) fn delete_may_emit_events(&self, query: &DeleteQuery) -> bool {
211 !query.suppress_events
212 && self.collection_has_event_subscriptions_for_operation(
213 &query.table,
214 crate::catalog::SubscriptionOperation::Delete,
215 )
216 }
217
218 fn collection_has_event_subscriptions_for_operation(
219 &self,
220 collection: &str,
221 operation: crate::catalog::SubscriptionOperation,
222 ) -> bool {
223 let Some(contract) = self.db().collection_contract_arc(collection) else {
224 return false;
225 };
226 contract.subscriptions.iter().any(|subscription| {
227 subscription.enabled
228 && (subscription.ops_filter.is_empty()
229 || subscription.ops_filter.contains(&operation))
230 })
231 }
232
233 pub(crate) fn record_pending_store_wal_actions(
234 &self,
235 conn_id: u64,
236 actions: crate::storage::unified::DeferredStoreWalActions,
237 ) {
238 if actions.is_empty() {
239 return;
240 }
241 let mut guard = self.inner.pending_store_wal_actions.write();
242 guard
243 .entry(conn_id)
244 .or_default()
245 .push((self.current_xid().unwrap_or(0), actions));
246 }
247
248 pub(crate) fn flush_pending_store_wal_actions(&self, conn_id: u64) -> RedDBResult<()> {
249 let groups = self
250 .inner
251 .pending_store_wal_actions
252 .write()
253 .remove(&conn_id)
254 .unwrap_or_default();
255 let mut actions = crate::storage::unified::DeferredStoreWalActions::default();
256 for (xid, group) in groups {
257 if !self.snapshot_manager().is_aborted(xid) {
258 actions.extend(group);
259 }
260 }
261 self.commit_transactional_replication(conn_id, actions)
262 }
263
264 pub(crate) fn discard_pending_store_wal_actions(&self, conn_id: u64) {
265 self.inner.pending_commit_policies.lock().remove(&conn_id);
266 self.inner.pending_logical_records.lock().remove(&conn_id);
267 self.inner
268 .pending_store_wal_actions
269 .write()
270 .remove(&conn_id);
271 }
272
273 fn xid_conflicts_with_snapshot(
274 &self,
275 xid: crate::storage::transaction::snapshot::Xid,
276 snapshot: &crate::storage::transaction::snapshot::Snapshot,
277 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
278 ) -> bool {
279 xid != 0
280 && !own_xids.contains(&xid)
281 && !self
282 .inner
283 .transaction_state
284 .snapshot_manager()
285 .is_aborted(xid)
286 && !self
287 .inner
288 .transaction_state
289 .snapshot_manager()
290 .is_active(xid)
291 && (xid > snapshot.xid || snapshot.in_progress.contains(&xid))
292 }
293
294 pub(super) fn conflict_error(
295 collection: &str,
296 logical_id: crate::storage::unified::entity::EntityId,
297 xid: crate::storage::transaction::snapshot::Xid,
298 ) -> RedDBError {
299 RedDBError::Query(format!(
300 "serialization conflict: table row {collection}/{} was modified by concurrent transaction {xid}",
301 logical_id.raw()
302 ))
303 }
304
305 fn check_logical_row_conflict(
306 &self,
307 collection: &str,
308 logical_id: crate::storage::unified::entity::EntityId,
309 excluded_ids: &[crate::storage::unified::entity::EntityId],
310 snapshot: &crate::storage::transaction::snapshot::Snapshot,
311 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
312 ) -> RedDBResult<()> {
313 let store = self.inner.db.store();
314 let Some(manager) = store.get_collection(collection) else {
315 return Ok(());
316 };
317
318 for candidate in manager.query_all(|_| true) {
319 if excluded_ids.contains(&candidate.id) || candidate.logical_id() != logical_id {
320 continue;
321 }
322 if self.xid_conflicts_with_snapshot(candidate.xmin, snapshot, own_xids) {
323 return Err(Self::conflict_error(collection, logical_id, candidate.xmin));
324 }
325 if self.xid_conflicts_with_snapshot(candidate.xmax, snapshot, own_xids) {
326 return Err(Self::conflict_error(collection, logical_id, candidate.xmax));
327 }
328 }
329 Ok(())
330 }
331
332 pub(crate) fn check_table_row_write_conflicts(
333 &self,
334 conn_id: u64,
335 snapshot: &crate::storage::transaction::snapshot::Snapshot,
336 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
337 isolation: crate::storage::transaction::IsolationLevel,
338 ) -> RedDBResult<()> {
339 let versioned_updates = self
340 .inner
341 .pending_versioned_updates
342 .read()
343 .get(&conn_id)
344 .cloned()
345 .unwrap_or_default();
346 let tombstones = self
347 .inner
348 .pending_tombstones
349 .read()
350 .get(&conn_id)
351 .cloned()
352 .unwrap_or_default();
353
354 let mut serializable_write_set = std::collections::HashSet::new();
355 let store = self.inner.db.store();
356 for (collection, old_id, new_id, xid, previous_xmax) in versioned_updates {
357 let Some(manager) = store.get_collection(&collection) else {
358 continue;
359 };
360 let Some(old) = manager.get(old_id) else {
361 continue;
362 };
363 let logical_id = old.logical_id();
364 serializable_write_set.insert((collection.clone(), logical_id));
365 if self.xid_conflicts_with_snapshot(previous_xmax, snapshot, own_xids) {
366 return Err(Self::conflict_error(&collection, logical_id, previous_xmax));
367 }
368 if old.xmax != xid && self.xid_conflicts_with_snapshot(old.xmax, snapshot, own_xids) {
369 return Err(Self::conflict_error(&collection, logical_id, old.xmax));
370 }
371 self.check_logical_row_conflict(
372 &collection,
373 logical_id,
374 &[old_id, new_id],
375 snapshot,
376 own_xids,
377 )?;
378 }
379
380 for (collection, id, xid, previous_xmax) in tombstones {
381 let Some(manager) = store.get_collection(&collection) else {
382 continue;
383 };
384 let Some(entity) = manager.get(id) else {
385 continue;
386 };
387 let logical_id = entity.logical_id();
388 serializable_write_set.insert((collection.clone(), logical_id));
389 if self.xid_conflicts_with_snapshot(previous_xmax, snapshot, own_xids) {
390 return Err(Self::conflict_error(&collection, logical_id, previous_xmax));
391 }
392 if entity.xmax != xid
393 && self.xid_conflicts_with_snapshot(entity.xmax, snapshot, own_xids)
394 {
395 return Err(Self::conflict_error(&collection, logical_id, entity.xmax));
396 }
397 self.check_logical_row_conflict(&collection, logical_id, &[id], snapshot, own_xids)?;
398 }
399
400 if isolation == crate::storage::transaction::IsolationLevel::Serializable
401 && self
402 .inner
403 .transaction_state
404 .snapshot_manager()
405 .serializable_commit_would_be_dangerous(
406 *own_xids.iter().min().unwrap_or(&0),
407 &serializable_write_set,
408 )
409 {
410 return Err(RedDBError::Query(
411 "serialization conflict: serializable transaction would complete rw-antidependency dangerous structure".to_string(),
412 ));
413 }
414
415 Ok(())
416 }
417
418 pub(crate) fn check_queue_dedup_write_conflicts(
419 &self,
420 conn_id: u64,
421 snapshot: &crate::storage::transaction::snapshot::Snapshot,
422 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
423 ) -> RedDBResult<()> {
424 let pending = self
425 .inner
426 .pending_queue_dedup
427 .read()
428 .get(&conn_id)
429 .cloned()
430 .unwrap_or_default();
431 if pending.is_empty() {
432 return Ok(());
433 }
434
435 let store = self.inner.db.store();
436 let Some(manager) = store.get_collection(crate::runtime::impl_queue::QUEUE_META_COLLECTION)
437 else {
438 return Ok(());
439 };
440
441 for (queue, dedup_key, metadata_id) in pending {
442 for candidate in manager.query_all(|entity| {
443 entity.data.as_row().is_some_and(|row| {
444 crate::runtime::impl_queue::row_text(row, "kind").as_deref()
445 == Some(crate::runtime::impl_queue::KIND_QUEUE_PUSH_DEDUP)
446 && crate::runtime::impl_queue::row_text(row, "queue").as_deref()
447 == Some(&queue)
448 && crate::runtime::impl_queue::row_text(row, "dedup_key").as_deref()
449 == Some(&dedup_key)
450 })
451 }) {
452 if candidate.id == metadata_id
453 || self
454 .inner
455 .transaction_state
456 .snapshot_manager()
457 .is_aborted(candidate.xmin)
458 {
459 continue;
460 }
461 if self.xid_conflicts_with_snapshot(candidate.xmin, snapshot, own_xids) {
462 return Err(RedDBError::Query(format!(
463 "serialization conflict: queue dedup key {queue}/{dedup_key} was modified by concurrent transaction {}",
464 candidate.xmin
465 )));
466 }
467 }
468 }
469 Ok(())
470 }
471
472 pub(crate) fn restore_pending_write_stamps(&self, conn_id: u64) {
473 let versioned_updates = self
474 .inner
475 .pending_versioned_updates
476 .read()
477 .get(&conn_id)
478 .cloned()
479 .unwrap_or_default();
480 let tombstones = self
481 .inner
482 .pending_tombstones
483 .read()
484 .get(&conn_id)
485 .cloned()
486 .unwrap_or_default();
487
488 let store = self.inner.db.store();
489 for (collection, old_id, _new_id, xid, _previous_xmax) in versioned_updates {
490 if let Some(manager) = store.get_collection(&collection) {
491 if let Some(mut entity) = manager.get(old_id) {
492 entity.set_xmax(xid);
493 let _ = manager.update(entity);
494 }
495 }
496 }
497 for (collection, id, xid, _previous_xmax) in tombstones {
498 if let Some(manager) = store.get_collection(&collection) {
499 if let Some(mut entity) = manager.get(id) {
500 entity.set_xmax(xid);
501 let _ = manager.update(entity);
502 }
503 }
504 }
505 }
506
507 pub(crate) fn finalize_pending_versioned_updates(&self, conn_id: u64) {
508 self.inner
509 .pending_versioned_updates
510 .write()
511 .remove(&conn_id);
512 }
513
514 pub(crate) fn finalize_pending_queue_dedup(&self, conn_id: u64) {
515 self.inner.pending_queue_dedup.write().remove(&conn_id);
516 }
517
518 pub(crate) fn discard_pending_queue_dedup(&self, conn_id: u64) {
519 let Some(pending) = self.inner.pending_queue_dedup.write().remove(&conn_id) else {
520 return;
521 };
522 let store = self.inner.db.store();
523 for (_queue, _dedup_key, metadata_id) in pending {
524 let _ = store.delete(
525 crate::runtime::impl_queue::QUEUE_META_COLLECTION,
526 metadata_id,
527 );
528 }
529 }
530
531 pub(crate) fn revive_pending_versioned_updates(&self, conn_id: u64) -> RedDBResult<()> {
532 let Some(pending) = self
533 .inner
534 .pending_versioned_updates
535 .write()
536 .remove(&conn_id)
537 else {
538 return Ok(());
539 };
540 for (collection, old_id, new_id, xid, previous_xmax) in pending.into_iter().rev() {
542 let topology_lock = self.index_store_ref().collection_topology_lock(&collection);
543 #[cfg(test)]
544 self.index_store_ref().mutation_test_hook(
545 &collection,
546 super::index_store::MutationTestPhase::BeforeUndoTopology,
547 );
548 self.revive_versioned_update(
549 &collection,
550 old_id,
551 new_id,
552 xid,
553 previous_xmax,
554 topology_lock.read(),
555 )?;
556 }
557 Ok(())
558 }
559
560 pub(crate) fn revive_versioned_update(
561 &self,
562 collection: &str,
563 old_id: crate::storage::EntityId,
564 new_id: crate::storage::EntityId,
565 xid: u64,
566 previous_xmax: u64,
567 _topology_guard: parking_lot::RwLockReadGuard<'_, ()>,
568 ) -> RedDBResult<()> {
569 let _indexed_mutation = self.index_store_ref().begin_indexed_mutation();
570 let store = self.inner.db.store();
571 if let Some(new) = store.get(collection, new_id) {
572 let fields = crate::application::ports::entity_row_fields_snapshot(&new);
573 self.index_store_ref()
574 .index_entity_delete(collection, new_id, &fields)
575 .map_err(RedDBError::Internal)?;
576 #[cfg(test)]
577 self.index_store_ref().mutation_test_hook(
578 collection,
579 super::index_store::MutationTestPhase::UndoIndexRemoved,
580 );
581 }
582 store
583 .delete_batch(collection, &[new_id])
584 .map_err(|error| RedDBError::Internal(error.to_string()))?;
585 if let Some(manager) = store.get_collection(collection) {
586 if let Some(mut old) = manager.get(old_id) {
587 if old.xmax == xid {
588 old.set_xmax(previous_xmax);
589 manager
590 .update(old.clone())
591 .map_err(|error| RedDBError::Internal(error.to_string()))?;
592 store.context_index().index_entity(collection, &old);
593 let fields = crate::application::ports::entity_row_fields_snapshot(&old);
594 self.index_store_ref()
595 .index_entity_insert(collection, old_id, &fields)
596 .map_err(RedDBError::Internal)?;
597 }
598 }
599 }
600 Ok(())
601 }
602
603 pub(crate) fn finalize_pending_tombstones(&self, conn_id: u64) {
608 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
609 return;
610 };
611 if pending.is_empty() {
612 return;
613 }
614
615 let store = self.inner.db.store();
616 for (collection, id, _xid, _previous_xmax) in pending {
617 if let Some(manager) = store.get_collection(&collection) {
618 manager.remove_metadata_expiry(id);
619 }
620 store.context_index().remove_entity(id);
621 self.invalidate_result_cache_for_table(&collection);
622 if self.inner.db.replication.is_none() {
625 self.inner.cdc.emit(
626 crate::replication::cdc::ChangeOperation::Delete,
627 &collection,
628 id.raw(),
629 "entity",
630 );
631 }
632 }
633 }
634
635 pub(crate) fn revive_pending_tombstones(&self, conn_id: u64) {
642 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
643 return;
644 };
645
646 let store = self.inner.db.store();
647 for (collection, id, xid, previous_xmax) in pending {
648 let Some(manager) = store.get_collection(&collection) else {
649 continue;
650 };
651 if let Some(mut entity) = manager.get(id) {
652 if entity.xmax == xid {
653 entity.set_xmax(previous_xmax);
654 let _ = manager.update(entity);
655 }
656 }
657 }
658 }
659
660 pub fn queue_wait_registry(
662 &self,
663 ) -> std::sync::Arc<crate::runtime::queue_wait_registry::QueueWaitRegistry> {
664 self.inner.queue_wait_registry.clone()
665 }
666
667 pub(crate) fn record_queue_wake(&self, scope: &str, queue: &str) {
672 if self.current_xid().is_some() {
673 let conn_id = current_connection_id();
674 self.inner
675 .pending_queue_wakes
676 .write()
677 .entry(conn_id)
678 .or_default()
679 .push((scope.to_string(), queue.to_string()));
680 return;
681 }
682 self.inner.queue_wait_registry.notify(scope, queue);
683 }
684
685 pub(crate) fn finalize_pending_queue_wakes(&self, conn_id: u64) {
686 let Some(pending) = self.inner.pending_queue_wakes.write().remove(&conn_id) else {
687 return;
688 };
689 for (scope, queue) in pending {
690 self.inner.queue_wait_registry.notify(&scope, &queue);
691 }
692 }
693
694 pub(crate) fn discard_pending_queue_wakes(&self, conn_id: u64) {
695 self.inner.pending_queue_wakes.write().remove(&conn_id);
696 }
697
698 pub(crate) fn release_pending_claim_locks(&self, conn_id: u64) {
699 self.inner
700 .pending_claim_locks
701 .write()
702 .retain(|_, owner| *owner != conn_id);
703 }
704
705 pub(crate) fn finalize_pending_kv_watch_events(&self, conn_id: u64) {
706 let Some(pending) = self.inner.pending_kv_watch_events.write().remove(&conn_id) else {
707 return;
708 };
709 for (_, event) in pending {
710 self.cdc_emit_kv(
711 event.op,
712 &event.collection,
713 &event.key,
714 0,
715 event.before,
716 event.after,
717 );
718 }
719 }
720
721 pub(crate) fn discard_pending_kv_watch_events(&self, conn_id: u64) {
722 self.inner.pending_kv_watch_events.write().remove(&conn_id);
723 }
724
725 pub(crate) fn stamp_xmin_if_in_txn(
740 &self,
741 collection: &str,
742 id: crate::storage::unified::entity::EntityId,
743 ) {
744 let Some(xid) = self.current_xid() else {
745 return;
746 };
747 let store = self.inner.db.store();
748 let Some(manager) = store.get_collection(collection) else {
749 return;
750 };
751 if let Some(mut entity) = manager.get(id) {
752 entity.set_xmin(xid);
753 let _ = manager.update(entity);
754 }
755 }
756
757 pub(crate) fn revive_tombstones_since(&self, conn_id: u64, stamper_xid: u64) -> usize {
765 let mut guard = self.inner.pending_tombstones.write();
766 let Some(pending) = guard.get_mut(&conn_id) else {
767 return 0;
768 };
769
770 let store = self.inner.db.store();
771 let mut revived = 0usize;
772 pending.retain(|(collection, id, xid, previous_xmax)| {
773 if *xid < stamper_xid {
774 return true;
776 }
777 if let Some(manager) = store.get_collection(collection) {
778 if let Some(mut entity) = manager.get(*id) {
779 if entity.xmax == *xid {
780 entity.set_xmax(*previous_xmax);
781 let _ = manager.update(entity);
782 revived += 1;
783 }
784 }
785 }
786 false
787 });
788 if pending.is_empty() {
789 guard.remove(&conn_id);
790 }
791 revived
792 }
793
794 pub fn current_snapshot(&self) -> crate::storage::transaction::snapshot::Snapshot {
806 self.inner
807 .transaction_state
808 .current_snapshot(current_connection_id())
809 }
810
811 pub fn current_xid(&self) -> Option<crate::storage::transaction::snapshot::Xid> {
821 self.inner
822 .transaction_state
823 .writer_xid(current_connection_id())
824 }
825
826 pub(crate) fn own_transaction_xids(&self) -> std::collections::HashSet<u64> {
827 self.inner
828 .transaction_state
829 .own_xids(current_connection_id())
830 }
831
832 pub fn connection_in_transaction(&self, conn_id: u64) -> bool {
839 self.inner.transaction_state.in_transaction(conn_id)
840 }
841
842 pub fn snapshot_manager(&self) -> Arc<crate::storage::transaction::snapshot::SnapshotManager> {
845 self.inner.transaction_state.snapshot_manager()
846 }
847
848 pub(crate) fn mvcc_vacuum_cutoff_xid(&self) -> crate::storage::transaction::snapshot::Xid {
849 let manager = self.inner.transaction_state.snapshot_manager();
850 let next_xid = manager.peek_next_xid();
851 let mut cutoff = next_xid;
852 if let Some(oldest_required) = manager.oldest_required_xid() {
853 cutoff = cutoff.min(oldest_required);
854 }
855 let retention_xids = self.config_u64("runtime.mvcc.vacuum_retention_xids", 0);
856 if retention_xids > 0 {
857 cutoff = cutoff.min(next_xid.saturating_sub(retention_xids));
858 }
859 cutoff
860 }
861
862 pub fn current_txn_own_xids(
867 &self,
868 ) -> std::collections::HashSet<crate::storage::transaction::snapshot::Xid> {
869 self.inner
870 .transaction_state
871 .own_xids(current_connection_id())
872 }
873}