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