Skip to content

Commit c2cfdb8

Browse files
committed
CLAUDE.md densification: combination of strategies including in-place densification, moving content to docs/claude markdown files, removing duplication.
1 parent 568af59 commit c2cfdb8

8 files changed

Lines changed: 37 additions & 52 deletions

File tree

CLAUDE.md

Lines changed: 20 additions & 46 deletions
Large diffs are not rendered by default.

docs/claude/control-flow.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ Each statement parser still runs its full parse — the cursor advances normally
5656

5757
**Fidelity gap — un-taken-branch name resolution**: real SQL Server defers name resolution for un-taken branches — `IF 1=0 SELECT bad_col FROM bad_table` runs silently. The simulator's parsers do name resolution inline with parsing, so un-taken branches with non-existent table/column refs still raise Msg 208 / Msg 207. Common idioms (safe-CREATE / safe-DROP / safe-INSERT against pre-existing tables) work end-to-end because referenced names exist when the branch is skipped.
5858

59+
**Fidelity gap — `IF` cond divide-by-zero**: real SQL Server surfaces `IF 1/0 = 0 …` as Msg 8134; the simulator surfaces the raw `DivideByZeroException` from .NET decimal arithmetic (same gap as `TRY_CAST(1/0 AS INT)`).
60+
61+
**Fidelity gap — `IF (value-expr) …` positional**: `IF (1) select` raises Msg 4145 near `')'`; real SQL Server reports the post-paren token (`'select'`). Wording is correct (Msg 4145, non-boolean type); only the "near 'X'" suffix differs. Applies to any paren-wrapped non-boolean `IF` cond.
62+
63+
**Fidelity gap — CREATE/ALTER inside a control-flow body raises Msg 111, not Msg 156**: the must-be-first-statement check for `CREATE/ALTER PROCEDURE / FUNCTION / VIEW / TRIGGER / SCHEMA` is enforced at parse time. Inside `IF` / `WHILE` / `BEGIN…END`, `BatchContext.BlockDepth > 0` triggers Msg 111; real SQL Server's parser surfaces Msg 156 ("Incorrect syntax near 'procedure'") at the same position. Same end state (statement rejected), different code. Inner CommandText-equivalent contexts (procedure / function / trigger / dynamic-SQL bodies) get a fresh `BatchContext` and the flag resets, so a CREATE PROCEDURE as the first statement of a proc body succeeds (real SQL Server raises Msg 156 here — related minor divergence; no real application emits nested CREATE PROCEDUREs).
64+
5965
## TRY/CATCH + ERROR_*() + live @@ERROR + THROW
6066
`BEGIN TRY ... END TRY BEGIN CATCH ... END CATCH` blocks parse via `Simulation.TryCatch.cs:ParseTryCatch`. TRY and CATCH aren't reserved keywords (contextual identifiers), so the BEGIN dispatch site peeks the next token: `Tran`/`Transaction` routes to `TryParseBeginTransaction`, `TRY` (unquoted) routes here, `ATOMIC` raises `NotSupportedException`, anything else falls through to `ParseBeginBlock`. Probed against SQL Server 2025 (2026-05-12).
6167

docs/claude/function-coverage-todo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ Real bugs / limitations against shipped functions — fixes are tickable work, n
146146

147147
## Documented design choices (review rationale)
148148

149-
Function-level decisions documented in CLAUDE.md's Quirks section. These shipped intentionally — the simulator works correctly under the documented contract — but the original rationale may have aged out. Worth a look before either checking off or re-affirming.
149+
Function-level decisions documented in `scalars.md`'s divergence notes (the aggregate/string scalars below) and CLAUDE.md's cross-cutting Quirks (`float` text formatting). These shipped intentionally — the simulator works correctly under the documented contract — but the original rationale may have aged out. Worth a look before either checking off or re-affirming.
150150

151151
- [ ] **APPROX_COUNT_DISTINCT** implemented as exact `COUNT(DISTINCT)`. Original rationale: same semantic guarantee, no HyperLogLog dependency. Review: is the perf gap visible against the simulator's in-process workloads? If not, the simpler implementation stays defensible.
152152
- [ ] **CHECKSUM_AGG** uses an order-independent XOR fold. Original rationale: same-multiset-same-checksum guarantee preserved, bit-identical match wasn't required. Review: have any consumers needed bit-identical checksums (e.g., for replication-comparison parity)?

