Repository: ariga/atlas
Pull request #487 (2022-01-25 10:41:09)

sql/mysql: support unmarshal on update expressions in HCL
This is the "reverse" part to #486, unmarshaling HCL like shown into Go Types.


Patch:
diff --git a/sql/mysql/inspect.go b/sql/mysql/inspect.go
index 92a1d7bc..ab7381d3 100644
--- a/sql/mysql/inspect.go
+++ b/sql/mysql/inspect.go
@@ -447,18 +447,18 @@ func (i *inspect) tableNames(ctx context.Context, schema string, opts *schema.In
 	return names, nil
 }
 
-var reTimeOnUpdate = regexp.MustCompile(`^(?:default_generated )?on update current_timestamp(?:\(\d?\))?$`)
+var reTimeOnUpdate = regexp.MustCompile(`(?i)^(?:default_generated )?on update (current_timestamp(?:\(\d?\))?)$`)
 
 // extraAttr parses the EXTRA column from the INFORMATION_SCHEMA.COLUMNS table
 // and appends its parsed representation to the column.
 func (i *inspect) extraAttr(t *schema.Table, c *schema.Column, extra string) error {
-	extra = strings.ToLower(extra)
+	el := strings.ToLower(extra)
 	switch {
-	case extra == "", extra == "null": // ignore.
-	case extra == defaultGen:
+	case el == "", el == "null": // ignore.
+	case el == defaultGen:
 		// The column has an expression default value
 		// and it is handled in Driver.addColumn.
-	case extra == autoIncrement:
+	case el == autoIncrement:
 		a := &AutoIncrement{}
 		// A table can have only one AUTO_INCREMENT column. If it was returned as NULL
 		// from INFORMATION_SCHEMA, it is due to information_schema_stats_expiry and we
@@ -473,7 +473,7 @@ func (i *inspect) extraAttr(t *schema.Table, c *schema.Column, extra string) err
 		}
 		c.Attrs = append(c.Attrs, a)
 	case reTimeOnUpdate.MatchString(extra):
-		c.Attrs = append(c.Attrs, &OnUpdate{A: extra})
+		c.Attrs = append(c.Attrs, &OnUpdate{A: reTimeOnUpdate.FindStringSubmatch(extra)[1]})
 	default:
 		return fmt.Errorf("unknown attribute %q", extra)
 	}
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index f89eaf3b..ff2445ba 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -114,11 +114,11 @@ func convertColumn(spec *sqlspec.Column, _ *schema.Table) (*schema.Column, error
 		return nil, err
 	}
 	if attr, ok := spec.Attr("on_update"); ok {
-		s, err := attr.String()
-		if err != nil {
-			return nil, err
+		exp, ok := attr.V.(*schemaspec.RawExpr)
+		if !ok {
+			return nil, fmt.Errorf(`unexpected type %T for atrribute "on_update"`, attr.V)
 		}
-		c.AddAttrs(&OnUpdate{A: s})
+		c.AddAttrs(&OnUpdate{A: exp.X})
 	}
 	return c, err
 }


