Repository: ariga/atlas
Pull request #731 (2022-04-23 17:24:36)

sql: add support for renaming resources (tables, columns and indexes)
<strike>The only thing left is to support renaming indexes in SQLite, which requires recreation of the index. </strike>


Patch:
diff --git a/sql/internal/sqlx/sqlx.go b/sql/internal/sqlx/sqlx.go
index d3183cca..67e12e87 100644
--- a/sql/internal/sqlx/sqlx.go
+++ b/sql/internal/sqlx/sqlx.go
@@ -314,7 +314,7 @@ func (b *Builder) Wrap(f func(b *Builder)) *Builder {
 func (b *Builder) Clone() *Builder {
 	return &Builder{
 		QuoteChar: b.QuoteChar,
-		Buffer:    *bytes.NewBufferString(b.String()),
+		Buffer:    *bytes.NewBufferString(b.Buffer.String()),
 	}
 }
 
diff --git a/sql/mysql/migrate.go b/sql/mysql/migrate.go
index ebf37cf2..c711f22c 100644
--- a/sql/mysql/migrate.go
+++ b/sql/mysql/migrate.go
@@ -74,17 +74,18 @@ func (s *state) plan(changes []schema.Change) error {
 	for _, c := range planned {
 		switch c := c.(type) {
 		case *schema.AddTable:
-			if err := s.addTable(c); err != nil {
-				return err
-			}
+			err = s.addTable(c)
 		case *schema.DropTable:
 			s.dropTable(c)
 		case *schema.ModifyTable:
-			if err := s.modifyTable(c); err != nil {
-				return err
-			}
+			err = s.modifyTable(c)
+		case *schema.RenameTable:
+			s.renameTable(c)
 		default:
-			return fmt.Errorf("unsupported change %T", c)
+			err = fmt.Errorf("unsupported change %T", c)
+		}
+		if err != nil {
+			return err
 		}
 	}
 	return nil
@@ -347,6 +348,9 @@ func (s *state) alterTable(t *schema.Table, changes []schema.Change) error {
 					To:     change.From,
 					Change: change.Change,
 				})
+			case *schema.RenameColumn:
+				b.P("RENAME COLUMN").Ident(change.From.Name).P("TO").Ident(change.To.Name)
+				reverse = append(reverse, &schema.RenameColumn{From: change.To, To: change.From})
 			case *schema.DropColumn:
 				b.P("DROP COLUMN").Ident(change.C.Name)
 				reverse = append(reverse, &schema.AddColumn{C: change.C})
@@ -354,6 +358,9 @@ func (s *state) alterTable(t *schema.Table, changes []schema.Change) error {
 				b.P("ADD")
 				index(b, change.I)
 				reverse = append(reverse, &schema.DropIndex{I: change.I})
+			case *schema.RenameIndex:
+				b.P("RENAME INDEX").Ident(change.From.Name).P("TO").Ident(change.To.Name)
+				reverse = append(reverse, &schema.RenameIndex{From: change.To, To: change.From})
 			case *schema.DropIndex:
 				b.P("DROP INDEX").Ident(change.I.Name)
 				reverse = append(reverse, &schema.AddIndex{I: change.I})
@@ -438,6 +445,15 @@ func (s *state) alterTable(t *schema.Table, changes []schema.Change) error {
 	return nil
 }
 
+func (s *state) renameTable(c *schema.RenameTable) {
+	s.append(&migrate.Change{
+		Source:  c,
+		Comment: fmt.Sprintf("rename a table from %q to %q", c.From.Name, c.To.Name),
+		Cmd:     Build("RENAME TABLE").Table(c.From).P("TO").Table(c.To).String(),
+		Reverse: Build("RENAME TABLE").Table(c.To).P("TO").Table(c.From).String(),
+	})
+}
+
 func (s *state) column(b *sqlx.Builder, t *schema.Table, c *schema.Column) error {
 	typ, err := FormatType(c.Type.Type)
 	if err != nil {
diff --git a/sql/postgres/migrate.go b/sql/postgres/migrate.go
index a1848919..704cd5d3 100644
--- a/sql/postgres/migrate.go
+++ b/sql/postgres/migrate.go
@@ -71,6 +71,8 @@ func (s *state) plan(ctx context.Context, changes []schema.Change) error {
 			s.dropTable(c)
 		case *schema.ModifyTable:
 			err = s.modifyTable(ctx, c)
+		case *schema.RenameTable:
+			s.renameTable(c)
 		default:
 			err = fmt.Errorf("unsupported change %T", c)
 		}
@@ -219,6 +221,13 @@ func (s *state) modifyTable(ctx context.Context, modify *schema.ModifyTable) err
 			// Index modification requires rebuilding the index.
 			addI = append(addI, change.To)
 			dropI = append(dropI, change.From)
+		case *schema.RenameIndex:
+			s.append(&migrate.Change{
+				Source:  change,
+				Comment: fmt.Sprintf("rename an index from %q to %q", change.From.Name, change.To.Name),
+				Cmd:     Build("ALTER INDEX").Ident(change.From.Name).P("RENAME TO").Ident(change.To.Name).String(),
+				Reverse: Build("ALTER INDEX").Ident(change.To.Name).P("RENAME TO").Ident(change.From.Name).String(),
+			})
 		case *schema.ModifyForeignKey:
 			// Foreign-key modification is translated into 2 steps.
 			// Dropping the current foreign key and creating a new one.
@@ -299,9 +308,6 @@ func (s *state) alterTable(t *schema.Table, changes []schema.Change) error {
 					return err
 				}
 				reverse = append(reverse, &schema.DropColumn{C: change.C})
-			case *schema.DropColumn:
-				b.P("DROP COLUMN").Ident(change.C.Name)
-				reverse = append(reverse, &schema.AddColumn{C: change.C})
 			case *schema.ModifyColumn:
 				if err := s.alterColumn(b, change.Change, change.To); err != nil {
 					return err
@@ -314,6 +320,12 @@ func (s *state) alterTable(t *schema.Table, changes []schema.Change) error {
 					To:     change.From,
 					Change: change.Change & ^schema.ChangeGenerated,
 				})
+			case *schema.RenameColumn:
+				b.P("RENAME COLUMN").Ident(change.From.Name).P("TO").Ident(change.To.Name)
+				reverse = append(reverse, &schema.RenameColumn{From: change.To, To: change.From})
+			case *schema.DropColumn:
+				b.P("DROP COLUMN").Ident(change.C.Name)
+				reverse = append(reverse, &schema.AddColumn{C: change.C})
 			case *schema.AddForeignKey:
 				b.P("ADD")
 				s.fks(b, change.F)
@@ -378,6 +390,15 @@ func (s *state) alterTable(t *schema.Table, changes []schema.Change) error {
 	return nil
 }
 
+func (s *state) renameTable(c *schema.RenameTable) {
+	s.append(&migrate.Change{
+		Source:  c,
+		Comment: fmt.Sprintf("rename a table from %q to %q", c.From.Name, c.To.Name),
+		Cmd:     Build("ALTER TABLE").Table(c.From).P("RENAME TO").Table(c.To).String(),
+		Reverse: Build("ALTER TABLE").Table(c.To).P("RENAME TO").Table(c.From).String(),
+	})
+}
+
 func (s *state) addComments(t *schema.Table) {
 	var c schema.Comment
 	if sqlx.Has(t.Attrs, &c) && c.Text != "" {
@@ -430,7 +451,7 @@ func (s *state) dropIndexes(t *schema.Table, indexes ...*schema.Index) {
 	for i, idx := range indexes {
 		s.append(&migrate.Change{
 			Cmd:     rs.Changes[i].Reverse,
-			Comment: fmt.Sprintf("Drop index %q from table: %q", idx.Name, t.Name),
+			Comment: fmt.Sprintf("drop index %q from table: %q", idx.Name, t.Name),
 			Reverse: rs.Changes[i].Cmd,
 		})
 	}
@@ -507,7 +528,7 @@ func (s *state) addIndexes(t *schema.Table, indexes ...*schema.Index) {
 		s.index(b, idx)
 		s.append(&migrate.Change{
 			Cmd:     b.String(),
-			Comment: fmt.Sprintf("Create index %q to table: %q", idx.Name, t.Name),
+			Comment: fmt.Sprintf("create index %q to table: %q", idx.Name, t.Name),
 			Reverse: func() string {
 				b := Build("DROP INDEX")
 				// Unlike MySQL, the DROP command is not attached to ALTER TABLE.
diff --git a/sql/schema/migrate.go b/sql/schema/migrate.go
index 56c9ab8c..6cf7cf7a 100644
--- a/sql/schema/migrate.go
+++ b/sql/schema/migrate.go
@@ -81,6 +81,11 @@ type (
 		Changes []Change
 	}
 
+	// RenameTable describes a table rename change.
+	RenameTable struct {
+		From, To *Table
+	}
+
 	// AddColumn describes a column creation change.
 	AddColumn struct {
 		C *Column
@@ -97,6 +102,11 @@ type (
 		Change   ChangeKind
 	}
 
+	// RenameColumn describes a column rename change.
+	RenameColumn struct {
+		From, To *Column
+	}
+
 	// AddIndex describes an index creation change.
 	AddIndex struct {
 		I *Index
@@ -113,6 +123,11 @@ type (
 		Change   ChangeKind
 	}
 
+	// RenameIndex describes an index rename change.
+	RenameIndex struct {
+		From, To *Index
+	}
+
 	// AddForeignKey describes a foreign-key creation change.
 	AddForeignKey struct {
 		F *ForeignKey
@@ -278,15 +293,18 @@ func (*ModifySchema) change()     {}
 func (*AddTable) change()         {}
 func (*DropTable) change()        {}
 func (*ModifyTable) change()      {}
+func (*RenameTable) change()      {}
 func (*AddIndex) change()         {}
 func (*DropIndex) change()        {}
 func (*ModifyIndex) change()      {}
+func (*RenameIndex) change()      {}
 func (*AddCheck) change()         {}
 func (*DropCheck) change()        {}
 func (*ModifyCheck) change()      {}
 func (*AddColumn) change()        {}
 func (*DropColumn) change()       {}
 func (*ModifyColumn) change()     {}
+func (*RenameColumn) change()     {}
 func (*AddForeignKey) change()    {}
 func (*DropForeignKey) change()   {}
 func (*ModifyForeignKey) change() {}
diff --git a/sql/sqlite/migrate.go b/sql/sqlite/migrate.go
index 5161c273..f5289dae 100644
--- a/sql/sqlite/migrate.go
+++ b/sql/sqlite/migrate.go
@@ -65,6 +65,8 @@ func (s *state) plan(ctx context.Context, changes []schema.Change) (err error) {
 			err = s.dropTable(c)
 		case *schema.ModifyTable:
 			err = s.modifyTable(ctx, c)
+		case *schema.RenameTable:
+			s.renameTable(c)
 		default:
 			err = fmt.Errorf("unsupported change %T", c)
 		}
@@ -181,6 +183,15 @@ func (s *state) modifyTable(ctx context.Context, modify *schema.ModifyTable) err
 	return s.addIndexes(modify.T, indexes...)
 }
 
+func (s *state) renameTable(c *schema.RenameTable) {
+	s.append(&migrate.Change{
+		Source:  c,
+		Comment: fmt.Sprintf("rename a table from %q to %q", c.From.Name, c.To.Name),
+		Cmd:     Build("ALTER TABLE").Table(c.From).P("RENAME TO").Table(c.To).String(),
+		Reverse: Build("ALTER TABLE").Table(c.To).P("RENAME TO").Table(c.From).String(),
+	})
+}
+
 func (s *state) column(b *sqlx.Builder, c *schema.Column) error {
 	t, err := FormatType(c.Type.Type)
 	if err != nil {
@@ -211,6 +222,21 @@ func (s *state) column(b *sqlx.Builder, c *schema.Column) error {
 	return nil
 }
 
+func (s *state) dropIndexes(t *schema.Table, indexes ...*schema.Index) error {
+	rs := &state{conn: s.conn}
+	if err := rs.addIndexes(t, indexes...); err != nil {
+		return err
+	}
+	for i := range rs.Changes {
+		s.append(&migrate.Change{
+			Cmd:     rs.Changes[i].Reverse,
+			Reverse: rs.Changes[i].Cmd,
+			Comment: fmt.Sprintf("drop index %q from table: %q", indexes[i].Name, t.Name),
+		})
+	}
+	return nil
+}
+
 func (s *state) addIndexes(t *schema.Table, indexes ...*schema.Index) error {
 	for _, idx := range indexes {
 		// PRIMARY KEY or UNIQUE columns automatically create indexes with the generated name.
@@ -377,23 +403,37 @@ func (s *state) alterTable(modify *schema.ModifyTable) error {
 				return err
 			}
 		case *schema.DropIndex:
-			b := Build("DROP INDEX").Ident(change.I.Name)
-			s.append(&migrate.Change{
-				Cmd:     b.String(),
-				Source:  change,
-				Comment: fmt.Sprintf("drop index %q to table: %q", change.I.Name, modify.T.Name),
-			})
+			if err := s.dropIndexes(modify.T, change.I); err != nil {
+				return err
+			}
+		case *schema.RenameIndex:
+			if err := s.addIndexes(modify.T, change.To); err != nil {
+				return err
+			}
+			if err := s.dropIndexes(modify.T, change.From); err != nil {
+				return err
+			}
 		case *schema.AddColumn:
-			b := Build("ALTER TABLE").Ident(modify.T.Name).P("ADD COLUMN")
-			if err := s.column(b, change.C); err != nil {
+			b := Build("ALTER TABLE").Ident(modify.T.Name)
+			r := b.Clone()
+			if err := s.column(b.P("ADD COLUMN"), change.C); err != nil {
 				return err
 			}
-			// Unsupported reverse operation (DROP COLUMN).
 			s.append(&migrate.Change{
-				Cmd:     b.String(),
 				Source:  change,
+				Cmd:     b.String(),
+				Reverse: r.P("DROP COLUMN").Ident(change.C.Name).String(),
 				Comment: fmt.Sprintf("add column %q to table: %q", change.C.Name, modify.T.Name),
 			})
+		case *schema.RenameColumn:
+			b := Build("ALTER TABLE").Ident(modify.T.Name).P("RENAME COLUMN")
+			r := b.Clone()
+			s.append(&migrate.Change{
+				Source:  change,
+				Cmd:     b.Ident(change.From.Name).P("TO").Ident(change.To.Name).String(),
+				Reverse: r.Ident(change.To.Name).P("TO").Ident(change.From.Name).String(),
+				Comment: fmt.Sprintf("rename a column from %q to %q", change.From.Name, change.To.Name),
+			})
 		default:
 			return fmt.Errorf("unexpected change in alter table: %T", change)
 		}
@@ -439,7 +479,7 @@ func (s *state) append(c *migrate.Change) {
 func alterable(modify *schema.ModifyTable) bool {
 	for _, change := range modify.Changes {
 		switch change := change.(type) {
-		case *schema.DropIndex, *schema.AddIndex:
+		case *schema.RenameColumn, *schema.RenameIndex, *schema.DropIndex, *schema.AddIndex:
 		case *schema.AddColumn:
 			if len(change.C.Indexes) > 0 || len(change.C.ForeignKeys) > 0 || change.C.Default != nil {
 				return false


Test patch:
diff --git a/sql/mysql/migrate_test.go b/sql/mysql/migrate_test.go
index 3af3ce6c..8577704a 100644
--- a/sql/mysql/migrate_test.go
+++ b/sql/mysql/migrate_test.go
@@ -327,7 +327,7 @@ func TestPlanChanges(t *testing.T) {
 			},
 			wantPlan: &migrate.Plan{
 				Reversible: true,
-				Changes:    []*migrate.Change{{Cmd: "ALTER DATABASE `test` CHARSET latin1", Reverse: "ALTER DATABASE `test`CHARSET utf8"}},
+				Changes:    []*migrate.Change{{Cmd: "ALTER DATABASE `test` CHARSET latin1", Reverse: "ALTER DATABASE `test` CHARSET utf8"}},
 			},
 		},
 		// Modify charset.
@@ -342,7 +342,7 @@ func TestPlanChanges(t *testing.T) {
 			},
 			wantPlan: &migrate.Plan{
 				Reversible: true,
-				Changes:    []*migrate.Change{{Cmd: "ALTER DATABASE `test` CHARSET utf8", Reverse: "ALTER DATABASE `test`CHARSET latin1"}},
+				Changes:    []*migrate.Change{{Cmd: "ALTER DATABASE `test` CHARSET utf8", Reverse: "ALTER DATABASE `test` CHARSET latin1"}},
 			},
 		},
 		{
@@ -720,6 +720,84 @@ func TestPlanChanges(t *testing.T) {
 				},
 			},
 		},
+		{
+			input: []schema.Change{
+				&schema.RenameTable{
+					From: schema.NewTable("t1"),
+					To:   schema.NewTable("t2"),
+				},
+			},
+			wantPlan: &migrate.Plan{
+				Reversible: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     "RENAME TABLE `t1` TO `t2`",
+						Reverse: "RENAME TABLE `t2` TO `t1`",
+					},
+				},
+			},
+		},
+		{
+			input: []schema.Change{
+				&schema.RenameTable{
+					From: schema.NewTable("t1").SetSchema(schema.New("s1")),
+					To:   schema.NewTable("t2").SetSchema(schema.New("s2")),
+				},
+			},
+			wantPlan: &migrate.Plan{
+				Reversible: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     "RENAME TABLE `s1`.`t1` TO `s2`.`t2`",
+						Reverse: "RENAME TABLE `s2`.`t2` TO `s1`.`t1`",
+					},
+				},
+			},
+		},
+		{
+			input: []schema.Change{
+				&schema.ModifyTable{
+					T: schema.NewTable("t1").SetSchema(schema.New("s1")),
+					Changes: []schema.Change{
+						&schema.RenameColumn{
+							From: schema.NewColumn("a"),
+							To:   schema.NewColumn("b"),
+						},
+					},
+				},
+			},
+			wantPlan: &migrate.Plan{
+				Reversible: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     "ALTER TABLE `s1`.`t1` RENAME COLUMN `a` TO `b`",
+						Reverse: "ALTER TABLE `s1`.`t1` RENAME COLUMN `b` TO `a`",
+					},
+				},
+			},
+		},
+		{
+			input: []schema.Change{
+				&schema.ModifyTable{
+					T: schema.NewTable("t1").SetSchema(schema.New("s1")),
+					Changes: []schema.Change{
+						&schema.RenameIndex{
+							From: schema.NewIndex("a"),
+							To:   schema.NewIndex("b"),
+						},
+					},
+				},
+			},
+			wantPlan: &migrate.Plan{
+				Reversible: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     "ALTER TABLE `s1`.`t1` RENAME INDEX `a` TO `b`",
+						Reverse: "ALTER TABLE `s1`.`t1` RENAME INDEX `b` TO `a`",
+					},
+				},
+			},
+		},
 	}
 	for i, tt := range tests {
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
diff --git a/sql/postgres/migrate_test.go b/sql/postgres/migrate_test.go
index 61e2433c..ed33b7bf 100644
--- a/sql/postgres/migrate_test.go
+++ b/sql/postgres/migrate_test.go
@@ -668,6 +668,70 @@ func TestPlanChanges(t *testing.T) {
 				},
 			},
 		},
+		{
+			changes: []schema.Change{
+				&schema.RenameTable{
+					From: schema.NewTable("t1").SetSchema(schema.New("s1")),
+					To:   schema.NewTable("t2").SetSchema(schema.New("s2")),
+				},
+			},
+			plan: &migrate.Plan{
+				Reversible:    true,
+				Transactional: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     `ALTER TABLE "s1"."t1" RENAME TO "s2"."t2"`,
+						Reverse: `ALTER TABLE "s2"."t2" RENAME TO "s1"."t1"`,
+					},
+				},
+			},
+		},
+		{
+			changes: []schema.Change{
+				&schema.ModifyTable{
+					T: schema.NewTable("t1").SetSchema(schema.New("s1")),
+					Changes: []schema.Change{
+						&schema.RenameColumn{
+							From: schema.NewColumn("a"),
+							To:   schema.NewColumn("b"),
+						},
+					},
+				},
+			},
+			plan: &migrate.Plan{
+				Reversible:    true,
+				Transactional: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     `ALTER TABLE "s1"."t1" RENAME COLUMN "a" TO "b"`,
+						Reverse: `ALTER TABLE "s1"."t1" RENAME COLUMN "b" TO "a"`,
+					},
+				},
+			},
+		},
+		{
+			changes: []schema.Change{
+				&schema.ModifyTable{
+					T: schema.NewTable("t1").SetSchema(schema.New("s1")),
+					Changes: []schema.Change{
+						&schema.RenameIndex{
+							From: schema.NewIndex("a"),
+							To:   schema.NewIndex("b"),
+						},
+					},
+				},
+			},
+			plan: &migrate.Plan{
+				Reversible:    true,
+				Transactional: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     `ALTER INDEX "a" RENAME TO "b"`,
+						Reverse: `ALTER INDEX "b" RENAME TO "a"`,
+					},
+				},
+			},
+		},
 	}
 	for i, tt := range tests {
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
diff --git a/sql/sqlite/migrate_test.go b/sql/sqlite/migrate_test.go
index f244504f..6831de7e 100644
--- a/sql/sqlite/migrate_test.go
+++ b/sql/sqlite/migrate_test.go
@@ -121,9 +121,10 @@ func TestPlanChanges(t *testing.T) {
 				}(),
 			},
 			plan: &migrate.Plan{
+				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
-					{Cmd: "ALTER TABLE `users` ADD COLUMN `name` varchar(255) NOT NULL"},
+					{Cmd: "ALTER TABLE `users` ADD COLUMN `name` varchar(255) NOT NULL", Reverse: "ALTER TABLE `users` DROP COLUMN `name`"},
 					{Cmd: "CREATE INDEX `id_key` ON `users` (`id`)", Reverse: "DROP INDEX `id_key`"},
 				},
 			},
@@ -146,9 +147,10 @@ func TestPlanChanges(t *testing.T) {
 				}(),
 			},
 			plan: &migrate.Plan{
+				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
-					{Cmd: "ALTER TABLE `users` ADD COLUMN `nid` bigint NOT NULL AS (1)"},
+					{Cmd: "ALTER TABLE `users` ADD COLUMN `nid` bigint NOT NULL AS (1)", Reverse: "ALTER TABLE `users` DROP COLUMN `nid`"},
 				},
 			},
 		},
