super-graph/internal/serv/cmd_migrate.go

318 lines
7.0 KiB
Go
Raw Normal View History

2019-09-26 06:35:31 +02:00
package serv
import (
"fmt"
"os"
2019-09-29 02:46:55 +02:00
"path"
2019-09-26 06:35:31 +02:00
"path/filepath"
"strconv"
"strings"
"time"
2020-04-16 06:26:32 +02:00
"github.com/dosco/super-graph/internal/serv/internal/migrate"
2019-09-26 06:35:31 +02:00
"github.com/spf13/cobra"
)
var newMigrationText = `-- Write your migrate up statements here
---- create above / drop below ----
-- Write your migrate down statements here. If this migration is irreversible
-- Then delete the separator line above.
`
2019-09-28 17:34:03 +02:00
func cmdDBSetup(cmd *cobra.Command, args []string) {
initConfOnce()
2019-09-28 17:34:03 +02:00
cmdDBCreate(cmd, []string{})
cmdDBMigrate(cmd, []string{"up"})
2019-09-29 02:46:55 +02:00
2020-04-11 08:45:06 +02:00
sfile := path.Join(conf.cpath, conf.SeedFile)
2019-09-29 02:46:55 +02:00
_, err := os.Stat(sfile)
if err == nil {
cmdDBSeed(cmd, []string{})
return
}
if !os.IsNotExist(err) {
log.Fatalf("ERR unable to check if '%s' exists: %s", sfile, err)
2019-09-29 02:46:55 +02:00
}
log.Printf("WRN failed to read seed file '%s'", sfile)
2019-09-28 17:34:03 +02:00
}
func cmdDBReset(cmd *cobra.Command, args []string) {
initConfOnce()
2019-09-28 17:34:03 +02:00
if conf.Production {
log.Fatalln("ERR db:reset does not work in production")
2019-09-28 17:34:03 +02:00
}
cmdDBDrop(cmd, []string{})
cmdDBSetup(cmd, []string{})
}
2019-09-28 17:34:03 +02:00
func cmdDBCreate(cmd *cobra.Command, args []string) {
initConfOnce()
2019-09-28 17:34:03 +02:00
2020-04-13 06:43:18 +02:00
db, err := initDB(conf, false)
2019-09-28 17:34:03 +02:00
if err != nil {
log.Fatalf("ERR failed to connect to database: %s", err)
2019-09-28 17:34:03 +02:00
}
defer db.Close()
2019-09-28 17:34:03 +02:00
sql := fmt.Sprintf(`CREATE DATABASE "%s"`, conf.DB.DBName)
2019-09-28 17:34:03 +02:00
_, err = db.Exec(sql)
2019-09-28 17:34:03 +02:00
if err != nil {
log.Fatalf("ERR failed to create database: %s", err)
2019-09-28 17:34:03 +02:00
}
log.Printf("INF created database '%s'", conf.DB.DBName)
2019-09-28 17:34:03 +02:00
}
func cmdDBDrop(cmd *cobra.Command, args []string) {
initConfOnce()
2019-09-28 17:34:03 +02:00
2020-04-13 06:43:18 +02:00
db, err := initDB(conf, false)
2019-09-28 17:34:03 +02:00
if err != nil {
log.Fatalf("ERR failed to connect to database: %s", err)
2019-09-28 17:34:03 +02:00
}
defer db.Close()
2019-09-28 17:34:03 +02:00
sql := fmt.Sprintf(`DROP DATABASE IF EXISTS "%s"`, conf.DB.DBName)
2019-09-28 17:34:03 +02:00
_, err = db.Exec(sql)
2019-09-28 17:34:03 +02:00
if err != nil {
log.Fatalf("ERR failed to drop database: %s", err)
2019-09-28 17:34:03 +02:00
}
log.Printf("INF dropped database '%s'", conf.DB.DBName)
2019-09-28 17:34:03 +02:00
}
func cmdDBNew(cmd *cobra.Command, args []string) {
2019-09-26 06:35:31 +02:00
if len(args) != 1 {
cmd.Help() //nolint: errcheck
2019-09-26 06:35:31 +02:00
os.Exit(1)
}
initConfOnce()
2019-09-26 06:35:31 +02:00
name := args[0]
m, err := migrate.FindMigrations(conf.MigrationsPath)
if err != nil {
log.Fatalf("ERR error loading migrations: %s", err)
2019-09-26 06:35:31 +02:00
}
mname := fmt.Sprintf("%d_%s.sql", len(m), name)
2019-09-26 06:35:31 +02:00
// Write new migration
mpath := filepath.Join(conf.MigrationsPath, mname)
mfile, err := os.OpenFile(mpath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666)
if err != nil {
log.Fatalf("ERR %s", err)
2019-09-26 06:35:31 +02:00
}
defer mfile.Close()
_, err = mfile.WriteString(newMigrationText)
if err != nil {
log.Fatalf("ERR %s", err)
2019-09-26 06:35:31 +02:00
}
log.Printf("INR created migration '%s'", mpath)
2019-09-26 06:35:31 +02:00
}
2019-09-28 17:34:03 +02:00
func cmdDBMigrate(cmd *cobra.Command, args []string) {
if len(args) == 0 {
cmd.Help() //nolint: errcheck
2019-09-28 17:34:03 +02:00
os.Exit(1)
}
initConfOnce()
2019-09-28 17:34:03 +02:00
dest := args[0]
2020-04-13 06:43:18 +02:00
conn, err := initDB(conf, true)
2019-09-26 06:35:31 +02:00
if err != nil {
log.Fatalf("ERR failed to connect to database: %s", err)
2019-09-26 06:35:31 +02:00
}
defer conn.Close()
2019-09-26 06:35:31 +02:00
m, err := migrate.NewMigrator(conn, "schema_version")
if err != nil {
log.Fatalf("ERR failed to initializing migrator: %s", err)
2019-09-26 06:35:31 +02:00
}
2019-09-28 17:34:03 +02:00
m.Data = getMigrationVars()
2019-09-26 06:35:31 +02:00
2020-04-11 08:45:06 +02:00
err = m.LoadMigrations(path.Join(conf.cpath, conf.MigrationsPath))
2019-09-26 06:35:31 +02:00
if err != nil {
log.Fatalf("ERR failed to load migrations: %s", err)
2019-09-26 06:35:31 +02:00
}
if len(m.Migrations) == 0 {
log.Fatalf("ERR no migrations found")
2019-09-26 06:35:31 +02:00
}
m.OnStart = func(sequence int32, name, direction, sql string) {
log.Printf("INF %s executing %s %s\n%s\n\n",
2019-09-26 06:35:31 +02:00
time.Now().Format("2006-01-02 15:04:05"), name, direction, sql)
}
var currentVersion int32
currentVersion, err = m.GetCurrentVersion()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to get current version:\n %v\n", err)
os.Exit(1)
}
mustParseDestination := func(d string) int32 {
var n int64
n, err = strconv.ParseInt(d, 10, 32)
if err != nil {
log.Fatalf("ERR invalid destination: %s", err)
2019-09-26 06:35:31 +02:00
}
return int32(n)
}
2019-09-28 17:34:03 +02:00
if dest == "up" {
2019-09-26 06:35:31 +02:00
err = m.Migrate()
2019-09-28 17:34:03 +02:00
} else if dest == "down" {
err = m.MigrateTo(currentVersion - 1)
2019-09-26 06:35:31 +02:00
} else if len(dest) >= 3 && dest[0:2] == "-+" {
err = m.MigrateTo(currentVersion - mustParseDestination(dest[2:]))
if err == nil {
err = m.MigrateTo(currentVersion)
}
} else if len(dest) >= 2 && dest[0] == '-' {
err = m.MigrateTo(currentVersion - mustParseDestination(dest[1:]))
} else if len(dest) >= 2 && dest[0] == '+' {
err = m.MigrateTo(currentVersion + mustParseDestination(dest[1:]))
} else {
cmd.Help() //nolint: errcheck
2019-09-28 17:34:03 +02:00
os.Exit(1)
2019-09-26 06:35:31 +02:00
}
if err != nil {
log.Fatalf("ERR %s", err)
2019-09-26 06:35:31 +02:00
// if err, ok := err.(m.MigrationPgError); ok {
// if err.Detail != "" {
// log.Fatalf("ERR %s", err.Detail)
2019-09-26 06:35:31 +02:00
// }
// if err.Position != 0 {
// ele, err := ExtractErrorLine(err.Sql, int(err.Position))
// if err != nil {
// log.Fatalf("ERR %s", err)
2019-09-26 06:35:31 +02:00
// }
// log.Fatalf("INF line %d, %s%s", ele.LineNum, ele.Text)
2019-09-26 06:35:31 +02:00
// }
2019-09-26 06:35:31 +02:00
// }
}
log.Println("INF migration done")
2019-09-26 06:35:31 +02:00
}
2019-09-28 17:34:03 +02:00
func cmdDBStatus(cmd *cobra.Command, args []string) {
initConfOnce()
2020-04-13 06:43:18 +02:00
db, err := initDB(conf, true)
2019-09-26 06:35:31 +02:00
if err != nil {
log.Fatalf("ERR failed to connect to database: %s", err)
2019-09-26 06:35:31 +02:00
}
defer db.Close()
2019-09-26 06:35:31 +02:00
m, err := migrate.NewMigrator(db, "schema_version")
2019-09-26 06:35:31 +02:00
if err != nil {
log.Fatalf("ERR failed to initialize migrator: %s", err)
2019-09-26 06:35:31 +02:00
}
2019-09-28 17:34:03 +02:00
m.Data = getMigrationVars()
2019-09-26 06:35:31 +02:00
err = m.LoadMigrations(conf.MigrationsPath)
if err != nil {
log.Fatalf("ERR failed to load migrations: %s", err)
2019-09-26 06:35:31 +02:00
}
if len(m.Migrations) == 0 {
log.Fatalf("ERR no migrations found")
2019-09-26 06:35:31 +02:00
}
mver, err := m.GetCurrentVersion()
if err != nil {
log.Fatalf("ERR failed to retrieve migration: %s", err)
2019-09-26 06:35:31 +02:00
}
var status string
behindCount := len(m.Migrations) - int(mver)
if behindCount == 0 {
status = "up to date"
} else {
status = "migration(s) pending"
}
log.Printf("INF status: %s, version: %d of %d, host: %s, database: %s",
status, mver, len(m.Migrations), conf.DB.Host, conf.DB.DBName)
2019-09-26 06:35:31 +02:00
}
type ErrorLineExtract struct {
LineNum int // Line number starting with 1
ColumnNum int // Column number starting with 1
Text string // Text of the line without a new line character.
}
// ExtractErrorLine takes source and character position extracts the line
// number, column number, and the line of text.
//
// The first character is position 1.
func ExtractErrorLine(source string, position int) (ErrorLineExtract, error) {
ele := ErrorLineExtract{LineNum: 1}
if position > len(source) {
return ele, fmt.Errorf("position (%d) is greater than source length (%d)", position, len(source))
}
lines := strings.SplitAfter(source, "\n")
for _, ele.Text = range lines {
if position-len(ele.Text) < 1 {
ele.ColumnNum = position
break
}
ele.LineNum += 1
position -= len(ele.Text)
}
ele.Text = strings.TrimSuffix(ele.Text, "\n")
return ele, nil
}
2019-09-28 17:34:03 +02:00
func getMigrationVars() map[string]interface{} {
return map[string]interface{}{
"app_name": strings.Title(conf.AppName),
"app_name_slug": strings.ToLower(strings.Replace(conf.AppName, " ", "_", -1)),
"env": strings.ToLower(os.Getenv("GO_ENV")),
}
}
func initConfOnce() {
var err error
if conf != nil {
return
}
conf, err = initConf()
if err != nil {
log.Fatalf("ERR failed to read config: %s", err)
}
}