Skip to content

Commit

Permalink
Identify duplicated foreign key constraints by prefix (#245)
Browse files Browse the repository at this point in the history
When duplicating a column for backfilling, ensure that any FK
constraints on the column are duplicated with a special prefix to
identify them as duplicated constraints. Naming the duplicated FK
constraints in this way allows the rename process to ensure that it only
attempts to rename duplicated FK constraints.
  • Loading branch information
andrew-farries committed Jan 19, 2024
1 parent b2e93a9 commit 7a38e1f
Show file tree
Hide file tree
Showing 8 changed files with 26 additions and 10 deletions.
14 changes: 13 additions & 1 deletion pkg/migrations/duplicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (d *Duplicator) Duplicate(ctx context.Context) error {
for _, fk := range d.table.ForeignKeys {
if slices.Contains(fk.Columns, d.column.Name) {
sql += fmt.Sprintf(", "+cAddForeignKeySQL,
pq.QuoteIdentifier(TemporaryName(fk.Name)),
pq.QuoteIdentifier(DuplicationName(fk.Name)),
strings.Join(quoteColumnNames(copyAndReplace(fk.Columns, d.column.Name, d.asName)), ", "),
pq.QuoteIdentifier(fk.ReferencedTable),
strings.Join(quoteColumnNames(fk.ReferencedColumns), ", "))
Expand All @@ -73,6 +73,18 @@ func (d *Duplicator) Duplicate(ctx context.Context) error {
return err
}

func DuplicationName(name string) string {
return "_pgroll_dup_" + name
}

func IsDuplicatedName(name string) bool {
return strings.HasPrefix(name, "_pgroll_dup_")
}

func StripDuplicationPrefix(name string) string {
return strings.TrimPrefix(name, "_pgroll_dup_")
}

func copyAndReplace(xs []string, oldValue, newValue string) []string {
ys := slices.Clone(xs)

Expand Down
2 changes: 1 addition & 1 deletion pkg/migrations/op_change_type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func TestChangeColumnType(t *testing.T) {
},
afterStart: func(t *testing.T, db *sql.DB) {
// A temporary FK constraint has been created on the temporary column
ValidatedForeignKeyMustExist(t, db, "public", "employees", migrations.TemporaryName("fk_employee_department"))
ValidatedForeignKeyMustExist(t, db, "public", "employees", migrations.DuplicationName("fk_employee_department"))
},
afterRollback: func(t *testing.T, db *sql.DB) {
},
Expand Down
2 changes: 1 addition & 1 deletion pkg/migrations/op_set_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ func TestSetCheckConstraint(t *testing.T) {
},
afterStart: func(t *testing.T, db *sql.DB) {
// A temporary FK constraint has been created on the temporary column
ValidatedForeignKeyMustExist(t, db, "public", "employees", migrations.TemporaryName("fk_employee_department"))
ValidatedForeignKeyMustExist(t, db, "public", "employees", migrations.DuplicationName("fk_employee_department"))
},
afterRollback: func(t *testing.T, db *sql.DB) {
},
Expand Down
4 changes: 2 additions & 2 deletions pkg/migrations/op_set_fk.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (o *OpSetForeignKey) Complete(ctx context.Context, conn *sql.DB, s *schema.
// Validate the foreign key constraint
_, err := conn.ExecContext(ctx, fmt.Sprintf("ALTER TABLE IF EXISTS %s VALIDATE CONSTRAINT %s",
pq.QuoteIdentifier(o.Table),
pq.QuoteIdentifier(TemporaryName(o.References.Name))))
pq.QuoteIdentifier(o.References.Name)))
if err != nil {
return err
}
Expand Down Expand Up @@ -173,7 +173,7 @@ func (o *OpSetForeignKey) addForeignKeyConstraint(ctx context.Context, conn *sql

_, err := conn.ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s) NOT VALID",
pq.QuoteIdentifier(o.Table),
pq.QuoteIdentifier(TemporaryName(o.References.Name)),
pq.QuoteIdentifier(o.References.Name),
pq.QuoteIdentifier(tempColumnName),
pq.QuoteIdentifier(o.References.Table),
pq.QuoteIdentifier(o.References.Column),
Expand Down
4 changes: 2 additions & 2 deletions pkg/migrations/op_set_fk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestSetForeignKey(t *testing.T) {
ColumnMustExist(t, db, "public", "posts", migrations.TemporaryName("user_id"))

// A temporary FK constraint has been created on the temporary column
NotValidatedForeignKeyMustExist(t, db, "public", "posts", migrations.TemporaryName("fk_users_id"))
NotValidatedForeignKeyMustExist(t, db, "public", "posts", "fk_users_id")

// Inserting some data into the `users` table works.
MustInsert(t, db, "public", "02_add_fk_constraint", "users", map[string]string{
Expand Down Expand Up @@ -348,7 +348,7 @@ func TestSetForeignKey(t *testing.T) {
},
afterStart: func(t *testing.T, db *sql.DB) {
// A temporary FK constraint has been created on the temporary column
ValidatedForeignKeyMustExist(t, db, "public", "posts", migrations.TemporaryName("fk_users_id_1"))
ValidatedForeignKeyMustExist(t, db, "public", "posts", migrations.DuplicationName("fk_users_id_1"))
},
afterRollback: func(t *testing.T, db *sql.DB) {
},
Expand Down
2 changes: 1 addition & 1 deletion pkg/migrations/op_set_notnull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func TestSetNotNull(t *testing.T) {
},
afterStart: func(t *testing.T, db *sql.DB) {
// A temporary FK constraint has been created on the temporary column
ValidatedForeignKeyMustExist(t, db, "public", "employees", migrations.TemporaryName("fk_employee_department"))
ValidatedForeignKeyMustExist(t, db, "public", "employees", migrations.DuplicationName("fk_employee_department"))
},
afterRollback: func(t *testing.T, db *sql.DB) {
},
Expand Down
2 changes: 1 addition & 1 deletion pkg/migrations/op_set_unique_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ func TestSetColumnUnique(t *testing.T) {
},
afterStart: func(t *testing.T, db *sql.DB) {
// A temporary FK constraint has been created on the temporary column
ValidatedForeignKeyMustExist(t, db, "public", "employees", migrations.TemporaryName("fk_employee_department"))
ValidatedForeignKeyMustExist(t, db, "public", "employees", migrations.DuplicationName("fk_employee_department"))
},
afterRollback: func(t *testing.T, db *sql.DB) {
},
Expand Down
6 changes: 5 additions & 1 deletion pkg/migrations/rename.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ func RenameDuplicatedColumn(ctx context.Context, conn *sql.DB, table *schema.Tab
// to their original name
var renameConstraintSQL string
for _, fk := range table.ForeignKeys {
if !IsDuplicatedName(fk.Name) {
continue
}

if slices.Contains(fk.Columns, TemporaryName(column.Name)) {
renameConstraintSQL = fmt.Sprintf(cRenameConstraintSQL,
pq.QuoteIdentifier(table.Name),
pq.QuoteIdentifier(fk.Name),
pq.QuoteIdentifier(StripTemporaryPrefix(fk.Name)),
pq.QuoteIdentifier(StripDuplicationPrefix(fk.Name)),
)

_, err = conn.ExecContext(ctx, renameConstraintSQL)
Expand Down

0 comments on commit 7a38e1f

Please sign in to comment.