package main import ( "log" "fmt" "path/filepath" "io" "os" "os/signal" "os/user" "os/exec" "strconv" "syscall" "database/sql" _ "github.com/go-sql-driver/mysql" ) const databaseSchemaVersion int = 2 // this defines the needed version for the // executable func setupDatabase() *sql.DB { u,err := user.Current() if err != nil { log.Fatal(err) } target := fmt.Sprintf("%s@unix(%s)/%s", u.Username, config.Database.Socket, config.Database.Database) db,err := sql.Open("mysql", target) if err != nil { log.Fatal(err) } err = db.Ping() if err != nil { log.Fatal(err) } migrate(db) // allow graceful shutdown var listener = make(chan os.Signal) signal.Notify(listener, syscall.SIGTERM) signal.Notify(listener, syscall.SIGINT) go func() { signal := <-listener log.Printf("\nGot signal '%+v'. Shutting down ...\n", signal) dbCleanup(db) os.Exit(0) // TODO this does not belong to a database - write utils file 'shutdown.go' }() return db } func migrate(db *sql.DB) { const t = databaseSchemaVersion // targeted database schema version for { v := schemaVersion(db) // read schema version from DB table // handle current database schema which is newer than targeted one if v > t { log.Fatalf( "Current database schema version is %d but newest is %d!", v, t) } // break if targeted version is already reached if v == t { break } // execute migration log.Printf("Starting database schema migration to version %d.\n", v+1) path := filepath.Join(config.Database.Migrations, fmt.Sprintf("%04d_migration.sql", v+1)) RunSqlScript(path) log.Printf("Finished database schema migration to version %d.\n", v+1) } } func RunSqlScript(path string) { script, err := os.Open(path) if err != nil { log.Fatalf("Could not open SQL script '%s'!\n", path) } cmd := exec.Command("mariadb") stdin, err := cmd.StdinPipe() if err != nil { log.Fatalf("Could not open stdin of mariadb process!\n%v", err) } err = cmd.Start() if err != nil { log.Fatalf("Could not start mariadb process!\n%v", err) } io.Copy(stdin, script) stdin.Close() err = cmd.Wait() if err != nil { log.Fatalf("Failed to wait for SQL script to finish!\n%v", err) } } func schemaVersion(db *sql.DB) int { // ask database for schema version cmd := "SELECT value FROM meta WHERE (identifier='version');" rows, err := db.Query(cmd) // handle missing meta table if err != nil { log.Fatal(err) } // handle successful schema version query defer rows.Close() rows.Next() var version string err = rows.Scan(&version) // handle missing version field in meta table if err != nil { log.Fatal(err) } // convert to integer and handle error v, err := strconv.Atoi(version) if err != nil { log.Fatalf("Could not convert database schema version '%s' to int.\n", version) } return v } func dbCleanup(db *sql.DB) { err := db.Close() if err != nil { log.Println("Could not close database connection") } else { log.Println("Closed database connection") } }