Skip to content

Commit

Permalink
Preserve foreign key constraints when changing a column's type (#232)
Browse files Browse the repository at this point in the history
Preserve any foreign key constraints defined on columns when changing a
column's type.

Uses the `Duplicator` type added in #230 in the 'change type' operation
to ensure that FK constraints are preserved when the column is
duplicated for backfilling.

#230 did the same thing for
preserving FK constraints when a column has a `NOT NULL` constraint
added.

Part of #227
  • Loading branch information
andrew-farries authored Jan 15, 2024
1 parent b81c42c commit fc7370f
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 148 deletions.
25 changes: 16 additions & 9 deletions pkg/migrations/duplicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,29 @@ import (
)

type Duplicator struct {
conn *sql.DB
table *schema.Table
column *schema.Column
asName string
conn *sql.DB
table *schema.Table
column *schema.Column
asName string
withType string
}

// NewColumnDuplicator creates a new Duplicator for a column.
func NewColumnDuplicator(conn *sql.DB, table *schema.Table, column *schema.Column) *Duplicator {
return &Duplicator{
conn: conn,
table: table,
column: column,
asName: TemporaryName(column.Name),
conn: conn,
table: table,
column: column,
asName: TemporaryName(column.Name),
withType: column.Type,
}
}

func (d *Duplicator) WithType(t string) *Duplicator {
d.withType = t
return d
}

// Duplicate creates a new column with the same type and foreign key
// constraints as the original column.
func (d *Duplicator) Duplicate(ctx context.Context) error {
Expand All @@ -41,7 +48,7 @@ func (d *Duplicator) Duplicate(ctx context.Context) error {
sql := fmt.Sprintf(cAlterTableSQL,
pq.QuoteIdentifier(d.table.Name),
pq.QuoteIdentifier(d.asName),
d.column.Type)
d.withType)

for _, fk := range d.table.ForeignKeys {
if slices.Contains(fk.Columns, d.column.Name) {
Expand Down
26 changes: 8 additions & 18 deletions pkg/migrations/op_change_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ func (o *OpChangeType) Start(ctx context.Context, conn *sql.DB, stateSchema stri
column := table.GetColumn(o.Column)

// Create a copy of the column on the underlying table.
if err := duplicateColumnForTypeChange(ctx, conn, table, *column, o.Type); err != nil {
d := NewColumnDuplicator(conn, table, column).WithType(o.Type)
if err := d.Duplicate(ctx); err != nil {
return fmt.Errorf("failed to duplicate column: %w", err)
}

Expand Down Expand Up @@ -99,12 +100,13 @@ func (o *OpChangeType) Complete(ctx context.Context, conn *sql.DB, s *schema.Sch
}

// Rename the new column to the old column name
_, err = conn.ExecContext(ctx, fmt.Sprintf("ALTER TABLE IF EXISTS %s RENAME COLUMN %s TO %s",
pq.QuoteIdentifier(o.Table),
pq.QuoteIdentifier(TemporaryName(o.Column)),
pq.QuoteIdentifier(o.Column)))
table := s.GetTable(o.Table)
column := table.GetColumn(o.Column)
if err := RenameDuplicatedColumn(ctx, conn, table, column); err != nil {
return err
}

return err
return nil
}

func (o *OpChangeType) Rollback(ctx context.Context, conn *sql.DB) error {
Expand Down Expand Up @@ -143,15 +145,3 @@ func (o *OpChangeType) Validate(ctx context.Context, s *schema.Schema) error {
}
return nil
}

func duplicateColumnForTypeChange(ctx context.Context, conn *sql.DB, table *schema.Table, column schema.Column, newType string) error {
column.Name = TemporaryName(column.Name)
column.Type = newType

_, err := conn.ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s",
pq.QuoteIdentifier(table.Name),
schemaColumnToSQL(column),
))

return err
}
Loading

0 comments on commit fc7370f

Please sign in to comment.