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
Restructure the function-coverage TODO into a living docs/claude/backlog.md (completed items removed; organized into missing-features / fidelity-gaps / design-choices-to-revisit / won't-model with a completion-loop preamble), migrate durable conventions and project facts out of local agent-memory into CLAUDE.md + Tests/CLAUDE.md (and the BIN2 code-unit guard into collations.md), trim CLAUDE.md.
Copy file name to clipboardExpand all lines: CLAUDE.md
+15-10Lines changed: 15 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,17 +8,19 @@ Auto-loaded orientation. `README.md` is for humans.
8
8
9
9
`SqlServerSimulator.EFCore` is a sibling package whose only public method is `UseSqlServerSimulator(DbContextOptionsBuilder, DbConnection)`. EF Core's SqlServer provider keeps emitting SQL-Server-flavored SQL; the adapter just registers an `IRelationalTypeMappingSourcePlugin` for the (CLR, store) pairs whose default mappings downcast to `SqlParameter` (since the simulator's connection isn't a `SqlConnection`).
10
10
11
+
**Packaging:** only `SqlServerSimulator` publishes; `SqlServerSimulator.EFCore` and `Example` are `IsPackable=false` — the adapter stays in-repo-but-unpublished as a deliberate demand signal, so don't pitch publishing it without a user request.
12
+
11
13
## Operating goal
12
14
13
-
High-fidelity emulation. Authenticity over desirability — when SQL Server's behavior is quirky or lossy (CP1252 `?` replacement, ANSI trailing-space `=` padding, `LEN` excluding trailing spaces), mirror it. EF Core trusts the simulator end-to-end (`*.Tests.EFCore` is the regression oracle and must stay green). Beyond that floor, priority is broad SQL Server coverage weighted by popularity (user wins) and ease (thoroughness wins). The function-coverage backlog at [`docs/claude/function-coverage-todo.md`](docs/claude/function-coverage-todo.md) is sorted by that weighting and tracks category-completion milestones — read it before pitching a new built-in function.
15
+
High-fidelity emulation. Authenticity over desirability — when SQL Server's behavior is quirky or lossy (CP1252 `?` replacement, ANSI trailing-space `=` padding, `LEN` excluding trailing spaces), mirror it. Fidelity has caught real upstream bugs (EF Core 10's `Math.Sign(decimal)` int/decimal mismatch) — when a probe feels wrong, verify against the reference before relaxing the simulator; matching a real cross-stack quirk is the feature, not a bug to paper over. EF Core trusts the simulator end-to-end (`*.Tests.EFCore` is the regression oracle and must stay green). Beyond that floor, priority is broad SQL Server coverage weighted by popularity (user wins) and ease (thoroughness wins). The living [`docs/claude/backlog.md`](docs/claude/backlog.md)— missing features, fidelity gaps, design choices to revisit, deliberate exclusions — is ordered by that weighting but non-authoritatively; read it before picking up new feature work or pitching a built-in.
14
16
15
17
## Feature-bundle workflow
16
18
17
-
1.**Probe.** Behavior questions get answered against the real SQL Server 2025 reference instance (connection details in user memory). Probe scaffolds live in `/tmp/<probe-name>/`; deleted after the bundle. Only graduated regression tests land in `*.Tests` / `*.Tests.EFCore`.
18
-
2.**Surface decisions.** Before writing code, surface 2–3 concrete design choices and recommend one each.
19
-
3.**Implement + test.**`*.Tests` exercises public API; `*.Tests.EFCore` validates the oracle. `*.Tests.Internal` only for things genuinely unreachable from public SQL.
19
+
1.**Probe.** Behavior questions get answered against the real SQL Server 2025 reference instance (connection details in user memory). Probe scaffolds live in `/tmp/<probe-name>/`; deleted after the bundle. Only graduated regression tests land in `*.Tests` / `*.Tests.EFCore`. EF Core probes against the reference clean up with `DROP TABLE IF EXISTS`, **never**`Database.EnsureDeleted()` (the login can drop but not recreate the reference DB). A probe reveals *server* behavior, not *doc* content: don't write "Microsoft's docs claim X but…" without actually reading the MSDN page.
20
+
2.**Surface decisions.** Before writing code, surface 2–3 concrete design choices and recommend one each. Before pitching a built-in as net-new, grep `Parser/Expression.cs:ResolveBuiltIn` + `Parser/Expressions/` — the What's-modeled catalog lags the code, and variants ride a flag on the existing class (`tryMode` on Convert, `isBig` on DateDiff, `kind` discriminators), not a new one.
21
+
3.**Implement + test.**`*.Tests` exercises public API; `*.Tests.EFCore` validates the oracle. `*.Tests.Internal` only for things genuinely unreachable from public SQL.`*.Tests.EFCore` must drive EF Core's LINQ→SQL emission (and C#-side surface like `HasTrigger` / `UseHiLo` / `UseTpcMappingStrategy` that shifts the emit shape across EF versions), **not** hand-written SQL through `FromSql*` / `SqlQuery` — that's parser testing in disguise (covered once by `EFCoreFromSql.cs`).
20
22
4.**Update CLAUDE.md and `docs/claude/`.** Move bullets between What's-modeled / Not-modeled as scope changes. Deep-dive catalogs — *and feature-specific quirks/divergences* — live under `docs/claude/`; CLAUDE.md's Quirks section is reserved for cross-cutting divergences with no single feature-doc home (keep it short).
21
-
5.**Single-sentence commit.** Squashes capture end state. Don't run `git commit` — the user holds signing credentials.
23
+
5.**Single-sentence commit.** Squashes capture end state. Focus the message on *what changed* and *why*, plus probe-confirmed facts (Msg numbers, wording, semantic decisions) future-me would otherwise re-derive from the diff; omit CI-visible status (test counts, build / `dotnet format` state) — GitHub surfaces that and it goes stale. Don't run `git commit` — the user holds signing credentials.
22
24
23
25
Behavioral claims below were probed against a live SQL Server 2025 reference instance unless flagged otherwise.
24
26
@@ -91,12 +93,15 @@ Six scopes, one home each. **Add new state to whichever class matches its true s
91
93
-**No temporal words in code comments** — "currently", "now", "yet", "new" age badly.
92
94
-**No comments inside expression chains** — IDE0055 fails on comments in ternary chains or between `=>` and body. Restructure or hoist to XML doc.
93
95
-**Fields over auto-properties on non-public types** (SSS001 generalized).
96
+
-**No internal `<see cref>` in public-API XML docs** — a cref to an internal type dangles in consumer IntelliSense and implicitly promises stability for a name we're free to rename; state the contract in prose instead (`"an unrecognized collation name raises ArgumentException"`, not a cref to internal `Collation.IsRecognized`).
97
+
-**No conversation-scratch framing in code/docs/commits** — "Camp A/B", "this bundle", "Stage 1/2", "as we discussed" carry no meaning to a future reader; describe behavior/motivation absolutely, and cross-reference a sibling by the behavior it names, not the work-stage that produced it. Pre-existing repo terms (e.g. the transactions feature's "Bundle 1/2") are load-bearing, not transient — leave them.
94
98
-**AssemblyHooks**: each test project has `AssemblyHooks.cs` with a `static [TestClass] [AssemblyInitialize]` to warm shared initialization paths once before the parallel test run. Without it, the first batch of tests races to initialize hot shared state and serializes on contention. The analyzer-tests' Roslyn-cache warm-up is the most extreme case observed (~3x slowdown), but the pattern generalizes to any expensive first-touch shared resource.
95
99
96
100
## SimulatedSqlException vs NotSupportedException
97
101
98
102
-`SimulatedSqlException` for behavior matching SQL Server: invalid SQL, type mismatches, constraint violations, oversize columns, truncation. Mirrors number/class/state/message.
99
103
-`NotSupportedException` for valid SQL Server features the simulator hasn't built. Name the unmodeled feature.
104
+
-**Control flow signals via flags, not exceptions.** BREAK / CONTINUE / RETURN / THROW set a typed flag on `BatchContext` (`LoopControl` + the skip predicate), never a signal exception — `yield return` inside try/catch composes badly with the iterator-based dispatch, and the flag reuses the same skip-mode plumbing that no-ops un-taken IF branches. Parse-time structural checks (BREAK outside WHILE → Msg 135) still fire regardless of skip state. Exceptions stay for true error conditions.
100
105
101
106
## What's modeled
102
107
@@ -107,17 +112,17 @@ The `*.Tests` and `*.Tests.EFCore` suites are the authoritative behavior contrac
107
112
108
113
### Constraints
109
114
-`CHECK`: inline single-column and table-level forms; Msg 547 per row on definitely-false predicate. Inline column-level CHECK predicates may only reference their owning column — peer references raise **Msg 8141** at CREATE TABLE (probe-confirmed verbatim wording). The walker is structural via `Expression.VisitColumnReferences` + `BooleanExpression.VisitOperandExpressions`; coverage is currently limited to common container subclasses (`Reference`, `Parenthesized`, `TwoSidedExpression`, `Cast`, `Length`) — peer refs buried in less-common containers (`DATEPART`, `SUBSTRING`, nested `CASE`, etc.) silently escape the CREATE-TABLE check and surface at INSERT instead. Table-level CHECK has no peer restriction.
110
-
-`PRIMARY KEY` / `UNIQUE` / secondary `CREATE INDEX`: linear scan (O(N) per insert); no B-tree. Reads get **incrementally-maintained** per-`Heap` seek acceleration (no per-mutation warm-up), identical across keys and non-clustered indexes: equality / IN on the longest leading prefix, range on a leading key column, ORDER BY elimination, and keyset pagination. The same narrowing accelerates `UPDATE` / `DELETE` target scans and `MERGE` (via loop inversion). See [`indexes.md`](docs/claude/indexes.md) for the seek shapes, mutation/MERGE seeking, journal mechanics, decline rules, and the residual-WHERE correctness invariant.
111
-
-`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`** (the same per-`Heap` index the query path uses): child-insert parent-existence probes the parent's PK/UNIQUE; parent-delete/update cascades seek the child's FK columns — both verifying candidates against live bytes (no residual WHERE), with a full-scan fallback only for non-stored key columns. Referential-action, cascade-cycle, PK/UNIQUE-target, and NULL-skip rules + Msg numbers in [`foreign-keys.md`](docs/claude/foreign-keys.md).
115
+
-`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 / ORDER BY elimination / keyset). Seek shapes, mutation/MERGE seeking, journal mechanics, decline rules, residual-WHERE invariant in [`indexes.md`](docs/claude/indexes.md).
116
+
-`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, and NULL-skip rules + Msg numbers in [`foreign-keys.md`](docs/claude/foreign-keys.md).
112
117
113
118
### Transactions
114
119
Three entry points share one per-connection undo log: implicit (statement-level atomicity), SqlClient API (`BeginTransaction()`/`Commit()`/`Rollback()`), SQL-text (`BEGIN`/`COMMIT`/`ROLLBACK`/`SAVE TRANSACTION`).
115
120
116
121
-**Statement-level atomicity**: a single mutation throwing mid-execution rolls back its partial writes. Multi-row INSERT failing on row 3 leaves zero rows.
117
122
-**Explicit txs**: `BEGIN TRAN` increments `TranCount`; only outermost `COMMIT` actually commits; `ROLLBACK` zeroes `TranCount` and walks the entire 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.
118
123
-`@@TRANCOUNT` reads connection depth as int.
119
-
-**Identity counters and the database-scoped rowversion counter bypass the log** — both keep advancing through rollback. (A rolled-back INSERT's off-row LOB chain and its heap-page row-payload bytes are both reclaimed — the undo marks the slot reclaimable, since rollback is terminal and an uncommitted insert is invisible to every snapshot.)
120
-
-**Temp-table CREATE/DROP participates in the log** via `TempTableCreation` / `TempTableRemoval``UndoEntry` subtypes (rollback removes from / restores into the connection's `TempTables` dict). Regular CREATE/DROP TABLE is NOT logged (the temp-vs-regular DDL asymmetry is detailed in [`temp-tables.md`](docs/claude/temp-tables.md)).
124
+
-**Identity counters and the database-scoped rowversion counter bypass the log** — both keep advancing through rollback. (A rolled-back INSERT's off-row LOB chain and heap-page bytes are reclaimed — rollback is terminal, so an uncommitted insert is invisible to every snapshot.)
125
+
-**Temp-table CREATE/DROP participates in the log** via `TempTableCreation` / `TempTableRemoval``UndoEntry` subtypes. Regular CREATE/DROP TABLE is NOT logged — the temp-vs-regular DDL asymmetry is detailed in [`temp-tables.md`](docs/claude/temp-tables.md).
121
126
- 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 detection, Msg 1222 timeouts, SNAPSHOT + RCSI with version chains + GC + DMVs. See [`docs/claude/locking.md`](docs/claude/locking.md).
122
127
123
128
### EF Core adapter coverage
@@ -177,7 +182,7 @@ Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger:
177
182
-**Key-range locks** — sole remaining phase 4+ deferral. See [`locking.md`](docs/claude/locking.md) for what does ship (full 8-mode matrix, SNAPSHOT/RCSI, DMVs, Msg 1205/1222/3952/3960).
178
183
-`BEGIN DISTRIBUTED TRANSACTION` raises `NotSupportedException` at dispatch. `BEGIN TRANSACTION <name> WITH MARK 'm'` raises **Msg 319** at parse (the parser doesn't accept `WITH` here); bare named transactions (`BEGIN TRAN t1`) ship.
179
184
-**`CREATE DATABASE`** / **`CREATE ASSEMBLY`** — Msg 102 at parse. Adding databases routes through `Simulation.ImportBacpac`; CLR assemblies aren't modeled at all.
180
-
-**Cross-database / cross-server DML** (`INSERT`/`UPDATE`/`DELETE`/`MERGE` through a 3- or 4-part name targeting a different database or linked server) raises `NotSupportedException` via `BatchContext.RejectCrossDatabaseMutation` — issue `USE <db>`and use a 1-/2-part name. Cross-database and four-part-name *reads* (SELECT / JOIN, the latter through the remote `Simulation`'s full ADO.NET pipeline) ship; catalog-view reads through four-part names (`srv.db.sys.tables`) fall through to Msg 208 — query the remote `Simulation` directly. See [`schemas.md`](docs/claude/schemas.md), [`linked-servers.md`](docs/claude/linked-servers.md).
185
+
-**Cross-database / cross-server DML** (`INSERT`/`UPDATE`/`DELETE`/`MERGE` through a 3-/4-part name to another database or linked server) raises `NotSupportedException` via `BatchContext.RejectCrossDatabaseMutation` — issue `USE <db>`first. Cross-database / four-part-name *reads* (SELECT / JOIN) ship; catalog-view reads through four-part names (`srv.db.sys.tables`) fall through to Msg 208. See [`schemas.md`](docs/claude/schemas.md), [`linked-servers.md`](docs/claude/linked-servers.md).
-**`ALTER DATABASE … SET` / `COLLATE` surface** — see [`database-options.md`](docs/claude/database-options.md). Most options parse-and-discard; `COMPATIBILITY_LEVEL`, `ALLOW_SNAPSHOT_ISOLATION`, `READ_COMMITTED_SNAPSHOT` are load-bearing.
183
188
-`RANGE BETWEEN <N> PRECEDING/FOLLOWING` numeric-offset — Msg 4194, matching real SQL Server's licensed-feature rejection. `ROWS` numeric-offset ships. Default frame with ORDER BY is `RANGE UNBOUNDED PRECEDING TO CURRENT ROW`; without it, whole partition. LAST_VALUE matches real SQL Server's default-frame semantic.
Copy file name to clipboardExpand all lines: SqlServerSimulator.Tests/CLAUDE.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,6 +21,8 @@ public void TableLevel_Composite_Match_Succeeds()
21
21
22
22
`Simulation.ExecuteScalar` / `ExecuteNonQuery` accept a multi-statement raw string; the final `SELECT`'s first value is the scalar return. Don't split CREATE + INSERT + SELECT into three calls unless a later assertion needs an intermediate observation.
23
23
24
+
**Batching trap — `ExecuteNonQuery` returns the *sum* of rows-affected across all DML in the batch** (faithful to TDS DONE tokens; DDL like CREATE TABLE doesn't contribute). So `AreEqual(1, sim.ExecuteNonQuery("…; insert 2 rows; delete 1 row"))` measures 3, not the DELETE's 1, and fails for the wrong reason. Safe to fold setup into the asserted call only for `ExecuteScalar` (DML counts don't leak in) and `AssertSqlError` (throws). When the assertion *is* the `ExecuteNonQuery` row count, keep setup in a separate call so the measured statement is isolated.
25
+
24
26
Canonical example: [`CheckConstraintTests.cs`](CheckConstraintTests.cs). The full set of helpers ships in [`Extensions.cs`](Extensions.cs).
`ExecuteScalar` returning `object?` only needs `_ =` when discarded — wrap with `AreEqual(...)` when asserting.
84
86
87
+
## Collation / Unicode fixtures
88
+
89
+
-**Literal `N'…'`** for any character with a distinctive visible glyph (`€`, `ƒ`, `Ÿ`, `ア`, `café`) — the literal *is* the explanation, no `U+xxxx` comment needed.
90
+
-**`nchar(N)` (decimal, never `0xNN`)** only for invisible / ambiguous characters: NBSP (U+00A0, renders like a space), Private Use Area (no glyph), surrogate pairs (`nchar(55357) + nchar(56832)` for 😀). Hex `nchar(0x…)` fails — the parser reads `0xNN` as varbinary and the varbinary→int coercion isn't implemented.
91
+
-**Skip the `cast(<literal> as varchar(N))` / `char(N)` on INSERT** — assignment-time coercion handles `N'…'` → `varchar(N)` and `nchar(N)` → `char(N)` automatically; the cast is noise.
92
+
85
93
## Probe code is not a style reference
86
94
87
95
Probe scaffolds in `/tmp/<feature>/` are throwaway data-collection scripts: bare `using` blocks, `Console.WriteLine`, no analyzer enforcement. When the graduated tests land in this directory, **don't mirror the probe's shape** — go directly to the dense form above. Probes get deleted after the feature bundle commits.
0 commit comments