Skip to content

Commit e689a12

Browse files
Multi-operation migration support for alter_column rename operations (#593)
Ensure that `alter_column` rename column operations can be used as part of multi-operation migrations: Add testcases for: * add column, rename column * rename table, rename column on the renamed table * rename column, drop rename column Update the `Rollback` and `Validate` methods to be aware of the effect of previous operations in the same migration and make sure their own changes are visible to other operations in the same migration. Part of #239
1 parent 9118345 commit e689a12

File tree

2 files changed

+242
-0
lines changed

2 files changed

+242
-0
lines changed

pkg/migrations/op_alter_column.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ func (o *OpAlterColumn) Complete(ctx context.Context, conn db.DB, tr SQLTransfor
153153

154154
func (o *OpAlterColumn) Rollback(ctx context.Context, conn db.DB, tr SQLTransformer, s *schema.Schema) error {
155155
ops := o.subOperations()
156+
table := s.GetTable(o.Table)
156157

157158
// Perform any operation specific rollback steps
158159
for _, ops := range ops {
@@ -188,6 +189,12 @@ func (o *OpAlterColumn) Rollback(ctx context.Context, conn db.DB, tr SQLTransfor
188189
}
189190
}
190191

192+
// Rename the column back to its original name in the virtual schema if
193+
// required
194+
if o.Name != nil {
195+
table.RenameColumn(*o.Name, o.Column)
196+
}
197+
191198
return nil
192199
}
193200

@@ -244,6 +251,12 @@ func (o *OpAlterColumn) Validate(ctx context.Context, s *schema.Schema) error {
244251
}
245252
}
246253

254+
// Rename the column in the virtual schema if required so that subsequent
255+
// operations can validate using the new column name
256+
if o.Name != nil {
257+
table.RenameColumn(o.Column, *o.Name)
258+
}
259+
247260
return nil
248261
}
249262

pkg/migrations/op_alter_column_test.go

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,235 @@ func TestAlterColumnMultipleSubOperations(t *testing.T) {
505505
})
506506
}
507507

