From 16494fcb687d7264da9a15a4560f86ca1be3545c Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Fri, 4 Mar 2022 03:06:51 +0200 Subject: [PATCH 01/15] Add rollback. --- Makefile | 2 +- cmd/gloat/main.go | 73 +++++++++++++++++++++++++++++++--------- gloat.go | 9 +++++ go.mod | 2 +- migration.go | 84 +++++++++++++++++++++++++++++++++++++++++------ store.go | 62 ++++++++++++++++++++++++---------- 6 files changed, 187 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index ceb7dcc..88273d6 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: build build: @mkdir -p bin - @go build -o bin/gloat github.com/gsamokovarov/gloat/cmd/gloat + @go build -o bin/gloat github.com/webedx-spark/gloat/cmd/gloat .PHONY: test test: diff --git a/cmd/gloat/main.go b/cmd/gloat/main.go index d0fd504..8473e09 100644 --- a/cmd/gloat/main.go +++ b/cmd/gloat/main.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "github.com/gsamokovarov/gloat" + "github.com/webedx-spark/gloat" _ "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" @@ -22,22 +22,28 @@ const usage = `Usage gloat: [OPTION ...] [COMMAND ...] Gloat is a Go SQL migration utility. Commands: - new Create a new migration folder - up Apply new migrations - down Revert the last applied migration + new Create a new migration folder + up Apply new migrations + down Revert the last applied migration + to Migrate to versionTag. If there are migrations with that versionTag + applied, they will be reverted. If the versionTag is the one used + as option (or loaded from env) new migrations will be applied. Options: -src The folder with migrations - (default $DATABASE_SRC or db/migrations) + (default $DATABASE_SRC or database/migrations) -url The database connection URL (default $DATABASE_URL) + -versionTag The version tag + (default $VERSION_TAG) -help Show this message ` type arguments struct { - url string - src string - rest []string + url string + src string + versionTag string + rest []string } func main() { @@ -56,6 +62,8 @@ func main() { err = downCmd(args) case "new": err = newCmd(args) + case "to": + err = toCmd(args) default: fmt.Fprintf(os.Stderr, usage) os.Exit(2) @@ -81,7 +89,7 @@ func upCmd(args arguments) error { appliedMigrations := map[int64]bool{} for _, migration := range migrations { - fmt.Printf("Applying: %d...\n", migration.Version) + fmt.Printf("Applying: [%s] %d...\n", args.versionTag, migration.Version) if err := gl.Apply(migration); err != nil { return err @@ -97,6 +105,36 @@ func upCmd(args arguments) error { return nil } +func toCmd(args arguments) error { + gl, err := setupGloat(args) + if err != nil { + return err + } + if len(args.rest) < 2 { + return errors.New("migrate to requires a versionTag to migrate to") + } + + versionTag := args.rest[1] + if args.versionTag == versionTag { + upCmd(args) + } else { + migrations, err := gl.AppliedAfter(versionTag) + if err != nil { + return err + } + + for _, migration := range migrations { + fmt.Printf("\nReverting: [%s] %d...\n", migration.VersionTag, migration.Version) + + if err := gl.Revert(migration); err != nil { + return err + } + } + } + + return nil +} + func downCmd(args arguments) error { gl, err := setupGloat(args) if err != nil { @@ -109,11 +147,11 @@ func downCmd(args arguments) error { } if migration == nil { - fmt.Printf("No migrations to apply\n") + fmt.Printf("No migrations to revert\n") return nil } - fmt.Printf("Reverting: %d...\n", migration.Version) + fmt.Printf("Reverting: [%s] %d...\n", migration.VersionTag, migration.Version) if err := gl.Revert(migration); err != nil { return err @@ -163,12 +201,16 @@ func parseArguments() arguments { srcDefault := os.Getenv("DATABASE_SRC") if srcDefault == "" { - srcDefault = "db/migrations" + srcDefault = "database/migrations" } srcUsage := `the folder with migrations` + versionTagDefault := os.Getenv("VERSION_TAG") + versionTagUsage := `version_tag of applied migrations` + flag.StringVar(&args.url, "url", urlDefault, urlUsage) flag.StringVar(&args.src, "src", srcDefault, srcUsage) + flag.StringVar(&args.versionTag, "versionTag", versionTagDefault, versionTagUsage) flag.Usage = func() { fmt.Fprintf(os.Stderr, usage) } @@ -196,9 +238,10 @@ func setupGloat(args arguments) (*gloat.Gloat, error) { } return &gloat.Gloat{ - Store: store, - Source: gloat.NewFileSystemSource(args.src), - Executor: gloat.NewSQLExecutor(db), + Store: store, + Source: gloat.NewFileSystemSource(args.src), + Executor: gloat.NewSQLExecutor(db), + VersionTag: args.versionTag, }, nil } diff --git a/gloat.go b/gloat.go index f88d774..4becf1f 100644 --- a/gloat.go +++ b/gloat.go @@ -16,6 +16,13 @@ type Gloat struct { // Executor applies migrations and marks the newly applied migration // versions in the Store. Executor Executor + + VersionTag string +} + +// AppliedAfter returns migrations that were applied after a given version tag +func (c *Gloat) AppliedAfter(versionTag string) (Migrations, error) { + return AppliedAfter(c.Store, c.Source, versionTag) } // Unapplied returns the unapplied migrations in the current gloat. @@ -48,6 +55,7 @@ func (c *Gloat) Current() (*Migration, error) { migration := availableMigrations[i] if migration.Version == currentMigration.Version { + migration.VersionTag = currentMigration.VersionTag return migration, nil } } @@ -57,6 +65,7 @@ func (c *Gloat) Current() (*Migration, error) { // Apply applies a migration. func (c *Gloat) Apply(migration *Migration) error { + migration.VersionTag = c.VersionTag return c.Executor.Up(migration, c.Store) } diff --git a/go.mod b/go.mod index 8fb451b..14975f9 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/gsamokovarov/gloat +module github.com/webedx-spark/gloat require ( github.com/go-sql-driver/mysql v1.4.0 diff --git a/migration.go b/migration.go index bfb840e..dc94b81 100644 --- a/migration.go +++ b/migration.go @@ -1,6 +1,7 @@ package gloat import ( + "errors" "fmt" "path/filepath" "regexp" @@ -22,11 +23,12 @@ var ( // determine the order of which the migrations would be executed. The path is // the name in a store. type Migration struct { - UpSQL []byte - DownSQL []byte - Path string - Version int64 - Options MigrationOptions + UpSQL []byte + DownSQL []byte + Path string + Version int64 + VersionTag string + Options MigrationOptions } // Reversible returns true if the migration DownSQL content is present. E.g. if @@ -116,19 +118,21 @@ type Migrations []*Migration // Except selects migrations that does not exist in the current ones. func (m Migrations) Except(migrations Migrations) (excepted Migrations) { // Mark the current transactions. - current := make(map[int64]bool) + current := make(map[int64]string) for _, migration := range m { - current[migration.Version] = true + current[migration.Version] = migration.VersionTag } // Mark the ones in the migrations set, which we do have to get. - new := make(map[int64]bool) + new := make(map[int64]string) for _, migration := range migrations { - new[migration.Version] = true + new[migration.Version] = migration.VersionTag } for _, migration := range migrations { - if new[migration.Version] && !current[migration.Version] { + _, will := new[migration.Version] + _, has := current[migration.Version] + if will && !has { excepted = append(excepted, migration) } } @@ -136,6 +140,32 @@ func (m Migrations) Except(migrations Migrations) (excepted Migrations) { return } +// Intersect selects migrations that does exist in the current ones. +func (m Migrations) Intersect(migrations Migrations) (intersect Migrations) { + // Mark the current transactions. + store := make(map[int64]string) + for _, migration := range m { + store[migration.Version] = migration.VersionTag + } + + // Mark the ones in the migrations set, which we do have to get. + source := make(map[int64]string) + for _, migration := range migrations { + source[migration.Version] = migration.VersionTag + } + + for _, migration := range migrations { + _, will := source[migration.Version] + versionTag, has := store[migration.Version] + if will && has { + migration.VersionTag = versionTag + intersect = append(intersect, migration) + } + } + + return +} + // Implementation for the sort.Sort interface. func (m Migrations) Len() int { return len(m) } @@ -145,6 +175,9 @@ func (m Migrations) Swap(i, j int) { m[i], m[j] = m[j], m[i] } // Sort is a convenience sorting method. func (m Migrations) Sort() { sort.Sort(m) } +// ReverseSort is a convenience sorting method. +func (m Migrations) ReverseSort() { sort.Sort(sort.Reverse(m)) } + // Current returns the latest applied migration. Can be nil, if the migrations // are empty. func (m Migrations) Current() *Migration { @@ -157,6 +190,37 @@ func (m Migrations) Current() *Migration { return m[len(m)-1] } +// AppliedAfter selects the applied migrations from a Store after a given versionTag. +func AppliedAfter(store Source, source Source, versionTag string) (Migrations, error) { + var appliedAfter Migrations + appliedMigrations, err := store.Collect() + if err != nil { + return nil, err + } + + found := false + for _, migration := range appliedMigrations { + if migration.VersionTag == versionTag { + found = true + break + } + appliedAfter = append(appliedAfter, migration) + } + if !found { + return nil, errors.New("versionTag not found") + } + appliedAfter.ReverseSort() + + availableMigrations, err := source.Collect() + if err != nil { + return nil, err + } + + intersect := appliedAfter.Intersect(availableMigrations) + intersect.ReverseSort() + return intersect, nil +} + // UnappliedMigrations selects the unapplied migrations from a Source. For a // migration to be unapplied it should not be present in the Store. func UnappliedMigrations(store, source Source) (Migrations, error) { diff --git a/store.go b/store.go index fe5dee6..ab84c02 100644 --- a/store.go +++ b/store.go @@ -16,6 +16,7 @@ type DatabaseStore struct { db SQLTransactor createTableStatement string + createIndexStatement string insertMigrationStatement string removeMigrationStatement string selectAllMigrationsStatement string @@ -31,7 +32,7 @@ func (s *DatabaseStore) Insert(migration *Migration, execer SQLExecer) error { return err } - _, err := execer.Exec(s.insertMigrationStatement, migration.Version) + _, err := execer.Exec(s.insertMigrationStatement, migration.Version, migration.VersionTag) return err } @@ -64,7 +65,7 @@ func (s *DatabaseStore) Collect() (migrations Migrations, err error) { for rows.Next() { migration := &Migration{} - if err = rows.Scan(&migration.Version); err != nil { + if err = rows.Scan(&migration.Version, &migration.VersionTag); err != nil { return } @@ -75,8 +76,15 @@ func (s *DatabaseStore) Collect() (migrations Migrations, err error) { } func (s *DatabaseStore) ensureSchemaTableExists() error { - _, err := s.db.Exec(s.createTableStatement) - return err + if _, err := s.db.Exec(s.createTableStatement); err != nil { + return err + } + + if _, err := s.db.Exec(s.createIndexStatement); err != nil { + return err + } + + return nil } // NewPostgreSQLStore creates a Store for PostgreSQL. @@ -85,17 +93,23 @@ func NewPostgreSQLStore(db SQLTransactor) Store { db: db, createTableStatement: ` CREATE TABLE IF NOT EXISTS schema_migrations ( - version BIGINT PRIMARY KEY NOT NULL + version BIGINT PRIMARY KEY NOT NULL, + version_tag text )`, + createIndexStatement: ` + CREATE INDEX IF NOT EXISTS schema_migrations_version_tag + ON schema_migrations (version_tag) + `, insertMigrationStatement: ` - INSERT INTO schema_migrations (version) - VALUES ($1)`, + INSERT INTO schema_migrations (version, version_tag) + VALUES ($1, $2)`, removeMigrationStatement: ` DELETE FROM schema_migrations WHERE version=$1`, selectAllMigrationsStatement: ` - SELECT version - FROM schema_migrations`, + SELECT version, version_tag + FROM schema_migrations + ORDER BY version DESC`, } } @@ -105,17 +119,23 @@ func NewMySQLStore(db SQLTransactor) Store { db: db, createTableStatement: ` CREATE TABLE IF NOT EXISTS schema_migrations ( - version BIGINT PRIMARY KEY NOT NULL + version BIGINT PRIMARY KEY NOT NULL, + version_tag text )`, + createIndexStatement: ` + CREATE INDEX IF NOT EXISTS schema_migrations_version_tag + ON schema_migrations (version_tag) + `, insertMigrationStatement: ` - INSERT INTO schema_migrations (version) - VALUES (?)`, + INSERT INTO schema_migrations (version, version_tag) + VALUES (?, ?)`, removeMigrationStatement: ` DELETE FROM schema_migrations WHERE version=?`, selectAllMigrationsStatement: ` - SELECT version - FROM schema_migrations`, + SELECT version, version_tag + FROM schema_migrations + ORDER BY version DESC`, } } @@ -126,15 +146,21 @@ func NewSQLite3Store(db SQLTransactor) Store { createTableStatement: ` CREATE TABLE IF NOT EXISTS schema_migrations ( version BIGINT PRIMARY KEY NOT NULL + version_tag text )`, insertMigrationStatement: ` - INSERT INTO schema_migrations (version) - VALUES (?)`, + INSERT INTO schema_migrations (version, version_tag) + VALUES (?, ?)`, + createIndexStatement: ` + CREATE INDEX IF NOT EXISTS schema_migrations_version_tag + ON schema_migrations (version_tag) + `, removeMigrationStatement: ` DELETE FROM schema_migrations WHERE version=?`, selectAllMigrationsStatement: ` - SELECT version - FROM schema_migrations`, + SELECT version, version_tag + FROM schema_migrations + ORDER BY version DESC`, } } From 5074d7429e0e1da013b198e8c241915b251f0126 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Wed, 9 Mar 2022 19:26:53 +0200 Subject: [PATCH 02/15] implement alternative solution --- cmd/gloat/main.go | 98 +++++++++++++++++++++++++++++++---------------- gloat.go | 24 ++++++++---- migration.go | 70 +++++++++++++++++++-------------- store.go | 38 +++++++++--------- 4 files changed, 144 insertions(+), 86 deletions(-) diff --git a/cmd/gloat/main.go b/cmd/gloat/main.go index 8473e09..ae4be6b 100644 --- a/cmd/gloat/main.go +++ b/cmd/gloat/main.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "path/filepath" + "strconv" "strings" "github.com/webedx-spark/gloat" @@ -25,25 +26,22 @@ Commands: new Create a new migration folder up Apply new migrations down Revert the last applied migration - to Migrate to versionTag. If there are migrations with that versionTag - applied, they will be reverted. If the versionTag is the one used - as option (or loaded from env) new migrations will be applied. + to Migrate to a given version (down to). + latest Latest migration in the source. + current Latest Applied migration. Options: -src The folder with migrations (default $DATABASE_SRC or database/migrations) -url The database connection URL (default $DATABASE_URL) - -versionTag The version tag - (default $VERSION_TAG) -help Show this message ` type arguments struct { - url string - src string - versionTag string - rest []string + url string + src string + rest []string } func main() { @@ -64,6 +62,10 @@ func main() { err = newCmd(args) case "to": err = toCmd(args) + case "latest": + err = latestCmd(args) + case "current": + err = currentCmd(args) default: fmt.Fprintf(os.Stderr, usage) os.Exit(2) @@ -89,7 +91,7 @@ func upCmd(args arguments) error { appliedMigrations := map[int64]bool{} for _, migration := range migrations { - fmt.Printf("Applying: [%s] %d...\n", args.versionTag, migration.Version) + fmt.Printf("Applying: %d...\n", migration.Version) if err := gl.Apply(migration); err != nil { return err @@ -105,30 +107,67 @@ func upCmd(args arguments) error { return nil } +func latestCmd(args arguments) error { + gl, err := setupGloat(args) + if err != nil { + return err + } + + latest, err := gl.Latest() + if err != nil { + return err + } + + if latest != nil { + fmt.Printf("%d", latest.Version) + } + return nil +} + +func currentCmd(args arguments) error { + gl, err := setupGloat(args) + if err != nil { + return err + } + + current, err := gl.Current() + if err != nil { + return err + } + + if current != nil { + fmt.Printf("%d", current.Version) + } + return nil +} + func toCmd(args arguments) error { gl, err := setupGloat(args) if err != nil { return err } if len(args.rest) < 2 { - return errors.New("migrate to requires a versionTag to migrate to") + return errors.New("migrate to requires a version to migrate to") } - versionTag := args.rest[1] - if args.versionTag == versionTag { - upCmd(args) - } else { - migrations, err := gl.AppliedAfter(versionTag) - if err != nil { - return err + version, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return err + } + + migrations, err := gl.AppliedAfter(version) + if err != nil { + if err == gl.ErrNotFound { + return upCmd(args) } + return err + } - for _, migration := range migrations { - fmt.Printf("\nReverting: [%s] %d...\n", migration.VersionTag, migration.Version) + for _, migration := range migrations { + fmt.Printf("\nReverting: %d...\n", migration.Version) - if err := gl.Revert(migration); err != nil { - return err - } + if err := gl.Revert(migration); err != nil { + return err } } @@ -151,7 +190,7 @@ func downCmd(args arguments) error { return nil } - fmt.Printf("Reverting: [%s] %d...\n", migration.VersionTag, migration.Version) + fmt.Printf("Reverting: %d...\n", migration.Version) if err := gl.Revert(migration); err != nil { return err @@ -205,12 +244,8 @@ func parseArguments() arguments { } srcUsage := `the folder with migrations` - versionTagDefault := os.Getenv("VERSION_TAG") - versionTagUsage := `version_tag of applied migrations` - flag.StringVar(&args.url, "url", urlDefault, urlUsage) flag.StringVar(&args.src, "src", srcDefault, srcUsage) - flag.StringVar(&args.versionTag, "versionTag", versionTagDefault, versionTagUsage) flag.Usage = func() { fmt.Fprintf(os.Stderr, usage) } @@ -238,10 +273,9 @@ func setupGloat(args arguments) (*gloat.Gloat, error) { } return &gloat.Gloat{ - Store: store, - Source: gloat.NewFileSystemSource(args.src), - Executor: gloat.NewSQLExecutor(db), - VersionTag: args.versionTag, + Store: store, + Source: gloat.NewFileSystemSource(args.src), + Executor: gloat.NewSQLExecutor(db), }, nil } diff --git a/gloat.go b/gloat.go index 4becf1f..2176158 100644 --- a/gloat.go +++ b/gloat.go @@ -1,6 +1,8 @@ package gloat -import "database/sql" +import ( + "database/sql" +) // Gloat glues all the components needed to apply and revert // migrations. @@ -16,13 +18,11 @@ type Gloat struct { // Executor applies migrations and marks the newly applied migration // versions in the Store. Executor Executor - - VersionTag string } // AppliedAfter returns migrations that were applied after a given version tag -func (c *Gloat) AppliedAfter(versionTag string) (Migrations, error) { - return AppliedAfter(c.Store, c.Source, versionTag) +func (c *Gloat) AppliedAfter(version int) (Migrations, error) { + return AppliedAfter(c.Store, c.Source, version) } // Unapplied returns the unapplied migrations in the current gloat. @@ -30,6 +30,17 @@ func (c *Gloat) Unapplied() (Migrations, error) { return UnappliedMigrations(c.Store, c.Source) } +// Latest returns the latest migration in the source. +func (c *Gloat) Latest() (*Migration, error) { + availableMigrations, err := c.Source.Collect() + if err != nil { + return nil, err + } + + latest := availableMigrations.Current() + return latest, nil +} + // Current returns the latest applied migration. Even if no error is returned, // the current migration can be nil. // @@ -55,7 +66,7 @@ func (c *Gloat) Current() (*Migration, error) { migration := availableMigrations[i] if migration.Version == currentMigration.Version { - migration.VersionTag = currentMigration.VersionTag + migration.AppliedAt = currentMigration.AppliedAt return migration, nil } } @@ -65,7 +76,6 @@ func (c *Gloat) Current() (*Migration, error) { // Apply applies a migration. func (c *Gloat) Apply(migration *Migration) error { - migration.VersionTag = c.VersionTag return c.Executor.Up(migration, c.Store) } diff --git a/migration.go b/migration.go index dc94b81..d4a6435 100644 --- a/migration.go +++ b/migration.go @@ -14,6 +14,7 @@ import ( var ( now = time.Now().UTC() + ErrNotFound = errors.New("version not found") nameNormalizerRe = regexp.MustCompile(`([a-z])([A-Z])`) versionFormat = "20060102150405" ) @@ -23,12 +24,12 @@ var ( // determine the order of which the migrations would be executed. The path is // the name in a store. type Migration struct { - UpSQL []byte - DownSQL []byte - Path string - Version int64 - VersionTag string - Options MigrationOptions + UpSQL []byte + DownSQL []byte + Path string + Version int64 + Options MigrationOptions + AppliedAt time.Time } // Reversible returns true if the migration DownSQL content is present. E.g. if @@ -85,11 +86,12 @@ func MigrationFromBytes(path string, read func(string) ([]byte, error)) (*Migrat } return &Migration{ - UpSQL: upSQL, - DownSQL: downSQL, - Path: path, - Version: version, - Options: options, + UpSQL: upSQL, + DownSQL: downSQL, + Path: path, + Version: version, + Options: options, + AppliedAt: time.Time{0}, }, nil } @@ -118,15 +120,15 @@ type Migrations []*Migration // Except selects migrations that does not exist in the current ones. func (m Migrations) Except(migrations Migrations) (excepted Migrations) { // Mark the current transactions. - current := make(map[int64]string) + current := make(map[int64]time.Time) for _, migration := range m { - current[migration.Version] = migration.VersionTag + current[migration.Version] = migration.AppliedAt } // Mark the ones in the migrations set, which we do have to get. - new := make(map[int64]string) + new := make(map[int64]time.Time) for _, migration := range migrations { - new[migration.Version] = migration.VersionTag + new[migration.Version] = migration.AppliedAt } for _, migration := range migrations { @@ -143,22 +145,22 @@ func (m Migrations) Except(migrations Migrations) (excepted Migrations) { // Intersect selects migrations that does exist in the current ones. func (m Migrations) Intersect(migrations Migrations) (intersect Migrations) { // Mark the current transactions. - store := make(map[int64]string) + store := make(map[int64]time.Time) for _, migration := range m { - store[migration.Version] = migration.VersionTag + store[migration.Version] = migration.AppliedAt } // Mark the ones in the migrations set, which we do have to get. - source := make(map[int64]string) + source := make(map[int64]time.Time) for _, migration := range migrations { - source[migration.Version] = migration.VersionTag + source[migration.Version] = migration.AppliedAt } for _, migration := range migrations { _, will := source[migration.Version] - versionTag, has := store[migration.Version] + appliedAt, has := store[migration.Version] if will && has { - migration.VersionTag = versionTag + migration.AppliedAt = appliedAt intersect = append(intersect, migration) } } @@ -167,10 +169,20 @@ func (m Migrations) Intersect(migrations Migrations) (intersect Migrations) { } // Implementation for the sort.Sort interface. +func (m Migrations) Len() int { return len(m) } + +// Less will sort by AppliedAt. If equal will sort by Version +func (m Migrations) Less(i, j int) bool { + if m[i].AppliedAt.Before(m[j].AppliedAt) { + return true + } + if m[i].AppliedAt.After(m[j].AppliedAt) { + return false + } + return m[i].Version < m[j].Version +} -func (m Migrations) Len() int { return len(m) } -func (m Migrations) Less(i, j int) bool { return m[i].Version < m[j].Version } -func (m Migrations) Swap(i, j int) { m[i], m[j] = m[j], m[i] } +func (m Migrations) Swap(i, j int) { m[i], m[j] = m[j], m[i] } // Sort is a convenience sorting method. func (m Migrations) Sort() { sort.Sort(m) } @@ -190,8 +202,8 @@ func (m Migrations) Current() *Migration { return m[len(m)-1] } -// AppliedAfter selects the applied migrations from a Store after a given versionTag. -func AppliedAfter(store Source, source Source, versionTag string) (Migrations, error) { +// AppliedAfter selects the applied migrations from a Store after a given version. +func AppliedAfter(store Source, source Source, version int64) (Migrations, error) { var appliedAfter Migrations appliedMigrations, err := store.Collect() if err != nil { @@ -200,15 +212,17 @@ func AppliedAfter(store Source, source Source, versionTag string) (Migrations, e found := false for _, migration := range appliedMigrations { - if migration.VersionTag == versionTag { + if migration.Version == version { found = true break } appliedAfter = append(appliedAfter, migration) } + if !found { - return nil, errors.New("versionTag not found") + return nil, ErrNotFound } + appliedAfter.ReverseSort() availableMigrations, err := source.Collect() diff --git a/store.go b/store.go index ab84c02..089c714 100644 --- a/store.go +++ b/store.go @@ -32,7 +32,7 @@ func (s *DatabaseStore) Insert(migration *Migration, execer SQLExecer) error { return err } - _, err := execer.Exec(s.insertMigrationStatement, migration.Version, migration.VersionTag) + _, err := execer.Exec(s.insertMigrationStatement, migration.Version, migration.AppliedAt) return err } @@ -65,7 +65,7 @@ func (s *DatabaseStore) Collect() (migrations Migrations, err error) { for rows.Next() { migration := &Migration{} - if err = rows.Scan(&migration.Version, &migration.VersionTag); err != nil { + if err = rows.Scan(&migration.Version, &migration.AppliedAt); err != nil { return } @@ -94,22 +94,22 @@ func NewPostgreSQLStore(db SQLTransactor) Store { createTableStatement: ` CREATE TABLE IF NOT EXISTS schema_migrations ( version BIGINT PRIMARY KEY NOT NULL, - version_tag text + applied_at timestamp without time zone default (now() at time zone 'utc') )`, createIndexStatement: ` - CREATE INDEX IF NOT EXISTS schema_migrations_version_tag - ON schema_migrations (version_tag) + CREATE INDEX IF NOT EXISTS schema_migrations_applied_at + ON schema_migrations (applied_at) `, insertMigrationStatement: ` - INSERT INTO schema_migrations (version, version_tag) + INSERT INTO schema_migrations (version, applied_at) VALUES ($1, $2)`, removeMigrationStatement: ` DELETE FROM schema_migrations WHERE version=$1`, selectAllMigrationsStatement: ` - SELECT version, version_tag + SELECT version, applied_at FROM schema_migrations - ORDER BY version DESC`, + ORDER BY applied_at DESC, version DESC`, } } @@ -120,14 +120,14 @@ func NewMySQLStore(db SQLTransactor) Store { createTableStatement: ` CREATE TABLE IF NOT EXISTS schema_migrations ( version BIGINT PRIMARY KEY NOT NULL, - version_tag text + applied_at TIMESTAMP DEFAULT UTC_TIMESTAMP )`, createIndexStatement: ` - CREATE INDEX IF NOT EXISTS schema_migrations_version_tag - ON schema_migrations (version_tag) + CREATE INDEX IF NOT EXISTS schema_migrations_applied_at + ON schema_migrations (applied_at) `, insertMigrationStatement: ` - INSERT INTO schema_migrations (version, version_tag) + INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, removeMigrationStatement: ` DELETE FROM schema_migrations @@ -135,7 +135,7 @@ func NewMySQLStore(db SQLTransactor) Store { selectAllMigrationsStatement: ` SELECT version, version_tag FROM schema_migrations - ORDER BY version DESC`, + ORDER BY applied_at DESC, version DESC`, } } @@ -146,21 +146,21 @@ func NewSQLite3Store(db SQLTransactor) Store { createTableStatement: ` CREATE TABLE IF NOT EXISTS schema_migrations ( version BIGINT PRIMARY KEY NOT NULL - version_tag text + applied_at DATETIME DEFAULT CURRENT_TIMESTAMP )`, insertMigrationStatement: ` - INSERT INTO schema_migrations (version, version_tag) + INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, createIndexStatement: ` - CREATE INDEX IF NOT EXISTS schema_migrations_version_tag - ON schema_migrations (version_tag) + CREATE INDEX IF NOT EXISTS schema_migrations_applied_at + ON schema_migrations (applied_at) `, removeMigrationStatement: ` DELETE FROM schema_migrations WHERE version=?`, selectAllMigrationsStatement: ` - SELECT version, version_tag + SELECT version, applied_at FROM schema_migrations - ORDER BY version DESC`, + ORDER BY applied_at DESC, version DESC`, } } From b4ab8b7bd8fd0b03543946b8c26dc47f1f8c46c8 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Wed, 9 Mar 2022 19:29:45 +0200 Subject: [PATCH 03/15] fix version parsing --- cmd/gloat/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gloat/main.go b/cmd/gloat/main.go index ae4be6b..3dc3129 100644 --- a/cmd/gloat/main.go +++ b/cmd/gloat/main.go @@ -150,7 +150,7 @@ func toCmd(args arguments) error { return errors.New("migrate to requires a version to migrate to") } - version, err := strconv.ParseInt(s, 10, 64) + version, err := strconv.ParseInt(args[1], 10, 64) if err != nil { return err } From 6096f93946478c712bf10b20b74de40d8114846b Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Wed, 9 Mar 2022 19:45:17 +0200 Subject: [PATCH 04/15] fix types --- gloat.go | 2 +- migration.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gloat.go b/gloat.go index 2176158..d596438 100644 --- a/gloat.go +++ b/gloat.go @@ -21,7 +21,7 @@ type Gloat struct { } // AppliedAfter returns migrations that were applied after a given version tag -func (c *Gloat) AppliedAfter(version int) (Migrations, error) { +func (c *Gloat) AppliedAfter(version int64) (Migrations, error) { return AppliedAfter(c.Store, c.Source, version) } diff --git a/migration.go b/migration.go index d4a6435..5862c0b 100644 --- a/migration.go +++ b/migration.go @@ -91,7 +91,7 @@ func MigrationFromBytes(path string, read func(string) ([]byte, error)) (*Migrat Path: path, Version: version, Options: options, - AppliedAt: time.Time{0}, + AppliedAt: time.UTC(), }, nil } From 3ce8b81b25737ea2a3b4a841653e15e575447fe1 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Wed, 9 Mar 2022 19:46:25 +0200 Subject: [PATCH 05/15] fix types --- migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migration.go b/migration.go index 5862c0b..4ad649a 100644 --- a/migration.go +++ b/migration.go @@ -91,7 +91,7 @@ func MigrationFromBytes(path string, read func(string) ([]byte, error)) (*Migrat Path: path, Version: version, Options: options, - AppliedAt: time.UTC(), + AppliedAt: time.Now(), }, nil } From 5e8e5f30352f046549addbfd43f5837f2ce9cdd4 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Wed, 9 Mar 2022 19:50:13 +0200 Subject: [PATCH 06/15] fix types --- cmd/gloat/main.go | 28 ++++++++++++++++++++++++++-- gloat.go | 5 +++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/cmd/gloat/main.go b/cmd/gloat/main.go index 3dc3129..66c6e69 100644 --- a/cmd/gloat/main.go +++ b/cmd/gloat/main.go @@ -29,6 +29,7 @@ Commands: to Migrate to a given version (down to). latest Latest migration in the source. current Latest Applied migration. + present List all present versions. Options: -src The folder with migrations @@ -66,6 +67,8 @@ func main() { err = latestCmd(args) case "current": err = currentCmd(args) + case "present": + err = presentCmd(args) default: fmt.Fprintf(os.Stderr, usage) os.Exit(2) @@ -124,6 +127,27 @@ func latestCmd(args arguments) error { return nil } +func presentCmd(args arguments) error { + gl, err := setupGloat(args) + if err != nil { + return err + } + + migrations, err := gl.Present() + if err != nil { + return err + } + + for i, m := range migrations { + fmt.Printf("%d", m.Version) + if i != len(migrations)-1 { + fmt.Print(", ") + } + } + + return nil +} + func currentCmd(args arguments) error { gl, err := setupGloat(args) if err != nil { @@ -150,14 +174,14 @@ func toCmd(args arguments) error { return errors.New("migrate to requires a version to migrate to") } - version, err := strconv.ParseInt(args[1], 10, 64) + version, err := strconv.ParseInt(args.rest[1], 10, 64) if err != nil { return err } migrations, err := gl.AppliedAfter(version) if err != nil { - if err == gl.ErrNotFound { + if err == gloat.ErrNotFound { return upCmd(args) } return err diff --git a/gloat.go b/gloat.go index d596438..3434bae 100644 --- a/gloat.go +++ b/gloat.go @@ -25,6 +25,11 @@ func (c *Gloat) AppliedAfter(version int64) (Migrations, error) { return AppliedAfter(c.Store, c.Source, version) } +// Present returns all available migrations. +func (c *Gloat) Present() (Migrations, error) { + return c.Source.Collect() +} + // Unapplied returns the unapplied migrations in the current gloat. func (c *Gloat) Unapplied() (Migrations, error) { return UnappliedMigrations(c.Store, c.Source) From 86dfd482d677858365100c5dabaa8db28c7c0413 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 10 Mar 2022 03:41:18 +0200 Subject: [PATCH 07/15] formatting. --- cmd/gloat/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gloat/main.go b/cmd/gloat/main.go index 66c6e69..61f1592 100644 --- a/cmd/gloat/main.go +++ b/cmd/gloat/main.go @@ -141,7 +141,7 @@ func presentCmd(args arguments) error { for i, m := range migrations { fmt.Printf("%d", m.Version) if i != len(migrations)-1 { - fmt.Print(", ") + fmt.Print(",") } } From d5da18d9c92298422785f3fade022d554f8d74b8 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Wed, 16 Mar 2022 13:28:41 +0200 Subject: [PATCH 08/15] Address PR comments and improve variable names. --- cmd/gloat/main.go | 4 ++-- migration.go | 54 ++++++++++++++++++++++++----------------------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/cmd/gloat/main.go b/cmd/gloat/main.go index 61f1592..32b6755 100644 --- a/cmd/gloat/main.go +++ b/cmd/gloat/main.go @@ -62,7 +62,7 @@ func main() { case "new": err = newCmd(args) case "to": - err = toCmd(args) + err = migrateToCmd(args) case "latest": err = latestCmd(args) case "current": @@ -165,7 +165,7 @@ func currentCmd(args arguments) error { return nil } -func toCmd(args arguments) error { +func migrateToCmd(args arguments) error { gl, err := setupGloat(args) if err != nil { return err diff --git a/migration.go b/migration.go index 4ad649a..e041eb8 100644 --- a/migration.go +++ b/migration.go @@ -12,8 +12,6 @@ import ( ) var ( - now = time.Now().UTC() - ErrNotFound = errors.New("version not found") nameNormalizerRe = regexp.MustCompile(`([a-z])([A-Z])`) versionFormat = "20060102150405" @@ -91,7 +89,7 @@ func MigrationFromBytes(path string, read func(string) ([]byte, error)) (*Migrat Path: path, Version: version, Options: options, - AppliedAt: time.Now(), + AppliedAt: time.Now().UTC(), }, nil } @@ -101,7 +99,7 @@ func generateMigrationPath(version int64, str string) string { } func generateVersion() int64 { - version, _ := strconv.ParseInt(now.Format(versionFormat), 10, 64) + version, _ := strconv.ParseInt(time.Now().UTC().Format(versionFormat), 10, 64) return version } @@ -118,24 +116,26 @@ func versionFromPath(path string) (int64, error) { type Migrations []*Migration // Except selects migrations that does not exist in the current ones. -func (m Migrations) Except(migrations Migrations) (excepted Migrations) { +// NB: other should be full migrations (e.g. from source) +// the ones from store are only metadata migrations (they don't include SQL statements) +func (m Migrations) Except(other Migrations) (result Migrations) { // Mark the current transactions. current := make(map[int64]time.Time) - for _, migration := range m { - current[migration.Version] = migration.AppliedAt + for _, migrationMetadata := range m { + current[migrationMetadata.Version] = migrationMetadata.AppliedAt } // Mark the ones in the migrations set, which we do have to get. new := make(map[int64]time.Time) - for _, migration := range migrations { - new[migration.Version] = migration.AppliedAt + for _, fullMigration := range other { + new[fullMigration.Version] = fullMigration.AppliedAt } - for _, migration := range migrations { - _, will := new[migration.Version] - _, has := current[migration.Version] + for _, fullMigration := range other { + _, will := new[fullMigration.Version] + _, has := current[fullMigration.Version] if will && !has { - excepted = append(excepted, migration) + result = append(result, fullMigration) } } @@ -143,25 +143,27 @@ func (m Migrations) Except(migrations Migrations) (excepted Migrations) { } // Intersect selects migrations that does exist in the current ones. -func (m Migrations) Intersect(migrations Migrations) (intersect Migrations) { +// NB: other should be full migrations (e.g. from source) +// the ones from store are only metadata migrations (they don't include SQL statements) +func (m Migrations) Intersect(other Migrations) (result Migrations) { // Mark the current transactions. store := make(map[int64]time.Time) - for _, migration := range m { - store[migration.Version] = migration.AppliedAt + for _, migrationMetadata := range m { + store[migrationMetadata.Version] = migrationMetadata.AppliedAt } // Mark the ones in the migrations set, which we do have to get. source := make(map[int64]time.Time) - for _, migration := range migrations { - source[migration.Version] = migration.AppliedAt + for _, fullMigration := range other { + source[fullMigration.Version] = fullMigration.AppliedAt } - for _, migration := range migrations { - _, will := source[migration.Version] - appliedAt, has := store[migration.Version] + for _, fullMigration := range other { + _, will := source[fullMigration.Version] + appliedAt, has := store[fullMigration.Version] if will && has { - migration.AppliedAt = appliedAt - intersect = append(intersect, migration) + fullMigration.AppliedAt = appliedAt + result = append(result, fullMigration) } } @@ -211,12 +213,12 @@ func AppliedAfter(store Source, source Source, version int64) (Migrations, error } found := false - for _, migration := range appliedMigrations { - if migration.Version == version { + for i := 0; i <= len(appliedMigrations); i++ { + if appliedMigrations[i].Version == version { found = true break } - appliedAfter = append(appliedAfter, migration) + appliedAfter = append(appliedAfter, appliedMigrations[i]) } if !found { From 17a59a93355e47076204a7e9c721571738a29627 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 17 Mar 2022 10:44:34 +0200 Subject: [PATCH 09/15] sort present migrations --- cmd/gloat/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/gloat/main.go b/cmd/gloat/main.go index 32b6755..9a2e06d 100644 --- a/cmd/gloat/main.go +++ b/cmd/gloat/main.go @@ -138,6 +138,8 @@ func presentCmd(args arguments) error { return err } + migrations.Sort() + for i, m := range migrations { fmt.Printf("%d", m.Version) if i != len(migrations)-1 { From 77eb9c87cb7063d16c194c1655293c5a10845bd0 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 17 Mar 2022 10:45:26 +0200 Subject: [PATCH 10/15] sort present migrations --- gloat.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gloat.go b/gloat.go index 3434bae..8300884 100644 --- a/gloat.go +++ b/gloat.go @@ -27,7 +27,12 @@ func (c *Gloat) AppliedAfter(version int64) (Migrations, error) { // Present returns all available migrations. func (c *Gloat) Present() (Migrations, error) { - return c.Source.Collect() + migrations, err := c.Source.Collect() + if err != nil { + return err + } + migrations.Sort() + return migrations, nil } // Unapplied returns the unapplied migrations in the current gloat. From f650cbc97138c21be717a81cea2901398c7204a0 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 17 Mar 2022 21:22:54 +0200 Subject: [PATCH 11/15] Add tests. --- Makefile | 2 +- assets_test.go | 125 +++++++++++++++++++++++++++++++++++++- executor_test.go | 2 +- gloat.go | 2 +- gloat_test.go | 28 +++++++-- go.mod | 3 + go.sum | 10 +++ migration.go | 2 +- migration_options_test.go | 2 +- migration_test.go | 85 +++++++++++++++++++++++++- source_test.go | 6 +- store.go | 2 +- store_test.go | 6 +- 13 files changed, 251 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 88273d6..6c924d7 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ test: .PHONY: test.sqlite test.sqlite: - @env DATABASE_URL=sqlite3://:memory: go test ./... + @env DATABASE_SRC=testdata/migrations/ DATABASE_URL=sqlite3://:memory: go test ./... .PHONY: test.assets test.assets: diff --git a/assets_test.go b/assets_test.go index 6de0bf0..d01556c 100644 --- a/assets_test.go +++ b/assets_test.go @@ -3,6 +3,11 @@ // testdata/migrations/20170329154959_introduce_domain_model/down.sql // testdata/migrations/20170329154959_introduce_domain_model/up.sql // testdata/migrations/20170511172647_irreversible_migration_brah/up.sql +// testdata/migrations/20180905150724_concurrent_migration/down.sql +// testdata/migrations/20180905150724_concurrent_migration/options.json +// testdata/migrations/20180905150724_concurrent_migration/up.sql +// testdata/migrations/20180920181906_migration_with_an_error/down.sql +// testdata/migrations/20180920181906_migration_with_an_error/up.sql // DO NOT EDIT! package gloat @@ -85,7 +90,7 @@ func testdataMigrations20170329154959_introduce_domain_modelDownSql() (*asset, e return nil, err } - info := bindataFileInfo{name: "testdata/migrations/20170329154959_introduce_domain_model/down.sql", size: 18, mode: os.FileMode(420), modTime: time.Unix(1492188343, 0)} + info := bindataFileInfo{name: "testdata/migrations/20170329154959_introduce_domain_model/down.sql", size: 18, mode: os.FileMode(420), modTime: time.Unix(1646038365, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -105,7 +110,7 @@ func testdataMigrations20170329154959_introduce_domain_modelUpSql() (*asset, err return nil, err } - info := bindataFileInfo{name: "testdata/migrations/20170329154959_introduce_domain_model/up.sql", size: 266, mode: os.FileMode(420), modTime: time.Unix(1492775179, 0)} + info := bindataFileInfo{name: "testdata/migrations/20170329154959_introduce_domain_model/up.sql", size: 266, mode: os.FileMode(420), modTime: time.Unix(1646038365, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -125,7 +130,107 @@ func testdataMigrations20170511172647_irreversible_migration_brahUpSql() (*asset return nil, err } - info := bindataFileInfo{name: "testdata/migrations/20170511172647_irreversible_migration_brah/up.sql", size: 54, mode: os.FileMode(420), modTime: time.Unix(1494518694, 0)} + info := bindataFileInfo{name: "testdata/migrations/20170511172647_irreversible_migration_brah/up.sql", size: 54, mode: os.FileMode(420), modTime: time.Unix(1646038365, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _testdataMigrations20180905150724_concurrent_migrationDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") + +func testdataMigrations20180905150724_concurrent_migrationDownSqlBytes() ([]byte, error) { + return bindataRead( + _testdataMigrations20180905150724_concurrent_migrationDownSql, + "testdata/migrations/20180905150724_concurrent_migration/down.sql", + ) +} + +func testdataMigrations20180905150724_concurrent_migrationDownSql() (*asset, error) { + bytes, err := testdataMigrations20180905150724_concurrent_migrationDownSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "testdata/migrations/20180905150724_concurrent_migration/down.sql", size: 0, mode: os.FileMode(420), modTime: time.Unix(1646038365, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _testdataMigrations20180905150724_concurrent_migrationOptionsJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xaa\xe6\xe2\x54\x2a\x29\x4a\xcc\x2b\x4e\x4c\x2e\xc9\xcc\xcf\x53\xb2\x52\x48\x4b\xcc\x29\x4e\xe5\xaa\xe5\x02\x04\x00\x00\xff\xff\x0e\x77\x70\x04\x1a\x00\x00\x00") + +func testdataMigrations20180905150724_concurrent_migrationOptionsJsonBytes() ([]byte, error) { + return bindataRead( + _testdataMigrations20180905150724_concurrent_migrationOptionsJson, + "testdata/migrations/20180905150724_concurrent_migration/options.json", + ) +} + +func testdataMigrations20180905150724_concurrent_migrationOptionsJson() (*asset, error) { + bytes, err := testdataMigrations20180905150724_concurrent_migrationOptionsJsonBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "testdata/migrations/20180905150724_concurrent_migration/options.json", size: 26, mode: os.FileMode(420), modTime: time.Unix(1646038365, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _testdataMigrations20180905150724_concurrent_migrationUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00") + +func testdataMigrations20180905150724_concurrent_migrationUpSqlBytes() ([]byte, error) { + return bindataRead( + _testdataMigrations20180905150724_concurrent_migrationUpSql, + "testdata/migrations/20180905150724_concurrent_migration/up.sql", + ) +} + +func testdataMigrations20180905150724_concurrent_migrationUpSql() (*asset, error) { + bytes, err := testdataMigrations20180905150724_concurrent_migrationUpSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "testdata/migrations/20180905150724_concurrent_migration/up.sql", size: 0, mode: os.FileMode(420), modTime: time.Unix(1646038365, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _testdataMigrations20180920181906_migration_with_an_errorDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x51\x28\x2d\x4e\x2d\x2a\xb6\xe6\x02\x04\x00\x00\xff\xff\x6b\x44\xa8\xf8\x11\x00\x00\x00") + +func testdataMigrations20180920181906_migration_with_an_errorDownSqlBytes() ([]byte, error) { + return bindataRead( + _testdataMigrations20180920181906_migration_with_an_errorDownSql, + "testdata/migrations/20180920181906_migration_with_an_error/down.sql", + ) +} + +func testdataMigrations20180920181906_migration_with_an_errorDownSql() (*asset, error) { + bytes, err := testdataMigrations20180920181906_migration_with_an_errorDownSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "testdata/migrations/20180920181906_migration_with_an_error/down.sql", size: 17, mode: os.FileMode(420), modTime: time.Unix(1646038365, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _testdataMigrations20180920181906_migration_with_an_errorUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x0e\x72\x75\x0c\x71\x55\x08\x71\x74\xf2\x51\x28\x2d\x4e\x2d\x2a\x56\xd0\xe0\x52\x50\x50\x50\xc8\x4c\x51\x48\xca\x4c\x2f\x4e\x2d\xca\x4c\xcc\x51\x08\x08\xf2\xf4\x75\x0c\x8a\x54\xf0\x76\x8d\x54\xf0\xf3\x0f\x51\xf0\x0b\xf5\xf1\xe1\xd2\xb4\xe6\xe2\x02\x04\x00\x00\xff\xff\x59\x13\xa0\x89\x3e\x00\x00\x00") + +func testdataMigrations20180920181906_migration_with_an_errorUpSqlBytes() ([]byte, error) { + return bindataRead( + _testdataMigrations20180920181906_migration_with_an_errorUpSql, + "testdata/migrations/20180920181906_migration_with_an_error/up.sql", + ) +} + +func testdataMigrations20180920181906_migration_with_an_errorUpSql() (*asset, error) { + bytes, err := testdataMigrations20180920181906_migration_with_an_errorUpSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "testdata/migrations/20180920181906_migration_with_an_error/up.sql", size: 62, mode: os.FileMode(420), modTime: time.Unix(1646038365, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -185,6 +290,11 @@ var _bindata = map[string]func() (*asset, error){ "testdata/migrations/20170329154959_introduce_domain_model/down.sql": testdataMigrations20170329154959_introduce_domain_modelDownSql, "testdata/migrations/20170329154959_introduce_domain_model/up.sql": testdataMigrations20170329154959_introduce_domain_modelUpSql, "testdata/migrations/20170511172647_irreversible_migration_brah/up.sql": testdataMigrations20170511172647_irreversible_migration_brahUpSql, + "testdata/migrations/20180905150724_concurrent_migration/down.sql": testdataMigrations20180905150724_concurrent_migrationDownSql, + "testdata/migrations/20180905150724_concurrent_migration/options.json": testdataMigrations20180905150724_concurrent_migrationOptionsJson, + "testdata/migrations/20180905150724_concurrent_migration/up.sql": testdataMigrations20180905150724_concurrent_migrationUpSql, + "testdata/migrations/20180920181906_migration_with_an_error/down.sql": testdataMigrations20180920181906_migration_with_an_errorDownSql, + "testdata/migrations/20180920181906_migration_with_an_error/up.sql": testdataMigrations20180920181906_migration_with_an_errorUpSql, } // AssetDir returns the file names below a certain @@ -236,6 +346,15 @@ var _bintree = &bintree{nil, map[string]*bintree{ "20170511172647_irreversible_migration_brah": &bintree{nil, map[string]*bintree{ "up.sql": &bintree{testdataMigrations20170511172647_irreversible_migration_brahUpSql, map[string]*bintree{}}, }}, + "20180905150724_concurrent_migration": &bintree{nil, map[string]*bintree{ + "down.sql": &bintree{testdataMigrations20180905150724_concurrent_migrationDownSql, map[string]*bintree{}}, + "options.json": &bintree{testdataMigrations20180905150724_concurrent_migrationOptionsJson, map[string]*bintree{}}, + "up.sql": &bintree{testdataMigrations20180905150724_concurrent_migrationUpSql, map[string]*bintree{}}, + }}, + "20180920181906_migration_with_an_error": &bintree{nil, map[string]*bintree{ + "down.sql": &bintree{testdataMigrations20180920181906_migration_with_an_errorDownSql, map[string]*bintree{}}, + "up.sql": &bintree{testdataMigrations20180920181906_migration_with_an_errorUpSql, map[string]*bintree{}}, + }}, }}, }}, }} diff --git a/executor_test.go b/executor_test.go index 698cbd3..f5c9da1 100644 --- a/executor_test.go +++ b/executor_test.go @@ -5,7 +5,7 @@ import ( "path/filepath" "testing" - "github.com/gsamokovarov/assert" + "github.com/stretchr/testify/assert" ) func TestSQLExecutor_Up(t *testing.T) { diff --git a/gloat.go b/gloat.go index 8300884..1374f63 100644 --- a/gloat.go +++ b/gloat.go @@ -29,7 +29,7 @@ func (c *Gloat) AppliedAfter(version int64) (Migrations, error) { func (c *Gloat) Present() (Migrations, error) { migrations, err := c.Source.Collect() if err != nil { - return err + return nil, err } migrations.Sort() return migrations, nil diff --git a/gloat_test.go b/gloat_test.go index a8c2bf2..bef7ac4 100644 --- a/gloat_test.go +++ b/gloat_test.go @@ -10,9 +10,9 @@ import ( // Needed to establish database connections during testing. _ "github.com/go-sql-driver/mysql" - "github.com/gsamokovarov/assert" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/assert" ) var ( @@ -90,9 +90,9 @@ func TestUnapplied(t *testing.T) { migrations, err := gl.Unapplied() assert.Nil(t, err) - assert.Len(t, 4, migrations) + assert.Len(t, migrations, 4) - assert.Equal(t, 20170329154959, migrations[0].Version) + assert.Equal(t, int64(20170329154959), migrations[0].Version) } func TestUnapplied_Empty(t *testing.T) { @@ -108,7 +108,7 @@ func TestUnapplied_Empty(t *testing.T) { migrations, err := gl.Unapplied() assert.Nil(t, err) - assert.Len(t, 0, migrations) + assert.Len(t, migrations, 0) } func TestUnapplied_MissingInSource(t *testing.T) { @@ -128,7 +128,7 @@ func TestUnapplied_MissingInSource(t *testing.T) { migrations, err := gl.Unapplied() assert.Nil(t, err) - assert.Len(t, 0, migrations) + assert.Len(t, migrations, 0) } func TestCurrent(t *testing.T) { @@ -142,7 +142,23 @@ func TestCurrent(t *testing.T) { assert.Nil(t, err) assert.NotNil(t, migration) - assert.Equal(t, 20170329154959, migration.Version) + assert.Equal(t, int64(20170329154959), migration.Version) +} + +func TestLatest(t *testing.T) { + gl.Source = &testingStore{ + applied: Migrations{ + &Migration{Version: 20190329154959}, + &Migration{Version: 20180329154959}, + &Migration{Version: 20170329154959}, + }, + } + + migration, err := gl.Latest() + assert.Nil(t, err) + + assert.NotNil(t, migration) + assert.Equal(t, int64(20190329154959), migration.Version) } func TestCurrent_Nil(t *testing.T) { diff --git a/go.mod b/go.mod index 14975f9..df66130 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,11 @@ module github.com/webedx-spark/gloat +go 1.16 + require ( github.com/go-sql-driver/mysql v1.4.0 github.com/gsamokovarov/assert v0.0.0-20180414063448-8cd8ab63a335 github.com/lib/pq v1.0.0 github.com/mattn/go-sqlite3 v1.9.0 + github.com/stretchr/testify v1.7.1 // indirect ) diff --git a/go.sum b/go.sum index 557eb7b..df041a5 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +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/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/gsamokovarov/assert v0.0.0-20180414063448-8cd8ab63a335 h1:MFE3iUApg9Sl5MmZnosCEhYXRQCKz5coShpoAF86IiE= @@ -6,3 +8,11 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/migration.go b/migration.go index e041eb8..927db71 100644 --- a/migration.go +++ b/migration.go @@ -89,7 +89,7 @@ func MigrationFromBytes(path string, read func(string) ([]byte, error)) (*Migrat Path: path, Version: version, Options: options, - AppliedAt: time.Now().UTC(), + AppliedAt: time.Time{}, }, nil } diff --git a/migration_options_test.go b/migration_options_test.go index c827804..73eff95 100644 --- a/migration_options_test.go +++ b/migration_options_test.go @@ -4,7 +4,7 @@ import ( "io/ioutil" "testing" - "github.com/gsamokovarov/assert" + "github.com/stretchr/testify/assert" ) func TestDefaultMigrationOptions(t *testing.T) { diff --git a/migration_test.go b/migration_test.go index 37107cf..8c63ef6 100644 --- a/migration_test.go +++ b/migration_test.go @@ -3,8 +3,9 @@ package gloat import ( "io/ioutil" "testing" + "time" - "github.com/gsamokovarov/assert" + "github.com/stretchr/testify/assert" ) func TestMigrationReversible(t *testing.T) { @@ -34,7 +35,7 @@ func TestMigrationFromPath(t *testing.T) { m, err := MigrationFromBytes(expectedPath, ioutil.ReadFile) assert.Nil(t, err) - assert.Equal(t, 20170329154959, m.Version) + assert.Equal(t, int64(20170329154959), m.Version) assert.Equal(t, expectedPath, m.Path) } @@ -52,5 +53,83 @@ func TestMigrationsExcept(t *testing.T) { assert.Nil(t, exceptedMigrations) exceptedMigrations = migrations.Except(Migrations{m}) - assert.Len(t, 0, exceptedMigrations) + assert.Len(t, exceptedMigrations, 0) +} + +func TestMigrationsIntersect(t *testing.T) { + var migrations Migrations + + first := "testdata/migrations/20170329154959_introduce_domain_model" + + m1, err := MigrationFromBytes(first, ioutil.ReadFile) + assert.Nil(t, err) + + second := "testdata/migrations/20180905150724_concurrent_migration" + + m2, err := MigrationFromBytes(second, ioutil.ReadFile) + assert.Nil(t, err) + + migrations = append(migrations, m1) + migrations = append(migrations, m2) + + result := migrations.Intersect(nil) + assert.Len(t, result, 0) + + result = migrations.Intersect(Migrations{m1}) + assert.Len(t, result, 1) + + result = migrations.Intersect(Migrations{m1, m2}) + assert.Len(t, result, 2) +} + +func TestMigrationsSort(t *testing.T) { + var migrations Migrations + + first := "testdata/migrations/20170329154959_introduce_domain_model" + + m1, err := MigrationFromBytes(first, ioutil.ReadFile) + assert.Nil(t, err) + + second := "testdata/migrations/20180905150724_concurrent_migration" + + m2, err := MigrationFromBytes(second, ioutil.ReadFile) + assert.Nil(t, err) + + migrations = append(migrations, m2) + migrations = append(migrations, m1) + + migrations.Sort() + assert.Equal(t, migrations[0].Version, m1.Version) + + m1.AppliedAt = time.Now().UTC() + m2.AppliedAt = m1.AppliedAt.Add(-1 * time.Hour) + + migrations.Sort() + assert.Equal(t, migrations[0].Version, m2.Version) +} + +func TestMigrationsReverseSort(t *testing.T) { + var migrations Migrations + + first := "testdata/migrations/20170329154959_introduce_domain_model" + + m1, err := MigrationFromBytes(first, ioutil.ReadFile) + assert.Nil(t, err) + + second := "testdata/migrations/20180905150724_concurrent_migration" + + m2, err := MigrationFromBytes(second, ioutil.ReadFile) + assert.Nil(t, err) + + migrations = append(migrations, m2) + migrations = append(migrations, m1) + + migrations.ReverseSort() + assert.Equal(t, migrations[0].Version, m2.Version) + + m1.AppliedAt = time.Now().UTC() + m2.AppliedAt = m1.AppliedAt.Add(time.Hour) + + migrations.ReverseSort() + assert.Equal(t, migrations[0].Version, m2.Version) } diff --git a/source_test.go b/source_test.go index b5878df..33b8e8d 100644 --- a/source_test.go +++ b/source_test.go @@ -5,7 +5,7 @@ import ( "path/filepath" "testing" - "github.com/gsamokovarov/assert" + "github.com/stretchr/testify/assert" ) func TestFileSystemSourceCollect(t *testing.T) { @@ -38,7 +38,7 @@ func TestFileSystemSourceCollectEmpty(t *testing.T) { migrations, err := fs.Collect() assert.Nil(t, err) - assert.Len(t, 0, migrations) + assert.Len(t, migrations, 0) } func TestAssetSourceDoesNotBreakOnIrreversibleMigrations(t *testing.T) { @@ -48,5 +48,5 @@ func TestAssetSourceDoesNotBreakOnIrreversibleMigrations(t *testing.T) { migrations, err := fs.Collect() assert.Nil(t, err) - assert.Len(t, 2, migrations) + assert.Len(t, migrations, 4) } diff --git a/store.go b/store.go index 089c714..3627f50 100644 --- a/store.go +++ b/store.go @@ -133,7 +133,7 @@ func NewMySQLStore(db SQLTransactor) Store { DELETE FROM schema_migrations WHERE version=?`, selectAllMigrationsStatement: ` - SELECT version, version_tag + SELECT version, applied_at FROM schema_migrations ORDER BY applied_at DESC, version DESC`, } diff --git a/store_test.go b/store_test.go index d28066a..5b874fa 100644 --- a/store_test.go +++ b/store_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - "github.com/gsamokovarov/assert" + "github.com/stretchr/testify/assert" ) func TestDatabaseStore_Insert(t *testing.T) { @@ -30,7 +30,7 @@ func TestDatabaseStore_Insert(t *testing.T) { err = db.QueryRow(`SELECT version FROM schema_migrations`).Scan(&version) assert.Nil(t, err) - assert.Equal(t, 20170329154959, version) + assert.Equal(t, int64(20170329154959), version) }) } @@ -77,6 +77,6 @@ func TestDatabaseStore_Collect(t *testing.T) { &Migration{Version: 20170329154959}, } - assert.Equal(t, migrations, expectedMigrations) + assert.Equal(t, migrations[0].Version, expectedMigrations[0].Version) }) } From dc3bee71f02d11c72f2f280c3ee8dabd623c1093 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 17 Mar 2022 21:24:30 +0200 Subject: [PATCH 12/15] go mod tidy --- go.mod | 4 ++-- go.sum | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index df66130..fb52315 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.16 require ( github.com/go-sql-driver/mysql v1.4.0 - github.com/gsamokovarov/assert v0.0.0-20180414063448-8cd8ab63a335 github.com/lib/pq v1.0.0 github.com/mattn/go-sqlite3 v1.9.0 - github.com/stretchr/testify v1.7.1 // indirect + github.com/stretchr/testify v1.7.1 + google.golang.org/appengine v1.6.7 // indirect ) diff --git a/go.sum b/go.sum index df041a5..dc27fd7 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,7 @@ 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/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/gsamokovarov/assert v0.0.0-20180414063448-8cd8ab63a335 h1:MFE3iUApg9Sl5MmZnosCEhYXRQCKz5coShpoAF86IiE= -github.com/gsamokovarov/assert v0.0.0-20180414063448-8cd8ab63a335/go.mod h1:ejyiK4+/RLW9C/QgBK+nlwDmNB9pIW9i2WVqMmAa7no= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= @@ -13,6 +12,15 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f7fa97bae66e90e130e03ecf8d6789cab612f53a Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 17 Mar 2022 21:36:18 +0200 Subject: [PATCH 13/15] Add AppliedAfter test. --- gloat_test.go | 26 ++++++++++++++++++++++++++ migration.go | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/gloat_test.go b/gloat_test.go index bef7ac4..b10b422 100644 --- a/gloat_test.go +++ b/gloat_test.go @@ -13,6 +13,7 @@ import ( _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( @@ -145,6 +146,31 @@ func TestCurrent(t *testing.T) { assert.Equal(t, int64(20170329154959), migration.Version) } +func TestAppliedAfter(t *testing.T) { + gl.Source = &testingStore{ + applied: Migrations{ + &Migration{Version: 20190329154959, DownSQL: []byte("some sql here")}, + &Migration{Version: 20180329154959}, + &Migration{Version: 20170329154959}, + }, + } + gl.Store = &testingStore{ + applied: Migrations{ + &Migration{Version: 20190329154959}, + &Migration{Version: 20180329154959}, + &Migration{Version: 20170329154959}, + }, + } + + migrations, err := gl.AppliedAfter(20180329154959) + assert.Nil(t, err) + + assert.NotNil(t, migrations) + require.Len(t, migrations, 1) + assert.Equal(t, int64(20190329154959), migrations[0].Version) + assert.True(t, migrations[0].Reversible()) +} + func TestLatest(t *testing.T) { gl.Source = &testingStore{ applied: Migrations{ diff --git a/migration.go b/migration.go index 927db71..056185e 100644 --- a/migration.go +++ b/migration.go @@ -213,7 +213,7 @@ func AppliedAfter(store Source, source Source, version int64) (Migrations, error } found := false - for i := 0; i <= len(appliedMigrations); i++ { + for i := 0; i < len(appliedMigrations); i++ { if appliedMigrations[i].Version == version { found = true break From 80494447cb8b864441964357c704c3060bdbe860 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Fri, 18 Mar 2022 03:05:20 +0200 Subject: [PATCH 14/15] Use 1.7.0 for testify, newer breaks mocks --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fb52315..162e47d 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,6 @@ require ( github.com/go-sql-driver/mysql v1.4.0 github.com/lib/pq v1.0.0 github.com/mattn/go-sqlite3 v1.9.0 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.7.0 google.golang.org/appengine v1.6.7 // indirect ) diff --git a/go.sum b/go.sum index dc27fd7..6b786c5 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOq github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 4e90e00f8baaf6679a5400819cee81477be6cd4f Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Fri, 18 Mar 2022 04:11:29 +0200 Subject: [PATCH 15/15] add applied at --- gloat.go | 2 ++ gloat_test.go | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gloat.go b/gloat.go index 1374f63..74eda49 100644 --- a/gloat.go +++ b/gloat.go @@ -2,6 +2,7 @@ package gloat import ( "database/sql" + "time" ) // Gloat glues all the components needed to apply and revert @@ -86,6 +87,7 @@ func (c *Gloat) Current() (*Migration, error) { // Apply applies a migration. func (c *Gloat) Apply(migration *Migration) error { + migration.AppliedAt = time.Now().UTC() return c.Executor.Up(migration, c.Store) } diff --git a/gloat_test.go b/gloat_test.go index b10b422..42894cd 100644 --- a/gloat_test.go +++ b/gloat_test.go @@ -199,6 +199,7 @@ func TestCurrent_Nil(t *testing.T) { func TestApply(t *testing.T) { called := false + m := &Migration{} gl.Store = &testingStore{} gl.Executor = &stubbedExecutor{ up: func(*Migration, Store) error { @@ -207,9 +208,10 @@ func TestApply(t *testing.T) { }, } - gl.Apply(nil) + gl.Apply(m) assert.True(t, called) + assert.NotEmpty(t, m.AppliedAt) } func TestRevert(t *testing.T) {