Skip to content

parser: honour inline column-level PRIMARY KEY / UNIQUE#81

Merged
winebarrel merged 4 commits into
mainfrom
fix-inline-column-pk-unique
May 5, 2026
Merged

parser: honour inline column-level PRIMARY KEY / UNIQUE#81
winebarrel merged 4 commits into
mainfrom
fix-inline-column-pk-unique

Conversation

@winebarrel

Copy link
Copy Markdown
Owner

Summary

AGENTS.md "In scope (v1)" lists "inline column-level PK / UNIQUE / REFERENCES", but only REFERENCES was actually wired up. parser/parser.go's column loop checked cd.Type.Options.Reference (FK) but ignored cd.Type.Options.KeyOpt, so vitess's ColKeyPrimary / ColKeyUnique / ColKeyUniqueKey silently dropped:

echo 'CREATE TABLE t (id INT NOT NULL PRIMARY KEY);' > desired.sql
myschema apply desired.sql
mysql ... -e 'SHOW CREATE TABLE t'
# CREATE TABLE `t` (
#   `id` int NOT NULL
# )                  ← no PK

This PR adds applyInlineColumnKey to mirror the table-level path's behaviour:

  • ColKeyPrimary → PRIMARY constraint + PRIMARY index, column forced to NotNull (MySQL implicitly does this for PK columns; mirror so dump round-trips stay stable).
  • ColKeyUnique / ColKeyUniqueKey → UNIQUE index named after the column (matches MySQL's auto-naming and the table-level path at parser.go's IndexTypeUnique case).
  • Other ColKey* values (None, spatial, fulltext, bare Key) aren't valid column-level attributes — pass through silently to match the parser's existing "ignore unmodelled shapes" policy.

Test plan

  • Unit tests: TestParseInlineColumnPrimaryKey (explicit + implicit NOT NULL), TestParseInlineColumnUnique (UNIQUE + UNIQUE KEY keywords).
  • Integration fixture: testdata/apply/inline_column_primary_key_and_unique.yml exercises plan → apply → re-plan-empty round-trip with verify_no_drift: true. Without the fix the inline PK / UNIQUE silently drop and the round-trip never closes.
  • make lint clean, make test all six packages green.
  • Manual SHOW CREATE TABLE after myschema apply against MySQL 8.0 confirms PRIMARY KEY and UNIQUE KEY email land as expected.

🤖 Generated with Claude Code

