package db import ( "context" "database/sql" "fmt" "os" "path/filepath" "sort" "strings" "github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/stdlib" "github.com/pressly/goose/v3" ) const schemaTable = "goose_db_version" // RunMigrations 执行 goose 迁移链。 func RunMigrations(ctx context.Context, pool *pgxpool.Pool, migrationsDir string) error { if pool == nil { return fmt.Errorf("db migrate: pool required") } absDir, err := filepath.Abs(migrationsDir) if err != nil { return err } if _, err := os.Stat(absDir); err != nil { return fmt.Errorf("db migrate: directory %s: %w", absDir, err) } db := stdlib.OpenDB(*pool.Config().ConnConfig) defer db.Close() if err := goose.SetDialect("postgres"); err != nil { return err } goose.SetTableName(schemaTable) if err := goose.UpContext(ctx, db, absDir); err != nil { return fmt.Errorf("db migrate up: %w", err) } return nil } // CurrentVersion 返回当前 schema 版本号。 func CurrentVersion(ctx context.Context, pool *pgxpool.Pool) (int64, error) { if pool == nil { return 0, fmt.Errorf("db migrate: pool required") } var version int64 err := pool.QueryRow(ctx, fmt.Sprintf(` SELECT COALESCE(MAX(version_id), 0)::bigint FROM %s `, schemaTable)).Scan(&version) if err != nil { // 表不存在时视为未迁移 if strings.Contains(err.Error(), "does not exist") { return 0, nil } return 0, err } return version, nil } // ExpectedMigrationCount 统计 migrations 目录下 .sql 文件数量。 func ExpectedMigrationCount(migrationsDir string) (int, error) { entries, err := os.ReadDir(migrationsDir) if err != nil { return 0, err } count := 0 for _, e := range entries { if !e.IsDir() && strings.HasSuffix(e.Name(), ".sql") { count++ } } return count, nil } // ResolveMigrationsDir 定位 goose 迁移目录。 func ResolveMigrationsDir() string { candidates := []string{ "deploy/postgres/goose", "../deploy/postgres/goose", "../../deploy/postgres/goose", } for _, c := range candidates { if fi, err := os.Stat(c); err == nil && fi.IsDir() { return c } } return "deploy/postgres/goose" } // OpenSQLDB 从 pgx pool 打开 database/sql(测试辅助)。 func OpenSQLDB(pool *pgxpool.Pool) *sql.DB { return stdlib.OpenDB(*pool.Config().ConnConfig) } // ListMigrationFiles 按名称排序列出迁移文件(测试辅助)。 func ListMigrationFiles(dir string) ([]string, error) { entries, err := os.ReadDir(dir) if err != nil { return nil, err } var files []string for _, e := range entries { if !e.IsDir() && strings.HasSuffix(e.Name(), ".sql") { files = append(files, e.Name()) } } sort.Strings(files) return files, nil }