Skip to content

Commit 0be116c

Browse files
feat(migrate): Add migrate command (#4)
1 parent ded407c commit 0be116c

File tree

7 files changed

+395
-19
lines changed

7 files changed

+395
-19
lines changed

.gometalinter.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
{
22
"Enable": [
33
"deadcode",
4-
"dupl",
5-
"errcheck",
64
"goconst",
75
"gocyclo",
86
"gofmt",

Gopkg.lock

Lines changed: 21 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ setup:
4444
go get -u -v github.com/alecthomas/gometalinter github.com/golang/dep/cmd/dep
4545
gometalinter --install
4646
ifdef PSQL
47-
dropuser --if-exists $(TEST_DATABASE_USER)
4847
dropdb --if-exists $(TEST_DATABASE_NAME)
48+
dropuser --if-exists $(TEST_DATABASE_USER)
4949
createuser --createdb $(TEST_DATABASE_USER)
5050
createdb -U $(TEST_DATABASE_USER) $(TEST_DATABASE_NAME)
5151
else

migrate.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package migrations
2+
3+
import (
4+
"sort"
5+
"time"
6+
7+
"github.com/go-pg/pg"
8+
"github.com/go-pg/pg/orm"
9+
)
10+
11+
var migrations []migration
12+
13+
// Register accepts a name, up, down, and options and adds the migration to the
14+
// global migrations slice.
15+
func Register(name string, up, down func(orm.DB) error, opts MigrationOptions) {
16+
migrations = append(migrations, migration{
17+
Name: name,
18+
Up: up,
19+
Down: down,
20+
DisableTransaction: opts.DisableTransaction,
21+
})
22+
}
23+
24+
func migrate(db *pg.DB, directory string) error {
25+
// sort the registered migrations by name (which will sort by the
26+
// timestamp in their names)
27+
sort.Slice(migrations, func(i, j int) bool {
28+
return migrations[i].Name < migrations[j].Name
29+
})
30+
31+
// look at the migrations table to see the already run migrations
32+
completed, err := getCompletedMigrations(db)
33+
if err != nil {
34+
return err
35+
}
36+
37+
// diff the completed migrations from the registered migrations to find
38+
// the migrations we still need to run
39+
uncompleted := filterMigrations(migrations, completed, false)
40+
41+
// if there are no migrations that need to be run, exit early
42+
if len(uncompleted) == 0 {
43+
return nil
44+
}
45+
46+
// acquire the migration lock from the migrations_lock table
47+
err = acquireLock(db)
48+
if err != nil {
49+
return err
50+
}
51+
defer releaseLock(db)
52+
53+
// find the last batch number
54+
batch, err := getLastBatchNumber(db)
55+
if err != nil {
56+
return err
57+
}
58+
batch = batch + 1
59+
60+
for _, m := range uncompleted {
61+
m.Batch = batch
62+
var err error
63+
if m.DisableTransaction {
64+
err = m.Up(db)
65+
} else {
66+
err = db.RunInTransaction(func(tx *pg.Tx) error {
67+
return m.Up(tx)
68+
})
69+
}
70+
if err != nil {
71+
return err
72+
}
73+
74+
m.CompletedAt = time.Now()
75+
err = db.Insert(&m)
76+
if err != nil {
77+
return err
78+
}
79+
}
80+
81+
return nil
82+
}
83+
84+
func getCompletedMigrations(db orm.DB) ([]migration, error) {
85+
var completed []migration
86+
87+
err := db.
88+
Model(&completed).
89+
Order("id").
90+
Select()
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
return completed, nil
96+
}
97+
98+
func filterMigrations(all, subset []migration, wantCompleted bool) []migration {
99+
subsetMap := map[string]bool{}
100+
101+
for _, c := range subset {
102+
subsetMap[c.Name] = true
103+
}
104+
105+
var d []migration
106+
107+
for _, a := range all {
108+
if subsetMap[a.Name] == wantCompleted {
109+
d = append(d, a)
110+
}
111+
}
112+
113+
return d
114+
}
115+
116+
func acquireLock(db *pg.DB) error {
117+
return db.RunInTransaction(func(tx *pg.Tx) error {
118+
l := lock{ID: lockID}
119+
120+
err := tx.Model(&l).
121+
For("UPDATE").
122+
Select()
123+
if err != nil {
124+
return err
125+
}
126+
if l.IsLocked {
127+
return ErrAlreadyLocked
128+
}
129+
130+
l.IsLocked = true
131+
132+
err = tx.Update(&l)
133+
return err
134+
})
135+
}
136+
137+
func releaseLock(db orm.DB) error {
138+
l := lock{ID: lockID, IsLocked: false}
139+
return db.Update(&l)
140+
}
141+
142+
func getLastBatchNumber(db orm.DB) (int32, error) {
143+
var res struct{ Batch int32 }
144+
err := db.Model(&migration{}).
145+
ColumnExpr("COALESCE(MAX(batch), 0) AS batch").
146+
Select(&res)
147+
if err != nil {
148+
return 0, err
149+
}
150+
return res.Batch, nil
151+
}

0 commit comments

Comments
 (0)