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 (
|
||||
//"bytes"
|
||||
"reflect"
|
||||
"bufio"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strings"
|
||||
"github.com/dimag-jfrog/migrate/file"
|
||||
"os"
|
||||
"path"
|
||||
"bufio"
|
||||
"github.com/dimag-jfrog/migrate/file"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UnregisteredMethodsReceiverError string
|
||||
|
||||
func (e UnregisteredMethodsReceiverError) Error() string {
|
||||
return "Unregistered methods receiver: " + string(e)
|
||||
}
|
||||
|
||||
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) }
|
||||
|
||||
func (e WrongMethodSignatureError) Error() string {
|
||||
return fmt.Sprintf("Method %s has wrong signature", e)
|
||||
}
|
||||
|
||||
type MethodInvocationFailedError struct {
|
||||
MethodName string
|
||||
|
@ -28,14 +36,23 @@ func (e *MethodInvocationFailedError) Error() string {
|
|||
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 {
|
||||
MigrationMethodsReceiver interface{}
|
||||
RollbackOnFailure bool
|
||||
MethodInvoker MigrationMethodInvoker
|
||||
}
|
||||
|
||||
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 {
|
||||
pipe <- err
|
||||
return err
|
||||
|
@ -43,7 +60,7 @@ func (m *Migrator) Migrate(f file.File, pipe chan interface{}) error {
|
|||
|
||||
for i, methodName := range methods {
|
||||
pipe <- methodName
|
||||
err := m.Invoke(methodName)
|
||||
err := m.MethodInvoker.Invoke(methodName, methodsReceiver)
|
||||
if err != nil {
|
||||
pipe <- err
|
||||
if !m.RollbackOnFailure {
|
||||
|
@ -53,12 +70,13 @@ func (m *Migrator) Migrate(f file.File, pipe chan interface{}) error {
|
|||
// on failure, try to rollback methods in this migration
|
||||
for j := i - 1; j >= 0; j-- {
|
||||
rollbackToMethodName := getRollbackToMethod(methods[j])
|
||||
if rollbackToMethodName == "" || !m.IsValid(rollbackToMethodName) {
|
||||
if rollbackToMethodName == "" ||
|
||||
!m.MethodInvoker.IsValid(rollbackToMethodName, methodsReceiver) {
|
||||
continue
|
||||
}
|
||||
|
||||
pipe <- rollbackToMethodName
|
||||
err = m.Invoke(rollbackToMethodName)
|
||||
err = m.MethodInvoker.Invoke(rollbackToMethodName, methodsReceiver)
|
||||
if err != nil {
|
||||
pipe <- err
|
||||
break
|
||||
|
@ -71,33 +89,6 @@ func (m *Migrator) Migrate(f file.File, pipe chan interface{}) error {
|
|||
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) {
|
||||
for i := 0; i < len(a)/2; i++ {
|
||||
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) {
|
||||
var lines, methods []string
|
||||
lines, err := getFileLines(f)
|
||||
func (m *Migrator) getMigrationMethods(f file.File) (methods []string, methodsReceiver interface{}, err error) {
|
||||
var lines []string
|
||||
|
||||
lines, err = getFileLines(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
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
|
||||
continue
|
||||
}
|
||||
|
||||
if !m.IsValid(methodName) {
|
||||
return nil, MissingMethodError(methodName)
|
||||
if methodsReceiver == nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return methods, nil
|
||||
return methods, methodsReceiver, nil
|
||||
|
||||
}
|
||||
|
|
|
@ -10,67 +10,35 @@ import (
|
|||
pipep "github.com/dimag-jfrog/migrate/pipe"
|
||||
)
|
||||
|
||||
type FakeGoMethodsDriver struct {
|
||||
type FakeGoMethodsInvoker struct {
|
||||
InvokedMethods []string
|
||||
Migrator Migrator
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) Initialize(url string) error {
|
||||
func (invoker *FakeGoMethodsInvoker) IsValid(methodName string, methodReceiver interface{}) bool {
|
||||
if methodName == "V001_some_non_existing_method_up" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (invoker *FakeGoMethodsInvoker) Invoke(methodName string, methodReceiver interface{}) error {
|
||||
invoker.InvokedMethods = append(invoker.InvokedMethods, methodName)
|
||||
|
||||
if methodName == "V001_some_failing_method_up" || methodName == "V001_some_failing_method_down" {
|
||||
return &MethodInvocationFailedError{
|
||||
MethodName: methodName,
|
||||
Err: SomeError{},
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *FakeGoMethodsDriver) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -88,6 +56,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
FakeMethodsReceiver
|
||||
V001_init_organizations_up
|
||||
V001_init_users_up
|
||||
`),
|
||||
|
@ -104,6 +73,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: []byte(`
|
||||
FakeMethodsReceiver
|
||||
V001_init_users_down
|
||||
V001_init_organizations_down
|
||||
`),
|
||||
|
@ -120,6 +90,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
FakeMethodsReceiver
|
||||
V001_init_organizations_up
|
||||
V001_init_users_up
|
||||
V001_some_non_existing_method_up
|
||||
|
@ -137,6 +108,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
FakeMethodsReceiver
|
||||
V001_init_organizations_up
|
||||
V001_some_failing_method_up
|
||||
V001_init_users_up
|
||||
|
@ -160,6 +132,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: []byte(`
|
||||
FakeMethodsReceiver
|
||||
V001_init_users_down
|
||||
V001_some_failing_method_down
|
||||
V001_init_organizations_down
|
||||
|
@ -184,6 +157,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
FakeMethodsReceiver
|
||||
V001_init_organizations_up
|
||||
V001_init_users_up
|
||||
V001_some_failing_method_up
|
||||
|
@ -211,6 +185,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: []byte(`
|
||||
FakeMethodsReceiver
|
||||
V001_init_users_down
|
||||
V001_some_failing_method_down
|
||||
V001_init_organizations_down
|
||||
|
@ -226,13 +201,15 @@ func TestMigrate(t *testing.T) {
|
|||
Err: SomeError{},
|
||||
}},
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
RegisterMethodsReceiver("FakeMethodsReceiver", "")
|
||||
|
||||
for _, c := range cases {
|
||||
migrator := Migrator{}
|
||||
d := &FakeGoMethodsDriver{Migrator: migrator, InvokedMethods:[]string{}}
|
||||
migrator.MigrationMethodsReceiver = d
|
||||
fakeInvoker := &FakeGoMethodsInvoker{InvokedMethods: []string{}}
|
||||
|
||||
migrator.MethodInvoker = fakeInvoker
|
||||
migrator.RollbackOnFailure = c.expectRollback
|
||||
|
||||
pipe := pipep.New()
|
||||
|
@ -243,9 +220,9 @@ func TestMigrate(t *testing.T) {
|
|||
errs := pipep.ReadErrors(pipe)
|
||||
|
||||
var failed bool
|
||||
if !reflect.DeepEqual(d.InvokedMethods, c.expectedInvokedMethods) {
|
||||
if !reflect.DeepEqual(fakeInvoker.InvokedMethods, c.expectedInvokedMethods) {
|
||||
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) {
|
||||
failed = true
|
||||
|
@ -258,8 +235,6 @@ func TestMigrate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func TestGetRollbackToMethod(t *testing.T) {
|
||||
cases := []struct {
|
||||
method string
|
||||
|
@ -304,4 +279,3 @@ func TestReverseInPlace(t *testing.T) {
|
|||
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 (
|
||||
"testing"
|
||||
|
@ -6,13 +6,13 @@ import (
|
|||
"github.com/dimag-jfrog/migrate/file"
|
||||
"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"
|
||||
"reflect"
|
||||
"time"
|
||||
"github.com/dimag-jfrog/migrate/driver/gomethods"
|
||||
)
|
||||
|
||||
|
||||
type ExpectedMigrationResult struct {
|
||||
Organizations []Organization
|
||||
Organizations_v2 []Organization_v2
|
||||
|
@ -23,7 +23,7 @@ type ExpectedMigrationResult struct {
|
|||
func RunMigrationAndAssertResult(
|
||||
t *testing.T,
|
||||
title string,
|
||||
d *GoMethodsMongoDbDriver,
|
||||
d *mongodb.MongoDbGoMethodsDriver,
|
||||
file file.File,
|
||||
expected *ExpectedMigrationResult) {
|
||||
|
||||
|
@ -53,6 +53,10 @@ func RunMigrationAndAssertResult(
|
|||
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) {
|
||||
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)
|
||||
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
func TestMigrate(t *testing.T) {
|
||||
//host := os.Getenv("MONGODB_PORT_27017_TCP_ADDR")
|
||||
//port := os.Getenv("MONGODB_PORT_27017_TCP_PORT")
|
||||
|
@ -79,7 +79,9 @@ func TestMigrate(t *testing.T) {
|
|||
port := "27017"
|
||||
driverUrl := "mongodb://" + host + ":" + port
|
||||
|
||||
d := &GoMethodsMongoDbDriver{}
|
||||
//gomethods.RegisterMethodsReceiver("MyMgoMethodsReceiver", &MyMgoMethodsReceiver{})
|
||||
d := &mongodb.MongoDbGoMethodsDriver{}
|
||||
|
||||
if err := d.Initialize(driverUrl); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -106,6 +108,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
MyMgoMethodsReceiver
|
||||
V001_init_organizations_up
|
||||
V001_init_users_up
|
||||
`),
|
||||
|
@ -134,6 +137,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
MyMgoMethodsReceiver
|
||||
V002_organizations_rename_location_field_to_headquarters_up
|
||||
V002_change_user_cleo_to_cleopatra_up
|
||||
`),
|
||||
|
@ -162,6 +166,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: []byte(`
|
||||
MyMgoMethodsReceiver
|
||||
V002_change_user_cleo_to_cleopatra_down
|
||||
V002_organizations_rename_location_field_to_headquarters_down
|
||||
`),
|
||||
|
@ -190,6 +195,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Down,
|
||||
Content: []byte(`
|
||||
MyMgoMethodsReceiver
|
||||
V001_init_users_down
|
||||
V001_init_organizations_down
|
||||
`),
|
||||
|
@ -210,6 +216,7 @@ func TestMigrate(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Direction: direction.Up,
|
||||
Content: []byte(`
|
||||
MyMgoMethodsReceiver
|
||||
V001_init_organizations_up
|
||||
V001_init_users_up
|
||||
v001_non_existing_method_up
|
||||
|
@ -222,6 +229,26 @@ func TestMigrate(t *testing.T) {
|
|||
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")},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, m := range migrations {
|
|
@ -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