Skip to content

Commit

Permalink
feat(status): Add status command (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
psucodervn authored Oct 17, 2024
1 parent 71d2432 commit 6fa28e1
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 2 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ files to be saved in (which will be the same directory of the main package, e.g.
`example`), an instance of `*pg.DB`, and `os.Args`; and log any potential errors
that could be returned.

Once this has been set up, then you can use the `create`, `migrate`, `rollback`,
Once this has been set up, then you can use the `create`, `migrate`, `status`, `rollback`,
`help` commands like so:

```
Expand All @@ -43,6 +43,13 @@ $ go run example/*.go migrate
Running batch 1 with 1 migration(s)...
Finished running "20180812001528_create_users_table"
$ go run example/*.go status
+---------+-----------------------------------+-------+
| Applied | Migration | Batch |
+---------+-----------------------------------+-------+
| √ | 20180812001528_create_users_table | 1 |
+---------+-----------------------------------+-------+
$ go run example/*.go rollback
Rolling back batch 1 with 1 migration(s)...
Finished rolling back "20180812001528_create_users_table"
Expand All @@ -55,12 +62,14 @@ Commands:
create - create a new migration in example with the provided name
migrate - run any migrations that haven't been run yet
rollback - roll back the previous run batch of migrations
status - show the status of each migration
help - print this help text
Examples:
go run example/*.go create create_users_table
go run example/*.go migrate
go run example/*.go rollback
go run example/*.go status
go run example/*.go help
```

Expand Down
4 changes: 3 additions & 1 deletion help.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ Commands:
create - create a new migration in %s with the provided name
migrate - run any migrations that haven't been run yet
rollback - roll back the previous run batch of migrations
status - show the status of each migration
help - print this help text
Examples:
go run %s/*.go create create_users_table
go run %s/*.go migrate
go run %s/*.go rollback
go run %s/*.go status
go run %s/*.go help
`

func help(directory string) {
fmt.Printf(helpText, directory, directory, directory, directory, directory, directory)
fmt.Printf(helpText, directory, directory, directory, directory, directory, directory, directory)
}
8 changes: 8 additions & 0 deletions migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package migrations

import (
"errors"
"os"
"time"

"github.com/go-pg/pg/v10"
Expand Down Expand Up @@ -72,6 +73,13 @@ func Run(db *pg.DB, directory string, args []string) error {
}

return rollback(db)
case "status":
err := ensureMigrationTables(db)
if err != nil {
return err
}

return status(db, os.Stdout)
default:
help(directory)
return nil
Expand Down
82 changes: 82 additions & 0 deletions status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package migrations

import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"unicode/utf8"

"github.com/go-pg/pg/v10"
)

type migrationWithStatus struct {
migration
}

func status(db *pg.DB, w io.Writer) error {
// sort the registered migrations by name (which will sort by the
// timestamp in their names)
sort.Slice(migrations, func(i, j int) bool {
return migrations[i].Name < migrations[j].Name
})

// look at the migrations table to see the already run migrations
completed, err := getCompletedMigrations(db)
if err != nil {
return err
}

// diff the completed migrations from the registered migrations to find
// the migrations we still need to run
uncompleted := filterMigrations(migrations, completed, false)

return writeStatusTable(w, completed, uncompleted)

Check failure on line 35 in status.go

View workflow job for this annotation

GitHub Actions / build

cannot use completed (variable of type []*migration) as []migration value in argument to writeStatusTable

Check failure on line 35 in status.go

View workflow job for this annotation

GitHub Actions / build

cannot use uncompleted (variable of type []*migration) as []migration value in argument to writeStatusTable) (typecheck)

Check failure on line 35 in status.go

View workflow job for this annotation

GitHub Actions / build

cannot use completed (variable of type []*migration) as []migration value in argument to writeStatusTable

Check failure on line 35 in status.go

View workflow job for this annotation

GitHub Actions / build

cannot use uncompleted (variable of type []*migration) as []migration value in argument to writeStatusTable
}

func writeStatusTable(w io.Writer, completed []migration, uncompleted []migration) error {
if len(completed)+len(uncompleted) == 0 {
_, err := fmt.Fprintln(w, "No migrations found")
return err
}

maxNameLength := 20
for _, m := range completed {
maxNameLength = maxInt(maxNameLength, utf8.RuneCountInString(m.Name))
}
for _, m := range uncompleted {
maxNameLength = maxInt(maxNameLength, utf8.RuneCountInString(m.Name))
}

bf := bytes.NewBuffer(nil)

// write header
bf.WriteString("+---------+" + strings.Repeat("-", maxNameLength+2) + "+-------+\n")
bf.WriteString("| Applied | Migration" + strings.Repeat(" ", maxNameLength-8) + "| Batch |\n")
bf.WriteString("+---------+" + strings.Repeat("-", maxNameLength+2) + "+-------+\n")

// write completed migrations
for _, m := range completed {
bf.WriteString("| √ | " + m.Name + strings.Repeat(" ", maxNameLength-len(m.Name)) + " | " + fmt.Sprintf("%5d", m.Batch) + " |\n")
}

// write uncompleted migrations
for _, m := range uncompleted {
bf.WriteString("| | " + m.Name + strings.Repeat(" ", maxNameLength-len(m.Name)) + " | |\n")
}

// write footer
bf.WriteString("+---------+" + strings.Repeat("-", maxNameLength+2) + "+-------+\n")

_, err := bf.WriteTo(w)
return err
}

func maxInt(a, b int) int {
if a > b {
return a
} else {
return b
}
}
92 changes: 92 additions & 0 deletions status_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package migrations

import (
"bytes"
"os"
"strings"
"testing"

"github.com/go-pg/pg/v10"
"github.com/stretchr/testify/require"
)

func TestStatus(t *testing.T) {
db := pg.Connect(&pg.Options{
Addr: "localhost:5432",
User: os.Getenv("TEST_DATABASE_USER"),
Database: os.Getenv("TEST_DATABASE_NAME"),
})

db.AddQueryHook(logQueryHook{})

err := ensureMigrationTables(db)
require.Nil(t, err)

defer clearMigrations(t, db)
defer resetMigrations(t)

completed := []migration{
{Name: "2021_02_26_151503_dump", Up: noopMigration, Down: noopMigration, Batch: 1},
{Name: "2021_02_26_151504_create_a_dump_table_for_test", Up: noopMigration, Down: noopMigration, Batch: 2},
}
uncompleted := []migration{
{Name: "2021_02_26_151502_create_2nd_dump_table", Up: noopMigration, Down: noopMigration},
{Name: "2021_02_26_151505_create_3rd_dump_table", Up: noopMigration, Down: noopMigration},
}
expected := strings.TrimSpace(`
+---------+------------------------------------------------+-------+
| Applied | Migration | Batch |
+---------+------------------------------------------------+-------+
| √ | 2021_02_26_151503_dump | 1 |
| √ | 2021_02_26_151504_create_a_dump_table_for_test | 2 |
| | 2021_02_26_151502_create_2nd_dump_table | |
| | 2021_02_26_151505_create_3rd_dump_table | |
+---------+------------------------------------------------+-------+
`)

t.Run("status_command", func(tt *testing.T) {
clearMigrations(tt, db)
resetMigrations(tt)

migrations = completed[:1]

Check failure on line 51 in status_test.go

View workflow job for this annotation

GitHub Actions / build

cannot use completed[:1] (value of type []migration) as []*migration value in assignment
err := migrate(db)
require.Nil(tt, err, "migrate: %v", err)
migrations = completed[:2]

Check failure on line 54 in status_test.go

View workflow job for this annotation

GitHub Actions / build

cannot use completed[:2] (value of type []migration) as []*migration value in assignment
err = migrate(db)
require.Nil(tt, err, "migrate: %v", err)

migrations = append(migrations, uncompleted...)

Check failure on line 58 in status_test.go

View workflow job for this annotation

GitHub Actions / build

cannot use uncompleted (variable of type []migration) as []*migration value in argument to append (typecheck)
bf := bytes.NewBuffer(nil)
err = status(db, bf)
require.Nil(tt, err, "status: %v", err)

got := strings.TrimSpace(bf.String())
if got != expected {
tt.Errorf("status table not match:\nEXPECTED:\n%s\nACTUAL:\n%s", expected, got)
}
})

t.Run("write_status_table", func(tt *testing.T) {
bf := bytes.NewBuffer(nil)
err := writeStatusTable(bf, completed, uncompleted)
require.Nil(tt, err, "write_status_table: %v", err)

got := strings.TrimSpace(bf.String())
if got != expected {
tt.Errorf("status table not match:\nEXPECTED:\n%s\nACTUAL:\n%s", expected, got)
}
})

t.Run("no_migrations_found", func(tt *testing.T) {
expected := strings.TrimSpace(`No migrations found`)

bf := bytes.NewBuffer(nil)
err := writeStatusTable(bf, nil, nil)
require.Nil(tt, err, "write_status_table: %v", err)

got := strings.TrimSpace(bf.String())
if got != expected {
tt.Errorf("status table not match:\nEXPECTED:\n%s\nACTUAL:\n%s", expected, got)
}
})
}

0 comments on commit 6fa28e1

Please sign in to comment.