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 with_deferred_store_wal_if_transaction<T>(
70 &self,
71 f: impl FnOnce() -> RedDBResult<T>,
72 ) -> RedDBResult<T> {
73 let conn_id = current_connection_id();
74 if !self.inner.tx_contexts.read().contains_key(&conn_id) {
75 return f();
76 }
77
78 crate::storage::UnifiedStore::begin_deferred_store_wal_capture();
79 let result = f();
80 let captured = crate::storage::UnifiedStore::take_deferred_store_wal_capture();
81 match result {
82 Ok(value) => {
83 self.record_pending_store_wal_actions(conn_id, captured);
84 Ok(value)
85 }
86 Err(err) => Err(err),
87 }
88 }
89
90 pub(crate) fn with_deferred_store_wal_for_dml<T>(
91 &self,
92 capture_autocommit_events: bool,
93 f: impl FnOnce() -> RedDBResult<T>,
94 ) -> RedDBResult<T> {
95 let conn_id = current_connection_id();
96 if self.inner.tx_contexts.read().contains_key(&conn_id) {
97 return self.with_deferred_store_wal_if_transaction(f);
98 }
99 if !capture_autocommit_events {
100 return f();
101 }
102
103 crate::storage::UnifiedStore::begin_deferred_store_wal_capture();
104 let result = f();
105 let captured = crate::storage::UnifiedStore::take_deferred_store_wal_capture();
106 self.inner
107 .db
108 .store()
109 .append_deferred_store_wal_actions(captured)
110 .map_err(|err| RedDBError::Internal(err.to_string()))?;
111 result
112 }
113
114 pub(crate) fn insert_may_emit_events(&self, query: &InsertQuery) -> bool {
115 !query.suppress_events
116 && self.collection_has_event_subscriptions_for_operation(
117 &query.table,
118 crate::catalog::SubscriptionOperation::Insert,
119 )
120 }
121
122 pub(crate) fn update_may_emit_events(&self, query: &UpdateQuery) -> bool {
123 !query.suppress_events
124 && self.collection_has_event_subscriptions_for_operation(
125 &query.table,
126 crate::catalog::SubscriptionOperation::Update,
127 )
128 }
129
130 pub(crate) fn delete_may_emit_events(&self, query: &DeleteQuery) -> bool {
131 !query.suppress_events
132 && self.collection_has_event_subscriptions_for_operation(
133 &query.table,
134 crate::catalog::SubscriptionOperation::Delete,
135 )
136 }
137
138 fn collection_has_event_subscriptions_for_operation(
139 &self,
140 collection: &str,
141 operation: crate::catalog::SubscriptionOperation,
142 ) -> bool {
143 let Some(contract) = self.db().collection_contract_arc(collection) else {
144 return false;
145 };
146 contract.subscriptions.iter().any(|subscription| {
147 subscription.enabled
148 && (subscription.ops_filter.is_empty()
149 || subscription.ops_filter.contains(&operation))
150 })
151 }
152
153 fn record_pending_store_wal_actions(
154 &self,
155 conn_id: u64,
156 actions: crate::storage::unified::DeferredStoreWalActions,
157 ) {
158 if actions.is_empty() {
159 return;
160 }
161 let mut guard = self.inner.pending_store_wal_actions.write();
162 guard.entry(conn_id).or_default().extend(actions);
163 }
164
165 pub(crate) fn flush_pending_store_wal_actions(&self, conn_id: u64) -> RedDBResult<()> {
166 let Some(actions) = self
167 .inner
168 .pending_store_wal_actions
169 .write()
170 .remove(&conn_id)
171 else {
172 return Ok(());
173 };
174 self.inner
175 .db
176 .store()
177 .append_deferred_store_wal_actions(actions)
178 .map_err(|err| RedDBError::Internal(err.to_string()))
179 }
180
181 pub(crate) fn discard_pending_store_wal_actions(&self, conn_id: u64) {
182 self.inner
183 .pending_store_wal_actions
184 .write()
185 .remove(&conn_id);
186 }
187
188 fn xid_conflicts_with_snapshot(
189 &self,
190 xid: crate::storage::transaction::snapshot::Xid,
191 snapshot: &crate::storage::transaction::snapshot::Snapshot,
192 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
193 ) -> bool {
194 xid != 0
195 && !own_xids.contains(&xid)
196 && !self.inner.snapshot_manager.is_aborted(xid)
197 && !self.inner.snapshot_manager.is_active(xid)
198 && (xid > snapshot.xid || snapshot.in_progress.contains(&xid))
199 }
200
201 fn conflict_error(
202 collection: &str,
203 logical_id: crate::storage::unified::entity::EntityId,
204 xid: crate::storage::transaction::snapshot::Xid,
205 ) -> RedDBError {
206 RedDBError::Query(format!(
207 "serialization conflict: table row {collection}/{} was modified by concurrent transaction {xid}",
208 logical_id.raw()
209 ))
210 }
211
212 fn check_logical_row_conflict(
213 &self,
214 collection: &str,
215 logical_id: crate::storage::unified::entity::EntityId,
216 excluded_ids: &[crate::storage::unified::entity::EntityId],
217 snapshot: &crate::storage::transaction::snapshot::Snapshot,
218 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
219 ) -> RedDBResult<()> {
220 let store = self.inner.db.store();
221 let Some(manager) = store.get_collection(collection) else {
222 return Ok(());
223 };
224
225 for candidate in manager.query_all(|_| true) {
226 if excluded_ids.contains(&candidate.id) || candidate.logical_id() != logical_id {
227 continue;
228 }
229 if self.xid_conflicts_with_snapshot(candidate.xmin, snapshot, own_xids) {
230 return Err(Self::conflict_error(collection, logical_id, candidate.xmin));
231 }
232 if self.xid_conflicts_with_snapshot(candidate.xmax, snapshot, own_xids) {
233 return Err(Self::conflict_error(collection, logical_id, candidate.xmax));
234 }
235 }
236 Ok(())
237 }
238
239 pub(crate) fn check_table_row_write_conflicts(
240 &self,
241 conn_id: u64,
242 snapshot: &crate::storage::transaction::snapshot::Snapshot,
243 own_xids: &std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
244 isolation: crate::storage::transaction::IsolationLevel,
245 ) -> RedDBResult<()> {
246 let versioned_updates = self
247 .inner
248 .pending_versioned_updates
249 .read()
250 .get(&conn_id)
251 .cloned()
252 .unwrap_or_default();
253 let tombstones = self
254 .inner
255 .pending_tombstones
256 .read()
257 .get(&conn_id)
258 .cloned()
259 .unwrap_or_default();
260
261 let mut serializable_write_set = std::collections::HashSet::new();
262 let store = self.inner.db.store();
263 for (collection, old_id, new_id, xid, previous_xmax) in versioned_updates {
264 let Some(manager) = store.get_collection(&collection) else {
265 continue;
266 };
267 let Some(old) = manager.get(old_id) else {
268 continue;
269 };
270 let logical_id = old.logical_id();
271 serializable_write_set.insert((collection.clone(), logical_id));
272 if self.xid_conflicts_with_snapshot(previous_xmax, snapshot, own_xids) {
273 return Err(Self::conflict_error(&collection, logical_id, previous_xmax));
274 }
275 if old.xmax != xid && self.xid_conflicts_with_snapshot(old.xmax, snapshot, own_xids) {
276 return Err(Self::conflict_error(&collection, logical_id, old.xmax));
277 }
278 self.check_logical_row_conflict(
279 &collection,
280 logical_id,
281 &[old_id, new_id],
282 snapshot,
283 own_xids,
284 )?;
285 }
286
287 for (collection, id, xid, previous_xmax) in tombstones {
288 let Some(manager) = store.get_collection(&collection) else {
289 continue;
290 };
291 let Some(entity) = manager.get(id) else {
292 continue;
293 };
294 let logical_id = entity.logical_id();
295 serializable_write_set.insert((collection.clone(), logical_id));
296 if self.xid_conflicts_with_snapshot(previous_xmax, snapshot, own_xids) {
297 return Err(Self::conflict_error(&collection, logical_id, previous_xmax));
298 }
299 if entity.xmax != xid
300 && self.xid_conflicts_with_snapshot(entity.xmax, snapshot, own_xids)
301 {
302 return Err(Self::conflict_error(&collection, logical_id, entity.xmax));
303 }
304 self.check_logical_row_conflict(&collection, logical_id, &[id], snapshot, own_xids)?;
305 }
306
307 if isolation == crate::storage::transaction::IsolationLevel::Serializable
308 && self
309 .inner
310 .snapshot_manager
311 .serializable_commit_would_be_dangerous(
312 *own_xids.iter().min().unwrap_or(&0),
313 &serializable_write_set,
314 )
315 {
316 return Err(RedDBError::Query(
317 "serialization conflict: serializable transaction would complete rw-antidependency dangerous structure".to_string(),
318 ));
319 }
320
321 Ok(())
322 }
323
324 pub(crate) fn restore_pending_write_stamps(&self, conn_id: u64) {
325 let versioned_updates = self
326 .inner
327 .pending_versioned_updates
328 .read()
329 .get(&conn_id)
330 .cloned()
331 .unwrap_or_default();
332 let tombstones = self
333 .inner
334 .pending_tombstones
335 .read()
336 .get(&conn_id)
337 .cloned()
338 .unwrap_or_default();
339
340 let store = self.inner.db.store();
341 for (collection, old_id, _new_id, xid, _previous_xmax) in versioned_updates {
342 if let Some(manager) = store.get_collection(&collection) {
343 if let Some(mut entity) = manager.get(old_id) {
344 entity.set_xmax(xid);
345 let _ = manager.update(entity);
346 }
347 }
348 }
349 for (collection, id, xid, _previous_xmax) in tombstones {
350 if let Some(manager) = store.get_collection(&collection) {
351 if let Some(mut entity) = manager.get(id) {
352 entity.set_xmax(xid);
353 let _ = manager.update(entity);
354 }
355 }
356 }
357 }
358
359 pub(crate) fn finalize_pending_versioned_updates(&self, conn_id: u64) {
360 self.inner
361 .pending_versioned_updates
362 .write()
363 .remove(&conn_id);
364 }
365
366 pub(crate) fn revive_pending_versioned_updates(&self, conn_id: u64) {
367 let Some(pending) = self
368 .inner
369 .pending_versioned_updates
370 .write()
371 .remove(&conn_id)
372 else {
373 return;
374 };
375
376 let store = self.inner.db.store();
377 for (collection, old_id, new_id, xid, previous_xmax) in pending {
378 if let Some(manager) = store.get_collection(&collection) {
379 if let Some(mut old) = manager.get(old_id) {
380 if old.xmax == xid {
381 old.set_xmax(previous_xmax);
382 let _ = manager.update(old);
383 }
384 }
385 }
386 let _ = store.delete_batch(&collection, &[new_id]);
387 }
388 }
389
390 pub(crate) fn finalize_pending_tombstones(&self, conn_id: u64) {
395 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
396 return;
397 };
398 if pending.is_empty() {
399 return;
400 }
401
402 let store = self.inner.db.store();
403 for (collection, id, _xid, _previous_xmax) in pending {
404 store.context_index().remove_entity(id);
405 self.cdc_emit(
406 crate::replication::cdc::ChangeOperation::Delete,
407 &collection,
408 id.raw(),
409 "entity",
410 );
411 }
412 }
413
414 pub(crate) fn revive_pending_tombstones(&self, conn_id: u64) {
421 let Some(pending) = self.inner.pending_tombstones.write().remove(&conn_id) else {
422 return;
423 };
424
425 let store = self.inner.db.store();
426 for (collection, id, xid, previous_xmax) in pending {
427 let Some(manager) = store.get_collection(&collection) else {
428 continue;
429 };
430 if let Some(mut entity) = manager.get(id) {
431 if entity.xmax == xid {
432 entity.set_xmax(previous_xmax);
433 let _ = manager.update(entity);
434 }
435 }
436 }
437 }
438
439 pub fn queue_wait_registry(
441 &self,
442 ) -> std::sync::Arc<crate::runtime::queue_wait_registry::QueueWaitRegistry> {
443 self.inner.queue_wait_registry.clone()
444 }
445
446 pub(crate) fn record_queue_wake(&self, scope: &str, queue: &str) {
451 if self.current_xid().is_some() {
452 let conn_id = current_connection_id();
453 self.inner
454 .pending_queue_wakes
455 .write()
456 .entry(conn_id)
457 .or_default()
458 .push((scope.to_string(), queue.to_string()));
459 return;
460 }
461 self.inner.queue_wait_registry.notify(scope, queue);
462 }
463
464 pub(crate) fn finalize_pending_queue_wakes(&self, conn_id: u64) {
465 let Some(pending) = self.inner.pending_queue_wakes.write().remove(&conn_id) else {
466 return;
467 };
468 for (scope, queue) in pending {
469 self.inner.queue_wait_registry.notify(&scope, &queue);
470 }
471 }
472
473 pub(crate) fn discard_pending_queue_wakes(&self, conn_id: u64) {
474 self.inner.pending_queue_wakes.write().remove(&conn_id);
475 }
476
477 pub(crate) fn release_pending_claim_locks(&self, conn_id: u64) {
478 self.inner
479 .pending_claim_locks
480 .write()
481 .retain(|_, owner| *owner != conn_id);
482 }
483
484 pub(crate) fn finalize_pending_kv_watch_events(&self, conn_id: u64) {
485 let Some(pending) = self.inner.pending_kv_watch_events.write().remove(&conn_id) else {
486 return;
487 };
488 for event in pending {
489 self.cdc_emit_kv(
490 event.op,
491 &event.collection,
492 &event.key,
493 0,
494 event.before,
495 event.after,
496 );
497 }
498 }
499
500 pub(crate) fn discard_pending_kv_watch_events(&self, conn_id: u64) {
501 self.inner.pending_kv_watch_events.write().remove(&conn_id);
502 }
503
504 pub(crate) fn stamp_xmin_if_in_txn(
519 &self,
520 collection: &str,
521 id: crate::storage::unified::entity::EntityId,
522 ) {
523 let Some(xid) = self.current_xid() else {
524 return;
525 };
526 let store = self.inner.db.store();
527 let Some(manager) = store.get_collection(collection) else {
528 return;
529 };
530 if let Some(mut entity) = manager.get(id) {
531 entity.set_xmin(xid);
532 let _ = manager.update(entity);
533 }
534 }
535
536 pub(crate) fn revive_tombstones_since(&self, conn_id: u64, stamper_xid: u64) -> usize {
544 let mut guard = self.inner.pending_tombstones.write();
545 let Some(pending) = guard.get_mut(&conn_id) else {
546 return 0;
547 };
548
549 let store = self.inner.db.store();
550 let mut revived = 0usize;
551 pending.retain(|(collection, id, xid, previous_xmax)| {
552 if *xid < stamper_xid {
553 return true;
555 }
556 if let Some(manager) = store.get_collection(collection) {
557 if let Some(mut entity) = manager.get(*id) {
558 if entity.xmax == *xid {
559 entity.set_xmax(*previous_xmax);
560 let _ = manager.update(entity);
561 revived += 1;
562 }
563 }
564 }
565 false
566 });
567 if pending.is_empty() {
568 guard.remove(&conn_id);
569 }
570 revived
571 }
572
573 pub fn current_snapshot(&self) -> crate::storage::transaction::snapshot::Snapshot {
585 let conn_id = current_connection_id();
586 if let Some(ctx) = self.inner.tx_contexts.read().get(&conn_id).cloned() {
587 if ctx.isolation == crate::storage::transaction::IsolationLevel::ReadCommitted {
588 let high_water = self.inner.snapshot_manager.peek_next_xid();
589 return self.inner.snapshot_manager.snapshot(high_water);
590 }
591 return ctx.snapshot;
592 }
593 let high_water = self.inner.snapshot_manager.peek_next_xid();
599 self.inner.snapshot_manager.snapshot(high_water)
600 }
601
602 pub fn current_xid(&self) -> Option<crate::storage::transaction::snapshot::Xid> {
612 let conn_id = current_connection_id();
613 self.inner
614 .tx_contexts
615 .read()
616 .get(&conn_id)
617 .map(|ctx| ctx.writer_xid())
618 }
619
620 pub fn connection_in_transaction(&self, conn_id: u64) -> bool {
627 self.inner.tx_contexts.read().contains_key(&conn_id)
628 }
629
630 pub fn snapshot_manager(&self) -> Arc<crate::storage::transaction::snapshot::SnapshotManager> {
633 Arc::clone(&self.inner.snapshot_manager)
634 }
635
636 pub(crate) fn mvcc_vacuum_cutoff_xid(&self) -> crate::storage::transaction::snapshot::Xid {
637 let manager = &self.inner.snapshot_manager;
638 let next_xid = manager.peek_next_xid();
639 let mut cutoff = next_xid;
640 if let Some(oldest_active) = manager.oldest_active_xid() {
641 cutoff = cutoff.min(oldest_active);
642 }
643 if let Some(oldest_pinned) = manager.oldest_pinned_xid() {
644 cutoff = cutoff.min(oldest_pinned);
645 }
646 let retention_xids = self.config_u64("runtime.mvcc.vacuum_retention_xids", 0);
647 if retention_xids > 0 {
648 cutoff = cutoff.min(next_xid.saturating_sub(retention_xids));
649 }
650 cutoff
651 }
652
653 pub fn current_txn_own_xids(
658 &self,
659 ) -> std::collections::HashSet<crate::storage::transaction::snapshot::Xid> {
660 let mut set = std::collections::HashSet::new();
661 if let Some(ctx) = self.inner.tx_contexts.read().get(¤t_connection_id()) {
662 set.insert(ctx.xid);
663 for (_, sub) in &ctx.savepoints {
664 set.insert(*sub);
665 }
666 for sub in &ctx.released_sub_xids {
667 set.insert(*sub);
668 }
669 }
670 set
671 }
672}