fix(github): refuse a Vitess foreign key at plan time instead of at apply time - #966
Draft
aparajon wants to merge 1 commit into
Draft
fix(github): refuse a Vitess foreign key at plan time instead of at apply time#966aparajon wants to merge 1 commit into
aparajon wants to merge 1 commit into
Conversation
…pply time Vitess does not accept foreign key constraints, but a Vitess plan had no way to say so. The engine's execution-mode verdict — the vocabulary behind the "Cannot apply" section and the apply-command gate — is set only by the MySQL engine, so a Vitess plan could report a statement unsafe or lint it, never refuse it. An ADD FOREIGN KEY therefore planned with an apply offer and failed 43 seconds later, at DDL execution on the branch. The refusal machinery is already built and correct; this is a classification. A Vitess plan now marks a statement that declares a foreign key as refused, so the author sees the guaranteed failure while reading the plan and the apply commands reject it. The predicate is a property of the statement, not of the table's resulting shape. The existing lint walks the post-state, which flags every change to a table that already carries a legacy constraint, including an unrelated ADD COLUMN — refusing on that would block changes Vitess accepts. Dropping a foreign key declares none, which is what leaves an operator a way to remove a legacy constraint. This is the only Vitess rejection the plan predicts. Every other statement Vitess will not accept is rejected against a throwaway branch before any data is copied, so the contract for the rest stays "the apply surfaces it". Also stop the first-failure lines from rendering an engine error raw. A rejection carries the offending statement on a second line, which escaped the blockquote and broke into the surrounding comment; the error is now sanitized, escaped and re-quoted like every other rendered error, and an error with no detail left falls back to the no-detail form rather than trailing a dangling separator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR improves the Vitess (PlanetScale) planning UX by deterministically blocking foreign-key–declaring statements at plan time (so the plan/apply gate reflects guaranteed failure), and hardens webhook comment rendering to prevent multi-line engine errors from escaping Markdown blockquotes.
Changes:
- Add
ddl.DeclaresForeignKeyand use it in the PlanetScale/Vitess planner to mark FK-declaring statements asExecutionModeBlockedwith a clear refusal reason. - Sanitize + HTML-escape + re-quote “first failure” error details so multi-line errors can’t break surrounding PR comment structure.
- Add targeted tests covering both the FK refusal behavior and the first-failure error rendering.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/webhook/templates/sharded_apply.go | Uses quotedCommentError for shard first-failure rendering to keep multi-line errors inside blockquotes. |
| pkg/webhook/templates/multi_apply.go | Uses quotedCommentError for aggregate first-failure rendering and falls back cleanly when no safe detail remains. |
| pkg/webhook/templates/common.go | Introduces quotedCommentError (sanitize + escape + re-quote) to safely render untrusted multi-line errors. |
| pkg/webhook/templates/common_test.go | Adds coverage to ensure multi-line errors stay quoted, markup is escaped, endpoints are redacted, and empty detail renders nothing. |
| pkg/engine/planetscale/branch.go | Blocks Vitess plans that declare foreign keys by setting ExecutionModeBlocked and ModeReason during diff planning. |
| pkg/engine/planetscale/branch_test.go | Verifies FK-declaring table creation is refused while an unrelated ALTER on an FK-bearing table remains applicable. |
| pkg/ddl/foreign_key.go | Adds statement-level FK declaration detection for CREATE TABLE and ALTER TABLE ADD CONSTRAINT/FOREIGN KEY. |
| pkg/ddl/foreign_key_test.go | Adds unit tests for FK detection across CREATE/ALTER forms and error cases (multi-statement, unparseable). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+28
to
+32
| // foreignKeyRefusalReason is the plan-time disclosure for a statement that | ||
| // declares a foreign key. Vitess rejects it outright, so the statement is a | ||
| // guaranteed failure rather than a risk for the author to weigh — and a plan | ||
| // that offered to apply it would be offering a 43-second wait for an error. | ||
| // |
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.
Why this matters
Vitess does not accept foreign key constraints, but a Vitess plan had no way to say so. The engine's execution-mode verdict — the vocabulary behind the "Cannot apply" section and the apply-command gate — is set only by the MySQL engine, so a Vitess plan could report a statement unsafe or lint it, never refuse it. An
ADD FOREIGN KEYtherefore planned with an apply offer and failed 43 seconds later, at DDL execution on the branch.What it does
The refusal machinery is already built and correct; this is a classification. A Vitess plan marks a statement that declares a foreign key as refused, so the author sees the guaranteed failure while reading the plan and the apply commands reject it.
The predicate is a property of the statement, not of the table's resulting shape. The existing lint walks the post-state, which flags every change to a table that already carries a legacy constraint, including an unrelated
ADD COLUMN— refusing on that would block changes Vitess accepts. Dropping a foreign key declares none, which is what leaves an operator a way to remove a legacy constraint.This is the only Vitess rejection the plan predicts. Every other statement Vitess will not accept is rejected against a throwaway branch before any data is copied, so the contract for the rest stays "the apply surfaces it".
Also stops the first-failure lines from rendering an engine error raw. A rejection carries the offending statement on a second line, which escaped the blockquote and broke into the surrounding comment; the error is now sanitized, escaped and re-quoted like every other rendered error, and an error with no detail left falls back to the no-detail form rather than trailing a dangling separator.
🤖 Generated with Claude Code