Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SchemaMigrationsHistory to determine unapplied out of order migrations #31

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,34 @@ $ wrench migrate up --directory ./_examples
# load ddl from database to file ./_examples/schema.sql
$ wrench load --directory ./_examples

# show time and date of migrations
$ wrench migrate history
Version Dirty Created Modified
1 false 2020-06-16 08:07:11.763755 +0000 UTC 2020-06-16 08:07:11.76998 +0000 UTC

# finally, we have successfully migrated database!
$ cat ./_examples/schema.sql
CREATE TABLE SchemaMigrations (
Version INT64 NOT NULL,
Dirty BOOL NOT NULL,
) PRIMARY KEY(Version);

CREATE TABLE Singers (
SingerID STRING(36) NOT NULL,
FirstName STRING(1024),
LastName STRING(1024),
) PRIMARY KEY(SingerID);

CREATE TABLE SchemaMigrations (
Version INT64 NOT NULL,
Dirty BOOL NOT NULL,
) PRIMARY KEY(Version);

CREATE TABLE SchemaMigrationsHistory (
Version INT64 NOT NULL,
Dirty BOOL NOT NULL,
Created TIMESTAMP NOT NULL OPTIONS (
allow_commit_timestamp = true
),
Modified TIMESTAMP NOT NULL OPTIONS (
allow_commit_timestamp = true
),
) PRIMARY KEY(Version);
```

## Installation
Expand Down Expand Up @@ -107,7 +123,14 @@ This creates a next migration file like `_examples/migrations/000001.sql`. You w
$ wrench migrate up --directory ./_examples
```

This executes migrations. This also creates `SchemaMigrations` table into your database to manage schema version if it does not exist.
This executes migrations. This also creates `SchemaMigrations` & `SchemaMigrationsHistory` tables in your database to manage schema version if it does not exist.

### Migrations history
```sh
$ wrench migrate history
```
This displays the history of migrations applied to your database, ordered by when they were first attempted.
Migrations left in a dirty state and subsequently retried are reflected in the Modified timestamp.

### Apply single DDL/DML

Expand Down
17 changes: 17 additions & 0 deletions _examples/schema.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
CREATE TABLE Singers (
SingerID STRING(36) NOT NULL,
FirstName STRING(1024),
LastName STRING(1024),
) PRIMARY KEY(SingerID);

CREATE TABLE SchemaMigrations (
Version INT64 NOT NULL,
Dirty BOOL NOT NULL,
) PRIMARY KEY(Version);

CREATE TABLE SchemaMigrationsHistory (
Version INT64 NOT NULL,
Dirty BOOL NOT NULL,
Created TIMESTAMP NOT NULL OPTIONS (
allow_commit_timestamp = true
),
Modified TIMESTAMP NOT NULL OPTIONS (
allow_commit_timestamp = true
),
) PRIMARY KEY(Version);
61 changes: 57 additions & 4 deletions cmd/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import (
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"text/tabwriter"

"github.com/cloudspannerecosystem/wrench/pkg/spanner"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -64,12 +66,18 @@ func init() {
Short: "Set version V but don't run migration (ignores dirty state)",
RunE: migrateSet,
}
migrateHistoryCmd := &cobra.Command{
Use: "history",
Short: "Print migration version history",
RunE: migrateHistory,
}

migrateCmd.AddCommand(
migrateCreateCmd,
migrateUpCmd,
migrateVersionCmd,
migrateSetCmd,
migrateHistoryCmd,
)

migrateCmd.PersistentFlags().String(flagNameDirectory, "", "Directory that migration files placed (required)")
Expand Down Expand Up @@ -127,23 +135,40 @@ func migrateUp(c *cobra.Command, args []string) error {
}
defer client.Close()

dir := filepath.Join(c.Flag(flagNameDirectory).Value.String(), migrationsDirName)
migrations, err := spanner.LoadMigrations(dir)
if err != nil {
return &Error{
cmd: c,
err: err,
}
}

if err = client.EnsureMigrationTable(ctx, migrationTableName); err != nil {
return &Error{
cmd: c,
err: err,
}
}

dir := filepath.Join(c.Flag(flagNameDirectory).Value.String(), migrationsDirName)
migrations, err := spanner.LoadMigrations(dir)
status, err := client.DetermineUpgradeStatus(ctx, migrationTableName)
if err != nil {
return &Error{
cmd: c,
err: err,
}
}

return client.ExecuteMigrations(ctx, migrations, limit, migrationTableName)
switch status {
case spanner.ExistingMigrationsUpgradeStarted:
return client.UpgradeExecuteMigrations(ctx, migrations, limit, migrationTableName)
case spanner.ExistingMigrationsUpgradeCompleted:
return client.ExecuteMigrations(ctx, migrations, limit, migrationTableName)
default:
return &Error{
cmd: c,
err: errors.New("migration in undetermined state"),
}
}
}

func migrateVersion(c *cobra.Command, args []string) error {
Expand Down Expand Up @@ -180,6 +205,34 @@ func migrateVersion(c *cobra.Command, args []string) error {
return nil
}

func migrateHistory(c *cobra.Command, args []string) error {
ctx := context.Background()

client, err := newSpannerClient(ctx, c)
if err != nil {
return err
}
defer client.Close()

history, err := client.GetMigrationHistory(ctx, migrationTableName)
if err != nil {
return err
}
sort.SliceStable(history, func(i, j int) bool {
return history[i].Created.Before(history[j].Created) // order by Created
})

writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight)
fmt.Fprintln(writer, "Version\tDirty\tCreated\tModified")
for i := range history {
h := history[i]
fmt.Fprintf(writer, "%d\t%v\t%v\t%v\n", h.Version, h.Dirty, h.Created, h.Modified)
}
writer.Flush()

return nil
}

func migrateSet(c *cobra.Command, args []string) error {
ctx := context.Background()

Expand Down
Loading