Skip to content

Commit 78ec11b

Browse files
committed
feat: add check command to check pending migrations
The check command checks for pending migrations and returns an error if there are any. This allows a user to use it in a script where the user doesn't want to run the migrations but still ensure that there are none pending before continuing. For example, when using a Kubernetes Job to perform migrations while a Deployment runs the service itself, a user may want to prevent the Deployment from starting fully before the migrations have completed.
1 parent c378583 commit 78ec11b

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

internal/cli/commands.go

+15
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var (
1818
errInvalidSequenceWidth = errors.New("Digits must be positive")
1919
errIncompatibleSeqAndFormat = errors.New("The seq and format options are mutually exclusive")
2020
errInvalidTimeFormat = errors.New("Time format may not be empty")
21+
errPendingMigrations = errors.New("There are pending migrations")
2122
)
2223

2324
func nextSeqVersion(matches []string, seqDigits int) (string, error) {
@@ -222,6 +223,20 @@ func versionCmd(m *migrate.Migrate) error {
222223
return nil
223224
}
224225

226+
func checkCmd(m *migrate.Migrate) error {
227+
pending, err := m.Check()
228+
if err != nil {
229+
return err
230+
}
231+
232+
log.Printf("%v pending migration(s)\n", pending)
233+
if pending > 0 {
234+
return errPendingMigrations
235+
}
236+
237+
return nil
238+
}
239+
225240
// numDownMigrationsFromArgs returns an int for number of migrations to apply
226241
// and a bool indicating if we need a confirm before applying
227242
func numDownMigrationsFromArgs(applyAll bool, args []string) (int, bool, error) {

internal/cli/main.go

+10
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Commands:
9292
%s
9393
%s
9494
version Print current migration version
95+
check Check if database has pending migrations
9596
9697
Source drivers: `+strings.Join(source.List(), ", ")+`
9798
Database drivers: `+strings.Join(database.List(), ", ")+"\n", createUsage, gotoUsage, upUsage, downUsage, dropUsage, forceUsage)
@@ -371,6 +372,15 @@ Database drivers: `+strings.Join(database.List(), ", ")+"\n", createUsage, gotoU
371372
log.fatalErr(err)
372373
}
373374

375+
case "check":
376+
if migraterErr != nil {
377+
log.fatalErr(migraterErr)
378+
}
379+
380+
if err := checkCmd(migrater); err != nil {
381+
log.fatalErr(err)
382+
}
383+
374384
default:
375385
printUsageAndExit()
376386
}

migrate.go

+38
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,44 @@ func (m *Migrate) Version() (version uint, dirty bool, err error) {
395395
return suint(v), d, nil
396396
}
397397

398+
// Check returns number of pending migrations.
399+
func (m *Migrate) Check() (pending int, err error) {
400+
// Disable prefetch as we'll only be checking and not applying migrations.
401+
m.PrefetchMigrations = 0
402+
403+
curVersion, dirty, err := m.databaseDrv.Version()
404+
if err != nil {
405+
return 0, err
406+
}
407+
408+
if dirty {
409+
return 0, ErrDirty{curVersion}
410+
}
411+
412+
pending = 0
413+
414+
ret := make(chan interface{}, m.PrefetchMigrations)
415+
go m.readUp(curVersion, -1, ret)
416+
417+
for r := range ret {
418+
if m.stop() {
419+
break
420+
}
421+
422+
switch r := r.(type) {
423+
case error:
424+
if r != ErrNoChange {
425+
return 0, r
426+
}
427+
428+
case *Migration:
429+
pending += 1
430+
}
431+
}
432+
433+
return pending, nil
434+
}
435+
398436
// read reads either up or down migrations from source `from` to `to`.
399437
// Each migration is then written to the ret channel.
400438
// If an error occurs during reading, that error is written to the ret channel, too.

0 commit comments

Comments
 (0)