summaryrefslogtreecommitdiff
path: root/database.go
diff options
context:
space:
mode:
Diffstat (limited to 'database.go')
-rw-r--r--database.go175
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")
+ }
+}