feat(db): embedded goose migrations fallback (U10a first slice)
- deploy/postgres (package dbassets) embeds the full goose chain; RunMigrations prefers the on-disk dir (dev behavior unchanged) and falls back to the embedded FS when the source tree is absent (single-binary deployments per plan U10/KTD16) - goose SetBaseFS global state reset after use - TestRunMigrationsEmbeddedFallback verified against a throwaway isolated pgvector container: nonexistent dir -> embedded chain -> version 78, migration gate count agrees
This commit is contained in:
+16
-3
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user