@@ -221,6 +223,74 @@ func TestPlanChanges(t *testing.T) {
 				},
 			},
 		},
+		{
+			changes: []schema.Change{
+				&schema.RenameTable{
+					From: schema.NewTable("t1"),
+					To:   schema.NewTable("t2"),
+				},
+			},
+			plan: &migrate.Plan{
+				Reversible:    true,
+				Transactional: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     "ALTER TABLE `t1` RENAME TO `t2`",
+						Reverse: "ALTER TABLE `t2` RENAME TO `t1`",
+					},
+				},
+			},
+		},
+		{
+			changes: []schema.Change{
+				&schema.ModifyTable{
+					T: schema.NewTable("t1"),
+					Changes: []schema.Change{
+						&schema.RenameColumn{
+							From: schema.NewColumn("a"),
+							To:   schema.NewColumn("b"),
+						},
+					},
+				},
+			},
+			plan: &migrate.Plan{
+				Reversible:    true,
+				Transactional: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     "ALTER TABLE `t1` RENAME COLUMN `a` TO `b`",
+						Reverse: "ALTER TABLE `t1` RENAME COLUMN `b` TO `a`",
+					},
+				},
+			},
+		},
+		{
+			changes: []schema.Change{
+				&schema.ModifyTable{
+					T: schema.NewTable("t1"),
+					Changes: []schema.Change{
+						&schema.RenameIndex{
+							From: schema.NewIndex("a").AddColumns(schema.NewColumn("a")),
+							To:   schema.NewIndex("b").AddColumns(schema.NewColumn("a")),
+						},
+					},
+				},
+			},
+			plan: &migrate.Plan{
+				Reversible:    true,
+				Transactional: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     "CREATE INDEX `b` ON `t1` (`a`)",
+						Reverse: "DROP INDEX `b`",
+					},
+					{
+						Cmd:     "DROP INDEX `a`",
+						Reverse: "CREATE INDEX `a` ON `t1` (`a`)",
+					},
+				},
+			},
+		},
 	}
 	for i, tt := range tests {
 		t.Run(strconv.Itoa(i), func(t *testing.T) {