Fix migration tool to honor NamingStyle and MapTo (#324)#327
Open
DJGosnell wants to merge 9 commits into
Open
Conversation
The migration tool's ProjectSchemaReader read the naming style from a property literally named 'Naming' (real API is NamingStyle) and never parsed MapTo, so snapshots/DDL used C# property names while the runtime uses physical names. - ExtractTableDef: detect the real NamingStyle override, parsing the enum value from the expression/getter body (mirrors runtime SchemaParser). - ExtractColumnDef: parse MapTo (chained .MapTo and standalone MapTo<T>, the latter via a new GenericNameSyntax case); set the physical column name (MapTo ?? styled(prop)) and populate ColumnDef.MappedName. - Ref FK constraint column now reuses the resolved column name so it stays consistent when a Ref uses MapTo/NamingStyle. - De-mask the 3 ProjectSchemaReaderIndexTests naming tests: the fictional 'public NamingStyle Naming' becomes the real 'protected override NamingStyle NamingStyle', so they now exercise the real API. Step 1 of 4.
Covers: NamingStyle.SnakeCase styling with null MappedName; MapTo in standalone generic (MapTo<T>), chained (.MapTo after a modifier), and generic-not-outermost forms; MapTo overriding the naming style per column; and an Exact/no-MapTo regression guard. Step 2 of 4.
Extracts snapshots via ProjectSchemaReader with and without MapTo and asserts SchemaDiffer.Diff produces a RenameColumn (or drop+add) in both directions, never zero steps. Step 3 of 4.
Compiles the real committed AccountSchema.cs + Money.cs (loaded via CallerFilePath) with a minimal UserSchema stub, and asserts the tool extracts credit_limit (not CreditLimit) and that the generated migration code carries the physical name — matching the runtime SQL already covered by CrossDialectSchemaTests.Select_MapToColumn_CreditLimit. Step 4 of 4.
Addresses review findings F1-F8: - F2/F7 (A): tighten ProjectSchemaReader NamingStyle detection to mirror SchemaParser.ExtractNamingStyle exactly — gate on !IsStatic && IsOverride and read only an expression-bodied member access. The prior version also honored initializer/getter-arrow forms the runtime ignores, which would let migration and runtime disagree on column names (the #324 bug inverted). - F4/F5 (B): add parity tests — snake_case driven through MigrationCodeGenerator, and getter-arrow / non-override / non-literal-MapTo forms asserting Exact / null MappedName (matching runtime). - F1 (B): record the Step-4 sample-compilation decision in Working Notes. - F8 (B): behavioral-correction note deferred to PR body. - F3/F6 (D): dismissed (see review.md).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the migration tool's
ProjectSchemaReaderhonor the two column-naming mechanisms the runtimesource generator already honors — the
NamingStyleoverride and per-columnMapTo("physical")— soquarry migrate add/diffproduce snapshots and DDL whose column names match the physical names theruntime actually queries.
Reason for Change
The migration reader detected the naming style from a property literally named
Naming(the real API isNamingStyle) and never parsedMapTo. As a result:UserName,CreditLimit) while the runtime emits SQL usingphysical names (
user_name,credit_limit) — a generated migration created columns the runtimenever queries.
silently ignored, so the physical column the runtime targets was never created or renamed.
Impact
src/Quarry.Tool/Schema/ProjectSchemaReader.csExtractTableDefnow detects the realNamingStyleoverride, mirroring the runtimeSchemaParser.ExtractNamingStyleexactly: it gates on!IsStatic && IsOverrideand reads only anexpression-bodied member access (
protected override NamingStyle NamingStyle => NamingStyle.SnakeCase;).Honoring any looser form than the runtime would let the two disagree on column names again.
ExtractColumnDefnow parsesMapTo— both the chained.MapTo("x")and the standalone genericMapTo<T>("x")(via a newGenericNameSyntaxcase, mirroring runtimeGetMethodName). The physicalcolumn name becomes
MapTo ?? ToColumnName(prop, style)andColumnDef.MappedNameis populated so thesnapshot re-emits
.MapTo(...)andSchemaHasher/RenameMatcherstay faithful.Ref's foreign-key constraint column now reuses the resolved column name, so it stays consistentwhen a Ref uses
MapTo/NamingStyle.migrate addandmigrate diff(both callProjectSchemaReader.ExtractSchemaSnapshot).No changes were needed to
SchemaDiffer,DdlRenderer,MigrationCodeGenerator,SnapshotCodeGenerator,or the
ColumnDefmodel — they already consumeName/MappedNamecorrectly (the migration DDL name isgenerated from
ColumnDef.Name). The runtimeSchemaParserand theQuarry.Migration/SchemaResolver(SchemaMap for result materialization) already honored both mechanisms and are untouched.
Plan items implemented as specified
NamingStyleandMapToinProjectSchemaReader; de-mask the three naming tests inProjectSchemaReaderIndexTests(fictionalNamingStyle Naming→ the realoverride NamingStyle).NamingStyleandMapTo(chained, standalone-generic, override-vs-style precedence,and Exact regression).
MapToadd/remove produces a rename-or-drop+add, never a silent no-op.AccountSchema(credit_limit) and asserting theextracted name and the generated migration code carry the physical name — matching the runtime SQL
already covered by
CrossDialectSchemaTests.Select_MapToColumn_CreditLimit.Gaps in original plan implemented
DatabaseSchemaReader.cs/NormalizeForDiff) was stale — no suchfile/method exists. The diff already keys columns off
ColumnDef.NameinSchemaDiffer, so onceNamecarries the physical name, add/remove-
MapTodiffs stop being no-ops with no change needed there.NamingStyledetection was more permissive than the runtime (it also readinitializer/getter-arrow forms and skipped the override guard) — itself a divergence risk. Tightened to
mirror the runtime exactly, with added parity tests for getter-arrow, non-override, and non-literal-
MapToforms.
Breaking Changes
migrate addon a schema usingNamingStyleorMapTohassnapshots recorded with PascalCase (property) column names. After this fix, its next
migrate addwill show a physical-name diff — column rename (or drop+add), foreign-key constraint-name shifts
(
FK_{table}_{physicalName}), and a changed snapshot hash chain (SchemaHasherincludesMappedName).Those projects were already broken (their migrations diverged from what the runtime queries), so this is a
one-time correction, not a regression. Projects using
Exactnaming and noMapToare unaffected(verified: the committed
1_SimpleMigration,Quarry.Sample.Aot,Quarry.Sample.WebAppmigrations useExact; the snake_case andMapTosamples have no committed migrations).