reddb_server/runtime/
table_row_mvcc_resolver.rs1use crate::storage::unified::entity::{EntityId, EntityKind, UnifiedEntity};
2use crate::storage::unified::UnifiedStore;
3
4#[derive(Clone)]
9pub(crate) struct TableRowMvccReadResolver {
10 snapshot: TableRowReadSnapshot,
11}
12
13#[derive(Clone)]
14enum TableRowReadSnapshot {
15 CurrentThread,
16 Captured(Option<super::impl_core::SnapshotContext>),
17}
18
19impl TableRowMvccReadResolver {
20 pub(crate) fn current_statement() -> Self {
21 Self {
22 snapshot: TableRowReadSnapshot::CurrentThread,
23 }
24 }
25
26 pub(crate) fn captured(snapshot: Option<super::impl_core::SnapshotContext>) -> Self {
27 Self {
28 snapshot: TableRowReadSnapshot::Captured(snapshot),
29 }
30 }
31
32 pub(crate) fn resolve_candidate<'a>(
33 &self,
34 candidate: &'a UnifiedEntity,
35 ) -> Option<&'a UnifiedEntity> {
36 if !matches!(candidate.kind, EntityKind::TableRow { .. }) {
37 return None;
38 }
39 if self.visible(candidate) {
40 Some(candidate)
41 } else {
42 None
43 }
44 }
45
46 pub(crate) fn resolve_read_candidate<'a>(
47 &self,
48 candidate: &'a UnifiedEntity,
49 ) -> Option<&'a UnifiedEntity> {
50 if matches!(candidate.kind, EntityKind::TableRow { .. }) {
51 return self.resolve_candidate(candidate);
52 }
53 if self.visible(candidate) {
54 Some(candidate)
55 } else {
56 None
57 }
58 }
59
60 pub(crate) fn resolve_logical_id(
61 &self,
62 store: &UnifiedStore,
63 collection: &str,
64 logical_id: EntityId,
65 ) -> Option<UnifiedEntity> {
66 reddb_engine::transaction::read::resolve_logical_id(
67 store,
68 collection,
69 logical_id,
70 self.needs_version_selection(),
71 |entity| self.resolve_candidate(entity).is_some(),
72 )
73 }
74
75 fn needs_version_selection(&self) -> bool {
76 match &self.snapshot {
77 TableRowReadSnapshot::CurrentThread => {
78 super::impl_core::capture_current_snapshot().is_some()
79 }
80 TableRowReadSnapshot::Captured(snapshot) => snapshot.is_some(),
81 }
82 }
83
84 fn visible(&self, candidate: &UnifiedEntity) -> bool {
85 match &self.snapshot {
86 TableRowReadSnapshot::CurrentThread => {
87 super::impl_core::entity_visible_under_current_snapshot(candidate)
88 }
89 TableRowReadSnapshot::Captured(snapshot) => {
90 super::impl_core::entity_visible_with_context(snapshot.as_ref(), candidate)
91 }
92 }
93 }
94}
95
96impl reddb_engine::transaction::read::TableRowVersionStore for UnifiedStore {
97 fn table_row_versions_by_logical_id(
98 &self,
99 collection: &str,
100 logical_id: EntityId,
101 ) -> Vec<UnifiedEntity> {
102 UnifiedStore::table_row_versions_by_logical_id(self, collection, logical_id)
103 }
104
105 fn get_table_row_by_logical_id(
106 &self,
107 collection: &str,
108 logical_id: EntityId,
109 ) -> Option<UnifiedEntity> {
110 UnifiedStore::get_table_row_by_logical_id(self, collection, logical_id)
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use std::collections::HashSet;
117 use std::sync::Arc;
118
119 use super::*;
120 use crate::storage::transaction::snapshot::{Snapshot, SnapshotManager};
121 use crate::storage::unified::entity::{EntityId, UnifiedEntity};
122 use reddb_types::Value;
123
124 fn row_with_xids(xmin: u64, xmax: u64) -> UnifiedEntity {
125 let mut row =
126 UnifiedEntity::table_row(EntityId::new(1), "accounts", 1, vec![Value::Integer(1)]);
127 row.set_xmin(xmin);
128 row.set_xmax(xmax);
129 row
130 }
131
132 fn snapshot_context(xid: u64) -> super::super::impl_core::SnapshotContext {
133 super::super::impl_core::SnapshotContext {
134 snapshot: Snapshot {
135 xid,
136 in_progress: HashSet::new(),
137 },
138 manager: Arc::new(SnapshotManager::new()),
139 own_xids: HashSet::new(),
140 requires_index_fallback: false,
141 serializable_reader: None,
142 }
143 }
144
145 #[test]
146 fn current_row_fallback_keeps_live_legacy_rows_visible() {
147 super::super::impl_core::clear_current_snapshot();
148 let resolver = TableRowMvccReadResolver::current_statement();
149 let row = row_with_xids(0, 0);
150
151 assert!(resolver.resolve_candidate(&row).is_some());
152 }
153
154 #[test]
155 fn current_row_fallback_hides_tombstoned_rows() {
156 super::super::impl_core::clear_current_snapshot();
157 let resolver = TableRowMvccReadResolver::current_statement();
158 let row = row_with_xids(0, 4);
159
160 assert!(resolver.resolve_candidate(&row).is_none());
161 }
162
163 #[test]
164 fn captured_snapshot_applies_xmin_and_xmax_visibility() {
165 let resolver = TableRowMvccReadResolver::captured(Some(snapshot_context(10)));
166
167 assert!(resolver.resolve_candidate(&row_with_xids(5, 0)).is_some());
168 assert!(resolver.resolve_candidate(&row_with_xids(11, 0)).is_none());
169 assert!(resolver.resolve_candidate(&row_with_xids(5, 9)).is_none());
170 assert!(resolver.resolve_candidate(&row_with_xids(5, 11)).is_some());
171 }
172
173 #[test]
174 fn read_candidate_uses_same_snapshot_gate_for_table_rows() {
175 let resolver = TableRowMvccReadResolver::captured(Some(snapshot_context(10)));
176
177 assert!(resolver
178 .resolve_read_candidate(&row_with_xids(5, 11))
179 .is_some());
180 assert!(resolver
181 .resolve_read_candidate(&row_with_xids(11, 0))
182 .is_none());
183 }
184}