508+
func TestAlterColumnInMultiOperationMigrations(t *testing.T) {
509+
t.Parallel()
510+
511+
ExecuteTests(t, TestCases{
512+
{
513+
name: "add column, rename column",
514+
migrations: []migrations.Migration{
515+
{
516+
Name: "01_create_table",
517+
Operations: migrations.Operations{
518+
&migrations.OpCreateTable{
519+
Name: "items",
520+
Columns: []migrations.Column{
521+
{
522+
Name: "id",
523+
Type: "serial",
524+
Pk: true,
525+
},
526+
{
527+
Name: "name",
528+
Type: "varchar(255)",
529+
},
530+
},
531+
},
532+
},
533+
},
534+
{
535+
Name: "02_multi_operation",
536+
Operations: migrations.Operations{
537+
&migrations.OpAddColumn{
538+
Table: "items",
539+
Column: migrations.Column{
540+
Name: "description",
541+
Type: "text",
542+
Nullable: true,
543+
},
544+
},
545+
&migrations.OpAlterColumn{
546+
Table: "items",
547+
Column: "description",
548+
Name: ptr("item_description"),
549+
},
550+
},
551+
},
552+
},
553+
afterStart: func(t *testing.T, db *sql.DB, schema string) {
554+
// Can insert into the new column under its new name
555+
MustInsert(t, db, schema, "02_multi_operation", "items", map[string]string{
556+
"name": "apples",
557+
"item_description": "amazing",
558+
})
559+
560+
// Can't insert into the new column under its old name
561+
MustNotInsert(t, db, schema, "02_multi_operation", "items", map[string]string{
562+
"name": "bananas",
563+
"description": "brilliant",
564+
}, testutils.UndefinedColumnErrorCode)
565+
566+
// The table has the expected rows in the new schema
567+
rows := MustSelect(t, db, schema, "02_multi_operation", "items")
568+
assert.Equal(t, []map[string]any{
569+
{"id": 1, "name": "apples", "item_description": "amazing"},
570+
}, rows)
571+
},
572+
afterRollback: func(t *testing.T, db *sql.DB, schema string) {
573+
// The table has been cleaned up
574+
TableMustBeCleanedUp(t, db, schema, "items", "description")
575+
},
576+
afterComplete: func(t *testing.T, db *sql.DB, schema string) {
577+
// Can insert into the new column under its new name
578+
MustInsert(t, db, schema, "02_multi_operation", "items", map[string]string{
579+
"name": "bananas",
580+
"item_description": "brilliant",
581+
})
582+
583+
// The table has the expected rows in the new schema
584+
rows := MustSelect(t, db, schema, "02_multi_operation", "items")
585+
assert.Equal(t, []map[string]any{
586+
{"id": 1, "name": "apples", "item_description": nil},
587+
{"id": 2, "name": "bananas", "item_description": "brilliant"},
588+
}, rows)
589+
},
590+
},
591+
{
592+
name: "rename table, rename column",
593+
migrations: []migrations.Migration{
594+
{
595+
Name: "01_create_table",
596+
Operations: migrations.Operations{
597+
&migrations.OpCreateTable{
598+
Name: "items",
599+
Columns: []migrations.Column{
600+
{
601+
Name: "id",
602+
Type: "serial",
603+
Pk: true,
604+
},
605+
{
606+
Name: "name",
607+
Type: "varchar(255)",
608+
},
609+
},
610+
},
611+
},
612+
},
613+
{
614+
Name: "02_multi_operation",
615+
Operations: migrations.Operations{
616+
&migrations.OpRenameTable{
617+
From: "items",
618+
To: "products",
619+
},
620+
&migrations.OpAlterColumn{
621+
Table: "products",
622+
Column: "name",
623+
Name: ptr("item_name"),
624+
},
625+
},
626+
},
627+
},
628+
afterStart: func(t *testing.T, db *sql.DB, schema string) {
629+
// Can insert using the old version schema (old table name, and old
630+
// column name)
631+
MustInsert(t, db, schema, "01_create_table", "items", map[string]string{
632+
"name": "apples",
633+
})
634+
635+
// Can insert using the new version schema (new table name, and new
636+
// column name)
637+
MustInsert(t, db, schema, "02_multi_operation", "products", map[string]string{
638+
"item_name": "bananas",
639+
})
640+
641+
// The table has the expected rows
642+
rows := MustSelect(t, db, schema, "02_multi_operation", "products")
643+
assert.Equal(t, []map[string]any{
644+
{"id": 1, "item_name": "apples"},
645+
{"id": 2, "item_name": "bananas"},
646+
}, rows)
647+
},
648+
afterRollback: func(t *testing.T, db *sql.DB, schema string) {
649+
// Can insert using the old version schema (old table name, and old
650+
// column name)
651+
MustInsert(t, db, schema, "01_create_table", "items", map[string]string{
652+
"name": "carrots",
653+
})
654+
},
655+
afterComplete: func(t *testing.T, db *sql.DB, schema string) {
656+
// Can insert using the new version schema (new table name, and new
657+
// column name)
658+
MustInsert(t, db, schema, "02_multi_operation", "products", map[string]string{
659+
"item_name": "durian",
660+
})
661+
662+
// The table has the expected rows
663+
rows := MustSelect(t, db, schema, "02_multi_operation", "products")
664+
assert.Equal(t, []map[string]any{
665+
{"id": 1, "item_name": "apples"},
666+
{"id": 2, "item_name": "bananas"},
667+
{"id": 3, "item_name": "carrots"},
668+
{"id": 4, "item_name": "durian"},
669+
}, rows)
670+
},
671+
},
672+
{
673+
name: "rename column, drop column",
674+
migrations: []migrations.Migration{
675+
{
676+
Name: "01_create_table",
677+
Operations: migrations.Operations{
678+
&migrations.OpCreateTable{
679+
Name: "items",
680+
Columns: []migrations.Column{
681+
{
682+
Name: "id",
683+
Type: "serial",
684+
Pk: true,
685+
},
686+
{
687+
Name: "name",
688+
Type: "varchar(255)",
689+
Nullable: true,
690+
},
691+
},
692+
},
693+
},
694+
},
695+
{
696+
Name: "02_multi_operation",
697+
Operations: migrations.Operations{
698+
&migrations.OpAlterColumn{
699+
Table: "items",
700+
Column: "name",
701+
Name: ptr("item_name"),
702+
},
703+
&migrations.OpDropColumn{
704+
Table: "items",
705+
Column: "item_name",
706+
},
707+
},
708+
},
709+
},
710+
afterStart: func(t *testing.T, db *sql.DB, schema string) {
711+
// Can't insert into the dropped column in the new schema
712+
MustNotInsert(t, db, schema, "02_multi_operation", "items", map[string]string{
713+
"item_name": "apples",
714+
}, testutils.UndefinedColumnErrorCode)
715+
716+
// Can insert into the old column name in the old schema
717+
MustInsert(t, db, schema, "01_create_table", "items", map[string]string{
718+
"name": "apples",
719+
})
720+
},
721+
afterRollback: func(t *testing.T, db *sql.DB, schema string) {
722+
// Can insert into the old column name in the old schema
723+
MustInsert(t, db, schema, "01_create_table", "items", map[string]string{
724+
"name": "bananas",
725+
})
726+
},
727+
afterComplete: func(t *testing.T, db *sql.DB, schema string) {
728+
// Can't insert into the dropped column in the new schema
729+
MustNotInsert(t, db, schema, "02_multi_operation", "items", map[string]string{
730+
"item_name": "apples",
731+
}, testutils.UndefinedColumnErrorCode)
732+
},
733+
},
734+
})
735+
}
736+
508737
func TestIsRenameOnly(t *testing.T) {
509738
t.Parallel()
510739

0 commit comments

Comments
 (0)