reddb_engine/transaction/
read.rs1use std::sync::Arc;
4
5#[derive(Clone)]
20pub struct SnapshotContext {
21 pub branch: Option<Arc<BranchReadView>>,
22 pub snapshot: super::snapshot::Snapshot,
23 pub manager: Arc<super::snapshot::SnapshotManager>,
24 pub own_xids: std::collections::HashSet<super::snapshot::Xid>,
25 pub requires_index_fallback: bool,
26 pub serializable_reader: Option<super::snapshot::Xid>,
27}
28
29#[derive(Clone, Debug)]
32pub struct BranchReadView {
33 pub writer_epoch: u64,
34 pub epochs: std::collections::BTreeMap<u64, super::snapshot::Snapshot>,
35}
36
37pub fn entity_reserves_unique_key(
40 ctx: &SnapshotContext,
41 entity: &crate::storage::entity::UnifiedEntity,
42) -> bool {
43 let (Some(view), Some(version)) = (&ctx.branch, entity.branch_version()) else {
44 return ctx
45 .manager
46 .row_reserves_unique_key(entity.xmin, entity.xmax, &ctx.own_xids);
47 };
48 let inserted = if version.origin == view.writer_epoch {
49 ctx.manager
50 .row_reserves_unique_key(entity.xmin, 0, &ctx.own_xids)
51 } else {
52 view.epochs.get(&version.origin).is_some_and(|snapshot| {
53 !ctx.manager.is_aborted(entity.xmin) && snapshot.sees(entity.xmin, 0)
54 })
55 };
56 if !inserted {
57 return false;
58 }
59 let deleted = |epoch, xid| {
60 if xid == 0 {
61 return false;
62 }
63 if epoch == view.writer_epoch {
64 !ctx.manager.row_reserves_unique_key(0, xid, &ctx.own_xids)
65 } else {
66 view.epochs
67 .get(&epoch)
68 .is_some_and(|snapshot| !ctx.manager.is_aborted(xid) && snapshot.sees(xid, 0))
69 }
70 };
71 !deleted(version.origin, entity.xmax)
72 && !version
73 .deletions
74 .iter()
75 .any(|&(epoch, xid)| deleted(epoch, xid))
76}
77
78pub fn entity_visibility_check(
79 ctx: &SnapshotContext,
80 entity: &crate::storage::entity::UnifiedEntity,
81) -> bool {
82 let (Some(view), Some(version)) = (&ctx.branch, entity.branch_version()) else {
83 return visibility_check(ctx, entity.xmin, entity.xmax);
84 };
85 let visible_xid = |epoch, xid| {
86 view.epochs.get(&epoch).is_some_and(|snapshot| {
87 xid == 0
88 || (!ctx.manager.is_aborted(xid)
89 && (ctx.own_xids.contains(&xid) || snapshot.sees(xid, 0)))
90 })
91 };
92 visible_xid(version.origin, entity.xmin)
93 && (entity.xmax == 0 || !visible_xid(version.origin, entity.xmax))
94 && !version
95 .deletions
96 .iter()
97 .any(|&(epoch, xid)| visible_xid(epoch, xid))
98}
99
100#[cfg(test)]
101mod branch_tests {
102 use super::*;
103 use crate::storage::entity::{EntityId, UnifiedEntity};
104 use crate::transaction::snapshot::{Snapshot, SnapshotManager};
105 use std::collections::{BTreeMap, HashSet};
106
107 fn context(epoch: u64, inherited: u64) -> SnapshotContext {
108 let mut epochs = BTreeMap::from([(
109 0,
110 Snapshot {
111 xid: inherited,
112 in_progress: HashSet::new(),
113 },
114 )]);
115 epochs.insert(
116 epoch,
117 Snapshot {
118 xid: 100,
119 in_progress: HashSet::new(),
120 },
121 );
122 SnapshotContext {
123 branch: Some(Arc::new(BranchReadView {
124 writer_epoch: epoch,
125 epochs,
126 })),
127 snapshot: Snapshot {
128 xid: 100,
129 in_progress: HashSet::new(),
130 },
131 manager: Arc::new(SnapshotManager::new()),
132 own_xids: HashSet::new(),
133 requires_index_fallback: true,
134 serializable_reader: None,
135 }
136 }
137
138 #[test]
139 fn sibling_updates_and_deletes_have_independent_visibility() {
140 let mut base = UnifiedEntity::table_row(EntityId::new(1), "items", 1, vec![]);
141 base.set_xmin(10);
142 base.set_branch_origin(0);
143 base.set_branch_xmax(0, 30);
144 base.set_branch_xmax(1, 40);
145 let main = context(0, 100);
146 let first = context(1, 20);
147 let second = context(2, 20);
148 assert!(!entity_visibility_check(&main, &base));
149 assert!(!entity_visibility_check(&first, &base));
150 assert!(entity_visibility_check(&second, &base));
151 base.set_branch_xmax(2, 50);
152 base.set_branch_xmax(1, 0);
153 assert!(
154 entity_visibility_check(&first, &base),
155 "rollback only revives its branch"
156 );
157 assert!(!entity_visibility_check(&second, &base));
158 let mut replacement = base.clone();
159 replacement.set_branch_origin(1);
160 replacement.set_xmin(40);
161 replacement.set_xmax(0);
162 assert!(entity_visibility_check(&first, &replacement));
163 assert!(!entity_visibility_check(&main, &replacement));
164 assert!(!entity_visibility_check(&second, &replacement));
165 }
166
167 #[test]
168 fn active_and_aborted_branch_deletes_remain_invisible() {
169 let mut entity = UnifiedEntity::table_row(EntityId::new(1), "items", 1, vec![]);
170 entity.set_branch_origin(0);
171 entity.set_xmin(10);
172 entity.set_branch_xmax(1, 40);
173 let mut reader = context(1, 20);
174 Arc::make_mut(reader.branch.as_mut().expect("view"))
175 .epochs
176 .get_mut(&1)
177 .expect("writer epoch")
178 .in_progress
179 .insert(40);
180 assert!(entity_visibility_check(&reader, &entity));
181 reader.own_xids.insert(40);
182 assert!(!entity_visibility_check(&reader, &entity));
183 reader.manager.rollback(40);
184 assert!(entity_visibility_check(&reader, &entity));
185 }
186}
187
188pub fn record_serializable_read(
191 ctx: &SnapshotContext,
192 entity: &crate::storage::entity::UnifiedEntity,
193) {
194 let Some(reader) = ctx.serializable_reader else {
195 return;
196 };
197 if !matches!(
198 &entity.kind,
199 crate::storage::entity::EntityKind::TableRow { .. }
200 ) {
201 return;
202 }
203 ctx.manager
204 .record_serializable_read(reader, entity.kind.collection(), entity.logical_id());
205}
206
207#[inline]
210pub fn visibility_check(ctx: &SnapshotContext, xmin: u64, xmax: u64) -> bool {
211 if xmin != 0 && ctx.manager.is_aborted(xmin) {
215 return false;
216 }
217 let effective_xmax = if xmax != 0 && ctx.manager.is_aborted(xmax) {
219 0
220 } else {
221 xmax
222 };
223 let own_xmin = xmin != 0 && ctx.own_xids.contains(&xmin);
227 let own_xmax = effective_xmax != 0 && ctx.own_xids.contains(&effective_xmax);
228 if own_xmax {
229 return false;
231 }
232 if own_xmin {
233 return true;
234 }
235 ctx.snapshot.sees(xmin, effective_xmax)
236}
237
238pub trait TableRowVersionStore {
241 fn table_row_versions_by_logical_id(
242 &self,
243 collection: &str,
244 logical_id: crate::storage::entity::EntityId,
245 ) -> Vec<crate::storage::entity::UnifiedEntity>;
246
247 fn get_table_row_by_logical_id(
248 &self,
249 collection: &str,
250 logical_id: crate::storage::entity::EntityId,
251 ) -> Option<crate::storage::entity::UnifiedEntity>;
252}
253
254pub fn resolve_logical_id(
258 store: &impl TableRowVersionStore,
259 collection: &str,
260 logical_id: crate::storage::entity::EntityId,
261 select_versions: bool,
262 mut visible: impl FnMut(&crate::storage::entity::UnifiedEntity) -> bool,
263) -> Option<crate::storage::entity::UnifiedEntity> {
264 if select_versions {
265 return store
266 .table_row_versions_by_logical_id(collection, logical_id)
267 .into_iter()
268 .filter(|entity| visible(entity))
269 .max_by_key(|entity| entity.xmin);
270 }
271 store
272 .get_table_row_by_logical_id(collection, logical_id)
273 .filter(|entity| visible(entity))
274}
275
276pub trait FallibleTableRowVersionStore {
279 type Error;
280
281 fn try_get_table_row_by_logical_id(
282 &self,
283 collection: &str,
284 logical_id: crate::storage::entity::EntityId,
285 ) -> Result<Option<crate::storage::entity::UnifiedEntity>, Self::Error>;
286
287 fn try_for_each_table_row_version(
288 &self,
289 collection: &str,
290 logical_id: crate::storage::entity::EntityId,
291 visit: impl FnMut(&crate::storage::entity::UnifiedEntity) -> bool,
292 ) -> Result<(), Self::Error>;
293}
294
295pub fn try_resolve_logical_id<S: FallibleTableRowVersionStore>(
297 store: &S,
298 collection: &str,
299 logical_id: crate::storage::entity::EntityId,
300 select_versions: bool,
301 mut visible: impl FnMut(&crate::storage::entity::UnifiedEntity) -> bool,
302) -> Result<Option<crate::storage::entity::UnifiedEntity>, S::Error> {
303 if !select_versions {
304 return store
305 .try_get_table_row_by_logical_id(collection, logical_id)
306 .map(|entity| entity.filter(|entity| visible(entity)));
307 }
308 let mut selected: Option<crate::storage::entity::UnifiedEntity> = None;
309 store.try_for_each_table_row_version(collection, logical_id, |entity| {
310 if visible(entity) && selected.as_ref().is_none_or(|old| entity.xmin >= old.xmin) {
311 selected = Some(entity.clone());
312 }
313 true
314 })?;
315 Ok(selected)
316}