Skip to content

Commit 12e2cd8

Browse files
committed
Split root CLAUDE.md content out into triggered files.
1 parent 4ffb601 commit 12e2cd8

7 files changed

Lines changed: 71 additions & 24 deletions

File tree

CLAUDE.md

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -106,43 +106,24 @@ Six scopes, one home each. **Add new state to whichever class matches its true s
106106

107107
## What's modeled
108108

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.
128110

129111
### EF Core adapter coverage
130112
`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`.
131113

132114
### `text` / `ntext` / `image` restrictions
133115
Comparison (Msg 402), ORDER BY/DISTINCT (Msg 306), and aggregates (Msg 8117 from MAX/MIN) all enforced.
134116

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-
138117
### Feature reference
139118

140119
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.
141120

142121
- **Built-in scalars** — math; date (DATETRUNC / DATE_BUCKET / SWITCHOFFSET / TODATETIMEOFFSET / `*FROMPARTS` / AT TIME ZONE / current-time); string (CONCAT / SOUNDEX / TRANSLATE / STRING_ESCAPE / DIFFERENCE); CHOOSE / IIF; bit (BIT_COUNT / GET_BIT / SET_BIT / shifts); CHECKSUM / BINARY_CHECKSUM; FORMAT / FORMATMESSAGE; RAND; STRING_SPLIT / GENERATE_SERIES; COMPRESS / DECOMPRESS; PWDENCRYPT / PWDCOMPARE / LOGINPROPERTY; `@@`-constants + HOST_NAME / APP_NAME / GETANSINULL / ORIGINAL_DB_NAME; session-state (SESSION_CONTEXT / sp_set_session_context / CONTEXT_INFO / CONNECTIONPROPERTY) → [`scalars.md`](docs/claude/scalars.md).
143122
- **`SqlType.Promote` / `PromoteForArithmetic` / decimal precision-scale / int↔string promotion**[`arithmetic.md`](docs/claude/arithmetic.md).
144123
- **`Cast` / coercion error paths** (CAST/CONVERT narrow targets, TRY_CAST/TRY_CONVERT swallow set, PARSE/TRY_PARSE culture-aware parsing) → [`casting.md`](docs/claude/casting.md).
124+
- **`SimulatedDbDataReader` client surface** (typed accessors, `datetime` client-millisecond rounding, `GetOrdinal` precedence, GetBytes/GetChars materialization divergence) → [`data-reader.md`](docs/claude/data-reader.md).
145125
- **`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).
146127
- **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).
147128
- **`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).
148129
- **UPDATE / DELETE / INSERT…SELECT / SELECT…INTO / rowversion** (`@@DBTS` / `MIN_ACTIVE_ROWVERSION`) **/ identity helpers** (`@@IDENTITY` / `SCOPE_IDENTITY` / `IDENT_CURRENT`/`_INCR`/`_SEED`) **/ `@@ROWCOUNT` / `ROWCOUNT_BIG` / OUTPUT / MERGE**[`dml.md`](docs/claude/dml.md).
@@ -160,12 +141,15 @@ Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger:
160141
- **`sp_addextendedproperty` / `fn_listextendedproperty` / `sys.extended_properties`**[`extended-properties.md`](docs/claude/extended-properties.md).
161142
- **`CREATE/ALTER/DROP SEQUENCE`, `NEXT VALUE FOR`, `sys.sequences`**[`sequences.md`](docs/claude/sequences.md).
162143
- **DML + DDL triggers** (`CREATE TRIGGER` incl. `ON DATABASE`, `INSERTED`/`DELETED`, `TRIGGER_NESTLEVEL`, `sys.triggers`) → [`triggers.md`](docs/claude/triggers.md).
144+
- **CHECK / PRIMARY KEY / UNIQUE enforcement** (Msg 547 / 8141 peer-reference gate, Msg 2627 vs 2601, NULLs-equal UNIQUE) → [`constraints.md`](docs/claude/constraints.md).
163145
- **`FOREIGN KEY` + referential actions, `sys.foreign_keys`**[`foreign-keys.md`](docs/claude/foreign-keys.md).
164146
- **`PERIOD FOR SYSTEM_TIME`, `FOR SYSTEM_TIME ALL/AS OF`, history sibling, `temporal_type`**[`temporal-tables.md`](docs/claude/temporal-tables.md).
165147
- **`ALTER TABLE` ADD/DROP/ALTER COLUMN + CONSTRAINT (incl. trust toggling)**[`alter-table.md`](docs/claude/alter-table.md).
166148
- **`CREATE INDEX` (UNIQUE / CLUSTERED / INCLUDE / WHERE filter), `sys.indexes`**[`indexes.md`](docs/claude/indexes.md).
167149
- **Table hints (`WITH (NOLOCK …)`) + statement `OPTION (…)` hints**[`query-hints.md`](docs/claude/query-hints.md).
150+
- **Heap page lifecycle** (reclamation/reuse, tail-only shrink, `DBCC SHRINKDATABASE`/`SHRINKFILE`) → [`heap-storage.md`](docs/claude/heap-storage.md).
168151
- **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).
152+
- **Transactions** (statement atomicity, undo log, BEGIN/COMMIT/ROLLBACK/SAVE, `@@TRANCOUNT`, identity/rowversion log bypass, temp-table logging asymmetry) → [`transactions.md`](docs/claude/transactions.md).
169153
- **Locking, MVCC, SNAPSHOT/RCSI, deadlock/timeout, lock-related DMVs**[`locking.md`](docs/claude/locking.md).
170154
- **Application locks** (`sp_getapplock` / `sp_releaseapplock` / `APPLOCK_MODE` / `APPLOCK_TEST`, return-code-vs-raised-error asymmetry, EF 9/10 `Database.Migrate()`'s `__EFMigrationsLock`) → [`app-locks.md`](docs/claude/app-locks.md).
171155
- **`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
212196
- `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).
213197
- `float` text formatting: .NET `G15`/`G7`, not SQL Server's `1e+015`-style scientific.
214198
- 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). Deterministic across runs, distinct from SQL Server's object-id-derived hex (won't byte-match).
215-
- **`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).
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# 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`).

docs/claude/constraints.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# CHECK / PRIMARY KEY / UNIQUE constraint enforcement
2+
3+
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).

docs/claude/data-reader.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# `SimulatedDbDataReader` client surface
2+
3+
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).)
7+
- `GetDecimal` covers Decimal/Numeric/Money/SmallMoney; `GetFieldValue<T>` short-circuits EF's `DateOnly`-over-`Date` / `TimeOnly`-over-`Time`.
8+
- `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

Comments
 (0)