package model import ( "database/sql" "log" _ "github.com/mattn/go-sqlite3" "xengineering.eu/ceres/model/migrations" ) var db *sql.DB func ConnectDatabase(path string) { var err error db, err = sql.Open("sqlite3", path) if err != nil { log.Fatal(err) } err = db.Ping() if err != nil { log.Fatal(err) } } func Transaction(f func(*sql.Tx) error) error { tx, err := db.Begin() if err != nil { log.Printf("Failed to start database transaction: %v", err) return err } defer func() { if tx.Rollback() == nil { log.Println("Rolled back transaction") } }() err = f(tx) if err != nil { log.Printf("Failed transaction: %v", err) return err } return tx.Commit() } func isDatabaseEmpty(tx *sql.Tx) bool { cmd := `SELECT COUNT(*) FROM sqlite_master WHERE type='table'` rows, err := tx.Query(cmd) if err != nil { log.Fatal(err) } defer rows.Close() if !rows.Next() { log.Fatalf("No rows on request of database table number") } var number int err = rows.Scan(&number) if err != nil { log.Fatalf("Failed to scan number of database tables to integer") } return number == 0 } func setupMinimalDatabase(tx *sql.Tx, execVersion string) error { cmd := ` CREATE TABLE metadata ( key TEXT PRIMARY KEY, value TEXT ); INSERT INTO metadata (key, value) VALUES ('version', ?); ` _, err := tx.Exec(cmd, execVersion) return err } func getDatabaseVersion(tx *sql.Tx) string { rows, err := tx.Query(`SELECT value FROM metadata WHERE key='version';`) if err != nil { log.Fatal(err) } defer rows.Close() if !rows.Next() { log.Fatalf("No rows on request of database version") } var version string err = rows.Scan(&version) if err != nil { log.Fatalf("Failed to scan database version to string") } return version } func MigrateDatabase(execVersion string) { err := Transaction(func(tx *sql.Tx) error { if isDatabaseEmpty(tx) { log.Println("Starting with empty database") err := setupMinimalDatabase(tx, execVersion) if err != nil { log.Fatalf("Failed to setup minimal database schema: %v", err) } log.Println("Executing initial migration") err = migrations.Migration001(tx) if err != nil { return err } } dbVersion := getDatabaseVersion(tx) if dbVersion != execVersion { log.Fatalf( "Database version '%s' does not match executable version '%s'", dbVersion, execVersion, ) } return nil }) if err != nil { log.Fatalf("Fatal: Database migration failed") } } func InjectExampleRecipes() { err := Transaction(func(tx *sql.Tx) error { recipes := RecipeTestData() for _, recipe := range recipes { err := recipe.Create(tx) if err != nil { return err } } return nil }) if err != nil { log.Fatalf("Failed to inject example recipes: %v", err) } } func DisconnectDatabase() { var err error = db.Close() if err != nil { log.Printf("Failed to close database: %v\n", err) } else { log.Println("Closed database") } }