Test patch:
diff --git a/internal/integration/mysql_test.go b/internal/integration/mysql_test.go
index 2d3cecc8..35539b42 100644
--- a/internal/integration/mysql_test.go
+++ b/internal/integration/mysql_test.go
@@ -605,45 +605,47 @@ func TestMySQL_Sanity(t *testing.T) {
 		ddl := `
 create table atlas_types_sanity
 (
-    tBit                 bit(10)              default b'100'               null,
-    tInt                 int(10)              default 4                not null,
-    tTinyInt             tinyint(10)          default 8                    null,
-    tSmallInt            smallint(10)         default 2                    null,
-    tMediumInt           mediumint(10)        default 11                   null,
-    tBigInt              bigint(10)           default 4                    null,
-    tDecimal             decimal              default 4                    null,
-    tNumeric             numeric              default 4                not null,
-    tFloat               float(10, 0)         default 4                    null,
-    tDouble              double(10, 0)        default 4                    null,
-    tReal                double(10, 0)        default 4                    null,
-    tTimestamp           timestamp            default CURRENT_TIMESTAMP    null,
-    tTimestampFraction   timestamp(6)         default CURRENT_TIMESTAMP(6) null,
-    tDate                date                                              null,
-    tTime                time                                              null,
-    tDateTime            datetime                                          null,
-    tYear                year                                              null,
-    tVarchar             varchar(10)          default 'Titan'              null,
-    tChar                char(25)             default 'Olimpia'        not null,
-    tVarBinary           varbinary(30)        default 'Titan'              null,
-    tBinary              binary(5)            default 'Titan'              null,
-    tBlob                blob(5)              default                      null,
-    tTinyBlob            tinyblob                                          null,
-    tMediumBlob          mediumblob           default                      null,
-    tLongBlob            longblob             default                      null,
-    tText                text(13)             default                      null,
-    tTinyText            tinytext             default                      null,
-    tMediumText          mediumtext           default                      null,
-    tLongText            longtext             default                      null,
-    tEnum                enum('a','b')        default                      null,
-    tSet                 set('a','b')         default                      null,
-    tGeometry            geometry             default                      null,
-    tPoint               point                default                      null,
-    tMultiPoint          multipoint           default                      null,
-    tLineString          linestring           default                      null,
-    tMultiLineString     multilinestring      default                      null,
-    tPolygon             polygon              default                      null,
-    tMultiPolygon        multipolygon         default                      null,
-    tGeometryCollection  geometrycollection   default                      null
+    tBit                        bit(10)              default b'100'                                              null,
+    tInt                        int(10)              default 4                                               not null,
+    tTinyInt                    tinyint(10)          default 8                                                   null,
+    tSmallInt                   smallint(10)         default 2                                                   null,
+    tMediumInt                  mediumint(10)        default 11                                                  null,
+    tBigInt                     bigint(10)           default 4                                                   null,
+    tDecimal                    decimal              default 4                                                   null,
+    tNumeric                    numeric              default 4                                               not null,
+    tFloat                      float(10, 0)         default 4                                                   null,
+    tDouble                     double(10, 0)        default 4                                                   null,
+    tReal                       double(10, 0)        default 4                                                   null,
+    tTimestamp                  timestamp            default CURRENT_TIMESTAMP                                   null,
+    tTimestampFraction          timestamp(6)         default CURRENT_TIMESTAMP(6)                                null,
+    tTimestampOnUpdate          timestamp            default CURRENT_TIMESTAMP    ON UPDATE CURRENT_TIMESTAMP    null,
+    tTimestampFractionOnUpdate  timestamp(6)         default CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) null,
+    tDate                       date                                                                             null,
+    tTime                       time                                                                             null,
+    tDateTime                   datetime                                                                         null,
+    tYear                       year                                                                             null,
+    tVarchar                    varchar(10)          default 'Titan'                                             null,
+    tChar                       char(25)             default 'Olimpia'                                       not null,
+    tVarBinary                  varbinary(30)        default 'Titan'                                             null,
+    tBinary                     binary(5)            default 'Titan'                                             null,
+    tBlob                       blob(5)              default                                                     null,
+    tTinyBlob                   tinyblob                                                                         null,
+    tMediumBlob                 mediumblob           default                                                     null,
+    tLongBlob                   longblob             default                                                     null,
+    tText                       text(13)             default                                                     null,
+    tTinyText                   tinytext             default                                                     null,
+    tMediumText                 mediumtext           default                                                     null,
+    tLongText                   longtext             default                                                     null,
+    tEnum                       enum('a','b')        default                                                     null,
+    tSet                        set('a','b')         default                                                     null,
+    tGeometry                   geometry             default                                                     null,
+    tPoint                      point                default                                                     null,
+    tMultiPoint                 multipoint           default                                                     null,
+    tLineString                 linestring           default                                                     null,
+    tMultiLineString            multilinestring      default                                                     null,
+    tPolygon                    polygon              default                                                     null,
+    tMultiPolygon               multipolygon         default                                                     null,
+    tGeometryCollection         geometrycollection   default                                                     null
 ) CHARSET = latin1 COLLATE latin1_swedish_ci;
 `
 		myRun(t, func(t *myTest) {
@@ -753,6 +755,52 @@ create table atlas_types_sanity
 							}(),
 						},
 					},
