Go · 2554 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package db
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9 "io/fs"
10
11 "github.com/jackc/pgx/v5"
12 "github.com/jackc/pgx/v5/stdlib"
13 "github.com/pressly/goose/v3"
14 )
15
16 // MigrationsFS holds the embedded migrations. The web/cmd packages set this
17 // at init time via SetMigrationsFS(); we don't embed here because embed
18 // directives can't traverse upward to the migrations/ directory at the repo
19 // root.
20 var migrationsFS fs.FS
21
22 // SetMigrationsFS registers the migrations filesystem. The repo's
23 // `internal/migrationsfs` package embeds and registers it; tests can swap
24 // it for a fixture FS.
25 func SetMigrationsFS(fsys fs.FS) {
26 migrationsFS = fsys
27 }
28
29 // MigrateAction is one of the migrate operations the CLI exposes.
30 type MigrateAction string
31
32 const (
33 MigrateUp MigrateAction = "up"
34 MigrateDown MigrateAction = "down"
35 MigrateStatus MigrateAction = "status"
36 MigrateVersion MigrateAction = "version"
37 MigrateRedo MigrateAction = "redo"
38 MigrateReset MigrateAction = "reset"
39 )
40
41 // Migrate runs the requested goose action against the pool's underlying
42 // database. We reuse the pool's connection config rather than opening a
43 // fresh sql.DB from scratch, so the same DSN env-var-driven configuration
44 // applies to migrations.
45 //
46 // goose requires `database/sql`. We bridge from pgx's connection config
47 // using `pgx/v5/stdlib`.
48 func Migrate(ctx context.Context, cfg Config, action MigrateAction) error {
49 if migrationsFS == nil {
50 return errors.New("db: migrationsFS not registered (call SetMigrationsFS)")
51 }
52 cfg = cfg.Resolve()
53 if cfg.URL == "" {
54 return ErrNoURL
55 }
56
57 connCfg, err := pgx.ParseConfig(cfg.URL)
58 if err != nil {
59 return fmt.Errorf("db: parse migrate URL: %w", err)
60 }
61 sqldb := stdlib.OpenDB(*connCfg)
62 defer func() { _ = sqldb.Close() }()
63
64 if err := goose.SetDialect("postgres"); err != nil {
65 return fmt.Errorf("db: goose dialect: %w", err)
66 }
67 goose.SetBaseFS(migrationsFS)
68
69 switch action {
70 case MigrateUp:
71 return goose.UpContext(ctx, sqldb, ".")
72 case MigrateDown:
73 return goose.DownContext(ctx, sqldb, ".")
74 case MigrateStatus:
75 return goose.StatusContext(ctx, sqldb, ".")
76 case MigrateVersion:
77 v, err := goose.GetDBVersionContext(ctx, sqldb)
78 if err != nil {
79 return fmt.Errorf("db: version: %w", err)
80 }
81 fmt.Printf("schema version: %d\n", v)
82 return nil
83 case MigrateRedo:
84 return goose.RedoContext(ctx, sqldb, ".")
85 case MigrateReset:
86 return goose.ResetContext(ctx, sqldb, ".")
87 default:
88 return fmt.Errorf("db: unknown migrate action %q", action)
89 }
90 }
91