2021-11-21 14:05:07 +01:00
|
|
|
package migrate
|
|
|
|
|
2021-11-24 12:42:18 +01:00
|
|
|
import (
|
2021-11-28 13:52:27 +01:00
|
|
|
"database/sql"
|
2021-11-24 12:42:18 +01:00
|
|
|
"time"
|
|
|
|
|
2021-11-28 13:52:27 +01:00
|
|
|
"github.com/dustin/go-humanize/english"
|
2021-11-24 12:42:18 +01:00
|
|
|
"github.com/jinzhu/gorm"
|
|
|
|
)
|
2021-11-21 14:05:07 +01:00
|
|
|
|
|
|
|
// Migrations represents a sorted list of migrations.
|
|
|
|
type Migrations []Migration
|
|
|
|
|
2021-11-28 13:52:27 +01:00
|
|
|
// MigrationMap represents a map of migrations.
|
|
|
|
type MigrationMap map[string]Migration
|
|
|
|
|
|
|
|
// Existing finds and returns previously executed database schema migrations.
|
|
|
|
func Existing(db *gorm.DB) MigrationMap {
|
|
|
|
result := make(MigrationMap)
|
|
|
|
|
2022-01-05 18:15:39 +01:00
|
|
|
stmt := db.Model(Migration{})
|
2021-11-28 13:52:27 +01:00
|
|
|
stmt = stmt.Select("id, dialect, error, source, started_at, finished_at")
|
|
|
|
|
|
|
|
rows, err := stmt.Rows()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("migrate: %s (find existing)", err)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func(rows *sql.Rows) {
|
|
|
|
err = rows.Close()
|
|
|
|
}(rows)
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
m := Migration{}
|
|
|
|
|
|
|
|
if err = rows.Scan(&m.ID, &m.Dialect, &m.Error, &m.Source, &m.StartedAt, &m.FinishedAt); err != nil {
|
|
|
|
log.Warnf("migrate: %s (scan existing)", err)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
result[m.ID] = m
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-11-21 14:05:07 +01:00
|
|
|
// Start runs all migrations that haven't been executed yet.
|
2021-11-28 13:52:27 +01:00
|
|
|
func (m *Migrations) Start(db *gorm.DB, runFailed bool) {
|
|
|
|
// Find previously executed migrations.
|
|
|
|
executed := Existing(db)
|
|
|
|
|
2022-01-05 18:15:39 +01:00
|
|
|
if prev := len(executed); prev == 0 {
|
|
|
|
log.Infof("migrate: found no previous migrations")
|
|
|
|
} else {
|
|
|
|
log.Debugf("migrate: found %s", english.Plural(len(executed), "previous migration", "previous migrations"))
|
|
|
|
}
|
2021-11-28 13:52:27 +01:00
|
|
|
|
2021-11-21 14:05:07 +01:00
|
|
|
for _, migration := range *m {
|
2021-11-24 12:42:18 +01:00
|
|
|
start := time.Now()
|
|
|
|
migration.StartedAt = start.UTC().Round(time.Second)
|
|
|
|
|
2021-11-28 13:52:27 +01:00
|
|
|
// Already executed?
|
|
|
|
if done, ok := executed[migration.ID]; ok {
|
|
|
|
// Try to run failed migrations again?
|
|
|
|
if !runFailed || done.Error == "" {
|
|
|
|
log.Debugf("migrate: %s skipped", migration.ID)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else if err := db.Create(migration).Error; err != nil {
|
|
|
|
// Should not happen.
|
|
|
|
log.Warnf("migrate: creating %s failed with %s [%s]", migration.ID, err, time.Since(start))
|
2021-11-24 12:42:18 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-11-28 13:52:27 +01:00
|
|
|
// Run migration.
|
2021-11-24 12:42:18 +01:00
|
|
|
if err := migration.Execute(db); err != nil {
|
|
|
|
migration.Fail(err, db)
|
2021-11-28 13:52:27 +01:00
|
|
|
log.Errorf("migrate: executing %s failed with %s [%s]", migration.ID, err, time.Since(start))
|
2021-11-24 12:42:18 +01:00
|
|
|
} else if err = migration.Finish(db); err != nil {
|
2021-11-28 13:52:27 +01:00
|
|
|
log.Warnf("migrate: updating %s failed with %s [%s]", migration.ID, err, time.Since(start))
|
2021-11-24 12:42:18 +01:00
|
|
|
} else {
|
2021-11-28 13:52:27 +01:00
|
|
|
log.Infof("migrate: %s successful [%s]", migration.ID, time.Since(start))
|
2021-11-24 12:42:18 +01:00
|
|
|
}
|
2021-11-21 14:05:07 +01:00
|
|
|
}
|
|
|
|
}
|