From 6f4c1b3a89e963d9317be019aebe2fb162d1c8bc Mon Sep 17 00:00:00 2001 From: ZhengYang Date: Mon, 11 Aug 2014 17:23:52 +0800 Subject: [PATCH] basic workflow --- g_migration.go | 3 +- migrate.go | 172 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 162 insertions(+), 13 deletions(-) diff --git a/g_migration.go b/g_migration.go index 2c0aafb..3eccff6 100644 --- a/g_migration.go +++ b/g_migration.go @@ -64,8 +64,7 @@ func formatSourceCode(fpath string) { cmd.Run() } -const MIGRATION_TPL = ` -package main +const MIGRATION_TPL = `package main import ( "time" diff --git a/migrate.go b/migrate.go index 1e5c37a..d11924d 100644 --- a/migrate.go +++ b/migrate.go @@ -14,7 +14,13 @@ package main -import "os" +import ( + "database/sql" + "os" + "os/exec" + "path" + "strings" +) var cmdMigrate = &Command{ UsageLine: "migrate [Command]", @@ -34,6 +40,10 @@ bee migrate refresh `, } +const ( + TMP_DIR = "temp" +) + func init() { cmdMigrate.Run = runMigration } @@ -51,40 +61,180 @@ func runMigration(cmd *Command, args []string) { if len(args) == 0 { // run all outstanding migrations - ColorLog("[INFO] running all outstanding migrations\n") + ColorLog("[INFO] Running all outstanding migrations\n") migrateUpdate() } else { mcmd := args[0] switch mcmd { case "rollback": - ColorLog("[INFO] rolling back the last migration operation\n") + ColorLog("[INFO] Rolling back the last migration operation\n") migrateRollback() case "reset": - ColorLog("[INFO] reseting all migrations\n") + ColorLog("[INFO] Reseting all migrations\n") migrateReset() case "refresh": - ColorLog("[INFO] refreshing all migrations\n") + ColorLog("[INFO] Refreshing all migrations\n") migrateReset() default: - ColorLog("[ERRO] command is missing\n") + ColorLog("[ERRO] Command is missing\n") os.Exit(2) } - ColorLog("[SUCC] migration successful!\n") + ColorLog("[SUCC] Migration successful!\n") + } +} + +func checkForSchemaUpdateTable(driver string, connStr string) { + db, err := sql.Open(driver, connStr) + if err != nil { + ColorLog("[ERRO] Could not connect to %s: %s\n", driver, connStr) + os.Exit(2) + } + defer db.Close() + if rows, err := db.Query("SHOW TABLES LIKE 'migrations'"); err != nil { + ColorLog("[ERRO] Could not show migrations table: %s\n", err) + os.Exit(2) + } else if !rows.Next() { + // no migrations table, create anew + ColorLog("[INFO] Creating 'migrations' table...\n") + if _, err := db.Query(MYSQL_MIGRATION_DDL); err != nil { + ColorLog("[ERRO] Could not create migrations table: %s\n", err) + os.Exit(2) + } + } + // checking that migrations table schema are expected + if rows, err := db.Query("DESC migrations"); err != nil { + ColorLog("[ERRO] Could not show columns of migrations table: %s\n", err) + os.Exit(2) + } else { + for rows.Next() { + var fieldBytes, typeBytes, nullBytes, keyBytes, defaultBytes, extraBytes []byte + if err := rows.Scan(&fieldBytes, &typeBytes, &nullBytes, &keyBytes, &defaultBytes, &extraBytes); err != nil { + ColorLog("[ERRO] Could not read column information: %s\n", err) + os.Exit(2) + } + fieldStr, typeStr, nullStr, keyStr, defaultStr, extraStr := + string(fieldBytes), string(typeBytes), string(nullBytes), string(keyBytes), string(defaultBytes), string(extraBytes) + if fieldStr == "id_migration" { + if keyStr != "PRI" || extraStr != "auto_increment" { + ColorLog("[ERRO] Column migration.id_migration type mismatch: KEY: %s, EXTRA: %s\n", keyStr, extraStr) + ColorLog("[HINT] Expecting KEY: PRI, EXTRA: auto_increment\n") + os.Exit(2) + } + } else if fieldStr == "file" { + if !strings.HasPrefix(typeStr, "varchar") || nullStr != "YES" { + ColorLog("[ERRO] Column migration.file type mismatch: TYPE: %s, NULL: %s\n", typeStr, nullStr) + ColorLog("[HINT] Expecting TYPE: varchar, NULL: YES\n") + os.Exit(2) + } + + } else if fieldStr == "created_at" { + if typeStr != "timestamp" || defaultStr != "CURRENT_TIMESTAMP" { + ColorLog("[ERRO] Column migration.file type mismatch: TYPE: %s, DEFAULT: %s\n", typeStr, defaultStr) + ColorLog("[HINT] Expecting TYPE: timestamp, DEFAULT: CURRENT_TIMESTAMP\n") + os.Exit(2) + } + } + } + } +} + +func createTempMigrationDir(path string) { + if err := os.MkdirAll(path, 0777); err != nil { + ColorLog("[ERRO] Could not create path: %s\n", err) + os.Exit(2) + } +} + +func writeMigrationSourceFile(filename string, driver string, connStr string) { + if f, err := os.OpenFile(filename+".go", os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err != nil { + ColorLog("[ERRO] Could not create file: %s\n", err) + os.Exit(2) + } else { + content := strings.Replace(MIGRATION_MAIN_TPL, "{{DBDriver}}", driver, -1) + content = strings.Replace(content, "{{ConnStr}}", connStr, -1) + content = strings.Replace(content, "{{CurrTime}}", "123", -1) + if _, err := f.WriteString(content); err != nil { + ColorLog("[ERRO] Could not write to file: %s\n", err) + os.Exit(2) + } + f.Close() + } +} + +func buildMigrationBinary(filename string) { + cmd := exec.Command("go", "build", "-o", filename, filename+".go") + if err := cmd.Run(); err != nil { + ColorLog("[ERRO] Could not build migration binary: %s\n", err) + os.Exit(2) + } +} + +func runMigrationBinary(filename string) { + cmd := exec.Command("./" + filename) + if out, err := cmd.CombinedOutput(); err != nil { + ColorLog("[ERRO] Could not run migration binary\n") + os.Exit(2) + } else { + ColorLog("[INFO] %s", string(out)) + } +} + +func cleanUpMigrationFiles(tmpPath string) { + if err := os.RemoveAll(tmpPath); err != nil { + ColorLog("[ERRO] Could not remove temporary migration directory: %s\n", err) + os.Exit(2) } } func migrateUpdate() { - println("=>update") + connStr := "root:@tcp(127.0.0.1:3306)/sgfas?charset=utf8" + checkForSchemaUpdateTable("mysql", connStr) + filename := path.Join(TMP_DIR, "super") + createTempMigrationDir(TMP_DIR) + writeMigrationSourceFile(filename, "mysql", connStr) + buildMigrationBinary(filename) + runMigrationBinary(filename) + cleanUpMigrationFiles(TMP_DIR) } func migrateRollback() { - println("=>rollback") } func migrateReset() { - println("=>reset") } func migrateRefresh() { - println("=>refresh") + migrateReset() + migrateUpdate() } + +const ( + MIGRATION_MAIN_TPL = `package main + +import( + "github.com/astaxie/beego/orm" + "github.com/astaxie/beego/migration" +) + +func init(){ + orm.RegisterDb("default", "{{DBDriver}}","{{ConnStr}}") +} + +func main(){ + migration.Upgrade({{CurrTime}}) + //migration.Rollback() + //migration.Reset() + //migration.Refresh() +} + +` + MYSQL_MIGRATION_DDL = ` +CREATE TABLE migrations ( + id_migration int(10) unsigned NOT NULL AUTO_INCREMENT, + file varchar(255) DEFAULT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + statements text, + PRIMARY KEY (id_migration) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 +` +)