Skip to content

Commit 87793d1

Browse files
committed
wip
1 parent 5fcb0b0 commit 87793d1

File tree

8 files changed

+174
-9
lines changed

8 files changed

+174
-9
lines changed

Migrator.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,27 @@ func NewMigrator(driver Driver) (*Migrator, error) {
1616
return m, nil
1717
}
1818

19-
// TODO replace dir with opts, which includes direction and fresh commands, maybe more.
20-
func (m *Migrator) Run(dir string, migrations ...Migration) error {
19+
func (m *Migrator) Run(args []string, migrations ...Migration) error {
20+
opts := gatherOptions(args)
21+
2122
if err := m.driver.Init(); err != nil {
2223
return fmt.Errorf("cannot initialise migration driver: %w", err)
2324
}
2425

25-
if err := m.driver.Run(migrations); err != nil {
26+
if err := m.driver.Run(opts, migrations); err != nil {
2627
return fmt.Errorf("unable to run migrations: %w", err)
2728
}
2829

2930
return nil
3031
}
32+
33+
func gatherOptions(args []string) Options {
34+
dir := Unknown
35+
if len(args) > 0 {
36+
dir = directionFromString(args[1])
37+
}
38+
39+
return Options{
40+
direction: dir,
41+
}
42+
}

driver.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ type Driver interface {
55
Init() error
66
// Run takes a slice of exodus.Migration and runs it against the
77
// underlying data store defined in the driver.
8-
Run(payload []Migration) error
8+
Run(options Options, payload []Migration) error
99
// Close closes the underlying database instance.
1010
Close() error
1111
}

driver/mysql/const.go

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mysql
22

33
const (
4+
dropTableSchema = `DROP TABLE %s;`
45
renameTableSchema = `ALTER TABLE %s RENAME TO %s;`
56
createTableSchema = `CREATE TABLE %s (
67
%s

driver/mysql/mysql.go

+123-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ func NewDriver(datasource string) (*mysqlDriver, error) {
3535
}
3636

3737
func (d mysqlDriver) Init() error {
38-
d.fresh()
3938
// Before running migrations, make sure that the migrations table exists on
4039
// the underlying database. This table is used to track which migrations
4140
// have already been ran. If it doesn't exist, then create it.
@@ -52,7 +51,18 @@ func (d mysqlDriver) Close() error {
5251
return d.db.Close()
5352
}
5453

55-
func (d mysqlDriver) Run(migrations []exodus.Migration) error {
54+
func (d mysqlDriver) Run(opts exodus.Options, migrations []exodus.Migration) error {
55+
switch opts.Direction() {
56+
case exodus.Up:
57+
return d.runUp(opts, migrations)
58+
case exodus.Down:
59+
return d.runDown(opts, migrations)
60+
default:
61+
return errors.New("not running migrations, direction not specified")
62+
}
63+
}
64+
65+
func (d mysqlDriver) runUp(opts exodus.Options, migrations []exodus.Migration) error {
5666
// First, retrieve the list of migrations that have previously been ran. These
5767
// migrations are then used to determine which of the incoming migrations
5868
// should be ran against the database.
@@ -78,6 +88,29 @@ func (d mysqlDriver) Run(migrations []exodus.Migration) error {
7888
return nil
7989
}
8090

91+
func (d mysqlDriver) runDown(opts exodus.Options, migrations []exodus.Migration) error {
92+
// First, retrieve the list of migrations that have previously been ran. These
93+
// will be used to retrieve the Down method of each migration to run.
94+
previous, err := d.getLastBatchRan()
95+
if err != nil {
96+
return fmt.Errorf("unable to process migrations: %w", err)
97+
}
98+
migrations = filterRanMigrations(migrations, previous)
99+
100+
if len(migrations) == 0 {
101+
d.log.Info().Msg("Nothing to rollback.")
102+
return nil
103+
}
104+
105+
for _, migration := range migrations {
106+
if err := d.processDown(migration); err != nil {
107+
return fmt.Errorf("unable to execute SQL: %w", err)
108+
}
109+
}
110+
111+
return nil
112+
}
113+
81114
func (d mysqlDriver) getRan() ([]string, error) {
82115
rows, err := d.db.Query("SELECT migration FROM migrations")
83116
if err != nil {
@@ -99,6 +132,27 @@ func (d mysqlDriver) getRan() ([]string, error) {
99132
return ran, nil
100133
}
101134

135+
func (d mysqlDriver) getLastBatchRan() ([]string, error) {
136+
rows, err := d.db.Query("SELECT migration FROM migrations WHERE batch = (SELECT MAX(batch) FROM migrations)")
137+
if err != nil {
138+
return []string{}, fmt.Errorf("unable to get previous migrations from database: %w", err)
139+
}
140+
141+
var ran []string
142+
for rows.Next() {
143+
var migration string
144+
if err := rows.Scan(&migration); err != nil {
145+
return []string{}, fmt.Errorf("unable to get previous migrations from database: %w", err)
146+
}
147+
ran = append(ran, migration)
148+
}
149+
if err := rows.Err(); err != nil {
150+
return []string{}, fmt.Errorf("unable to get previous migrations from database: %w", err)
151+
}
152+
153+
return ran, nil
154+
}
155+
102156
func (d mysqlDriver) process(migration exodus.Migration, batch int) error {
103157
builder := &exodus.MigrationPayload{}
104158
migration.Up(builder)
@@ -125,6 +179,36 @@ func (d mysqlDriver) process(migration exodus.Migration, batch int) error {
125179
return nil
126180
}
127181

182+
func (d mysqlDriver) processDown(migration exodus.Migration) error {
183+
builder := &exodus.MigrationPayload{}
184+
migration.Down(builder)
185+
start := time.Now()
186+
d.log.Info().Msgf("Rolling back: %s", reflect.TypeOf(migration).String())
187+
for _, p := range builder.Operations() {
188+
switch p.Operation() {
189+
case exodus.CREATE_TABLE:
190+
if err := d.createTable(p); err != nil {
191+
return err
192+
}
193+
case exodus.RENAME_TABLE:
194+
if err := d.renameTable(p); err != nil {
195+
return err
196+
}
197+
case exodus.DROP_TABLE:
198+
if err := d.dropTable(p); err != nil {
199+
return err
200+
}
201+
default:
202+
return errors.New("operation not supported")
203+
}
204+
}
205+
206+
d.removeMigrationLog(migration)
207+
208+
d.log.Info().Msgf("Rolled back: %s in %v", reflect.TypeOf(migration).String(), time.Since(start))
209+
return nil
210+
}
211+
128212
func (d mysqlDriver) logMigration(migration exodus.Migration, batch int) error {
129213
stmt, err := d.db.Prepare("INSERT INTO migrations (migration, batch) VALUES ( ?, ? )")
130214
if err != nil {
@@ -139,6 +223,20 @@ func (d mysqlDriver) logMigration(migration exodus.Migration, batch int) error {
139223
return nil
140224
}
141225

226+
func (d mysqlDriver) removeMigrationLog(migration exodus.Migration) error {
227+
stmt, err := d.db.Prepare("DELETE FROM migrations WHERE migration = ?")
228+
if err != nil {
229+
log.Fatalln("Cannot create `migrations` remove statement. ")
230+
}
231+
defer stmt.Close()
232+
233+
if _, err = stmt.Exec(reflect.TypeOf(migration).String()); err != nil {
234+
return err
235+
}
236+
237+
return nil
238+
}
239+
142240
// nextBatchNumber retreives the highest batch number from the
143241
// migrations table and increments it by one.
144242
func (d mysqlDriver) nextBatchNumber() int {
@@ -154,6 +252,17 @@ func (d mysqlDriver) lastBatchNumber() int {
154252
return num
155253
}
156254

255+
func (d mysqlDriver) dropTable(payload *exodus.MigrationOperation) error {
256+
table := payload.Table()
257+
sql := fmt.Sprintf(dropTableSchema, table)
258+
259+
if _, err := d.db.Exec(sql); err != nil {
260+
return fmt.Errorf("unable to drop table `%s`: %w", table, err)
261+
}
262+
263+
return nil
264+
}
265+
157266
func (d mysqlDriver) renameTable(payload *exodus.MigrationOperation) error {
158267
from := payload.Table()
159268
to := payload.Payload().(string)
@@ -254,6 +363,18 @@ func filterPendingMigrations(migrations []exodus.Migration, existing []string) [
254363
return response
255364
}
256365

366+
func filterRanMigrations(migrations []exodus.Migration, existing []string) []exodus.Migration {
367+
var response []exodus.Migration
368+
369+
for _, migration := range migrations {
370+
if exists(migration, existing) {
371+
response = append(response, migration)
372+
}
373+
}
374+
375+
return response
376+
}
377+
257378
func exists(migration exodus.Migration, existing []string) bool {
258379
for _, ex := range existing {
259380
if reflect.TypeOf(migration).String() == ex {

options.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package exodus
2+
3+
type direction int
4+
5+
const (
6+
Unknown direction = iota
7+
Up
8+
Down
9+
)
10+
11+
func directionFromString(dir string) direction {
12+
if dir == "up" {
13+
return Up
14+
}
15+
16+
if dir == "down" {
17+
return Down
18+
}
19+
20+
return Unknown
21+
}
22+
23+
type Options struct {
24+
direction direction
25+
}
26+
27+
func (o Options) Direction() direction {
28+
return o.direction
29+
}

usage/migrations/20220115115948_create_users_table.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ func (migration20220115115948create_users_table) Up(schema *exodus.MigrationPayl
2020
}
2121

2222
func (migration20220115115948create_users_table) Down(schema *exodus.MigrationPayload) {
23-
23+
schema.Drop("example")
2424
}

usage/migrations/20220120041331_create_posts_table.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ func (migration20220120041331create_posts_table) Up(schema *exodus.MigrationPayl
1414
})
1515
}
1616

17-
func (migration20220120041331create_posts_table) Down(schema *exodus.MigrationPayload) {}
17+
func (migration20220120041331create_posts_table) Down(schema *exodus.MigrationPayload) {
18+
schema.Drop("posts")
19+
}

usage/migrations/run.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)