Repository: ariga/atlas
Pull request #710 (2022-04-15 13:54:48)

sql/mysql: support diffing generated columns


Patch:
diff --git a/sql/mysql/diff.go b/sql/mysql/diff.go
index 3af96f72..37bba5fa 100644
--- a/sql/mysql/diff.go
+++ b/sql/mysql/diff.go
@@ -98,6 +98,9 @@ func (d *diff) ColumnChange(from, to *schema.Column) (schema.ChangeKind, error)
 	if changed {
 		change |= schema.ChangeDefault
 	}
+	if changed := d.generatedChanged(from, to); changed {
+		change |= schema.ChangeGenerated
+	}
 	return change, nil
 }
 
@@ -173,7 +176,7 @@ func (*diff) Normalize(from, to *schema.Table) {
 }
 
 // collationChange returns the schema change for migrating the collation if
-// it was changed and its not the default attribute inherited from its parent.
+// it was changed, and it is not the default attribute inherited from its parent.
 func (*diff) collationChange(from, top, to []schema.Attr) schema.Change {
 	var fromC, topC, toC schema.Collation
 	switch fromHas, topHas, toHas := sqlx.Has(from, &fromC), sqlx.Has(top, &topC), sqlx.Has(to, &toC); {
@@ -328,8 +331,7 @@ func (d *diff) typeChanged(from, to *schema.Column) (bool, error) {
 	return changed, nil
 }
 
-// defaultChanged reports if the a default value of a column
-// type was changed.
+// defaultChanged reports if the default value of a column was changed.
 func (d *diff) defaultChanged(from, to *schema.Column) (bool, error) {
 	d1, ok1 := sqlx.DefaultValue(from)
 	d2, ok2 := sqlx.DefaultValue(to)
@@ -371,6 +373,30 @@ func (d *diff) defaultChanged(from, to *schema.Column) (bool, error) {
 	}
 }
 
+// generatedChanged reports if the generated expression of a column was changed.
+func (*diff) generatedChanged(from, to *schema.Column) bool {
+	var fromX, toX schema.GeneratedExpr
+	switch fromHas, toHas := sqlx.Has(from.Attrs, &fromX), sqlx.Has(to.Attrs, &toX); {
+	case fromHas != toHas:
+		return true
+	case fromHas && toHas:
+		t := func(s string) string {
+			switch s = strings.ToUpper(s); s {
+			// The default is VIRTUAL if no type is specified.
+			case "":
+				return virtual
+			// In MariaDB, PERSISTENT is synonyms for STORED.
+			case persistent:
+				return stored
+			}
+			return s
+		}
+		return t(fromX.Type) != t(toX.Type) || fromX.Expr != toX.Expr
+	default: // !fromHas && !toHas.
+		return false
+	}
+}
+
 // equalIntValues report if the 2 int default values are ~equal.
 // Note that default expression are not supported atm.
 func (d *diff) equalIntValues(x1, x2 string) bool {
diff --git a/sql/schema/migrate.go b/sql/schema/migrate.go
index 7ff84672..56c9ab8c 100644
--- a/sql/schema/migrate.go
+++ b/sql/schema/migrate.go
@@ -197,6 +197,8 @@ const (
 	ChangeType
 	// ChangeDefault describe a column default change.
 	ChangeDefault
+	// ChangeGenerated describe a change to the generated expression.
+	ChangeGenerated
 
 	// Index specific changes.
 


Test patch:
diff --git a/sql/mysql/diff_test.go b/sql/mysql/diff_test.go
index a8acacdd..2e23e822 100644
--- a/sql/mysql/diff_test.go
+++ b/sql/mysql/diff_test.go
@@ -229,6 +229,51 @@ func TestDiff_TableDiff(t *testing.T) {
 				},
 			}
 		}(),
+		func() testcase {
+			var (
+				s    = schema.New("public")
+				from = schema.NewTable("t1").
+					SetSchema(s).
+					AddColumns(
+						schema.NewIntColumn("c1", "int"),
+						schema.NewIntColumn("c2", "int").
+							SetGeneratedExpr(&schema.GeneratedExpr{Expr: "1"}),
+						schema.NewIntColumn("c3", "int").
+							SetGeneratedExpr(&schema.GeneratedExpr{Expr: "1"}),
+						schema.NewIntColumn("c4", "int").
+							SetGeneratedExpr(&schema.GeneratedExpr{Expr: "1", Type: "VIRTUAL"}),
+						schema.NewIntColumn("c5", "int").
+							SetGeneratedExpr(&schema.GeneratedExpr{Expr: "1", Type: "PERSISTENT"}),
+					)
+				to = schema.NewTable("t1").
+					SetSchema(s).
+					AddColumns(
+						// Add generated expression.
+						schema.NewIntColumn("c1", "int").
+							SetGeneratedExpr(&schema.GeneratedExpr{Expr: "1"}),
+						// Drop generated expression.
+						schema.NewIntColumn("c2", "int"),
+						// Modify generated expression.
+						schema.NewIntColumn("c3", "int").
+							SetGeneratedExpr(&schema.GeneratedExpr{Expr: "2"}),
+						// No change.
+						schema.NewIntColumn("c4", "int").
+							SetGeneratedExpr(&schema.GeneratedExpr{Expr: "1"}),
+						schema.NewIntColumn("c5", "int").
+							SetGeneratedExpr(&schema.GeneratedExpr{Expr: "1", Type: "STORED"}),
+					)
+			)
+			return testcase{
+				name: "modify column generated",
+				from: from,
+				to:   to,
+				wantChanges: []schema.Change{
+					&schema.ModifyColumn{From: from.Columns[0], To: to.Columns[0], Change: schema.ChangeGenerated},
+					&schema.ModifyColumn{From: from.Columns[1], To: to.Columns[1], Change: schema.ChangeGenerated},
+					&schema.ModifyColumn{From: from.Columns[2], To: to.Columns[2], Change: schema.ChangeGenerated},
+				},
+			}
+		}(),
 		func() testcase {
 			var (
 				from = &schema.Table{