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
+7-24Lines changed: 7 additions & 24 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -106,43 +106,24 @@ Six scopes, one home each. **Add new state to whichever class matches its true s
106
106
107
107
## What's modeled
108
108
109
-
The `*.Tests` and `*.Tests.EFCore` suites are the authoritative behavior contract. Two maps: the inline subsections below cover cross-cutting behaviors with **no dedicated deep-dive** (their canonical home); the [Feature reference](#feature-reference) index lists everything that **has** one — **presence there means it's modeled**; read on demand. Inline notes cover only probe-confirmed quirks, deviations, non-obvious rules.
110
-
111
-
### Subqueries
112
-
`EXISTS`/`NOT EXISTS` (multi-column inner OK); `expr [NOT] IN (SELECT …)` (single inner column, Msg 116); scalar `(SELECT col FROM …)` (single column, single-row Msg 512 per outer row, empty → typed NULL); `expr <op> {ANY|SOME|ALL} (SELECT col …)` quantified comparison, all six operators + T-SQL synonyms (`!=``!<``!>`), predicate-only (SELECT-list use → Msg 102); SOME aliases ANY. Three-valued: empty inner → ALL vacuously true / ANY vacuously false (independent of LHS NULL); a NULL either side taints to UNKNOWN. All forms correlate at arbitrary depth. Set ops (`UNION`/`UNION ALL`/`INTERSECT`/`EXCEPT`) are legal in every subquery context (via `Selection.Parse` → `ParseQueryExpression`), so EF Core 7+'s TPC shape (UNION ALL in a derived table) ships end-to-end.
113
-
114
-
### Constraints
115
-
-`CHECK`: inline single-column + table-level; Msg 547 per row on definitely-false predicate. Inline column-level CHECK may only reference its owning column — peer refs raise **Msg 8141** at CREATE TABLE (probe-confirmed). The walker is structural (`Expression.VisitColumnReferences` + `BooleanExpression.VisitOperandExpressions`); coverage spans the common containers (`Reference`, `Parenthesized`, `TwoSidedExpression`, `Cast`, `Length`) — peer refs in rarer ones (`DATEPART`, `SUBSTRING`, nested `CASE`) escape the CREATE check and surface at INSERT. Table-level CHECK has no peer restriction.
116
-
-`PRIMARY KEY` / `UNIQUE` / secondary `CREATE INDEX`: linear scan (O(N) per insert), no B-tree; reads and `UPDATE`/`DELETE`/`MERGE` target scans get **incrementally-maintained** per-`Heap` seek acceleration (equality / IN / leading-column range / equality-prefix+range continuation / ORDER BY elimination / keyset). Seek shapes, mutation/MERGE seeking, journal mechanics, decline rules, residual-WHERE invariant in [`indexes.md`](docs/claude/indexes.md).
117
-
-`FOREIGN KEY`: inline / table-level / named forms; all four referential actions on `ON DELETE`/`ON UPDATE`; enforced at INSERT/UPDATE/DELETE/MERGE; full `sys.foreign_keys` / `sys.foreign_key_columns`. Enforcement **seeks the shared `HeapSeekCache`** (live-byte verified, no residual WHERE). Referential-action, cascade-cycle, PK/UNIQUE-target, NULL-skip rules + Msg numbers in [`foreign-keys.md`](docs/claude/foreign-keys.md).
118
-
119
-
### Transactions
120
-
Three entry points share one per-connection undo log: implicit (statement atomicity), SqlClient API (`BeginTransaction()`/`Commit()`/`Rollback()`), SQL-text (`BEGIN`/`COMMIT`/`ROLLBACK`/`SAVE TRANSACTION`).
121
-
122
-
-**Statement-level atomicity**: a mutation throwing mid-execution rolls back its partial writes. Multi-row INSERT failing on row 3 leaves zero rows.
123
-
-**Explicit txs**: `BEGIN TRAN` increments `TranCount`; only outermost `COMMIT` commits; `ROLLBACK` zeroes `TranCount` and walks the whole log. `SAVE TRAN <name>` + `ROLLBACK TRAN <name>` is the EF SaveChanges path inside an explicit tx. Parallel `BeginTransaction` → `InvalidOperationException`. `COMMIT`/`ROLLBACK` with no active tx → Msg 3902/3903.
124
-
-`@@TRANCOUNT` reads connection depth as int.
125
-
-**Identity counters and the database-scoped rowversion counter bypass the log** — both advance through rollback. (A rolled-back INSERT's off-row LOB chain + heap bytes are reclaimed — rollback is terminal, so an uncommitted insert is invisible to every snapshot.)
126
-
-**Temp-table CREATE/DROP participates in the log** via `TempTableCreation` / `TempTableRemoval``UndoEntry` subtypes. Regular CREATE/DROP TABLE is NOT logged — asymmetry in [`temp-tables.md`](docs/claude/temp-tables.md).
127
-
- Locking + MVCC: full 8-mode matrix, row-X writers + row-mode readers per hints/iso, RR/SER/UPDLOCK/XLOCK/TABLOCK/HOLDLOCK/REPEATABLEREAD/NOLOCK/READPAST hints, escalation at 5000 row-locks, Msg 1205 deadlock / Msg 1222 timeout, SNAPSHOT + RCSI (version chains + GC + DMVs). See [`locking.md`](docs/claude/locking.md).
109
+
The `*.Tests` and `*.Tests.EFCore` suites are the authoritative behavior contract. The [Feature reference](#feature-reference) index maps every modeled area to its deep-dive — **presence there means it's modeled**; read the linked doc on demand when working in that area. The two small subsections below are inline-only notes with no deep-dive.
128
110
129
111
### EF Core adapter coverage
130
112
`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 these throw at SaveChanges. MAX-string family flows through plain `UseSqlServer`.
131
113
132
114
### `text` / `ntext` / `image` restrictions
133
115
Comparison (Msg 402), ORDER BY/DISTINCT (Msg 306), and aggregates (Msg 8117 from MAX/MIN) all enforced.
134
116
135
-
### `SimulatedDbDataReader`
136
-
Full `DbDataReader` contract. Typed accessors read `SqlValue` via the cursor indexer, unwrap via `As*` (no boxing); NULL → `SqlNullValueException` (SqlClient parity). `GetDateTime` covers Date/DateTime/SmallDateTime/DateTime2 (Date at midnight, `Kind=Unspecified`). A **`datetime` rounds to whole milliseconds at the ADO.NET boundary** (`DateTimeSqlType.RoundToClientMilliseconds`, in `GetDateTime` + `SqlValue.ToObject`, covering `GetValue`/`GetFieldValue`/output-param writeback) matching SqlClient's `.000`/`.003`/`.007`; the engine keeps full 1/300-second resolution internally, so only the client surface rounds. `GetDecimal` covers Decimal/Numeric/Money/SmallMoney; `GetFieldValue<T>` short-circuits EF's `DateOnly`-over-`Date` / `TimeOnly`-over-`Time`. `GetOrdinal` two-pass (case-sensitive then -insensitive, SqlClient precedence). `HasRows` sticky. `GetChar(int)` raises `InvalidCastException`.
137
-
138
117
### Feature reference
139
118
140
119
Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger: read the linked file on demand when working in the matching area.
-**`Selection`, aggregates, window functions, set ops, CASE, OFFSET/FETCH** → [`query.md`](docs/claude/query.md).
126
+
-**Subqueries** (EXISTS / IN(SELECT) / scalar / quantified ANY-SOME-ALL, three-valued rules, arbitrary-depth correlation, set ops in subquery contexts) → [`subqueries.md`](docs/claude/subqueries.md).
146
127
-**JOIN / APPLY** — INNER/LEFT/RIGHT/FULL/CROSS + CROSS/OUTER APPLY, ANSI-89 comma-FROM, EF `LeftJoin`/`RightJoin` routing (no LINQ `FullJoin`, so FULL is raw-SQL-only); `JoinDriver` equi-join hash fast path + `SqlValueKey` keying, nested-loop fallback, comma/`CROSS JOIN`+WHERE → equi-`INNER` rewrite, RIGHT/FULL materialization + derived-table-right, `JoinDiagnostics` guard → [`joins.md`](docs/claude/joins.md).
147
128
-**`PIVOT` / `UNPIVOT`** — PIVOT desugars to grouped conditional aggregation (implicit grouping = inner columns minus FOR + aggregate-arg), UNPIVOT is a NULL-skipping unfold; both attach as a postfix FROM-source wrapper on the derived-table `LateralPlan` seam → [`pivot.md`](docs/claude/pivot.md).
-**Per-`Simulation` plan cache** (single-SELECT `Selection` reuse keyed by text + db + param-type sig, `SchemaVersion`-stamped invalidation, inline-in-SELECT-arm promotion since the iterator's post-yield code is unreachable on a non-draining `ExecuteReader`; `VariableReference` Run-time slot lookup is the co-fix) → [`plan-cache.md`](docs/claude/plan-cache.md).
-**`hierarchyid` type** (incl. deferred byte-identical CAST research notes) → [`hierarchyid.md`](docs/claude/hierarchyid.md).
@@ -212,5 +196,4 @@ Cross-cutting divergences with no single feature-doc home; feature-specific quir
212
196
-`decimal` / `numeric`: backed by .NET `decimal`. Values needing more than 28 significant digits aren't modeled (declarations up through `decimal(38, *)` accepted so storage byte-width matches).
213
197
-`float` text formatting: .NET `G15`/`G7`, not SQL Server's `1e+015`-style scientific.
-**`GetBytes` / `GetChars` materialize, don't stream**: each call decodes the full column value via `RowDecoder` and slices into the caller's buffer. Per-call observation matches; the streaming-memory guarantee doesn't.
216
-
-**Reclaimed heap space is reused; page lists shrink only from the tail**: superseded rows + off-row LOB chains are freed and reused (`HeapPage.Compact` / `Heap.FreeLobChain`) — memory tracks the *peak concurrent* working set. Dead interior pages are reused in place, never removed from `Heap.Pages` (a reclaimed slot keeps a 2-byte zero-extent entry) — mid-list removal would break the stable `(page, slot)` addresses cursors, version Rids, and forward pointers depend on. `DBCC SHRINKDATABASE`/`SHRINKFILE` trim only the *trailing* run of dead/freed-LOB pages (`Heap.TrimTrailing*`, post version-store GC); interior dead + pinned tail pages stay. SHRINKDATABASE emits no result set; SHRINKFILE returns the per-file row from heap page totals (no physical file model). A versioning-on **autocommit** UPDATE/DELETE reclaims superseded chains via a statement-end GC pass when no snapshot is open.
199
+
- Client-surface streaming and heap-page divergences live in [`data-reader.md`](docs/claude/data-reader.md) and [`heap-storage.md`](docs/claude/heap-storage.md).
# Claude Working Notes — `Network/` (TDS endpoint internals)
2
+
3
+
Auto-loaded when working in this directory. The behavior deep-dive is [`docs/claude/tds-endpoint.md`](../../docs/claude/tds-endpoint.md) — protocol scope, response shapes, divergences, deferred items. These notes are the local implementation contracts.
4
+
5
+
-**Session layout**: `TdsSession` is a partial — main file (prelogin/TLS/login/batch loop + `StreamOutcomesAsync`), `.Rpc.cs` (RPC dispatch + prepared-handle map), `.TransactionManager.cs` (TM requests + the session `transaction` field). One task per socket; teardown is socket-close-driven; the catch list in `RunAsync` is the session's crash boundary — an exception type not listed there kills the session silently (client sees a transport error), so wire-format parse failures must throw `InvalidDataException`, unsupported features `NotSupportedException` (converted to ERROR tokens in handlers, never leaked).
6
+
-**Validate before emitting**: any per-column rejection (`TdsTypeCodec.ValidateSchema`) must fire *before* COLMETADATA bytes are written — a throw mid-token desyncs the stream, which surfaces client-side as a corrupted-frame/transport error, not a useful message.
7
+
-**Token writer contract**: `TdsTokenWriter` buffers synchronously; `FlushAsync(final: false)` after every row keeps memory bounded to max(row, packet). Only the final flush sets EOM. Tokens may legally split across packet boundaries.
8
+
-**Codec conventions**: nullable wire variants everywhere (INTN/BITN/FLTN/MONEYN/DECIMALN/GUIDN); COLMETADATA always claims nullable (result schema carries no nullability). PLP is written known-length (total + one chunk + terminator) but must be *read* in both known- and unknown-length (0x…FE) chunked forms — SqlClient streams large params with the latter.
9
+
-**Length-prefix trap**: most strings are char-counted (B_VARCHAR / US_VARCHAR), but TM-request transaction names and LOGIN7 password bytes are byte-counted. Misreading the TM name as char-counted overruns the payload on every `SqlTransaction.Save`.
10
+
-**Collation bytes**: `TdsCollationCodec` derives the 5-byte structure generatively (flags/version from name tokens, LCID/code page from the probe-built `TdsCollationRegistry`, sortId from `Collation.SqlServerSortOrders`), cached per interned `Collation` reference. Probe-anchored rules: width=bit22 / kana=bit23 (MS-TDS's own field-order text is wrong), fUTF8=bit26 *displaces* the binary bits, version nibble 160→4, `SQL_Latin1_General_CP1254_*` → Turkish LCID. Baseline must derive to `09 04 D0 00 34`.
11
+
-**Login response**: the ENVCHANGE type 7 (server collation) is load-bearing — SqlClient NREs building any RPC without it. TLS is pinned 1.2 (TLS 1.3 post-handshake tickets would be prelogin-wrapped after the client stops unwrapping; TDS 8.0 is the 1.3 path).
12
+
-**Oracle**: `SqlServerSimulator.Tests.SqlClient` (real SqlClient over loopback) is the regression contract for this directory; `EFCoreOverWire` in `*.Tests.EFCore` covers vanilla `UseSqlServer`. For nontrivial expected values use the dual-read pattern (same query in-process and over the wire against one `Simulation`).
Sibling deep-dives: [`foreign-keys.md`](foreign-keys.md) (the FK family in full), [`indexes.md`](indexes.md) (seek acceleration mechanics), [`alter-table.md`](alter-table.md) (ADD/DROP CONSTRAINT incl. trust toggling).
4
+
5
+
-`CHECK`: inline single-column + table-level; Msg 547 per row on definitely-false predicate (UNKNOWN passes — opposite of WHERE). Inline column-level CHECK may only reference its owning column — peer refs raise **Msg 8141** at CREATE TABLE (probe-confirmed). The walker is structural (`Expression.VisitColumnReferences` + `BooleanExpression.VisitOperandExpressions`); coverage spans the common containers (`Reference`, `Parenthesized`, `TwoSidedExpression`, `Cast`, `Length`) — peer refs in rarer ones (`DATEPART`, `SUBSTRING`, nested `CASE`) escape the CREATE check and surface at INSERT. Table-level CHECK has no peer restriction.
6
+
-`PRIMARY KEY` / `UNIQUE` / secondary `CREATE INDEX`: linear scan (O(N) per insert), no B-tree; reads and `UPDATE`/`DELETE`/`MERGE` target scans get **incrementally-maintained** per-`Heap` seek acceleration (equality / IN / leading-column range / equality-prefix+range continuation / ORDER BY elimination / keyset). Seek shapes, mutation/MERGE seeking, journal mechanics, decline rules, residual-WHERE invariant in [`indexes.md`](indexes.md). Violations: PK/UNIQUE *constraints* raise Msg 2627; unique *indexes* raise Msg 2601. UNIQUE treats NULLs as equal (the signature SQL Server divergence from ANSI).
7
+
-`FOREIGN KEY`: inline / table-level / named forms; all four referential actions on `ON DELETE`/`ON UPDATE`; enforced at INSERT/UPDATE/DELETE/MERGE; full `sys.foreign_keys` / `sys.foreign_key_columns`. Enforcement **seeks the shared `HeapSeekCache`** (live-byte verified, no residual WHERE). Referential-action, cascade-cycle, PK/UNIQUE-target, NULL-skip rules + Msg numbers in [`foreign-keys.md`](foreign-keys.md).
Full `DbDataReader` contract. Typed accessors read `SqlValue` via the cursor indexer, unwrap via `As*` (no boxing); NULL → `SqlNullValueException` (SqlClient parity).
4
+
5
+
-`GetDateTime` covers Date/DateTime/SmallDateTime/DateTime2 (Date at midnight, `Kind=Unspecified`).
6
+
- A **`datetime` rounds to whole milliseconds at the ADO.NET boundary** (`DateTimeSqlType.RoundToClientMilliseconds`, in `GetDateTime` + `SqlValue.ToObject` — the latter covers `GetValue`/`GetFieldValue`/output-param writeback) matching SqlClient's `.000`/`.003`/`.007`; the engine keeps full 1/300-second resolution internally, so only the client surface rounds. (The TDS endpoint transfers the full internal resolution and lets real SqlClient do the same client-side rounding — see [`tds-endpoint.md`](tds-endpoint.md).)
-`GetOrdinal` two-pass linear (case-sensitive then -insensitive, SqlClient precedence). `HasRows` sticky. `GetChar(int)` always raises `InvalidCastException`.
9
+
10
+
## Divergence
11
+
12
+
**`GetBytes` / `GetChars` materialize, don't stream**: each call decodes the full column value via `RowDecoder` and slices into the caller's buffer. Per-call observation matches SqlClient; the streaming-memory guarantee doesn't.
0 commit comments