docs/claude/joins.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ INNER / bare `JOIN` (= INNER) / LEFT [OUTER] / RIGHT [OUTER] / FULL [OUTER] / CR
44

55
## Comma-separated FROM (ANSI-89)
66

7-
`FROM a, b WHERE a.id = b.id` parses as a sequence of explicit-join chains spliced with `JoinKind.Cross` joins. Each comma starts a fresh chain via the same `ParseExplicitJoinChain` helper the JOIN-keyword loop calls, so any explicit JOINs *within* a chain bind before the cross-splice. See the quirk in the root CLAUDE.md about a back-reference-across-comma case that diverges toward more-permissive.
7+
`FROM a, b WHERE a.id = b.id` parses as a sequence of explicit-join chains spliced with `JoinKind.Cross` joins. Each comma starts a fresh chain via the same `ParseExplicitJoinChain` helper the JOIN-keyword loop calls, so any explicit JOINs *within* a chain bind before the cross-splice.
8+
9+
**Quirk — back-reference across a comma silently succeeds** (e.g. `FROM a, b JOIN c ON c.id = a.id`): real SQL Server binds `b JOIN c ON …` as its own scope and raises Msg 4104 because `a` isn't visible there; the simulator doesn't do parse-time scope-checking on ON predicates (column refs resolve at runtime through `ResolveAcrossTuple` across all FROM sources in the tuple), so the query runs and returns the Cartesian-filtered rowset. The common shapes — basic `FROM a, b WHERE …`, multi-comma chains, comma + derived table, explicit JOIN followed by comma — all match real SQL Server byte-for-byte; only this rare back-reference-across-comma case diverges, toward "more permissive" rather than wrong rowset.
810

911
## JoinDriver
1012

