From 5597f7eb10c42304faf5ce5c8aba791ffc2cdc56 Mon Sep 17 00:00:00 2001 From: Jiulong Wang Date: Tue, 10 Jan 2017 07:10:51 -0800 Subject: [PATCH] Enable fractional seconds in Time column for Mysql Mysql (5.6.4 and up) supports up to 6 digits fractional seconds in Time values. This patch adds "frac" field annotation to support it. http://dev.mysql.com/doc/refman/5.7/en/fractional-seconds.html Sample usage: Foo { ts time.Time `sql:"frac:3"` } --- dialect_mysql.go | 15 ++++++++--- migration_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/dialect_mysql.go b/dialect_mysql.go index 11b894b3..89b65bc7 100644 --- a/dialect_mysql.go +++ b/dialect_mysql.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" "regexp" + "strconv" "strings" "time" "unicode/utf8" @@ -80,10 +81,18 @@ func (mysql) DataTypeOf(field *StructField) string { } case reflect.Struct: if _, ok := dataValue.Interface().(time.Time); ok { - if _, ok := field.TagSettings["NOT NULL"]; ok { - sqlType = "timestamp" + if num, ok := field.TagSettings["FRAC"]; ok { + frac, err := strconv.Atoi(num) + if err != nil || frac < 0 { + frac = 0 + } + if frac > 6 { + frac = 6 + } + + sqlType = fmt.Sprintf("timestamp(%d)", frac) } else { - sqlType = "timestamp NULL" + sqlType = "timestamp" } } default: diff --git a/migration_test.go b/migration_test.go index 8b3c4ab6..10d52a64 100644 --- a/migration_test.go +++ b/migration_test.go @@ -436,3 +436,66 @@ func TestMultipleIndexes(t *testing.T) { t.Error("MultipleIndexes unique index failed") } } + +type FractionalSeconds struct { + Id int + NoFractional time.Time + MillisecFractional time.Time `sql:"frac:3"` + MicrosecFractional *time.Time `sql:"frac:6;not null"` +} + +func TestFractionalSeconds(t *testing.T) { + if err := DB.DropTableIfExists(&FractionalSeconds{}).Error; err != nil { + fmt.Printf("Failed to delete table fractional_seconds: %+v\n", err) + return + } + + if err := DB.AutoMigrate(&FractionalSeconds{}).Error; err != nil { + t.Error("Auto Migrating FractionalSeconds should not raise any error.") + return + } + + testTime := time.Date(2017, 1, 9, 10, 12, 23, 123456789, time.UTC) + + record := FractionalSeconds{ + Id: 123, + NoFractional: testTime, + MillisecFractional: testTime, + MicrosecFractional: &testTime, + } + + if err := DB.Save(&record).Error; err != nil { + t.Error("Saving FractionalSeconds should not raise any error.") + return + } + + retrieve := FractionalSeconds{} + if err := DB.First(&retrieve).Error; err != nil { + t.Error("Retrieving FractionalSeconds should not raise any error.") + return + } + + if retrieve.NoFractional.Nanosecond() != 0 { + t.Error("NoFractional column value is incorrect.") + } + + if retrieve.MillisecFractional.Nanosecond() != 123000000 { + t.Error("MillisecFractional column value is incorrect.") + } + + if retrieve.MicrosecFractional.Nanosecond() != 123456000 { + t.Error("MicrosecFractional column value is incorrect.") + } + + record = FractionalSeconds{ + Id: 456, + NoFractional: testTime, + MillisecFractional: testTime, + MicrosecFractional: nil, + } + + if err := DB.Save(&record).Error; err == nil { + t.Error("Saving FractionalSeconds should raise error when not null column is null.") + return + } +}