Added proper db migrations

This commit is contained in:
Jan Zípek 2022-08-23 21:26:42 +02:00
parent 54497370ce
commit d381528db1
Signed by: kamen
GPG Key ID: A17882625B33AC31
5 changed files with 179 additions and 45 deletions

View File

@ -1,44 +0,0 @@
package app
import "database/sql"
func initializeDb(databaseUrl string) *sql.DB {
db, err := sql.Open("sqlite3", databaseUrl)
if err != nil {
panic(err)
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS sensor_values (
timestamp INTEGER NOT NULL,
sensor TEXT NOT NULL,
value REAL NOT NULL
);`)
if err != nil {
panic(err)
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS sensor_config (
sensor TEXT NOT NULL,
key TEXT NOT NULL,
value REAL NOT NULL,
PRIMARY KEY (sensor, key)
);`)
if err != nil {
panic(err)
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS sessions (
id TEXT NOT NULL,
expires_at INTEGER NOT NULL,
PRIMARY KEY (id)
);`)
if err != nil {
panic(err)
}
return db
}

View File

@ -2,6 +2,7 @@ package app
import ( import (
"basic-sensor-receiver/config" "basic-sensor-receiver/config"
"basic-sensor-receiver/database"
"basic-sensor-receiver/services" "basic-sensor-receiver/services"
"database/sql" "database/sql"
) )
@ -15,7 +16,14 @@ type Server struct {
func InitializeServer() *Server { func InitializeServer() *Server {
server := Server{} server := Server{}
server.Config = config.LoadConfig() server.Config = config.LoadConfig()
server.DB = initializeDb(server.Config.DatabaseUrl)
db, err := database.Initialize(server.Config.DatabaseUrl)
if err != nil {
panic(err)
}
server.DB = db
ctx := services.Context{DB: server.DB, Config: server.Config} ctx := services.Context{DB: server.DB, Config: server.Config}

View File

@ -0,0 +1,52 @@
package database
import (
"database/sql"
"fmt"
"time"
)
func Initialize(databaseUrl string) (*sql.DB, error) {
db, err := sql.Open("sqlite3", databaseUrl)
if err != nil {
return nil, fmt.Errorf("failed to open database connection: %w", err)
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS migrations (
id TEXT NOT NULL,
run_at INTEGER NOT NULL
);`)
if err != nil {
return nil, fmt.Errorf("failed to initialize migrations table: %w", err)
}
pendingMigrations, err := getPendingMigrations(db)
if err != nil {
return nil, fmt.Errorf("failed to get pending migrations: %w", err)
}
for _, migrationId := range pendingMigrations {
migrationSource, err := getMigrationContent(migrationId)
if err != nil {
return nil, err
}
_, err = db.Exec(migrationSource)
if err != nil {
return nil, fmt.Errorf("failed to run migration %s: %w", migrationId, err)
}
_, err = db.Exec("INSERT INTO migrations (id, run_at) VALUES (?, ?)", migrationId, time.Now().Unix())
if err != nil {
return nil, fmt.Errorf("failed to store run migration: %w", err)
}
}
return db, nil
}

View File

@ -0,0 +1,100 @@
package database
import (
"database/sql"
"embed"
"fmt"
"io/fs"
"sort"
"strings"
)
//go:embed migrations/*.sql
var migrationStorage embed.FS
func getAppliedMigrationsSet(db *sql.DB) (map[string]bool, error) {
set := make(map[string]bool)
rows, err := db.Query("SELECT id FROM migrations")
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var id string
err := rows.Scan(&id)
if err != nil {
return nil, err
}
set[id] = true
}
err = rows.Err()
if err != nil {
return nil, err
}
return set, nil
}
func getMigrations() ([]string, error) {
var items []string
entries, err := migrationStorage.ReadDir("migrations")
if err != nil {
return nil, err
}
for _, entry := range entries {
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".sql") {
items = append(items, strings.TrimSuffix(entry.Name(), ".sql"))
}
}
if err != nil {
return nil, err
}
sort.Strings(items)
return items, nil
}
func getMigrationContent(id string) (string, error) {
content, err := fs.ReadFile(migrationStorage, "migrations/"+id+".sql")
if err != nil {
return "", err
}
return string(content), nil
}
func getPendingMigrations(db *sql.DB) ([]string, error) {
var migrationsToRun []string
migrationSet, err := getAppliedMigrationsSet(db)
if err != nil {
return nil, err
}
storedMigrations, err := getMigrations()
if err != nil {
return nil, fmt.Errorf("failed to load stored migrations: %w", err)
}
for _, storedMigration := range storedMigrations {
if !migrationSet[storedMigration] {
migrationsToRun = append(migrationsToRun, storedMigration)
}
}
return migrationsToRun, nil
}

View File

@ -0,0 +1,18 @@
CREATE TABLE IF NOT EXISTS sensor_values (
timestamp INTEGER NOT NULL,
sensor TEXT NOT NULL,
value REAL NOT NULL
);
CREATE TABLE IF NOT EXISTS sensor_config (
sensor TEXT NOT NULL,
key TEXT NOT NULL,
value REAL NOT NULL,
PRIMARY KEY (sensor, key)
);
CREATE TABLE IF NOT EXISTS sessions (
id TEXT NOT NULL,
expires_at INTEGER NOT NULL,
PRIMARY KEY (id)
);