Files
mal/internal/database/migrate.go

80 lines
1.8 KiB
Go

package database
import (
"database/sql"
"fmt"
"log"
"os"
"path/filepath"
"sort"
"strings"
)
func RunMigrations(db *sql.DB) error {
// Create migration tracking table
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS migration_version (
name TEXT PRIMARY KEY,
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)
`)
if err != nil {
return err
}
migrations, err := filepath.Glob("migrations/*.sql")
if err != nil {
return err
}
sort.Strings(migrations)
for _, migrationFile := range migrations {
// Check if migration already applied
var exists int
err := db.QueryRow("SELECT COUNT(*) FROM migration_version WHERE name = ?", migrationFile).Scan(&exists)
if err != nil {
return err
}
if exists > 0 {
log.Printf("migration %s already applied, skipping", migrationFile)
continue
}
// Read and execute migration
migrationSQL, err := os.ReadFile(migrationFile)
if err != nil {
return err
}
// Split by statement and execute one by one
statements := strings.Split(string(migrationSQL), ";")
for _, stmt := range statements {
stmt = strings.TrimSpace(stmt)
if stmt == "" {
continue
}
if _, err := db.Exec(stmt); err != nil {
errStr := err.Error()
// Safely ignore duplicate columns/tables caused by old manual sqlite3 runs
if strings.Contains(errStr, "duplicate column name") || strings.Contains(errStr, "already exists") {
log.Printf("warning: ignoring expected error in %s: %v", migrationFile, err)
} else {
return fmt.Errorf("failed to execute statement in %s: %v\nStatement: %s", migrationFile, err, stmt)
}
}
}
// Mark as applied
_, err = db.Exec("INSERT INTO migration_version (name) VALUES (?)", migrationFile)
if err != nil {
return err
}
log.Printf("migration %s applied successfully", migrationFile)
}
return nil
}