1package goose
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7)
8
9// Down rolls back a single migration from the current version.
10func Down(db *sql.DB, dir string, opts ...OptionsFunc) error {
11 ctx := context.Background()
12 return DownContext(ctx, db, dir, opts...)
13}
14
15// DownContext rolls back a single migration from the current version.
16func DownContext(ctx context.Context, db *sql.DB, dir string, opts ...OptionsFunc) error {
17 option := &options{}
18 for _, f := range opts {
19 f(option)
20 }
21 migrations, err := CollectMigrations(dir, minVersion, maxVersion)
22 if err != nil {
23 return err
24 }
25 if option.noVersioning {
26 if len(migrations) == 0 {
27 return nil
28 }
29 currentVersion := migrations[len(migrations)-1].Version
30 // Migrate only the latest migration down.
31 return downToNoVersioning(ctx, db, migrations, currentVersion-1)
32 }
33 currentVersion, err := GetDBVersionContext(ctx, db)
34 if err != nil {
35 return err
36 }
37 current, err := migrations.Current(currentVersion)
38 if err != nil {
39 return fmt.Errorf("migration %v: %w", currentVersion, err)
40 }
41 return current.DownContext(ctx, db)
42}
43
44// DownTo rolls back migrations to a specific version.
45func DownTo(db *sql.DB, dir string, version int64, opts ...OptionsFunc) error {
46 ctx := context.Background()
47 return DownToContext(ctx, db, dir, version, opts...)
48}
49
50// DownToContext rolls back migrations to a specific version.
51func DownToContext(ctx context.Context, db *sql.DB, dir string, version int64, opts ...OptionsFunc) error {
52 option := &options{}
53 for _, f := range opts {
54 f(option)
55 }
56 migrations, err := CollectMigrations(dir, minVersion, maxVersion)
57 if err != nil {
58 return err
59 }
60 if option.noVersioning {
61 return downToNoVersioning(ctx, db, migrations, version)
62 }
63
64 for {
65 currentVersion, err := GetDBVersionContext(ctx, db)
66 if err != nil {
67 return err
68 }
69
70 if currentVersion == 0 {
71 log.Printf("goose: no migrations to run. current version: %d", currentVersion)
72 return nil
73 }
74 current, err := migrations.Current(currentVersion)
75 if err != nil {
76 log.Printf("goose: migration file not found for current version (%d), error: %s", currentVersion, err)
77 return err
78 }
79
80 if current.Version <= version {
81 log.Printf("goose: no migrations to run. current version: %d", currentVersion)
82 return nil
83 }
84
85 if err = current.DownContext(ctx, db); err != nil {
86 return err
87 }
88 }
89}
90
91// downToNoVersioning applies down migrations down to, but not including, the
92// target version.
93func downToNoVersioning(ctx context.Context, db *sql.DB, migrations Migrations, version int64) error {
94 var finalVersion int64
95 for i := len(migrations) - 1; i >= 0; i-- {
96 if version >= migrations[i].Version {
97 finalVersion = migrations[i].Version
98 break
99 }
100 migrations[i].noVersioning = true
101 if err := migrations[i].DownContext(ctx, db); err != nil {
102 return err
103 }
104 }
105 log.Printf("goose: down to current file version: %d", finalVersion)
106 return nil
107}