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

Add --strict flag #441

Merged
merged 20 commits into from Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
eb36c90
Add --strict-order flag
philip-hartmann May 4, 2023
68da288
Limit --strict-order flag to appropriate verbs
philip-hartmann May 12, 2023
9822a96
Rename --strict-flag to --strict
philip-hartmann May 12, 2023
d278d6a
Throw error on strict numerical order conflict
philip-hartmann Jul 29, 2023
dc0e0a1
Identify latest applied migration only in strict mode
philip-hartmann Jul 29, 2023
f912ddf
Change wording of strict mode error message
philip-hartmann Jul 29, 2023
5b890ff
Update usage text
amacneil Aug 3, 2023
d9d7ce2
Check pending migrations version in `Migrate()` instead of `FindMigra…
philip-hartmann Aug 11, 2023
b9e0bdf
Change wording of strict mode error message
philip-hartmann Aug 11, 2023
d0ded5d
Check only pending migrations in `Migrate()`
philip-hartmann Aug 11, 2023
311dc19
Check only first pending migration
philip-hartmann Aug 11, 2023
d20cf09
Abort migration if no pending migrations files exist
philip-hartmann Aug 11, 2023
cd88906
Abort migration with no error if no pending migrations files exist
philip-hartmann Aug 11, 2023
3323fa5
Derive highest applied version number from migration list
philip-hartmann Aug 11, 2023
d896ce2
Undo refactor to find applied migrations
philip-hartmann Aug 11, 2023
4b85b89
Dump schema even if no migrations are applied
philip-hartmann Nov 7, 2023
5d95143
Fix backwards-incompatible breaking condition
philip-hartmann Nov 8, 2023
c216c93
Add tests for strict mode
philip-hartmann Nov 10, 2023
9949e88
Fix tests
philip-hartmann Nov 10, 2023
02d2d7b
Remove whitespaces
philip-hartmann Nov 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -142,6 +142,7 @@ The following options are available with all commands. You must use command line
- `--migrations-table "schema_migrations"` - database table to record migrations in. _(env: `DBMATE_MIGRATIONS_TABLE`)_
- `--schema-file, -s "./db/schema.sql"` - a path to keep the schema.sql file. _(env: `DBMATE_SCHEMA_FILE`)_
- `--no-dump-schema` - don't auto-update the schema.sql file on migrate/rollback _(env: `DBMATE_NO_DUMP_SCHEMA`)_
- `--strict` - fail if migrations would be applied out of order _(env: `DBMATE_STRICT`)_
- `--wait` - wait for the db to become available before executing the subsequent command _(env: `DBMATE_WAIT`)_
- `--wait-timeout 60s` - timeout for --wait flag _(env: `DBMATE_WAIT_TIMEOUT`)_

Expand Down
13 changes: 13 additions & 0 deletions main.go
Expand Up @@ -101,6 +101,11 @@ func NewApp() *cli.App {
Name: "up",
Usage: "Create database (if necessary) and migrate to the latest version",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "strict",
EnvVars: []string{"DBMATE_STRICT"},
Usage: "fail if migrations would be applied out of order",
},
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Expand All @@ -109,6 +114,7 @@ func NewApp() *cli.App {
},
},
Action: action(func(db *dbmate.DB, c *cli.Context) error {
db.Strict = c.Bool("strict")
db.Verbose = c.Bool("verbose")
return db.CreateAndMigrate()
}),
Expand All @@ -131,6 +137,11 @@ func NewApp() *cli.App {
Name: "migrate",
Usage: "Migrate to the latest version",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "strict",
EnvVars: []string{"DBMATE_STRICT"},
Usage: "fail if migrations would be applied out of order",
},
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Expand All @@ -139,6 +150,7 @@ func NewApp() *cli.App {
},
},
Action: action(func(db *dbmate.DB, c *cli.Context) error {
db.Strict = c.Bool("strict")
db.Verbose = c.Bool("verbose")
return db.Migrate()
}),
Expand Down Expand Up @@ -174,6 +186,7 @@ func NewApp() *cli.App {
},
},
Action: action(func(db *dbmate.DB, c *cli.Context) error {
db.Strict = c.Bool("strict")
setExitCode := c.Bool("exit-code")
quiet := c.Bool("quiet")
if quiet {
Expand Down
69 changes: 43 additions & 26 deletions pkg/dbmate/db.go
Expand Up @@ -49,6 +49,8 @@ type DB struct {
MigrationsTableName string
// SchemaFile specifies the location for schema.sql file
SchemaFile string
// Fail if migrations would be applied out of order
Strict bool
// Verbose prints the result of each statement execution
Verbose bool
// WaitBefore will wait for database to become available before running any actions
Expand All @@ -75,6 +77,7 @@ func New(databaseURL *url.URL) *DB {
MigrationsDir: []string{"./db/migrations"},
MigrationsTableName: "schema_migrations",
SchemaFile: "./db/schema.sql",
Strict: false,
Verbose: false,
WaitBefore: false,
WaitInterval: time.Second,
Expand Down Expand Up @@ -309,47 +312,61 @@ func (db *DB) Migrate() error {
return ErrNoMigrationFiles
}

sqlDB, err := db.openDatabaseForMigration(drv)
if err != nil {
return err
}
defer dbutil.MustClose(sqlDB)

highestAppliedMigrationVersion := ""
pendingMigrations := []Migration{}
for _, migration := range migrations {
if migration.Applied {
continue
if db.Strict && highestAppliedMigrationVersion <= migration.Version {
highestAppliedMigrationVersion = migration.Version
}
} else {
pendingMigrations = append(pendingMigrations, migration)
}
}

fmt.Fprintf(db.Log, "Applying: %s\n", migration.FileName)
if len(pendingMigrations) > 0 {
if db.Strict && pendingMigrations[0].Version <= highestAppliedMigrationVersion {
return fmt.Errorf("migration `%s` is out of order with already applied migrations, the version number has to be higher than the applied migration `%s` in --strict mode", pendingMigrations[0].Version, highestAppliedMigrationVersion)
}

parsed, err := migration.Parse()
sqlDB, err := db.openDatabaseForMigration(drv)
if err != nil {
return err
}
defer dbutil.MustClose(sqlDB)

for _, migration := range pendingMigrations {
fmt.Fprintf(db.Log, "Applying: %s\n", migration.FileName)

execMigration := func(tx dbutil.Transaction) error {
// run actual migration
result, err := tx.Exec(parsed.Up)
parsed, err := migration.Parse()
if err != nil {
return err
} else if db.Verbose {
db.printVerbose(result)
}

// record migration
return drv.InsertMigration(tx, migration.Version)
}
execMigration := func(tx dbutil.Transaction) error {
// run actual migration
result, err := tx.Exec(parsed.Up)
if err != nil {
return err
} else if db.Verbose {
db.printVerbose(result)
}

// record migration
return drv.InsertMigration(tx, migration.Version)
}

if parsed.UpOptions.Transaction() {
// begin transaction
err = doTransaction(sqlDB, execMigration)
} else {
// run outside of transaction
err = execMigration(sqlDB)
}
if parsed.UpOptions.Transaction() {
// begin transaction
err = doTransaction(sqlDB, execMigration)
} else {
// run outside of transaction
err = execMigration(sqlDB)
}

if err != nil {
return err
if err != nil {
return err
}
}
}

Expand Down