docs/claude/json.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Value formatting matches real SQL Server byte-for-byte except float / real (docu
2121
- other strings → JSON-escaped (`\"` `\\` `\b` `\f` `\n` `\r` `\t` `\uHHHH` for control chars; non-ASCII / `/` / `<` / `>` left literal)
2222
- nested `JSON_OBJECT` / `JSON_ARRAY` / `JSON_QUERY` results — embedded **raw** (not re-quoted), via compile-time `JsonValueRender.ProducesJson(Expression)` detection that unwraps `Parenthesized`. Other strings — including `'{"x":1}'` literals — go through the quote-and-escape path, matching SQL Server's JSON-typed-input detection without needing an `SqlValue`-level marker bit.
2323

24-
`OPENJSON(json [, doc_path]) [WITH (col TYPE [path] [AS JSON], …)]` — rowset-returning, structurally a new FromSource kind. Without WITH: default schema `(key nvarchar, value nvarchar, type int)` — type codes 0=null/1=string/2=number/3=bool/4=array/5=object. With WITH: each column extracts via `$.<col-name>` (default) or explicit `'$path'`; primitive collections use `'$'`. `AS JSON` modifier → `NotSupportedException`. NULL/invalid JSON → zero rows under lax.
24+
`OPENJSON(json [, doc_path]) [WITH (col TYPE [path] [AS JSON], …)]` — rowset-returning, structurally a new FromSource kind. Without WITH: default schema `(key nvarchar, value nvarchar, type int)` — type codes 0=null/1=string/2=number/3=bool/4=array/5=object. With WITH: each column extracts via `$.<col-name>` (default) or explicit `'$path'`; primitive collections use `'$'`. `AS JSON` modifier → `NotSupportedException` on `nvarchar(max)` (sub-tree extraction not modeled, even though real SQL Server accepts the column form there); on a non-`nvarchar(max)` column `AS JSON` raises **Msg 13618**, matching real. NULL/invalid JSON → zero rows under lax.
2525

2626
OPENJSON WITH-clause types: `int`/`bigint`/`decimal(p,s)`/`float`/`bit`/`nvarchar(N|max)`/`varchar(N)`/`date`/`datetime2(N)`/`datetimeoffset(N)`/`uniqueidentifier`. Coercion via `SqlValue.CoerceTo`. Backed by `System.Text.Json`. JSON-path quoted-property escape `""` → literal `"`.
2727

docs/claude/query.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
The aggregate executor (`Selection.Execution.Aggregate.cs`) **buffers** WHERE-filtered rows once (snapshotting each tuple because `EnumerateJoinedRows` reuses a single shared array in-place) then iterates each grouping set, partitioning the buffer per set's columns and accumulating fresh aggregators per group. The projection's column resolver returns typed NULL for columns that aren't in the current set but appear in another set's columns — that's the subtotal/total-row semantic. Without GROUP BY the executor synthesizes a single empty grouping set `[[]]` and runs one implicit group; same code path covers `GROUPING SETS(())`. TOP / OFFSET / FETCH apply to the concatenated stream across all grouping sets.
2929

30-
`GROUPING(col)` / `GROUPING_ID(c1, ..., cN)` read the executor-published context off `BatchContext.GroupingSetExpressions` (current set's column list) and `BatchContext.AllGroupingExpressions` (union across query). Returns `tinyint` 0/1 and `int` bitmap respectively; **leftmost arg of `GROUPING_ID` occupies the most-significant bit** (probe-confirmed against SQL Server 2025 — `GROUPING_ID(region, product)` with region grouped + product not grouped returns `2`, the inverse case returns `1`). Argument must match a GROUP BY expression by column-leaf-name; non-Reference arguments raise `NotSupportedException`. Arg not in any grouping set → Msg 8161; same Msg for GROUPING outside any GROUP BY context.
30+
`GROUPING(col)` / `GROUPING_ID(c1, ..., cN)` read the executor-published context off `BatchContext.GroupingSetExpressions` (current set's column list) and `BatchContext.AllGroupingExpressions` (union across query). Returns `tinyint` 0/1 and `int` bitmap respectively; **leftmost arg of `GROUPING_ID` occupies the most-significant bit** (probe-confirmed against SQL Server 2025 — `GROUPING_ID(region, product)` with region grouped + product not grouped returns `2`, the inverse case returns `1`). Argument must match a GROUP BY expression by column-leaf-name. Arg not in any grouping set → Msg 8161; same Msg for GROUPING outside any GROUP BY context. **Non-Reference args always raise Msg 8161**: the match is leaf-name equality on `Reference` arguments only, so any wrapped form (`GROUPING(a+1)` paired with `GROUP BY a+1`) fails the match and raises 8161 where real SQL Server matches by structural equality and returns 0. Right Msg, wrong row count; no real application emits the wrapped form.
3131

3232
`STRING_AGG(expr, sep) WITHIN GROUP (ORDER BY ...)` reorders concatenation per group (EF emits this from `GroupBy(...).Select(g => string.Join(sep, g.OrderBy(...)))`). NULL operand rows skip both ORDER BY input and output. Non-`STRING_AGG` aggregate with `WITHIN GROUP`**Msg 10757**; ORDER BY ordinal in this context → **Msg 5308** (distinct from projection-level ORDER BY which accepts ordinals); `WITHIN` is contextual (not reserved). Cross-aggregate Msg 8711 isn't modeled (EF doesn't emit).
3333

docs/claude/scalars.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ Five integer scalars sharing one dispatch file (`Parser/Expressions/BitManipulat
123123
## CHECKSUM family
124124

125125
- **`CHECKSUM(args...)` / `BINARY_CHECKSUM(args...)`** (`Parser/Expressions/ChecksumAndRowVersion.cs`) — fast 32-bit fold over the argument list. Implementation uses FNV-1a; semantic guarantee matches SQL Server (same inputs → same checksum, deterministically). **Bit-pattern divergence**: real SQL Server uses an undocumented byte-mix; the simulator's FNV-1a output won't match real SQL Server bit-for-bit. Same-value-same-checksum invariant holds; same-multiset-same-checksum doesn't (CHECKSUM is order-sensitive, unlike CHECKSUM_AGG). Result `int`.
126-
- **`CHECKSUM_AGG(expr)`** uses an order-independent XOR fold for the aggregate form — same multiset → same checksum, bit pattern won't match real SQL Server. Documented in CLAUDE.md's Quirks.
126+
- **`CHECKSUM_AGG(expr)`** uses an order-independent XOR fold for the aggregate form — same multiset → same checksum, bit pattern won't match real SQL Server.
127+
- **`APPROX_COUNT_DISTINCT(expr)`** is implemented as an exact `COUNT(DISTINCT expr)` — no HyperLogLog approximation, so results are exact rather than within real SQL Server's ~2% error bound.
128+
- **`DATALENGTH(expr)`** returns `int` even for `varchar(MAX)` / `nvarchar(MAX)` and the legacy LOB family, where real SQL Server returns `bigint`. The value still fits in int for anything the simulator can produce; only the declared result type doesn't widen.
127129

128130
## Session / connection placeholders
129131

@@ -150,7 +152,7 @@ Server-instance metadata accessed via **`SERVERPROPERTY(name)`** — see [`catal
150152
## Built-in TVF: `STRING_SPLIT`
151153
`STRING_SPLIT(input, separator [, enable_ordinal])` dispatches in `ParseSingleFromSource` alongside `OPENJSON` — case-insensitive name match before generic name resolution. Yields one row per substring split on the single-character separator.
152154

153-
- Schema is decided at parse time: 2-arg form projects `(value <input-string-type>)`; 3-arg form with literal `enable_ordinal = 1` adds `ordinal bigint`. `enable_ordinal = 0` or NULL collapses back to the 2-arg schema. The third argument must be a parse-time-constant integer expression — column / parameter references raise `NotSupportedException`; real SQL Server's grammar enforces the same constraint (the schema is shape-fixed at compile time).
155+
- Schema is decided at parse time: 2-arg form projects `(value <input-string-type>)`; 3-arg form with literal `enable_ordinal = 1` adds `ordinal bigint`. `enable_ordinal = 0` or NULL collapses back to the 2-arg schema. The third argument must be a parse-time-constant integer expression — column / parameter references raise `NotSupportedException`; real SQL Server's grammar enforces the same constraint (the schema is shape-fixed at compile time). **Const-only gate edge**: a bare `@v` is rejected (Msg 8748, matching real), but a `CAST` / `Parenthesized` wrapper around the variable (`cast(@v as int)`) slips past the gate where real SQL Server rejects all variable-bearing shapes regardless of wrapping. No real-world emission hits this.
154156
- NULL `input` → zero rows; empty `input` → one row with empty value (and ordinal 1 in the ordinal-enabled form).
155157
- NULL / empty / multi-character `separator` → Msg 214 at runtime (probe-confirmed: validated before the input — NULL sep raises 214 even when input is also NULL).
156158
- Non-int third argument → Msg 8116; `enable_ordinal` literal outside {0, 1, NULL} → Msg 4199.

docs/claude/temporal-tables.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ Read this when working on `PERIOD FOR SYSTEM_TIME`, `GENERATED ALWAYS AS ROW STA
3434
- **Auto-named history** (`SYSTEM_VERSIONING = ON` without `(HISTORY_TABLE = ...)`).
3535
- **`FOR SYSTEM_TIME BETWEEN ... AND ...`** / **`FROM ... TO ...`** / **`CONTAINED IN (..., ...)`** query forms. Only `ALL` and `AS OF <expr>` ship.
3636
- **Column-shape match validation** between base and history on `ALTER ... SET (SYSTEM_VERSIONING = ON ...)`. Real SQL Server validates same column count + names + types + nullability + period-column wiring; the simulator establishes the link unconditionally (matching CREATE WITH SYSTEM_VERSIONING = ON, which builds the history from the base and so doesn't need separate validation). Mismatched-shape history tables will surface at query time rather than at ALTER time.
37+
- **Msg 13544 qualified-name format**: real SQL Server pads temp-table names with their internal allocation suffix (`#x____...___…000000000148`); the simulator emits the bare `tempdb.dbo.#x` form. Same Msg number / framing, less verbose name.
3738
- **LOB-eligible columns** (`varchar(MAX)` / `nvarchar(MAX)` / `varbinary(MAX)` / `text` / `ntext` / `image`) on a temporal table — the `FOR SYSTEM_TIME` row source presents one `lobStore` reference to the FROM machinery; mixing parent and history rows in the same enumerator would need per-row LOB-store dispatch. No test scenario reaches this path.

0 commit comments

Comments
 (0)