You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -128,7 +128,7 @@ Three entry points share one per-connection undo log: implicit (statement-level
128
128
-`MERGE INTO target USING <source> ON … WHEN [NOT] MATCHED [BY TARGET|SOURCE] [AND …] THEN UPDATE|DELETE|INSERT … [OUTPUT $action, …]` — source = VALUES / SELECT / set-op / bare-table; multiple per-family AND-conditioned clauses; trailing `;` required. Msg 5324 / 8672 / 10713 / 10714 enforced. Triggers fire one INSERT → UPDATE → DELETE pass with combined affected rows. See [`docs/claude/dml.md`](docs/claude/dml.md).
129
129
130
130
### Cursors
131
-
Session-scoped T-SQL cursors: `DECLARE <name> [INSENSITIVE][SCROLL] CURSOR [LOCAL|GLOBAL][FORWARD_ONLY|SCROLL][STATIC|KEYSET|DYNAMIC|FAST_FORWARD][READ_ONLY|…] FOR <select> [FOR {READ ONLY|UPDATE [OF cols]}]`, `OPEN`, `FETCH NEXT|PRIOR|FIRST|LAST|ABSOLUTE n|RELATIVE n [FROM] <cursor> [INTO @v,…]`, `CLOSE`, `DEALLOCATE`, plus `@@FETCH_STATUS` / `@@CURSOR_ROWS` / `CURSOR_STATUS` and positioned `WHERE CURRENT OF` UPDATE/DELETE. Effective sensitivity resolves at DECLARE: non-updatable query (not a single base table with a PK/UNIQUE) → STATIC; STATIC frozen-snapshot, KEYSET frozen-membership with live value re-reads (deleted member → `@@FETCH_STATUS=-2`), DYNAMIC fully live (skips deletes, sees inserts, `@@CURSOR_ROWS=-1`). Position tracked by the logical unique key (not a RID — UPDATE relocates rows). Errors Msg 16905/16915/16916/16917/16924/16925/16929/16931 probe-confirmed verbatim. Cursor variables (`DECLARE @c CURSOR`) raise `NotSupportedException`. See [`docs/claude/cursors.md`](docs/claude/cursors.md).
131
+
Session-scoped T-SQL cursors: `DECLARE <name> [INSENSITIVE][SCROLL] CURSOR [LOCAL|GLOBAL][FORWARD_ONLY|SCROLL][STATIC|KEYSET|DYNAMIC|FAST_FORWARD][READ_ONLY|…] FOR <select> [FOR {READ ONLY|UPDATE [OF cols]}]`, `OPEN`, `FETCH NEXT|PRIOR|FIRST|LAST|ABSOLUTE n|RELATIVE n [FROM] <cursor> [INTO @v,…]`, `CLOSE`, `DEALLOCATE`, plus `@@FETCH_STATUS` / `@@CURSOR_ROWS` / `CURSOR_STATUS` and positioned `WHERE CURRENT OF` UPDATE/DELETE. Effective sensitivity resolves at DECLARE: non-updatable query (not a single base table) → STATIC; STATIC frozen-snapshot, KEYSET frozen-membership with live value re-reads (deleted-or-key-changed member → `@@FETCH_STATUS=-2`), DYNAMIC fully live (skips deletes, sees inserts, `@@CURSOR_ROWS=-1`). Position tracked by the row's stable `(page, slot)` heap address (preserved through UPDATEs via `Heap.UpdateAt`'s in-place / forwarding-pointer machinery); KEYSET additionally tracks the unique-key tuple when the base table has a PK/UNIQUE, so a unique-column UPDATE produces -2. Errors Msg 16905/16915/16916/16917/16924/16925/16929/16931 probe-confirmed verbatim. Cursor variables (`DECLARE @c CURSOR`) raise `NotSupportedException`. See [`docs/claude/cursors.md`](docs/claude/cursors.md).
132
132
133
133
### EF Core adapter coverage
134
134
`UseSqlServerSimulator(...)` covers seven SqlParameter-downcast pairs: `DateOnly→date`, `DateTime→date`, `DateTime→smalldatetime`, `TimeOnly→time(N)`, `TimeSpan→time(N)`, `decimal→money`, `decimal→smallmoney`. Without the adapter those mappings throw at SaveChanges. MAX-string family flows through plain `UseSqlServer`.
@@ -213,8 +213,8 @@ Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger:
213
213
-`decimal` / `numeric`: backed by .NET `decimal`. Values requiring more than 28 significant digits aren't modeled (declarations up through `decimal(38, *)` accepted so storage byte-width matches).
214
214
-`float` text formatting: .NET `G15`/`G7` rather than SQL Server's `1e+015`-style scientific.
215
215
- Auto-generated constraint names: PK / UNIQUE shape `PK__<table8>__<16hex>` / `UQ__<table8>__<16hex>` (16-hex 64-bit FNV-1a); CK / FK / DF shape `CK__<table8>__[<col8>__]<8hex>` (8-hex 32-bit FNV-1a). Both are deterministic across runs, distinct from SQL Server's object-id-derived hex (so won't byte-match).
216
-
-**DELETE / UPDATE leak page space**: deleted (or relocated) row payload bytes stay in their original page until process exit; only the slot is tombstoned. Slot directory entries also never reused.
217
-
-**DELETE / UPDATE leak LOB chains**: orphaned LOB chains stay in `Heap.LobPages`. Other rows reference LOB pages by stable index, so list compaction would corrupt them.
216
+
-**DELETE / forwarding UPDATE leak page space**: deleted row payload bytes stay in their original page until process exit; only the slot is tombstoned. Slot directory entries also never reused. An UPDATE whose new payload exceeds the slot's existing extent forwards (single-level pointer matching SQL Server's heap-update behavior) and similarly leaves the original slot's payload bytes dead. An UPDATE that fits in place reuses the slot's bytes and adds no leak.
217
+
-**UPDATE leaks LOB chains**: a UPDATE-replaced row's LOB chains stay in `Heap.LobPages`; the row encoder allocates fresh chains for the new payload regardless of whether the UPDATE rewrites in place or forwards. DELETE leaks similarly. Other rows reference LOB pages by stable index, so list compaction would corrupt them.
218
218
-**`GetBytes`/`GetChars` materialize, don't stream**: each call decodes the full column value via `RowDecoder` and slices into the caller's buffer. Behavior matches per-call observation; the streaming-memory guarantee doesn't.
219
219
-**`SELECT INTO` string `+` reads as nullable**: real SQL Server projects `cs + 'x'` (both NOT NULL) as NOT NULL; the simulator can't statically distinguish string-concat from integer-add at projection-schema time (the dispatch happens runtime on operand types), so all `Add` results read as nullable. Conservative; no test reliance on string-`+`-non-null.
220
220
-**`SELECT INTO` from a CTE drops identity + nullability**: CTE bindings synthesize their wrapper `HeapColumn` entries with `nullable: true` and no identity, so the analyzer treats CTE sources as derived plans. Real SQL Server preserves both through simple single-source CTEs. Fix requires propagating column metadata through CTE bindings.
@@ -230,4 +230,4 @@ Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger:
230
230
-**`STRING_SPLIT(..., ..., cast(@v as int))` — wrapped variable accepted**: the simulator's `enable_ordinal` const-only gate rejects bare `@v` (Msg 8748, matching real) but a Cast / Parenthesized wrapper around the variable slips past the gate. Real SQL Server rejects all variable-bearing shapes regardless of wrapping. No real-world emission hits this — the const-only restriction means `@v` ergonomics are weak in any form.
231
231
-**`OPENJSON``WITH ... AS JSON`** on `nvarchar(max)` raises `NotSupportedException` even though real SQL Server accepts the column form there (sub-tree extraction not modeled). Non-`nvarchar(max)` columns with `AS JSON` raise **Msg 13618**, matching real.
232
232
-**`FOR SYSTEM_TIME` qualified-name format**: real SQL Server pads temp-table names in Msg 13544 with their internal allocation suffix (`#x____...___…000000000148`); the simulator emits the bare `tempdb.dbo.#x` form. Same Msg number / framing, less verbose name.
233
-
-**Cursor edges**: cursors over a **JOIN / derived table / view** are forced to STATIC (read-only snapshot, `@@CURSOR_ROWS`=count); real SQL Server makes them DYNAMIC (`@@CURSOR_ROWS`=-1, sees mid-loop changes, `WHERE CURRENT OF` works) — the simulator only takes the updatable KEYSET/DYNAMIC path for a *direct single base table with a PK/UNIQUE* (set-op/UNION cursors are forced STATIC on the real server too, so those match). `@@CURSOR_ROWS` is `-1` throughout for DYNAMIC (real may report a transient positive count pre-fetch); a keyset `-2` (deleted-member) fetch leaves the `INTO` variables unchanged rather than zeroing/NULLing them; `FETCH` without `INTO` omits the trailing `ROWSTAT` column. See [`docs/claude/cursors.md`](docs/claude/cursors.md).
233
+
-**Cursor edges**: cursors over a **JOIN / derived table / view** are forced to STATIC (read-only snapshot, `@@CURSOR_ROWS`=count); real SQL Server makes them DYNAMIC (`@@CURSOR_ROWS`=-1, sees mid-loop changes, `WHERE CURRENT OF` works) — the simulator only takes the updatable KEYSET/DYNAMIC path for a *direct single base table* (set-op/UNION cursors are forced STATIC on the real server too, so those match). `@@CURSOR_ROWS` is `-1` throughout for DYNAMIC (real may report a transient positive count pre-fetch); a keyset `-2` (deleted-member) fetch leaves the `INTO` variables unchanged rather than zeroing/NULLing them; `FETCH` without `INTO` omits the trailing `ROWSTAT` column. See [`docs/claude/cursors.md`](docs/claude/cursors.md).
0 commit comments