AGENTS.md "In scope (v1)" lists "inline column-level PK / UNIQUE /
REFERENCES", but only the REFERENCES path was actually wired up
(parser/parser.go's column loop checked `cd.Type.Options.Reference`).
vitess parses `id INT PRIMARY KEY` and `email VARCHAR(255) UNIQUE`
as a column attribute (`cd.Type.Options.KeyOpt = ColKeyPrimary` /
`ColKeyUnique` / `ColKeyUniqueKey`) rather than promoting them to
the table-level Indexes / Constraints buckets, so both forms were
silently dropped. Plan / apply / dump confirmed (`SHOW CREATE
TABLE` after apply showed neither the PK nor the UNIQUE index).

Add `applyInlineColumnKey` to mirror the table-level path's
behaviour from `addIndex`'s IndexTypePrimary / IndexTypeUnique
cases:

- `ColKeyPrimary` → PRIMARY constraint + PRIMARY index, with the
  column forced to NotNull (MySQL silently does that for PK
  columns; mirror so the dump round-trip stays stable).
- `ColKeyUnique` / `ColKeyUniqueKey` → UNIQUE index named after the
  column (matches MySQL's auto-naming and the table-level UNIQUE
  case at parser.go's IndexTypeUnique branch).
- Other ColKey* values (None, Spatial, Fulltext, bare Key) aren't
  valid column-level MySQL attributes — pass through silently to
  match the parser's existing "ignore unmodelled shapes" policy.

Tests: TestParseInlineColumnPrimaryKey / TestParseInlineColumnUnique
in parser_test.go pin the parsed model. Integration fixture
testdata/apply/inline_column_primary_key_and_unique.yml exercises
the full plan → apply → re-plan-empty round-trip; without the fix
the inline PK / UNIQUE silently drop and verify_no_drift catches
the regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 5, 2026 12:33
@codecov

codecov Bot commented May 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.09677% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.62%. Comparing base (32fd267) to head (1223520).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
parser/parser.go 87.09% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main      #81   +/-   ##
=======================================
  Coverage   86.62%   86.62%           
=======================================
  Files          30       30           
  Lines        3237     3268   +31     
=======================================
+ Hits         2804     2831   +27     
- Misses        267      269    +2     
- Partials      166      168    +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the SQL parser to correctly promote Vitess’s inline column-level key attributes (PRIMARY KEY, UNIQUE [KEY]) into myschema’s internal table model, preventing these constraints from being silently dropped during apply and ensuring stable plan/apply/dump round-trips.

Changes:

  • Parse inline column-level PRIMARY KEY / UNIQUE [KEY] via a new applyInlineColumnKey helper in parser/parser.go.
  • Add unit coverage for inline PK/UNIQUE promotion and PK-implied NOT NULL behavior.
  • Add an end-to-end apply fixture that verifies no drift when inline PK/UNIQUE are present.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
parser/parser.go Promotes inline column KeyOpt (PRIMARY/UNIQUE) into Constraints/Indexes during CREATE TABLE parsing.
parser/parser_test.go Adds unit tests validating inline PK/UNIQUE promotion and PK-implied NOT NULL.
testdata/apply/inline_column_primary_key_and_unique.yml Adds integration fixture to pin round-trip behavior and prevent regression.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread parser/parser.go Outdated
Comment thread parser/parser_test.go Outdated
Comment thread parser/parser_test.go Outdated
- applyInlineColumnKey: rewrite the duplicate-handling comment.
  The previous text claimed addIndex's IndexTypePrimary case has an
  "existing PRIMARY KEY equality check" that would let the duplicate
  through — that's wrong, addIndex unconditionally overwrites
  Constraints["PRIMARY"] / Indexes["PRIMARY"]. The skip is still
  correct (same end state when both forms reference the same column,
  MySQL-rejected at apply when they don't); explain that instead.
- TestParseInlineColumn{PrimaryKey,Unique} comments referenced
  parser.go:608 / parser.go:617. Replace with stable references
  ("addIndex's IndexTypePrimary case", "addIndex's IndexTypeUnique
  case") so the comments don't rot when the file is edited.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@winebarrel winebarrel requested a review from Copilot May 5, 2026 12:46
Coverage gap audit on the inline column-level PK / UNIQUE work:

- New `AUTO_INCREMENT combines with inline PRIMARY KEY` sub-test
  pins the most common real-world surrogate-key shape
  (`id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY`). vitess holds
  KeyOpt and Autoincrement on different fields, so the promotion
  must not clobber AutoIncrement — pin so a refactor that mishandles
  the column attributes surfaces in CI.
- New `two inline PRIMARY KEYs: first column wins` sub-test reaches
  applyInlineColumnKey's idempotent skip branch (previously
  uncovered — vitess parses two-inline-PK SQL without error even
  though MySQL rejects at apply, so the second column hits the
  skip-when-exists guard). Pins the parser-side "first wins"
  precedence; MySQL still surfaces the duplicate-PK at apply.
- Document the dup-index guard in applyInlineColumnKey's
  ColKeyUnique case as defensive: it's unreachable in the current
  architecture (column loop runs before table-level Indexes; column
  names are unique-checked upstream), kept for symmetry with
  addIndex's IndexTypeUnique branch and to fail closed under a
  future pass-order refactor.

applyInlineColumnKey coverage: 84.6% → 92.3%. The remaining 7.7%
is the documented unreachable defensive branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread testdata/apply/inline_column_primary_key_and_unique.yml
winebarrel added a commit that referenced this pull request May 5, 2026
Audit triggered by the inline column-level PRIMARY KEY / UNIQUE bug
fix on PR #81. Probed adjacent column / table / index attributes
that vitess parses but myschema might silently drop. Results:

High (silent data loss, not just feature drop):
- vitess `Parse2` swallows some syntax errors as WARN logs and
  returns a partially-parsed AST; myschema treats that as success
  and can emit an empty `CREATE TABLE t ();` for inputs vitess
  rejects. Repro: COLUMN_FORMAT + STORAGE on a column.

Medium (silent fidelity gaps; parser populates the model but the
emitter / catalog reader don't):
- Column-level INVISIBLE (MySQL 8.0+) dropped — catalog has it
  via EXTRA, parser side doesn't.
- SRID on spatial columns dropped — catalog surfaces it via
  ST_GEOMETRY_COLUMNS, parser side doesn't read
  `Options.SRID`.
- Table-level ROW_FORMAT / KEY_BLOCK_SIZE / COMPRESSION /
  ENCRYPTION dropped — `applyTableOption` only handles
  ENGINE / CHARSET / COLLATE / COMMENT / AUTO_INCREMENT.
- Index-level KEY_BLOCK_SIZE dropped — `addIndex` doesn't read
  the option, `Index.SQL()` doesn't emit it.

Each entry includes the repro path, where in the code to look,
and the fix sketch. Logged to TODO so the next contributor
working in that area can pick them up; no code change here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
winebarrel added a commit that referenced this pull request May 5, 2026
docs: log silent-drop audit findings (TODO follow-ups from PR #81)
@winebarrel winebarrel requested a review from Copilot May 5, 2026 12:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread parser/parser_test.go Outdated
The previous comment opened with "Both columns get NotNull forced"
which is wrong — only column `a` (the first inline PK) gets the
forced NotNull from its own promotion. Column `b` hits the skip
branch in applyInlineColumnKey and is left untouched; its NotNull
flag stays at whatever vitess parsed (here, implicit nullable INT
because the source SQL doesn't write NOT NULL on `b`).

Reword so the lead matches the rest of the comment instead of
contradicting it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 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.

@winebarrel winebarrel merged commit 23c3b4b into main May 5, 2026
9 checks passed
@winebarrel winebarrel deleted the fix-inline-column-pk-unique branch May 5, 2026 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants