From 570e4b42be4f85e573e605e6885bfa64951a49f2 Mon Sep 17 00:00:00 2001 From: dimag Date: Tue, 9 Aug 2016 16:47:34 +0300 Subject: [PATCH] - 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 --- driver/gomethods/gomethods_migrator.go | 105 ++++++------- driver/gomethods/gomethods_migrator_test.go | 144 +++++++----------- driver/gomethods/gomethods_registry.go | 30 ++++ driver/gomethods/mongodb/mongodb.go | 134 ++++++++++++++++ driver/gomethods/mongodb/mongodb_template.go | 107 ------------- .../mongodb_example/methods_receiver.go | 129 ++++++++++++++++ .../mongodb_test.go | 85 +++++++---- driver/gomethods/usage_examples/mongodb.go | 140 ----------------- 8 files changed, 462 insertions(+), 412 deletions(-) create mode 100644 driver/gomethods/gomethods_registry.go create mode 100644 driver/gomethods/mongodb/mongodb.go delete mode 100644 driver/gomethods/mongodb/mongodb_template.go create mode 100644 driver/gomethods/mongodb_example/methods_receiver.go rename driver/gomethods/{usage_examples => mongodb_example}/mongodb_test.go (71%) delete mode 100644 driver/gomethods/usage_examples/mongodb.go diff --git a/driver/gomethods/gomethods_migrator.go b/driver/gomethods/gomethods_migrator.go index 0ff848a..2ee0192 100644 --- a/driver/gomethods/gomethods_migrator.go +++ b/driver/gomethods/gomethods_migrator.go @@ -2,40 +2,57 @@ 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) } +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 - Err error + Err error } 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 { @@ -51,14 +68,15 @@ 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-- { + 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 - methods = append(methods, methodName) + } 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 } diff --git a/driver/gomethods/gomethods_migrator_test.go b/driver/gomethods/gomethods_migrator_test.go index a50d6c1..eb33103 100644 --- a/driver/gomethods/gomethods_migrator_test.go +++ b/driver/gomethods/gomethods_migrator_test.go @@ -10,133 +10,105 @@ 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 { - return nil +func (invoker *FakeGoMethodsInvoker) IsValid(methodName string, methodReceiver interface{}) bool { + if methodName == "V001_some_non_existing_method_up" { + return false + } + + return true } -func (driver *FakeGoMethodsDriver) Close() error { - return nil -} +func (invoker *FakeGoMethodsInvoker) Invoke(methodName string, methodReceiver interface{}) error { + invoker.InvokedMethods = append(invoker.InvokedMethods, methodName) -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 + if methodName == "V001_some_failing_method_up" || methodName == "V001_some_failing_method_down" { + return &MethodInvocationFailedError{ + MethodName: methodName, + Err: SomeError{}, + } + } else { + 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 (e SomeError) Error() string { return "Some error happened" } func TestMigrate(t *testing.T) { cases := []struct { - name string - file file.File + name string + file file.File expectedInvokedMethods []string - expectedErrors []error - expectRollback bool + expectedErrors []error + expectRollback bool }{ { name: "up migration invokes up methods", - file: file.File { + file: file.File{ Path: "/foobar", FileName: "001_foobar.up.gm", Version: 1, Name: "foobar", Direction: direction.Up, Content: []byte(` + FakeMethodsReceiver V001_init_organizations_up V001_init_users_up `), }, expectedInvokedMethods: []string{"V001_init_organizations_up", "V001_init_users_up"}, - expectedErrors: []error{}, + expectedErrors: []error{}, }, { name: "down migration invoked down methods", - file: file.File { + file: file.File{ Path: "/foobar", FileName: "001_foobar.down.gm", Version: 1, Name: "foobar", Direction: direction.Down, Content: []byte(` + FakeMethodsReceiver V001_init_users_down V001_init_organizations_down `), }, expectedInvokedMethods: []string{"V001_init_users_down", "V001_init_organizations_down"}, - expectedErrors: []error{}, + expectedErrors: []error{}, }, { name: "up migration: non-existing method causes migration not to execute", - file: file.File { + file: file.File{ Path: "/foobar", FileName: "001_foobar.up.gm", Version: 1, Name: "foobar", Direction: direction.Up, Content: []byte(` + FakeMethodsReceiver V001_init_organizations_up V001_init_users_up V001_some_non_existing_method_up `), }, 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", - file: file.File { + file: file.File{ Path: "/foobar", FileName: "001_foobar.up.gm", Version: 1, Name: "foobar", Direction: direction.Up, Content: []byte(` + FakeMethodsReceiver V001_init_organizations_up V001_some_failing_method_up V001_init_users_up @@ -146,20 +118,21 @@ func TestMigrate(t *testing.T) { "V001_init_organizations_up", "V001_some_failing_method_up", }, - expectedErrors: []error{ &MethodInvocationFailedError{ + expectedErrors: []error{&MethodInvocationFailedError{ MethodName: "V001_some_failing_method_up", - Err: SomeError{}, + Err: SomeError{}, }}, }, { name: "down migration: failing method stops migration", - file: file.File { + file: file.File{ Path: "/foobar", FileName: "001_foobar.down.gm", Version: 1, Name: "foobar", Direction: direction.Down, Content: []byte(` + FakeMethodsReceiver V001_init_users_down V001_some_failing_method_down V001_init_organizations_down @@ -169,21 +142,22 @@ func TestMigrate(t *testing.T) { "V001_init_users_down", "V001_some_failing_method_down", }, - expectedErrors: []error{ &MethodInvocationFailedError{ + expectedErrors: []error{&MethodInvocationFailedError{ MethodName: "V001_some_failing_method_down", - Err: SomeError{}, + Err: SomeError{}, }}, }, { - name: "up migration: failing method causes rollback in rollback mode", + name: "up migration: failing method causes rollback in rollback mode", expectRollback: true, - file: file.File { + file: file.File{ Path: "/foobar", FileName: "001_foobar.up.gm", Version: 1, Name: "foobar", Direction: direction.Up, Content: []byte(` + FakeMethodsReceiver V001_init_organizations_up V001_init_users_up V001_some_failing_method_up @@ -196,21 +170,22 @@ func TestMigrate(t *testing.T) { "V001_init_users_down", "V001_init_organizations_down", }, - expectedErrors: []error{ &MethodInvocationFailedError{ + expectedErrors: []error{&MethodInvocationFailedError{ MethodName: "V001_some_failing_method_up", - Err: SomeError{}, + Err: SomeError{}, }}, }, { - name: "down migration: failing method causes rollback in rollback mode", + name: "down migration: failing method causes rollback in rollback mode", expectRollback: true, - file: file.File { + file: file.File{ Path: "/foobar", FileName: "001_foobar.down.gm", Version: 1, Name: "foobar", Direction: direction.Down, Content: []byte(` + FakeMethodsReceiver V001_init_users_down V001_some_failing_method_down V001_init_organizations_down @@ -221,18 +196,20 @@ func TestMigrate(t *testing.T) { "V001_some_failing_method_down", "V001_init_users_up", }, - expectedErrors: []error{ &MethodInvocationFailedError{ + expectedErrors: []error{&MethodInvocationFailedError{ MethodName: "V001_some_failing_method_down", - Err: SomeError{}, + 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,11 +235,9 @@ func TestMigrate(t *testing.T) { } } - - func TestGetRollbackToMethod(t *testing.T) { cases := []struct { - method string + method string expectedRollbackMethod string }{ {"some_method_up", "some_method_down"}, @@ -282,7 +257,7 @@ func TestGetRollbackToMethod(t *testing.T) { } func TestReverseInPlace(t *testing.T) { - methods := []string { + methods := []string{ "method1_down", "method2_down", "method3_down", @@ -290,7 +265,7 @@ func TestReverseInPlace(t *testing.T) { "method5_down", } - expectedReversedMethods := []string { + expectedReversedMethods := []string{ "method5_down", "method4_down", "method3_down", @@ -304,4 +279,3 @@ func TestReverseInPlace(t *testing.T) { t.Errorf("Expected reverse methods %v but got %v", expectedReversedMethods, methods) } } - diff --git a/driver/gomethods/gomethods_registry.go b/driver/gomethods/gomethods_registry.go new file mode 100644 index 0000000..78ac84c --- /dev/null +++ b/driver/gomethods/gomethods_registry.go @@ -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 +} diff --git a/driver/gomethods/mongodb/mongodb.go b/driver/gomethods/mongodb/mongodb.go new file mode 100644 index 0000000..0acad17 --- /dev/null +++ b/driver/gomethods/mongodb/mongodb.go @@ -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 +} diff --git a/driver/gomethods/mongodb/mongodb_template.go b/driver/gomethods/mongodb/mongodb_template.go deleted file mode 100644 index 1edbe78..0000000 --- a/driver/gomethods/mongodb/mongodb_template.go +++ /dev/null @@ -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 - } - } -} diff --git a/driver/gomethods/mongodb_example/methods_receiver.go b/driver/gomethods/mongodb_example/methods_receiver.go new file mode 100644 index 0000000..24433e9 --- /dev/null +++ b/driver/gomethods/mongodb_example/methods_receiver.go @@ -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) +} diff --git a/driver/gomethods/usage_examples/mongodb_test.go b/driver/gomethods/mongodb_example/mongodb_test.go similarity index 71% rename from driver/gomethods/usage_examples/mongodb_test.go rename to driver/gomethods/mongodb_example/mongodb_test.go index a57c51d..2ef2797 100644 --- a/driver/gomethods/usage_examples/mongodb_test.go +++ b/driver/gomethods/mongodb_example/mongodb_test.go @@ -1,4 +1,4 @@ -package usage_examples +package mongodb_example import ( "testing" @@ -6,24 +6,24 @@ 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 []Organization Organizations_v2 []Organization_v2 - Users []User - Errors []error + Users []User + Errors []error } 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) } @@ -93,8 +95,8 @@ func TestMigrate(t *testing.T) { date3, _ := time.Parse(SHORT_DATE_LAYOUT, "2008-Apr-28") migrations := []struct { - name string - file file.File + name string + file file.File expectedResult ExpectedMigrationResult }{ { @@ -106,15 +108,16 @@ func TestMigrate(t *testing.T) { Name: "foobar", Direction: direction.Up, Content: []byte(` + MyMgoMethodsReceiver V001_init_organizations_up V001_init_users_up `), }, expectedResult: ExpectedMigrationResult{ Organizations: []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}, + {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}, }, Organizations_v2: []Organization_v2{}, Users: []User{ @@ -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 `), @@ -141,9 +145,9 @@ func TestMigrate(t *testing.T) { expectedResult: ExpectedMigrationResult{ Organizations: []Organization{}, Organizations_v2: []Organization_v2{ - {Id: OrganizationIds[0], Name: "Amazon", Headquarters:"Seattle", DateFounded: date1}, - {Id: OrganizationIds[1], Name: "Google", Headquarters:"Mountain View", DateFounded: date2}, - {Id: OrganizationIds[2], Name: "JFrog", Headquarters:"Santa Clara", DateFounded: date3}, + {Id: OrganizationIds[0], Name: "Amazon", Headquarters: "Seattle", DateFounded: date1}, + {Id: OrganizationIds[1], Name: "Google", Headquarters: "Mountain View", DateFounded: date2}, + {Id: OrganizationIds[2], Name: "JFrog", Headquarters: "Santa Clara", DateFounded: date3}, }, Users: []User{ {Id: UserIds[0], Name: "Alex"}, @@ -162,15 +166,16 @@ 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 `), }, expectedResult: ExpectedMigrationResult{ Organizations: []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}, + {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}, }, Organizations_v2: []Organization_v2{}, Users: []User{ @@ -190,15 +195,16 @@ func TestMigrate(t *testing.T) { Name: "foobar", Direction: direction.Down, Content: []byte(` + MyMgoMethodsReceiver V001_init_users_down V001_init_organizations_down `), }, expectedResult: ExpectedMigrationResult{ - Organizations: []Organization{}, + Organizations: []Organization{}, Organizations_v2: []Organization_v2{}, - Users: []User{}, - Errors: []error{}, + Users: []User{}, + Errors: []error{}, }, }, { @@ -210,16 +216,37 @@ 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 `), }, expectedResult: ExpectedMigrationResult{ - Organizations: []Organization{}, + Organizations: []Organization{}, Organizations_v2: []Organization_v2{}, - Users: []User{}, - Errors: []error{ gomethods.MissingMethodError("v001_non_existing_method_up") }, + Users: []User{}, + 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")}, }, }, } diff --git a/driver/gomethods/usage_examples/mongodb.go b/driver/gomethods/usage_examples/mongodb.go deleted file mode 100644 index e7f30c0..0000000 --- a/driver/gomethods/usage_examples/mongodb.go +++ /dev/null @@ -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) -} \ No newline at end of file