Repository: ariga/atlas
Pull request #417 (2022-01-15 22:18:17)

sql/mysql: reject CHECK constraints when it is not supported by the d…
…atabase

masseelch: What about working around this by adding a trigger instead if a check constraint prior to version 8?
a8m: > What about working around this by adding a trigger instead if a check constraint prior to version 8?

Not at this stage. I think we should add support in HCL for triggers, events, etc.

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index fea0e902..ca0cb701 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,3 +1,6 @@
+run:
+  timeout: 3m
+
 issues:
   include:
-    - EXC0012
\ No newline at end of file
+    - EXC0012
diff --git a/sql/internal/sqlx/diff.go b/sql/internal/sqlx/diff.go
index 9671401f..07650540 100644
--- a/sql/internal/sqlx/diff.go
+++ b/sql/internal/sqlx/diff.go
@@ -33,7 +33,7 @@ type (
 
 		// TableAttrDiff returns a changeset for migrating table attributes from
 		// one state to the other. For example, dropping or adding a `CHECK` constraint.
-		TableAttrDiff(from, to *schema.Table) []schema.Change
+		TableAttrDiff(from, to *schema.Table) ([]schema.Change, error)
 
 		// ColumnChange returns the schema changes (if any) for migrating one column to the other.
 		ColumnChange(from, to *schema.Column) (schema.ChangeKind, error)
@@ -154,7 +154,11 @@ func (d *Diff) TableDiff(from, to *schema.Table) ([]schema.Change, error) {
 	}
 
 	// Drop or modify attributes (collations, checks, etc).
-	changes = append(changes, d.TableAttrDiff(from, to)...)
+	change, err := d.TableAttrDiff(from, to)
+	if err != nil {
+		return nil, err
+	}
+	changes = append(changes, change...)
 
 	// Drop or modify columns.
 	for _, c1 := range from.Columns {
diff --git a/sql/mysql/diff.go b/sql/mysql/diff.go
index 600e0738..6a732e94 100644
--- a/sql/mysql/diff.go
+++ b/sql/mysql/diff.go
@@ -33,7 +33,7 @@ func (d *diff) SchemaAttrDiff(from, to *schema.Schema) []schema.Change {
 }
 
 // TableAttrDiff returns a changeset for migrating table attributes from one state to the other.
-func (d *diff) TableAttrDiff(from, to *schema.Table) []schema.Change {
+func (d *diff) TableAttrDiff(from, to *schema.Table) ([]schema.Change, error) {
 	var changes []schema.Change
 	if change := d.autoIncChange(from.Attrs, to.Attrs); change != noChange {
 		changes = append(changes, change)
@@ -47,9 +47,12 @@ func (d *diff) TableAttrDiff(from, to *schema.Table) []schema.Change {
 	if change := d.collationChange(from.Attrs, from.Schema.Attrs, to.Attrs); change != noChange {
 		changes = append(changes, change)
 	}
+	if _, ok := d.supportsCheck(); !ok && sqlx.Has(to.Attrs, &schema.Check{}) {
+		return nil, fmt.Errorf("version %q does not support CHECK constraints", d.version)
+	}
 	return append(changes, sqlx.CheckDiff(from, to, func(c1, c2 *schema.Check) bool {
 		return c1.Expr != c2.Expr || sqlx.Has(c1.Attrs, &Enforced{}) != sqlx.Has(c2.Attrs, &Enforced{})
-	})...)
+	})...), nil
 }
 
 // ColumnChange returns the schema changes (if any) for migrating one column to the other.
diff --git a/sql/postgres/diff.go b/sql/postgres/diff.go
index a2d251ec..0f5eac38 100644
--- a/sql/postgres/diff.go
+++ b/sql/postgres/diff.go
@@ -24,14 +24,14 @@ func (d *diff) SchemaAttrDiff(_, _ *schema.Schema) []schema.Change {
 }
 
 // TableAttrDiff returns a changeset for migrating table attributes from one state to the other.
-func (d *diff) TableAttrDiff(from, to *schema.Table) []schema.Change {
+func (d *diff) TableAttrDiff(from, to *schema.Table) ([]schema.Change, error) {
 	var changes []schema.Change
 	if change := sqlx.CommentDiff(from.Attrs, to.Attrs); change != nil {
 		changes = append(changes, change)
 	}
 	return append(changes, sqlx.CheckDiff(from, to, func(c1, c2 *schema.Check) bool {
 		return c1.Expr != c2.Expr || sqlx.Has(c1.Attrs, &NoInherit{}) != sqlx.Has(c2.Attrs, &NoInherit{})
-	})...)
+	})...), nil
 }
 
 // ColumnChange returns the schema changes (if any) for migrating one column to the other.
diff --git a/sql/sqlite/diff.go b/sql/sqlite/diff.go
index e0a68019..9583e9d5 100644
--- a/sql/sqlite/diff.go
+++ b/sql/sqlite/diff.go
@@ -22,7 +22,7 @@ func (d *diff) SchemaAttrDiff(_, _ *schema.Schema) []schema.Change {
 }
 
 // TableAttrDiff returns a changeset for migrating table attributes from one state to the other.
-func (d *diff) TableAttrDiff(from, to *schema.Table) []schema.Change {
+func (d *diff) TableAttrDiff(from, to *schema.Table) ([]schema.Change, error) {
 	var changes []schema.Change
 	switch {
 	case sqlx.Has(from.Attrs, &WithoutRowID{}) && !sqlx.Has(to.Attrs, &WithoutRowID{}):
@@ -36,7 +36,7 @@ func (d *diff) TableAttrDiff(from, to *schema.Table) []schema.Change {
 	}
 	return append(changes, sqlx.CheckDiff(from, to, func(c1, c2 *schema.Check) bool {
 		return c1.Expr != c2.Expr
-	})...)
+	})...), nil
 }
 
 // ColumnChange returns the schema changes (if any) for migrating one column to the other.


Test patch:
diff --git a/sql/mysql/diff_test.go b/sql/mysql/diff_test.go
index 8006244a..d5985be2 100644
--- a/sql/mysql/diff_test.go
+++ b/sql/mysql/diff_test.go
@@ -339,6 +339,21 @@ func TestDiff_TableDiff(t *testing.T) {
 	}
 }
 
+func TestDiff_UnsupportedChecks(t *testing.T) {
+	db, m, err := sqlmock.New()
+	require.NoError(t, err)
+	mock{m}.version("5.6.35")
+	drv, err := Open(db)
+	require.NoError(t, err)
+	s := schema.New("public")
+	changes, err := drv.TableDiff(
+		schema.NewTable("t").SetSchema(s),
+		schema.NewTable("t").SetSchema(s).AddChecks(schema.NewCheck()),
+	)
+	require.Nil(t, changes)
+	require.EqualError(t, err, `version "5.6.35" does not support CHECK constraints`)
+}
+
 func TestDiff_SchemaDiff(t *testing.T) {
 	db, m, err := sqlmock.New()
 	require.NoError(t, err)