Commit 7ae5038
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
- SqlServerSimulator.Tests
- SqlServerSimulator
- Errors
- Simulation
- docs/claude
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
31 | 30 | | |
32 | 31 | | |
33 | | - | |
| 32 | + | |
34 | 33 | | |
35 | 34 | | |
36 | 35 | | |
| |||
152 | 151 | | |
153 | 152 | | |
154 | 153 | | |
155 | | - | |
| 154 | + | |
156 | 155 | | |
157 | 156 | | |
158 | 157 | | |
| |||
178 | 177 | | |
179 | 178 | | |
180 | 179 | | |
181 | | - | |
| 180 | + | |
182 | 181 | | |
183 | 182 | | |
184 | 183 | | |
| |||
0 commit comments