Commit 97f5a62
committed
Locking phase 3 — SNAPSHOT isolation + READ_COMMITTED_SNAPSHOT (MVCC). New per-database flags
Database.AllowSnapshotIsolation / ReadCommittedSnapshot (default OFF) flipped via ALTER DATABASE … SET (ALLOW_SNAPSHOT_ISOLATION | READ_COMMITTED_SNAPSHOT) { ON | OFF } — bare-name / CURRENT / bracketed forms all parse through the refactored TryParseAlterDatabaseSet switch over three contextual keywords. New Database.AllocateTransactionCommitId() monotonic per-database counter stamps committed versions; readers under SI/RCSI capture their snapshot via CurrentTransactionCommitId. Per-table HeapTable.RowVersions dict holds RowVersionChain { LiveXmin, WriterTx?, IsDeletedLive, Head }; HistoricalVersion carries Payload / Xmin / Xmax / Next for the chain walk. New Heap.ReadSlotBytes / HeapPage.ReadSlotBytes expose raw slot bytes (tombstoned slots included) for pre-mutation snapshotting. Writer-side capture lives in VersionStore.CaptureWrite(batch, table, newRid, oldRid?, oldPayload?, kind) — wired at INSERT (Simulation.Insert.cs after the post-Heap.Insert row-X), UPDATE (Simulation.Update.cs:CommitUpdate via oldBytesPerAffected captured before the delete loop, then captured per row after the re-insert), and DELETE (Simulation.Delete.cs:CommitDelete via Heap.ReadSlotBytes immediately before each DeleteAt). UPDATE eagerly attaches a HistoricalVersion { Xmax = PendingXmax } to the new chain's Head so concurrent SI readers see the pre-write payload before the writer commits; Commit replaces PendingXmax with the real commit Xid, drops the abandoned old-slot chain, stamps LiveXmin. INSERT stamps LiveXmin at commit; DELETE pushes the historical payload + sets IsDeletedLive at commit. Pending entries live on SimulatedDbTransaction.PendingVersionEntries for explicit txs (drained by Commit via VersionStore.FinalizePendingEntries / discarded by Rollback + dispose via DiscardPendingEntries) and on BatchContext.CurrentStatementVersionEntries for auto-commit DML; RunMutation's statement-atomic mid-execution failure also unwinds entries added since versionEntriesMarker. Reader visibility plugs into BatchContext.WrapWithRowConflictChecks via the new ResolveSnapshotXidForRead(table) — returns tx.SnapshotXid (lazy at first user-table read) for SI sessions, BatchContext.RcsiStatementSnapshotXid (lazy at first user-table read this statement, cleared between statements at DispatchOneStatement's top) for default-RC sessions when RCSI is on, null otherwise; the per-row payload routes through VersionStore.ResolveVisibleVersion which returns the live payload / a historical payload / or null (skip — inserted-after-snapshot). SimulatedDbConnection.BeginDbTransaction(IsolationLevel) now overrides SessionIsolationLevel for the tx's duration (SimulatedDbTransaction.PreviousSessionIsolationLevel + OverrodeSessionIsolation carry the save / restore on Commit / Rollback / dispose). Msg 3952 verbatim wording (Snapshot isolation transaction failed accessing database '<db>' because snapshot isolation is not allowed in this database. Use ALTER DATABASE to allow snapshot isolation. Cls 16 St 1) — probe-confirmed fires at first user-table access in an SI session with ASI=OFF; SET TRANSACTION ISOLATION LEVEL SNAPSHOT is silent, system-catalog reads bypass the gate, both reads + writes trigger it. Msg 3960 verbatim wording (Snapshot isolation transaction aborted due to update conflict. You cannot use snapshot isolation to access table '<schema>.<table>' directly or indirectly in database '<db>' to update, delete, or insert the row that has been modified or deleted by another transaction. Retry the transaction or change the isolation level for the update/delete statement. Cls 16 St 2) — VersionStore.CheckSnapshotUpdateConflict at the top of CommitUpdate / CommitDelete; auto-rolls back the SI tx before throwing (probe-confirmed @@TRANCOUNT = 0 in CATCH). Two new error factories on SimulatedSqlException.ConcurrencyErrors.cs, two new contextual keywords (Allow_Snapshot_Isolation, Read_Committed_Snapshot). 14 new SnapshotIsolationTests: ALTER DATABASE flips (bare / CURRENT / bracketed); Msg 3952 verbatim at user-table access; system-catalog access bypass; Msg 3960 verbatim with auto-rollback; Msg 3960 for SI DELETE on RC-updated row; SI reader sees prior committed version after concurrent UPDATE; multi-update prior-committed visibility; SI snapshot baseline refresh after commit; RCSI reader sees committed value without blocking on uncommitted writer; RCSI per-statement snapshot (second statement sees the new committed value, distinct from SI). Known limitations documented in docs/claude/locking.md's new "Snapshot isolation + MVCC (phase 3)" section: DELETE-not-visible-to-SI (heap iteration skips tombstoned slots so SI readers miss rows their snapshot would see); SI-writer-on-RC-deleted-row Msg 3960 (same root cause); intra-tx multi-update history collapse; no version-store GC; no sys.dm_tran_version_store DMV. Real SQL Server's WITH NO_WAIT / WITH ROLLBACK IMMEDIATE termination options on versioning-state changes (probe-confirmed Msg 5083 rejection) aren't modeled — the simulator's flip takes effect immediately, skipping the brief-stabilization-wait + single-connection-required gates. CLAUDE.md "Not modeled" rewritten: phase 3 ships, phase 4+ deferrals tightened to key-range locks proper, ALTER SCHEMA TRANSFER Sch-M, row-lock cleanup on DELETE, and SI-visible DELETE.1 parent d145adc commit 97f5a62
18 files changed
Lines changed: 1169 additions & 10 deletions
File tree
- SqlServerSimulator.Tests
- SqlServerSimulator
- Errors
- Parser
- Simulation
- Storage
- docs/claude
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
157 | | - | |
| 157 | + | |
158 | 158 | | |
159 | 159 | | |
160 | 160 | | |
161 | 161 | | |
162 | | - | |
| 162 | + | |
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
| |||
0 commit comments