answer/internal/migrations/migrations.go

106 lines
3.0 KiB
Go

package migrations
import (
"fmt"
"github.com/segmentfault/answer/internal/base/data"
"github.com/segmentfault/answer/internal/entity"
"github.com/segmentfault/pacman/log"
"xorm.io/xorm"
)
const minDBVersion = 0 // answer 1.0.0
// Migration describes on migration from lower version to high version
type Migration interface {
Description() string
Migrate(*xorm.Engine) error
}
type migration struct {
description string
migrate func(*xorm.Engine) error
}
// Description returns the migration's description
func (m *migration) Description() string {
return m.description
}
// Migrate executes the migration
func (m *migration) Migrate(x *xorm.Engine) error {
return m.migrate(x)
}
// NewMigration creates a new migration
func NewMigration(desc string, fn func(*xorm.Engine) error) Migration {
return &migration{description: desc, migrate: fn}
}
// Use noopMigration when there is a migration that has been no-oped
var noopMigration = func(_ *xorm.Engine) error { return nil }
var migrations = []Migration{
// 0->1
NewMigration("this is first version, no operation", noopMigration),
}
// GetCurrentDBVersion returns the current db version
func GetCurrentDBVersion(engine *xorm.Engine) (int64, error) {
if err := engine.Sync(new(entity.Version)); err != nil {
return -1, fmt.Errorf("sync version failed: %v", err)
}
currentVersion := &entity.Version{ID: 1}
has, err := engine.Get(currentVersion)
if err != nil {
return -1, fmt.Errorf("get first version failed: %v", err)
}
if !has {
_, err := engine.InsertOne(&entity.Version{ID: 1, VersionNumber: 0})
if err != nil {
return -1, fmt.Errorf("insert first version failed: %v", err)
}
return 0, nil
}
return currentVersion.VersionNumber, nil
}
// ExpectedVersion returns the expected db version
func ExpectedVersion() int64 {
return int64(minDBVersion + len(migrations))
}
// Migrate database to current version
func Migrate(dataConf *data.Database) error {
engine, err := data.NewDB(false, dataConf)
if err != nil {
fmt.Println("new database failed: ", err.Error())
return err
}
currentDBVersion, err := GetCurrentDBVersion(engine)
if err != nil {
return err
}
expectedVersion := ExpectedVersion()
for currentDBVersion < expectedVersion {
log.Infof("[migrate] current db version is %d, try to migrate version %d, latest version is %d",
currentDBVersion, currentDBVersion+1, expectedVersion)
migrationFunc := migrations[currentDBVersion]
log.Infof("[migrate] try to migrate db version %d, description: %s", currentDBVersion+1, migrationFunc.Description())
if err := migrationFunc.Migrate(engine); err != nil {
log.Errorf("[migrate] migrate to db version %d failed: ", currentDBVersion+1, err.Error())
return err
}
log.Infof("[migrate] migrate to db version %d success", currentDBVersion+1)
if _, err := engine.Update(&entity.Version{ID: 1, VersionNumber: currentDBVersion + 1}); err != nil {
log.Errorf("[migrate] migrate to db version %d, update failed: %s", currentDBVersion+1, err.Error())
return err
}
currentDBVersion++
}
return nil
}