Skip to content

Commit 7ae5038

Browse files
committed
ALTER TABLE ALTER COLUMN col TYPE[(prec[,scale])] [COLLATE coll] [NULL|NOT NULL] — closes the last EF-Migrations-shaped ALTER TABLE column-grammar gap. Routed from TryParseAlterTable via Keyword.Alter into new TryParseAlterTableAlterColumn in Simulation.AlterTableColumn.cs (single-column shape only — real SQL Server's grammar doesn't accept comma-separated multi-column ALTER COLUMN; ADD/DROP {PERSISTED|MASKED|ROWGUIDCOL|SPARSE} sub-clause forms detected early and raise NotSupportedException). Type-name parser reuses SqlType.GetByName with declared length/scale; COLLATE clause parse-accepted then discarded; trailing NULL/NOT NULL optional — omitting preserves existingCol.Nullable (probe-confirmed). Per-row conversion routes through SqlValue.CoerceTo so the matching error codes surface verbatim: Msg 220 (int→tinyint overflow — translated from System.OverflowException via the OverflowException catch path with the source value widened to BigInt for ToString rendering — "Arithmetic overflow error for data type tinyint, value = 500."), Msg 245 (varchar→int with non-numeric data — from ParseStringToInteger), Msg 241 (varchar→date with bad format — from CoerceStringToDateLikeWithStyle), Msg 8115 (decimal precision narrow overflow — from ArithmeticOverflowToNumeric on non-integer-category overflow), Msg 2628 (bounded-string narrow with overflow — explicit MaxLength check post-CoerceTo since SqlValue is length-agnostic at the value level), Msg 515 (NULL→NOT NULL flip with existing NULL data — fresh AlterColumnNullInNonNullColumn factory). Msg 4928 rejects COMPUTED ("Cannot alter column 'X' because it is 'COMPUTED'.") and ROWVERSION ("…because it is 'timestamp'.") up front via new CannotAlterColumnOfKind factory; Msg 4924 (column doesn't exist) via new AlterColumnDoesNotExist factory distinct from the DROP COLUMN variant by wording. Msg 5074 multi-blocker enumeration (new AlterColumnHasDependencies factory + AlterColumnBlockerKind enum {Object,Index,Column} mapping to "The object 'X'" / "The index 'X'" / "The column 'X'" prefixes — probe-confirmed three-kind shape vs DROP COLUMN's two-kind {Object,Index}): PK/UQ via StorageOrdinal walk; outgoing FK via ChildColumnOrdinals; incoming FK via ReferencedColumnOrdinals; computed-column dependency via Expression.VisitColumnReferences walk over every HeapColumn.Computed (new ComputedReferencesColumn helper); indexes ONLY when isSubclassChange (existingCol.Type.GetType() != newType.GetType()) — probe-confirmed that varchar(50)→varchar(100) widening passes under an index, varchar→nvarchar doesn't. CHECK and DEFAULT do NOT block — probe-confirmed they survive intact through the alter. Storage rewrite (new RewriteHeapForAlterColumn) walks every row decoding under the pre-alter HeapColumn array (captured pre-mutation), coercing the target column's value, re-encoding the row against the post-alter StoredColumns; eager rewrite even for length-widening within the same family (the SqlType reference differs between varchar(50) and varchar(100) singletons, so StoredColumns/Schema arrays must mirror the new shape before re-encoding writes). Apply pipeline: clone Columns array, swap target ordinal to new HeapColumn (carrying over Identity / Default / DefaultConstraint / GeneratedAs / IsHidden via constructor params + post-init DefaultConstraint assignment), RecomputeStorageProjections, run heap walk in try/catch with restore-and-rethrow rollback path. ALTER COLUMN of an IDENTITY column to a non-integer type rejected as NotSupportedException (the grammar already excludes IDENTITY itself via Msg 156 in real SQL Server). 31 new AlterTableColumnTests cover the full surface: type widening (varchar, int, varchar→nvarchar); narrowing fitting vs overflow (varchar Msg 2628, int Msg 220, decimal Msg 8115); conversion failures (varchar→int bad data Msg 245, varchar→date bad data Msg 241); nullability flip (NULL→NOT NULL with/without NULL data, NOT NULL→NULL, omitted-keyword preserves existing); blockers (PK Msg 5074, outgoing FK, incoming FK, non-unique index type change, computed-column dependency, computed column itself Msg 4928, rowversion Msg 4928); preservation (DEFAULT survives, identity counter survives + advances, inline CHECK still enforced post-alter, COLLATE parse-accepted, no-op alter, empty-table alter); plus an index-length-widening passes-under-index case. One pre-existing placeholder test (TemporalTableTests.AlterTable_UnsupportedShape_RaisesNotSupported) re-targeted from "alter column" to "rebuild" — REBUILD remains the deferred shape. CLAUDE.md "Not modeled" entry rewritten — ALTER TABLE now ships seven shapes; only REBUILD / SWITCH PARTITION / SET-versioning-on / ALTER COLUMN ADD-DROP sub-clauses / DROP PERIOD remain. Feature-reference trigger for alter-table.md extended to cover the new ALTER COLUMN surface. docs/claude/alter-table.md gains a full "ALTER COLUMN" section with grammar, conversion-fidelity matrix, blocker table (including the CHECK/DEFAULT non-blocker distinction from DROP COLUMN), rejection-paths table, preservation walkthrough, storage-rewrite mechanics, and four documented fidelity gaps (eager rewrite even for byte-identical widening, simplified index-protection nuance vs SQL Server's per-pair rules, deferred ADD/DROP sub-grammar, non-transactional column DDL).
1 parent 14e362f commit 7ae5038

8 files changed

Lines changed: 980 additions & 15 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,14 @@ High-fidelity emulation. Authenticity over desirability — when SQL Server's be
2222

2323
Behavioral claims below were probed against a live SQL Server 2025 reference instance unless flagged otherwise.
2424

25-
## Build / test / format
25+
## Build / test
2626

2727
```
2828
dotnet build
2929
dotnet test
30-
dotnet format --verify-no-changes
3130
```
3231

33-
`dotnet format whitespace` (IDE0055 + textual rules) lives outside the analyzer host — CI runs it separately. CI matrix: Debug + Release. If `obj/` permission errors appear, the user's been building outside the dev container; `rm -rf obj/ bin/` clears them.
32+
Every `.csproj` sets `EnforceCodeStyleInBuild=true`, so `dotnet build` runs the IDE / SSS / MSTEST analyzer rules and fails on violations. No separate `dotnet format` pass is needed — running it costs seconds and catches nothing build doesn't already. CI matrix: Debug + Release. If `obj/` permission errors appear, the user's been building outside the dev container; `rm -rf obj/ bin/` clears them.
3433

3534
## Architecture — load-bearing patterns
3635

@@ -152,7 +151,7 @@ Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger:
152151
- **Touching `CREATE TRIGGER` / `ALTER TRIGGER` / `DROP TRIGGER` / `DISABLE`/`ENABLE TRIGGER`, the `INSERTED` / `DELETED` pseudo-table materialization, the `TriggerFrame` / `FiringTriggerIds` recursion guard, `TRIGGER_NESTLEVEL()`, or `sys.triggers`**[`docs/claude/triggers.md`](docs/claude/triggers.md).
153152
- **Touching `FOREIGN KEY` parsing (`REFERENCES` inline / `FOREIGN KEY (cols) REFERENCES other(cols)` table-level), referential-action wiring (`ON DELETE` / `ON UPDATE` × `NO ACTION` / `CASCADE` / `SET NULL` / `SET DEFAULT`), the FK enforcement loop in `Simulation.ForeignKeys.cs`, the `HeapTable.OutgoingForeignKeys` / `IncomingForeignKeys` lists, cascade-cycle detection (Msg 1785), DROP-TABLE protection (Msg 3726), or `sys.foreign_keys` / `sys.foreign_key_columns`**[`docs/claude/foreign-keys.md`](docs/claude/foreign-keys.md).
154153
- **Touching `PERIOD FOR SYSTEM_TIME` / `GENERATED ALWAYS AS ROW START / END [HIDDEN]` / `WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = …))` DDL, `ALTER TABLE … SET (SYSTEM_VERSIONING = OFF)`, the auto-created history sibling, `FOR SYSTEM_TIME ALL / AS OF` query syntax, or `HeapTable.PeriodColumns` / `HeapTable.SystemVersioning` / `HeapColumn.GeneratedAs` / `HeapColumn.IsHidden`**[`docs/claude/temporal-tables.md`](docs/claude/temporal-tables.md).
155-
- **Touching `ALTER TABLE … ADD CONSTRAINT` (PK / UQ / FK / CHECK / DEFAULT), `ALTER TABLE … DROP CONSTRAINT [IF EXISTS]`, `ALTER TABLE … (CHECK | NOCHECK) CONSTRAINT (ALL | name [,…])` trust toggling, `WITH CHECK` / `WITH NOCHECK`, the existing-data-validation scan, the constraint-name uniqueness check, the atomic multi-drop / multi-toggle pipeline, the `IsDisabled` / `IsNotTrusted` enforcement gates, or `sys.check_constraints` / `sys.key_constraints` / `sys.default_constraints`**[`docs/claude/alter-table.md`](docs/claude/alter-table.md).
154+
- **Touching `ALTER TABLE … ADD CONSTRAINT` (PK / UQ / FK / CHECK / DEFAULT), `ALTER TABLE … DROP CONSTRAINT [IF EXISTS]`, `ALTER TABLE … (CHECK | NOCHECK) CONSTRAINT (ALL | name [,…])` trust toggling, `ALTER TABLE … ADD [COLUMN]` / `DROP COLUMN` / `ALTER COLUMN`, `WITH CHECK` / `WITH NOCHECK`, the existing-data-validation scan, the constraint-name uniqueness check, the atomic multi-drop / multi-toggle pipeline, the `IsDisabled` / `IsNotTrusted` enforcement gates, the type-change conversion (`SqlValue.CoerceTo` surfacing Msg 220 / 245 / 241 / 8115 / 2628), the nullability-flip Msg 515 scan, the Msg 4928 COMPUTED / rowversion rejection, the Msg 5074 ALTER COLUMN blocker walker (PK/UQ/FK/computed-deps unconditional; index when SqlType-subclass changes), or `sys.check_constraints` / `sys.key_constraints` / `sys.default_constraints`**[`docs/claude/alter-table.md`](docs/claude/alter-table.md).
156155
- **Touching `CREATE [UNIQUE] [CLUSTERED | NONCLUSTERED] INDEX` (with `INCLUDE` / `WHERE` filter / `WITH (options)` clauses), `DROP INDEX [IF EXISTS] name ON table [, …]`, the `HeapTable.Indexes` list, UNIQUE-index enforcement (filter-aware Msg 2601 via `EnforceUniqueIndexes` / `EnforceUniqueIndexesForUpdate`), the PK-or-UQ-name DROP rejection (Msg 3723), or `sys.indexes` / `sys.index_columns`**[`docs/claude/indexes.md`](docs/claude/indexes.md).
157156
- **Adding a new top-level statement parser or changing the dispatch loop's statement-separator rules**[`docs/claude/grammar.md`](docs/claude/grammar.md) + [`docs/claude/control-flow.md`](docs/claude/control-flow.md).
158157

@@ -178,7 +177,7 @@ Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger:
178177
- T-SQL `GOTO` / labels — `IF` / `BEGIN…END` / `WHILE` / `BREAK` / `CONTINUE` / `RETURN` (bare + UDF-body and stored-procedure-body value form) ship; unconditional jumps don't.
179178
- CLR functions, INSTEAD OF UPDATE / DELETE on *non-updatable* views (INSTEAD OF INSERT on any view ships; INSTEAD OF UPDATE / DELETE on updatable single-base views ships). DDL / logon triggers also unmodeled. Scalar UDFs, inline TVFs, multi-statement TVFs (`RETURNS @r TABLE (cols) AS BEGIN ... END` — EF Core `HasDbFunction` end-to-end), views, DML-through-views (single-source updatable shape), stored procedures (including CREATE / ALTER / DROP, EXEC with input/output/default params, `@rc = EXEC` return-code capture, `CommandType.StoredProcedure`, `EXEC (@sql)` and `sp_executesql` dynamic SQL), user-defined table types + table-valued parameters (CREATE TYPE … AS TABLE / DROP TYPE / DECLARE @t MyType / READONLY proc params / EXEC with TVP arg / ADO.NET Structured parameter via the `DbParameter.TypeName` C# 14 extension property + DataTable/IDataReader sources), sequence objects (CREATE / ALTER / DROP SEQUENCE / NEXT VALUE FOR / sys.sequences — supports EF Core HiLo identity strategy end-to-end), and DML triggers (CREATE / ALTER / CREATE OR ALTER / DROP TRIGGER, FOR-as-AFTER synonym, AFTER on heap tables + INSTEAD OF on heap tables / views, multi-action INSERT,UPDATE,DELETE, INSERTED/DELETED pseudo-tables, multiple AFTER triggers per parent / one INSTEAD OF per action per target (Msg 2111), DISABLE/ENABLE TRIGGER, TRIGGER_NESTLEVEL(), direct-recursion suppression matching RECURSIVE_TRIGGERS OFF, sys.triggers + sys.objects integration, trigger-error rolls back firing DML, MERGE per-action routing through INSTEAD OF, DROP TABLE / DROP VIEW cascade-drops attached triggers) ship (see `docs/claude/programmable.md`, `docs/claude/table-valued-parameters.md`, `docs/claude/sequences.md`, and `docs/claude/triggers.md`). JOIN-view single-base-table UPDATE/DELETE, OUTPUT through views, and multi-source alias-form UPDATE/DELETE through views are deferred (Msg 4405 or `NotSupportedException` at the DML site). `BEGIN ATOMIC` / `BEGIN DISTRIBUTED TRANSACTION` raise `NotSupportedException` at dispatch. Value-form `RETURN N` is legal inside a scalar-UDF body and a stored-procedure body; bare batch / dynamic-SQL scope raises Msg 178. TRY/CATCH + THROW + RAISERROR (printf-style `%s %d/%i %u %o %x %X %% %ld %I64d` with width / precision / left-align / zero-pad; severity routing 0-10 informational vs 11-18 catchable; `WITH SETERROR`/`NOWAIT` ship, `WITH LOG` uniformly raises Msg 2778; numeric `msg_id` raises Msg 2732 for 50000 / `< 13000` and Msg 18054 otherwise — `sys.messages` registry / `sp_addmessage` not modeled) + live `@@ERROR` + `ERROR_*()` functions ship (see `docs/claude/control-flow.md`).
180179
- **`PRINT` message capture** — the statement parses + evaluates the operand (so operand-side errors like Msg 245 surface), but the message is discarded. `DbConnection` has no `InfoMessage` event (that's a `SqlConnection` extension), so adding a public observability surface would mean a new event on `SimulatedDbConnection`. Defer until an application needs it.
181-
- **`ALTER TABLE` grammar beyond SET / ADD / DROP / CHECK / NOCHECK CONSTRAINT** — `SET (SYSTEM_VERSIONING = OFF)` (see [`docs/claude/temporal-tables.md`](docs/claude/temporal-tables.md)), `[WITH CHECK | WITH NOCHECK] ADD [CONSTRAINT name] (PRIMARY KEY | UNIQUE | FOREIGN KEY | CHECK | DEFAULT) …`, `DROP CONSTRAINT [IF EXISTS] name [, …]`, `[WITH CHECK | WITH NOCHECK] (CHECK | NOCHECK) CONSTRAINT (ALL | name [, …])` (trust toggling for bulk imports — sets / clears `is_disabled` + `is_not_trusted`), `ADD [COLUMN] col TYPE [, …]` (multi-column add — inline DEFAULT / IDENTITY / CHECK / FK / computed all supported; Msg 4901 on NOT NULL without DEFAULT on non-empty table; eager row rewrite with NULL backfill for nullable adds and constant-snapshot DEFAULT for NOT NULL), and `DROP COLUMN [IF EXISTS] col [, …]` (Msg 4924 missing column, Msg 5074 dependency rejection with multi-blocker enumeration across PK/UQ/FK/CHECK/DEFAULT/index, atomic multi-drop) all ship (see [`docs/claude/alter-table.md`](docs/claude/alter-table.md)). ALTER COLUMN, DROP PERIOD FOR SYSTEM_TIME, REBUILD, SWITCH PARTITION, the SET-versioning-on direction, and every other shape raise `NotSupportedException`. Multi-constraint ADD (`ADD CONSTRAINT pk1 PK (a), CONSTRAINT fk1 FK …`) also raises.
180+
- **`ALTER TABLE` grammar beyond SET / ADD / DROP / ALTER COLUMN / CHECK / NOCHECK CONSTRAINT** — `SET (SYSTEM_VERSIONING = OFF)` (see [`docs/claude/temporal-tables.md`](docs/claude/temporal-tables.md)), `[WITH CHECK | WITH NOCHECK] ADD [CONSTRAINT name] (PRIMARY KEY | UNIQUE | FOREIGN KEY | CHECK | DEFAULT) …`, `DROP CONSTRAINT [IF EXISTS] name [, …]`, `[WITH CHECK | WITH NOCHECK] (CHECK | NOCHECK) CONSTRAINT (ALL | name [, …])` (trust toggling for bulk imports — sets / clears `is_disabled` + `is_not_trusted`), `ADD [COLUMN] col TYPE [, …]` (multi-column add — inline DEFAULT / IDENTITY / CHECK / FK / computed all supported; Msg 4901 on NOT NULL without DEFAULT on non-empty table; eager row rewrite with NULL backfill for nullable adds and constant-snapshot DEFAULT for NOT NULL), `DROP COLUMN [IF EXISTS] col [, …]` (Msg 4924 missing column, Msg 5074 dependency rejection with multi-blocker enumeration across PK/UQ/FK/CHECK/DEFAULT/index, atomic multi-drop), and `ALTER COLUMN col TYPE[(prec[,scale])] [COLLATE coll] [NULL|NOT NULL]` (single-column shape — type widening / narrowing routed through `SqlValue.CoerceTo` so Msg 220 / 245 / 241 / 8115 / 2628 surface verbatim; nullability flip raises Msg 515 on existing NULL; Msg 4928 for COMPUTED / rowversion; Msg 5074 multi-blocker enumeration across PK / UQ / FK both directions / computed-column dependencies / type-changing index references — length widening within the same `SqlType` family allowed under an index; COLLATE clause parse-accepted then ignored; identity / DEFAULT / inline CHECK preserve through the column instance swap) all ship (see [`docs/claude/alter-table.md`](docs/claude/alter-table.md)). DROP PERIOD FOR SYSTEM_TIME, REBUILD, SWITCH PARTITION, the SET-versioning-on direction, the `ALTER COLUMN col ADD/DROP {PERSISTED|MASKED|ROWGUIDCOL|SPARSE}` sub-clause forms, ALTER COLUMN on an identity column targeting a non-integer type, and every other shape raise `NotSupportedException`. Multi-constraint ADD (`ADD CONSTRAINT pk1 PK (a), CONSTRAINT fk1 FK …`) also raises.
182181
- `hierarchyid`, `geography`, `geometry`.
183182

184183
## Quirks (modeled, not byte-identical to SQL Server)

0 commit comments

Comments
 (0)