Skip to content

Commit aab6af2

Browse files
author
Sergey Podgornyy
committed
Add precision to timeable columns
1 parent 0267f0c commit aab6af2

File tree

7 files changed

+72
-24
lines changed

7 files changed

+72
-24
lines changed

README.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,12 @@ log.Print("Migration did run successfully")
107107
After the first migration run, `migrations` table will be created:
108108

109109
```
110-
+----+-------------------------------------+-------+---------------------+
111-
| id | name | batch | applied_at |
112-
+----+-------------------------------------+-------+---------------------+
113-
| 1 | 19700101_0001_create_posts_table | 1 | 2020-06-27 00:00:00 |
114-
| 2 | 19700101_0002_create_comments_table | 1 | 2020-06-27 00:00:00 |
115-
+----+-------------------------------------+-------+---------------------+
110+
+----+-------------------------------------+-------+----------------------------+
111+
| id | name | batch | applied_at |
112+
+----+-------------------------------------+-------+----------------------------+
113+
| 1 | 19700101_0001_create_posts_table | 1 | 2020-06-27 00:00:00.000000 |
114+
| 2 | 19700101_0002_create_comments_table | 1 | 2020-06-27 00:00:00.000000 |
115+
+----+-------------------------------------+-------+----------------------------+
116116
```
117117

118118
If you want to use another name for migration table, change it `Migrator` before running migrations:

