mirror of
https://github.com/astaxie/beego.git
synced 2024-12-23 12:00:50 +00:00
313 lines
7.3 KiB
Go
313 lines
7.3 KiB
Go
// Copyright 2014 beego Author. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package migration is used for migration
|
|
//
|
|
// The table structure is as follow:
|
|
//
|
|
// CREATE TABLE `migrations` (
|
|
// `id_migration` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key',
|
|
// `name` varchar(255) DEFAULT NULL COMMENT 'migration name, unique',
|
|
// `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'date migrated or rolled back',
|
|
// `statements` longtext COMMENT 'SQL statements for this migration',
|
|
// `rollback_statements` longtext,
|
|
// `status` enum('update','rollback') DEFAULT NULL COMMENT 'update indicates it is a normal migration while rollback means this migration is rolled back',
|
|
// PRIMARY KEY (`id_migration`)
|
|
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
package migration
|
|
|
|
import (
|
|
"errors"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/astaxie/beego/logs"
|
|
"github.com/astaxie/beego/orm"
|
|
)
|
|
|
|
// const the data format for the bee generate migration datatype
|
|
const (
|
|
DateFormat = "20060102_150405"
|
|
DBDateFormat = "2006-01-02 15:04:05"
|
|
)
|
|
|
|
// Migrationer is an interface for all Migration struct
|
|
type Migrationer interface {
|
|
Up()
|
|
Down()
|
|
Reset()
|
|
Exec(name, status string) error
|
|
GetCreated() int64
|
|
}
|
|
|
|
//Migration defines the migrations by either SQL or DDL
|
|
type Migration struct {
|
|
sqls []string
|
|
Created string
|
|
TableName string
|
|
Engine string
|
|
Charset string
|
|
ModifyType string
|
|
Columns []*Column
|
|
Indexes []*Index
|
|
Primary []*Column
|
|
Uniques []*Unique
|
|
Foreigns []*Foreign
|
|
Renames []*RenameColumn
|
|
RemoveColumns []*Column
|
|
RemoveIndexes []*Index
|
|
RemoveUniques []*Unique
|
|
RemoveForeigns []*Foreign
|
|
}
|
|
|
|
var (
|
|
migrationMap map[string]Migrationer
|
|
)
|
|
|
|
func init() {
|
|
migrationMap = make(map[string]Migrationer)
|
|
}
|
|
|
|
// Up implement in the Inheritance struct for upgrade
|
|
func (m *Migration) Up() {
|
|
|
|
switch m.ModifyType {
|
|
case "reverse":
|
|
m.ModifyType = "alter"
|
|
case "delete":
|
|
m.ModifyType = "create"
|
|
}
|
|
m.sqls = append(m.sqls, m.GetSQL())
|
|
}
|
|
|
|
// Down implement in the Inheritance struct for down
|
|
func (m *Migration) Down() {
|
|
|
|
switch m.ModifyType {
|
|
case "alter":
|
|
m.ModifyType = "reverse"
|
|
case "create":
|
|
m.ModifyType = "delete"
|
|
}
|
|
m.sqls = append(m.sqls, m.GetSQL())
|
|
}
|
|
|
|
//Migrate adds the SQL to the execution list
|
|
func (m *Migration) Migrate(migrationType string) {
|
|
m.ModifyType = migrationType
|
|
m.sqls = append(m.sqls, m.GetSQL())
|
|
}
|
|
|
|
// SQL add sql want to execute
|
|
func (m *Migration) SQL(sql string) {
|
|
m.sqls = append(m.sqls, sql)
|
|
}
|
|
|
|
// Reset the sqls
|
|
func (m *Migration) Reset() {
|
|
m.sqls = make([]string, 0)
|
|
}
|
|
|
|
// Exec execute the sql already add in the sql
|
|
func (m *Migration) Exec(name, status string) error {
|
|
o := orm.NewOrm()
|
|
for _, s := range m.sqls {
|
|
logs.Info("exec sql:", s)
|
|
r := o.Raw(s)
|
|
_, err := r.Exec()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return m.addOrUpdateRecord(name, status)
|
|
}
|
|
|
|
func (m *Migration) addOrUpdateRecord(name, status string) error {
|
|
o := orm.NewOrm()
|
|
if status == "down" {
|
|
status = "rollback"
|
|
p, err := o.Raw("update migrations set status = ?, rollback_statements = ?, created_at = ? where name = ?").Prepare()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
_, err = p.Exec(status, strings.Join(m.sqls, "; "), time.Now().Format(DBDateFormat), name)
|
|
return err
|
|
}
|
|
status = "update"
|
|
p, err := o.Raw("insert into migrations(name, created_at, statements, status) values(?,?,?,?)").Prepare()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = p.Exec(name, time.Now().Format(DBDateFormat), strings.Join(m.sqls, "; "), status)
|
|
return err
|
|
}
|
|
|
|
// GetCreated get the unixtime from the Created
|
|
func (m *Migration) GetCreated() int64 {
|
|
t, err := time.Parse(DateFormat, m.Created)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return t.Unix()
|
|
}
|
|
|
|
// Register register the Migration in the map
|
|
func Register(name string, m Migrationer) error {
|
|
if _, ok := migrationMap[name]; ok {
|
|
return errors.New("already exist name:" + name)
|
|
}
|
|
migrationMap[name] = m
|
|
return nil
|
|
}
|
|
|
|
// Upgrade upgrade the migration from lasttime
|
|
func Upgrade(lasttime int64) error {
|
|
sm := sortMap(migrationMap)
|
|
i := 0
|
|
for _, v := range sm {
|
|
if v.created > lasttime {
|
|
logs.Info("start upgrade", v.name)
|
|
v.m.Reset()
|
|
v.m.Up()
|
|
err := v.m.Exec(v.name, "up")
|
|
if err != nil {
|
|
logs.Error("execute error:", err)
|
|
time.Sleep(2 * time.Second)
|
|
return err
|
|
}
|
|
logs.Info("end upgrade:", v.name)
|
|
i++
|
|
}
|
|
}
|
|
logs.Info("total success upgrade:", i, " migration")
|
|
time.Sleep(2 * time.Second)
|
|
return nil
|
|
}
|
|
|
|
// Rollback rollback the migration by the name
|
|
func Rollback(name string) error {
|
|
if v, ok := migrationMap[name]; ok {
|
|
logs.Info("start rollback")
|
|
v.Reset()
|
|
v.Down()
|
|
err := v.Exec(name, "down")
|
|
if err != nil {
|
|
logs.Error("execute error:", err)
|
|
time.Sleep(2 * time.Second)
|
|
return err
|
|
}
|
|
logs.Info("end rollback")
|
|
time.Sleep(2 * time.Second)
|
|
return nil
|
|
}
|
|
logs.Error("not exist the migrationMap name:" + name)
|
|
time.Sleep(2 * time.Second)
|
|
return errors.New("not exist the migrationMap name:" + name)
|
|
}
|
|
|
|
// Reset reset all migration
|
|
// run all migration's down function
|
|
func Reset() error {
|
|
sm := sortMap(migrationMap)
|
|
i := 0
|
|
for j := len(sm) - 1; j >= 0; j-- {
|
|
v := sm[j]
|
|
if isRollBack(v.name) {
|
|
logs.Info("skip the", v.name)
|
|
time.Sleep(1 * time.Second)
|
|
continue
|
|
}
|
|
logs.Info("start reset:", v.name)
|
|
v.m.Reset()
|
|
v.m.Down()
|
|
err := v.m.Exec(v.name, "down")
|
|
if err != nil {
|
|
logs.Error("execute error:", err)
|
|
time.Sleep(2 * time.Second)
|
|
return err
|
|
}
|
|
i++
|
|
logs.Info("end reset:", v.name)
|
|
}
|
|
logs.Info("total success reset:", i, " migration")
|
|
time.Sleep(2 * time.Second)
|
|
return nil
|
|
}
|
|
|
|
// Refresh first Reset, then Upgrade
|
|
func Refresh() error {
|
|
err := Reset()
|
|
if err != nil {
|
|
logs.Error("execute error:", err)
|
|
time.Sleep(2 * time.Second)
|
|
return err
|
|
}
|
|
err = Upgrade(0)
|
|
return err
|
|
}
|
|
|
|
type dataSlice []data
|
|
|
|
type data struct {
|
|
created int64
|
|
name string
|
|
m Migrationer
|
|
}
|
|
|
|
// Len is part of sort.Interface.
|
|
func (d dataSlice) Len() int {
|
|
return len(d)
|
|
}
|
|
|
|
// Swap is part of sort.Interface.
|
|
func (d dataSlice) Swap(i, j int) {
|
|
d[i], d[j] = d[j], d[i]
|
|
}
|
|
|
|
// Less is part of sort.Interface. We use count as the value to sort by
|
|
func (d dataSlice) Less(i, j int) bool {
|
|
return d[i].created < d[j].created
|
|
}
|
|
|
|
func sortMap(m map[string]Migrationer) dataSlice {
|
|
s := make(dataSlice, 0, len(m))
|
|
for k, v := range m {
|
|
d := data{}
|
|
d.created = v.GetCreated()
|
|
d.name = k
|
|
d.m = v
|
|
s = append(s, d)
|
|
}
|
|
sort.Sort(s)
|
|
return s
|
|
}
|
|
|
|
func isRollBack(name string) bool {
|
|
o := orm.NewOrm()
|
|
var maps []orm.Params
|
|
num, err := o.Raw("select * from migrations where `name` = ? order by id_migration desc", name).Values(&maps)
|
|
if err != nil {
|
|
logs.Info("get name has error", err)
|
|
return false
|
|
}
|
|
if num <= 0 {
|
|
return false
|
|
}
|
|
if maps[0]["status"] == "rollback" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|