1use super::execution_context::current_connection_id;
22use super::*;
23
24thread_local! {
25 static DEFERRED_UPDATES: std::cell::RefCell<Option<Vec<DeferredUpdate>>> = const { std::cell::RefCell::new(None) };
26}
27
28struct DeferredUpdate {
29 runtime: RedDBRuntime,
30 collection: String,
31 old_id: EntityId,
32 new_id: EntityId,
33 previous_xmax: u64,
34 update: Arc<crate::application::entity::AutocommitUpdate>,
35 committed: bool,
36}
37
38impl Drop for DeferredUpdate {
39 fn drop(&mut self) {
40 if !self.committed {
41 let topology = self
42 .runtime
43 .index_store_ref()
44 .collection_topology_lock(&self.collection);
45 if let Err(error) = self.runtime.revive_versioned_update(
46 &self.collection,
47 self.old_id,
48 self.new_id,
49 self.update.xid,
50 self.previous_xmax,
51 topology.read_recursive(),
52 ) {
53 tracing::error!(%error, "autocommit update rollback failed");
54 }
55 }
56 }
57}
58
59struct DeferredUpdates;
60
61impl DeferredUpdates {
62 fn begin() -> Self {
63 DEFERRED_UPDATES.with(|slot| {
64 assert!(slot.borrow().is_none(), "update WAL scopes cannot nest");
65 *slot.borrow_mut() = Some(Vec::new());
66 });
67 Self
68 }
69
70 fn commit(self) {
71 DEFERRED_UPDATES.with(|slot| {
72 for mut pending in slot.borrow_mut().take().expect("update WAL scope") {
73 let update = &pending.update;
74 update.commit();
75 pending.committed = true;
76 }
77 });
78 }
79
80 fn prepare(&self) -> RedDBResult<()> {
81 DEFERRED_UPDATES.with(|slot| {
82 for pending in slot.borrow().as_ref().expect("update WAL scope") {
83 let store = pending.runtime.db().store();
84 let Some(manager) = store.get_collection(&pending.collection) else {
85 return Err(RedDBError::Internal(
86 "updated collection disappeared".into(),
87 ));
88 };
89 let old_owned = manager
90 .try_get(pending.old_id, usize::MAX)
91 .map_err(|error| RedDBError::Internal(error.to_string()))?
92 .is_some_and(|old| old.xmax == pending.update.xid);
93 let new_owned = manager
94 .try_get(pending.new_id, usize::MAX)
95 .map_err(|error| RedDBError::Internal(error.to_string()))?
96 .is_some_and(|new| new.xmin == pending.update.xid);
97 if !old_owned || !new_owned {
98 return Err(RedDBRuntime::conflict_error(
99 &pending.collection,
100 pending.old_id,
101 pending.update.xid,
102 ));
103 }
104 }
105 Ok(())
106 })
107 }
108}
109
110impl Drop for DeferredUpdates {
111 fn drop(&mut self) {
112 let pending = DEFERRED_UPDATES.with(|slot| slot.borrow_mut().take());
113 if let Some(pending) = pending {
114 for update in pending.into_iter().rev() {
115 drop(update);
116 }
117 }
118 }
119}
120
121pub(crate) fn autocommit_update_capture_available() -> bool {
122 DEFERRED_UPDATES.with(|slot| slot.borrow().is_some())
123}
124
125pub(crate) fn publish_autocommit_update(
126 runtime: &RedDBRuntime,
127 item: &crate::application::entity::AppliedEntityMutation,
128 update: &Arc<crate::application::entity::AutocommitUpdate>,
129) {
130 if UnifiedStore::deferred_store_wal_capture_active() {
131 DEFERRED_UPDATES.with(|slot| {
132 slot.borrow_mut()
133 .as_mut()
134 .expect("update WAL scope")
135 .push(DeferredUpdate {
136 runtime: runtime.clone(),
137 collection: item.collection.clone(),
138 old_id: item.replaced_entity.as_ref().expect("versioned update").id,
139 new_id: item.entity.id,
140 previous_xmax: item.replaced_entity_previous_xmax,
141 update: Arc::clone(update),
142 committed: false,
143 });
144 });
145 } else {
146 update.commit();
147 }
148}
149
150struct DmlWalCapture(bool);
152
153impl DmlWalCapture {
154 fn begin() -> Self {
155 assert!(
156 !UnifiedStore::deferred_store_wal_capture_active(),
157 "DML WAL captures cannot nest"
158 );
159 UnifiedStore::begin_deferred_store_wal_capture();
160 Self(true)
161 }
162
163 fn finish(mut self) -> crate::storage::unified::DeferredStoreWalActions {
164 self.0 = false;
165 UnifiedStore::take_deferred_store_wal_capture()
166 }
167}
168
169impl Drop for DmlWalCapture {
170 fn drop(&mut self) {
171 if self.0 {
172 let _ = UnifiedStore::take_deferred_store_wal_capture();
173 }
174 }
175}
176
177impl RedDBRuntime {
178 pub(crate) fn rollback_connection(
179 &self,
180 connection_id: u64,
181 ) -> RedDBResult<Option<crate::storage::transaction::snapshot::TxnContext>> {
182 let undo = self.revive_pending_versioned_updates(connection_id);
186 self.revive_pending_tombstones(connection_id);
187 let context = self.inner.transaction_state.rollback(connection_id);
188 self.discard_pending_queue_dedup(connection_id);
189 self.discard_pending_kv_watch_events(connection_id);
190 self.discard_pending_queue_wakes(connection_id);
191 self.discard_pending_store_wal_actions(connection_id);
192 self.release_pending_claim_locks(connection_id);
193 self.discard_pending_vault_writes(connection_id)?;
194 undo?;
195 Ok(context)
196 }
197
198 pub(crate) fn record_pending_tombstone(
204 &self,
205 conn_id: u64,
206 collection: &str,
207 id: crate::storage::unified::entity::EntityId,
208 stamper_xid: crate::storage::transaction::snapshot::Xid,
209 previous_xmax: crate::storage::transaction::snapshot::Xid,
210 ) {
211 self.inner
212 .pending_tombstones
213 .write()
214 .entry(conn_id)
215 .or_default()
216 .push((collection.to_string(), id, stamper_xid, previous_xmax));
217 }
218
219 pub(crate) fn record_pending_versioned_update(
220 &self,
221 conn_id: u64,
222 collection: &str,
223 old_id: crate::storage::unified::entity::EntityId,
224 new_id: crate::storage::unified::entity::EntityId,
225 stamper_xid: crate::storage::transaction::snapshot::Xid,
226 previous_xmax: crate::storage::transaction::snapshot::Xid,
227 ) {
228 self.inner
229 .pending_versioned_updates
230 .write()
231 .entry(conn_id)
232 .or_default()
233 .push((
234 collection.to_string(),
235 old_id,
236 new_id,
237 stamper_xid,
238 previous_xmax,
239 ));
240 }
241
242 pub(crate) fn record_pending_queue_dedup(
243 &self,
244 conn_id: u64,
245 queue: &str,
246 dedup_key: &str,
247 metadata_id: crate::storage::unified::entity::EntityId,
248 ) {
249 self.inner
250 .pending_queue_dedup
251 .write()
252 .entry(conn_id)
253 .or_default()
254 .push((queue.to_string(), dedup_key.to_string(), metadata_id));
255 }
256
257 pub(crate) fn with_deferred_store_wal_if_transaction<T>(
258 &self,
259 f: impl FnOnce() -> RedDBResult<T>,
260 ) -> RedDBResult<T> {
261 let conn_id = current_connection_id();
262 if !self.inner.transaction_state.in_transaction(conn_id) {
263 return f();
264 }
265
266 let logical_start = self
267 .inner
268 .pending_logical_records
269 .lock()
270 .get(&conn_id)
271 .map_or(0, |pending| pending.len());
272 let capture = DmlWalCapture::begin();
273 let result = f();
274 let captured = capture.finish();
275 self.flush_deferred_cache_invalidation();
276 match result {
277 Ok(value) => {
278 self.record_pending_store_wal_actions(conn_id, captured);
279 Ok(value)
280 }
281 Err(err) => {
282 if let Some(records) = self.inner.pending_logical_records.lock().get_mut(&conn_id) {
283 records.truncate(logical_start);
284 }
285 Err(err)
286 }
287 }
288 }
289
290 pub(crate) fn with_deferred_store_wal_for_dml<T>(
291 &self,
292 capture_autocommit_events: bool,
293 f: impl FnOnce() -> RedDBResult<T>,
294 ) -> RedDBResult<T> {
295 let conn_id = current_connection_id();
296 if self.inner.transaction_state.in_transaction(conn_id) {
297 return self.with_deferred_store_wal_if_transaction(f);
298 }
299 self.drain_committed_writes_under_pressure(0)?;
300 if !capture_autocommit_events {
301 return f();
302 }
303
304 let deletions = super::delete_publication::DeletePublicationScope::begin();
305 let updates = DeferredUpdates::begin();
306 let capture = DmlWalCapture::begin();
307 let result = f();
308 let captured = capture.finish();
309 if result.is_err() && deletions.has_pending() {
310 self.flush_deferred_cache_invalidation();
311 return result;
312 }
313 let published = deletions.publish(self, || {
314 updates.prepare()?;
315 self.inner
316 .db
317 .store()
318 .append_deferred_store_wal_actions(captured)
319 .map_err(|err| RedDBError::Internal(err.to_string()))?;
320 updates.commit();
321 Ok(())
322 });
323 self.flush_deferred_cache_invalidation();
324 published?;
325 result
326 }
327
328 pub(crate) fn insert_may_emit_events(&self, query: &InsertQuery) -> bool {
329 !query.suppress_events
330 && self.collection_has_event_subscriptions_for_operation(
331 &query.table,
332 crate::catalog::SubscriptionOperation::Insert,
333 )
334 }
335
336 pub(crate) fn update_may_emit_events(&self, query: &UpdateQuery) -> bool {
337 !query.suppress_events
338 && self.collection_has_event_subscriptions_for_operation(
339 &query.table,
340 crate::catalog::SubscriptionOperation::Update,
341 )
342 }
343
344 pub(crate) fn delete_may_emit_events(&self, query: &DeleteQuery) -> bool {
345 !query.suppress_events
346 && self.collection_has_event_subscriptions_for_operation(
347 &query.table,
348 crate::catalog::SubscriptionOperation::Delete,
349 )
350 }
351
352 fn collection_has_event_subscriptions_for_operation(
353 &self,
354 collection: &str,
355 operation: crate::catalog::SubscriptionOperation,
356 ) -> bool {
357 let Some(contract) = self.db().collection_contract_arc(collection) else {
358 return false;
359 };
360 contract.subscriptions.iter().any(|subscription| {
361 subscription.enabled
362 && (subscription.ops_filter.is_empty()
363 || subscription.ops_filter.contains(&operation))
364 })
365 }
366
367 pub(crate) fn record_pending_store_wal_actions(
368 &self,
369 conn_id: u64,
370 actions: crate::storage::unified::DeferredStoreWalActions,
371 ) {
372 if actions.is_empty() {
373 return;
374 }
375 let mut guard = self.inner.pending_store_wal_actions.write();
376 guard
377 .entry(conn_id)
378 .or_default()
379 .push((self.current_xid().unwrap_or(0), actions));
380 }
381
382 pub(crate) fn flush_pending_store_wal_actions(&self, conn_id: u64) -> RedDBResult<()> {
383 let groups = self
384 .inner
385 .pending_store_wal_actions
386 .write()
387 .remove(&conn_id)
388 .unwrap_or_default();
389 let mut actions = crate::storage::unified::DeferredStoreWalActions::default();
390 for (xid, group) in groups {
391 if !self.snapshot_manager().is_aborted(xid) {
392 actions.extend(group);
393 }
394 }
395 self.commit_transactional_replication(conn_id, actions)
396 }
397
398 pub(crate) fn discard_pending_store_wal_actions(&self, conn_id: u64) {
399 self.inner.pending_commit_policies.lock().remove(&conn_id);
400 self.inner.pending_logical_records.lock().remove(&conn_id);
401 self.inner
402 .pending_store_wal_actions
403 .write()
404 .remove(&conn_id);
405 }
406
407 fn xid_conflicts_with_snapshot(
408 &self,
409 xid: crate::storage::transaction::snapshot::Xid,
410 snapshot: &crate::storage::transaction::snapshot::Snapshot,
411 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
412 ) -> bool {
413 xid != 0
414 && !own_xids.contains(&xid)
415 && !self
416 .inner
417 .transaction_state
418 .snapshot_manager()
419 .is_aborted(xid)
420 && !self
421 .inner
422 .transaction_state
423 .snapshot_manager()
424 .is_active(xid)
425 && (xid > snapshot.xid || snapshot.in_progress.contains(&xid))
426 }
427
428 pub(super) fn conflict_error(
429 collection: &str,
430 logical_id: crate::storage::unified::entity::EntityId,
431 xid: crate::storage::transaction::snapshot::Xid,
432 ) -> RedDBError {
433 RedDBError::Query(format!(
434 "serialization conflict: table row {collection}/{} was modified by concurrent transaction {xid}",
435 logical_id.raw()
436 ))
437 }
438
439 fn check_logical_row_conflict(
440 &self,
441 collection: &str,
442 logical_id: crate::storage::unified::entity::EntityId,
443 excluded_ids: &[crate::storage::unified::entity::EntityId],
444 snapshot: &crate::storage::transaction::snapshot::Snapshot,
445 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
446 ) -> RedDBResult<()> {
447 let store = self.inner.db.store();
448 let Some(manager) = store.get_collection(collection) else {
449 return Ok(());
450 };
451
452 for candidate in manager.query_all(|_| true) {
453 if excluded_ids.contains(&candidate.id) || candidate.logical_id() != logical_id {
454 continue;
455 }
456 if self.xid_conflicts_with_snapshot(candidate.xmin, snapshot, own_xids) {
457 return Err(Self::conflict_error(collection, logical_id, candidate.xmin));
458 }
459 if self.xid_conflicts_with_snapshot(candidate.xmax, snapshot, own_xids) {
460 return Err(Self::conflict_error(collection, logical_id, candidate.xmax));
461 }
462 }
463 Ok(())
464 }
465
466 pub(crate) fn check_table_row_write_conflicts(
467 &self,
468 conn_id: u64,
469 snapshot: &crate::storage::transaction::snapshot::Snapshot,
470 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
471 isolation: crate::storage::transaction::IsolationLevel,
472 ) -> RedDBResult<()> {
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 mut serializable_write_set = std::collections::HashSet::new();
489 let store = self.inner.db.store();
490 for (collection, old_id, new_id, xid, previous_xmax) in versioned_updates {
491 let Some(manager) = store.get_collection(&collection) else {
492 continue;
493 };
494 let Some(old) = manager.get(old_id) else {
495 continue;
496 };
497 let logical_id = old.logical_id();
498 serializable_write_set.insert((collection.clone(), logical_id));
499 if self.xid_conflicts_with_snapshot(previous_xmax, snapshot, own_xids) {
500 return Err(Self::conflict_error(&collection, logical_id, previous_xmax));
501 }
502 if old.xmax != xid && self.xid_conflicts_with_snapshot(old.xmax, snapshot, own_xids) {
503 return Err(Self::conflict_error(&collection, logical_id, old.xmax));
504 }
505 self.check_logical_row_conflict(
506 &collection,
507 logical_id,
508 &[old_id, new_id],
509 snapshot,
510 own_xids,
511 )?;
512 }
513
514 for (collection, id, xid, previous_xmax) in tombstones {
515 let Some(manager) = store.get_collection(&collection) else {
516 continue;
517 };
518 let Some(entity) = manager.get(id) else {
519 continue;
520 };
521 let logical_id = entity.logical_id();
522 serializable_write_set.insert((collection.clone(), logical_id));
523 if self.xid_conflicts_with_snapshot(previous_xmax, snapshot, own_xids) {
524 return Err(Self::conflict_error(&collection, logical_id, previous_xmax));
525 }
526 if entity.xmax != xid
527 && self.xid_conflicts_with_snapshot(entity.xmax, snapshot, own_xids)
528 {
529 return Err(Self::conflict_error(&collection, logical_id, entity.xmax));
530 }
531 self.check_logical_row_conflict(&collection, logical_id, &[id], snapshot, own_xids)?;
532 }
533
534 if isolation == crate::storage::transaction::IsolationLevel::Serializable
535 && self
536 .inner
537 .transaction_state
538 .snapshot_manager()
539 .serializable_commit_would_be_dangerous(
540 *own_xids.iter().min().unwrap_or(&0),
541 &serializable_write_set,
542 )
543 {
544 return Err(RedDBError::Query(
545 "serialization conflict: serializable transaction would complete rw-antidependency dangerous structure".to_string(),
546 ));
547 }
548
549 Ok(())
550 }
551
552 pub(crate) fn check_queue_dedup_write_conflicts(
553 &self,
554 conn_id: u64,
555 snapshot: &crate::storage::transaction::snapshot::Snapshot,
556 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
557 ) -> RedDBResult<()> {
558 let pending = self
559 .inner
560 .pending_queue_dedup
561 .read()
562 .get(&conn_id)
563 .cloned()
564 .unwrap_or_default();
565 if pending.is_empty() {
566 return Ok(());
567 }
568
569 let store = self.inner.db.store();
570 let Some(manager) = store.get_collection(crate::runtime::impl_queue::QUEUE_META_COLLECTION)
571 else {
572 return Ok(());
573 };
574
575 for (queue, dedup_key, metadata_id) in pending {
576 for candidate in manager.query_all(|entity| {
577 entity.data.as_row().is_some_and(|row| {
578 crate::runtime::impl_queue::row_text(row, "kind").as_deref()
579 == Some(crate::runtime::impl_queue::KIND_QUEUE_PUSH_DEDUP)
580 && crate::runtime::impl_queue::row_text(row, "queue").as_deref()
581 == Some(&queue)
582 && crate::runtime::impl_queue::row_text(row, "dedup_key").as_deref()
583 == Some(&dedup_key)
584 })
585 }) {
586 if candidate.id == metadata_id
587 || self
588 .inner
589 .transaction_state
590 .snapshot_manager()
591 .is_aborted(candidate.xmin)
592 {
593 continue;
594 }
595 if self.xid_conflicts_with_snapshot(candidate.xmin, snapshot, own_xids) {
596 return Err(RedDBError::Query(format!(
597 "serialization conflict: queue dedup key {queue}/{dedup_key} was modified by concurrent transaction {}",
598 candidate.xmin
599 )));
600 }
601 }
602 }
603 Ok(())
604 }
605
606 pub(crate) fn restore_pending_write_stamps(&self, conn_id: u64) {
607 let versioned_updates = self
608 .inner
609 .pending_versioned_updates
610 .read()
611 .get(&conn_id)
612 .cloned()
613 .unwrap_or_default();
614 let tombstones = self
615 .inner
616 .pending_tombstones
617 .read()
618 .get(&conn_id)
619 .cloned()
620 .unwrap_or_default();
621
622 let store = self.inner.db.store();
623 for (collection, old_id, _new_id, xid, _previous_xmax) in versioned_updates {
624 if let Some(manager) = store.get_collection(&collection) {
625 if let Some(mut entity) = manager.get(old_id) {
626 entity.set_xmax(xid);
627 let _ = manager.update(entity);
628 }
629 }
630 }
631 for (collection, id, xid, _previous_xmax) in tombstones {
632 if let Some(manager) = store.get_collection(&collection) {
633 if let Some(mut entity) = manager.get(id) {
634 entity.set_xmax(xid);
635 let _ = manager.update(entity);
636 }
637 }
638 }
639 }
640
641 pub(crate) fn finalize_pending_versioned_updates(&self, conn_id: u64) {
642 self.inner
643 .pending_versioned_updates
644 .write()
645 .remove(&conn_id);
646 }
647
648 pub(crate) fn finalize_pending_queue_dedup(&self, conn_id: u64) {
649 self.inner.pending_queue_dedup.write().remove(&conn_id);
650 }
651
652 pub(crate) fn discard_pending_queue_dedup(&self, conn_id: u64) {
653 let Some(pending) = self.inner.pending_queue_dedup.write().remove(&conn_id) else {
654 return;
655 };
656 let store = self.inner.db.store();
657 for (_queue, _dedup_key, metadata_id) in pending {
658 let _ = store.delete(
659 crate::runtime::impl_queue::QUEUE_META_COLLECTION,
660 metadata_id,
661 );
662 }
663 }
664
665 pub(crate) fn revive_pending_versioned_updates(&self, conn_id: u64) -> RedDBResult<()> {
666 let Some(pending) = self
667 .inner
668 .pending_versioned_updates
669 .write()
670 .remove(&conn_id)
671 else {
672 return Ok(());
673 };
674 for (collection, old_id, new_id, xid, previous_xmax) in pending.into_iter().rev() {
676 let topology_lock = self.index_store_ref().collection_topology_lock(&collection);
677 #[cfg(test)]
678 self.index_store_ref().mutation_test_hook(
679 &collection,
680 super::index_store::MutationTestPhase::BeforeUndoTopology,
681 );
682 self.revive_versioned_update(
683 &collection,
684 old_id,
685 new_id,
686 xid,
687 previous_xmax,
688 topology_lock.read(),
689 )?;
690 }
691 Ok(())
692 }
693
694 pub(crate) fn revive_versioned_update(
695 &self,
696 collection: &str,
697 old_id: crate::storage::EntityId,
698 new_id: crate::storage::EntityId,
699 xid: u64,
700 previous_xmax: u64,
701 _topology_guard: parking_lot::RwLockReadGuard<'_, ()>,
702 ) -> RedDBResult<()> {
703 let _indexed_mutation = self.index_store_ref().begin_indexed_mutation();
704 let store = self.inner.db.store();
705 if let Some(new) = store.get(collection, new_id) {
706 let fields = crate::application::ports::entity_row_fields_snapshot(&new);
707 self.index_store_ref()
708 .index_entity_delete(collection, new_id, &fields)
709 .map_err(RedDBError::Internal)?;
710 #[cfg(test)]
711 self.index_store_ref().mutation_test_hook(
712 collection,
713 super::index_store::MutationTestPhase::UndoIndexRemoved,
714 );
715 }
716 store
717 .delete_batch(collection, &[new_id])
718 .map_err(|error| RedDBError::Internal(error.to_string()))?;
719 if let Some(manager) = store.get_collection(collection) {
720 if let Some(mut old) = manager.get(old_id) {
721 if old.xmax == xid {
722 old.set_xmax(previous_xmax);
723 manager
724 .update(old.clone())
725 .map_err(|error| RedDBError::Internal(error.to_string()))?;
726 store.context_index().index_entity(collection, &old);
727 let fields = crate::application::ports::entity_row_fields_snapshot(&old);
728 self.index_store_ref()
729 .index_entity_insert(collection, old_id, &fields)
730 .map_err(RedDBError::Internal)?;
731 }
732 }
733 }
734 Ok(())
735 }
736
737 pub(crate) fn finalize_pending_tombstones(&self, conn_id: u64) {
742 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
743 return;
744 };
745 if pending.is_empty() {
746 return;
747 }
748
749 let store = self.inner.db.store();
750 for (collection, id, _xid, _previous_xmax) in pending {
751 if let Some(manager) = store.get_collection(&collection) {
752 manager.remove_metadata_expiry(id);
753 }
754 store.context_index().remove_entity(id);
755 self.invalidate_result_cache_for_table(&collection);
756 if self.inner.db.replication.is_none() {
759 self.inner.cdc.emit(
760 crate::replication::cdc::ChangeOperation::Delete,
761 &collection,
762 id.raw(),
763 "entity",
764 );
765 }
766 }
767 }
768
769 pub(crate) fn revive_pending_tombstones(&self, conn_id: u64) {
776 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
777 return;
778 };
779
780 let store = self.inner.db.store();
781 for (collection, id, xid, previous_xmax) in pending {
782 let Some(manager) = store.get_collection(&collection) else {
783 continue;
784 };
785 if let Some(mut entity) = manager.get(id) {
786 if entity.xmax == xid {
787 entity.set_xmax(previous_xmax);
788 let _ = manager.update(entity);
789 }
790 }
791 }
792 }
793
794 pub fn queue_wait_registry(
796 &self,
797 ) -> std::sync::Arc<crate::runtime::queue_wait_registry::QueueWaitRegistry> {
798 self.inner.queue_wait_registry.clone()
799 }
800
801 pub(crate) fn record_queue_wake(&self, scope: &str, queue: &str) {
806 if self.current_xid().is_some() {
807 let conn_id = current_connection_id();
808 self.inner
809 .pending_queue_wakes
810 .write()
811 .entry(conn_id)
812 .or_default()
813 .push((scope.to_string(), queue.to_string()));
814 return;
815 }
816 self.inner.queue_wait_registry.notify(scope, queue);
817 }
818
819 pub(crate) fn finalize_pending_queue_wakes(&self, conn_id: u64) {
820 let Some(pending) = self.inner.pending_queue_wakes.write().remove(&conn_id) else {
821 return;
822 };
823 for (scope, queue) in pending {
824 self.inner.queue_wait_registry.notify(&scope, &queue);
825 }
826 }
827
828 pub(crate) fn discard_pending_queue_wakes(&self, conn_id: u64) {
829 self.inner.pending_queue_wakes.write().remove(&conn_id);
830 }
831
832 pub(crate) fn release_pending_claim_locks(&self, conn_id: u64) {
833 self.inner
834 .pending_claim_locks
835 .write()
836 .retain(|_, owner| *owner != conn_id);
837 }
838
839 pub(crate) fn finalize_pending_kv_watch_events(&self, conn_id: u64) {
840 let Some(pending) = self.inner.pending_kv_watch_events.write().remove(&conn_id) else {
841 return;
842 };
843 for (_, event) in pending {
844 self.cdc_emit_kv(
845 event.op,
846 &event.collection,
847 &event.key,
848 0,
849 event.before,
850 event.after,
851 );
852 }
853 }
854
855 pub(crate) fn discard_pending_kv_watch_events(&self, conn_id: u64) {
856 self.inner.pending_kv_watch_events.write().remove(&conn_id);
857 }
858
859 pub(crate) fn stamp_xmin_if_in_txn(
874 &self,
875 collection: &str,
876 id: crate::storage::unified::entity::EntityId,
877 ) {
878 let Some(xid) = self.current_xid() else {
879 return;
880 };
881 let store = self.inner.db.store();
882 let Some(manager) = store.get_collection(collection) else {
883 return;
884 };
885 if let Some(mut entity) = manager.get(id) {
886 entity.set_xmin(xid);
887 let _ = manager.update(entity);
888 }
889 }
890
891 pub(crate) fn revive_tombstones_since(&self, conn_id: u64, stamper_xid: u64) -> usize {
899 let mut guard = self.inner.pending_tombstones.write();
900 let Some(pending) = guard.get_mut(&conn_id) else {
901 return 0;
902 };
903
904 let store = self.inner.db.store();
905 let mut revived = 0usize;
906 pending.retain(|(collection, id, xid, previous_xmax)| {
907 if *xid < stamper_xid {
908 return true;
910 }
911 if let Some(manager) = store.get_collection(collection) {
912 if let Some(mut entity) = manager.get(*id) {
913 if entity.xmax == *xid {
914 entity.set_xmax(*previous_xmax);
915 let _ = manager.update(entity);
916 revived += 1;
917 }
918 }
919 }
920 false
921 });
922 if pending.is_empty() {
923 guard.remove(&conn_id);
924 }
925 revived
926 }
927
928 pub fn current_snapshot(&self) -> crate::storage::transaction::snapshot::Snapshot {
940 self.inner
941 .transaction_state
942 .current_snapshot(current_connection_id())
943 }
944
945 pub fn current_xid(&self) -> Option<crate::storage::transaction::snapshot::Xid> {
955 self.inner
956 .transaction_state
957 .writer_xid(current_connection_id())
958 }
959
960 pub(crate) fn own_transaction_xids(&self) -> std::collections::HashSet<u64> {
961 self.inner
962 .transaction_state
963 .own_xids(current_connection_id())
964 }
965
966 pub fn connection_in_transaction(&self, conn_id: u64) -> bool {
973 self.inner.transaction_state.in_transaction(conn_id)
974 }
975
976 pub fn snapshot_manager(&self) -> Arc<crate::storage::transaction::snapshot::SnapshotManager> {
979 self.inner.transaction_state.snapshot_manager()
980 }
981
982 pub(crate) fn mvcc_vacuum_cutoff_xid(&self) -> crate::storage::transaction::snapshot::Xid {
983 let manager = self.inner.transaction_state.snapshot_manager();
984 let next_xid = manager.peek_next_xid();
985 let mut cutoff = next_xid;
986 if let Some(oldest_required) = manager.oldest_required_xid() {
987 cutoff = cutoff.min(oldest_required);
988 }
989 let retention_xids = self.config_u64("runtime.mvcc.vacuum_retention_xids", 0);
990 if retention_xids > 0 {
991 cutoff = cutoff.min(next_xid.saturating_sub(retention_xids));
992 }
993 cutoff
994 }
995
996 pub fn current_txn_own_xids(
1001 &self,
1002 ) -> std::collections::HashSet<crate::storage::transaction::snapshot::Xid> {
1003 self.inner
1004 .transaction_state
1005 .own_xids(current_connection_id())
1006 }
1007}