1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
//! MVCC (Multi-Version Concurrency Control) snapshot machinery.
//!
//! # Phase 1b of #5136
//!
//! This module is the second of four sequential PRs decomposing MVCC
//! Phase 1 from the umbrella issue #5136:
//!
//! - **Phase 1a** ([PR #5142]) — added `xmin`/`xmax` fields to [`Row`] and
//! the v6→v7 persistence shim. Inert at the executor level.
//! - **Phase 1b** (this PR, #5149) — introduces [`TxnSnapshot`] and the
//! [`Row::visible_to`] predicate. Snapshot capture is wired into
//! `TransactionState::Active`, but **nothing reads it yet**. The
//! predicate is pure-function and has no callers in this PR; Phase 1c
//! and Phase 1d wire up the writer and reader respectively.
//! - **Phase 1c** (#5150, future) — write-path `xmin`/`xmax` stamping on
//! INSERT/UPDATE/DELETE.
//! - **Phase 1d** (#5151, future) — read-path visibility filtering on
//! SELECT scans; cross-snapshot FK deferred-replay coordination; flips
//! the `mvcc_enabled` feature flag on.
//!
//! # API surface (kept minimal on purpose)
//!
//! - [`TxnSnapshot`] — a captured view of which transactions are
//! committed-as-of-then. Plain data, `Clone + Debug`.
//! - [`TxnSnapshot::empty`] — the "everything pre-MVCC is visible, nothing
//! else is" snapshot used by non-transactional reads and as the default
//! in transactions that haven't yet captured one.
//! - [`Row::visible_to`] — `&self, &TxnSnapshot -> bool`. The single
//! visibility predicate.
//!
//! Phase 1c/1d should not need to expand this surface — they only need to
//! call `Row::visible_to(snapshot)` from the scan boundary and to pass the
//! `TransactionState::Active.snapshot` field into reads.
//!
//! # Visibility predicate contract
//!
//! A row `r` with `(r.xmin, r.xmax)` is **visible to** a snapshot `s`
//! iff all three of these hold:
//!
//! 1. `r.xmin <= s.xmax_committed` — the row's creator committed before
//! our snapshot was taken (or `r.xmin` is the pre-MVCC sentinel `0`,
//! which is `<= s.xmax_committed` for any non-zero snapshot).
//! 2. `r.xmin` is **not** in `s.in_progress` — the row's creator was
//! still mid-flight when our snapshot was taken, so under snapshot
//! isolation we don't see it.
//! 3. Either `r.xmax` is `None` (still live), OR `r.xmax > s.xmin_active`
//! (deletion happened after the oldest still-running txn at snapshot
//! time, so it can't have been committed before our snapshot), OR
//! `r.xmax` is in `s.in_progress` (the deleter was still mid-flight,
//! so the delete isn't yet visible to us).
//!
//! The third clause is intentionally optimistic: it errs on the side of
//! **showing** rows that *might* have been deleted by a concurrent
//! transaction. Snapshot isolation forbids the opposite (hiding rows
//! that were definitely live at snapshot time), but allows showing rows
//! that have been concurrently deleted. Phase 1c's commit path is what
//! resolves the "definitely committed" status; this predicate operates
//! purely on the snapshot data it was given.
//!
//! # Abort handling
//!
//! This phase does **not** model transaction abort/rollback in the
//! snapshot data. A snapshot only carries information about
//! committed-or-still-running transactions. If a transaction aborts,
//! Phase 1c is responsible for either:
//!
//! - Reverting the `xmin`/`xmax` stamps it made (current `rollback_transaction`
//! already restores `original_tables` from snapshot, which discards
//! any stamping), OR
//! - Adding an `aborted: HashSet<TxnId>` field to [`TxnSnapshot`] and
//! extending the predicate with an "ignore writes from aborted txns"
//! clause.
//!
//! The current snapshot-isolation semantics — combined with
//! `TransactionManager::rollback_transaction` restoring full table
//! snapshots — make the "revert on abort" path the simpler choice; Phase
//! 1c will document its final decision.
//!
//! [PR #5142]: https://github.com/rjwalters/vibesql/pull/5142
//! [`Row`]: crate::Row
//! [`Row::visible_to`]: crate::Row::visible_to
use HashSet;
use crateTxnId;
/// A point-in-time snapshot of the set of transactions visible to a
/// reader under snapshot isolation.
///
/// A `TxnSnapshot` is captured **once per transaction at `BEGIN`** and
/// held for the lifetime of that transaction (see
/// [`TransactionState::Active`]). All reads within the transaction
/// consult the same snapshot, so a transaction sees a stable view of the
/// database regardless of concurrent commits.
///
/// # Fields
///
/// - [`xmin_active`](Self::xmin_active) — the lowest [`TxnId`] that was
/// **still running** at snapshot time. Any row whose `xmax > xmin_active`
/// was deleted by a transaction that started after our snapshot's
/// oldest concurrent peer, and thus may not yet have been committed
/// when we took our snapshot.
/// - [`xmax_committed`](Self::xmax_committed) — the largest [`TxnId`]
/// that had **already committed** at snapshot time. Rows whose
/// `xmin > xmax_committed` were definitely created after our snapshot
/// and are invisible.
/// - [`in_progress`](Self::in_progress) — the set of transactions that
/// were still running at snapshot time. Writes by these transactions
/// are invisible to us regardless of where their `TxnId` falls
/// relative to `xmin_active` / `xmax_committed`.
///
/// # Phase 1b note
///
/// This struct is intentionally `Clone` (cheap modulo the `HashSet`) so
/// `TransactionState::Active` can hold it by value without lifetime
/// gymnastics. The `in_progress` set is typically small (one per
/// concurrent active transaction); in single-writer workloads it is
/// usually empty.
///
/// [`TxnId`]: crate::row::TxnId
/// [`TransactionState::Active`]: crate::database::TransactionState
// ============================================================================
// Write-path stamping helpers (Phase 1c of #5136)
// ============================================================================
//
// These helpers are the single source of truth for "should this write stamp
// xmin/xmax with the current transaction id?". The decision is gated on the
// `mvcc_enabled` feature flag:
//
// - Feature OFF (default): all helpers are no-ops. Rows pass through with
// their constructor defaults (xmin = PRE_MVCC_TXN_ID, xmax = None). This
// preserves bit-for-bit pre-MVCC behavior.
// - Feature ON: when called with `Some(txn_id)`, the helpers stamp the
// row with the active transaction id. When called with `None` (autocommit
// path with no active transaction), the row keeps the pre-MVCC sentinel.
// Phase 1d will revisit autocommit semantics; this is the conservative
// choice for Phase 1c.
//
// The helpers operate by `&mut Row` so the caller can choose where they
// fit into the write pipeline (validators may run before or after
// stamping; the stamp is purely additive to existing row state).
use crateRow;
/// Stamp `row.xmin = txn_id` (when MVCC is enabled and a transaction is
/// active). For INSERT / UPDATE-new-version writes.
///
/// With the `mvcc_enabled` feature OFF this is a no-op — the row keeps
/// whatever `xmin` it came in with, which for executor-produced rows is
/// always [`PRE_MVCC_TXN_ID`](crate::row::PRE_MVCC_TXN_ID).
///
/// With the feature ON and `txn_id = Some(t)`, the row's xmin field is
/// set to `t`. With the feature ON and `txn_id = None` (no active
/// transaction at write time — e.g. autocommit) the row is left with
/// the pre-MVCC sentinel; Phase 1d will revisit autocommit semantics.
/// Stamp `row.xmax = Some(txn_id)` (when MVCC is enabled and a
/// transaction is active). For UPDATE-old-version and DELETE writes.
///
/// With the `mvcc_enabled` feature OFF this is a no-op — the row keeps
/// `xmax = None`, matching the pre-MVCC contract that "live rows have no
/// deleter."
///
/// With the feature ON and `txn_id = Some(t)`, the row's xmax field is
/// set to `Some(t)`. The physical storage layer continues to remove or
/// overwrite the row as before; the stamp is purely additive metadata
/// that Phase 1d's visibility filter will consult. With the feature ON
/// and `txn_id = None` (autocommit), the row is left with `xmax = None`.
/// Returns true when the `mvcc_enabled` feature is compiled in.
///
/// This is a compile-time constant exposed as a runtime fn so callers
/// outside this crate can branch on feature state without re-declaring
/// the `#[cfg]` themselves.
pub const