package store import ( "context" "database/sql" "embed" "fmt" "sort" "strings" ) //go:embed migrations/*.sql var migrationsFS embed.FS func migrate(db *sql.DB) error { entries, err := migrationsFS.ReadDir("migrations") if err != nil { return fmt.Errorf("read migrations dir: %w", err) } names := make([]string, 0, len(entries)) for _, e := range entries { if !e.IsDir() && strings.HasSuffix(e.Name(), ".sql") { names = append(names, e.Name()) } } sort.Strings(names) if _, err := db.ExecContext(context.Background(), `CREATE TABLE IF NOT EXISTS schema_migrations (name TEXT PRIMARY KEY, applied_at DATETIME NOT NULL)`); err != nil { return fmt.Errorf("create schema_migrations: %w", err) } for _, name := range names { var existing string err := db.QueryRowContext(context.Background(), `SELECT name FROM schema_migrations WHERE name = ?`, name).Scan(&existing) if err == nil { continue } if err != sql.ErrNoRows { return fmt.Errorf("check migration %s: %w", name, err) } sqlBytes, err := migrationsFS.ReadFile("migrations/" + name) if err != nil { return fmt.Errorf("read migration %s: %w", name, err) } if _, err := db.ExecContext(context.Background(), string(sqlBytes)); err != nil { return fmt.Errorf("apply migration %s: %w", name, err) } if _, err := db.ExecContext(context.Background(), `INSERT INTO schema_migrations (name, applied_at) VALUES (?, datetime('now'))`, name); err != nil { return fmt.Errorf("record migration %s: %w", name, err) } } return nil }