2014-08-18 08:41:43 +00:00
// Copyright 2014 beego Author. All Rights Reserved.
2014-08-07 08:30:28 +00:00
//
2014-08-18 08:41:43 +00:00
// 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
2014-08-07 08:30:28 +00:00
//
2014-08-18 08:41:43 +00:00
// http://www.apache.org/licenses/LICENSE-2.0
2014-08-07 08:30:28 +00:00
//
2014-08-18 08:41:43 +00:00
// 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.
2015-09-11 15:16:05 +00:00
// Package migration is used for migration
2014-08-07 08:30:28 +00:00
//
2014-08-18 08:41:43 +00:00
// The table structure is as follow:
2014-08-12 07:50:28 +00:00
//
// 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',
2014-08-13 08:42:16 +00:00
// PRIMARY KEY (`id_migration`)
2014-08-12 07:50:28 +00:00
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2014-08-07 08:30:28 +00:00
package migration
import (
"errors"
"sort"
2014-08-12 07:49:30 +00:00
"strings"
2014-08-07 08:30:28 +00:00
"time"
2016-03-25 02:56:15 +00:00
"github.com/astaxie/beego/logs"
2014-08-07 08:30:28 +00:00
"github.com/astaxie/beego/orm"
)
// const the data format for the bee generate migration datatype
2014-08-13 03:16:19 +00:00
const (
2015-09-11 15:16:05 +00:00
DateFormat = "20060102_150405"
DBDateFormat = "2006-01-02 15:04:05"
2014-08-13 03:16:19 +00:00
)
2014-08-07 08:30:28 +00:00
// Migrationer is an interface for all Migration struct
type Migrationer interface {
Up ( )
Down ( )
2014-08-14 05:37:48 +00:00
Reset ( )
2014-08-12 07:49:30 +00:00
Exec ( name , status string ) error
2014-08-07 08:30:28 +00:00
GetCreated ( ) int64
}
2017-07-13 15:33:30 +00:00
//Migration defines the migrations by either SQL or DDL
2017-07-06 01:10:28 +00:00
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
}
2014-08-14 02:19:55 +00:00
var (
migrationMap map [ string ] Migrationer
)
2014-08-07 08:30:28 +00:00
func init ( ) {
migrationMap = make ( map [ string ] Migrationer )
}
2015-09-11 15:16:05 +00:00
// Up implement in the Inheritance struct for upgrade
2014-08-07 08:30:28 +00:00
func ( m * Migration ) Up ( ) {
2017-07-13 15:33:30 +00:00
switch m . ModifyType {
case "reverse" :
m . ModifyType = "alter"
case "delete" :
m . ModifyType = "create"
}
m . sqls = append ( m . sqls , m . GetSQL ( ) )
2014-08-07 08:30:28 +00:00
}
2015-09-11 15:16:05 +00:00
// Down implement in the Inheritance struct for down
2014-08-07 08:30:28 +00:00
func ( m * Migration ) Down ( ) {
2017-07-13 15:33:30 +00:00
switch m . ModifyType {
case "alter" :
m . ModifyType = "reverse"
case "create" :
m . ModifyType = "delete"
}
m . sqls = append ( m . sqls , m . GetSQL ( ) )
2014-08-07 08:30:28 +00:00
}
2017-07-06 14:56:37 +00:00
//Migrate adds the SQL to the execution list
2017-07-06 01:10:28 +00:00
func ( m * Migration ) Migrate ( migrationType string ) {
m . ModifyType = migrationType
m . sqls = append ( m . sqls , m . GetSQL ( ) )
}
2015-09-11 15:16:05 +00:00
// SQL add sql want to execute
func ( m * Migration ) SQL ( sql string ) {
2014-08-07 08:30:28 +00:00
m . sqls = append ( m . sqls , sql )
}
2014-08-14 05:37:48 +00:00
// Reset the sqls
func ( m * Migration ) Reset ( ) {
m . sqls = make ( [ ] string , 0 )
}
2015-09-11 15:16:05 +00:00
// Exec execute the sql already add in the sql
2014-08-12 07:49:30 +00:00
func ( m * Migration ) Exec ( name , status string ) error {
2014-08-07 08:30:28 +00:00
o := orm . NewOrm ( )
for _ , s := range m . sqls {
2016-03-25 02:56:15 +00:00
logs . Info ( "exec sql:" , s )
2014-08-07 08:30:28 +00:00
r := o . Raw ( s )
_ , err := r . Exec ( )
if err != nil {
return err
}
}
2014-08-12 07:49:30 +00:00
return m . addOrUpdateRecord ( name , status )
}
2014-08-13 02:43:05 +00:00
func ( m * Migration ) addOrUpdateRecord ( name , status string ) error {
2014-08-12 07:49:30 +00:00
o := orm . NewOrm ( )
if status == "down" {
status = "rollback"
2015-11-10 01:39:47 +00:00
p , err := o . Raw ( "update migrations set status = ?, rollback_statements = ?, created_at = ? where name = ?" ) . Prepare ( )
2014-08-12 07:49:30 +00:00
if err != nil {
return nil
}
2015-09-11 15:16:05 +00:00
_ , err = p . Exec ( status , strings . Join ( m . sqls , "; " ) , time . Now ( ) . Format ( DBDateFormat ) , name )
2014-08-12 07:49:30 +00:00
return err
2015-09-11 15:16:05 +00:00
}
status = "update"
2015-11-10 01:39:47 +00:00
p , err := o . Raw ( "insert into migrations(name, created_at, statements, status) values(?,?,?,?)" ) . Prepare ( )
2015-09-11 15:16:05 +00:00
if err != nil {
2014-08-12 07:49:30 +00:00
return err
}
2015-09-11 15:16:05 +00:00
_ , err = p . Exec ( name , time . Now ( ) . Format ( DBDateFormat ) , strings . Join ( m . sqls , "; " ) , status )
return err
2014-08-07 08:30:28 +00:00
}
2015-09-11 15:16:05 +00:00
// GetCreated get the unixtime from the Created
2014-08-07 08:30:28 +00:00
func ( m * Migration ) GetCreated ( ) int64 {
2015-09-11 15:16:05 +00:00
t , err := time . Parse ( DateFormat , m . Created )
2014-08-07 08:30:28 +00:00
if err != nil {
return 0
}
return t . Unix ( )
}
2015-09-11 15:16:05 +00:00
// Register register the Migration in the map
2014-08-07 08:30:28 +00:00
func Register ( name string , m Migrationer ) error {
if _ , ok := migrationMap [ name ] ; ok {
return errors . New ( "already exist name:" + name )
}
migrationMap [ name ] = m
return nil
}
2015-09-11 15:16:05 +00:00
// Upgrade upgrate the migration from lasttime
2014-08-07 08:30:28 +00:00
func Upgrade ( lasttime int64 ) error {
sm := sortMap ( migrationMap )
i := 0
for _ , v := range sm {
if v . created > lasttime {
2016-03-25 02:56:15 +00:00
logs . Info ( "start upgrade" , v . name )
2014-08-14 05:37:48 +00:00
v . m . Reset ( )
2014-08-07 08:30:28 +00:00
v . m . Up ( )
2014-08-12 07:49:30 +00:00
err := v . m . Exec ( v . name , "up" )
2014-08-07 08:30:28 +00:00
if err != nil {
2016-03-25 02:56:15 +00:00
logs . Error ( "execute error:" , err )
2014-08-13 06:50:32 +00:00
time . Sleep ( 2 * time . Second )
2014-08-07 08:30:28 +00:00
return err
}
2016-03-25 02:56:15 +00:00
logs . Info ( "end upgrade:" , v . name )
2014-08-07 08:30:28 +00:00
i ++
}
}
2016-03-25 02:56:15 +00:00
logs . Info ( "total success upgrade:" , i , " migration" )
2014-08-14 03:39:59 +00:00
time . Sleep ( 2 * time . Second )
2014-08-07 08:30:28 +00:00
return nil
}
2015-09-11 15:16:05 +00:00
// Rollback rollback the migration by the name
2014-08-07 08:30:28 +00:00
func Rollback ( name string ) error {
if v , ok := migrationMap [ name ] ; ok {
2016-03-25 02:56:15 +00:00
logs . Info ( "start rollback" )
2014-08-14 05:37:48 +00:00
v . Reset ( )
2014-08-07 08:30:28 +00:00
v . Down ( )
2014-08-12 07:49:30 +00:00
err := v . Exec ( name , "down" )
2014-08-07 08:30:28 +00:00
if err != nil {
2016-03-25 02:56:15 +00:00
logs . Error ( "execute error:" , err )
2014-08-13 06:50:32 +00:00
time . Sleep ( 2 * time . Second )
2014-08-07 08:30:28 +00:00
return err
}
2016-03-25 02:56:15 +00:00
logs . Info ( "end rollback" )
2014-08-14 03:39:59 +00:00
time . Sleep ( 2 * time . Second )
2014-08-07 08:30:28 +00:00
return nil
}
2016-03-25 02:56:15 +00:00
logs . Error ( "not exist the migrationMap name:" + name )
2015-09-11 15:16:05 +00:00
time . Sleep ( 2 * time . Second )
return errors . New ( "not exist the migrationMap name:" + name )
2014-08-07 08:30:28 +00:00
}
2015-09-11 15:16:05 +00:00
// Reset reset all migration
2014-08-07 08:30:28 +00:00
// run all migration's down function
func Reset ( ) error {
2014-08-14 03:44:10 +00:00
sm := sortMap ( migrationMap )
2014-08-07 08:30:28 +00:00
i := 0
2014-08-14 03:54:15 +00:00
for j := len ( sm ) - 1 ; j >= 0 ; j -- {
v := sm [ j ]
2014-08-14 03:44:10 +00:00
if isRollBack ( v . name ) {
2016-03-25 02:56:15 +00:00
logs . Info ( "skip the" , v . name )
2014-08-14 03:39:59 +00:00
time . Sleep ( 1 * time . Second )
2014-08-14 02:19:55 +00:00
continue
}
2016-03-25 02:56:15 +00:00
logs . Info ( "start reset:" , v . name )
2014-08-14 05:37:48 +00:00
v . m . Reset ( )
2014-08-14 03:44:10 +00:00
v . m . Down ( )
err := v . m . Exec ( v . name , "down" )
2014-08-07 08:30:28 +00:00
if err != nil {
2016-03-25 02:56:15 +00:00
logs . Error ( "execute error:" , err )
2014-08-13 06:50:32 +00:00
time . Sleep ( 2 * time . Second )
2014-08-07 08:30:28 +00:00
return err
}
2014-08-14 03:44:10 +00:00
i ++
2016-03-25 02:56:15 +00:00
logs . Info ( "end reset:" , v . name )
2014-08-07 08:30:28 +00:00
}
2016-03-25 02:56:15 +00:00
logs . Info ( "total success reset:" , i , " migration" )
2014-08-14 03:39:59 +00:00
time . Sleep ( 2 * time . Second )
2014-08-07 08:30:28 +00:00
return nil
}
2015-09-11 15:16:05 +00:00
// Refresh first Reset, then Upgrade
2014-08-07 08:30:28 +00:00
func Refresh ( ) error {
err := Reset ( )
if err != nil {
2016-03-25 02:56:15 +00:00
logs . Error ( "execute error:" , err )
2014-08-13 06:50:32 +00:00
time . Sleep ( 2 * time . Second )
2014-08-07 08:30:28 +00:00
return err
}
2014-08-14 03:54:15 +00:00
err = Upgrade ( 0 )
return err
2014-08-07 08:30:28 +00:00
}
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
}
2014-08-14 02:19:55 +00:00
2014-08-14 02:56:49 +00:00
func isRollBack ( name string ) bool {
o := orm . NewOrm ( )
var maps [ ] orm . Params
2014-08-14 03:39:59 +00:00
num , err := o . Raw ( "select * from migrations where `name` = ? order by id_migration desc" , name ) . Values ( & maps )
2014-08-14 02:56:49 +00:00
if err != nil {
2016-03-25 02:56:15 +00:00
logs . Info ( "get name has error" , err )
2014-08-14 02:56:49 +00:00
return false
}
if num <= 0 {
return false
}
if maps [ 0 ] [ "status" ] == "rollback" {
return true
2014-08-14 02:19:55 +00:00
}
return false
}