Skip to content

Commit

Permalink
Add support for loading go migrations from precompiled plugins (press…
Browse files Browse the repository at this point in the history
  • Loading branch information
przemyslaw-dobrowolski-cl committed Mar 29, 2017
1 parent 99e2954 commit 31bb74f
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sudo: false
language: go
go:
- 1.6
- 1.8
- tip

install:
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ github.com/pressly/goose is a fork of bitbucket.org/liamstask/goose with the fol
- Instead, you can create your own goose binary, import `github.com/pressly/goose`
package and run complex Go migrations with your own `*sql.DB` connection
- Each Go migration function is called with `*sql.Tx` argument - within its own transaction
- If a deployment model does not allow go compiler to be available from PATH it is possible
to load go migrations from one or more precompiled plugins (*.so)
- The goose pkg is decoupled from the default binary:
- goose pkg doesn't register any SQL drivers anymore
(no driver `panic()` conflict within your codebase!)
Expand Down Expand Up @@ -173,7 +175,7 @@ language plpgsql;
A [sample Go migration 00002_users_add_email.go file](./example/migrations-go/00002_rename_root.go) looks like:

```go
package migrations
package main

import (
"database/sql"
Expand Down Expand Up @@ -202,6 +204,20 @@ func Down(tx *sql.Tx) error {
}
```

## Go Migrations from a precompiled plugins

1. Import `github.com/pressly/goose`
2. Load migration plugins with LoadMigrationPlugins
4. Run goose command, ie. `goose.Up(db *sql.DB, dir string)`

A migration plugin can be created from a go template generated by goose.
Golang plugin system requires that the plugin's namespace is main.
To compile a plugin one can use the following command:

```bash
go build -buildmode=plugin -o 002_next.so 002_next.sql
```

## License

Licensed under [MIT License](./LICENSE)
Expand Down
19 changes: 19 additions & 0 deletions migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"log"
"path/filepath"
"plugin"
"runtime"
"sort"
)
Expand Down Expand Up @@ -128,6 +129,24 @@ func CollectMigrations(dirpath string, current, target int64) (Migrations, error
return migrations, nil
}

// LoadMigrationPlugins loads all the valid looking precompiled migration libraries
// in the migrations folder and go func registry, and key them by version. Registration
// is done automatically by shared library init func.
func LoadMigrationPlugins(dirpath string) error {
migrationsPlugins, err := filepath.Glob(dirpath + "/*.so")
if err != nil {
return err
}

for _, file := range migrationsPlugins {
if _, err = plugin.Open(file); err != nil {
return err
}
}

return nil
}

func sortAndConnectMigrations(migrations Migrations) Migrations {
sort.Sort(migrations)

Expand Down
28 changes: 17 additions & 11 deletions migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ type Migration struct {
DownFn func(*sql.Tx) error // Down go migration function
}

type TemplateParams struct {
Version, Name string
}

func (m *Migration) String() string {
return fmt.Sprintf(m.Source)
}
Expand All @@ -40,6 +44,7 @@ func (m *Migration) Down(db *sql.DB) error {
}

func (m *Migration) run(db *sql.DB, direction bool) error {

switch filepath.Ext(m.Source) {
case ".sql":
if err := runSQLMigration(db, m.Source, m.Version, direction); err != nil {
Expand Down Expand Up @@ -105,16 +110,16 @@ func CreateMigration(name, migrationType, dir string, t time.Time) (path string,
return "", errors.New("migration type must be 'go' or 'sql'")
}

timestamp := t.Format("20060102150405")
filename := fmt.Sprintf("%v_%v.%v", timestamp, name, migrationType)
version := t.Format("20060102150405")
filename := fmt.Sprintf("%v_%v.%v", version, name, migrationType)

fpath := filepath.Join(dir, filename)
tmpl := sqlMigrationTemplate
if migrationType == "go" {
tmpl = goSqlMigrationTemplate
tmpl = goMigrationTemplate
}

path, err = writeTemplateToFile(fpath, tmpl, timestamp)
path, err = writeTemplateToFile(fpath, tmpl, TemplateParams{Version: version, Name: filename})

return
}
Expand All @@ -133,17 +138,18 @@ func FinalizeMigration(tx *sql.Tx, direction bool, v int64) error {
return tx.Commit()
}

var sqlMigrationTemplate = template.Must(template.New("goose.sql-migration").Parse(`
-- +goose Up
var sqlMigrationTemplate = template.Must(template.New("goose.sql-migration").Parse(
`-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
`))
var goSqlMigrationTemplate = template.Must(template.New("goose.go-migration").Parse(`
package migration

var goMigrationTemplate = template.Must(template.New("goose.go-migration").Parse(
`package main
import (
"database/sql"
Expand All @@ -152,14 +158,14 @@ import (
)
func init() {
goose.AddMigration(Up_{{.}}, Down_{{.}})
goose.AddNamedMigration("{{.Name}}", Up_{{.Version}}, Down_{{.Version}})
}
func Up_{{.}}(tx *sql.Tx) error {
func Up_{{.Version}}(tx *sql.Tx) error {
return nil
}
func Down_{{.}}(tx *sql.Tx) error {
func Down_{{.Version}}(tx *sql.Tx) error {
return nil
}
`))

0 comments on commit 31bb74f

Please sign in to comment.