Skip to content

Commit 0601c76

Browse files
tianzhouclaude
andauthored
fix: skip FK constraint drop when referenced table is dropped with CASCADE (#382) (#383)
* fix: skip FK constraint drop when referenced table is dropped with CASCADE (#382) When dropping a table with CASCADE, PostgreSQL automatically removes FK constraints on other tables that reference it. The diff generator was also emitting explicit ALTER TABLE DROP CONSTRAINT for these same FKs, causing "constraint does not exist" errors during apply. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add empty-schema fallback for ReferencedSchema consistency Mirror the pattern used in shouldDeferConstraint and topological sorting to default empty ReferencedSchema to the owning table's schema. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cbecb2a commit 0601c76

8 files changed

Lines changed: 79 additions & 4 deletions

File tree

internal/diff/diff.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1644,7 +1644,7 @@ func (d *ddlDiff) generateModifySQL(targetSchema string, collector *diffCollecto
16441644
generateModifySequencesSQL(d.modifiedSequences, targetSchema, collector)
16451645

16461646
// Modify tables
1647-
generateModifyTablesSQL(d.modifiedTables, targetSchema, collector)
1647+
generateModifyTablesSQL(d.modifiedTables, d.droppedTables, targetSchema, collector)
16481648

16491649
// Find views that depend on views being recreated (issue #268, #308)
16501650
// Handles both materialized views and regular views with RequiresRecreate

internal/diff/table.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -536,11 +536,17 @@ func generateDeferredConstraintsSQL(deferred []*deferredConstraint, targetSchema
536536
}
537537

538538
// generateModifyTablesSQL generates ALTER TABLE statements
539-
func generateModifyTablesSQL(diffs []*tableDiff, targetSchema string, collector *diffCollector) {
539+
func generateModifyTablesSQL(diffs []*tableDiff, droppedTables []*ir.Table, targetSchema string, collector *diffCollector) {
540+
// Build a set of tables being dropped (CASCADE will remove their dependent FK constraints)
541+
droppedTableSet := make(map[string]bool, len(droppedTables))
542+
for _, t := range droppedTables {
543+
droppedTableSet[t.Schema+"."+t.Name] = true
544+
}
545+
540546
// Diffs are already sorted by the Diff operation
541547
for _, diff := range diffs {
542548
// Pass collector to generateAlterTableStatements to collect with proper context
543-
diff.generateAlterTableStatements(targetSchema, collector)
549+
diff.generateAlterTableStatements(targetSchema, collector, droppedTableSet)
544550
}
545551
}
546552

@@ -654,9 +660,24 @@ func shouldDeferConstraint(table *ir.Table, constraint *ir.Constraint, currentKe
654660
// generateAlterTableStatements generates SQL statements for table modifications
655661
// Note: DroppedTriggers are skipped here because they are already processed in the DROP phase
656662
// (see generateDropTriggersFromModifiedTables in trigger.go)
657-
func (td *tableDiff) generateAlterTableStatements(targetSchema string, collector *diffCollector) {
663+
// droppedTableSet contains "schema.table" keys for tables being dropped with CASCADE;
664+
// FK constraints referencing these tables are skipped since CASCADE already removes them.
665+
func (td *tableDiff) generateAlterTableStatements(targetSchema string, collector *diffCollector, droppedTableSet map[string]bool) {
658666
// Drop constraints first (before dropping columns) - already sorted by the Diff operation
659667
for _, constraint := range td.DroppedConstraints {
668+
// Skip FK constraints whose referenced table is being dropped with CASCADE,
669+
// since the CASCADE will already remove the constraint. (#382)
670+
if constraint.Type == ir.ConstraintTypeForeignKey && constraint.ReferencedTable != "" {
671+
refSchema := constraint.ReferencedSchema
672+
if refSchema == "" {
673+
refSchema = td.Table.Schema
674+
}
675+
refKey := refSchema + "." + constraint.ReferencedTable
676+
if droppedTableSet[refKey] {
677+
continue
678+
}
679+
}
680+
660681
tableName := getTableNameWithSchema(td.Table.Schema, td.Table.Name, targetSchema)
661682
sql := fmt.Sprintf("ALTER TABLE %s DROP CONSTRAINT %s;", tableName, ir.QuoteIdentifier(constraint.Name))
662683

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
DROP TABLE IF EXISTS b CASCADE;
2+
3+
ALTER TABLE a DROP COLUMN b_id;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CREATE TABLE a (id bigint PRIMARY KEY);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CREATE TABLE a (id bigint PRIMARY KEY, b_id bigint);
2+
3+
CREATE TABLE b (id bigint PRIMARY KEY);
4+
5+
ALTER TABLE a ADD CONSTRAINT "a_b_id_fkey" FOREIGN KEY ("b_id") REFERENCES "b" ("id");
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"version": "1.0.0",
3+
"pgschema_version": "1.8.0",
4+
"created_at": "1970-01-01T00:00:00Z",
5+
"source_fingerprint": {
6+
"hash": "62dd4ce9d3f9c6521a16cdbf81cf9511ce4c5a18c65272f5dfa306bfe476cad3"
7+
},
8+
"groups": [
9+
{
10+
"steps": [
11+
{
12+
"sql": "DROP TABLE IF EXISTS b CASCADE;",
13+
"type": "table",
14+
"operation": "drop",
15+
"path": "public.b"
16+
},
17+
{
18+
"sql": "ALTER TABLE a DROP COLUMN b_id;",
19+
"type": "table.column",
20+
"operation": "drop",
21+
"path": "public.a.b_id"
22+
}
23+
]
24+
}
25+
]
26+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
DROP TABLE IF EXISTS b CASCADE;
2+
3+
ALTER TABLE a DROP COLUMN b_id;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Plan: 1 to modify, 1 to drop.
2+
3+
Summary by type:
4+
tables: 1 to modify, 1 to drop
5+
6+
Tables:
7+
~ a
8+
- b_id (column)
9+
- b
10+
11+
DDL to be executed:
12+
--------------------------------------------------
13+
14+
DROP TABLE IF EXISTS b CASCADE;
15+
16+
ALTER TABLE a DROP COLUMN b_id;

0 commit comments

Comments
 (0)