From f26e535fd8e3290574186db17b0b703da049f582 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 18 Sep 2019 13:40:36 +0300 Subject: [PATCH] object migrations --- core/engine.go | 2 +- core/migrate.go | 260 ++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 7 +- go.sum | 17 ++++ 4 files changed, 280 insertions(+), 6 deletions(-) create mode 100644 core/migrate.go diff --git a/core/engine.go b/core/engine.go index 8b606cc..56fbb48 100644 --- a/core/engine.go +++ b/core/engine.go @@ -4,8 +4,8 @@ import ( "html/template" "github.com/gin-gonic/gin" - "github.com/op/go-logging" "github.com/gobuffalo/packr/v2" + "github.com/op/go-logging" ) // Engine struct diff --git a/core/migrate.go b/core/migrate.go new file mode 100644 index 0000000..3517237 --- /dev/null +++ b/core/migrate.go @@ -0,0 +1,260 @@ +package core + +import ( + "sort" + + "github.com/jinzhu/gorm" + "github.com/pkg/errors" + "gopkg.in/gormigrate.v1" +) + +// migrations default GORMigrate tool +var migrations *Migrate + +// Migrate tool, decorates gormigrate.Migration in order to provide better interface & versioning +type Migrate struct { + db *gorm.DB + first *gormigrate.Migration + versions []string + migrations map[string]*gormigrate.Migration + GORMigrate *gormigrate.Gormigrate + prepared bool +} + +// MigrationInfo with migration info +type MigrationInfo struct { + ID string `gorm:"column:id; type:varchar(255)"` +} + +// TableName for MigrationInfo +func (MigrationInfo) TableName() string { + return "migrations" +} + +// Migrations returns default migrate +func Migrations() *Migrate { + if migrations == nil { + migrations = &Migrate{ + db: nil, + prepared: false, + migrations: map[string]*gormigrate.Migration{}, + } + } + + return migrations +} + +// Add GORMigrate to migrate +func (m *Migrate) Add(migration *gormigrate.Migration) { + if migration == nil { + return + } + + m.migrations[migration.ID] = migration +} + +// SetORM to migrate +func (m *Migrate) SetDB(db *gorm.DB) *Migrate { + m.db = db + return m +} + +// Migrate all, including schema initialization +func (m *Migrate) Migrate() error { + if err := m.prepareMigrations(); err != nil { + return err + } + + if len(m.migrations) > 0 { + return m.GORMigrate.Migrate() + } + + return nil +} + +// Rollback all migrations +func (m *Migrate) Rollback() error { + if err := m.prepareMigrations(); err != nil { + return err + } + + if err := m.GORMigrate.RollbackTo(m.first.ID); err == nil { + if err := m.GORMigrate.RollbackMigration(m.first); err == nil { + return nil + } else { + return err + } + } else { + return err + } +} + +// MigrateTo specified version +func (m *Migrate) MigrateTo(version string) error { + if err := m.prepareMigrations(); err != nil { + return err + } + + current := m.Current() + switch { + case current > version: + return m.GORMigrate.RollbackTo(version) + case current < version: + return m.GORMigrate.MigrateTo(version) + default: + return nil + } +} + +// MigrateNextTo migrate to next version from specified version +func (m *Migrate) MigrateNextTo(version string) error { + if err := m.prepareMigrations(); err != nil { + return err + } + + if next, err := m.NextFrom(version); err == nil { + current := m.Current() + switch { + case current > next: + return m.GORMigrate.RollbackTo(next) + case current < next: + return m.GORMigrate.MigrateTo(next) + default: + return nil + } + } else { + return nil + } +} + +// MigratePreviousTo migrate to previous version from specified version +func (m *Migrate) MigratePreviousTo(version string) error { + if err := m.prepareMigrations(); err != nil { + return err + } + + if prev, err := m.PreviousFrom(version); err == nil { + current := m.Current() + switch { + case current > prev: + return m.GORMigrate.RollbackTo(prev) + case current < prev: + return m.GORMigrate.MigrateTo(prev) + default: + return nil + } + } else { + return nil + } +} + +// RollbackTo specified version +func (m *Migrate) RollbackTo(version string) error { + if err := m.prepareMigrations(); err != nil { + return err + } + + return m.GORMigrate.RollbackTo(version) +} + +// Current migration version +func (m *Migrate) Current() string { + var migrationInfo MigrationInfo + + if m.db == nil { + return "0" + } + + if !m.db.HasTable(MigrationInfo{}) { + m.db.CreateTable(MigrationInfo{}) + return "0" + } + + if err := m.db.Last(&migrationInfo).Error; err == nil { + return migrationInfo.ID + } else { + return "0" + } +} + +// NextFrom returns next version from passed version +func (m *Migrate) NextFrom(version string) (string, error) { + for key, ver := range m.versions { + if ver == version { + if key < (len(m.versions) - 1) { + return m.versions[key+1], nil + } else { + return "", errors.New("this is last migration") + } + } + } + + return "", errors.New("cannot find specified migration") +} + +// PreviousFrom returns previous version from passed version +func (m *Migrate) PreviousFrom(version string) (string, error) { + for key, ver := range m.versions { + if ver == version { + if key > 0 { + return m.versions[key-1], nil + } else { + return "", errors.New("this is first migration") + } + } + } + + return "", errors.New("cannot find specified migration") +} + +// Close db connection +func (m *Migrate) Close() error { + return m.db.Close() +} + +// prepareMigrations prepare migrate +func (m *Migrate) prepareMigrations() error { + var ( + keys []string + migrations []*gormigrate.Migration + ) + + if m.db == nil { + return errors.New("db must not be nil") + } + + if m.prepared { + return nil + } + + for key := range m.migrations { + keys = append(keys, key) + } + + sort.Strings(keys) + m.versions = keys + + if len(keys) > 0 { + if i, ok := m.migrations[keys[0]]; ok { + m.first = i + } + } + + for _, key := range keys { + if i, ok := m.migrations[key]; ok { + migrations = append(migrations, i) + } + } + + options := &gormigrate.Options{ + TableName: gormigrate.DefaultOptions.TableName, + IDColumnName: gormigrate.DefaultOptions.IDColumnName, + IDColumnSize: gormigrate.DefaultOptions.IDColumnSize, + UseTransaction: true, + ValidateUnknownMigrations: true, + } + + m.GORMigrate = gormigrate.New(m.db, options, migrations) + m.prepared = true + return nil +} diff --git a/go.mod b/go.mod index 3e278fd..c16d2bd 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,14 @@ require ( github.com/aws/aws-sdk-go v1.23.9 github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 // indirect github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd // indirect - github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect github.com/getsentry/raven-go v0.0.0-20180903072508-084a9de9eb03 github.com/gin-contrib/multitemplate v0.0.0-20180827023943-5799bbbb6dce github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.3.0 - github.com/go-sql-driver/mysql v1.4.1 // indirect github.com/gobuffalo/packr/v2 v2.6.0 github.com/golang/protobuf v1.3.2 // indirect github.com/google/go-querystring v1.0.0 // indirect - github.com/jinzhu/gorm v1.9.1 - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.0.1 // indirect + github.com/jinzhu/gorm v1.9.10 github.com/lib/pq v1.2.0 // indirect github.com/mattn/go-isatty v0.0.9 // indirect github.com/mattn/go-sqlite3 v1.11.0 // indirect @@ -32,5 +28,6 @@ require ( golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 golang.org/x/tools v0.0.0-20190830082254-f340ed3ae274 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 + gopkg.in/gormigrate.v1 v1.6.0 // indirect gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index b0e6ed1..abb9f4e 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= @@ -25,6 +26,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd h1:DoaaxHqzWPQCWKSTmsi8UDSiFqxbfue+Xt+qi/BFKb8= github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd/go.mod h1:uU0N10vx1abI4qeVe79CxepBP6PPREVTgMS5Gx6/mOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -51,6 +54,7 @@ github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSC github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= github.com/gobuffalo/packr/v2 v2.6.0/go.mod h1:sgEE1xNZ6G0FNN5xn9pevVu4nywaxHvgup67xisti08= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -79,8 +83,14 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jinzhu/gorm v1.9.1 h1:lDSDtsCt5AGGSKTs8AHlSDbbgif4G4+CKJ8ETBDVHTA= github.com/jinzhu/gorm v1.9.1/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= +github.com/jinzhu/gorm v1.9.2 h1:lCvgEaqe/HVE+tjAR2mt4HbbHAZsQOv3XAZiEZV37iw= +github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= +github.com/jinzhu/gorm v1.9.10 h1:HvrsqdhCW78xpJF67g1hMxS6eCToo9PZH4LDB8WKPac= +github.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY= +github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v0.0.0-20181116074157-8ec929ed50c3/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc= github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= @@ -96,6 +106,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -103,6 +115,7 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -161,6 +174,7 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= @@ -219,6 +233,7 @@ golang.org/x/tools v0.0.0-20190830082254-f340ed3ae274/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -233,6 +248,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI= +gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=