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 _indexed_mutation = self.index_store_ref().begin_indexed_mutation();
554 let store = self.inner.db.store();
555 if let Some(new) = store.get(collection, new_id) {
556 let fields = crate::application::ports::entity_row_fields_snapshot(&new);
557 self.index_store_ref()
558 .index_entity_delete(collection, new_id, &fields)
559 .map_err(RedDBError::Internal)?;
560 #[cfg(test)]
561 self.index_store_ref().mutation_test_hook(
562 collection,
563 super::index_store::MutationTestPhase::UndoIndexRemoved,
564 );
565 }
566 store
567 .delete_batch(collection, &[new_id])
568 .map_err(|error| RedDBError::Internal(error.to_string()))?;
569 if let Some(manager) = store.get_collection(collection) {
570 if let Some(mut old) = manager.get(old_id) {
571 if old.xmax == xid {
572 old.set_xmax(previous_xmax);
573 manager
574 .update(old.clone())
575 .map_err(|error| RedDBError::Internal(error.to_string()))?;
576 store.context_index().index_entity(collection, &old);
577 let fields = crate::application::ports::entity_row_fields_snapshot(&old);
578 self.index_store_ref()
579 .index_entity_insert(collection, old_id, &fields)
580 .map_err(RedDBError::Internal)?;
581 }
582 }
583 }
584 Ok(())
585 }
586
587 pub(crate) fn finalize_pending_tombstones(&self, conn_id: u64) {
592 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
593 return;
594 };
595 if pending.is_empty() {
596 return;
597 }
598
599 let store = self.inner.db.store();
600 for (collection, id, _xid, _previous_xmax) in pending {
601 store.context_index().remove_entity(id);
602 self.cdc_emit(
603 crate::replication::cdc::ChangeOperation::Delete,
604 &collection,
605 id.raw(),
606 "entity",
607 );
608 }
609 }
610
611 pub(crate) fn revive_pending_tombstones(&self, conn_id: u64) {
618 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
619 return;
620 };
621
622 let store = self.inner.db.store();
623 for (collection, id, xid, previous_xmax) in pending {
624 let Some(manager) = store.get_collection(&collection) else {
625 continue;
626 };
627 if let Some(mut entity) = manager.get(id) {
628 if entity.xmax == xid {
629 entity.set_xmax(previous_xmax);
630 let _ = manager.update(entity);
631 }
632 }
633 }
634 }
635
636 pub fn queue_wait_registry(
638 &self,
639 ) -> std::sync::Arc<crate::runtime::queue_wait_registry::QueueWaitRegistry> {
640 self.inner.queue_wait_registry.clone()
641 }
642
643 pub(crate) fn record_queue_wake(&self, scope: &str, queue: &str) {
648 if self.current_xid().is_some() {
649 let conn_id = current_connection_id();
650 self.inner
651 .pending_queue_wakes
652 .write()
653 .entry(conn_id)
654 .or_default()
655 .push((scope.to_string(), queue.to_string()));
656 return;
657 }
658 self.inner.queue_wait_registry.notify(scope, queue);
659 }
660
661 pub(crate) fn finalize_pending_queue_wakes(&self, conn_id: u64) {
662 let Some(pending) = self.inner.pending_queue_wakes.write().remove(&conn_id) else {
663 return;
664 };
665 for (scope, queue) in pending {
666 self.inner.queue_wait_registry.notify(&scope, &queue);
667 }
668 }
669
670 pub(crate) fn discard_pending_queue_wakes(&self, conn_id: u64) {
671 self.inner.pending_queue_wakes.write().remove(&conn_id);
672 }
673
674 pub(crate) fn release_pending_claim_locks(&self, conn_id: u64) {
675 self.inner
676 .pending_claim_locks
677 .write()
678 .retain(|_, owner| *owner != conn_id);
679 }
680
681 pub(crate) fn finalize_pending_kv_watch_events(&self, conn_id: u64) {
682 let Some(pending) = self.inner.pending_kv_watch_events.write().remove(&conn_id) else {
683 return;
684 };
685 for (_, event) in pending {
686 self.cdc_emit_kv(
687 event.op,
688 &event.collection,
689 &event.key,
690 0,
691 event.before,
692 event.after,
693 );
694 }
695 }
696
697 pub(crate) fn discard_pending_kv_watch_events(&self, conn_id: u64) {
698 self.inner.pending_kv_watch_events.write().remove(&conn_id);
699 }
700
701 pub(crate) fn stamp_xmin_if_in_txn(
716 &self,
717 collection: &str,
718 id: crate::storage::unified::entity::EntityId,
719 ) {
720 let Some(xid) = self.current_xid() else {
721 return;
722 };
723 let store = self.inner.db.store();
724 let Some(manager) = store.get_collection(collection) else {
725 return;
726 };
727 if let Some(mut entity) = manager.get(id) {
728 entity.set_xmin(xid);
729 let _ = manager.update(entity);
730 }
731 }
732
733 pub(crate) fn revive_tombstones_since(&self, conn_id: u64, stamper_xid: u64) -> usize {
741 let mut guard = self.inner.pending_tombstones.write();
742 let Some(pending) = guard.get_mut(&conn_id) else {
743 return 0;
744 };
745
746 let store = self.inner.db.store();
747 let mut revived = 0usize;
748 pending.retain(|(collection, id, xid, previous_xmax)| {
749 if *xid < stamper_xid {
750 return true;
752 }
753 if let Some(manager) = store.get_collection(collection) {
754 if let Some(mut entity) = manager.get(*id) {
755 if entity.xmax == *xid {
756 entity.set_xmax(*previous_xmax);
757 let _ = manager.update(entity);
758 revived += 1;
759 }
760 }
761 }
762 false
763 });
764 if pending.is_empty() {
765 guard.remove(&conn_id);
766 }
767 revived
768 }
769
770 pub fn current_snapshot(&self) -> crate::storage::transaction::snapshot::Snapshot {
782 self.inner
783 .transaction_state
784 .current_snapshot(current_connection_id())
785 }
786
787 pub fn current_xid(&self) -> Option<crate::storage::transaction::snapshot::Xid> {
797 self.inner
798 .transaction_state
799 .writer_xid(current_connection_id())
800 }
801
802 pub(crate) fn own_transaction_xids(&self) -> std::collections::HashSet<u64> {
803 self.inner
804 .transaction_state
805 .own_xids(current_connection_id())
806 }
807
808 pub fn connection_in_transaction(&self, conn_id: u64) -> bool {
815 self.inner.transaction_state.in_transaction(conn_id)
816 }
817
818 pub fn snapshot_manager(&self) -> Arc<crate::storage::transaction::snapshot::SnapshotManager> {
821 self.inner.transaction_state.snapshot_manager()
822 }
823
824 pub(crate) fn mvcc_vacuum_cutoff_xid(&self) -> crate::storage::transaction::snapshot::Xid {
825 let manager = self.inner.transaction_state.snapshot_manager();
826 let next_xid = manager.peek_next_xid();
827 let mut cutoff = next_xid;
828 if let Some(oldest_required) = manager.oldest_required_xid() {
829 cutoff = cutoff.min(oldest_required);
830 }
831 let retention_xids = self.config_u64("runtime.mvcc.vacuum_retention_xids", 0);
832 if retention_xids > 0 {
833 cutoff = cutoff.min(next_xid.saturating_sub(retention_xids));
834 }
835 cutoff
836 }
837
838 pub fn current_txn_own_xids(
843 &self,
844 ) -> std::collections::HashSet<crate::storage::transaction::snapshot::Xid> {
845 self.inner
846 .transaction_state
847 .own_xids(current_connection_id())
848 }
849}