+					{
+						Name: "tTimestampOnUpdate",
+						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp"},
+							Raw: "timestamp", Null: true},
+						Default: &schema.RawExpr{
+							X: func() string {
+								if t.mariadb() {
+									return "current_timestamp()"
+								}
+								return "CURRENT_TIMESTAMP"
+							}(),
+						},
+						Attrs: []schema.Attr{
+							&mysql.OnUpdate{
+								A: func() string {
+									if t.mariadb() {
+										return "current_timestamp()"
+									}
+									return "CURRENT_TIMESTAMP"
+								}(),
+							},
+						},
+					},
+					{
+						Name: "tTimestampFractionOnUpdate",
+						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp", Precision: 6},
+							Raw: "timestamp(6)", Null: true},
+						Default: &schema.RawExpr{
+							X: func() string {
+								if t.mariadb() {
+									return "current_timestamp(6)"
+								}
+								return "CURRENT_TIMESTAMP(6)"
+							}(),
+						},
+						Attrs: []schema.Attr{
+							&mysql.OnUpdate{
+								A: func() string {
+									if t.mariadb() {
+										return "current_timestamp(6)"
+									}
+									return "CURRENT_TIMESTAMP(6)"
+								}(),
+							},
+						},
+					},
 					{
 						Name: "tDate",
 						Type: &schema.ColumnType{Type: &schema.TimeType{T: "date"},
diff --git a/sql/mysql/inspect_test.go b/sql/mysql/inspect_test.go
index ed1e6509..0dabbb9a 100644
--- a/sql/mysql/inspect_test.go
+++ b/sql/mysql/inspect_test.go
@@ -383,10 +383,10 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Name: "c1", Type: &schema.ColumnType{Raw: "date", Type: &schema.TimeType{T: "date"}}},
 					{Name: "c2", Type: &schema.ColumnType{Raw: "datetime", Type: &schema.TimeType{T: "datetime"}}},
 					{Name: "c3", Type: &schema.ColumnType{Raw: "time", Type: &schema.TimeType{T: "time"}}},
-					{Name: "c4", Type: &schema.ColumnType{Raw: "timestamp", Type: &schema.TimeType{T: "timestamp"}}, Default: &schema.RawExpr{X: "CURRENT_TIMESTAMP"}, Attrs: []schema.Attr{&OnUpdate{A: "on update current_timestamp"}}},
+					{Name: "c4", Type: &schema.ColumnType{Raw: "timestamp", Type: &schema.TimeType{T: "timestamp"}}, Default: &schema.RawExpr{X: "CURRENT_TIMESTAMP"}, Attrs: []schema.Attr{&OnUpdate{A: "CURRENT_TIMESTAMP"}}},
 					{Name: "c5", Type: &schema.ColumnType{Raw: "year(4)", Type: &schema.TimeType{T: "year", Precision: 4}}},
 					{Name: "c6", Type: &schema.ColumnType{Raw: "year", Type: &schema.TimeType{T: "year"}}},
-					{Name: "c7", Type: &schema.ColumnType{Raw: "timestamp(6)", Type: &schema.TimeType{T: "timestamp", Precision: 6}}, Default: &schema.RawExpr{X: "CURRENT_TIMESTAMP(6)"}, Attrs: []schema.Attr{&OnUpdate{A: "on update current_timestamp(6)"}}},
+					{Name: "c7", Type: &schema.ColumnType{Raw: "timestamp(6)", Type: &schema.TimeType{T: "timestamp", Precision: 6}}, Default: &schema.RawExpr{X: "CURRENT_TIMESTAMP(6)"}, Attrs: []schema.Attr{&OnUpdate{A: "CURRENT_TIMESTAMP(6)"}}},
 				}, t.Columns)
 			},
 		},
diff --git a/sql/mysql/sqlspec_test.go b/sql/mysql/sqlspec_test.go
index e886283a..4085c9c9 100644
--- a/sql/mysql/sqlspec_test.go
+++ b/sql/mysql/sqlspec_test.go
@@ -30,6 +30,15 @@ table "table" {
 	column "account_name" {
 		type = varchar(32)
 	}
+	column "created_at" {
+		type    = datetime(4)
+		default = sql("now(4)")
+	}
+	column "updated_at" {
+		type      = timestamp(6)
+		default   = sql("current_timestamp(6)")
+		on_update = sql("current_timestamp(6)")
+	}
 	primary_key {
 		columns = [table.table.column.col]
 	}
@@ -129,6 +138,27 @@ table "accounts" {
 						},
 					},
 				},
+				{
+					Name: "created_at",
+					Type: &schema.ColumnType{
+						Type: &schema.TimeType{
+							T:         TypeDateTime,
+							Precision: 4,
+						},
+					},
+					Default: &schema.RawExpr{X: "now(4)"},
+				},
+				{
+					Name: "updated_at",
+					Type: &schema.ColumnType{
+						Type: &schema.TimeType{
+							T:         TypeTimestamp,
+							Precision: 6,
+						},
+					},
+					Default: &schema.RawExpr{X: "current_timestamp(6)"},
+					Attrs:   []schema.Attr{&OnUpdate{A: "current_timestamp(6)"}},
+				},
 			},
 			Attrs: []schema.Attr{
 				&schema.Check{