├── .travis.yml ├── go.mod ├── .env.example ├── go.sum ├── .gitignore ├── README.md └── migrate.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.x 5 | 6 | os: 7 | - linux 8 | - osx 9 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fengzifz/migration-go 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/fatih/color v1.9.0 7 | github.com/go-sql-driver/mysql v1.5.0 8 | github.com/joho/godotenv v1.3.0 9 | ) 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=MigrationExample 2 | 3 | DB_CONNECTION=mysql 4 | DB_HOST=127.0.0.1 5 | DB_PORT=3306 6 | DB_DATABASE= 7 | DB_USERNAME= 8 | DB_PASSWORD= 9 | DB_CHARSET=utf8 10 | DB_PARSETIME=True 11 | DB_LOC=Local -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= 2 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 3 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 4 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 5 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 6 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 7 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= 8 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 9 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 10 | github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= 11 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 12 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 13 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= 14 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/usage.statistics.xml 10 | .idea/**/dictionaries 11 | .idea/**/shelf 12 | 13 | # Sensitive or high-churn files 14 | .idea/**/dataSources/ 15 | .idea/**/dataSources.ids 16 | .idea/**/dataSources.local.xml 17 | .idea/**/sqlDataSources.xml 18 | .idea/**/dynamic.xml 19 | .idea/**/uiDesigner.xml 20 | .idea/**/dbnavigator.xml 21 | 22 | # Gradle 23 | .idea/**/gradle.xml 24 | .idea/**/libraries 25 | 26 | .idea/ 27 | 28 | # Gradle and Maven with auto-import 29 | # When using Gradle or Maven with auto-import, you should exclude module files, 30 | # since they will be recreated, and may cause churn. Uncomment if using 31 | # auto-import. 32 | # .idea/modules.xml 33 | # .idea/*.iml 34 | # .idea/modules 35 | 36 | # CMake 37 | cmake-build-*/ 38 | 39 | # Mongo Explorer plugin 40 | .idea/**/mongoSettings.xml 41 | 42 | # File-based project format 43 | *.iws 44 | 45 | # IntelliJ 46 | out/ 47 | 48 | # mpeltonen/sbt-idea plugin 49 | .idea_modules/ 50 | 51 | # JIRA plugin 52 | atlassian-ide-plugin.xml 53 | 54 | # Cursive Clojure plugin 55 | .idea/replstate.xml 56 | 57 | # Crashlytics plugin (for Android Studio and IntelliJ) 58 | com_crashlytics_export_strings.xml 59 | crashlytics.properties 60 | crashlytics-build.properties 61 | fabric.properties 62 | 63 | # Editor-based Rest Client 64 | .idea/httpRequests 65 | ### Go template 66 | # Binaries for programs and plugins 67 | *.exe 68 | *.exe~ 69 | *.dll 70 | *.so 71 | *.dylib 72 | 73 | # Test binary, build with `go test -c` 74 | *.test 75 | 76 | # Output of the go coverage tool, specifically when used with LiteIDE 77 | *.out 78 | 79 | .env 80 | vendor/ 81 | database/migrations 82 | Gopkg.lock 83 | Gopkg.toml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Migration written in Go 2 | 3 | [![Build Status](https://travis-ci.org/fengzifz/migration-go.svg?branch=master)](https://travis-ci.org/fengzifz/migration-go) 4 | 5 | 像 Laravel migration 那样管理数据库 6 | 7 | ## 目前支持的命令: 8 | - **make:migration**: 创建 migration 9 | - **make:seeder**: 创建 seeder 10 | - **up**: 升级数据库 11 | - **down **: 回滚,默认回滚 1 步,`` 是可选参数,直接填数字 12 | - **refresh**: 重新运行所有的 migration 13 | - **db:seed**: 运行 seeder 14 | 15 | ## 支持的数据库: 16 | - MySQL 17 | 18 | **日后会支持更多的类型的** 19 | 20 | ## 第三方依赖: 21 | - github.com/joho/godotenv 22 | - github.com/go-sql-driver/mysql 23 | - github.com/fatih/color 24 | 25 | ## 目录结构 26 | ``` 27 | ├── database 28 | │ ├── migrations # Migration files 29 | ├── migrate.go 30 | └── .env # DB information 31 | ``` 32 | 33 | ## 安装 34 | 1. 直接把 `migrate.go` 拷贝到项目的根目录,并根据你自己的项目所使用的包管理工具,下载欠缺的依赖; 35 | 2. 在根目录添加 `.env` 文件,并按照下面模板填入你的数据库信息: 36 | ``` 37 | APP_NAME=MigrationExample 38 | 39 | DB_CONNECTION=mysql 40 | DB_HOST=127.0.0.1 41 | DB_PORT=3306 42 | DB_DATABASE= 43 | DB_USERNAME= 44 | DB_PASSWORD= 45 | DB_CHARSET=utf8 46 | DB_PARSETIME=True 47 | DB_LOC=Local 48 | ``` 49 | 50 | ## 使用 51 | 1. 创建 migration 52 | ``` 53 | go run migrate.go make:migration create_user_table 54 | 55 | # 然后会在 database/migration 下面创建对应的 migration,并生成一个 up.sql 和 一个 down.sql 56 | # database/migration/20180914180229_create_user_table/up.sql 57 | # database/migration/20180914180229_create_user_table/down.sql 58 | ``` 59 | 60 | 2. 升级 migration 61 | ``` 62 | go run migrate.go up 63 | ``` 64 | 65 | 3. 回滚 66 | ``` 67 | go run migrate.go down # 默认回滚 1 步 68 | 69 | go run migrate.go down 2 # 回滚 2 步 70 | 71 | ``` 72 | 73 | 4. 重新运行所有 migration 74 | ``` 75 | go run migrate.go refresh 76 | ``` 77 | 78 | 5. 创建 seeder 79 | ``` 80 | go run migrate.go make:seeder user_seeder 81 | ``` 82 | 83 | 6. 运行 seeder 84 | ``` 85 | # 插入数据 86 | go run migrate.go db:seed user_seeder 87 | 88 | # 清空表,重新插入数据 89 | go run migrate.go db:seed user_seeder refresh 90 | ``` 91 | **注意**:要支持 refresh,那么创建 seeder 时,seeder 的名字需要遵守这个约束:_seeder -------------------------------------------------------------------------------- /migrate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "database/sql" 6 | "github.com/fatih/color" 7 | _ "github.com/go-sql-driver/mysql" 8 | "github.com/joho/godotenv" 9 | "io/ioutil" 10 | "os" 11 | "regexp" 12 | "strconv" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // Support command: 18 | // - make: CreateMigration a migration file, "go Migrate make create_user_table" 19 | // - Migrate: Migrate the database to the latest version, "go Migrate Migrate" 20 | // - Rollback : Rollback the database to an old version, "go Migrate Rollback", default Rollback 1 version 21 | // or "go Migrate Rollback 2", it means Rollback 2 versions 22 | 23 | var ( 24 | createMigrationSql = "CREATE TABLE IF NOT EXISTS migrations (" + 25 | "id int(10) UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY," + 26 | "migration varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL," + 27 | "batch int(11) NOT NULL " + 28 | ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;" 29 | queryAllMigrationSql = "SELECT * FROM migrations;" 30 | queryLastMigrationSql = "SELECT batch FROM migrations ORDER BY batch DESC;" 31 | updateMigrationSql = "INSERT INTO migrations (migration, batch) VALUES DummyString;" 32 | dropTableSql = "DROP TABLE IF EXISTS `DummyTable`;" 33 | createTableSql = "CREATE TABLE DummyTable (\n" + 34 | "id int(10) UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, \n" + 35 | "created_at timestamp NULL DEFAULT NULL, \n" + 36 | "updated_at timestamp NULL DEFAULT NULL\n" + 37 | ");" 38 | insertSql = "INSERT INTO `` \n" + 39 | "() \n" + 40 | "VALUES \n" + 41 | "();" 42 | ) 43 | 44 | // Migration files save path 45 | var migrationPath = "./database/migrations/" 46 | var seedPath = "./database/seeds/" 47 | 48 | type rowScanner interface { 49 | Scan(dst ...interface{}) error 50 | } 51 | 52 | type Migration struct { 53 | ID int64 54 | Migration string 55 | Batch int64 56 | } 57 | 58 | var db *sql.DB 59 | 60 | func init() { 61 | conf() 62 | createDir(migrationPath) 63 | createDir(seedPath) 64 | InitMigration() 65 | } 66 | 67 | func conf() { 68 | err := godotenv.Load() 69 | checkErr(err) 70 | 71 | // Database configuration 72 | conn := os.Getenv("DB_CONNECTION") 73 | dbName := os.Getenv("DB_DATABASE") 74 | username := os.Getenv("DB_USERNAME") 75 | password := os.Getenv("DB_PASSWORD") 76 | str := []string{username, ":", password, "@/", dbName} 77 | connInfo := strings.Join(str, "") 78 | 79 | db, err = sql.Open(conn, connInfo) 80 | if err != nil { 81 | checkErr(err) 82 | } 83 | } 84 | 85 | // Connect to database 86 | // Read configurations' info in .env 87 | func InitMigration() { 88 | // CreateMigration migrations table if not exist 89 | _, err := db.Exec(createMigrationSql) 90 | checkErr(err) 91 | } 92 | 93 | // CreateMigration dir 94 | func createDir(path string) { 95 | // Check ./database/migrations is exist, CreateMigration it if not 96 | if _, err := os.Stat(path); os.IsNotExist(err) { 97 | _ = os.Mkdir(path, 0755) 98 | } 99 | } 100 | 101 | // Check err and output 102 | func checkErr(err error) { 103 | if err != nil { 104 | color.Red("Error: %v", err) 105 | os.Exit(1) 106 | } 107 | } 108 | 109 | func main() { 110 | command := os.Args[1] 111 | 112 | if strings.Compare(command, "make:migration") == 0 { 113 | 114 | // *********************** 115 | // CreateMigration a migration file 116 | // ./migrate make xxx 117 | // *********************** 118 | 119 | fileName := os.Args[2] 120 | 121 | if len(fileName) < 0 { 122 | color.Red("Please enter a migration file name") 123 | os.Exit(2) 124 | } 125 | 126 | _, err := CreateMigration(fileName) 127 | checkErr(err) 128 | 129 | color.Green("Create migration successfully!") 130 | 131 | } else if strings.Compare(command, "up") == 0 { 132 | 133 | // **************** 134 | // Migrate database 135 | // ./migrate migrate 136 | // **************** 137 | err := Migrate() 138 | checkErr(err) 139 | 140 | color.Green("Migrate successfully!") 141 | 142 | } else if strings.Compare(command, "down") == 0 { 143 | 144 | // ******** 145 | // Rollback 146 | // ./migrate rollback OR ./migrate rollback 3 147 | // ******** 148 | var step string 149 | if len(os.Args) < 3 { 150 | // Default step is 1 151 | step = "1" 152 | } else { 153 | step = os.Args[2] 154 | } 155 | 156 | err := Rollback(step) 157 | checkErr(err) 158 | 159 | color.Green("Rollback successfully!") 160 | 161 | } else if strings.Compare(command, "refresh") == 0 { 162 | 163 | // ********************************** 164 | // Refresh - Rollback and re-Migrate 165 | // ./migrate refresh 166 | // ********************************** 167 | 168 | ok, err := Refresh() 169 | checkErr(err) 170 | 171 | if ok { 172 | color.Green("Refresh successfully!") 173 | } else { 174 | color.Blue("Refresh nothing") 175 | } 176 | } else if strings.Compare(command, "make:seeder") == 0 { 177 | 178 | // ********************************** 179 | // Create seeder 180 | // ./migrate make:seeder 181 | // ********************************** 182 | 183 | filename := os.Args[2] 184 | if len(filename) < 0 { 185 | color.Red("Please enter seeder name") 186 | os.Exit(2) 187 | } 188 | 189 | _, err := CreateSeeder(filename) 190 | checkErr(err) 191 | 192 | color.Green("Create seeder successfully!") 193 | } else if strings.Compare(command, "db:seed") == 0 { 194 | 195 | // ********************************** 196 | // Seed 197 | // ./migrate db:seed 198 | // ********************************** 199 | 200 | filename := os.Args[2] 201 | if len(filename) < 0 { 202 | color.Red("Please enter seeder name") 203 | os.Exit(2) 204 | } 205 | 206 | var refresh bool 207 | isRefresh := os.Args[3] 208 | if strings.Compare(isRefresh, "refresh") == 0 { 209 | refresh = true 210 | } else { 211 | refresh = false 212 | } 213 | 214 | err := InsertSeeder(filename, refresh) 215 | checkErr(err) 216 | 217 | color.Green("Update seed successfully!") 218 | } else { 219 | color.Red("Command not support: %v", command) 220 | } 221 | } 222 | 223 | // **** Command line tools **** 224 | // CreateMigration a migration file in /database/migration/ 225 | // It will CreateMigration a directory named _name, 226 | // there are two sql files inside: up.sql and down.sql 227 | func CreateMigration(name string) (string, error) { 228 | 229 | var ( 230 | err error 231 | upFile *os.File 232 | downFile *os.File 233 | ) 234 | 235 | timestamp := time.Now().Format("20060102150405") 236 | str := []string{migrationPath, timestamp, "_", name} 237 | dirName := strings.Join(str, "") 238 | createDir(dirName) 239 | 240 | // Match table creation 241 | // use CreateMigration.stub template for table creation 242 | // use blank.stub template for others 243 | reg := regexp.MustCompile(`^create_(\w+)_table$`) 244 | 245 | upFile, err = os.Create(dirName + "/up.sql") 246 | if err != nil { 247 | return "", err 248 | } 249 | 250 | downFile, err = os.Create(dirName + "/down.sql") 251 | if err != nil { 252 | return "", err 253 | } 254 | 255 | defer upFile.Close() 256 | defer downFile.Close() 257 | 258 | upWriter := bufio.NewWriter(upFile) 259 | downWriter := bufio.NewWriter(downFile) 260 | 261 | if reg.MatchString(name) { 262 | r := strings.NewReplacer("create_", "", "_table", "") 263 | tableName := r.Replace(name) 264 | _, err = upWriter.WriteString(strings.Replace(createTableSql, "DummyTable", tableName, -1)) 265 | if err != nil { 266 | return "", err 267 | } 268 | 269 | _ = upWriter.Flush() 270 | 271 | _, err = downWriter.WriteString(strings.Replace(dropTableSql, "DummyTable", tableName, -1)) 272 | if err != nil { 273 | return "", err 274 | } 275 | 276 | _ = downWriter.Flush() 277 | } else { 278 | _, err = upWriter.WriteString("") 279 | if err != nil { 280 | return "", err 281 | } 282 | 283 | _, err = downWriter.WriteString("") 284 | if err != nil { 285 | return "", err 286 | } 287 | } 288 | 289 | color.Green("Created: %v", name) 290 | return dirName, nil 291 | } 292 | 293 | // Migration 294 | func Migrate() error { 295 | var ( 296 | fSlices []string 297 | arr []string 298 | batch int 299 | files []os.FileInfo 300 | err error 301 | rows *sql.Rows 302 | lastBatch int 303 | dbMigrate []string 304 | toMigrate []string 305 | m *Migration 306 | insertStr string 307 | symbol string 308 | upSql []byte 309 | ) 310 | 311 | // List migrations files 312 | files, err = ioutil.ReadDir(migrationPath) 313 | if err != nil { 314 | return err 315 | } 316 | 317 | for _, f := range files { 318 | arr = strings.Split(f.Name(), ".") 319 | fSlices = append(fSlices, arr[0]) 320 | } 321 | 322 | // Check migration version in database 323 | rows, err = db.Query(queryAllMigrationSql) 324 | if err != nil { 325 | return err 326 | } 327 | 328 | lastRow := db.QueryRow(queryLastMigrationSql) 329 | _ = lastRow.Scan(&lastBatch) 330 | batch = lastBatch + 1 331 | 332 | defer rows.Close() 333 | 334 | if lastBatch == 0 { 335 | // No migration record in database, all migrations should to be Migrate 336 | toMigrate = fSlices 337 | } else { 338 | // Get migrated files' name 339 | for rows.Next() { 340 | m, err = scanRow(rows) 341 | if err != nil { 342 | return err 343 | } 344 | 345 | dbMigrate = append(dbMigrate, m.Migration) 346 | } 347 | 348 | // Compare and get which migration not migrated yet 349 | for _, v := range fSlices { 350 | if !sliceContain(dbMigrate, v) { 351 | toMigrate = append(toMigrate, v) 352 | } 353 | } 354 | } 355 | 356 | // Nothing to Migrate, stop and log fatal 357 | toMigrateLen := len(toMigrate) 358 | if toMigrateLen == 0 { 359 | color.Blue("Nothing migrated") 360 | os.Exit(2) 361 | } 362 | 363 | // Migrate 364 | for i, v := range toMigrate { 365 | 366 | // Read up.sql 367 | upSql, err = ioutil.ReadFile(migrationPath + v + "/up.sql") 368 | if err != nil { 369 | return err 370 | } 371 | 372 | _, err = db.Exec(string(upSql)) 373 | if err != nil { 374 | return err 375 | } 376 | 377 | color.Green("Migrated: %v", v) 378 | 379 | // Calculate the batch number, which is need to Migrate 380 | if i+1 == toMigrateLen { 381 | symbol = "" 382 | } else { 383 | symbol = "," 384 | } 385 | 386 | insertStr += "('" + v + "', " + strconv.Itoa(batch) + ")" + symbol 387 | } 388 | 389 | // Connect sql update statement 390 | updateMigrationSql = strings.Replace(updateMigrationSql, "DummyString", insertStr, -1) 391 | 392 | _, err = db.Exec(updateMigrationSql) 393 | if err != nil { 394 | return err 395 | } 396 | 397 | return nil 398 | } 399 | 400 | // Rollback migration 401 | func Rollback(step string) error { 402 | 403 | var ( 404 | lastBatch int 405 | toBatch int 406 | err error 407 | rows *sql.Rows 408 | rollBackMig []string 409 | m *Migration 410 | downSql []byte 411 | ) 412 | 413 | lastRow := db.QueryRow(queryLastMigrationSql) 414 | _ = lastRow.Scan(&lastBatch) 415 | 416 | if i, err := strconv.Atoi(step); err == nil { 417 | if lastBatch >= i { 418 | toBatch = lastBatch - (i - 1) 419 | } else { 420 | color.Red("Can not Rollback %d steps", i) 421 | return err 422 | } 423 | } 424 | 425 | // Which migrations need to be Rollback 426 | rows, err = db.Query("SELECT * FROM migrations WHERE `batch`>=" + strconv.Itoa(toBatch)) 427 | if err != nil { 428 | return err 429 | } 430 | 431 | // Rollback slice 432 | for rows.Next() { 433 | m, err = scanRow(rows) 434 | if err != nil { 435 | return err 436 | } 437 | 438 | rollBackMig = append(rollBackMig, m.Migration) 439 | } 440 | 441 | // Rolling back 442 | for _, v := range rollBackMig { 443 | 444 | downSql, err = ioutil.ReadFile(migrationPath + v + "/down.sql") 445 | if err != nil { 446 | return err 447 | } 448 | 449 | _, err = db.Exec(string(downSql)) 450 | if err != nil { 451 | return err 452 | } 453 | 454 | color.Green("Rollback: %s", v) 455 | } 456 | 457 | // Delete migrations record 458 | _, err = db.Exec("DELETE FROM migrations WHERE `batch`>=" + strconv.Itoa(toBatch)) 459 | if err != nil { 460 | return err 461 | } 462 | 463 | return nil 464 | } 465 | 466 | // Refresh migration: Rollback all and re-Migrate 467 | func Refresh() (bool, error) { 468 | var ( 469 | insertStr string 470 | symbol string 471 | fileByte []byte 472 | err error 473 | rows *sql.Rows 474 | rollBackMig []string 475 | m *Migration 476 | ) 477 | 478 | rows, err = db.Query("SELECT * FROM migrations;") 479 | if err != nil { 480 | return false, err 481 | } 482 | 483 | for rows.Next() { 484 | m, err = scanRow(rows) 485 | if err != nil { 486 | return false, err 487 | } 488 | 489 | rollBackMig = append(rollBackMig, m.Migration) 490 | } 491 | 492 | // Rollback and re-Migrate 493 | fileLen := len(rollBackMig) 494 | if fileLen > 0 { 495 | for i, v := range rollBackMig { 496 | // down 497 | fileByte, err = ioutil.ReadFile(migrationPath + v + "/down.sql") 498 | if err != nil { 499 | return false, err 500 | } 501 | 502 | _, err = db.Exec(string(fileByte)) 503 | if err != nil { 504 | return false, err 505 | } 506 | 507 | // up 508 | fileByte, err = ioutil.ReadFile(migrationPath + v + "/up.sql") 509 | if err != nil { 510 | return false, err 511 | } 512 | 513 | _, err = db.Exec(string(fileByte)) 514 | if err != nil { 515 | return false, err 516 | } 517 | 518 | if i == fileLen-1 { 519 | symbol = "" 520 | } else { 521 | symbol = "," 522 | } 523 | 524 | insertStr += "('" + v + "', 1)" + symbol 525 | } 526 | 527 | // Update migrations table 528 | _, _ = db.Exec("TRUNCATE migrations;") 529 | _, err = db.Exec(strings.Replace(updateMigrationSql, "DummyString", insertStr, -1)) 530 | if err != nil { 531 | return false, err 532 | } 533 | 534 | return true, nil 535 | 536 | } else { 537 | return false, nil 538 | } 539 | } 540 | 541 | // Create seeder 542 | func CreateSeeder(name string) (string, error) { 543 | 544 | // check duplicate filename 545 | file, err := ioutil.ReadDir(seedPath) 546 | if err != nil { 547 | return "", err 548 | } 549 | 550 | for _, v := range file { 551 | filename := strings.Split(v.Name(), ".")[0] 552 | if strings.Compare(strings.ToLower(filename), strings.ToLower(name)) == 0 { 553 | color.Red("%s already exist.", name) 554 | os.Exit(2) 555 | } 556 | } 557 | 558 | // Create a seed file 559 | f, err := os.Create(seedPath + strings.ToLower(name) + ".sql") 560 | if err != nil { 561 | return "", err 562 | } 563 | 564 | defer f.Close() 565 | 566 | w := bufio.NewWriter(f) 567 | 568 | _, err = w.WriteString(insertSql) 569 | if err != nil { 570 | return "", err 571 | } 572 | 573 | _ = w.Flush() 574 | 575 | return name, nil 576 | } 577 | 578 | // Insert seeder into database 579 | func InsertSeeder(name string, refresh bool) error { 580 | 581 | seedSql, err := ioutil.ReadFile(seedPath + name + ".sql") 582 | if err != nil { 583 | return err 584 | } 585 | 586 | if refresh { 587 | tableName := strings.Replace(name, "_seeder", "", -1) 588 | _, err = db.Exec("TRUNCATE " + tableName) 589 | if err != nil { 590 | return err 591 | } 592 | } 593 | _, err = db.Exec(string(seedSql)) 594 | if err != nil { 595 | return err 596 | } 597 | 598 | color.Green("db seed successfully!") 599 | 600 | return nil 601 | } 602 | 603 | // **** other helpers **** 604 | // Check if slice contain a string 605 | func sliceContain(s []string, str string) bool { 606 | for _, v := range s { 607 | if strings.Compare(v, str) == 0 { 608 | return true 609 | } 610 | } 611 | return false 612 | } 613 | 614 | // Map sql row to struct 615 | func scanRow(s rowScanner) (*Migration, error) { 616 | var ( 617 | id int64 618 | migration sql.NullString 619 | batch int64 620 | ) 621 | 622 | if err := s.Scan(&id, &migration, &batch); err != nil { 623 | return nil, err 624 | } 625 | 626 | mig := &Migration{ 627 | ID: id, 628 | Migration: migration.String, 629 | Batch: batch, 630 | } 631 | 632 | return mig, nil 633 | } 634 | --------------------------------------------------------------------------------