mirror of https://github.com/status-im/migrate.git
Changes that interfere with the Migrate open source code:
- Added go methods migrator, mongo db template: different from the usual driver model. - Added support for bidirectional files (for go methods), appending _up or _down upon context - Added DriverWithFilnameParser for providing custom filename parser functionality that knows to parse bi-directional file names.
This commit is contained in:
parent
175550643c
commit
1838852d4d
|
@ -1,2 +1,11 @@
|
|||
.DS_Store
|
||||
test.db
|
||||
test.db
|
||||
*.iml
|
||||
.DS_Store
|
||||
.idea
|
||||
*.zip
|
||||
*.tar.*
|
||||
*.nuget
|
||||
*.jar
|
||||
*.war
|
||||
|
||||
|
|
|
@ -34,6 +34,15 @@ type Driver interface {
|
|||
Version() (uint64, error)
|
||||
}
|
||||
|
||||
// Driver that has some custom migration file format
|
||||
// and wants to use a different parsing strategy.
|
||||
type DriverWithFilenameParser interface {
|
||||
|
||||
Driver
|
||||
|
||||
FilenameParser() file.FilenameParser
|
||||
}
|
||||
|
||||
// New returns Driver and calls Initialize on it
|
||||
func New(url string) (Driver, error) {
|
||||
u, err := neturl.Parse(url)
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
package gomethods
|
||||
|
||||
import (
|
||||
//"bytes"
|
||||
"reflect"
|
||||
"fmt"
|
||||
"strings"
|
||||
"os"
|
||||
"path"
|
||||
"bufio"
|
||||
"github.com/dimag-jfrog/migrate/driver"
|
||||
"github.com/dimag-jfrog/migrate/file"
|
||||
"github.com/dimag-jfrog/migrate/migrate/direction"
|
||||
)
|
||||
|
||||
|
||||
type MissingMethodError string
|
||||
func (e MissingMethodError) Error() string { return "Non existing migrate method: " + string(e) }
|
||||
|
||||
|
||||
type WrongMethodSignatureError string
|
||||
func (e WrongMethodSignatureError) Error() string { return fmt.Sprintf("Method %s has wrong signature", e) }
|
||||
|
||||
type MethodInvocationFailedError struct {
|
||||
MethodName string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *MethodInvocationFailedError) Error() string {
|
||||
return fmt.Sprintf("Method %s returned an error: %v", e.MethodName, e.Error)
|
||||
}
|
||||
|
||||
|
||||
type Migrator struct {
|
||||
Driver driver.DriverWithFilenameParser
|
||||
RollbackOnFailure bool
|
||||
}
|
||||
|
||||
func (m *Migrator) Migrate(f file.File, pipe chan interface{}) error {
|
||||
methods, err := m.getMigrationMethods(f)
|
||||
if err != nil {
|
||||
pipe <- err
|
||||
return err
|
||||
}
|
||||
|
||||
for i, methodName := range methods {
|
||||
pipe <- methodName
|
||||
err := m.Invoke(methodName)
|
||||
if err != nil {
|
||||
pipe <- err
|
||||
if !m.RollbackOnFailure {
|
||||
return err
|
||||
}
|
||||
|
||||
// on failure, try to rollback methods in this migration
|
||||
for j := i-1; j >= 0; j-- {
|
||||
rollbackToMethodName := getRollbackToMethod(methods[j])
|
||||
pipe <- rollbackToMethodName
|
||||
err = m.Invoke(rollbackToMethodName)
|
||||
if err != nil {
|
||||
pipe <- err
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) IsValid(methodName string) bool {
|
||||
return reflect.ValueOf(m.Driver).MethodByName(methodName).IsValid()
|
||||
}
|
||||
|
||||
func (m *Migrator) Invoke(methodName string) error {
|
||||
name := methodName
|
||||
migrateMethod := reflect.ValueOf(m.Driver).MethodByName(name)
|
||||
if !migrateMethod.IsValid() {
|
||||
return MissingMethodError(methodName)
|
||||
}
|
||||
|
||||
retValues := migrateMethod.Call([]reflect.Value{})
|
||||
if len(retValues) != 1 {
|
||||
return WrongMethodSignatureError(name)
|
||||
}
|
||||
|
||||
if !retValues[0].IsNil() {
|
||||
err, ok := retValues[0].Interface().(error)
|
||||
if !ok {
|
||||
return WrongMethodSignatureError(name)
|
||||
}
|
||||
return &MethodInvocationFailedError{ MethodName:name, Err:err}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func reverseInPlace(a []string) {
|
||||
for i := 0; i < len(a)/2; i++ {
|
||||
j := len(a) - i - 1
|
||||
a[i], a[j] = a[j], a[i]
|
||||
}
|
||||
}
|
||||
|
||||
func getRollbackToMethod(methodName string) string {
|
||||
if strings.HasSuffix(methodName, "_up") {
|
||||
return strings.TrimSuffix(methodName, "_up") + "_down"
|
||||
} else {
|
||||
return strings.TrimSuffix(methodName, "_down") + "_up"
|
||||
}
|
||||
}
|
||||
|
||||
func getFileLines(file file.File) ([]string, error) {
|
||||
if len(file.Content) == 0 {
|
||||
lines := make([]string, 0)
|
||||
file, err := os.Open(path.Join(file.Path, file.FileName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
return lines, nil
|
||||
} else {
|
||||
//n := bytes.IndexByte(file.Content, 0)
|
||||
//n := bytes.Index(file.Content, []byte{0})
|
||||
//s := string(file.Content[:n])
|
||||
s := string(file.Content)
|
||||
return strings.Split(s, "\n"), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Migrator) getMigrationMethods(f file.File) ([]string, error) {
|
||||
var lines, methods []string
|
||||
lines, err := getFileLines(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
operationName := strings.TrimSpace(line)
|
||||
|
||||
if operationName == "" || strings.HasPrefix(operationName, "--") {
|
||||
// an empty line or a comment, ignore
|
||||
continue
|
||||
}
|
||||
|
||||
upMethodName := operationName + "_up"
|
||||
downMethodName := operationName + "_down"
|
||||
|
||||
if !m.IsValid(upMethodName) {
|
||||
return nil, MissingMethodError(upMethodName)
|
||||
}
|
||||
if !m.IsValid(downMethodName) {
|
||||
return nil, MissingMethodError(downMethodName)
|
||||
}
|
||||
|
||||
if f.Direction == direction.Up {
|
||||
methods = append(methods, upMethodName)
|
||||
} else {
|
||||
methods = append(methods, downMethodName)
|
||||
}
|
||||
}
|
||||
|
||||
_,_,fileType,_ := m.Driver.FilenameParser().Parse(f.FileName)
|
||||
if fileType == direction.Both && f.Direction == direction.Down {
|
||||
reverseInPlace(methods)
|
||||
}
|
||||
return methods, nil
|
||||
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
package gomethods
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/dimag-jfrog/migrate/file"
|
||||
"github.com/dimag-jfrog/migrate/migrate/direction"
|
||||
|
||||
pipep "github.com/dimag-jfrog/migrate/pipe"
|
||||
)
|
||||
|
||||
type FakeGoMethodsDriver struct {
|
||||
InvokedMethods []string
|
||||
Migrator Migrator
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) Initialize(url string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) FilenameParser() file.FilenameParser {
|
||||
return file.UpDownAndBothFilenameParser{ FilenameExtension: driver.FilenameExtension() }
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) FilenameExtension() string {
|
||||
return "gm"
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) Version() (uint64, error) {
|
||||
return uint64(0), nil
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) Migrate(f file.File, pipe chan interface{}) {
|
||||
defer close(pipe)
|
||||
pipe <- f
|
||||
return
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) V001_init_organizations_up() error {
|
||||
driver.InvokedMethods = append(driver.InvokedMethods, "V001_init_organizations_up")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) V001_init_organizations_down() error {
|
||||
driver.InvokedMethods = append(driver.InvokedMethods, "V001_init_organizations_down")
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) V001_init_users_up() error {
|
||||
driver.InvokedMethods = append(driver.InvokedMethods, "V001_init_users_up")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) V001_init_users_down() error {
|
||||
driver.InvokedMethods = append(driver.InvokedMethods, "V001_init_users_down")
|
||||
return nil
|
||||
}
|
||||
|
||||
type SomeError struct{}
|
||||
func (e SomeError) Error() string { return "Some error happened" }
|
||||
|
||||
func (driver *FakeGoMethodsDriver) V001_some_failing_method_up() error {
|
||||
driver.InvokedMethods = append(driver.InvokedMethods, "V001_some_failing_method_up")
|
||||
return SomeError{}
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) V001_some_failing_method_down() error {
|
||||
driver.InvokedMethods = append(driver.InvokedMethods, "V001_some_failing_method_down")
|
||||
return SomeError{}
|
||||
}
|
||||
|
||||
func TestMigrate(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
file file.File
|
||||
expectedInvokedMethods []string
|
||||
expectedErrors []error
|
||||
expectRollback bool
|
||||
}{
|
||||
{
|
||||
name: "up migration, both directions-file: invokes up methods in order",
|
||||
file: file.File {
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.gm",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
V001_init_organizations
|
||||
V001_init_users
|
||||
`),
|
||||
},
|
||||
expectedInvokedMethods: []string{"V001_init_organizations_up", "V001_init_users_up"},
|
||||
expectedErrors: []error{},
|
||||
},
|
||||
{
|
||||
name: "down migration, both-directions-file: reverts direction of invoked down methods",
|
||||
file: file.File {
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.gm",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: []byte(`
|
||||
V001_init_organizations
|
||||
V001_init_users
|
||||
`),
|
||||
},
|
||||
expectedInvokedMethods: []string{"V001_init_users_down", "V001_init_organizations_down"},
|
||||
expectedErrors: []error{},
|
||||
},
|
||||
{
|
||||
name: "up migration, up direction-file: invokes up methods in order",
|
||||
file: file.File {
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.up.gm",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
V001_init_organizations
|
||||
V001_init_users
|
||||
`),
|
||||
},
|
||||
expectedInvokedMethods: []string{"V001_init_organizations_up", "V001_init_users_up"},
|
||||
expectedErrors: []error{},
|
||||
},
|
||||
{
|
||||
name: "down migration, down directions-file: keeps order of invoked down methods",
|
||||
file: file.File {
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.down.gm",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: []byte(`
|
||||
V001_init_organizations
|
||||
V001_init_users
|
||||
`),
|
||||
},
|
||||
expectedInvokedMethods: []string{"V001_init_organizations_down", "V001_init_users_down"},
|
||||
expectedErrors: []error{},
|
||||
},
|
||||
{
|
||||
name: "up migration: non-existing method causes migration not to execute",
|
||||
file: file.File {
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.gm",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
V001_init_organizations
|
||||
V001_init_users
|
||||
V001_some_non_existing_method
|
||||
`),
|
||||
},
|
||||
expectedInvokedMethods: []string{},
|
||||
expectedErrors: []error{ MissingMethodError("V001_some_non_existing_method_up") },
|
||||
},
|
||||
{
|
||||
name: "up migration: failing method stops execution",
|
||||
file: file.File {
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.gm",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
V001_init_organizations
|
||||
V001_some_failing_method
|
||||
V001_init_users
|
||||
`),
|
||||
},
|
||||
expectedInvokedMethods: []string{
|
||||
"V001_init_organizations_up",
|
||||
"V001_some_failing_method_up",
|
||||
},
|
||||
expectedErrors: []error{ &MethodInvocationFailedError{
|
||||
MethodName: "V001_some_failing_method_up",
|
||||
Err: SomeError{},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "down migration, both-directions-file: failing method stops migration",
|
||||
file: file.File {
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.gm",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: []byte(`
|
||||
V001_init_organizations
|
||||
V001_some_failing_method
|
||||
V001_init_users
|
||||
`),
|
||||
},
|
||||
expectedInvokedMethods: []string{
|
||||
"V001_init_users_down",
|
||||
"V001_some_failing_method_down",
|
||||
},
|
||||
expectedErrors: []error{ &MethodInvocationFailedError{
|
||||
MethodName: "V001_some_failing_method_down",
|
||||
Err: SomeError{},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "up migration: failing method causes rollback in rollback mode",
|
||||
expectRollback: true,
|
||||
file: file.File {
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.gm",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
V001_init_organizations
|
||||
V001_init_users
|
||||
V001_some_failing_method
|
||||
`),
|
||||
},
|
||||
expectedInvokedMethods: []string{
|
||||
"V001_init_organizations_up",
|
||||
"V001_init_users_up",
|
||||
"V001_some_failing_method_up",
|
||||
"V001_init_users_down",
|
||||
"V001_init_organizations_down",
|
||||
},
|
||||
expectedErrors: []error{ &MethodInvocationFailedError{
|
||||
MethodName: "V001_some_failing_method_up",
|
||||
Err: SomeError{},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "down migration, both-directions-file: failing method causes rollback in rollback mode",
|
||||
expectRollback: true,
|
||||
file: file.File {
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.gm",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: []byte(`
|
||||
V001_init_organizations
|
||||
V001_some_failing_method
|
||||
V001_init_users
|
||||
`),
|
||||
},
|
||||
expectedInvokedMethods: []string{
|
||||
"V001_init_users_down",
|
||||
"V001_some_failing_method_down",
|
||||
"V001_init_users_up",
|
||||
},
|
||||
expectedErrors: []error{ &MethodInvocationFailedError{
|
||||
MethodName: "V001_some_failing_method_down",
|
||||
Err: SomeError{},
|
||||
}},
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
migrator := Migrator{}
|
||||
d := &FakeGoMethodsDriver{Migrator: migrator, InvokedMethods:[]string{}}
|
||||
migrator.Driver = d
|
||||
migrator.RollbackOnFailure = c.expectRollback
|
||||
|
||||
pipe := pipep.New()
|
||||
go func() {
|
||||
migrator.Migrate(c.file, pipe)
|
||||
close(pipe)
|
||||
}()
|
||||
errs := pipep.ReadErrors(pipe)
|
||||
|
||||
var failed bool
|
||||
if !reflect.DeepEqual(d.InvokedMethods, c.expectedInvokedMethods) {
|
||||
failed = true
|
||||
t.Errorf("case '%s': FAILED\nexpected invoked methods %v\nbut got %v", c.name, c.expectedInvokedMethods, d.InvokedMethods)
|
||||
}
|
||||
if !reflect.DeepEqual(errs, c.expectedErrors) {
|
||||
failed = true
|
||||
t.Errorf("case '%s': FAILED\nexpected errors %v\nbut got %v", c.name, c.expectedErrors, errs)
|
||||
|
||||
}
|
||||
if !failed {
|
||||
t.Logf("case '%s': PASSED", c.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func TestGetRollbackToMethod(t *testing.T) {
|
||||
cases := []struct {
|
||||
method string
|
||||
expectedRollbackMethod string
|
||||
}{
|
||||
{"some_method_up", "some_method_down"},
|
||||
{"some_method_down", "some_method_up"},
|
||||
{"up_down_up", "up_down_down"},
|
||||
{"down_up", "down_down"},
|
||||
{"down_down", "down_up"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
actualRollbackMethod := getRollbackToMethod(c.method)
|
||||
if actualRollbackMethod != c.expectedRollbackMethod {
|
||||
t.Errorf("Expected rollback method to be %s but got %s", c.expectedRollbackMethod, actualRollbackMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverseInPlace(t *testing.T) {
|
||||
methods := []string {
|
||||
"method1_down",
|
||||
"method2_down",
|
||||
"method3_down",
|
||||
"method4_down",
|
||||
"method5_down",
|
||||
}
|
||||
|
||||
expectedReversedMethods := []string {
|
||||
"method5_down",
|
||||
"method4_down",
|
||||
"method3_down",
|
||||
"method2_down",
|
||||
"method1_down",
|
||||
}
|
||||
|
||||
reverseInPlace(methods)
|
||||
|
||||
if !reflect.DeepEqual(methods, expectedReversedMethods) {
|
||||
t.Errorf("Expected reverse methods %v but got %v", expectedReversedMethods, methods)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package mongodb
|
||||
|
||||
import (
|
||||
"gopkg.in/mgo.v2"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"errors"
|
||||
"strings"
|
||||
"github.com/dimag-jfrog/migrate/migrate/direction"
|
||||
"github.com/dimag-jfrog/migrate/file"
|
||||
"github.com/dimag-jfrog/migrate/driver/gomethods"
|
||||
)
|
||||
|
||||
const MIGRATE_C = "db_migrations"
|
||||
|
||||
|
||||
// This is not a real driver since the Initialize method requires a gomethods.Migrator
|
||||
// The real driver will contain the DriverTemplate and implement all the custom migration Golang methods
|
||||
// See example in usage_examples for details
|
||||
type DriverTemplate struct {
|
||||
Session *mgo.Session
|
||||
DbName string
|
||||
|
||||
migrator gomethods.Migrator
|
||||
}
|
||||
|
||||
|
||||
type DbMigration struct {
|
||||
Id bson.ObjectId `bson:"_id,omitempty"`
|
||||
Version uint64 `bson:"version"`
|
||||
}
|
||||
|
||||
func (driver *DriverTemplate) Initialize(url, dbName string, migrator gomethods.Migrator) error {
|
||||
urlWithoutScheme := strings.SplitN(url, "mongodb://", 2)
|
||||
if len(urlWithoutScheme) != 2 {
|
||||
return errors.New("invalid mongodb:// scheme")
|
||||
}
|
||||
|
||||
session, err := mgo.Dial(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
session.SetMode(mgo.Monotonic, true)
|
||||
|
||||
driver.Session = session
|
||||
driver.DbName = dbName
|
||||
driver.migrator = migrator
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *DriverTemplate) Close() error {
|
||||
if driver.Session != nil {
|
||||
driver.Session.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *DriverTemplate) FilenameParser() file.FilenameParser {
|
||||
return file.UpDownAndBothFilenameParser{FilenameExtension: driver.FilenameExtension()}
|
||||
}
|
||||
|
||||
func (driver *DriverTemplate) FilenameExtension() string {
|
||||
return "mgo"
|
||||
}
|
||||
|
||||
|
||||
func (driver *DriverTemplate) Version() (uint64, error) {
|
||||
var latestMigration DbMigration
|
||||
c := driver.Session.DB(driver.DbName).C(MIGRATE_C)
|
||||
|
||||
|
||||
err := c.Find(bson.M{}).Sort("-version").One(&latestMigration)
|
||||
|
||||
switch {
|
||||
case err == mgo.ErrNotFound:
|
||||
return 0, nil
|
||||
case err != nil:
|
||||
return 0, err
|
||||
default:
|
||||
return latestMigration.Version, nil
|
||||
}
|
||||
}
|
||||
func (driver *DriverTemplate) Migrate(f file.File, pipe chan interface{}) {
|
||||
defer close(pipe)
|
||||
pipe <- f
|
||||
|
||||
err := driver.migrator.Migrate(f, pipe)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
migrate_c := driver.Session.DB(driver.DbName).C(MIGRATE_C)
|
||||
|
||||
if f.Direction == direction.Up {
|
||||
id := bson.NewObjectId()
|
||||
dbMigration := DbMigration{Id: id, Version: f.Version}
|
||||
|
||||
err := migrate_c.Insert(dbMigration)
|
||||
if err != nil {
|
||||
pipe <- err
|
||||
return
|
||||
}
|
||||
|
||||
} else if f.Direction == direction.Down {
|
||||
err := migrate_c.Remove(bson.M{"version": f.Version})
|
||||
if err != nil {
|
||||
pipe <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package usage_examples
|
||||
|
||||
import (
|
||||
"github.com/dimag-jfrog/migrate/driver"
|
||||
"github.com/dimag-jfrog/migrate/driver/gomethods"
|
||||
"github.com/dimag-jfrog/migrate/driver/gomethods/mongodb"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This boilerplate part is necessary and the same
|
||||
// regardless of the specific mongodb golang methods driver
|
||||
|
||||
type GoMethodsMongoDbDriver struct {
|
||||
mongodb.DriverTemplate
|
||||
}
|
||||
|
||||
func (d *GoMethodsMongoDbDriver) Initialize(url string) error {
|
||||
return d.DriverTemplate.Initialize(url, DB_NAME, gomethods.Migrator{Driver: d})
|
||||
}
|
||||
|
||||
func init() {
|
||||
driver.RegisterDriver("mongodb", &GoMethodsMongoDbDriver{mongodb.DriverTemplate{}})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Here goes the specific mongodb golang methods driver logic
|
||||
|
||||
const DB_NAME = "test"
|
||||
const SHORT_DATE_LAYOUT = "2000-Jan-01"
|
||||
const USERS_C = "users"
|
||||
const ORGANIZATIONS_C = "organizations"
|
||||
|
||||
type Organization struct {
|
||||
Id bson.ObjectId `bson:"_id,omitempty"`
|
||||
Name string `bson:"name"`
|
||||
Location string `bson:"location"`
|
||||
DateFounded time.Time `bson:"date_founded"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id bson.ObjectId `bson:"_id"`
|
||||
Name string `bson:"name"`
|
||||
}
|
||||
|
||||
func (m *GoMethodsMongoDbDriver) V001_init_organizations_up() error {
|
||||
date1, _ := time.Parse(SHORT_DATE_LAYOUT, "1994-Jul-05")
|
||||
date2, _ := time.Parse(SHORT_DATE_LAYOUT, "1998-Sep-04")
|
||||
date3, _ := time.Parse(SHORT_DATE_LAYOUT, "2008-Apr-28")
|
||||
|
||||
orgs := []Organization{
|
||||
{Id: bson.NewObjectId(), Name: "Amazon", Location:"Seattle", DateFounded: date1},
|
||||
{Id: bson.NewObjectId(), Name: "Google", Location:"Mountain View", DateFounded: date2},
|
||||
{Id: bson.NewObjectId(), Name: "JFrog", Location:"Santa Clara", DateFounded: date3},
|
||||
}
|
||||
|
||||
for _, org := range orgs {
|
||||
err := m.Session.DB(DB_NAME).C(ORGANIZATIONS_C).Insert(org)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoMethodsMongoDbDriver) V001_init_organizations_down() error {
|
||||
return m.Session.DB(DB_NAME).C(ORGANIZATIONS_C).DropCollection()
|
||||
}
|
||||
|
||||
func (m *GoMethodsMongoDbDriver) V001_init_users_up() error {
|
||||
users := []User{
|
||||
{Id: bson.NewObjectId(), Name: "Alex"},
|
||||
{Id: bson.NewObjectId(), Name: "Beatrice"},
|
||||
{Id: bson.NewObjectId(), Name: "Cleo"},
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
err := m.Session.DB(DB_NAME).C(USERS_C).Insert(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoMethodsMongoDbDriver) V001_init_users_down() error {
|
||||
return m.Session.DB(DB_NAME).C(USERS_C).DropCollection()
|
||||
}
|
||||
|
||||
func (m *GoMethodsMongoDbDriver) V002_organizations_rename_location_field_to_headquarters_up() error {
|
||||
c := m.Session.DB(DB_NAME).C(ORGANIZATIONS_C)
|
||||
|
||||
_, err := c.UpdateAll(nil, bson.M{"$rename": bson.M{"location": "headquarters"}})
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *GoMethodsMongoDbDriver) V002_organizations_rename_location_field_to_headquarters_down() error {
|
||||
c := m.Session.DB(DB_NAME).C(ORGANIZATIONS_C)
|
||||
|
||||
_, err := c.UpdateAll(nil, bson.M{"$rename": bson.M{"headquarters": "location"}})
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *GoMethodsMongoDbDriver) V002_change_user_cleo_to_cleopatra_up() error {
|
||||
c := m.Session.DB(DB_NAME).C(USERS_C)
|
||||
|
||||
colQuerier := bson.M{"name": "Cleo"}
|
||||
change := bson.M{"$set": bson.M{"name": "Cleopatra"}}
|
||||
|
||||
return c.Update(colQuerier, change)
|
||||
}
|
||||
|
||||
func (m *GoMethodsMongoDbDriver) V002_change_user_cleo_to_cleopatra_down() error {
|
||||
c := m.Session.DB(DB_NAME).C(USERS_C)
|
||||
|
||||
colQuerier := bson.M{"name": "Cleopatra"}
|
||||
change := bson.M{"$set": bson.M{"name": "Cleo",}}
|
||||
|
||||
return c.Update(colQuerier, change)
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package usage_examples
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dimag-jfrog/migrate/file"
|
||||
"github.com/dimag-jfrog/migrate/migrate/direction"
|
||||
|
||||
pipep "github.com/dimag-jfrog/migrate/pipe"
|
||||
)
|
||||
|
||||
|
||||
|
||||
func TestMigrate(t *testing.T) {
|
||||
//host := os.Getenv("MONGODB_PORT_27017_TCP_ADDR")
|
||||
//port := os.Getenv("MONGODB_PORT_27017_TCP_PORT")
|
||||
host := "127.0.0.1"
|
||||
port := "27017"
|
||||
driverUrl := "mongodb://" + host + ":" + port
|
||||
|
||||
d := &GoMethodsMongoDbDriver{}
|
||||
if err := d.Initialize(driverUrl); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
content1 := []byte(`
|
||||
V001_init_organizations
|
||||
V001_init_users
|
||||
`)
|
||||
content2 := []byte(`
|
||||
V002_organizations_rename_location_field_to_headquarters
|
||||
V002_change_user_cleo_to_cleopatra
|
||||
`)
|
||||
|
||||
files := []file.File{
|
||||
{
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.mgo",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: content1,
|
||||
},
|
||||
{
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.mgo",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: content1,
|
||||
},
|
||||
{
|
||||
Path: "/foobar",
|
||||
FileName: "002_foobar.mgo",
|
||||
Version: 2,
|
||||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: content2,
|
||||
},
|
||||
{
|
||||
Path: "/foobar",
|
||||
FileName: "002_foobar.mgo",
|
||||
Version: 2,
|
||||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: content2,
|
||||
},
|
||||
{
|
||||
Path: "/foobar",
|
||||
FileName: "001_foobar.mgo",
|
||||
Version: 1,
|
||||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
V001_init_organizations
|
||||
V001_init_users
|
||||
V001_non_existing_operation
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
var pipe chan interface{}
|
||||
var errs []error
|
||||
|
||||
pipe = pipep.New()
|
||||
go d.Migrate(files[0], pipe)
|
||||
errs = pipep.ReadErrors(pipe)
|
||||
if len(errs) > 0 {
|
||||
t.Fatal(errs)
|
||||
}
|
||||
|
||||
pipe = pipep.New()
|
||||
go d.Migrate(files[2], pipe)
|
||||
errs = pipep.ReadErrors(pipe)
|
||||
if len(errs) > 0 {
|
||||
t.Fatal(errs)
|
||||
}
|
||||
|
||||
pipe = pipep.New()
|
||||
go d.Migrate(files[3], pipe)
|
||||
errs = pipep.ReadErrors(pipe)
|
||||
if len(errs) > 0 {
|
||||
t.Fatal(errs)
|
||||
}
|
||||
|
||||
//pipe = pipep.New()
|
||||
//go d.Migrate(files[1], pipe)
|
||||
//errs = pipep.ReadErrors(pipe)
|
||||
//if len(errs) > 0 {
|
||||
// t.Fatal(errs)
|
||||
//}
|
||||
|
||||
pipe = pipep.New()
|
||||
go d.Migrate(files[4], pipe)
|
||||
errs = pipep.ReadErrors(pipe)
|
||||
if len(errs) == 0 {
|
||||
t.Error("Expected test case to fail")
|
||||
}
|
||||
|
||||
if err := d.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
106
file/file.go
106
file/file.go
|
@ -9,20 +9,11 @@ import (
|
|||
"go/token"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var filenameRegex = `^([0-9]+)_(.*)\.(up|down)\.%s$`
|
||||
|
||||
// FilenameRegex builds regular expression stmt with given
|
||||
// filename extension from driver.
|
||||
func FilenameRegex(filenameExtension string) *regexp.Regexp {
|
||||
return regexp.MustCompile(fmt.Sprintf(filenameRegex, filenameExtension))
|
||||
}
|
||||
|
||||
// File represents one file on disk.
|
||||
// Example: 001_initial_plan_to_do_sth.up.sql
|
||||
type File struct {
|
||||
|
@ -149,8 +140,17 @@ func (mf *MigrationFiles) From(version uint64, relativeN int) (Files, error) {
|
|||
return files, nil
|
||||
}
|
||||
|
||||
|
||||
func ReadMigrationFiles(path, filenameExtension string) (MigrationFiles, error){
|
||||
return doReadMigrationFiles(path, DefaultFilenameParser{FilenameExtension: filenameExtension})
|
||||
}
|
||||
|
||||
func ReadMigrationFilesWithFilenameParser(path string, filenameParser FilenameParser) (MigrationFiles, error){
|
||||
return doReadMigrationFiles(path, filenameParser)
|
||||
}
|
||||
|
||||
// ReadMigrationFiles reads all migration files from a given path
|
||||
func ReadMigrationFiles(path string, filenameRegex *regexp.Regexp) (files MigrationFiles, err error) {
|
||||
func doReadMigrationFiles(path string, filenameParser FilenameParser) (files MigrationFiles, err error) {
|
||||
// find all migration files in path
|
||||
ioFiles, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
|
@ -165,7 +165,7 @@ func ReadMigrationFiles(path string, filenameRegex *regexp.Regexp) (files Migrat
|
|||
tmpFiles := make([]*tmpFile, 0)
|
||||
tmpFileMap := map[uint64]map[direction.Direction]tmpFile{}
|
||||
for _, file := range ioFiles {
|
||||
version, name, d, err := parseFilenameSchema(file.Name(), filenameRegex)
|
||||
version, name, d, err := filenameParser.Parse(file.Name())
|
||||
if err == nil {
|
||||
if _, ok := tmpFileMap[version]; !ok {
|
||||
tmpFileMap[version] = map[direction.Direction]tmpFile{}
|
||||
|
@ -210,33 +210,56 @@ func ReadMigrationFiles(path string, filenameRegex *regexp.Regexp) (files Migrat
|
|||
Direction: direction.Down,
|
||||
}
|
||||
lookFordirection = direction.Up
|
||||
case direction.Both:
|
||||
migrationFile.UpFile = &File{
|
||||
Path: path,
|
||||
FileName: file.filename,
|
||||
Version: file.version,
|
||||
Name: file.name,
|
||||
Content: nil,
|
||||
Direction: direction.Up,
|
||||
}
|
||||
migrationFile.DownFile = &File{
|
||||
Path: path,
|
||||
FileName: file.filename,
|
||||
Version: file.version,
|
||||
Name: file.name,
|
||||
Content: nil,
|
||||
Direction: direction.Down,
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("Unsupported direction.Direction Type")
|
||||
}
|
||||
|
||||
for _, file2 := range tmpFiles {
|
||||
if file2.version == file.version && file2.d == lookFordirection {
|
||||
switch lookFordirection {
|
||||
case direction.Up:
|
||||
migrationFile.UpFile = &File{
|
||||
Path: path,
|
||||
FileName: file2.filename,
|
||||
Version: file.version,
|
||||
Name: file2.name,
|
||||
Content: nil,
|
||||
Direction: direction.Up,
|
||||
if file2.version == file.version {
|
||||
if file.d == direction.Both {
|
||||
if file.d != file2.d {
|
||||
return nil, errors.New("Incompatible direction.Direction types")
|
||||
}
|
||||
case direction.Down:
|
||||
migrationFile.DownFile = &File{
|
||||
Path: path,
|
||||
FileName: file2.filename,
|
||||
Version: file.version,
|
||||
Name: file2.name,
|
||||
Content: nil,
|
||||
Direction: direction.Down,
|
||||
} else if file2.d == lookFordirection {
|
||||
switch lookFordirection {
|
||||
case direction.Up:
|
||||
migrationFile.UpFile = &File{
|
||||
Path: path,
|
||||
FileName: file2.filename,
|
||||
Version: file.version,
|
||||
Name: file2.name,
|
||||
Content: nil,
|
||||
Direction: direction.Up,
|
||||
}
|
||||
case direction.Down:
|
||||
migrationFile.DownFile = &File{
|
||||
Path: path,
|
||||
FileName: file2.filename,
|
||||
Version: file.version,
|
||||
Name: file2.name,
|
||||
Content: nil,
|
||||
Direction: direction.Down,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,29 +272,6 @@ func ReadMigrationFiles(path string, filenameRegex *regexp.Regexp) (files Migrat
|
|||
return newFiles, nil
|
||||
}
|
||||
|
||||
// parseFilenameSchema parses the filename
|
||||
func parseFilenameSchema(filename string, filenameRegex *regexp.Regexp) (version uint64, name string, d direction.Direction, err error) {
|
||||
matches := filenameRegex.FindStringSubmatch(filename)
|
||||
if len(matches) != 4 {
|
||||
return 0, "", 0, errors.New("Unable to parse filename schema")
|
||||
}
|
||||
|
||||
version, err = strconv.ParseUint(matches[1], 10, 0)
|
||||
if err != nil {
|
||||
return 0, "", 0, errors.New(fmt.Sprintf("Unable to parse version '%v' in filename schema", matches[0]))
|
||||
}
|
||||
|
||||
if matches[3] == "up" {
|
||||
d = direction.Up
|
||||
} else if matches[3] == "down" {
|
||||
d = direction.Down
|
||||
} else {
|
||||
return 0, "", 0, errors.New(fmt.Sprintf("Unable to parse up|down '%v' in filename schema", matches[3]))
|
||||
}
|
||||
|
||||
return version, matches[2], d, nil
|
||||
}
|
||||
|
||||
// Len is the number of elements in the collection.
|
||||
// Required by Sort Interface{}
|
||||
func (mf MigrationFiles) Len() int {
|
||||
|
|
|
@ -8,52 +8,6 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestParseFilenameSchema(t *testing.T) {
|
||||
var tests = []struct {
|
||||
filename string
|
||||
filenameExtension string
|
||||
expectVersion uint64
|
||||
expectName string
|
||||
expectDirection direction.Direction
|
||||
expectErr bool
|
||||
}{
|
||||
{"001_test_file.up.sql", "sql", 1, "test_file", direction.Up, false},
|
||||
{"001_test_file.down.sql", "sql", 1, "test_file", direction.Down, false},
|
||||
{"10034_test_file.down.sql", "sql", 10034, "test_file", direction.Down, false},
|
||||
{"-1_test_file.down.sql", "sql", 0, "", direction.Up, true},
|
||||
{"test_file.down.sql", "sql", 0, "", direction.Up, true},
|
||||
{"100_test_file.down", "sql", 0, "", direction.Up, true},
|
||||
{"100_test_file.sql", "sql", 0, "", direction.Up, true},
|
||||
{"100_test_file", "sql", 0, "", direction.Up, true},
|
||||
{"test_file", "sql", 0, "", direction.Up, true},
|
||||
{"100", "sql", 0, "", direction.Up, true},
|
||||
{".sql", "sql", 0, "", direction.Up, true},
|
||||
{"up.sql", "sql", 0, "", direction.Up, true},
|
||||
{"down.sql", "sql", 0, "", direction.Up, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
version, name, migrate, err := parseFilenameSchema(test.filename, FilenameRegex(test.filenameExtension))
|
||||
if test.expectErr && err == nil {
|
||||
t.Fatal("Expected error, but got none.", test)
|
||||
}
|
||||
if !test.expectErr && err != nil {
|
||||
t.Fatal("Did not expect error, but got one:", err, test)
|
||||
}
|
||||
if err == nil {
|
||||
if version != test.expectVersion {
|
||||
t.Error("Wrong version number", test)
|
||||
}
|
||||
if name != test.expectName {
|
||||
t.Error("wrong name", test)
|
||||
}
|
||||
if migrate != test.expectDirection {
|
||||
t.Error("wrong migrate", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFiles(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("/tmp", "TestLookForMigrationFilesInSearchPath")
|
||||
if err != nil {
|
||||
|
@ -77,7 +31,7 @@ func TestFiles(t *testing.T) {
|
|||
|
||||
ioutil.WriteFile(path.Join(tmpdir, "401_migrationfile.down.sql"), []byte("test"), 0755)
|
||||
|
||||
files, err := ReadMigrationFiles(tmpdir, FilenameRegex("sql"))
|
||||
files, err := ReadMigrationFiles(tmpdir, "sql")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -223,7 +177,7 @@ func TestDuplicateFiles(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = ReadMigrationFiles(root, FilenameRegex("sql"))
|
||||
_, err = ReadMigrationFiles(root, "sql")
|
||||
if err == nil {
|
||||
t.Fatal("Expected duplicate migration file error")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/dimag-jfrog/migrate/migrate/direction"
|
||||
"regexp"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
type FilenameParser interface {
|
||||
Parse (filename string) (version uint64, name string, d direction.Direction, err error)
|
||||
}
|
||||
|
||||
|
||||
var defaultFilenameRegexTemplate = `^([0-9]+)_(.*)\.(up|down)\.%s$`
|
||||
|
||||
func parseDefaultFilenameSchema(filename, filenameRegex string) (version uint64, name string, d direction.Direction, err error) {
|
||||
regexp := regexp.MustCompile(filenameRegex)
|
||||
matches := regexp.FindStringSubmatch(filename)
|
||||
if len(matches) != 4 {
|
||||
return 0, "", 0, errors.New("Unable to parse filename schema")
|
||||
}
|
||||
|
||||
version, err = strconv.ParseUint(matches[1], 10, 0)
|
||||
if err != nil {
|
||||
return 0, "", 0, errors.New(fmt.Sprintf("Unable to parse version '%v' in filename schema", matches[0]))
|
||||
}
|
||||
|
||||
name = matches[2]
|
||||
|
||||
if matches[3] == "up" {
|
||||
d = direction.Up
|
||||
} else if matches[3] == "down" {
|
||||
d = direction.Down
|
||||
} else {
|
||||
return 0, "", 0, errors.New(fmt.Sprintf("Unable to parse up|down '%v' in filename schema", matches[3]))
|
||||
}
|
||||
|
||||
return version, name, d, nil
|
||||
}
|
||||
|
||||
type DefaultFilenameParser struct {
|
||||
FilenameExtension string
|
||||
}
|
||||
|
||||
func (parser DefaultFilenameParser) Parse (filename string) (version uint64, name string, d direction.Direction, err error) {
|
||||
filenameRegex := fmt.Sprintf(defaultFilenameRegexTemplate, parser.FilenameExtension)
|
||||
return parseDefaultFilenameSchema(filename, filenameRegex)
|
||||
}
|
||||
|
||||
|
||||
type UpDownAndBothFilenameParser struct {
|
||||
FilenameExtension string
|
||||
}
|
||||
|
||||
func (parser UpDownAndBothFilenameParser) Parse(filename string) (version uint64, name string, d direction.Direction, err error) {
|
||||
ext := parser.FilenameExtension
|
||||
if !strings.HasSuffix(filename, ext) {
|
||||
return 0, "", 0, errors.New("Filename ")
|
||||
}
|
||||
|
||||
var matches []string
|
||||
if strings.HasSuffix(filename, ".up." + ext) || strings.HasSuffix(filename, ".down." + ext) {
|
||||
filenameRegex := fmt.Sprintf(defaultFilenameRegexTemplate, parser.FilenameExtension)
|
||||
return parseDefaultFilenameSchema(filename, filenameRegex)
|
||||
}
|
||||
|
||||
regex := regexp.MustCompile(fmt.Sprintf(`^([0-9]+)_(.*)\.%s$`, ext))
|
||||
matches = regex.FindStringSubmatch(filename)
|
||||
if len(matches) != 3 {
|
||||
return 0, "", 0, errors.New("Unable to parse filename schema")
|
||||
}
|
||||
|
||||
version, err = strconv.ParseUint(matches[1], 10, 0)
|
||||
if err != nil {
|
||||
return 0, "", 0, errors.New(fmt.Sprintf("Unable to parse version '%v' in filename schema", matches[0]))
|
||||
}
|
||||
name = matches[2]
|
||||
d = direction.Both
|
||||
|
||||
return version, name, d, nil
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"github.com/dimag-jfrog/migrate/migrate/direction"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
type ParsingTest struct {
|
||||
filename string
|
||||
filenameExtension string
|
||||
expectVersion uint64
|
||||
expectName string
|
||||
expectDirection direction.Direction
|
||||
expectErr bool
|
||||
}
|
||||
|
||||
func testParser(t *testing.T, parser FilenameParser, test *ParsingTest) {
|
||||
version, name, migrate, err := parser.Parse(test.filename)
|
||||
if test.expectErr && err == nil {
|
||||
t.Fatal("Expected error, but got none.", test)
|
||||
}
|
||||
if !test.expectErr && err != nil {
|
||||
t.Fatal("Did not expect error, but got one:", err, test)
|
||||
}
|
||||
if err == nil {
|
||||
if version != test.expectVersion {
|
||||
t.Error("Wrong version number", test)
|
||||
}
|
||||
if name != test.expectName {
|
||||
t.Error("wrong name", test)
|
||||
}
|
||||
if migrate != test.expectDirection {
|
||||
t.Error("wrong migrate", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDefaultFilenameSchema(t *testing.T) {
|
||||
var tests = []ParsingTest {
|
||||
{"001_test_file.up.sql", "sql", 1, "test_file", direction.Up, false},
|
||||
{"001_test_file.down.sql", "sql", 1, "test_file", direction.Down, false},
|
||||
{"10034_test_file.down.sql", "sql", 10034, "test_file", direction.Down, false},
|
||||
{"-1_test_file.down.sql", "sql", 0, "", direction.Up, true},
|
||||
{"test_file.down.sql", "sql", 0, "", direction.Up, true},
|
||||
{"100_test_file.down", "sql", 0, "", direction.Up, true},
|
||||
{"100_test_file.sql", "sql", 0, "", direction.Up, true},
|
||||
{"100_test_file", "sql", 0, "", direction.Up, true},
|
||||
{"test_file", "sql", 0, "", direction.Up, true},
|
||||
{"100", "sql", 0, "", direction.Up, true},
|
||||
{".sql", "sql", 0, "", direction.Up, true},
|
||||
{"up.sql", "sql", 0, "", direction.Up, true},
|
||||
{"down.sql", "sql", 0, "", direction.Up, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
parser := DefaultFilenameParser{FilenameExtension:test.filenameExtension}
|
||||
testParser(t, &parser, &test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseUpDownAndBothFilenameSchema(t *testing.T) {
|
||||
var tests = []ParsingTest {
|
||||
{"001_test_file.up.sql", "sql", 1, "test_file", direction.Up, false},
|
||||
{"001_test_file.down.sql", "sql", 1, "test_file", direction.Down, false},
|
||||
{"10034_test_file.down.sql", "sql", 10034, "test_file", direction.Down, false},
|
||||
{"-1_test_file.down.sql", "sql", 0, "", direction.Up, true},
|
||||
{"test_file.down.sql", "sql", 0, "", direction.Up, true},
|
||||
{"100_test_file.down", "sql", 0, "", direction.Up, true},
|
||||
{"100_test_file.sql", "sql", 100, "test_file", direction.Both, false},
|
||||
{"001_test_file.mgo", "mgo", 1, "test_file", direction.Both, false},
|
||||
{"-1_test_file.mgo", "sql", 0, "", direction.Up, true},
|
||||
{"100_test_file", "sql", 0, "", direction.Up, true},
|
||||
{"test_file", "sql", 0, "", direction.Up, true},
|
||||
{"100", "sql", 0, "", direction.Up, true},
|
||||
{".sql", "sql", 0, "", direction.Up, true},
|
||||
{"up.sql", "sql", 0, "", direction.Up, true},
|
||||
{"down.sql", "sql", 0, "", direction.Up, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
parser := UpDownAndBothFilenameParser{FilenameExtension:test.filenameExtension}
|
||||
testParser(t, &parser, &test)
|
||||
}
|
||||
}
|
|
@ -6,4 +6,5 @@ type Direction int
|
|||
const (
|
||||
Up Direction = +1
|
||||
Down = -1
|
||||
Both = 0
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue