Skip to content

Commit

Permalink
Merge pull request #3 from DirectDuck/skip_migrations
Browse files Browse the repository at this point in the history
Add: skip migrations
  • Loading branch information
sas1024 authored Apr 1, 2024
2 parents 7dd7b79 + ad22471 commit 69a97f6
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 24 deletions.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Run
plan Shows migration files which can be applied
redo Rerun last applied migration from db
run Applies all new migrations
skip Marks migrations done without actually running them.
verify Checks and shows invalid migrations

Flags:
Expand Down Expand Up @@ -155,17 +156,21 @@ That is, you can call `pgmigrator --config docs/patches/pgmigrator.toml plan` an
* If there is a NONTR - do not let dryrun run (only up to a certain filename)
* `StatementTimeout` setting is ignored

### Skip

Like `Run`, but without actually running sql migration, only adding migration success record

### Last

Shows the latest database migrations from a table.

**Output**

Showing last migrations in public.pgMigrations:
34 - 2022-08-30 22:25:03 (ERR) > 2022-07-30-compilations-NONTR.sql
33 - 2022-08-30 22:25:03 (3s) > 2022-07-30-compilations-fix.sql
32 - 2022-08-30 22:25:34 (1s) > 2022-07-28-jwlinks.sql
31 - 2022-08-30 22:23:12 (5m 4s) > 2022-07-18-movieComments.sql
Showing last migrations in public.pgMigrations:
34 - 2022-08-30 22:25:03 (ERR) > 2022-07-30-compilations-NONTR.sql
33 - 2022-08-30 22:25:03 (3s) > 2022-07-30-compilations-fix.sql
32 - 2022-08-30 22:25:34 (1s) > 2022-07-28-jwlinks.sql
31 - 2022-08-30 22:23:12 (5m 4s) > 2022-07-18-movieComments.sql

### Verify

Expand Down
5 changes: 5 additions & 0 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ A: Цель - простая утилита, которая работает с
plan Shows migration files which can be applied
redo Rerun last applied migration from db
run Applies all new migrations
skip Marks migrations done without actually running them.
verify Checks and shows invalid migrations

Flags:
Expand Down Expand Up @@ -157,6 +158,10 @@ A: Цель - простая утилита, которая работает с

Как в `Run`, только в конце выводим сообщение о ROLLBACK.

### Skip

Как и `Run`, но без выполнения sql миграции. Только добавление записи о том, что миграция применена

### Last

Показываем последние транзакции.
Expand Down
42 changes: 41 additions & 1 deletion pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func New(rootCmd *cobra.Command, mg *migrator.Migrator, cfg Config) App {
}

