Skip to content

feat(planner): refuse a drop with CASCADE - #224

Draft
aparajon wants to merge 2 commits into
armand/dependent-view-refusalfrom
armand/refuse-cascading-alter-drop
Draft

aparajon wants to merge 2 commits into
armand/dependent-view-refusalfrom
armand/refuse-cascading-alter-drop

Conversation

@aparajon

Copy link
Copy Markdown
Collaborator

Why

CASCADE on a drop also drops whatever depends on the target: a view that reads a dropped column, or another table's foreign key on a dropped primary key. Those objects are not named in the statement, so the plan cannot show them, and dropping another table's foreign key locks that table, which pg-sprite's one-table lock does not cover.

Only DROP INDEX ... CASCADE was caught (#189). The ALTER TABLE forms were classified as ordinary metadata-only drops and migrate ran them natively.

What

The planner refuses every DROP COLUMN, DROP CONSTRAINT, or DROP INDEX with CASCADE under a new cascading-drop reason. The refusal is read from the statement alone, so lint and plan report it as well as migrate. The migrate verdict tells the operator to drop each dependent object explicitly, then resubmit without CASCADE; PostgreSQL then refuses the drop while anything still depends on the target.

lint used to suggest DROP INDEX CONCURRENTLY i CASCADE for DROP INDEX i CASCADE, which PostgreSQL rejects. That drop is now refused with no suggestion.

users has a primary key that orders.user_id references, and the operator submits ALTER TABLE users DROP CONSTRAINT users_pkey CASCADE through migrate.

Before                                After

┌──────────────────────────────┐      ┌──────────────────────────────┐
│ planner                      │      │ planner                      │
│ DROP CONSTRAINT users_pkey   │      │ DROP CONSTRAINT users_pkey   │
│ native, metadata-only        │      │ refuse, cascading-drop       │
└──────────────┬───────────────┘      └──────────────┬───────────────┘
               │ runs                                │ never sent
               ▼                                     ▼
┌──────────────────────────────┐      ┌──────────────────────────────┐
│ executed                     │      │ refused                      │
│ users_pkey dropped           │      │ users_pkey kept              │
│ orders_user_id_fkey dropped  │ ✗    │ orders_user_id_fkey kept     │
│ (not in the statement)       │      │ verdict: drop it explicitly, │
│                              │      │ resubmit without CASCADE     │
└──────────────────────────────┘      └──────────────────────────────┘

The plan report moves to format_version 11 and the lint report to 5, since the reason vocabulary is part of both contracts. #223's carve-out that let DROP COLUMN ... CASCADE through as a destructive drop is gone: that drop is now refused here instead.

Testing

  • TestRunRefusesACascadingDrop runs both ALTER TABLE forms through migrate against PostgreSQL and checks that the column, the primary key, the view, and the other table's foreign key all survive. With the planner refusal removed, the DROP CONSTRAINT case executes and drops the foreign key.
  • TestClassifyRefusesACascadingDrop covers every drop form with and without live facts, multi-index DROP INDEX, IF EXISTS, a multi-command ALTER TABLE, and that RESTRICT still classifies as before.

Stacked on #223.

🤖 Generated with Claude Code

CASCADE on DROP COLUMN, DROP CONSTRAINT, or DROP INDEX also drops
whatever depends on the target: a view that reads the column, or another
table's foreign key on a dropped primary key. Those objects are not in
the statement, so the plan cannot show them and the one-table lock does
not cover them. The ALTER TABLE forms were classified as plain
metadata-only drops and ran natively.

The planner now refuses every such drop with the new cascading-drop
reason, read from the statement alone, so lint reports it too. The
migrate verdict tells the operator to drop each dependent object
explicitly and resubmit without CASCADE, which PostgreSQL then refuses
while anything still depends on the target. DROP INDEX ... CASCADE no
longer gets a DROP INDEX CONCURRENTLY ... CASCADE suggestion, which the
server rejects.

The plan report moves to format_version 11 and the lint report to 5.

Co-Authored-By: Claude Opus 5.5 <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.

🟡 Changes recommended

The demo’s lint assertion still expects version 4 instead of 5, causing the smoke check to fail.

1 open issue
What changed in this PR

Adds planner refusals for cascading drops, preventing implicit removal of dependent objects outside the statement’s scope.

Changes:

  • Detects CASCADE on column, constraint, and index drops and reports cascading-drop.
  • Adds refusal coverage and operator guidance.
  • Bumps plan and lint report versions and updates documentation.
File Description
pkg/​statement/​ops.go Records cascading drop behavior.
pkg/​statement/​ops_test.go Tests cascade parsing.
pkg/​planner/​planner.go Adds cascading-drop refusal.
pkg/​planner/​planner_test.go Tests refusal and unchanged restrictions.
pkg/​plan/​plan.go Bumps report version to 11.
pkg/​plan/​plan_test.go Updates contract assertions.
pkg/​migrate/​verdicts.go Adds refusal guidance.
pkg/​migrate/​dependent_view_integration_test.go Removes obsolete cascade acceptance case.
pkg/​migrate/​cascading_drop_integration_test.go Checks refusal preserves dependent objects.
pkg/​lint/​lint.go Bumps report version to 5.
pkg/​lint/​lint_test.go Updates contract assertions.
pkg/​capabilities/​capabilities.yaml Records cascade restrictions.
internal/​cli/​dryrun_text.go Explains cascading-drop impact.
docs/​postgres-online-ddl-reference.md Documents the new refusal.
docs/​plan-report.md Updates reason vocabulary and version.
docs/​optimistic-attempt.md Marks cascading drops as refused.
docs/​lint-report.md Documents lint contract changes.
docs/​limitations.md Explains cascade limitations.
docs/​cli-output-examples.md Updates report versions.
docs/​capabilities.md Updates drop capabilities.
demo/​tour.sh Updates plan-version smoke assertions.

Review effort: Balanced

Comment thread demo/tour.sh
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aparajon

Copy link
Copy Markdown
Collaborator Author

🤖 Copilot's review raised one issue, the demo's lint format_version pin, which also failed the smoke check. Fixed in 03e40c4; see the reply on its thread.

Addressed by Claude Code

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