-
Notifications
You must be signed in to change notification settings - Fork 516
/
up.go
221 lines (195 loc) · 6.39 KB
/
up.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package goose
import (
"context"
"database/sql"
"fmt"
"sort"
"strings"
)
type options struct {
allowMissing bool
applyUpByOne bool
noVersioning bool
}
type OptionsFunc func(o *options)
func WithAllowMissing() OptionsFunc {
return func(o *options) { o.allowMissing = true }
}
func WithNoVersioning() OptionsFunc {
return func(o *options) { o.noVersioning = true }
}
func WithNoColor(b bool) OptionsFunc {
return func(o *options) { noColor = b }
}
func withApplyUpByOne() OptionsFunc {
return func(o *options) { o.applyUpByOne = true }
}
// UpTo migrates up to a specific version.
func UpTo(db *sql.DB, dir string, version int64, opts ...OptionsFunc) error {
ctx := context.Background()
return UpToContext(ctx, db, dir, version, opts...)
}
func UpToContext(ctx context.Context, db *sql.DB, dir string, version int64, opts ...OptionsFunc) error {
option := &options{}
for _, f := range opts {
f(option)
}
foundMigrations, err := CollectMigrations(dir, minVersion, version)
if err != nil {
return err
}
if option.noVersioning {
if len(foundMigrations) == 0 {
return nil
}
if option.applyUpByOne {
// For up-by-one this means keep re-applying the first
// migration over and over.
version = foundMigrations[0].Version
}
return upToNoVersioning(ctx, db, foundMigrations, version)
}
if _, err := EnsureDBVersionContext(ctx, db); err != nil {
return err
}
dbMigrations, err := listAllDBVersions(ctx, db)
if err != nil {
return err
}
dbMaxVersion := dbMigrations[len(dbMigrations)-1].Version
// lookupAppliedInDB is a map of all applied migrations in the database.
lookupAppliedInDB := make(map[int64]bool)
for _, m := range dbMigrations {
lookupAppliedInDB[m.Version] = true
}
missingMigrations := findMissingMigrations(dbMigrations, foundMigrations, dbMaxVersion)
// feature(mf): It is very possible someone may want to apply ONLY new migrations
// and skip missing migrations altogether. At the moment this is not supported,
// but leaving this comment because that's where that logic will be handled.
if len(missingMigrations) > 0 && !option.allowMissing {
var collected []string
for _, m := range missingMigrations {
output := fmt.Sprintf("version %d: %s", m.Version, m.Source)
collected = append(collected, output)
}
return fmt.Errorf("error: found %d missing migrations before current version %d:\n\t%s",
len(missingMigrations), dbMaxVersion, strings.Join(collected, "\n\t"))
}
var migrationsToApply Migrations
if option.allowMissing {
migrationsToApply = missingMigrations
}
// filter all migrations with a version greater than the supplied version (min) and less than or
// equal to the requested version (max). Note, we do not need to filter out missing migrations
// because we are only appending "new" migrations that have a higher version than the current
// database max version, which inevitably means they are not "missing".
for _, m := range foundMigrations {
if lookupAppliedInDB[m.Version] {
continue
}
if m.Version > dbMaxVersion && m.Version <= version {
migrationsToApply = append(migrationsToApply, m)
}
}
var current int64
for _, m := range migrationsToApply {
if err := m.UpContext(ctx, db); err != nil {
return err
}
if option.applyUpByOne {
return nil
}
current = m.Version
}
if len(migrationsToApply) == 0 {
current, err = GetDBVersionContext(ctx, db)
if err != nil {
return err
}
log.Printf("goose: no migrations to run. current version: %d\n", current)
} else {
log.Printf("goose: successfully migrated database to version: %d\n", current)
}
// At this point there are no more migrations to apply. But we need to maintain
// the following behaviour:
// UpByOne returns an error to signifying there are no more migrations.
// Up and UpTo return nil
if option.applyUpByOne {
return ErrNoNextVersion
}
return nil
}
// upToNoVersioning applies up migrations up to, and including, the
// target version.
func upToNoVersioning(ctx context.Context, db *sql.DB, migrations Migrations, version int64) error {
var finalVersion int64
for _, current := range migrations {
if current.Version > version {
break
}
current.noVersioning = true
if err := current.UpContext(ctx, db); err != nil {
return err
}
finalVersion = current.Version
}
log.Printf("goose: up to current file version: %d\n", finalVersion)
return nil
}
// Up applies all available migrations.
func Up(db *sql.DB, dir string, opts ...OptionsFunc) error {
ctx := context.Background()
return UpContext(ctx, db, dir, opts...)
}
// UpContext applies all available migrations.
func UpContext(ctx context.Context, db *sql.DB, dir string, opts ...OptionsFunc) error {
return UpToContext(ctx, db, dir, maxVersion, opts...)
}
// UpByOne migrates up by a single version.
func UpByOne(db *sql.DB, dir string, opts ...OptionsFunc) error {
ctx := context.Background()
return UpByOneContext(ctx, db, dir, opts...)
}
// UpByOneContext migrates up by a single version.
func UpByOneContext(ctx context.Context, db *sql.DB, dir string, opts ...OptionsFunc) error {
opts = append(opts, withApplyUpByOne())
return UpToContext(ctx, db, dir, maxVersion, opts...)
}
// listAllDBVersions returns a list of all migrations, ordered ascending.
func listAllDBVersions(ctx context.Context, db *sql.DB) (Migrations, error) {
dbMigrations, err := store.ListMigrations(ctx, db, TableName())
if err != nil {
return nil, err
}
all := make(Migrations, 0, len(dbMigrations))
for _, m := range dbMigrations {
all = append(all, &Migration{
Version: m.VersionID,
})
}
// ListMigrations returns migrations in descending order by id.
// But we want to return them in ascending order by version_id, so we re-sort.
sort.SliceStable(all, func(i, j int) bool {
return all[i].Version < all[j].Version
})
return all, nil
}
// findMissingMigrations migrations returns all missing migrations.
// A migrations is considered missing if it has a version less than the
// current known max version.
func findMissingMigrations(knownMigrations, newMigrations Migrations, dbMaxVersion int64) Migrations {
existing := make(map[int64]bool)
for _, known := range knownMigrations {
existing[known.Version] = true
}
var missing Migrations
for _, new := range newMigrations {
if !existing[new.Version] && new.Version < dbMaxVersion {
missing = append(missing, new)
}
}
sort.SliceStable(missing, func(i, j int) bool {
return missing[i].Version < missing[j].Version
})
return missing
}