func (a App) Run(ctx context.Context) error {
a.rootCmd.AddCommand(a.initCmd(), a.dryRunCmd(ctx), a.lastCmd(ctx), a.planCmd(ctx), a.redoCmd(ctx), a.runCmd(ctx), a.verifyCmd(ctx))
a.rootCmd.AddCommand(a.initCmd(), a.dryRunCmd(ctx), a.lastCmd(ctx), a.planCmd(ctx), a.redoCmd(ctx), a.runCmd(ctx), a.verifyCmd(ctx), a.skipCmd(ctx))
a.rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
if cmd.Name() == "init" || cmd.Name() == "help" {
return
Expand Down Expand Up @@ -262,6 +262,46 @@ If <count> applied, runs only <count> migrations. By default: 5`,
}
}

// skipCmd marks migrations done without actually running them
func (a App) skipCmd(ctx context.Context) *cobra.Command {
return &cobra.Command{
Use: "skip [<count>]",
Short: "Marks migrations done without actually running them",
Long: `Marks migrations done without actually running them.
If <count> applied, marks only first <count> migrations displayed in plan. Default <count> = 5.`,
Run: func(cmd *cobra.Command, args []string) {
// get list of migrations
mm, err := a.mg.Plan(ctx)
if err != nil {
log.Fatalf("Execute command failed: %v\n", err)
} else if len(mm) == 0 {
fmt.Println("No new migrations were found.")
return
}

// calculate count
cnt, err := count(args)
if err != nil {
log.Fatal("invalid argument")
} else if cnt > len(mm) {
cnt = len(mm)
}

// skip migrations
ch := make(chan string)
wg := &sync.WaitGroup{}
go readCh(ch, wg)
fmt.Println("Skipping migrations...")
if err = a.mg.Skip(ctx, mm[:cnt], ch); err != nil {
log.Fatalf("Skip migration error: %v", err)
return
}
wg.Wait()
fmt.Println("Done")
},
}
}

// redoCmd rerun last migration
func (a App) redoCmd(ctx context.Context) *cobra.Command {
return &cobra.Command{
Expand Down
77 changes: 59 additions & 18 deletions pkg/migrator/migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ func NewMigrator(db *pg.DB, cfg Config, rootDir string) *Migrator {
return m
}

// writeMigrationToDB inserts log that migration was completed in postgres
func writeMigrationToDB(ctx context.Context, mg Migration, tx *pg.Tx, start time.Time) error {
finish := time.Now()
pm := mg.ToDB()
pm.StartedAt = start
pm.FinishedAt = &finish

if _, err := tx.ModelContext(ctx, pm).Insert(); err != nil {
return fmt.Errorf(`add new migration "%s" failed: %w`, mg.Filename, err)
}
return nil
}

// readAllFiles read files from migrator root dir and return its filenames
func (m *Migrator) readAllFiles() ([]string, error) {
dir, err := os.Open(m.rootDir)
Expand Down Expand Up @@ -191,17 +204,7 @@ func (m *Migrator) applyMigration(ctx context.Context, mg Migration) (err error)
return fmt.Errorf(`apply migration failed: %w`, err)
}

// insert into pgMigrations
finish := time.Now()
pm := mg.ToDB()
pm.StartedAt = start
pm.FinishedAt = &finish

if _, err = tx.ModelContext(ctx, pm).Insert(); err != nil {
return fmt.Errorf(`add new migration failed: %w`, err)
}

return nil
return writeMigrationToDB(ctx, mg, tx, start)
}

// setStatementTimeout set statement timeout to transaction connection
Expand Down Expand Up @@ -293,14 +296,52 @@ func (m *Migrator) dryRunMigrations(ctx context.Context, mm Migrations, chCurren
return fmt.Errorf(`apply migration "%s" failed: %w`, mg.Filename, err)
}

// insert into pgMigrations
finish := time.Now()
pm := mg.ToDB()
pm.StartedAt = start
pm.FinishedAt = &finish
if err = writeMigrationToDB(ctx, mg, tx, start); err != nil {
return err
}
}

if _, err = tx.ModelContext(ctx, pm).Insert(); err != nil {
return fmt.Errorf(`add new migration "%s" failed: %w`, mg.Filename, err)
return nil
}

// Skip marks migrations as completed
func (m *Migrator) Skip(ctx context.Context, filenames []string, chCurrentFile chan string) error {
defer close(chCurrentFile)

// create migration table if not exists
if err := m.createMigratorTable(ctx); err != nil {
return err
}

// prepare migrations
mm, err := m.newMigrations(filenames)
if err != nil {
return fmt.Errorf("prepare migrations failed: %w", err)
}

// skip migrations
if err := m.skipMigrations(ctx, mm, chCurrentFile); err != nil {
return fmt.Errorf("skip migrations failed: %w", err)
}
return nil
}

func (m *Migrator) skipMigrations(ctx context.Context, mm Migrations, chCurrentFile chan string) (err error) {
var tx *pg.Tx
tx, err = m.db.Begin()
if err != nil {
return fmt.Errorf(`begin transaction failed: %w`, err)
}

defer func() {
err = finishTxOnErr(tx, err)
}()

// write migrations to pgMigrations table
for _, mg := range mm {
chCurrentFile <- mg.Filename
if err = writeMigrationToDB(ctx, mg, tx, time.Now()); err != nil {
return err
}
}

Expand Down
44 changes: 44 additions & 0 deletions pkg/migrator/migrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,50 @@ func TestMigrator_DryRun(t *testing.T) {
})
}

func TestMigrator_skipMigrations(t *testing.T) {
ctx := context.Background()
Convey("TestMigrator_skipMigrations", t, func() {
err := recreateSchema()
So(err, ShouldBeNil)
err = testMigrator.createMigratorTable(ctx)
So(err, ShouldBeNil)

dirFiles := []string{
"2022-12-12-01-create-table-statuses.sql",
"2022-12-12-02-create-table-news.sql",
}
mm, err := testMigrator.newMigrations(dirFiles)
So(err, ShouldBeNil)

ch := make(chan string)
go readFromCh(ch, t)
err = testMigrator.skipMigrations(ctx, mm, ch)
So(err, ShouldBeNil)

for _, mg := range mm {
var pm PgMigration
err = testMigrator.db.ModelContext(ctx, &pm).Where(`"filename" = ?`, mg.Filename).Select()
So(err, ShouldBeNil)
So(pm, ShouldNotBeNil)
So(pm.FinishedAt, ShouldNotBeEmpty)
}
})
}

func TestMigrator_Skip(t *testing.T) {
ctx := context.Background()
Convey("TestMigrator_Skip", t, func() {
err := recreateSchema()
So(err, ShouldBeNil)
filenames, err := testMigrator.Plan(ctx)
So(err, ShouldBeNil)
ch := make(chan string)
go readFromCh(ch, t)
err = testMigrator.Skip(ctx, filenames, ch)
So(err, ShouldBeNil)
})
}

func TestMigrator_compareMD5Sum(t *testing.T) {
Convey("TestMigrator_compareMD5Sum", t, func() {
Convey("check correct", func() {
Expand Down

0 comments on commit 69a97f6

Please sign in to comment.