U10a 首切片:goose 迁移内嵌回退(单文件部署去 cwd 依赖第一步) #9

Merged
weichen merged 1 commits from feature/u10a-embedded-migrations-2026-09-29 into dev 2026-09-29 07:13:46 +00:00
3 changed files with 48 additions and 3 deletions
+14
View File
@@ -0,0 +1,14 @@
// Package dbassets 内嵌数据库迁移链(U10a:单文件应用无源码目录时回退使用)。
// 磁盘目录存在时仍优先磁盘(开发态行为不变);磁盘与嵌入的一致性由
// 迁移门禁测试(ExpectedMigrationCount)约束。
package dbassets
import "embed"
// MigrationsFS 内嵌 deploy/postgres/goose 迁移链。
//
//go:embed all:goose
var MigrationsFS embed.FS
// MigrationsDir 是嵌入 FS 内的迁移子目录名(goose SetBaseFS 的根)。
const MigrationsDir = "goose"
+16 -3
View File
@@ -9,6 +9,7 @@ import (
"sort"
"strings"
"chat-agent-system/deploy/postgres"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/pgx/v5/stdlib"
"github.com/pressly/goose/v3"
@@ -25,8 +26,9 @@ func RunMigrations(ctx context.Context, pool *pgxpool.Pool, migrationsDir string
if err != nil {
return err
}
useDisk := true
if _, err := os.Stat(absDir); err != nil {
return fmt.Errorf("db migrate: directory %s: %w", absDir, err)
useDisk = false
}
db := stdlib.OpenDB(*pool.Config().ConnConfig)
@@ -37,8 +39,19 @@ func RunMigrations(ctx context.Context, pool *pgxpool.Pool, migrationsDir string
}
goose.SetTableName(schemaTable)
if err := goose.UpContext(ctx, db, absDir); err != nil {
return fmt.Errorf("db migrate up: %w", err)
if useDisk {
if err := goose.UpContext(ctx, db, absDir); err != nil {
return fmt.Errorf("db migrate up: %w", err)
}
return nil
}
// U10a:源码目录不存在(单文件部署)时回退内嵌迁移链。
// goose SetBaseFS 是全局状态;用后复位,避免污染后续磁盘调用。
goose.SetBaseFS(dbassets.MigrationsFS)
defer goose.SetBaseFS(nil)
if err := goose.UpContext(ctx, db, dbassets.MigrationsDir); err != nil {
return fmt.Errorf("db migrate up (embedded): %w", err)
}
return nil
}
+18
View File
@@ -278,3 +278,21 @@ func rewriteDatabaseName(dsn, name string) string {
}
return dsn
}
func TestRunMigrationsEmbeddedFallback(t *testing.T) {
dsn := os.Getenv("TEST_DATABASE_URL")
if dsn == "" {
t.Skip("TEST_DATABASE_URL not set; embedded fallback needs PG")
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, dsn)
if err != nil {
t.Fatalf("connect: %v", err)
}
defer pool.Close()
// 不存在的磁盘目录必须触发内嵌回退并成功走完迁移链(幂等)。
if err := RunMigrations(ctx, pool, filepath.Join(t.TempDir(), "no-such-dir")); err != nil {
t.Fatalf("embedded fallback migrations: %v", err)
}
}