2025-01-16 22:53:04 +00:00
|
|
|
package core
|
2025-01-16 19:46:37 +00:00
|
|
|
|
|
|
|
import (
|
2025-01-17 05:11:07 +00:00
|
|
|
"embed"
|
|
|
|
"log"
|
2025-01-16 19:46:37 +00:00
|
|
|
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
)
|
|
|
|
|
2025-01-17 05:11:07 +00:00
|
|
|
//go:embed sql/*.sql
|
|
|
|
var migrations embed.FS
|
|
|
|
|
|
|
|
// Idempotently initialize the database. Safe to call unconditionally.
|
2025-01-20 05:33:49 +00:00
|
|
|
func InitDatabase(db *DB) error {
|
2025-01-17 05:11:07 +00:00
|
|
|
rows, err := db.Query(`
|
|
|
|
select exists (
|
|
|
|
select 1
|
|
|
|
from sqlite_master
|
|
|
|
where type = 'table'
|
|
|
|
and name = 'migrations'
|
|
|
|
)
|
2025-01-16 19:46:37 +00:00
|
|
|
`)
|
2025-01-17 05:11:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2025-01-16 19:46:37 +00:00
|
|
|
|
2025-01-17 05:11:07 +00:00
|
|
|
var exists bool
|
|
|
|
for rows.Next() {
|
|
|
|
rows.Scan(&exists)
|
|
|
|
}
|
|
|
|
|
|
|
|
if exists {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ApplyMigration(db, "0000_baseline.sql")
|
|
|
|
return err
|
2025-01-16 19:46:37 +00:00
|
|
|
}
|
|
|
|
|
2025-01-17 05:30:41 +00:00
|
|
|
// Get a map of migration names to whether the migration has been applied.
|
2025-01-20 05:33:49 +00:00
|
|
|
func GetPendingMigrations(db *DB) (map[string]bool, error) {
|
2025-01-17 05:11:07 +00:00
|
|
|
allMigrations, err := migrations.ReadDir("sql")
|
2025-01-16 19:46:37 +00:00
|
|
|
if err != nil {
|
2025-01-17 05:11:07 +00:00
|
|
|
return nil, err
|
2025-01-16 19:46:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
complete := map[string]bool{}
|
2025-01-17 05:11:07 +00:00
|
|
|
for _, mig := range allMigrations {
|
|
|
|
complete[mig.Name()] = false
|
|
|
|
}
|
2025-01-16 19:46:37 +00:00
|
|
|
|
2025-01-17 05:11:07 +00:00
|
|
|
rows, err := db.Query("select name from migrations")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2025-01-16 19:46:37 +00:00
|
|
|
for rows.Next() {
|
|
|
|
var name string
|
2025-01-17 05:11:07 +00:00
|
|
|
rows.Scan(&name)
|
2025-01-16 19:46:37 +00:00
|
|
|
complete[name] = true
|
|
|
|
}
|
|
|
|
|
2025-01-17 05:30:41 +00:00
|
|
|
return complete, nil
|
2025-01-17 05:11:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Apply a migration by name.
|
2025-01-20 05:33:49 +00:00
|
|
|
func ApplyMigration(db *DB, name string) error {
|
2025-01-17 05:11:07 +00:00
|
|
|
data, err := migrations.ReadFile("sql/" + name)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Missing migration %s", name)
|
|
|
|
}
|
|
|
|
log.Printf("Applying migration %s", name)
|
|
|
|
_, err = db.Exec(string(data))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = db.Exec("insert into migrations (name) values (?)", name)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply all pending migrations.
|
2025-01-20 05:33:49 +00:00
|
|
|
func MigrateDatabase(db *DB) error {
|
2025-01-17 05:11:07 +00:00
|
|
|
pending, err := GetPendingMigrations(db)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2025-01-17 05:30:41 +00:00
|
|
|
for name, complete := range pending {
|
|
|
|
if !complete {
|
|
|
|
err = ApplyMigration(db, name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2025-01-16 19:46:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|