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.tx_contexts.read().contains_key(&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.tx_contexts.read().contains_key(&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.inner.snapshot_manager.is_aborted(xid)
212 && !self.inner.snapshot_manager.is_active(xid)
213 && (xid > snapshot.xid || snapshot.in_progress.contains(&xid))
214 }
215
216 fn conflict_error(
217 collection: &str,
218 logical_id: crate::storage::unified::entity::EntityId,
219 xid: crate::storage::transaction::snapshot::Xid,
220 ) -> RedDBError {
221 RedDBError::Query(format!(
222 "serialization conflict: table row {collection}/{} was modified by concurrent transaction {xid}",
223 logical_id.raw()
224 ))
225 }
226
227 fn check_logical_row_conflict(
228 &self,
229 collection: &str,
230 logical_id: crate::storage::unified::entity::EntityId,
231 excluded_ids: &[crate::storage::unified::entity::EntityId],
232 snapshot: &crate::storage::transaction::snapshot::Snapshot,
233 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
234 ) -> RedDBResult<()> {
235 let store = self.inner.db.store();
236 let Some(manager) = store.get_collection(collection) else {
237 return Ok(());
238 };
239
240 for candidate in manager.query_all(|_| true) {
241 if excluded_ids.contains(&candidate.id) || candidate.logical_id() != logical_id {
242 continue;
243 }
244 if self.xid_conflicts_with_snapshot(candidate.xmin, snapshot, own_xids) {
245 return Err(Self::conflict_error(collection, logical_id, candidate.xmin));
246 }
247 if self.xid_conflicts_with_snapshot(candidate.xmax, snapshot, own_xids) {
248 return Err(Self::conflict_error(collection, logical_id, candidate.xmax));
249 }
250 }
251 Ok(())
252 }
253
254 pub(crate) fn check_table_row_write_conflicts(
255 &self,
256 conn_id: u64,
257 snapshot: &crate::storage::transaction::snapshot::Snapshot,
258 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
259 isolation: crate::storage::transaction::IsolationLevel,
260 ) -> RedDBResult<()> {
261 let versioned_updates = self
262 .inner
263 .pending_versioned_updates
264 .read()
265 .get(&conn_id)
266 .cloned()
267 .unwrap_or_default();
268 let tombstones = self
269 .inner
270 .pending_tombstones
271 .read()
272 .get(&conn_id)
273 .cloned()
274 .unwrap_or_default();
275
276 let mut serializable_write_set = std::collections::HashSet::new();
277 let store = self.inner.db.store();
278 for (collection, old_id, new_id, xid, previous_xmax) in versioned_updates {
279 let Some(manager) = store.get_collection(&collection) else {
280 continue;
281 };
282 let Some(old) = manager.get(old_id) else {
283 continue;
284 };
285 let logical_id = old.logical_id();
286 serializable_write_set.insert((collection.clone(), logical_id));
287 if self.xid_conflicts_with_snapshot(previous_xmax, snapshot, own_xids) {
288 return Err(Self::conflict_error(&collection, logical_id, previous_xmax));
289 }
290 if old.xmax != xid && self.xid_conflicts_with_snapshot(old.xmax, snapshot, own_xids) {
291 return Err(Self::conflict_error(&collection, logical_id, old.xmax));
292 }
293 self.check_logical_row_conflict(
294 &collection,
295 logical_id,
296 &[old_id, new_id],
297 snapshot,
298 own_xids,
299 )?;
300 }
301
302 for (collection, id, xid, previous_xmax) in tombstones {
303 let Some(manager) = store.get_collection(&collection) else {
304 continue;
305 };
306 let Some(entity) = manager.get(id) else {
307 continue;
308 };
309 let logical_id = entity.logical_id();
310 serializable_write_set.insert((collection.clone(), logical_id));
311 if self.xid_conflicts_with_snapshot(previous_xmax, snapshot, own_xids) {
312 return Err(Self::conflict_error(&collection, logical_id, previous_xmax));
313 }
314 if entity.xmax != xid
315 && self.xid_conflicts_with_snapshot(entity.xmax, snapshot, own_xids)
316 {
317 return Err(Self::conflict_error(&collection, logical_id, entity.xmax));
318 }
319 self.check_logical_row_conflict(&collection, logical_id, &[id], snapshot, own_xids)?;
320 }
321
322 if isolation == crate::storage::transaction::IsolationLevel::Serializable
323 && self
324 .inner
325 .snapshot_manager
326 .serializable_commit_would_be_dangerous(
327 *own_xids.iter().min().unwrap_or(&0),
328 &serializable_write_set,
329 )
330 {
331 return Err(RedDBError::Query(
332 "serialization conflict: serializable transaction would complete rw-antidependency dangerous structure".to_string(),
333 ));
334 }
335
336 Ok(())
337 }
338
339 pub(crate) fn check_queue_dedup_write_conflicts(
340 &self,
341 conn_id: u64,
342 snapshot: &crate::storage::transaction::snapshot::Snapshot,
343 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
344 ) -> RedDBResult<()> {
345 let pending = self
346 .inner
347 .pending_queue_dedup
348 .read()
349 .get(&conn_id)
350 .cloned()
351 .unwrap_or_default();
352 if pending.is_empty() {
353 return Ok(());
354 }
355
356 let store = self.inner.db.store();
357 let Some(manager) = store.get_collection(crate::runtime::impl_queue::QUEUE_META_COLLECTION)
358 else {
359 return Ok(());
360 };
361
362 for (queue, dedup_key, metadata_id) in pending {
363 for candidate in manager.query_all(|entity| {
364 entity.data.as_row().is_some_and(|row| {
365 crate::runtime::impl_queue::row_text(row, "kind").as_deref()
366 == Some(crate::runtime::impl_queue::KIND_QUEUE_PUSH_DEDUP)
367 && crate::runtime::impl_queue::row_text(row, "queue").as_deref()
368 == Some(&queue)
369 && crate::runtime::impl_queue::row_text(row, "dedup_key").as_deref()
370 == Some(&dedup_key)
371 })
372 }) {
373 if candidate.id == metadata_id
374 || self.inner.snapshot_manager.is_aborted(candidate.xmin)
375 {
376 continue;
377 }
378 if self.xid_conflicts_with_snapshot(candidate.xmin, snapshot, own_xids) {
379 return Err(RedDBError::Query(format!(
380 "serialization conflict: queue dedup key {queue}/{dedup_key} was modified by concurrent transaction {}",
381 candidate.xmin
382 )));
383 }
384 }
385 }
386 Ok(())
387 }
388
389 pub(crate) fn restore_pending_write_stamps(&self, conn_id: u64) {
390 let versioned_updates = self
391 .inner
392 .pending_versioned_updates
393 .read()
394 .get(&conn_id)
395 .cloned()
396 .unwrap_or_default();
397 let tombstones = self
398 .inner
399 .pending_tombstones
400 .read()
401 .get(&conn_id)
402 .cloned()
403 .unwrap_or_default();
404
405 let store = self.inner.db.store();
406 for (collection, old_id, _new_id, xid, _previous_xmax) in versioned_updates {
407 if let Some(manager) = store.get_collection(&collection) {
408 if let Some(mut entity) = manager.get(old_id) {
409 entity.set_xmax(xid);
410 let _ = manager.update(entity);
411 }
412 }
413 }
414 for (collection, id, xid, _previous_xmax) in tombstones {
415 if let Some(manager) = store.get_collection(&collection) {
416 if let Some(mut entity) = manager.get(id) {
417 entity.set_xmax(xid);
418 let _ = manager.update(entity);
419 }
420 }
421 }
422 }
423
424 pub(crate) fn finalize_pending_versioned_updates(&self, conn_id: u64) {
425 self.inner
426 .pending_versioned_updates
427 .write()
428 .remove(&conn_id);
429 }
430
431 pub(crate) fn finalize_pending_queue_dedup(&self, conn_id: u64) {
432 self.inner.pending_queue_dedup.write().remove(&conn_id);
433 }
434
435 pub(crate) fn discard_pending_queue_dedup(&self, conn_id: u64) {
436 let Some(pending) = self.inner.pending_queue_dedup.write().remove(&conn_id) else {
437 return;
438 };
439 let store = self.inner.db.store();
440 for (_queue, _dedup_key, metadata_id) in pending {
441 let _ = store.delete(
442 crate::runtime::impl_queue::QUEUE_META_COLLECTION,
443 metadata_id,
444 );
445 }
446 }
447
448 pub(crate) fn revive_pending_versioned_updates(&self, conn_id: u64) {
449 let Some(pending) = self
450 .inner
451 .pending_versioned_updates
452 .write()
453 .remove(&conn_id)
454 else {
455 return;
456 };
457
458 let store = self.inner.db.store();
459 for (collection, old_id, new_id, xid, previous_xmax) in pending {
460 if let Some(manager) = store.get_collection(&collection) {
461 if let Some(mut old) = manager.get(old_id) {
462 if old.xmax == xid {
463 old.set_xmax(previous_xmax);
464 let _ = manager.update(old);
465 }
466 }
467 }
468 let _ = store.delete_batch(&collection, &[new_id]);
469 }
470 }
471
472 pub(crate) fn finalize_pending_tombstones(&self, conn_id: u64) {
477 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
478 return;
479 };
480 if pending.is_empty() {
481 return;
482 }
483
484 let store = self.inner.db.store();
485 for (collection, id, _xid, _previous_xmax) in pending {
486 store.context_index().remove_entity(id);
487 self.cdc_emit(
488 crate::replication::cdc::ChangeOperation::Delete,
489 &collection,
490 id.raw(),
491 "entity",
492 );
493 }
494 }
495
496 pub(crate) fn revive_pending_tombstones(&self, conn_id: u64) {
503 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
504 return;
505 };
506
507 let store = self.inner.db.store();
508 for (collection, id, xid, previous_xmax) in pending {
509 let Some(manager) = store.get_collection(&collection) else {
510 continue;
511 };
512 if let Some(mut entity) = manager.get(id) {
513 if entity.xmax == xid {
514 entity.set_xmax(previous_xmax);
515 let _ = manager.update(entity);
516 }
517 }
518 }
519 }
520
521 pub fn queue_wait_registry(
523 &self,
524 ) -> std::sync::Arc<crate::runtime::queue_wait_registry::QueueWaitRegistry> {
525 self.inner.queue_wait_registry.clone()
526 }
527
528 pub(crate) fn record_queue_wake(&self, scope: &str, queue: &str) {
533 if self.current_xid().is_some() {
534 let conn_id = current_connection_id();
535 self.inner
536 .pending_queue_wakes
537 .write()
538 .entry(conn_id)
539 .or_default()
540 .push((scope.to_string(), queue.to_string()));
541 return;
542 }
543 self.inner.queue_wait_registry.notify(scope, queue);
544 }
545
546 pub(crate) fn finalize_pending_queue_wakes(&self, conn_id: u64) {
547 let Some(pending) = self.inner.pending_queue_wakes.write().remove(&conn_id) else {
548 return;
549 };
550 for (scope, queue) in pending {
551 self.inner.queue_wait_registry.notify(&scope, &queue);
552 }
553 }
554
555 pub(crate) fn discard_pending_queue_wakes(&self, conn_id: u64) {
556 self.inner.pending_queue_wakes.write().remove(&conn_id);
557 }
558
559 pub(crate) fn release_pending_claim_locks(&self, conn_id: u64) {
560 self.inner
561 .pending_claim_locks
562 .write()
563 .retain(|_, owner| *owner != conn_id);
564 }
565
566 pub(crate) fn finalize_pending_kv_watch_events(&self, conn_id: u64) {
567 let Some(pending) = self.inner.pending_kv_watch_events.write().remove(&conn_id) else {
568 return;
569 };
570 for event in pending {
571 self.cdc_emit_kv(
572 event.op,
573 &event.collection,
574 &event.key,
575 0,
576 event.before,
577 event.after,
578 );
579 }
580 }
581
582 pub(crate) fn discard_pending_kv_watch_events(&self, conn_id: u64) {
583 self.inner.pending_kv_watch_events.write().remove(&conn_id);
584 }
585
586 pub(crate) fn stamp_xmin_if_in_txn(
601 &self,
602 collection: &str,
603 id: crate::storage::unified::entity::EntityId,
604 ) {
605 let Some(xid) = self.current_xid() else {
606 return;
607 };
608 let store = self.inner.db.store();
609 let Some(manager) = store.get_collection(collection) else {
610 return;
611 };
612 if let Some(mut entity) = manager.get(id) {
613 entity.set_xmin(xid);
614 let _ = manager.update(entity);
615 }
616 }
617
618 pub(crate) fn revive_tombstones_since(&self, conn_id: u64, stamper_xid: u64) -> usize {
626 let mut guard = self.inner.pending_tombstones.write();
627 let Some(pending) = guard.get_mut(&conn_id) else {
628 return 0;
629 };
630
631 let store = self.inner.db.store();
632 let mut revived = 0usize;
633 pending.retain(|(collection, id, xid, previous_xmax)| {
634 if *xid < stamper_xid {
635 return true;
637 }
638 if let Some(manager) = store.get_collection(collection) {
639 if let Some(mut entity) = manager.get(*id) {
640 if entity.xmax == *xid {
641 entity.set_xmax(*previous_xmax);
642 let _ = manager.update(entity);
643 revived += 1;
644 }
645 }
646 }
647 false
648 });
649 if pending.is_empty() {
650 guard.remove(&conn_id);
651 }
652 revived
653 }
654
655 pub fn current_snapshot(&self) -> crate::storage::transaction::snapshot::Snapshot {
667 let conn_id = current_connection_id();
668 if let Some(ctx) = self.inner.tx_contexts.read().get(&conn_id).cloned() {
669 if ctx.isolation == crate::storage::transaction::IsolationLevel::ReadCommitted {
670 let high_water = self.inner.snapshot_manager.peek_next_xid();
671 return self.inner.snapshot_manager.snapshot(high_water);
672 }
673 return ctx.snapshot;
674 }
675 let high_water = self.inner.snapshot_manager.peek_next_xid();
681 self.inner.snapshot_manager.snapshot(high_water)
682 }
683
684 pub fn current_xid(&self) -> Option<crate::storage::transaction::snapshot::Xid> {
694 let conn_id = current_connection_id();
695 self.inner
696 .tx_contexts
697 .read()
698 .get(&conn_id)
699 .map(|ctx| ctx.writer_xid())
700 }
701
702 pub fn connection_in_transaction(&self, conn_id: u64) -> bool {
709 self.inner.tx_contexts.read().contains_key(&conn_id)
710 }
711
712 pub fn snapshot_manager(&self) -> Arc<crate::storage::transaction::snapshot::SnapshotManager> {
715 Arc::clone(&self.inner.snapshot_manager)
716 }
717
718 pub(crate) fn mvcc_vacuum_cutoff_xid(&self) -> crate::storage::transaction::snapshot::Xid {
719 let manager = &self.inner.snapshot_manager;
720 let next_xid = manager.peek_next_xid();
721 let mut cutoff = next_xid;
722 if let Some(oldest_active) = manager.oldest_active_xid() {
723 cutoff = cutoff.min(oldest_active);
724 }
725 if let Some(oldest_pinned) = manager.oldest_pinned_xid() {
726 cutoff = cutoff.min(oldest_pinned);
727 }
728 let retention_xids = self.config_u64("runtime.mvcc.vacuum_retention_xids", 0);
729 if retention_xids > 0 {
730 cutoff = cutoff.min(next_xid.saturating_sub(retention_xids));
731 }
732 cutoff
733 }
734
735 pub fn current_txn_own_xids(
740 &self,
741 ) -> std::collections::HashSet<crate::storage::transaction::snapshot::Xid> {
742 let mut set = std::collections::HashSet::new();
743 if let Some(ctx) = self.inner.tx_contexts.read().get(¤t_connection_id()) {
744 set.insert(ctx.xid);
745 for (_, sub) in &ctx.savepoints {
746 set.insert(*sub);
747 }
748 for sub in &ctx.released_sub_xids {
749 set.insert(*sub);
750 }
751 }
752 set
753 }
754}