parser: reject inline column-level REFERENCES at parse time#92
Conversation
MySQL parses but ignores inline column-level REFERENCES (e.g. `user_id BIGINT REFERENCES users(id)`); the official 8.0 reference manual `ansi-diff-foreign-keys.html` calls it "a memo or comment ... no actual effect." Earlier myschema rescued the shape by promoting it to an explicit-named ALTER TABLE ADD CONSTRAINT, but that diverged from MySQL's semantics on two fronts: - The user thought they had an FK; MySQL alone wouldn't have created one. myschema's desired-side parse drifted from "what MySQL would do with the same SQL." - The rescued FK's auto-name (`<table>_ibfk_<col>`) didn't match MySQL's auto-name for table-level unnamed FKs created externally (`<table>_ibfk_<n>`), so importing an externally-managed schema saw a one-shot no-op DROP+ADD on first plan. Reject the shape outright with a helpful error pointing at the table-level form. Knock-on changes: - parseColumnDef errors when `cd.Type.Options.Reference != nil`. - The dead rescue branch in parseCreateTable is removed; the table-level `FOREIGN KEY (...)` path (handled by addTableConstraint) is unaffected and still uses `_ibfk_<col>` auto-naming. - TestParseInlineForeignKey rewritten as TestParseInlineColumnReferencesRejected. - CAVEATS.md gets a new "Inline column-level REFERENCES is rejected" entry; AGENTS.md "In scope (v1)" trims REFERENCES from the inline-column list. - kitchen_sink_round_trip.yml's "intentional omissions" comment swaps the auto-name-drift rationale for the parse-time rejection one. - TODO.md drops the now-fixed bug entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #92 +/- ##
==========================================
+ Coverage 86.90% 87.00% +0.09%
==========================================
Files 30 30
Lines 3277 3271 -6
==========================================
- Hits 2848 2846 -2
+ Misses 263 261 -2
+ Partials 166 164 -2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR tightens desired-SQL fidelity with MySQL by rejecting inline column-level REFERENCES during parsing, since MySQL accepts but ignores that syntax. This prevents myschema from “rescuing” a clause into a real FK (and potentially introducing name drift vs externally-created schemas).
Changes:
- Reject inline column-level
REFERENCESat parse time with an actionable error message pointing users to the table-levelFOREIGN KEY (...) REFERENCES ...form. - Remove the previous inline-FK rescue logic and update/rename the parser test accordingly.
- Update docs/fixtures to reflect the new rejection behavior and remove the related TODO entry.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| TODO.md | Removes the previous inline-REFERENCES auto-name drift TODO entry. |
| testdata/apply/kitchen_sink_round_trip.yml | Updates fixture commentary to match the new parse-time rejection behavior. |
| parser/parser.go | Drops inline-FK rescue branch and adds a parse-time rejection in parseColumnDef. |
| parser/parser_test.go | Renames/repurposes the inline-REFERENCES test to assert rejection and check the error message. |
| CAVEATS.md | Documents that inline column-level REFERENCES is rejected and explains rationale/workarounds. |
| AGENTS.md | Updates “In scope (v1)” docs to remove inline REFERENCES and point to the caveat. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- parser_test.go: drop the stale leading comment that still referenced the renamed `TestParseInlineForeignKey` and the rescue-side auto-naming claim. - CAVEATS.md: add a "Note on the unnamed table-level form" flagging that myschema's parser auto-name (`_ibfk_<col>`) differs from MySQL's auto-name (`_ibfk_<n>`), so importing an externally-created schema with unnamed table-level FKs produces a one-shot no-op DROP+ADD on the first plan. Recommend explicit `CONSTRAINT fk_xxx` names or `dump → apply` to skip it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- parser.go: error message now embeds the full URL (https://dev.mysql.com/doc/refman/8.0/en/ansi-diff-foreign-keys.html) instead of the abbreviated `dev.mysql.com docs ansi-diff-foreign-keys` so users can click or grep it directly. - CAVEATS.md: replace the lossy `…` placeholder in the error example with the exact emitted string (just wrapped for display width), and label it as such. The "Why" section also turns the page citation into a proper markdown link. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Three accuracy fixes in CAVEATS.md: - The "exact error string" block omitted the `table <db>.<table>: column <col>:` prefix that parseCreateTable wraps around column-level errors. Updated to show the full wrapped form so a user grepping the error message sees what they'll actually encounter. - "no-op DROP+ADD" was misleading in two places: dropping + re-adding an FK is real DDL (brief table lock + full row re-verification against the FK), not a no-op in execution cost — only the end state is functionally equivalent. Reworded to "redundant DROP+ADD" with a brief note on the actual operational impact, both in the "rescue regression" rationale and in the unnamed-table-level-FK note. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
PR #92 made inline column-level `REFERENCES` error at parse time (CAVEATS.md "Inline column-level REFERENCES is rejected"), so the third sub-bullet under "parseCreateTable column-loop interactions" — `inline REFERENCES × NOT NULL × ON UPDATE` — is no longer a reachable code shape. Drop the bullet, trim the buildFK reference from the parent description, and add a note explaining why the combination went away so the audit history stays legible. The other two sub-bullets (inline UNIQUE × DEFAULT, inline PK × column-level CHECK) are unaffected and still open. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
MySQL silently ignores inline column-level `REFERENCES` (the SQL-standard `col INT REFERENCES other(id)` shape). The official 8.0 manual (ansi-diff-foreign-keys.html) calls it "a memo or comment ... no actual effect."
myschema previously rescued the shape by promoting it to an explicit-named `ALTER TABLE ADD CONSTRAINT`, but that produced two real bugs:
This PR rejects the shape outright with a helpful error pointing at the table-level form.
Changes
Breaking change
Users who previously wrote inline `col TYPE REFERENCES other(col)` in desired SQL will now see a parse error. The fix is mechanical — move the clause to a table-level `[CONSTRAINT name] FOREIGN KEY (col) REFERENCES other(col)` line. `mysqldump` output already uses the table-level form, so dump → apply round-trips are unaffected.
Test plan
🤖 Generated with Claude Code