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 }