alt/services/worker/internal/storage/postgres/migrate.go
toki f762d2abd9 feat: worker persistence layer and socket session updates
- Add worker storage layer with PostgreSQL/sqlc setup
- Add migration files for worker backbone schema
- Add job runner and built-in jobs implementation
- Add Redis key definitions for worker state management
- Archive socket-session-loop milestone (completed/renamed)
- Update roadmap current.md and foundation-alignment phase
- Add socket endpoint integration and runtime smoke tests
- Add infra-check, worker-storage-check, worker-storage-gen CLI tools
- Update API socket server and config
- Add pubspec dependencies and client socket endpoint changes
- Add config tests for API and worker services
2026-05-28 19:05:10 +09:00

99 lines
2.6 KiB
Go

package postgres
import (
"context"
"embed"
"fmt"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/jackc/pgx/v5"
)
//go:embed migrations/*.sql
var migrationsFS embed.FS
// RunMigrations runs all embedded migrations in order.
func RunMigrations(ctx context.Context, conn *pgx.Conn) error {
// Create schema_migrations table if not exists
_, err := conn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS schema_migrations (
version BIGINT PRIMARY KEY,
applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
`)
if err != nil {
return fmt.Errorf("failed to create schema_migrations table: %w", err)
}
entries, err := migrationsFS.ReadDir("migrations")
if err != nil {
return fmt.Errorf("failed to read migrations directory: %w", err)
}
var migrationFiles []string
for _, entry := range entries {
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".up.sql") {
migrationFiles = append(migrationFiles, entry.Name())
}
}
sort.Strings(migrationFiles)
if len(migrationFiles) == 0 {
return fmt.Errorf("no up migration files found")
}
for _, filename := range migrationFiles {
parts := strings.Split(filename, "_")
if len(parts) == 0 {
return fmt.Errorf("invalid migration filename: %s", filename)
}
version, err := strconv.ParseInt(parts[0], 10, 64)
if err != nil {
return fmt.Errorf("failed to parse migration version from filename %s: %w", filename, err)
}
// Check if version is already applied
var exists bool
err = conn.QueryRow(ctx, "SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE version = $1)", version).Scan(&exists)
if err != nil {
return fmt.Errorf("failed to check migration status for version %d: %w", version, err)
}
if exists {
continue
}
// Read migration content
content, err := migrationsFS.ReadFile(filepath.Join("migrations", filename))
if err != nil {
return fmt.Errorf("failed to read migration file %s: %w", filename, err)
}
// Run migration within transaction
tx, err := conn.Begin(ctx)
if err != nil {
return fmt.Errorf("failed to begin transaction for migration %s: %w", filename, err)
}
defer tx.Rollback(ctx)
_, err = tx.Exec(ctx, string(content))
if err != nil {
return fmt.Errorf("failed to execute migration %s: %w", filename, err)
}
_, err = tx.Exec(ctx, "INSERT INTO schema_migrations (version) VALUES ($1)", version)
if err != nil {
return fmt.Errorf("failed to record migration version %d: %w", version, err)
}
err = tx.Commit(ctx)
if err != nil {
return fmt.Errorf("failed to commit transaction for migration %s: %w", filename, err)
}
}
return nil
}