github.com/astaxie/beego@v1.12.3/migration/migration.go (about)

     1  // Copyright 2014 beego Author. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package migration is used for migration
    16  //
    17  // The table structure is as follow:
    18  //
    19  //	CREATE TABLE `migrations` (
    20  //		`id_migration` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key',
    21  //		`name` varchar(255) DEFAULT NULL COMMENT 'migration name, unique',
    22  //		`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'date migrated or rolled back',
    23  //		`statements` longtext COMMENT 'SQL statements for this migration',
    24  //		`rollback_statements` longtext,
    25  //		`status` enum('update','rollback') DEFAULT NULL COMMENT 'update indicates it is a normal migration while rollback means this migration is rolled back',
    26  //		PRIMARY KEY (`id_migration`)
    27  //	) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    28  package migration
    29  
    30  import (
    31  	"errors"
    32  	"sort"
    33  	"strings"
    34  	"time"
    35  
    36  	"github.com/astaxie/beego/logs"
    37  	"github.com/astaxie/beego/orm"
    38  )
    39  
    40  // const the data format for the bee generate migration datatype
    41  const (
    42  	DateFormat   = "20060102_150405"
    43  	DBDateFormat = "2006-01-02 15:04:05"
    44  )
    45  
    46  // Migrationer is an interface for all Migration struct
    47  type Migrationer interface {
    48  	Up()
    49  	Down()
    50  	Reset()
    51  	Exec(name, status string) error
    52  	GetCreated() int64
    53  }
    54  
    55  //Migration defines the migrations by either SQL or DDL
    56  type Migration struct {
    57  	sqls           []string
    58  	Created        string
    59  	TableName      string
    60  	Engine         string
    61  	Charset        string
    62  	ModifyType     string
    63  	Columns        []*Column
    64  	Indexes        []*Index
    65  	Primary        []*Column
    66  	Uniques        []*Unique
    67  	Foreigns       []*Foreign
    68  	Renames        []*RenameColumn
    69  	RemoveColumns  []*Column
    70  	RemoveIndexes  []*Index
    71  	RemoveUniques  []*Unique
    72  	RemoveForeigns []*Foreign
    73  }
    74  
    75  var (
    76  	migrationMap map[string]Migrationer
    77  )
    78  
    79  func init() {
    80  	migrationMap = make(map[string]Migrationer)
    81  }
    82  
    83  // Up implement in the Inheritance struct for upgrade
    84  func (m *Migration) Up() {
    85  
    86  	switch m.ModifyType {
    87  	case "reverse":
    88  		m.ModifyType = "alter"
    89  	case "delete":
    90  		m.ModifyType = "create"
    91  	}
    92  	m.sqls = append(m.sqls, m.GetSQL())
    93  }
    94  
    95  // Down implement in the Inheritance struct for down
    96  func (m *Migration) Down() {
    97  
    98  	switch m.ModifyType {
    99  	case "alter":
   100  		m.ModifyType = "reverse"
   101  	case "create":
   102  		m.ModifyType = "delete"
   103  	}
   104  	m.sqls = append(m.sqls, m.GetSQL())
   105  }
   106  
   107  //Migrate adds the SQL to the execution list
   108  func (m *Migration) Migrate(migrationType string) {
   109  	m.ModifyType = migrationType
   110  	m.sqls = append(m.sqls, m.GetSQL())
   111  }
   112  
   113  // SQL add sql want to execute
   114  func (m *Migration) SQL(sql string) {
   115  	m.sqls = append(m.sqls, sql)
   116  }
   117  
   118  // Reset the sqls
   119  func (m *Migration) Reset() {
   120  	m.sqls = make([]string, 0)
   121  }
   122  
   123  // Exec execute the sql already add in the sql
   124  func (m *Migration) Exec(name, status string) error {
   125  	o := orm.NewOrm()
   126  	for _, s := range m.sqls {
   127  		logs.Info("exec sql:", s)
   128  		r := o.Raw(s)
   129  		_, err := r.Exec()
   130  		if err != nil {
   131  			return err
   132  		}
   133  	}
   134  	return m.addOrUpdateRecord(name, status)
   135  }
   136  
   137  func (m *Migration) addOrUpdateRecord(name, status string) error {
   138  	o := orm.NewOrm()
   139  	if status == "down" {
   140  		status = "rollback"
   141  		p, err := o.Raw("update migrations set status = ?, rollback_statements = ?, created_at = ? where name = ?").Prepare()
   142  		if err != nil {
   143  			return nil
   144  		}
   145  		_, err = p.Exec(status, strings.Join(m.sqls, "; "), time.Now().Format(DBDateFormat), name)
   146  		return err
   147  	}
   148  	status = "update"
   149  	p, err := o.Raw("insert into migrations(name, created_at, statements, status) values(?,?,?,?)").Prepare()
   150  	if err != nil {
   151  		return err
   152  	}
   153  	_, err = p.Exec(name, time.Now().Format(DBDateFormat), strings.Join(m.sqls, "; "), status)
   154  	return err
   155  }
   156  
   157  // GetCreated get the unixtime from the Created
   158  func (m *Migration) GetCreated() int64 {
   159  	t, err := time.Parse(DateFormat, m.Created)
   160  	if err != nil {
   161  		return 0
   162  	}
   163  	return t.Unix()
   164  }
   165  
   166  // Register register the Migration in the map
   167  func Register(name string, m Migrationer) error {
   168  	if _, ok := migrationMap[name]; ok {
   169  		return errors.New("already exist name:" + name)
   170  	}
   171  	migrationMap[name] = m
   172  	return nil
   173  }
   174  
   175  // Upgrade upgrade the migration from lasttime
   176  func Upgrade(lasttime int64) error {
   177  	sm := sortMap(migrationMap)
   178  	i := 0
   179  	migs, _ := getAllMigrations()
   180  	for _, v := range sm {
   181  		if _, ok := migs[v.name]; !ok {
   182  			logs.Info("start upgrade", v.name)
   183  			v.m.Reset()
   184  			v.m.Up()
   185  			err := v.m.Exec(v.name, "up")
   186  			if err != nil {
   187  				logs.Error("execute error:", err)
   188  				time.Sleep(2 * time.Second)
   189  				return err
   190  			}
   191  			logs.Info("end upgrade:", v.name)
   192  			i++
   193  		}
   194  	}
   195  	logs.Info("total success upgrade:", i, " migration")
   196  	time.Sleep(2 * time.Second)
   197  	return nil
   198  }
   199  
   200  // Rollback rollback the migration by the name
   201  func Rollback(name string) error {
   202  	if v, ok := migrationMap[name]; ok {
   203  		logs.Info("start rollback")
   204  		v.Reset()
   205  		v.Down()
   206  		err := v.Exec(name, "down")
   207  		if err != nil {
   208  			logs.Error("execute error:", err)
   209  			time.Sleep(2 * time.Second)
   210  			return err
   211  		}
   212  		logs.Info("end rollback")
   213  		time.Sleep(2 * time.Second)
   214  		return nil
   215  	}
   216  	logs.Error("not exist the migrationMap name:" + name)
   217  	time.Sleep(2 * time.Second)
   218  	return errors.New("not exist the migrationMap name:" + name)
   219  }
   220  
   221  // Reset reset all migration
   222  // run all migration's down function
   223  func Reset() error {
   224  	sm := sortMap(migrationMap)
   225  	i := 0
   226  	for j := len(sm) - 1; j >= 0; j-- {
   227  		v := sm[j]
   228  		if isRollBack(v.name) {
   229  			logs.Info("skip the", v.name)
   230  			time.Sleep(1 * time.Second)
   231  			continue
   232  		}
   233  		logs.Info("start reset:", v.name)
   234  		v.m.Reset()
   235  		v.m.Down()
   236  		err := v.m.Exec(v.name, "down")
   237  		if err != nil {
   238  			logs.Error("execute error:", err)
   239  			time.Sleep(2 * time.Second)
   240  			return err
   241  		}
   242  		i++
   243  		logs.Info("end reset:", v.name)
   244  	}
   245  	logs.Info("total success reset:", i, " migration")
   246  	time.Sleep(2 * time.Second)
   247  	return nil
   248  }
   249  
   250  // Refresh first Reset, then Upgrade
   251  func Refresh() error {
   252  	err := Reset()
   253  	if err != nil {
   254  		logs.Error("execute error:", err)
   255  		time.Sleep(2 * time.Second)
   256  		return err
   257  	}
   258  	err = Upgrade(0)
   259  	return err
   260  }
   261  
   262  type dataSlice []data
   263  
   264  type data struct {
   265  	created int64
   266  	name    string
   267  	m       Migrationer
   268  }
   269  
   270  // Len is part of sort.Interface.
   271  func (d dataSlice) Len() int {
   272  	return len(d)
   273  }
   274  
   275  // Swap is part of sort.Interface.
   276  func (d dataSlice) Swap(i, j int) {
   277  	d[i], d[j] = d[j], d[i]
   278  }
   279  
   280  // Less is part of sort.Interface. We use count as the value to sort by
   281  func (d dataSlice) Less(i, j int) bool {
   282  	return d[i].created < d[j].created
   283  }
   284  
   285  func sortMap(m map[string]Migrationer) dataSlice {
   286  	s := make(dataSlice, 0, len(m))
   287  	for k, v := range m {
   288  		d := data{}
   289  		d.created = v.GetCreated()
   290  		d.name = k
   291  		d.m = v
   292  		s = append(s, d)
   293  	}
   294  	sort.Sort(s)
   295  	return s
   296  }
   297  
   298  func isRollBack(name string) bool {
   299  	o := orm.NewOrm()
   300  	var maps []orm.Params
   301  	num, err := o.Raw("select * from migrations where `name` = ? order by id_migration desc", name).Values(&maps)
   302  	if err != nil {
   303  		logs.Info("get name has error", err)
   304  		return false
   305  	}
   306  	if num <= 0 {
   307  		return false
   308  	}
   309  	if maps[0]["status"] == "rollback" {
   310  		return true
   311  	}
   312  	return false
   313  }
   314  func getAllMigrations() (map[string]string, error) {
   315  	o := orm.NewOrm()
   316  	var maps []orm.Params
   317  	migs := make(map[string]string)
   318  	num, err := o.Raw("select * from migrations order by id_migration desc").Values(&maps)
   319  	if err != nil {
   320  		logs.Info("get name has error", err)
   321  		return migs, err
   322  	}
   323  	if num > 0 {
   324  		for _, v := range maps {
   325  			name := v["name"].(string)
   326  			migs[name] = v["status"].(string)
   327  		}
   328  	}
   329  	return migs, nil
   330  }