Repository: ariga/atlas
Pull request #498 (2022-01-26 14:23:11)

sql/mysql: support reverse drop fk


Patch:
diff --git a/schema/schemaspec/spec.go b/schema/schemaspec/spec.go
index 7a6a2249..c58a70cf 100644
--- a/schema/schemaspec/spec.go
+++ b/schema/schemaspec/spec.go
@@ -81,6 +81,9 @@ type (
 		// This field is optional and used to determine the TypeSpec in cases where the
 		// schema.Type does not have a `T` field.
 		RType reflect.Type
+		// Printer is an optional function used by the drivers to get a string representation of the
+		// represented Type, that can be parsed by the driver into a schema.Type.
+		Printer func(*Type) (string, error)
 	}
 
 	// TypeAttr describes an attribute of a TypeSpec, for example `varchar` fields
diff --git a/sql/internal/specutil/types.go b/sql/internal/specutil/types.go
index d21dd4b1..8ce0491c 100644
--- a/sql/internal/specutil/types.go
+++ b/sql/internal/specutil/types.go
@@ -21,6 +21,9 @@ func (r *TypeRegistry) PrintType(typ *schemaspec.Type) (string, error) {
 	if !ok {
 		return "", fmt.Errorf("specutil: type %q not found in registry", typ.T)
 	}
+	if spec.Printer != nil {
+		return spec.Printer(typ)
+	}
 	if len(spec.Attributes) == 0 {
 		return typ.T, nil
 	}
@@ -280,18 +283,38 @@ func (r *TypeRegistry) Type(typ *schemaspec.Type, extra []*schemaspec.Attr) (sch
 	return r.parser(printType)
 }
 
+// TypeSpecOption configures a schemaspec.TypeSpec.
+type TypeSpecOption func(*schemaspec.TypeSpec)
+
+// WithAttributes returns an attributes TypeSpecOption.
+func WithAttributes(attrs ...*schemaspec.TypeAttr) TypeSpecOption {
+	return func(spec *schemaspec.TypeSpec) {
+		spec.Attributes = attrs
+	}
+}
+
+// WithPrinter returns a printer TypeSpecOption.
+func WithPrinter(p func(*schemaspec.Type) (string, error)) TypeSpecOption {
+	return func(spec *schemaspec.TypeSpec) {
+		spec.Printer = p
+	}
+}
+
 // TypeSpec returns a TypeSpec with the provided name.
-func TypeSpec(name string, attrs ...*schemaspec.TypeAttr) *schemaspec.TypeSpec {
-	return AliasTypeSpec(name, name, attrs...)
+func TypeSpec(name string, opts ...TypeSpecOption) *schemaspec.TypeSpec {
+	return AliasTypeSpec(name, name, opts...)
 }
 
 // AliasTypeSpec returns a TypeSpec with the provided name.
-func AliasTypeSpec(name, dbType string, attrs ...*schemaspec.TypeAttr) *schemaspec.TypeSpec {
-	return &schemaspec.TypeSpec{
-		Name:       name,
-		T:          dbType,
-		Attributes: attrs,
+func AliasTypeSpec(name, dbType string, opts ...TypeSpecOption) *schemaspec.TypeSpec {
+	ts := &schemaspec.TypeSpec{
+		Name: name,
+		T:    dbType,
+	}
+	for _, opt := range opts {
+		opt(ts)
 	}
+	return ts
 }
 
 // SizeTypeAttr returns a TypeAttr for a size attribute.
diff --git a/sql/mysql/migrate.go b/sql/mysql/migrate.go
index c116aa01..26342cc9 100644
--- a/sql/mysql/migrate.go
+++ b/sql/mysql/migrate.go
@@ -377,7 +377,10 @@ func (s *state) alterTable(t *schema.Table, changes []schema.Change) error {
 			reverse.Comma().P("DROP FOREIGN KEY").Ident(change.F.Symbol)
 		case *schema.DropForeignKey:
 			b.P("DROP FOREIGN KEY").Ident(change.F.Symbol)
-			reversible = false
+			reverse.Comma().P("ADD")
+			if err := s.fks(reverse, change.F); err != nil {
+				errors = append(errors, err.Error())
+			}
 		case *schema.AddAttr:
 			s.tableAttr(b, change, change.A)
 			// Unsupported reverse operation.
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index ff2445ba..9b544bd0 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -273,32 +273,32 @@ var TypeRegistry = specutil.NewRegistry(
 		},
 		specutil.TypeSpec(TypeBool),
 		specutil.TypeSpec(TypeBoolean),
-		specutil.TypeSpec(TypeBit, specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeInt, unsignedTypeAttr(), specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeTinyInt, unsignedTypeAttr(), specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeSmallInt, unsignedTypeAttr(), specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeMediumInt, unsignedTypeAttr(), specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeBigInt, unsignedTypeAttr(), specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeDecimal, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeNumeric, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeFloat, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeDouble, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeReal, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeTimestamp, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeDate, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeTime, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeDateTime, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeYear, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeVarchar, specutil.SizeTypeAttr(true)),
-		specutil.TypeSpec(TypeChar, specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeVarBinary, specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeBinary, specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeBlob, specutil.SizeTypeAttr(false)),
+		specutil.TypeSpec(TypeBit, specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeInt, specutil.WithAttributes(unsignedTypeAttr(), specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeTinyInt, specutil.WithAttributes(unsignedTypeAttr(), specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeSmallInt, specutil.WithAttributes(unsignedTypeAttr(), specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeMediumInt, specutil.WithAttributes(unsignedTypeAttr(), specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeBigInt, specutil.WithAttributes(unsignedTypeAttr(), specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeDecimal, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeNumeric, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeFloat, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeDouble, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeReal, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeTimestamp, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeDate, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeTime, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeDateTime, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeYear, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeVarchar, specutil.WithAttributes(specutil.SizeTypeAttr(true))),
+		specutil.TypeSpec(TypeChar, specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeVarBinary, specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeBinary, specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeBlob, specutil.WithAttributes(specutil.SizeTypeAttr(false))),
 		specutil.TypeSpec(TypeTinyBlob),
 		specutil.TypeSpec(TypeMediumBlob),
 		specutil.TypeSpec(TypeLongBlob),
 		specutil.TypeSpec(TypeJSON),
-		specutil.TypeSpec(TypeText, specutil.SizeTypeAttr(false)),
+		specutil.TypeSpec(TypeText, specutil.WithAttributes(specutil.SizeTypeAttr(false))),
 		specutil.TypeSpec(TypeTinyText),
 		specutil.TypeSpec(TypeMediumText),
 		specutil.TypeSpec(TypeLongText),
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index c695e56a..d7005f38 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -308,12 +308,12 @@ var TypeRegistry = specutil.NewRegistry(
 	specutil.WithFormatter(FormatType),
 	specutil.WithParser(ParseType),
 	specutil.WithSpecs(
-		specutil.TypeSpec(TypeBit, &schemaspec.TypeAttr{Name: "len", Kind: reflect.Int64}),
-		specutil.AliasTypeSpec("bit_varying", TypeBitVar, &schemaspec.TypeAttr{Name: "len", Kind: reflect.Int64}),
-		specutil.TypeSpec(TypeVarChar, specutil.SizeTypeAttr(true)),
-		specutil.AliasTypeSpec("character_varying", TypeCharVar, &schemaspec.TypeAttr{Name: "size", Kind: reflect.Int}),
-		specutil.TypeSpec(TypeChar, specutil.SizeTypeAttr(true)),
-		specutil.TypeSpec(TypeCharacter, specutil.SizeTypeAttr(true)),
+		specutil.TypeSpec(TypeBit, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "len", Kind: reflect.Int64})),
+		specutil.AliasTypeSpec("bit_varying", TypeBitVar, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "len", Kind: reflect.Int64})),
+		specutil.TypeSpec(TypeVarChar, specutil.WithAttributes(specutil.SizeTypeAttr(true))),
+		specutil.AliasTypeSpec("character_varying", TypeCharVar, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "size", Kind: reflect.Int})),
+		specutil.TypeSpec(TypeChar, specutil.WithAttributes(specutil.SizeTypeAttr(true))),
+		specutil.TypeSpec(TypeCharacter, specutil.WithAttributes(specutil.SizeTypeAttr(true))),
 		specutil.TypeSpec(TypeInt2),
 		specutil.TypeSpec(TypeInt4),
 		specutil.TypeSpec(TypeInt8),
@@ -361,7 +361,7 @@ var TypeRegistry = specutil.NewRegistry(
 		specutil.TypeSpec(TypeUUID),
 		specutil.TypeSpec(TypeMoney),
 		specutil.TypeSpec("hstore"),
-		specutil.TypeSpec("sql", &schemaspec.TypeAttr{Name: "def", Required: true, Kind: reflect.String}),
+		specutil.TypeSpec("sql", specutil.WithAttributes(&schemaspec.TypeAttr{Name: "def", Required: true, Kind: reflect.String})),
 	),
 )
 
diff --git a/sql/sqlite/sqlspec.go b/sql/sqlite/sqlspec.go
index 609ae0d9..0a3fa3a2 100644
--- a/sql/sqlite/sqlspec.go
+++ b/sql/sqlite/sqlspec.go
@@ -73,30 +73,30 @@ var TypeRegistry = specutil.NewRegistry(
 	specutil.WithFormatter(FormatType),
 	specutil.WithParser(ParseType),
 	specutil.WithSpecs(
-		specutil.TypeSpec(TypeReal, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeBlob, specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeText, specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeInteger, specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("int", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("tinyint", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("smallint", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("mediumint", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("bigint", specutil.SizeTypeAttr(false)),
-		specutil.AliasTypeSpec("unsigned_big_int", "unsigned big int", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("int2", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("int8", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("double", specutil.SizeTypeAttr(false)),
-		specutil.AliasTypeSpec("double_precision", "double precision", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("float", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("character", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("varchar", specutil.SizeTypeAttr(false)),
-		specutil.AliasTypeSpec("varying_character", "varying character", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("nchar", specutil.SizeTypeAttr(false)),
-		specutil.AliasTypeSpec("native_character", "native character", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("nvarchar", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("clob", specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec("numeric", &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec("decimal", &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
+		specutil.TypeSpec(TypeReal, specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec(TypeBlob, specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeText, specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec(TypeInteger, specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("int", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("tinyint", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("smallint", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("mediumint", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("bigint", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.AliasTypeSpec("unsigned_big_int", "unsigned big int", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("int2", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("int8", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("double", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.AliasTypeSpec("double_precision", "double precision", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("float", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("character", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("varchar", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.AliasTypeSpec("varying_character", "varying character", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("nchar", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.AliasTypeSpec("native_character", "native character", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("nvarchar", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("clob", specutil.WithAttributes(specutil.SizeTypeAttr(false))),
+		specutil.TypeSpec("numeric", specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
+		specutil.TypeSpec("decimal", specutil.WithAttributes(&schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false})),
 		specutil.TypeSpec("boolean"),
 		specutil.TypeSpec("date"),
 		specutil.TypeSpec("datetime"),


Test patch:
diff --git a/sql/internal/specutil/types_test.go b/sql/internal/specutil/types_test.go
index 1f808529..6b54e247 100644
--- a/sql/internal/specutil/types_test.go
+++ b/sql/internal/specutil/types_test.go
@@ -1,6 +1,7 @@
 package specutil
 
 import (
+	"fmt"
 	"reflect"
 	"testing"
 
@@ -44,6 +45,21 @@ func TestTypePrint(t *testing.T) {
 			typ:      &schemaspec.Type{T: "varchar", Attrs: []*schemaspec.Attr{LitAttr("size", "255")}},
 			expected: "varchar(255)",
 		},
+		{
+			spec: &schemaspec.TypeSpec{
+				T:    "custom",
+				Name: "custom",
+				Attributes: []*schemaspec.TypeAttr{
+					{Name: "customAttr", Kind: reflect.Int, Required: true},
+				},
+				Printer: func(typ *schemaspec.Type) (string, error) {
+					v, _ := typ.Attrs[0].Int()
+					return fmt.Sprintf("%s(%d) and stuff", typ.T, v), nil
+				},
+			},
+			typ:      &schemaspec.Type{T: "custom", Attrs: []*schemaspec.Attr{LitAttr("customAttr", "3")}},
+			expected: "custom(3) and stuff",
+		},
 	} {
 		t.Run(tt.expected, func(t *testing.T) {
 			r := &TypeRegistry{}
@@ -128,26 +144,28 @@ func TestValidSpec(t *testing.T) {
 func TestRegistryConvert(t *testing.T) {
 	r := &TypeRegistry{}
 	err := r.Register(
-		TypeSpec("varchar", SizeTypeAttr(true)),
-		TypeSpec("int", unsignedTypeAttr()),
+		TypeSpec("varchar", WithAttributes(SizeTypeAttr(true))),
+		TypeSpec("int", WithAttributes(unsignedTypeAttr())),
 		TypeSpec(
 			"decimal",
-			&schemaspec.TypeAttr{
-				Name:     "precision",
-				Kind:     reflect.Int,
-				Required: false,
-			},
-			&schemaspec.TypeAttr{
-				Name:     "scale",
-				Kind:     reflect.Int,
-				Required: false,
-			},
+			WithAttributes(
+				&schemaspec.TypeAttr{
+					Name:     "precision",
+					Kind:     reflect.Int,
+					Required: false,
+				},
+				&schemaspec.TypeAttr{
+					Name:     "scale",
+					Kind:     reflect.Int,
+					Required: false,
+				},
+			),
 		),
-		TypeSpec("enum", &schemaspec.TypeAttr{
+		TypeSpec("enum", WithAttributes(&schemaspec.TypeAttr{
 			Name:     "values",
 			Kind:     reflect.Slice,
 			Required: true,
-		}),
+		})),
 	)
 	require.NoError(t, err)
 	for _, tt := range []struct {
diff --git a/sql/mysql/migrate_test.go b/sql/mysql/migrate_test.go
index 94dc2e72..a33778dd 100644
--- a/sql/mysql/migrate_test.go
+++ b/sql/mysql/migrate_test.go
@@ -213,6 +213,56 @@ func TestPlanChanges(t *testing.T) {
 				},
 			},
 		},
+		{
+			input: []schema.Change{
+				func() schema.Change {
+					users := &schema.Table{
+						Name: "users",
+						Columns: []*schema.Column{
+							{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "bigint"}}},
+						},
+					}
+					pets := &schema.Table{
+						Name: "pets",
+						Columns: []*schema.Column{
+							{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "bigint"}}},
+							{Name: "user_id",
+								Type: &schema.ColumnType{
+									Type: &schema.IntegerType{T: "bigint"},
+								},
+							},
+						},
+					}
+					fk := &schema.ForeignKey{
+						Symbol:     "user_id",
+						Table:      pets,
+						OnUpdate:   schema.NoAction,
+						OnDelete:   schema.Cascade,
+						RefTable:   users,
+						Columns:    []*schema.Column{pets.Columns[1]},
+						RefColumns: []*schema.Column{users.Columns[0]},
+					}
+					pets.ForeignKeys = []*schema.ForeignKey{fk}
+					return &schema.ModifyTable{
+						T: pets,
+						Changes: []schema.Change{
+							&schema.DropForeignKey{
+								F: fk,
+							},
+						},
+					}
+				}(),
+			},
+			wantPlan: &migrate.Plan{
+				Reversible: true,
+				Changes: []*migrate.Change{
+					{
+						Cmd:     "ALTER TABLE `pets` DROP FOREIGN KEY `user_id`",
+						Reverse: "ALTER TABLE `pets` ADD CONSTRAINT `user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE NO ACTION ON DELETE CASCADE",
+					},
+				},
+			},
+		},
 		{
 			input: []schema.Change{
 				&schema.AddSchema{S: &schema.Schema{Name: "test", Attrs: []schema.Attr{&schema.Charset{V: "latin"}}}},