Skip to content

parser/diff: implement -- myschema:renamed-from directive#27

Merged
winebarrel merged 22 commits into
mainfrom
implement-renamed-from
May 2, 2026
Merged

parser/diff: implement -- myschema:renamed-from directive#27
winebarrel merged 22 commits into
mainfrom
implement-renamed-from

Conversation

@winebarrel

Copy link
Copy Markdown
Owner

Summary

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.

What ships

  • -- myschema:renamed-from <old> directive (registry in parser/directive.go; unknown directive names fail loudly via ValidateDirectives)
  • Statement-level form on CREATE TABLE for table renames
  • Inline form (line above the column / KEY / INDEX / UNIQUE / etc.) for column and index renames
  • Diff emits ALTER TABLE … RENAME TO, ALTER TABLE … RENAME COLUMN, ALTER TABLE … RENAME INDEX
  • Index parts referencing renamed columns are rewritten in place so the index isn't also dropped+created
  • Idempotent re-apply (rename already happened) is a no-op
  • Missing source name or destination collision errors out — typos don't silently degrade into destructive DROP+CREATE

Out 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.go is structured as a registry + per-directive regex + per-directive extraction function, mirroring pistachio's pattern. Adding -- myschema:execute later means: add the name to knownDirectives, add a regex + extractor, add a model bucket, wire into the apply path.

Tests

YAML (testdata/apply/):

  • rename_table.ymlverify_no_drift: true
  • rename_column.ymlverify_no_drift: true
  • rename_index.ymlverify_no_drift: true

Go:

  • 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 rename

Test plan

  • make test (parser + diff + catalog + model + YAML harness)
  • make test-scenario
  • make lint (gofumpt-clean)

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings May 2, 2026 10:30
@codecov

codecov Bot commented May 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.33827% with 93 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.33%. Comparing base (212b650) to head (dab2a15).
⚠️ Report is 23 commits behind head on main.

Files with missing lines Patch % Lines
parser/directive.go 84.32% 22 Missing and 15 partials ⚠️
diff/rename.go 82.35% 16 Missing and 14 partials ⚠️
parser/parser.go 31.57% 20 Missing and 6 partials ⚠️
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.
📢 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

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, and model.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.

Comment thread parser/directive.go Outdated
Comment thread parser/directive.go Outdated
Comment thread parser/parser.go Outdated
Comment thread parser/parser.go Outdated
Comment thread diff/rename.go Outdated
Comment thread diff/tables.go
Comment thread parser/directive.go Outdated
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>

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 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.

Comment thread parser/directive.go Outdated
Comment thread parser/directive.go Outdated
Comment thread parser/directive.go
Comment thread diff/rename.go
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>

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 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/inlineRenames are only applied in the *sqlparser.CreateTable branch. If a -- myschema:renamed-from comment 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 when stmtRename != "" or inlineRenames contains 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.

Comment thread parser/directive.go
Comment thread parser/directive.go
Comment thread parser/directive.go
Comment thread parser/directive.go Outdated
Comment thread parser/directive.go Outdated
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>

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 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.

Comment thread parser/directive.go
Comment thread parser/directive.go
Comment thread parser/directive.go Outdated
Comment thread diff/tables.go
Comment thread diff/rename.go
Comment thread diff/rename.go
Comment thread diff/rename.go
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>

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 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.

Comment thread diff/rename.go
Comment thread diff/rename.go
Comment thread parser/directive.go
Comment thread parser/directive.go Outdated
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>

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 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.

Comment thread diff/tables.go Outdated
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>

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 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.

Comment thread parser/directive.go
Comment thread diff/rename.go
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>

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 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.

Comment thread parser/directive.go Outdated
Comment thread parser/directive.go Outdated
Comment thread diff/tables.go Outdated
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>

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 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.

Comment thread parser/directive.go Outdated
Comment thread parser/directive.go Outdated
Comment thread parser/directive.go Outdated
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>

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 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.

Comment thread parser/directive.go Outdated
Comment thread diff/rename_test.go Outdated
Comment thread parser/directive.go
Comment thread parser/directive.go Outdated
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>

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 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.

Comment thread parser/parser.go
Comment thread parser/parser.go
Comment thread diff/rename.go
Comment thread diff/rename.go

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 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.

Comment thread parser/directive.go Outdated
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>

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 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.

Comment thread parser/directive.go
Comment thread parser/directive.go

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 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.

Comment thread parser/directive.go
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>

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 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.

@winebarrel winebarrel merged commit 321e590 into main May 2, 2026
8 checks passed
@winebarrel winebarrel deleted the implement-renamed-from branch May 2, 2026 14:36
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