photoprism/internal/migrate/migrations.go

95 lines
2.5 KiB
Go

package migrate
import (
"database/sql"
"time"
"github.com/photoprism/photoprism/pkg/list"
"github.com/dustin/go-humanize/english"
"github.com/jinzhu/gorm"
)
// Migrations represents a sorted list of migrations.
type Migrations []Migration
// 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)
stmt := db.Model(Migration{})
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
}
// Start runs all migrations that haven't been executed yet.
func (m *Migrations) Start(db *gorm.DB, runFailed bool, ids []string) {
// Find previously executed migrations.
executed := Existing(db)
if prev := len(executed); prev == 0 {
log.Infof("migrate: no previously executed migrations")
} else {
log.Debugf("migrate: found %s", english.Plural(len(executed), "previous migration", "previous migrations"))
}
for _, migration := range *m {
start := time.Now()
migration.StartedAt = start.UTC().Round(time.Second)
// Excluded?
if list.Excludes(ids, migration.ID) {
log.Debugf("migrate: %s skipped", migration.ID)
continue
}
// Already executed?
if done, ok := executed[migration.ID]; ok {
// Try to run failed migrations again?
if (!runFailed || done.Error == "") && !list.Contains(ids, migration.ID) {
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))
continue
}
// Run migration.
if err := migration.Execute(db); err != nil {
migration.Fail(err, db)
log.Errorf("migrate: executing %s failed with %s [%s]", migration.ID, err, time.Since(start))
} else if err = migration.Finish(db); err != nil {
log.Warnf("migrate: updating %s failed with %s [%s]", migration.ID, err, time.Since(start))
} else {
log.Infof("migrate: %s successful [%s]", migration.ID, time.Since(start))
}
}
}