package utils 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 type Database struct { config DatabaseConfig target string Backend *sql.DB } func InitDatabase(config DatabaseConfig) Database { db := NewDatabase(config) db.Connect() db.Ping() db.Migrate(config.Migrations) // 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) db.Cleanup() os.Exit(0) // TODO this does not belong to a database - write utils file 'shutdown.go' }() return db } func NewDatabase(config DatabaseConfig) Database { db := Database{} db.config = config var username string user_ptr,err := user.Current() if err != nil { log.Fatal(err) } username = user_ptr.Username db.target = fmt.Sprintf("%s@unix(%s)/%s", username, config.Socket, config.Database) return db } func (db *Database) Connect() { var err error db.Backend,err = sql.Open("mysql", db.target) if err != nil { log.Fatal(err) } log.Printf("Connected to database '%s'\n", db.target) } func (db *Database) Ping() { err := db.Backend.Ping() if err != nil { log.Fatal(err) } else { log.Println("Database is responding") } } func (db *Database) Migrate(dir string) { const t = databaseSchemaVersion // targeted database schema version for { v := db.SchemaVersion() // 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(dir, fmt.Sprintf("%04d_migration.sql", v+1)) RunSql(path) log.Printf("Finished database schema migration to version %d.\n", v+1) } } func RunSql(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 (db *Database) SchemaVersion() int { // ask database for schema version cmd := "SELECT value FROM meta WHERE (identifier='version');" rows, err := db.Backend.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 (db *Database) Cleanup() { err := db.Backend.Close() if err != nil { log.Println("Could not close database connection") } else { log.Println("Closed database connection") } }