mirror of https://github.com/status-im/migrate.git
- redefined mongo db driver to be autonomous and not depending on specific methods
- defined methods receivers registration by name and change the migration files format to include them - added extensive testing
This commit is contained in:
parent
c71fea90d6
commit
570e4b42be
|
@ -2,22 +2,30 @@ package gomethods
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//"bytes"
|
//"bytes"
|
||||||
"reflect"
|
"bufio"
|
||||||
|
"database/sql/driver"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"github.com/dimag-jfrog/migrate/file"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"bufio"
|
"strings"
|
||||||
"github.com/dimag-jfrog/migrate/file"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type UnregisteredMethodsReceiverError string
|
||||||
|
|
||||||
|
func (e UnregisteredMethodsReceiverError) Error() string {
|
||||||
|
return "Unregistered methods receiver: " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
type MissingMethodError string
|
type MissingMethodError string
|
||||||
|
|
||||||
func (e MissingMethodError) Error() string { return "Non existing migrate method: " + string(e) }
|
func (e MissingMethodError) Error() string { return "Non existing migrate method: " + string(e) }
|
||||||
|
|
||||||
|
|
||||||
type WrongMethodSignatureError string
|
type WrongMethodSignatureError string
|
||||||
func (e WrongMethodSignatureError) Error() string { return fmt.Sprintf("Method %s has wrong signature", e) }
|
|
||||||
|
func (e WrongMethodSignatureError) Error() string {
|
||||||
|
return fmt.Sprintf("Method %s has wrong signature", e)
|
||||||
|
}
|
||||||
|
|
||||||
type MethodInvocationFailedError struct {
|
type MethodInvocationFailedError struct {
|
||||||
MethodName string
|
MethodName string
|
||||||
|
@ -28,14 +36,23 @@ func (e *MethodInvocationFailedError) Error() string {
|
||||||
return fmt.Sprintf("Method %s returned an error: %v", e.MethodName, e.Error)
|
return fmt.Sprintf("Method %s returned an error: %v", e.MethodName, e.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MigrationMethodInvoker interface {
|
||||||
|
IsValid(methodName string, methodReceiver interface{}) bool
|
||||||
|
Invoke(methodName string, methodReceiver interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type GoMethodsDriver interface {
|
||||||
|
driver.Driver
|
||||||
|
MigrationMethodInvoker
|
||||||
|
}
|
||||||
|
|
||||||
type Migrator struct {
|
type Migrator struct {
|
||||||
MigrationMethodsReceiver interface{}
|
|
||||||
RollbackOnFailure bool
|
RollbackOnFailure bool
|
||||||
|
MethodInvoker MigrationMethodInvoker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Migrator) Migrate(f file.File, pipe chan interface{}) error {
|
func (m *Migrator) Migrate(f file.File, pipe chan interface{}) error {
|
||||||
methods, err := m.getMigrationMethods(f)
|
methods, methodsReceiver, err := m.getMigrationMethods(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pipe <- err
|
pipe <- err
|
||||||
return err
|
return err
|
||||||
|
@ -43,7 +60,7 @@ func (m *Migrator) Migrate(f file.File, pipe chan interface{}) error {
|
||||||
|
|
||||||
for i, methodName := range methods {
|
for i, methodName := range methods {
|
||||||
pipe <- methodName
|
pipe <- methodName
|
||||||
err := m.Invoke(methodName)
|
err := m.MethodInvoker.Invoke(methodName, methodsReceiver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pipe <- err
|
pipe <- err
|
||||||
if !m.RollbackOnFailure {
|
if !m.RollbackOnFailure {
|
||||||
|
@ -51,14 +68,15 @@ func (m *Migrator) Migrate(f file.File, pipe chan interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// on failure, try to rollback methods in this migration
|
// on failure, try to rollback methods in this migration
|
||||||
for j := i-1; j >= 0; j-- {
|
for j := i - 1; j >= 0; j-- {
|
||||||
rollbackToMethodName := getRollbackToMethod(methods[j])
|
rollbackToMethodName := getRollbackToMethod(methods[j])
|
||||||
if rollbackToMethodName == "" || !m.IsValid(rollbackToMethodName) {
|
if rollbackToMethodName == "" ||
|
||||||
|
!m.MethodInvoker.IsValid(rollbackToMethodName, methodsReceiver) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pipe <- rollbackToMethodName
|
pipe <- rollbackToMethodName
|
||||||
err = m.Invoke(rollbackToMethodName)
|
err = m.MethodInvoker.Invoke(rollbackToMethodName, methodsReceiver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pipe <- err
|
pipe <- err
|
||||||
break
|
break
|
||||||
|
@ -71,33 +89,6 @@ func (m *Migrator) Migrate(f file.File, pipe chan interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Migrator) IsValid(methodName string) bool {
|
|
||||||
return reflect.ValueOf(m.MigrationMethodsReceiver).MethodByName(methodName).IsValid()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Migrator) Invoke(methodName string) error {
|
|
||||||
name := methodName
|
|
||||||
migrateMethod := reflect.ValueOf(m.MigrationMethodsReceiver).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) {
|
func reverseInPlace(a []string) {
|
||||||
for i := 0; i < len(a)/2; i++ {
|
for i := 0; i < len(a)/2; i++ {
|
||||||
j := len(a) - i - 1
|
j := len(a) - i - 1
|
||||||
|
@ -139,28 +130,40 @@ func getFileLines(file file.File) ([]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Migrator) getMigrationMethods(f file.File) ([]string, error) {
|
func (m *Migrator) getMigrationMethods(f file.File) (methods []string, methodsReceiver interface{}, err error) {
|
||||||
var lines, methods []string
|
var lines []string
|
||||||
lines, err := getFileLines(f)
|
|
||||||
|
lines, err = getFileLines(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
methodName := strings.TrimSpace(line)
|
line := strings.TrimSpace(line)
|
||||||
|
|
||||||
if methodName == "" || strings.HasPrefix(methodName, "--") {
|
if line == "" || strings.HasPrefix(line, "--") {
|
||||||
// an empty line or a comment, ignore
|
// an empty line or a comment, ignore
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !m.IsValid(methodName) {
|
if methodsReceiver == nil {
|
||||||
return nil, MissingMethodError(methodName)
|
receiverName := line
|
||||||
|
methodsReceiver = GetMethodsReceiver(receiverName)
|
||||||
|
if methodsReceiver == nil {
|
||||||
|
return nil, nil, UnregisteredMethodsReceiverError(receiverName)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
} else {
|
||||||
|
methodName := line
|
||||||
|
if !m.MethodInvoker.IsValid(methodName, methodsReceiver) {
|
||||||
|
return nil, nil, MissingMethodError(methodName)
|
||||||
}
|
}
|
||||||
|
|
||||||
methods = append(methods, methodName)
|
methods = append(methods, methodName)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return methods, nil
|
return methods, methodsReceiver, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,67 +10,35 @@ import (
|
||||||
pipep "github.com/dimag-jfrog/migrate/pipe"
|
pipep "github.com/dimag-jfrog/migrate/pipe"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FakeGoMethodsDriver struct {
|
type FakeGoMethodsInvoker struct {
|
||||||
InvokedMethods []string
|
InvokedMethods []string
|
||||||
Migrator Migrator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver *FakeGoMethodsDriver) Initialize(url string) error {
|
func (invoker *FakeGoMethodsInvoker) IsValid(methodName string, methodReceiver interface{}) bool {
|
||||||
return nil
|
if methodName == "V001_some_non_existing_method_up" {
|
||||||
}
|
return false
|
||||||
|
}
|
||||||
func (driver *FakeGoMethodsDriver) Close() error {
|
|
||||||
return nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver *FakeGoMethodsDriver) FilenameExtension() string {
|
func (invoker *FakeGoMethodsInvoker) Invoke(methodName string, methodReceiver interface{}) error {
|
||||||
return "gm"
|
invoker.InvokedMethods = append(invoker.InvokedMethods, methodName)
|
||||||
}
|
|
||||||
|
if methodName == "V001_some_failing_method_up" || methodName == "V001_some_failing_method_down" {
|
||||||
func (driver *FakeGoMethodsDriver) Version() (uint64, error) {
|
return &MethodInvocationFailedError{
|
||||||
return uint64(0), nil
|
MethodName: methodName,
|
||||||
}
|
Err: SomeError{},
|
||||||
|
}
|
||||||
func (driver *FakeGoMethodsDriver) Migrate(f file.File, pipe chan interface{}) {
|
} else {
|
||||||
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
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SomeError struct{}
|
type SomeError struct{}
|
||||||
|
|
||||||
func (e SomeError) Error() string { return "Some error happened" }
|
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) {
|
func TestMigrate(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -81,13 +49,14 @@ func TestMigrate(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "up migration invokes up methods",
|
name: "up migration invokes up methods",
|
||||||
file: file.File {
|
file: file.File{
|
||||||
Path: "/foobar",
|
Path: "/foobar",
|
||||||
FileName: "001_foobar.up.gm",
|
FileName: "001_foobar.up.gm",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Up,
|
Direction: direction.Up,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
FakeMethodsReceiver
|
||||||
V001_init_organizations_up
|
V001_init_organizations_up
|
||||||
V001_init_users_up
|
V001_init_users_up
|
||||||
`),
|
`),
|
||||||
|
@ -97,13 +66,14 @@ func TestMigrate(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "down migration invoked down methods",
|
name: "down migration invoked down methods",
|
||||||
file: file.File {
|
file: file.File{
|
||||||
Path: "/foobar",
|
Path: "/foobar",
|
||||||
FileName: "001_foobar.down.gm",
|
FileName: "001_foobar.down.gm",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Down,
|
Direction: direction.Down,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
FakeMethodsReceiver
|
||||||
V001_init_users_down
|
V001_init_users_down
|
||||||
V001_init_organizations_down
|
V001_init_organizations_down
|
||||||
`),
|
`),
|
||||||
|
@ -113,30 +83,32 @@ func TestMigrate(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "up migration: non-existing method causes migration not to execute",
|
name: "up migration: non-existing method causes migration not to execute",
|
||||||
file: file.File {
|
file: file.File{
|
||||||
Path: "/foobar",
|
Path: "/foobar",
|
||||||
FileName: "001_foobar.up.gm",
|
FileName: "001_foobar.up.gm",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Up,
|
Direction: direction.Up,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
FakeMethodsReceiver
|
||||||
V001_init_organizations_up
|
V001_init_organizations_up
|
||||||
V001_init_users_up
|
V001_init_users_up
|
||||||
V001_some_non_existing_method_up
|
V001_some_non_existing_method_up
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
expectedInvokedMethods: []string{},
|
expectedInvokedMethods: []string{},
|
||||||
expectedErrors: []error{ MissingMethodError("V001_some_non_existing_method_up") },
|
expectedErrors: []error{MissingMethodError("V001_some_non_existing_method_up")},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "up migration: failing method stops execution",
|
name: "up migration: failing method stops execution",
|
||||||
file: file.File {
|
file: file.File{
|
||||||
Path: "/foobar",
|
Path: "/foobar",
|
||||||
FileName: "001_foobar.up.gm",
|
FileName: "001_foobar.up.gm",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Up,
|
Direction: direction.Up,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
FakeMethodsReceiver
|
||||||
V001_init_organizations_up
|
V001_init_organizations_up
|
||||||
V001_some_failing_method_up
|
V001_some_failing_method_up
|
||||||
V001_init_users_up
|
V001_init_users_up
|
||||||
|
@ -146,20 +118,21 @@ func TestMigrate(t *testing.T) {
|
||||||
"V001_init_organizations_up",
|
"V001_init_organizations_up",
|
||||||
"V001_some_failing_method_up",
|
"V001_some_failing_method_up",
|
||||||
},
|
},
|
||||||
expectedErrors: []error{ &MethodInvocationFailedError{
|
expectedErrors: []error{&MethodInvocationFailedError{
|
||||||
MethodName: "V001_some_failing_method_up",
|
MethodName: "V001_some_failing_method_up",
|
||||||
Err: SomeError{},
|
Err: SomeError{},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "down migration: failing method stops migration",
|
name: "down migration: failing method stops migration",
|
||||||
file: file.File {
|
file: file.File{
|
||||||
Path: "/foobar",
|
Path: "/foobar",
|
||||||
FileName: "001_foobar.down.gm",
|
FileName: "001_foobar.down.gm",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Down,
|
Direction: direction.Down,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
FakeMethodsReceiver
|
||||||
V001_init_users_down
|
V001_init_users_down
|
||||||
V001_some_failing_method_down
|
V001_some_failing_method_down
|
||||||
V001_init_organizations_down
|
V001_init_organizations_down
|
||||||
|
@ -169,7 +142,7 @@ func TestMigrate(t *testing.T) {
|
||||||
"V001_init_users_down",
|
"V001_init_users_down",
|
||||||
"V001_some_failing_method_down",
|
"V001_some_failing_method_down",
|
||||||
},
|
},
|
||||||
expectedErrors: []error{ &MethodInvocationFailedError{
|
expectedErrors: []error{&MethodInvocationFailedError{
|
||||||
MethodName: "V001_some_failing_method_down",
|
MethodName: "V001_some_failing_method_down",
|
||||||
Err: SomeError{},
|
Err: SomeError{},
|
||||||
}},
|
}},
|
||||||
|
@ -177,13 +150,14 @@ func TestMigrate(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "up migration: failing method causes rollback in rollback mode",
|
name: "up migration: failing method causes rollback in rollback mode",
|
||||||
expectRollback: true,
|
expectRollback: true,
|
||||||
file: file.File {
|
file: file.File{
|
||||||
Path: "/foobar",
|
Path: "/foobar",
|
||||||
FileName: "001_foobar.up.gm",
|
FileName: "001_foobar.up.gm",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Up,
|
Direction: direction.Up,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
FakeMethodsReceiver
|
||||||
V001_init_organizations_up
|
V001_init_organizations_up
|
||||||
V001_init_users_up
|
V001_init_users_up
|
||||||
V001_some_failing_method_up
|
V001_some_failing_method_up
|
||||||
|
@ -196,7 +170,7 @@ func TestMigrate(t *testing.T) {
|
||||||
"V001_init_users_down",
|
"V001_init_users_down",
|
||||||
"V001_init_organizations_down",
|
"V001_init_organizations_down",
|
||||||
},
|
},
|
||||||
expectedErrors: []error{ &MethodInvocationFailedError{
|
expectedErrors: []error{&MethodInvocationFailedError{
|
||||||
MethodName: "V001_some_failing_method_up",
|
MethodName: "V001_some_failing_method_up",
|
||||||
Err: SomeError{},
|
Err: SomeError{},
|
||||||
}},
|
}},
|
||||||
|
@ -204,13 +178,14 @@ func TestMigrate(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "down migration: failing method causes rollback in rollback mode",
|
name: "down migration: failing method causes rollback in rollback mode",
|
||||||
expectRollback: true,
|
expectRollback: true,
|
||||||
file: file.File {
|
file: file.File{
|
||||||
Path: "/foobar",
|
Path: "/foobar",
|
||||||
FileName: "001_foobar.down.gm",
|
FileName: "001_foobar.down.gm",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Down,
|
Direction: direction.Down,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
FakeMethodsReceiver
|
||||||
V001_init_users_down
|
V001_init_users_down
|
||||||
V001_some_failing_method_down
|
V001_some_failing_method_down
|
||||||
V001_init_organizations_down
|
V001_init_organizations_down
|
||||||
|
@ -221,18 +196,20 @@ func TestMigrate(t *testing.T) {
|
||||||
"V001_some_failing_method_down",
|
"V001_some_failing_method_down",
|
||||||
"V001_init_users_up",
|
"V001_init_users_up",
|
||||||
},
|
},
|
||||||
expectedErrors: []error{ &MethodInvocationFailedError{
|
expectedErrors: []error{&MethodInvocationFailedError{
|
||||||
MethodName: "V001_some_failing_method_down",
|
MethodName: "V001_some_failing_method_down",
|
||||||
Err: SomeError{},
|
Err: SomeError{},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RegisterMethodsReceiver("FakeMethodsReceiver", "")
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
migrator := Migrator{}
|
migrator := Migrator{}
|
||||||
d := &FakeGoMethodsDriver{Migrator: migrator, InvokedMethods:[]string{}}
|
fakeInvoker := &FakeGoMethodsInvoker{InvokedMethods: []string{}}
|
||||||
migrator.MigrationMethodsReceiver = d
|
|
||||||
|
migrator.MethodInvoker = fakeInvoker
|
||||||
migrator.RollbackOnFailure = c.expectRollback
|
migrator.RollbackOnFailure = c.expectRollback
|
||||||
|
|
||||||
pipe := pipep.New()
|
pipe := pipep.New()
|
||||||
|
@ -243,9 +220,9 @@ func TestMigrate(t *testing.T) {
|
||||||
errs := pipep.ReadErrors(pipe)
|
errs := pipep.ReadErrors(pipe)
|
||||||
|
|
||||||
var failed bool
|
var failed bool
|
||||||
if !reflect.DeepEqual(d.InvokedMethods, c.expectedInvokedMethods) {
|
if !reflect.DeepEqual(fakeInvoker.InvokedMethods, c.expectedInvokedMethods) {
|
||||||
failed = true
|
failed = true
|
||||||
t.Errorf("case '%s': FAILED\nexpected invoked methods %v\nbut got %v", c.name, c.expectedInvokedMethods, d.InvokedMethods)
|
t.Errorf("case '%s': FAILED\nexpected invoked methods %v\nbut got %v", c.name, c.expectedInvokedMethods, fakeInvoker.InvokedMethods)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(errs, c.expectedErrors) {
|
if !reflect.DeepEqual(errs, c.expectedErrors) {
|
||||||
failed = true
|
failed = true
|
||||||
|
@ -258,8 +235,6 @@ func TestMigrate(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func TestGetRollbackToMethod(t *testing.T) {
|
func TestGetRollbackToMethod(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
method string
|
method string
|
||||||
|
@ -282,7 +257,7 @@ func TestGetRollbackToMethod(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReverseInPlace(t *testing.T) {
|
func TestReverseInPlace(t *testing.T) {
|
||||||
methods := []string {
|
methods := []string{
|
||||||
"method1_down",
|
"method1_down",
|
||||||
"method2_down",
|
"method2_down",
|
||||||
"method3_down",
|
"method3_down",
|
||||||
|
@ -290,7 +265,7 @@ func TestReverseInPlace(t *testing.T) {
|
||||||
"method5_down",
|
"method5_down",
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedReversedMethods := []string {
|
expectedReversedMethods := []string{
|
||||||
"method5_down",
|
"method5_down",
|
||||||
"method4_down",
|
"method4_down",
|
||||||
"method3_down",
|
"method3_down",
|
||||||
|
@ -304,4 +279,3 @@ func TestReverseInPlace(t *testing.T) {
|
||||||
t.Errorf("Expected reverse methods %v but got %v", expectedReversedMethods, methods)
|
t.Errorf("Expected reverse methods %v but got %v", expectedReversedMethods, methods)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package gomethods
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var methodsReceiversMu sync.Mutex
|
||||||
|
var methodsReceivers = make(map[string]interface{})
|
||||||
|
|
||||||
|
// Registers a methods receiver object so it can be created from its name. Users of gomethods migration drivers should
|
||||||
|
// call this method to register objects with their migration methods before executing the migration
|
||||||
|
func RegisterMethodsReceiver(name string, receiver interface{}) {
|
||||||
|
methodsReceiversMu.Lock()
|
||||||
|
defer methodsReceiversMu.Unlock()
|
||||||
|
if receiver == nil {
|
||||||
|
panic("Go methods: Register receiver object is nil")
|
||||||
|
}
|
||||||
|
if _, dup := methodsReceivers[name]; dup {
|
||||||
|
panic("Go methods: Register called twice for receiver " + name)
|
||||||
|
}
|
||||||
|
methodsReceivers[name] = receiver
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves a registered driver by name
|
||||||
|
func GetMethodsReceiver(name string) interface{} {
|
||||||
|
methodsReceiversMu.Lock()
|
||||||
|
defer methodsReceiversMu.Unlock()
|
||||||
|
receiver := methodsReceivers[name]
|
||||||
|
return receiver
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package mongodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/dimag-jfrog/migrate/driver"
|
||||||
|
"github.com/dimag-jfrog/migrate/driver/gomethods"
|
||||||
|
"github.com/dimag-jfrog/migrate/file"
|
||||||
|
"github.com/dimag-jfrog/migrate/migrate/direction"
|
||||||
|
"gopkg.in/mgo.v2"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MIGRATE_DB = "db_migrations"
|
||||||
|
const MIGRATE_C = "db_migrations"
|
||||||
|
|
||||||
|
type MongoDbGoMethodsDriver struct {
|
||||||
|
Session *mgo.Session
|
||||||
|
DbName string
|
||||||
|
|
||||||
|
migrator gomethods.Migrator
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
driver.RegisterDriver("mongodb", &MongoDbGoMethodsDriver{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type DbMigration struct {
|
||||||
|
Id bson.ObjectId `bson:"_id,omitempty"`
|
||||||
|
Version uint64 `bson:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MongoDbGoMethodsDriver) Initialize(url string) 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 = MIGRATE_DB
|
||||||
|
driver.migrator = gomethods.Migrator{MethodInvoker: driver}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MongoDbGoMethodsDriver) Close() error {
|
||||||
|
if driver.Session != nil {
|
||||||
|
driver.Session.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MongoDbGoMethodsDriver) FilenameExtension() string {
|
||||||
|
return "mgo"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MongoDbGoMethodsDriver) 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 *MongoDbGoMethodsDriver) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MongoDbGoMethodsDriver) IsValid(methodName string, methodsReceiver interface{}) bool {
|
||||||
|
return reflect.ValueOf(methodsReceiver).MethodByName(methodName).IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MongoDbGoMethodsDriver) Invoke(methodName string, methodsReceiver interface{}) error {
|
||||||
|
name := methodName
|
||||||
|
migrateMethod := reflect.ValueOf(methodsReceiver).MethodByName(name)
|
||||||
|
if !migrateMethod.IsValid() {
|
||||||
|
return gomethods.MissingMethodError(methodName)
|
||||||
|
}
|
||||||
|
|
||||||
|
retValues := migrateMethod.Call([]reflect.Value{reflect.ValueOf(driver.Session)})
|
||||||
|
if len(retValues) != 1 {
|
||||||
|
return gomethods.WrongMethodSignatureError(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !retValues[0].IsNil() {
|
||||||
|
err, ok := retValues[0].Interface().(error)
|
||||||
|
if !ok {
|
||||||
|
return gomethods.WrongMethodSignatureError(name)
|
||||||
|
}
|
||||||
|
return &gomethods.MethodInvocationFailedError{MethodName: name, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
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) 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,129 @@
|
||||||
|
package mongodb_example
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dimag-jfrog/migrate/driver/gomethods"
|
||||||
|
"gopkg.in/mgo.v2"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MyMgoMethodsReceiver struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gomethods.RegisterMethodsReceiver("MyMgoMethodsReceiver", &MyMgoMethodsReceiver{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Organization_v2 struct {
|
||||||
|
Id bson.ObjectId `bson:"_id,omitempty"`
|
||||||
|
Name string `bson:"name"`
|
||||||
|
Headquarters string `bson:"headquarters"`
|
||||||
|
DateFounded time.Time `bson:"date_founded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Id bson.ObjectId `bson:"_id"`
|
||||||
|
Name string `bson:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var OrganizationIds []bson.ObjectId = []bson.ObjectId{
|
||||||
|
bson.NewObjectId(),
|
||||||
|
bson.NewObjectId(),
|
||||||
|
bson.NewObjectId(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var UserIds []bson.ObjectId = []bson.ObjectId{
|
||||||
|
bson.NewObjectId(),
|
||||||
|
bson.NewObjectId(),
|
||||||
|
bson.NewObjectId(),
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MyMgoMethodsReceiver) V001_init_organizations_up(session *mgo.Session) 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: OrganizationIds[0], Name: "Amazon", Location: "Seattle", DateFounded: date1},
|
||||||
|
{Id: OrganizationIds[1], Name: "Google", Location: "Mountain View", DateFounded: date2},
|
||||||
|
{Id: OrganizationIds[2], Name: "JFrog", Location: "Santa Clara", DateFounded: date3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, org := range orgs {
|
||||||
|
err := session.DB(DB_NAME).C(ORGANIZATIONS_C).Insert(org)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MyMgoMethodsReceiver) V001_init_organizations_down(session *mgo.Session) error {
|
||||||
|
return session.DB(DB_NAME).C(ORGANIZATIONS_C).DropCollection()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MyMgoMethodsReceiver) V001_init_users_up(session *mgo.Session) error {
|
||||||
|
users := []User{
|
||||||
|
{Id: UserIds[0], Name: "Alex"},
|
||||||
|
{Id: UserIds[1], Name: "Beatrice"},
|
||||||
|
{Id: UserIds[2], Name: "Cleo"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
err := session.DB(DB_NAME).C(USERS_C).Insert(user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MyMgoMethodsReceiver) V001_init_users_down(session *mgo.Session) error {
|
||||||
|
return session.DB(DB_NAME).C(USERS_C).DropCollection()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MyMgoMethodsReceiver) V002_organizations_rename_location_field_to_headquarters_up(session *mgo.Session) error {
|
||||||
|
c := session.DB(DB_NAME).C(ORGANIZATIONS_C)
|
||||||
|
|
||||||
|
_, err := c.UpdateAll(nil, bson.M{"$rename": bson.M{"location": "headquarters"}})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MyMgoMethodsReceiver) V002_organizations_rename_location_field_to_headquarters_down(session *mgo.Session) error {
|
||||||
|
c := session.DB(DB_NAME).C(ORGANIZATIONS_C)
|
||||||
|
|
||||||
|
_, err := c.UpdateAll(nil, bson.M{"$rename": bson.M{"headquarters": "location"}})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MyMgoMethodsReceiver) V002_change_user_cleo_to_cleopatra_up(session *mgo.Session) error {
|
||||||
|
c := 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 (r *MyMgoMethodsReceiver) V002_change_user_cleo_to_cleopatra_down(session *mgo.Session) error {
|
||||||
|
c := 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)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package usage_examples
|
package mongodb_example
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -6,13 +6,13 @@ import (
|
||||||
"github.com/dimag-jfrog/migrate/file"
|
"github.com/dimag-jfrog/migrate/file"
|
||||||
"github.com/dimag-jfrog/migrate/migrate/direction"
|
"github.com/dimag-jfrog/migrate/migrate/direction"
|
||||||
|
|
||||||
|
"github.com/dimag-jfrog/migrate/driver/gomethods"
|
||||||
|
"github.com/dimag-jfrog/migrate/driver/gomethods/mongodb"
|
||||||
pipep "github.com/dimag-jfrog/migrate/pipe"
|
pipep "github.com/dimag-jfrog/migrate/pipe"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
"github.com/dimag-jfrog/migrate/driver/gomethods"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type ExpectedMigrationResult struct {
|
type ExpectedMigrationResult struct {
|
||||||
Organizations []Organization
|
Organizations []Organization
|
||||||
Organizations_v2 []Organization_v2
|
Organizations_v2 []Organization_v2
|
||||||
|
@ -23,7 +23,7 @@ type ExpectedMigrationResult struct {
|
||||||
func RunMigrationAndAssertResult(
|
func RunMigrationAndAssertResult(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
title string,
|
title string,
|
||||||
d *GoMethodsMongoDbDriver,
|
d *mongodb.MongoDbGoMethodsDriver,
|
||||||
file file.File,
|
file file.File,
|
||||||
expected *ExpectedMigrationResult) {
|
expected *ExpectedMigrationResult) {
|
||||||
|
|
||||||
|
@ -53,6 +53,10 @@ func RunMigrationAndAssertResult(
|
||||||
t.Fatal("Failed to query Users collection")
|
t.Fatal("Failed to query Users collection")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected.Errors, errs) {
|
||||||
|
t.Fatalf("Migration '%s': FAILED\nexpected errors %v\nbut got %v", title, expected.Errors, errs)
|
||||||
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected.Organizations, actualOrganizations) {
|
if !reflect.DeepEqual(expected.Organizations, actualOrganizations) {
|
||||||
t.Fatalf("Migration '%s': FAILED\nexpected organizations %v\nbut got %v", title, expected.Organizations, actualOrganizations)
|
t.Fatalf("Migration '%s': FAILED\nexpected organizations %v\nbut got %v", title, expected.Organizations, actualOrganizations)
|
||||||
}
|
}
|
||||||
|
@ -65,13 +69,9 @@ func RunMigrationAndAssertResult(
|
||||||
t.Fatalf("Migration '%s': FAILED\nexpected users %v\nbut got %v", title, expected.Users, actualUsers)
|
t.Fatalf("Migration '%s': FAILED\nexpected users %v\nbut got %v", title, expected.Users, actualUsers)
|
||||||
|
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(expected.Errors, errs) {
|
|
||||||
t.Fatalf("Migration '%s': FAILED\nexpected errors %v\nbut got %v", title, expected.Errors, errs)
|
|
||||||
}
|
|
||||||
t.Logf("Migration '%s': PASSED", title)
|
t.Logf("Migration '%s': PASSED", title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestMigrate(t *testing.T) {
|
func TestMigrate(t *testing.T) {
|
||||||
//host := os.Getenv("MONGODB_PORT_27017_TCP_ADDR")
|
//host := os.Getenv("MONGODB_PORT_27017_TCP_ADDR")
|
||||||
//port := os.Getenv("MONGODB_PORT_27017_TCP_PORT")
|
//port := os.Getenv("MONGODB_PORT_27017_TCP_PORT")
|
||||||
|
@ -79,7 +79,9 @@ func TestMigrate(t *testing.T) {
|
||||||
port := "27017"
|
port := "27017"
|
||||||
driverUrl := "mongodb://" + host + ":" + port
|
driverUrl := "mongodb://" + host + ":" + port
|
||||||
|
|
||||||
d := &GoMethodsMongoDbDriver{}
|
//gomethods.RegisterMethodsReceiver("MyMgoMethodsReceiver", &MyMgoMethodsReceiver{})
|
||||||
|
d := &mongodb.MongoDbGoMethodsDriver{}
|
||||||
|
|
||||||
if err := d.Initialize(driverUrl); err != nil {
|
if err := d.Initialize(driverUrl); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -106,15 +108,16 @@ func TestMigrate(t *testing.T) {
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Up,
|
Direction: direction.Up,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
MyMgoMethodsReceiver
|
||||||
V001_init_organizations_up
|
V001_init_organizations_up
|
||||||
V001_init_users_up
|
V001_init_users_up
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
expectedResult: ExpectedMigrationResult{
|
expectedResult: ExpectedMigrationResult{
|
||||||
Organizations: []Organization{
|
Organizations: []Organization{
|
||||||
{Id: OrganizationIds[0], Name: "Amazon", Location:"Seattle", DateFounded: date1},
|
{Id: OrganizationIds[0], Name: "Amazon", Location: "Seattle", DateFounded: date1},
|
||||||
{Id: OrganizationIds[1], Name: "Google", Location:"Mountain View", DateFounded: date2},
|
{Id: OrganizationIds[1], Name: "Google", Location: "Mountain View", DateFounded: date2},
|
||||||
{Id: OrganizationIds[2], Name: "JFrog", Location:"Santa Clara", DateFounded: date3},
|
{Id: OrganizationIds[2], Name: "JFrog", Location: "Santa Clara", DateFounded: date3},
|
||||||
},
|
},
|
||||||
Organizations_v2: []Organization_v2{},
|
Organizations_v2: []Organization_v2{},
|
||||||
Users: []User{
|
Users: []User{
|
||||||
|
@ -134,6 +137,7 @@ func TestMigrate(t *testing.T) {
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Up,
|
Direction: direction.Up,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
MyMgoMethodsReceiver
|
||||||
V002_organizations_rename_location_field_to_headquarters_up
|
V002_organizations_rename_location_field_to_headquarters_up
|
||||||
V002_change_user_cleo_to_cleopatra_up
|
V002_change_user_cleo_to_cleopatra_up
|
||||||
`),
|
`),
|
||||||
|
@ -141,9 +145,9 @@ func TestMigrate(t *testing.T) {
|
||||||
expectedResult: ExpectedMigrationResult{
|
expectedResult: ExpectedMigrationResult{
|
||||||
Organizations: []Organization{},
|
Organizations: []Organization{},
|
||||||
Organizations_v2: []Organization_v2{
|
Organizations_v2: []Organization_v2{
|
||||||
{Id: OrganizationIds[0], Name: "Amazon", Headquarters:"Seattle", DateFounded: date1},
|
{Id: OrganizationIds[0], Name: "Amazon", Headquarters: "Seattle", DateFounded: date1},
|
||||||
{Id: OrganizationIds[1], Name: "Google", Headquarters:"Mountain View", DateFounded: date2},
|
{Id: OrganizationIds[1], Name: "Google", Headquarters: "Mountain View", DateFounded: date2},
|
||||||
{Id: OrganizationIds[2], Name: "JFrog", Headquarters:"Santa Clara", DateFounded: date3},
|
{Id: OrganizationIds[2], Name: "JFrog", Headquarters: "Santa Clara", DateFounded: date3},
|
||||||
},
|
},
|
||||||
Users: []User{
|
Users: []User{
|
||||||
{Id: UserIds[0], Name: "Alex"},
|
{Id: UserIds[0], Name: "Alex"},
|
||||||
|
@ -162,15 +166,16 @@ func TestMigrate(t *testing.T) {
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Down,
|
Direction: direction.Down,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
MyMgoMethodsReceiver
|
||||||
V002_change_user_cleo_to_cleopatra_down
|
V002_change_user_cleo_to_cleopatra_down
|
||||||
V002_organizations_rename_location_field_to_headquarters_down
|
V002_organizations_rename_location_field_to_headquarters_down
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
expectedResult: ExpectedMigrationResult{
|
expectedResult: ExpectedMigrationResult{
|
||||||
Organizations: []Organization{
|
Organizations: []Organization{
|
||||||
{Id: OrganizationIds[0], Name: "Amazon", Location:"Seattle", DateFounded: date1},
|
{Id: OrganizationIds[0], Name: "Amazon", Location: "Seattle", DateFounded: date1},
|
||||||
{Id: OrganizationIds[1], Name: "Google", Location:"Mountain View", DateFounded: date2},
|
{Id: OrganizationIds[1], Name: "Google", Location: "Mountain View", DateFounded: date2},
|
||||||
{Id: OrganizationIds[2], Name: "JFrog", Location:"Santa Clara", DateFounded: date3},
|
{Id: OrganizationIds[2], Name: "JFrog", Location: "Santa Clara", DateFounded: date3},
|
||||||
},
|
},
|
||||||
Organizations_v2: []Organization_v2{},
|
Organizations_v2: []Organization_v2{},
|
||||||
Users: []User{
|
Users: []User{
|
||||||
|
@ -190,6 +195,7 @@ func TestMigrate(t *testing.T) {
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Down,
|
Direction: direction.Down,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
MyMgoMethodsReceiver
|
||||||
V001_init_users_down
|
V001_init_users_down
|
||||||
V001_init_organizations_down
|
V001_init_organizations_down
|
||||||
`),
|
`),
|
||||||
|
@ -210,6 +216,7 @@ func TestMigrate(t *testing.T) {
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
Direction: direction.Up,
|
Direction: direction.Up,
|
||||||
Content: []byte(`
|
Content: []byte(`
|
||||||
|
MyMgoMethodsReceiver
|
||||||
V001_init_organizations_up
|
V001_init_organizations_up
|
||||||
V001_init_users_up
|
V001_init_users_up
|
||||||
v001_non_existing_method_up
|
v001_non_existing_method_up
|
||||||
|
@ -219,7 +226,27 @@ func TestMigrate(t *testing.T) {
|
||||||
Organizations: []Organization{},
|
Organizations: []Organization{},
|
||||||
Organizations_v2: []Organization_v2{},
|
Organizations_v2: []Organization_v2{},
|
||||||
Users: []User{},
|
Users: []User{},
|
||||||
Errors: []error{ gomethods.MissingMethodError("v001_non_existing_method_up") },
|
Errors: []error{gomethods.MissingMethodError("v001_non_existing_method_up")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v0 -> v1: not defined message receiver",
|
||||||
|
file: file.File{
|
||||||
|
Path: "/foobar",
|
||||||
|
FileName: "001_foobar.up.gm",
|
||||||
|
Version: 1,
|
||||||
|
Name: "foobar",
|
||||||
|
Direction: direction.Up,
|
||||||
|
Content: []byte(`
|
||||||
|
V001_init_organizations_up
|
||||||
|
V001_init_users_up
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
expectedResult: ExpectedMigrationResult{
|
||||||
|
Organizations: []Organization{},
|
||||||
|
Organizations_v2: []Organization_v2{},
|
||||||
|
Users: []User{},
|
||||||
|
Errors: []error{gomethods.UnregisteredMethodsReceiverError("V001_init_organizations_up")},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
|
@ -1,140 +0,0 @@
|
||||||
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{MigrationMethodsReceiver: 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 Organization_v2 struct {
|
|
||||||
Id bson.ObjectId `bson:"_id,omitempty"`
|
|
||||||
Name string `bson:"name"`
|
|
||||||
Headquarters string `bson:"headquarters"`
|
|
||||||
DateFounded time.Time `bson:"date_founded"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Id bson.ObjectId `bson:"_id"`
|
|
||||||
Name string `bson:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var OrganizationIds []bson.ObjectId = []bson.ObjectId{
|
|
||||||
bson.NewObjectId(),
|
|
||||||
bson.NewObjectId(),
|
|
||||||
bson.NewObjectId(),
|
|
||||||
}
|
|
||||||
|
|
||||||
var UserIds []bson.ObjectId = []bson.ObjectId{
|
|
||||||
bson.NewObjectId(),
|
|
||||||
bson.NewObjectId(),
|
|
||||||
bson.NewObjectId(),
|
|
||||||
}
|
|
||||||
|
|
||||||
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: OrganizationIds[0], Name: "Amazon", Location:"Seattle", DateFounded: date1},
|
|
||||||
{Id: OrganizationIds[1], Name: "Google", Location:"Mountain View", DateFounded: date2},
|
|
||||||
{Id: OrganizationIds[2], 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: UserIds[0], Name: "Alex"},
|
|
||||||
{Id: UserIds[1], Name: "Beatrice"},
|
|
||||||
{Id: UserIds[2], 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)
|
|
||||||
}
|
|
Loading…
Reference in New Issue