column.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,16 @@ func (f Floatable) buildRow() string {
156156
// Timable represents DB representation of timable column type:
157157
// `date`, `datetime`, `timestamp`, `time` or `year`
158158
//
159-
// Default migrator.Timable will build a sql row: `timestamp NOT NULL`
159+
// Default migrator.Timable will build a sql row: `timestamp NOT NULL`.
160+
// Precision from 0 to 6 can be set for `datetime`, `timestamp`, `time`.
160161
//
161162
// Examples:
162163
// date ➡️ migrator.Timable{Type: "date", Nullable: true}
163164
// ↪️ date NULL
164-
// datetime ➡️ migrator.Timable{Type: "datetime", Default: "CURRENT_TIMESTAMP"}
165-
// ↪️ datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
166-
// timestamp ➡️ migrator.Timable{Default: "CURRENT_TIMESTAMP", OnUpdate: "CURRENT_TIMESTAMP"}
167-
// ↪️ timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
165+
// datetime ➡️ migrator.Timable{Type: "datetime", Precision: 3, Default: "CURRENT_TIMESTAMP"}
166+
// ↪️ datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
167+
// timestamp ➡️ migrator.Timable{Default: "CURRENT_TIMESTAMP(6)", OnUpdate: "CURRENT_TIMESTAMP(6)"}
168+
// ↪️ timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)
168169
// time ➡️ migrator.Timable{Type: "time", Comment: "meeting time"}
169170
// ↪️ time NOT NULL COMMENT 'meeting time'
170171
// year ➡️ migrator.Timable{Type: "year", Nullable: true}
@@ -175,7 +176,8 @@ type Timable struct {
175176
Comment string
176177
OnUpdate string
177178

178-
Type string // date, time, datetime, timestamp, year
179+
Type string // date, time, datetime, timestamp, year
180+
Precision uint16
179181
}
180182

181183
func (t Timable) buildRow() string {
@@ -185,6 +187,12 @@ func (t Timable) buildRow() string {
185187
sql = "timestamp"
186188
}
187189

190+
validForPrecision := list{"time", "datetime", "timestamp"}
191+
columnType := strings.ToLower(sql)
192+
if t.Precision > 0 && t.Precision <= 6 && validForPrecision.has(columnType) {
193+
sql += fmt.Sprintf("(%s)", strconv.Itoa(int(t.Precision)))
194+
}
195+
188196
if t.Nullable {
189197
sql += " NULL"
190198
} else {

column_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,26 @@ func TestTimeable(t *testing.T) {
172172
assert.Equal(t, "datetime NOT NULL", c.buildRow())
173173
})
174174

175+
t.Run("it does not set precision for invalid column type", func(t *testing.T) {
176+
c := Timable{Type: "date", Precision: 3}
177+
assert.Equal(t, "date NOT NULL", c.buildRow())
178+
})
179+
180+
t.Run("it does not set zero precision", func(t *testing.T) {
181+
c := Timable{Type: "timestamp", Precision: 0}
182+
assert.Equal(t, "timestamp NOT NULL", c.buildRow())
183+
})
184+
185+
t.Run("it does not set invalid precision", func(t *testing.T) {
186+
c := Timable{Type: "timestamp", Precision: 7}
187+
assert.Equal(t, "timestamp NOT NULL", c.buildRow())
188+
})
189+
190+
t.Run("it builds with precision", func(t *testing.T) {
191+
c := Timable{Type: "TIMESTAMP", Precision: 6}
192+
assert.Equal(t, "TIMESTAMP(6) NOT NULL", c.buildRow())
193+
})
194+
175195
t.Run("it builds nullable column type", func(t *testing.T) {
176196
c := Timable{Nullable: true}
177197
assert.Equal(t, "timestamp NULL", c.buildRow())

migrator.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func (m Migrator) createMigrationTable(db *sql.DB) error {
232232
"id int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY",
233233
"name varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL",
234234
"batch int(11) NOT NULL",
235-
"applied_at timestamp NULL DEFAULT CURRENT_TIMESTAMP",
235+
"applied_at timestamp(6) NULL DEFAULT CURRENT_TIMESTAMP(6)",
236236
}, ", "),
237237
)
238238

migrator_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ func TestCreateMigrationTable(t *testing.T) {
625625
defer resetDB()
626626

627627
mock.ExpectQuery(`SELECT \* FROM migrations`).WillReturnError(errTestDBQueryFailed)
628-
sql := `CREATE TABLE migrations \(id int\(10\) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, name varchar\(255\) COLLATE utf8mb4_unicode_ci NOT NULL, batch int\(11\) NOT NULL, applied_at timestamp NULL DEFAULT CURRENT_TIMESTAMP\) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
628+
sql := `CREATE TABLE migrations \(id int\(10\) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, name varchar\(255\) COLLATE utf8mb4_unicode_ci NOT NULL, batch int\(11\) NOT NULL, applied_at timestamp\(6\) NULL DEFAULT CURRENT_TIMESTAMP\(6\)\) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
629629
mock.ExpectExec(sql).WillReturnResult(sqlmock.NewResult(1, 1))
630630

631631
err := m.createMigrationTable(db)
@@ -642,7 +642,7 @@ func TestCreateMigrationTable(t *testing.T) {
642642
sql := `CREATE TABLE migrations \(` +
643643
`id int\(10\) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, ` +
644644
`name varchar\(255\) COLLATE utf8mb4_unicode_ci NOT NULL, ` +
645-
`batch int\(11\) NOT NULL, applied_at timestamp NULL DEFAULT CURRENT_TIMESTAMP\) ` +
645+
`batch int\(11\) NOT NULL, applied_at timestamp\(6\) NULL DEFAULT CURRENT_TIMESTAMP\(6\)\) ` +
646646
`ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
647647
mock.ExpectExec(sql).WillReturnError(errTestDBExecFailed)
648648

table.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,18 @@ func (t *Table) UUID(name string, def string, nullable bool) {
7171

7272
// Timestamps adds default timestamps: `created_at` and `updated_at`
7373
func (t *Table) Timestamps() {
74-
// created_at not null default CURRENT_TIMESTAMP
74+
// created_at timestamp(6) not null default CURRENT_TIMESTAMP(6)
7575
t.Column("created_at", Timable{
76-
Type: "timestamp",
77-
Default: "CURRENT_TIMESTAMP",
76+
Type: "timestamp",
77+
Precision: 6,
78+
Default: "CURRENT_TIMESTAMP(6)",
7879
})
79-
// updated_at not null default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
80+
// updated_at timestamp(6) not null default CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)
8081
t.Column("updated_at", Timable{
81-
Type: "timestamp",
82-
Default: "CURRENT_TIMESTAMP",
83-
OnUpdate: "CURRENT_TIMESTAMP",
82+
Type: "timestamp",
83+
Precision: 6,
84+
Default: "CURRENT_TIMESTAMP(6)",
85+
OnUpdate: "CURRENT_TIMESTAMP(6)",
8486
})
8587
}
8688

@@ -139,6 +141,11 @@ func (t *Table) Timestamp(name string, nullable bool, def string) {
139141
t.Column(name, Timable{Nullable: nullable, Default: def})
140142
}
141143

144+
// PreciseTimestamp adds timestamp column with precision to the table
145+
func (t *Table) PreciseTimestamp(name string, precision uint16, nullable bool, def string) {
146+
t.Column(name, Timable{Precision: precision, Nullable: nullable, Default: def})
147+
}
148+
142149
// Date adds date column to the table
143150
func (t *Table) Date(name string, nullable bool, def string) {
144151
t.Column(name, Timable{Type: "date", Nullable: nullable, Default: def})

table_test.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ func TestTimestampsColumn(t *testing.T) {
104104

105105
assert.Len(table.columns, 2)
106106
assert.Equal("created_at", table.columns[0].field)
107-
assert.Equal(Timable{Type: "timestamp", Default: "CURRENT_TIMESTAMP"}, table.columns[0].definition)
107+
assert.Equal(Timable{Type: "timestamp", Precision: 6, Default: "CURRENT_TIMESTAMP(6)"}, table.columns[0].definition)
108108
assert.Equal("updated_at", table.columns[1].field)
109-
assert.Equal(Timable{Type: "timestamp", Default: "CURRENT_TIMESTAMP", OnUpdate: "CURRENT_TIMESTAMP"}, table.columns[1].definition)
109+
assert.Equal(Timable{Type: "timestamp", Precision: 6, Default: "CURRENT_TIMESTAMP(6)", OnUpdate: "CURRENT_TIMESTAMP(6)"}, table.columns[1].definition)
110110
}
111111

112112
func TestIntColumn(t *testing.T) {
@@ -252,6 +252,19 @@ func TestTimestampColumn(t *testing.T) {
252252
assert.Equal(Timable{Nullable: true, Default: "CURRENT_TIMESTAMP"}, table.columns[0].definition)
253253
}
254254

255+
func TestPreciseTimestampColumn(t *testing.T) {
256+
assert := assert.New(t)
257+
table := Table{}
258+
259+
assert.Nil(table.columns)
260+
261+
table.PreciseTimestamp("date", 3, true, "CURRENT_TIMESTAMP")
262+
263+
assert.Len(table.columns, 1)
264+
assert.Equal("date", table.columns[0].field)
265+
assert.Equal(Timable{Precision: 3, Nullable: true, Default: "CURRENT_TIMESTAMP"}, table.columns[0].definition)
266+
}
267+
255268
func TestDateColumn(t *testing.T) {
256269
assert := assert.New(t)
257270
table := Table{}

0 commit comments

Comments
 (0)