diff options
Diffstat (limited to 'database.go')
-rw-r--r-- | database.go | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/database.go b/database.go new file mode 100644 index 0000000..20ec5a6 --- /dev/null +++ b/database.go @@ -0,0 +1,175 @@ + +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 + +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") + } +} |