Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e2f749b
Add dumping the entire applied migrations as a second column of schem…
Mar 11, 2025
ce6d66a
Add missing arguments in sql lite testing.
Mar 11, 2025
eb35cf4
Add missing argument handling in sql.
Mar 11, 2025
c757b90
Add recording both up and down migrations in dump column.
Mar 11, 2025
0971260
Add sync command line argument to synchronize existing database.
Mar 11, 2025
3a1201d
Edit few type mistakes.
Mar 11, 2025
a55ec8d
Cut some unused variables.
Mar 11, 2025
57cb532
Edit wrong return value and for loop.
Mar 11, 2025
ba8356b
Cut mistake in for loop over the map.
Mar 11, 2025
1dc390d
Update db.go
Mar 11, 2025
d7c8d02
Edit tests that had errors.
Mar 11, 2025
2905a94
Add two points for create variables.
Mar 11, 2025
de51878
Add missing columns in schema creation of some sql drivers.
Mar 11, 2025
a142429
Add quotes for from version string in query.
Mar 11, 2025
de366c3
Add selecting all with select query from version.
Mar 11, 2025
b9f5796
Add correct creation of new migration schema.
Mar 11, 2025
b423bb9
Cut strict condition.
Mar 11, 2025
1bd4eed
Add UpdateEmptyDump() function.
Mar 12, 2025
ecc0af1
Cut isEmpty function that doesn't exists.
Mar 12, 2025
0d192a1
Edit some mistakes.
Mar 12, 2025
84894be
Cut another mistake.
Mar 12, 2025
4908a89
Add forgotten print log.
Mar 12, 2025
b12155e
Edit a compile error.
Mar 12, 2025
55371bd
Add missing sprintf argumetns.
Mar 12, 2025
ce53c29
Edit error in postgres and sqlite test.
Mar 12, 2025
c0fbc20
Add missing quotes needed in sqlite.
Mar 12, 2025
acdaefe
Cut adding dump column in existing sqlite db because it doesn't work.
Mar 12, 2025
8886960
Update sqlite.go
Mar 12, 2025
7107dd9
Update clickhouse_test.go
Mar 12, 2025
5c21229
Edit sql procedure to add column because it was wrong.
Mar 12, 2025
a721334
Update mysql.go
Mar 12, 2025
9855666
Edit mysql add column if exists that should now work.
Mar 12, 2025
3a76bff
Update mysql.go
Mar 12, 2025
cea6db3
Update mysql.go
Mar 12, 2025
a34d444
Update mysql.go
Mar 12, 2025
634bc05
Update mysql.go
Mar 12, 2025
10eab03
Update mysql.go
Mar 12, 2025
1014657
Update mysql.go
Mar 12, 2025
7859ad1
Update mysql.go
Mar 12, 2025
78d8423
Update mysql.go
Mar 12, 2025
b14a045
Update mysql.go
Mar 12, 2025
0e59417
Update mysql.go
Mar 12, 2025
15591f0
Update mysql.go
Mar 12, 2025
bce2011
Edit version to v1.0.0.
Mar 12, 2025
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ dbmate drop # drop the database
dbmate migrate # run any pending migrations
dbmate rollback # roll back the most recent migration
dbmate down # alias for rollback
dbmate sync # sync the database to most recent migration or downgrade the database in case the available file migrations are older
dbmate status # show the status of all migrations (supports --exit-code and --quiet)
dbmate dump # write the database schema.sql file
dbmate load # load schema.sql file to the database
Expand Down
16 changes: 16 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,22 @@ func NewApp() *cli.App {
return db.Rollback()
}),
},
{
Name: "sync",
Usage: "Strips eventual later migrations or migrate up in case the database is older",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
EnvVars: []string{"DBMATE_VERBOSE"},
Usage: "print the result of each statement execution",
},
},
Action: action(func(db *dbmate.DB, c *cli.Context) error {
db.Verbose = c.Bool("verbose")
return db.Synchronize()
}),
},
{
Name: "status",
Usage: "List applied and pending migrations",
Expand Down
230 changes: 229 additions & 1 deletion pkg/dbmate/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ func (db *DB) Migrate() error {
}

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

if parsed.UpOptions.Transaction() {
Expand Down Expand Up @@ -514,6 +514,96 @@ func (db *DB) FindMigrations() ([]Migration, error) {
return migrations, nil
}

// UpdateEmptyDumps lists all available migrations
func (db *DB) UpdateEmptyDumps() (error) {
drv, err := db.Driver()
if err != nil {
return err
}

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

// find applied migrations
migrationsTableExists, err := drv.MigrationsTableExists(sqlDB)
if err != nil {
return err
}

// Get all migrations dump from database.
dumpMigrations := map[string]string{}
if migrationsTableExists {
dumpMigrations, err = drv.SelectMigrationsFromVersion(sqlDB, "")
if err != nil {
return err
}
}

// Get all migrations dump from database.
appliedMigrations := map[string]bool{}
if migrationsTableExists {
appliedMigrations, err = drv.SelectMigrations(sqlDB, -1)
if err != nil {
return err
}
}

for _, dir := range db.MigrationsDir {
// find filesystem migrations
files, err := db.readMigrationsDir(dir)
if err != nil {
return fmt.Errorf("%w `%s`", ErrMigrationDirNotFound, dir)
}

for _, file := range files {
if file.IsDir() {
continue
}

matches := migrationFileRegexp.FindStringSubmatch(file.Name())
if len(matches) < 2 {
continue
}

migration := Migration{
Applied: false,
FileName: matches[0],
FilePath: path.Join(dir, matches[0]),
FS: db.FS,
Version: matches[1],
}
if ok := appliedMigrations[migration.Version]; ok {
// Migration already applied, check if the dump column in db is eventually empty.
if dumpMigrations[migration.Version] == "" {
fmt.Fprintf(db.Log, "Updating dump column of: %s\n", migration.Version)

start := time.Now()

parsed, err := migration.Parse()
if err != nil {
return err
}

dumpMigrations[migration.Version] = parsed.Down

err = drv.UpdateMigrationDump(sqlDB, migration.Version, parsed.Down)
if err != nil {
return err
}

elapsed := time.Since(start)
fmt.Fprintf(db.Log, "Updated dump column of: %s in %s\n", migration.Version, elapsed)
}
}
}
}

return nil
}

// Rollback rolls back the most recent migration
func (db *DB) Rollback() error {
drv, err := db.Driver()
Expand Down Expand Up @@ -589,6 +679,144 @@ func (db *DB) Rollback() error {
return nil
}

// Strips eventual later migrations or migrate up in case the database is older.
func (db *DB) Synchronize() error {
drv, err := db.Driver()
if err != nil {
return err
}

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

// find last applied migration
migrations, err := db.FindMigrations()
if err != nil {
return err
}

err = db.UpdateEmptyDumps()
if err != nil {
return err
}

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

if len(pendingMigrations) > 0 && 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,
)
}

// If there is at least one pending up migration we apply them all.
if len(pendingMigrations) > 0 {

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

start := time.Now()

parsed, err := migration.Parse()
if err != nil {
return err
}

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

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

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

elapsed := time.Since(start)
fmt.Fprintf(db.Log, "Applied: %s in %s\n", migration.FileName, elapsed)

if err != nil {
return err
}
}
} else {
// Otherwise we need to check if we need to rollback some newer migration that the db have.
migrationsTableExists, err := drv.MigrationsTableExists(sqlDB)
if err != nil {
return err
}

fmt.Fprintf(db.Log, "Latest available migration file: %s.sql\n", highestAppliedMigrationVersion)

migrationsToBeRolledBack := map[string]string{}
if migrationsTableExists {
migrationsToBeRolledBack, err = drv.SelectMigrationsFromVersion(sqlDB, highestAppliedMigrationVersion)
if err != nil {
return err
}
}
for version, dump := range migrationsToBeRolledBack {
fmt.Fprintf(db.Log, "Rolling back later db migration: %s\n", version)

start := time.Now()

// run actual migration dump rollback
result, err := sqlDB.Exec(dump)
if err != nil {
return drv.QueryError(dump, err)
} else if db.Verbose {
db.printVerbose(result)
}

// record migration
err = drv.DeleteMigration(sqlDB, version)

if err != nil {
return err
}

elapsed := time.Since(start)
fmt.Fprintf(db.Log, "Rolled back: %s in %s\n", version, elapsed)

if err != nil {
return err
}
}
}

// automatically update schema file, silence errors
if db.AutoDumpSchema {
_ = db.DumpSchema()
}

return nil
}

// Status shows the status of all migrations
func (db *DB) Status(quiet bool) (int, error) {
results, err := db.FindMigrations()
Expand Down
4 changes: 3 additions & 1 deletion pkg/dbmate/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ type Driver interface {
MigrationsTableExists(*sql.DB) (bool, error)
CreateMigrationsTable(*sql.DB) error
SelectMigrations(*sql.DB, int) (map[string]bool, error)
InsertMigration(dbutil.Transaction, string) error
SelectMigrationsFromVersion(*sql.DB, string) (map[string]string, error)
InsertMigration(dbutil.Transaction, string, string) error
DeleteMigration(dbutil.Transaction, string) error
UpdateMigrationDump(dbutil.Transaction, string, string) error
Ping() error
QueryError(string, error) error
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/dbmate/version.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dbmate

// Version of dbmate
const Version = "2.26.0"
const Version = "1.0.0"
Loading
Loading