parser/diff: implement -- myschema:renamed-from directive#27
Conversation
Adds a comment-directive system shaped to grow into a small directive
family (renamed-from now, execute later) and uses it to wire up real
in-place renames for tables, columns, and secondary indexes — the
three MySQL objects that have a true ALTER … RENAME statement and
where DROP+CREATE would lose data.
parser/directive.go
- knownDirectives registry (one entry today: renamed-from)
- ValidateDirectives() rejects -- myschema:<unknown> at parse-front
- ExtractStmtRenameFrom() pulls the leading-comment-block directive
that attaches to a CREATE TABLE statement
- ExtractInlineRenameFrom() walks the parenthesised CREATE TABLE
body and collects "the directive on the line above" → "the first
identifier on the next non-comment line", which works for column
lines, KEY/INDEX/UNIQUE/etc. lines, and CONSTRAINT … lines.
parser/parser.go
- calls ValidateDirectives() once over the input (typo'd directive
names fail loudly instead of being silently ignored)
- per-statement, extracts the statement-level directive (table) and
inline directives (columns / indexes), attaches them to the new
Table / Column / Index models. Constraint and FK directives are
silently dropped for now — see TODO.md for the follow-up.
model/{table,column,index}.go
- RenameFrom *string field on each. Always nil on the catalog side.
diff/rename.go (new)
- applyTableRenames / applyColumnRenames / applyIndexRenames each
emit ALTER TABLE … RENAME { TO | COLUMN | INDEX } and re-key the
matching current-side entry so the rest of the diff sees the
object under its new name. Idempotent re-apply (rename already
happened) is a no-op; missing source name and destination
collision both error out so a typo doesn't silently degrade into
a destructive DROP+CREATE.
- rewriteIndexColumnRefs() rewrites IndexPart.Column references in
current after a column rename, so an index that "covers the same
column under the new name" doesn't subsequently mismatch and
trigger a needless DROP INDEX + CREATE INDEX.
diff/tables.go
- DiffTables runs applyTableRenames before the new/modified/dropped
classification so renamed tables don't show as drop+create.
- diffTable runs column rename → index-ref rewrite → index rename
before the regular column / constraint / index / FK passes.
- diffTable now returns (*tableDiffResult, error) so rename errors
propagate up.
testdata/apply/rename_{table,column,index}.yml
- Three end-to-end fixtures with verify_no_drift: true. Confirms
the rename DDL is what we generate and that the post-apply state
matches desired (data preserved).
diff/rename_test.go
- Go-level coverage for the rename pass, including missing-source
error, idempotent re-apply, and the index-part rewrite that
suppresses DROP+CREATE of an unchanged index after a column
rename.
parser/directive_test.go
- Lexer coverage: known/unknown directive validation, plain-comment
pass-through, statement-level extraction, inline extraction for
columns / KEY / UNIQUE KEY / CONSTRAINT, position guard (a
directive *after* a SQL line doesn't accidentally attach).
AGENTS.md / TODO.md
- Document renamed-from in "In scope (v1)" with the scope and
error-out semantics; replace the old "Renaming via directives"
cut with the constraint/FK follow-up + execute-directive note.
- TODO entry is replaced with the constraint/FK extension.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #27 +/- ##
==========================================
+ Coverage 70.37% 73.33% +2.95%
==========================================
Files 25 27 +2
Lines 1671 2141 +470
==========================================
+ Hits 1176 1570 +394
- Misses 375 411 +36
- Partials 120 160 +40 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Implements a -- myschema:renamed-from <old> comment directive and wires it through parsing + diffing to emit in-place MySQL renames (table / column / secondary index) instead of destructive DROP+CREATE, with accompanying tests and docs.
Changes:
- Added directive validation/extraction in the parser and stored rename metadata on
model.Table,model.Column, andmodel.Index. - Added diff-layer rename passes that emit
ALTER TABLE … RENAME TO / RENAME COLUMN / RENAME INDEX, including index-part rewriting for renamed columns. - Added YAML fixtures + Go tests, and updated docs/TODO to reflect current and planned directive support.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| testdata/apply/rename_table.yml | YAML apply fixture for table rename directive. |
| testdata/apply/rename_column.yml | YAML apply fixture for column rename directive. |
| testdata/apply/rename_index.yml | YAML apply fixture for index rename directive. |
| parser/parser.go | Validates directives, extracts rename directives per statement, populates RenameFrom fields. |
| parser/directive.go | Adds directive registry/validation and statement/inline rename extraction helpers. |
| parser/directive_test.go | Tests directive validation and extraction behavior. |
| model/table.go | Adds Table.RenameFrom for table renames. |
| model/column.go | Adds Column.RenameFrom for column renames. |
| model/index.go | Adds Index.RenameFrom for index renames. |
| diff/tables.go | Adds rename passes (table/column/index) ahead of regular diffs; updates diffTable signature. |
| diff/rename.go | Implements rename application helpers and index-part rewrite for renamed columns. |
| diff/rename_test.go | Tests rename DDL emission, idempotency, and index-part rewrite behavior. |
| TODO.md | Updates rename directive TODO scope (constraints/FKs deferred). |
| AGENTS.md | Documents the new directive and current rename capabilities. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Seven inline comments, all valid. Fixes:
parser/directive.go
- Whitespace tolerance: classifyInlineLine() now goes through
strings.Fields/tokenize() so tabs and runs of multiple spaces
between KEY/INDEX/UNIQUE/etc. and the name don't defeat the
kind classifier. (Copilot #1, two comments on the same issue.)
- Kind-aware extraction: ExtractInlineRenames() returns
*InlineRenames with separate Columns / Indexes maps plus an
Unsupported list, so a column and an index that share a name
(which happens when a KEY auto-names after its first column)
no longer compete for the same map key. (Copilot #2.)
- Directives that resolve to constraints / FKs / PRIMARY KEY /
anonymous FOREIGN KEY / unrecognised line shapes are now
recorded in Unsupported instead of silently dropped — the
parser turns them into errors. (Copilot #3.)
- renameDirectivePattern no longer accepts `.` in the old name;
the surrounding comment now matches reality (qualified names
were never plumbed through and are intentionally rejected).
(Copilot #6.)
- ExtractInlineRenames skips the leading-comment block (those
are statement-level, owned by ExtractStmtRenameFrom) so the
statement-level directive doesn't get re-attached to the
`CREATE TABLE …` opener line.
parser/parser.go
- Uses InlineRenames.Columns / .Indexes to attach RenameFrom by
kind, not by flat lookup. Errors out on Unsupported entries
(constraint / FK / dangling directive) so a typo or
mis-positioned directive fails the parse instead of silently
degrading into a destructive DROP+CREATE.
diff/rename.go
- Removes the misleading trailing comment in applyColumnRenames
(the "propagate to indexes" work happens in the caller, not
here). (Copilot #4.)
- Adds rewriteFKColumnRefs(), the FK counterpart to
rewriteIndexColumnRefs: rewrites FK Columns entries from
old → new after a column rename so fkEqual stays quiet. Without
this, a plain column rename emitted DROP FOREIGN KEY +
ADD CONSTRAINT for any FK on the renamed column. (Copilot #5.)
diff/tables.go
- Calls rewriteFKColumnRefs after applyColumnRenames.
Tests
- parser/directive_test.go: rewritten for the kind-aware API,
plus new cases for whitespace tolerance (tab, multi-space),
column/index name collision, qualified-name rejection,
constraint-target = unsupported, dangling-directive = error,
full ParseSQL error wrapping.
- diff/rename_test.go: new TestDiffRenameColumnAlsoRewritesFKColumns
covers the FK-rewrite path end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Four inline comments, all valid; one is a real bug.
diff/rename.go (BUG fix)
- applyTableRenames now also rewrites (RefDB, RefTable) on every
other table's FKs that pointed at the old name. Without this, a
pure table rename (shop.users → shop.members) made every FK on
referencing tables diff as DROP FOREIGN KEY + ADD CONSTRAINT,
and would fail to apply under restrictive --allow-drop=foreign_key
settings even though only the target name changed.
(Copilot #4.)
parser/directive.go
- ValidateDirectives now also checks the *syntax* of recognised
directives, not just the name. Malformed renamed-from lines
(qualified names like `db.tbl`, missing argument, trailing junk)
fail at validation time with a clear "malformed" message instead
of being silently dropped by the extractor and degrading to
DROP+CREATE. (Copilot #1.)
- renameDirectivePattern accepts any backtick-quoted blob as the
old name, mirroring model.Ident's behaviour for reserved words /
hyphens / etc. Bareword names still take the existing identifier
grammar. (Copilot #2.)
- classifyInlineLine detects the unnamed `KEY (col)` /
`INDEX (col)` form (tokens[1] starts with "(") and returns
inlineKindUnknown so the directive is reported as unsupported,
rather than later surfacing as a confusing "target index '(col)'
not found" error. (Copilot #3.)
Tests
- parser/directive_test.go: new cases for qualified-name rejection
at validate time, missing-arg, trailing-junk, backticked reserved
word in the directive arg, and unnamed `KEY (col)` form being
flagged as unsupported. The previously existing "qualified name
silently rejected" test is reframed to document that it's the
extractor's behaviour while ValidateDirectives now errors loudly.
- diff/rename_test.go: TestDiffRenameTableAlsoRewritesCrossTableFKRefs
covers the cross-table FK rewrite end-to-end (renames
shop.users → shop.members and asserts shop.posts' FK to it stays
quiet — no DROP FOREIGN KEY, no ADD CONSTRAINT).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
parser/parser.go:96
- Directives are extracted for every SQL piece, but
stmtRename/inlineRenamesare only applied in the*sqlparser.CreateTablebranch. If a-- myschema:renamed-fromcomment appears before or inside a non-CREATE-TABLE statement (e.g. CREATE VIEW), it will currently be validated as “known” but then silently ignored. Consider erroring whenstmtRename != ""orinlineRenamescontains any entries/unsupported directives and the parsed statement is not a CREATE TABLE, so directives can’t be accidentally dropped on the floor.
// Directive extraction runs against the raw piece before vitess
// strips comments. Statement-level directives sit on a leading
// comment block; inline directives (column / index renames) live
// inside the CREATE TABLE body, classified per-kind so a column
// and an index that share a name (which happens when a KEY is
// auto-named after its first column) don't compete for the same
// directive.
stmtRename := ExtractStmtRenameFrom(piece)
inlineRenames := ExtractInlineRenames(piece)
stmt, err := p.Parse(piece)
if err != nil {
return nil, fmt.Errorf("failed to parse SQL: %w", err)
}
switch s := stmt.(type) {
case *sqlparser.CreateTable:
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Five inline + one suppressed comment, all valid.
parser/directive.go
- ExtractStmtRenameFrom now returns (string, error) and errors when
more than one renamed-from directive appears in the leading
comment block — multiple sources is ambiguous and almost always a
typo, so failing loudly beats silently letting the last one win.
(Copilot #5.)
- ExtractInlineRenames detects two `-- myschema:renamed-from` lines
stacked with no SQL line between them; the first directive (which
never attached to anything) is now appended to Unsupported instead
of being silently overwritten. (Copilot #3.)
- classifyInlineLine handles column lines whose name is backtick-
quoted with embedded whitespace (`weird name VARCHAR(64)`) via a
new leadingBacktickedIdent helper. strings.Fields can't tokenize
these correctly because the name itself contains spaces; the
helper does a backtick-aware first-identifier parse. KEY / INDEX
/ CONSTRAINT keywords are MySQL reserved tokens and never quoted,
so a leading backtick can only be a column name. (Copilot #4.)
- Doubled backticks inside a backticked old-name (MySQL's escape
for an embedded backtick) remain rejected by renameDirectivePattern;
the validator's "malformed directive" error is the right surface.
Documented as out of scope in the leadingBacktickedIdent comment.
(Copilot #1, #2.)
parser/parser.go
- rejectMisplacedRenameDirectives() errors when a renamed-from
directive is extracted but the surrounding statement isn't
CREATE TABLE (currently the only kind that consumes directives).
Wired into the AlterTable, CreateView, and default switch arms
so a directive on a CREATE VIEW etc. fails the parse instead of
being silently dropped on the floor and degrading the next plan
into a destructive DROP+CREATE. (Suppressed Copilot comment on
parser.go:96.)
- ExtractStmtRenameFrom call now propagates the new error.
Tests
- parser/directive_test.go: existing call sites updated for the
new (string, error) signature; new cases for stacked directives,
multiple stmt directives error, backticked column with embedded
space, and renamed-from on CREATE VIEW being rejected by ParseSQL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Seven inline comments, all valid; two are real bugs.
parser/directive.go
- End-of-loop guard in ExtractInlineRenames: a `pending` directive
whose target line never arrived (statement body ran out) is now
appended to Unsupported instead of being silently dropped.
(Copilot #1.)
- leadingBacktickedIdent comment rewritten: previous wording had
curly quotes ("…" instead of `…`) and didn't explain MySQL's
backtick-doubling escape. New comment is precise: doubled
backticks remain unsupported; the regex correctly rejects them
so the validator surfaces "malformed directive". (Copilot #2, #3.)
diff/rename.go
- applyTableRenames / applyColumnRenames / applyIndexRenames now
short-circuit when RenameFrom equals the desired name. This
avoids generating `ALTER TABLE x RENAME TO x` (rejected on some
MySQL versions, no-op on others) for what is almost always a
user typo. (Copilot #5, #6, #7.)
- rewriteConstraintColumnRefs: PRIMARY KEY column lists in
current.Constraints are rewritten old → new alongside the index
and FK rewrites, so a renamed-PK column doesn't surface as
DROP+ADD PRIMARY KEY in diffConstraints. CHECK constraints are
deliberately skipped — their Definition is a free-form
expression and rewriting it requires a full SQL parser.
(Copilot #4.)
diff/tables.go
- Calls rewriteConstraintColumnRefs after applyColumnRenames,
alongside the existing index / FK rewrites.
Tests
- parser/directive_test.go: TestExtractInlineRenamesTrailingPendingUnsupported
covers the end-of-loop pending path.
- diff/rename_test.go: TestDiffRenameSelfRenameIsNoOp covers the
table / column / index self-rename guards in one shot.
TestDiffRenameColumnAlsoRewritesPKConstraint covers the new PK
rewrite end-to-end (renames `users.old_id` → `users.id` and
asserts no DROP/ADD PRIMARY KEY appears).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Four inline comments, all valid; one is a real bug.
diff/rename.go + diff/tables.go (BUG fix)
- rewriteCrossTableFKRefCols(): for every desired table whose
columns carry RenameFrom, walk all *other* current tables and
rewrite RefCols on FKs that reference (db, table) when the
referenced column matches one of the renames. Without this, a
pure column rename on the parent side (`users.id` → `users.user_id`)
diff'd every referencing FK as DROP+ADD even though MySQL
updates the parent-side reference automatically. (Copilot #1.)
- DiffTables runs the cross-table pass after table renames and
before the modified-tables loop, so per-table diffTable() sees
a consistent view.
diff/tables.go (doc)
- DiffTables doc-comment now warns that rename handling mutates
`current` in place (re-keys after table rename, updates Name /
Database / FK Columns / RefCols / index parts / PK constraint
columns). Production callers in diff_all.go build a fresh
`current` per invocation, so this is fine; tests that share
fixtures across subtests should clone first. (Copilot #2.)
parser/directive.go
- ExtractInlineRenames now skips `# ...` line comments and
single-line `/* ... */` block comments between a directive and
its target. Multi-line `/* ... */` is still not unwound — a rare
case in hand-written CREATE TABLE bodies — and is documented in
the func comment. Without this, a stray `# foo` between the
directive and the next column line would have been treated as
SQL and the directive mis-attached or surfaced as
"target not found". (Copilot #3.)
- leadingBacktickedIdent comment rewritten *for real* this time
(the previous attempt left U+201C "left double quotation mark"
in place where ASCII backticks were intended). The comment now
describes MySQL's escape rule in plain English without any
smart-quote characters. (Copilot #4.)
Tests
- parser/directive_test.go: TestExtractInlineRenamesSkipsBlockAndHashComments
covers the new comment-skipping logic.
- diff/rename_test.go: TestDiffRenameColumnAlsoRewritesCrossTableFKRefCols
renames `users.id` → `users.user_id` while `posts.fk_user`
references it, and asserts the FK on posts stays quiet (no
DROP FOREIGN KEY, no ADD CONSTRAINT) — the cross-table RefCols
rewrite is exercised end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot review #6: `k` is already used by the GetOk check above the rewrite call; the trailing `_ = k` was a leftover from an earlier draft. Drop 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 14 out of 14 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Two inline comments, both real bugs.
parser/directive.go
- ExtractStmtRenameFrom now also skips `#` line comments and
single-line `/* … */` block comments in the leading block.
Previously it stopped at the first non-`--` line, so a stray
`# header` or `/* generated */` between such a comment and
`-- myschema:renamed-from` defeated the scan: the validator
saw the directive as known, but the extractor never reached it,
and the rename silently degraded into DROP+CREATE. Mirrors the
same skip logic in ExtractInlineRenames. (Copilot #1.)
diff/rename.go + diff/tables.go
- rewriteFKColumnRefs() now takes (db, name) and, for any FK
whose (RefDB, RefTable) matches that table (i.e. self-
referential), rewrites RefCols too. The cross-table pass in
DiffTables explicitly skips the renamed table itself, so without
this self-ref handling a `users.parent_id REFERENCES users(id)`
FK would diff as DROP+ADD after renaming `users.id`. (Copilot #2.)
- diffTable threads `current.Database, current.Name` into the call.
Tests
- parser/directive_test.go: TestExtractStmtRenameFromSkipsLeadingHashAndBlockComments
covers `#` and `/* … */` lines preceding the directive in the
leading block.
- diff/rename_test.go: TestDiffRenameColumnAlsoRewritesSelfReferentialFK
covers the self-ref RefCols rewrite end-to-end (renames
users.id → users.user_id with a self-referential
fk_parent(parent_id) → users(id), asserts no DROP/ADD on
fk_parent).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 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 inline comments — one is a real ordering bug, two are leftover
edge cases for the line-scan comment skipper.
diff/tables.go + diff_all.go (BUG fix)
- TableDiffResult gains a RenameStmts bucket. Table renames
(`ALTER TABLE … RENAME TO …`) now go there instead of into
Stmts, and diff_all.go schedules them between view drops and
FK drops. Without this, a migration that both renames a table
and drops an FK on it would emit:
ALTER TABLE new_name DROP FOREIGN KEY fk; -- (FK drops first)
ALTER TABLE old_name RENAME TO new_name;
The FK drop runs against new_name which doesn't exist yet, and
apply errors out. Renames must precede FK drops so subsequent
ALTERs target an existing name. (Copilot #3.)
parser/directive.go
- stripLeadingBlockComment helper handles the `/* note */ <sql>`
shape: a line that starts with a single-line block comment but
has SQL after `*/`. Both ExtractStmtRenameFrom and
ExtractInlineRenames now call it. For stmt extraction, hitting
SQL after the comment stops the leading-block scan (the `*/`
line is the first SQL line). For inline extraction, the SQL
remainder is reprocessed as the target line so a pending
directive can attach to it (or trip sawSQL on the opener).
(Copilot #1, #2.)
Tests
- Existing tests using res.Stmts for table renames updated to use
res.RenameStmts.
- YAML apply fixtures (rename_table, rename_column, rename_index)
continue to pass — diff_all.go emits the rename in the expected
spot in the merged plan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 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 inline comments — two on the line-classification flow, one on
backtick-quoted identifier handling.
parser/directive.go
- reduceLeadingBlocks() iteratively strips zero-or-more leading
`/* … */` blocks from a line, returning either the remainder or
a flag indicating the block didn't close on this line. Both
ExtractStmtRenameFrom and ExtractInlineRenames now feed every
line through it before classification, so:
* `/* header */ -- myschema:renamed-from old` (block then
directive on the same line) is recognised as a directive,
not lost as "first SQL line".
* `/* note */ /* note2 */ name VARCHAR(64)` (two leading
blocks) reduces correctly.
The old single-shot stripLeadingBlockComment helper that ran
only after a dedicated `/* …` branch is removed in favour of
this unified reduce-then-classify flow. (Copilot #1, #2.)
- classifyInlineLine grew a backtick-aware fallback path. Index
and constraint names that are backtick-quoted AND contain
whitespace (e.g. `KEY \`weird name\` (col)`) get split by
strings.Fields and were mis-classified, surfacing as
"target index not found". Two new helpers run before the
Fields-based path:
* backtickedNameAfterPrefix(line, prefixes) tries to match
any of the given keyword prefixes followed by a backticked
identifier and returns the unquoted name.
* stripUntilAfterBacktickedName lets the CONSTRAINT branch
peek past the backticked name to detect a trailing `UNIQUE`
and route to inlineKindIndex.
These cover KEY / INDEX / UNIQUE [KEY|INDEX] /
FULLTEXT [KEY|INDEX] / SPATIAL [KEY|INDEX] / CONSTRAINT shapes
with backticked-with-whitespace names. (Copilot #3.)
Tests
- parser/directive_test.go:
* TestExtractStmtRenameFromBlockCommentBeforeDirectiveSameLine
* TestExtractInlineRenamesBlockCommentBeforeDirectiveSameLine
* TestExtractInlineRenamesBacktickedIndexNameWithSpace (KEY,
UNIQUE KEY, and CONSTRAINT-with-CHECK forms)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Four inline comments. Two typo nits, two flow consistency fixes.
Typos
- parser/directive.go:415 — comment for stripUntilAfterBacktickedName
contained a curly close-quote (\`name\“) instead of a backtick.
Replaced. (Copilot #1.)
- diff/rename_test.go:390 — comment said "RenameFrm"; should be
"RenameFrom". Fixed. (Copilot #2.)
parser/directive.go (flow consistency)
- ValidateDirectives now goes through the same
reduceLeadingBlocks + multi-line block state path the extractors
use, so a malformed directive after `/* header */` or after
`*/` on the same multi-line block-close line still errors at
validation time. Without this, the validator and extractor
disagreed on what counts as a "directive line", letting bad
directives slip past validation and be silently ignored.
(Copilot #3.)
- ExtractStmtRenameFrom and ExtractInlineRenames re-process the
suffix after a multi-line `*/` close on the same line, so
`*/ -- myschema:renamed-from old` still attaches. Previously
the code cleared inBlock and `continue`'d, dropping the
directive. (Copilot #4.)
Tests
- TestValidateDirectivesAfterLeadingBlockComment
- TestValidateDirectivesAfterMultiLineBlockClose
- TestExtractStmtRenameFromMultiLineBlockCloseSameLineAsDirective
- TestExtractInlineRenamesMultiLineBlockCloseSameLineAsDirective
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 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 15 out of 15 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot review #18 (single comment): line 442 of directive.go still held a literal `\“` (backslash + U+201C "left double quotation mark") where backticks were intended in the doc comment for stripUntilAfterBacktickedName. The previous round of curly-quote cleanup missed this site and another nearby one in the tokenize doc. Both comments are now rephrased without nested-backtick examples so the prose carries the meaning without relying on quoting tricks. No behaviour change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 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 15 out of 15 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot review #20: backtickedNameAfterPrefix matched its multi-word prefixes (e.g. "UNIQUE KEY", "FULLTEXT INDEX") with strings.HasPrefix, which only succeeds on exactly one space between the keywords. SQL formatted with multiple spaces or a tab between the two keywords (e.g. `UNIQUE KEY \`also weird\` (id)` or `FULLTEXT\tINDEX …`) silently fell through to the strings.Fields-based path, which can't extract a backticked index name containing whitespace and surfaces as "target index not found". New consumeKeywordSequence helper walks the prefix keyword-by-keyword against the line, allowing any run of `[ \t]+` between successive keywords (and requiring at least one whitespace character after the final keyword so `KEYWORD` doesn't accidentally match `KEY`). stripUntilAfterBacktickedName goes through the same helper for consistency, so the CONSTRAINT path picks up the same flexibility. Tests - TestExtractInlineRenamesBacktickedIndexMultiWhitespacePrefix covers `UNIQUE KEY` (multi-space) and `FULLTEXT\tINDEX` (tab). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 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.
Summary
Adds a comment-directive system shaped to grow into a small directive family (
renamed-fromnow,executelater) and uses it to wire up real in-place renames for tables, columns, and secondary indexes — the three MySQL objects that have a trueALTER … RENAMEstatement and where DROP+CREATE would lose data.What ships
-- myschema:renamed-from <old>directive (registry inparser/directive.go; unknown directive names fail loudly viaValidateDirectives)CREATE TABLEfor table renamesALTER TABLE … RENAME TO,ALTER TABLE … RENAME COLUMN,ALTER TABLE … RENAME INDEXOut of scope (follow-up TODO)
Constraint and FK renames. MySQL has no in-place
RENAME CONSTRAINT/RENAME FOREIGN KEY, so the diff already drops + adds; threading the directive through would only validate the source name. Tracked in TODO.md.Extensibility
parser/directive.gois structured as a registry + per-directive regex + per-directive extraction function, mirroring pistachio's pattern. Adding-- myschema:executelater means: add the name toknownDirectives, add a regex + extractor, add a model bucket, wire into the apply path.Tests
YAML (
testdata/apply/):rename_table.yml—verify_no_drift: truerename_column.yml—verify_no_drift: truerename_index.yml—verify_no_drift: trueGo:
parser/directive_test.go— lexer coverage (known / unknown / plain comments / position guard / inline column / inline KEY / UNIQUE KEY / CONSTRAINT)diff/rename_test.go— emits-the-right-DDL, missing-source error, idempotent re-apply, index-part rewrite suppresses DROP+CREATE after column renameTest plan
make test(parser + diff + catalog + model + YAML harness)make test-scenariomake lint(gofumpt-clean)🤖 Generated with Claude Code