mirror of
https://github.com/TECHNOFAB11/dbmate.git
synced 2025-12-13 00:20:04 +01:00
Add status command (#120)
Add new command to list applied and pending migrations. Closes #28 Closes #32 Closes #66 Closes #120
This commit is contained in:
parent
f7a3390299
commit
256f92ad19
5 changed files with 143 additions and 4 deletions
|
|
@ -93,6 +93,7 @@ dbmate drop # drop the database
|
||||||
dbmate migrate # run any pending migrations
|
dbmate migrate # run any pending migrations
|
||||||
dbmate rollback # roll back the most recent migration
|
dbmate rollback # roll back the most recent migration
|
||||||
dbmate down # alias for rollback
|
dbmate down # alias for rollback
|
||||||
|
dbmate status # show the status of all migrations
|
||||||
dbmate dump # write the database schema.sql file
|
dbmate dump # write the database schema.sql file
|
||||||
dbmate wait # wait for the database server to become available
|
dbmate wait # wait for the database server to become available
|
||||||
```
|
```
|
||||||
|
|
|
||||||
7
main.go
7
main.go
|
|
@ -102,6 +102,13 @@ func NewApp() *cli.App {
|
||||||
return db.Rollback()
|
return db.Rollback()
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "status",
|
||||||
|
Usage: "List applied and pending migrations",
|
||||||
|
Action: action(func(db *dbmate.DB, c *cli.Context) error {
|
||||||
|
return db.Status()
|
||||||
|
}),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "dump",
|
Name: "dump",
|
||||||
Usage: "Write the database schema to disk",
|
Usage: "Write the database schema to disk",
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,14 @@ type DB struct {
|
||||||
WaitTimeout time.Duration
|
WaitTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// migrationFileRegexp pattern for valid migration files
|
||||||
|
var migrationFileRegexp = regexp.MustCompile(`^\d.*\.sql$`)
|
||||||
|
|
||||||
|
type statusResult struct {
|
||||||
|
filename string
|
||||||
|
applied bool
|
||||||
|
}
|
||||||
|
|
||||||
// New initializes a new dbmate database
|
// New initializes a new dbmate database
|
||||||
func New(databaseURL *url.URL) *DB {
|
func New(databaseURL *url.URL) *DB {
|
||||||
return &DB{
|
return &DB{
|
||||||
|
|
@ -253,8 +261,7 @@ func (db *DB) openDatabaseForMigration() (Driver, *sql.DB, error) {
|
||||||
|
|
||||||
// Migrate migrates database to the latest version
|
// Migrate migrates database to the latest version
|
||||||
func (db *DB) Migrate() error {
|
func (db *DB) Migrate() error {
|
||||||
re := regexp.MustCompile(`^\d.*\.sql$`)
|
files, err := findMigrationFiles(db.MigrationsDir, migrationFileRegexp)
|
||||||
files, err := findMigrationFiles(db.MigrationsDir, re)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -445,3 +452,66 @@ func (db *DB) Rollback() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkMigrationsStatus(db *DB) ([]statusResult, error) {
|
||||||
|
files, err := findMigrationFiles(db.MigrationsDir, migrationFileRegexp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(files) == 0 {
|
||||||
|
return nil, fmt.Errorf("no migration files found")
|
||||||
|
}
|
||||||
|
|
||||||
|
drv, sqlDB, err := db.openDatabaseForMigration()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mustClose(sqlDB)
|
||||||
|
|
||||||
|
applied, err := drv.SelectMigrations(sqlDB, -1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []statusResult
|
||||||
|
|
||||||
|
for _, filename := range files {
|
||||||
|
ver := migrationVersion(filename)
|
||||||
|
res := statusResult{filename: filename}
|
||||||
|
if ok := applied[ver]; ok {
|
||||||
|
res.applied = true
|
||||||
|
} else {
|
||||||
|
res.applied = false
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status shows the status of all migrations
|
||||||
|
func (db *DB) Status() error {
|
||||||
|
results, err := checkMigrationsStatus(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalApplied int
|
||||||
|
|
||||||
|
for _, res := range results {
|
||||||
|
if res.applied {
|
||||||
|
fmt.Println("[X]", res.filename)
|
||||||
|
totalApplied++
|
||||||
|
} else {
|
||||||
|
fmt.Println("[ ]", res.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Applied: %d\n", totalApplied)
|
||||||
|
fmt.Printf("Pending: %d\n", len(results)-totalApplied)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,9 @@ func testRollbackURL(t *testing.T, u *url.URL) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, count)
|
require.Equal(t, 1, count)
|
||||||
|
|
||||||
|
err = sqlDB.QueryRow("select count(*) from posts").Scan(&count)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
// rollback
|
// rollback
|
||||||
err = db.Rollback()
|
err = db.Rollback()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -305,9 +308,9 @@ func testRollbackURL(t *testing.T, u *url.URL) {
|
||||||
// verify rollback
|
// verify rollback
|
||||||
err = sqlDB.QueryRow("select count(*) from schema_migrations").Scan(&count)
|
err = sqlDB.QueryRow("select count(*) from schema_migrations").Scan(&count)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 0, count)
|
require.Equal(t, 1, count)
|
||||||
|
|
||||||
err = sqlDB.QueryRow("select count(*) from users").Scan(&count)
|
err = sqlDB.QueryRow("select count(*) from posts").Scan(&count)
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
require.Regexp(t, "(does not exist|doesn't exist|no such table)", err.Error())
|
require.Regexp(t, "(does not exist|doesn't exist|no such table)", err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -317,3 +320,53 @@ func TestRollback(t *testing.T) {
|
||||||
testRollbackURL(t, u)
|
testRollbackURL(t, u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testStatusUrl(t *testing.T, u *url.URL) {
|
||||||
|
db := newTestDB(t, u)
|
||||||
|
|
||||||
|
// drop, recreate, and migrate database
|
||||||
|
err := db.Drop()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = db.Create()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// verify migration
|
||||||
|
sqlDB, err := GetDriverOpen(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer mustClose(sqlDB)
|
||||||
|
|
||||||
|
// two pending
|
||||||
|
results, err := checkMigrationsStatus(db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, results, 2)
|
||||||
|
require.False(t, results[0].applied)
|
||||||
|
require.False(t, results[1].applied)
|
||||||
|
|
||||||
|
// run migrations
|
||||||
|
err = db.Migrate()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// two applied
|
||||||
|
results, err = checkMigrationsStatus(db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, results, 2)
|
||||||
|
require.True(t, results[0].applied)
|
||||||
|
require.True(t, results[1].applied)
|
||||||
|
|
||||||
|
// rollback last migration
|
||||||
|
err = db.Rollback()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// one applied, one pending
|
||||||
|
results, err = checkMigrationsStatus(db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, results, 2)
|
||||||
|
require.True(t, results[0].applied)
|
||||||
|
require.False(t, results[1].applied)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatus(t *testing.T) {
|
||||||
|
for _, u := range testURLs(t) {
|
||||||
|
testStatusUrl(t, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
8
testdata/db/migrations/20200227231541_test_posts.sql
vendored
Normal file
8
testdata/db/migrations/20200227231541_test_posts.sql
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
-- migrate:up
|
||||||
|
create table posts (
|
||||||
|
id integer,
|
||||||
|
name varchar(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- migrate:down
|
||||||
|
drop table posts;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue