Skip to content

Commit 97dbca2

Browse files
Support table and column rename operations preceding create_constraint FOREIGN KEY operations (#682)
Ensure that `create_constraint` `FOREIGN KEY` operations can be preceded by rename table and rename column operations as in the following example: ```json { "name": "22_multiple_ops", "operations": [ { "rename_table": { "from": "items", "to": "products" } }, { "rename_column": { "table": "products", "from": "owner", "to": "owner_id" } }, { "create_constraint": { "table": "products", "type": "foreign_key", "name": "fk_items_users", "columns": ["owner_id"], "references": { "table": "users", "columns": ["id"] }, "up": { "owner_id": "owner_id" }, "down": { "owner_id": "owner_id" } } } ] } ``` Follow up to #674 and #676. Part of #239.
1 parent 9a3eaed commit 97dbca2

File tree

2 files changed

+150
-3
lines changed

2 files changed

+150
-3
lines changed

pkg/migrations/op_create_constraint.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (o *OpCreateConstraint) Start(ctx context.Context, conn db.DB, latestSchema
8686
case OpCreateConstraintTypeCheck:
8787
return table, o.addCheckConstraint(ctx, conn, table.Name)
8888
case OpCreateConstraintTypeForeignKey:
89-
return table, o.addForeignKeyConstraint(ctx, conn)
89+
return table, o.addForeignKeyConstraint(ctx, conn, table)
9090
}
9191

9292
return table, nil
@@ -274,8 +274,8 @@ func (o *OpCreateConstraint) addCheckConstraint(ctx context.Context, conn db.DB,
274274
return err
275275
}
276276

277-
func (o *OpCreateConstraint) addForeignKeyConstraint(ctx context.Context, conn db.DB) error {
278-
sql := fmt.Sprintf("ALTER TABLE %s ADD ", pq.QuoteIdentifier(o.Table))
277+
func (o *OpCreateConstraint) addForeignKeyConstraint(ctx context.Context, conn db.DB, table *schema.Table) error {
278+
sql := fmt.Sprintf("ALTER TABLE %s ADD ", pq.QuoteIdentifier(table.Name))
279279

280280
writer := &ConstraintSQLWriter{
281281
Name: o.Name,

pkg/migrations/op_create_constraint_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,153 @@ func TestCreateConstraintInMultiOperationMigrations(t *testing.T) {
10741074
{"id": 3, "item_name": "carrot"},
10751075
}, rows)
10761076

1077+
// The table has been cleaned up
1078+
TableMustBeCleanedUp(t, db, schema, "products", "name")
1079+
},
1080+
},
1081+
{
1082+
name: "rename table, rename column, create foreign key constraint",
1083+
migrations: []migrations.Migration{
1084+
{
1085+
Name: "01_create_tables",
1086+
Operations: migrations.Operations{
1087+
&migrations.OpCreateTable{
1088+
Name: "users",
1089+
Columns: []migrations.Column{
1090+
{
1091+
Name: "id",
1092+
Type: "int",
1093+
Pk: true,
1094+
},
1095+
{
1096+
Name: "name",
1097+
Type: "varchar(255)",
1098+
Nullable: true,
1099+
},
1100+
},
1101+
},
1102+
&migrations.OpCreateTable{
1103+
Name: "items",
1104+
Columns: []migrations.Column{
1105+
{
1106+
Name: "id",
1107+
Type: "int",
1108+
Pk: true,
1109+
},
1110+
{
1111+
Name: "name",
1112+
Type: "varchar(255)",
1113+
Nullable: true,
1114+
},
1115+
{
1116+
Name: "owner",
1117+
Type: "int",
1118+
Nullable: true,
1119+
},
1120+
},
1121+
},
1122+
},
1123+
},
1124+
{
1125+
Name: "02_multi_operation",
1126+
Operations: migrations.Operations{
1127+
&migrations.OpRenameTable{
1128+
From: "items",
1129+
To: "products",
1130+
},
1131+
&migrations.OpRenameColumn{
1132+
Table: "products",
1133+
From: "owner",
1134+
To: "owner_id",
1135+
},
1136+
&migrations.OpCreateConstraint{
1137+
Table: "products",
1138+
Type: migrations.OpCreateConstraintTypeForeignKey,
1139+
Name: "fk_item_owner",
1140+
Columns: []string{"owner_id"},
1141+
References: &migrations.TableForeignKeyReference{
1142+
Table: "users",
1143+
Columns: []string{"id"},
1144+
},
1145+
Up: map[string]string{
1146+
"owner_id": "SELECT CASE WHEN EXISTS (SELECT 1 FROM users WHERE users.id = owner_id) THEN owner_id ELSE NULL END",
1147+
},
1148+
Down: map[string]string{
1149+
"owner_id": "owner_id",
1150+
},
1151+
},
1152+
},
1153+
},
1154+
},
1155+
afterStart: func(t *testing.T, db *sql.DB, schema string) {
1156+
// Can insert a row into the users table
1157+
MustInsert(t, db, schema, "02_multi_operation", "users", map[string]string{
1158+
"id": "1",
1159+
"name": "alice",
1160+
})
1161+
1162+
// Can insert a row that meets the constraint into the new schema
1163+
MustInsert(t, db, schema, "02_multi_operation", "products", map[string]string{
1164+
"id": "1",
1165+
"name": "apple",
1166+
"owner_id": "1",
1167+
})
1168+
1169+
// Can't insert a row that violates the constraint into the new schema
1170+
MustNotInsert(t, db, schema, "02_multi_operation", "products", map[string]string{
1171+
"id": "2",
1172+
"name": "banana",
1173+
"owner_id": "2", // no such user
1174+
}, testutils.FKViolationErrorCode)
1175+
1176+
// Can insert a row that violates the constraint into the old schema
1177+
MustInsert(t, db, schema, "01_create_tables", "items", map[string]string{
1178+
"id": "2",
1179+
"name": "banana",
1180+
"owner": "2",
1181+
})
1182+
1183+
// The new view has the expected rows
1184+
rows := MustSelect(t, db, schema, "02_multi_operation", "products")
1185+
assert.Equal(t, []map[string]any{
1186+
{"id": 1, "name": "apple", "owner_id": 1},
1187+
{"id": 2, "name": "banana", "owner_id": nil},
1188+
}, rows)
1189+
1190+
// The old view has the expected rows
1191+
rows = MustSelect(t, db, schema, "01_create_tables", "items")
1192+
assert.Equal(t, []map[string]any{
1193+
{"id": 1, "name": "apple", "owner": 1},
1194+
{"id": 2, "name": "banana", "owner": 2},
1195+
}, rows)
1196+
},
1197+
afterRollback: func(t *testing.T, db *sql.DB, schema string) {
1198+
// The table has been cleaned up
1199+
TableMustBeCleanedUp(t, db, schema, "items", "name")
1200+
},
1201+
afterComplete: func(t *testing.T, db *sql.DB, schema string) {
1202+
// Can insert a row that meets the constraint into the new schema
1203+
MustInsert(t, db, schema, "02_multi_operation", "products", map[string]string{
1204+
"id": "3",
1205+
"name": "carrot",
1206+
"owner_id": "1",
1207+
})
1208+
1209+
// Can't insert a row into the new schema that violates the constraint
1210+
MustNotInsert(t, db, schema, "02_multi_operation", "products", map[string]string{
1211+
"id": "4",
1212+
"name": "durian",
1213+
"owner_id": "2", // no such user
1214+
}, testutils.FKViolationErrorCode)
1215+
1216+
// The new view has the expected rows
1217+
rows := MustSelect(t, db, schema, "02_multi_operation", "products")
1218+
assert.Equal(t, []map[string]any{
1219+
{"id": 1, "name": "apple", "owner_id": 1},
1220+
{"id": 2, "name": "banana", "owner_id": nil},
1221+
{"id": 3, "name": "carrot", "owner_id": 1},
1222+
}, rows)
1223+
10771224
// The table has been cleaned up
10781225
TableMustBeCleanedUp(t, db, schema, "products", "name")
10791226
},

0 commit comments

Comments
 (0)