package model import ( "database/sql" "log" _ "github.com/mattn/go-sqlite3" "xengineering.eu/ceres/model/migrations" ) type DB sql.DB func OpenDB(path string) *DB { db, err := sql.Open("sqlite3", path) if err != nil { log.Fatal(err) } err = db.Ping() if err != nil { log.Fatal(err) } return (*DB)(db) } func (db *DB) Transaction(f func(*sql.Tx) error) error { tx, err := (*sql.DB)(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 (db *DB) IsEmpty() bool { var number int _ = db.Transaction(func(tx *sql.Tx) error { 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") } err = rows.Scan(&number) if err != nil { log.Fatalf("Failed to scan number of database tables to integer") } return nil }) return number == 0 } func (db *DB) setupMinimal(execVersion string) error { return db.Transaction(func(tx *sql.Tx) 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 (db *DB) Version() string { var version string _ = db.Transaction(func(tx *sql.Tx) error { 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") } err = rows.Scan(&version) if err != nil { log.Fatalf("Failed to scan database version to string") } return nil }) return version } func (db *DB) Migrate(execVersion string) { err := db.Transaction(func(tx *sql.Tx) error { if db.IsEmpty() { log.Println("Starting with empty database") err := db.setupMinimal(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 := db.Version() 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 (db *DB) CreateExamples() { err := db.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 (db *DB) Close() { err := (*sql.DB)(db).Close() if err != nil { log.Printf("Failed to close database: %v\n", err) } else { log.Println("Closed database") } }