diff --git a/cmd/migrate/config.go b/cmd/migrate/config.go index de812156a..905b5af5d 100644 --- a/cmd/migrate/config.go +++ b/cmd/migrate/config.go @@ -37,6 +37,6 @@ var ( flagConfigFile = pflag.String("config.file", "", "configuration file name without extension") // goto command flags - flagDirty = pflag.Bool("dirty", false, "migration is dirty") - flagPVCPath = pflag.String("intermediate-path", "", "path to the mounted volume which is used to copy the migration files") + flagDirty = pflag.Bool("force-dirty-handling", false, "force the handling of dirty database state") + flagMountPath = pflag.String("cache-dir", "", "path to the mounted volume which is used to copy the migration files") ) diff --git a/internal/cli/main.go b/internal/cli/main.go index 5158de116..d15e33064 100644 --- a/internal/cli/main.go +++ b/internal/cli/main.go @@ -29,7 +29,9 @@ const ( Use -format option to specify a Go time format string. Note: migrations with the same time cause "duplicate migration version" error. Use -tz option to specify the timezone that will be used when generating non-sequential migrations (defaults: UTC). ` - gotoUsage = `goto V [-dirty] [-intermediate-path] Migrate to version V` + gotoUsage = `goto V [-force-dirty-handling] [-cache-dir P] Migrate to version V + Use -force-dirty-handling to handle dirty database state + Use -cache-dir to specify the intermediate path P for storing migrations` upUsage = `up [N] Apply all or N up migrations` downUsage = `down [N] [-all] Apply all or N down migrations Use -all to apply all down migrations` @@ -262,28 +264,18 @@ Database drivers: `+strings.Join(database.List(), ", ")+"\n", createUsage, gotoU if err != nil { log.fatal("error: can't read version argument V") } - handleDirty := viper.GetBool("dirty") - destPath := viper.GetString("intermediate-path") - srcPath := "" - // if sourcePtr is set, use it to get the source path - // otherwise, use the path flag - if path != "" { - srcPath = path - } - if sourcePtr != "" { - // parse the source path from the source argument - parse, err := url.Parse(sourcePtr) - if err != nil { - log.fatal("error: can't parse the source path from the source argument") + handleDirty := viper.GetBool("force-dirty-handling") + if handleDirty { + destPath := viper.GetString("cache-dir") + if destPath == "" { + log.fatal("error: cache-dir must be specified when force-dirty-handling is set") } - srcPath = parse.Path - } - if handleDirty && destPath == "" { - log.fatal("error: intermediate-path must be specified when dirty is set") + if err = migrater.WithDirtyStateHandler(sourcePtr, destPath, handleDirty); err != nil { + log.fatalErr(err) + } } - migrater.WithDirtyStateHandler(srcPath, destPath, handleDirty) if err = gotoCmd(migrater, uint(v)); err != nil { log.fatalErr(err) } diff --git a/migrate.go b/migrate.go index 0793924c1..1d1187254 100644 --- a/migrate.go +++ b/migrate.go @@ -7,7 +7,7 @@ package migrate import ( "errors" "fmt" - "io" + "net/url" "os" "path/filepath" "strconv" @@ -89,13 +89,19 @@ type Migrate struct { LockTimeout time.Duration // DirtyStateHandler is used to handle dirty state of the database - ds *dirtyStateHandler + dirtyStateConf *dirtyStateHandler } type dirtyStateHandler struct { - srcPath string - destPath string - isDirty bool + srcScheme string + srcPath string + destScheme string + destPath string + enable bool +} + +func (m *Migrate) IsDirtyHandlingEnabled() bool { + return m.dirtyStateConf != nil && m.dirtyStateConf.enable && m.dirtyStateConf.destPath != "" } // New returns a new Migrate instance from a source URL and a database URL. @@ -131,6 +137,11 @@ func New(sourceURL, databaseURL string) (*Migrate, error) { } func (m *Migrate) updateSourceDrv(sourceURL string) error { + sourceName, err := iurl.SchemeFromURL(sourceURL) + if err != nil { + return fmt.Errorf("failed to parse scheme from source URL: %w", err) + } + m.sourceName = sourceName sourceDrv, err := source.Open(sourceURL) if err != nil { return fmt.Errorf("failed to open source, %q: %w", sourceURL, err) @@ -207,12 +218,40 @@ func NewWithInstance(sourceName string, sourceInstance source.Driver, databaseNa return m, nil } -func (m *Migrate) WithDirtyStateHandler(srcPath, destPath string, isDirty bool) { - m.ds = &dirtyStateHandler{ - srcPath: srcPath, - destPath: destPath, - isDirty: isDirty, +func (m *Migrate) WithDirtyStateHandler(srcPath, destPath string, isDirty bool) error { + parser := func(path string) (string, string, error) { + var scheme, p string + uri, err := url.Parse(path) + if err != nil { + return "", "", err + } + scheme = uri.Scheme + p = uri.Path + // if no scheme is provided, assume it's a file path + if scheme == "" { + scheme = "file://" + } + return scheme, p, nil + } + + sScheme, sPath, err := parser(srcPath) + if err != nil { + return err + } + + dScheme, dPath, err := parser(destPath) + if err != nil { + return err } + + m.dirtyStateConf = &dirtyStateHandler{ + srcScheme: sScheme, + destScheme: dScheme, + srcPath: sPath, + destPath: dPath, + enable: isDirty, + } + return nil } func newCommon() *Migrate { @@ -255,29 +294,19 @@ func (m *Migrate) Migrate(version uint) error { // if the dirty flag is passed to the 'goto' command, handle the dirty state if dirty { - if m.ds != nil && m.ds.isDirty { - if err = m.unlock(); err != nil { - return m.unlockErr(err) - } - if err = m.HandleDirtyState(); err != nil { + if m.IsDirtyHandlingEnabled() { + if err = m.handleDirtyState(); err != nil { return m.unlockErr(err) } - if err = m.lock(); err != nil { - return err - } - if err = m.updateSourceDrv("file://" + m.ds.destPath); err != nil { - return m.unlockErr(err) - } - } else { - // default behaviour + // default behavior return m.unlockErr(ErrDirty{curVersion}) } } // Copy migrations to the destination directory, // if state was dirty when Migrate was called, we should handle the dirty state first before copying the migrations - if err = m.CopyFiles(); err != nil { + if err = m.copyFiles(); err != nil { return m.unlockErr(err) } @@ -285,16 +314,11 @@ func (m *Migrate) Migrate(version uint) error { go m.read(curVersion, int(version), ret) if err = m.runMigrations(ret); err != nil { - if m.ds != nil && m.ds.isDirty { - // Handle failure: store last successful migration version and exit - if err = m.HandleMigrationFailure(curVersion, version); err != nil { - return m.unlockErr(err) - } - } return m.unlockErr(err) } // Success: Clean up and confirm - if err = m.CleanupFiles(version); err != nil { + // Files are cleaned up after the migration is successful + if err = m.cleanupFiles(version); err != nil { return m.unlockErr(err) } // unlock the database @@ -793,6 +817,7 @@ func (m *Migrate) readDown(from int, limit int, ret chan<- interface{}) { // to stop execution because it might have received a stop signal on the // GracefulStop channel. func (m *Migrate) runMigrations(ret <-chan interface{}) error { + var lastCleanMigrationApplied int for r := range ret { if m.stop() { @@ -814,6 +839,15 @@ func (m *Migrate) runMigrations(ret <-chan interface{}) error { if migr.Body != nil { m.logVerbosePrintf("Read and execute %v\n", migr.LogString()) if err := m.databaseDrv.Run(migr.BufferedBody); err != nil { + if m.dirtyStateConf != nil && m.dirtyStateConf.enable { + // this condition is required if the first migration fails + if lastCleanMigrationApplied == 0 { + lastCleanMigrationApplied = migr.TargetVersion + } + if e := m.handleMigrationFailure(lastCleanMigrationApplied); e != nil { + return multierror.Append(err, e) + } + } return err } } @@ -822,7 +856,7 @@ func (m *Migrate) runMigrations(ret <-chan interface{}) error { if err := m.databaseDrv.SetVersion(migr.TargetVersion, false); err != nil { return err } - + lastCleanMigrationApplied = migr.TargetVersion endTime := time.Now() readTime := migr.FinishedReading.Sub(migr.StartedBuffering) runTime := endTime.Sub(migr.FinishedReading) @@ -1050,9 +1084,20 @@ func (m *Migrate) logErr(err error) { } } -func (m *Migrate) HandleDirtyState() error { - // Perform actions when the database state is dirty - lastSuccessfulMigrationPath := filepath.Join(m.ds.destPath, lastSuccessfulMigrationFile) +func (m *Migrate) handleDirtyState() error { + // Perform the following actions when the database state is dirty + /* + 1. Update the source driver to read the migrations from the mounted volume + 2. Read the last successful migration version from the file + 3. Set the last successful migration version in the schema_migrations table + 4. Delete the last successful migration file + */ + // the source driver should read the migrations from the mounted volume + // as the DB is dirty and last applied migrations to the database are not present in the source path + if err := m.updateSourceDrv(m.dirtyStateConf.destScheme + m.dirtyStateConf.destPath); err != nil { + return err + } + lastSuccessfulMigrationPath := filepath.Join(m.dirtyStateConf.destPath, lastSuccessfulMigrationFile) lastVersionBytes, err := os.ReadFile(lastSuccessfulMigrationPath) if err != nil { return err @@ -1063,11 +1108,12 @@ func (m *Migrate) HandleDirtyState() error { return fmt.Errorf("failed to parse last successful migration version: %w", err) } - if err = m.Force(int(lastVersion)); err != nil { + // Set the last successful migration version in the schema_migrations table + if err = m.databaseDrv.SetVersion(int(lastVersion), false); err != nil { return fmt.Errorf("failed to apply last successful migration: %w", err) } - m.logPrintf("Successfully applied migration: %s", lastVersionStr) + m.logPrintf("Successfully set last successful migration version: %s on the DB", lastVersionStr) if err = os.Remove(lastSuccessfulMigrationPath); err != nil { return err @@ -1077,134 +1123,74 @@ func (m *Migrate) HandleDirtyState() error { return nil } -func (m *Migrate) HandleMigrationFailure(curVersion int, v uint) error { - failedVersion, _, err := m.databaseDrv.Version() - if err != nil { - return err - } - // Determine the last successful migration - lastSuccessfulMigration := strconv.Itoa(curVersion) - ret := make(chan interface{}, m.PrefetchMigrations) - go m.read(curVersion, int(v), ret) - - for r := range ret { - mig, ok := r.(*Migration) - if ok { - if mig.Version == uint(failedVersion) { - break - } - lastSuccessfulMigration = strconv.Itoa(int(mig.Version)) - } +func (m *Migrate) handleMigrationFailure(lastSuccessfulMigration int) error { + if !m.IsDirtyHandlingEnabled() { + return nil } - - lastSuccessfulMigrationPath := filepath.Join(m.ds.destPath, lastSuccessfulMigrationFile) - return os.WriteFile(lastSuccessfulMigrationPath, []byte(lastSuccessfulMigration), 0644) + lastSuccessfulMigrationPath := filepath.Join(m.dirtyStateConf.destPath, lastSuccessfulMigrationFile) + return os.WriteFile(lastSuccessfulMigrationPath, []byte(strconv.Itoa(lastSuccessfulMigration)), 0644) } -func (m *Migrate) CleanupFiles(v uint) error { - if m.ds == nil || m.ds.destPath == "" { +func (m *Migrate) cleanupFiles(targetVersion uint) error { + if !m.IsDirtyHandlingEnabled() { return nil } - files, err := os.ReadDir(m.ds.destPath) + + files, err := os.ReadDir(m.dirtyStateConf.destPath) if err != nil { - return err + // If the directory does not exist + return fmt.Errorf("failed to read directory %s: %w", m.dirtyStateConf.destPath, err) } - targetVersion := uint64(v) - for _, file := range files { fileName := file.Name() - - // Check if file is a migration file we want to process - if !strings.HasSuffix(fileName, "down.sql") && !strings.HasSuffix(fileName, "up.sql") { - continue - } - - // Extract version and compare - versionEnd := strings.Index(fileName, "_") - if versionEnd == -1 { - // Skip files that don't match the expected naming pattern - continue - } - - fileVersion, err := strconv.ParseUint(fileName[:versionEnd], 10, 64) + migration, err := source.Parse(fileName) if err != nil { - m.logErr(fmt.Errorf("skipping file %s due to version parse error: %v", fileName, err)) - continue + return err } - // Delete file if version is greater than targetVersion - if fileVersion > targetVersion { - if err = os.Remove(filepath.Join(m.ds.destPath, fileName)); err != nil { + if migration.Version > targetVersion { + if err = os.Remove(filepath.Join(m.dirtyStateConf.destPath, fileName)); err != nil { m.logErr(fmt.Errorf("failed to delete file %s: %v", fileName, err)) continue } - m.logPrintf("Deleted file: %s", fileName) + m.logPrintf("Migration file: %s removed during cleanup", fileName) } } return nil } -// CopyFiles copies all files from srcDir to destDir. -func (m *Migrate) CopyFiles() error { - if m.ds == nil || m.ds.destPath == "" { +// copyFiles copies all files from source to destination volume. +func (m *Migrate) copyFiles() error { + // this is the case when the dirty handling is disabled + if !m.IsDirtyHandlingEnabled() { return nil } - _, err := os.ReadDir(m.ds.destPath) + + files, err := os.ReadDir(m.dirtyStateConf.srcPath) if err != nil { // If the directory does not exist - return err + return fmt.Errorf("failed to read directory %s: %w", m.dirtyStateConf.srcPath, err) } - - m.logPrintf("Copying files from %s to %s", m.ds.srcPath, m.ds.destPath) - - return filepath.Walk(m.ds.srcPath, func(src string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // ignore sub-directories in the migration directory - if info.IsDir() { - // Skip the tests directory and its files - if info.Name() == "tests" { - return filepath.SkipDir + m.logPrintf("Copying files from %s to %s", m.dirtyStateConf.srcPath, m.dirtyStateConf.destPath) + for _, file := range files { + fileName := file.Name() + if source.Regex.MatchString(fileName) { + fileContentBytes, err := os.ReadFile(filepath.Join(m.dirtyStateConf.srcPath, fileName)) + if err != nil { + return err } - return nil - } - // Ignore the current.sql file - if info.Name() == "current.sql" { - return nil - } - - var ( - srcFile *os.File - destFile *os.File - ) - dest := filepath.Join(m.ds.destPath, info.Name()) - if srcFile, err = os.Open(src); err != nil { - return err - } - defer func(srcFile *os.File) { - if err = srcFile.Close(); err != nil { - m.logErr(fmt.Errorf("failed to close file %s: %v", destFile.Name(), err)) + info, err := file.Info() + if err != nil { + return err } - }(srcFile) - - // Create the destination file - if destFile, err = os.Create(dest); err != nil { - return err - } - defer func(destFile *os.File) { - if err = destFile.Close(); err != nil { - m.logErr(fmt.Errorf("failed to close file %s: %v", destFile.Name(), err)) + if err = os.WriteFile(filepath.Join(m.dirtyStateConf.destPath, fileName), fileContentBytes, info.Mode().Perm()); err != nil { + return err } - }(destFile) - - // Copy the file - if _, err = io.Copy(destFile, srcFile); err != nil { - return err } - return os.Chmod(dest, info.Mode()) - }) + } + + m.logPrintf("Successfully Copied files from %s to %s", m.dirtyStateConf.srcPath, m.dirtyStateConf.destPath) + return nil } diff --git a/migrate_test.go b/migrate_test.go index 61c6b7d73..33bcb2cd9 100644 --- a/migrate_test.go +++ b/migrate_test.go @@ -1418,7 +1418,7 @@ func equalDbSeq(t *testing.T, i int, expected migrationSequence, got *dStub.Stub } } -// Setting up temp directory to be used as a PVC mount +// Setting up temp directory to be used as the volume mount func setupTempDir(t *testing.T) (string, func()) { tempDir, err := os.MkdirTemp("", "migrate_test") if err != nil { @@ -1432,60 +1432,43 @@ func setupTempDir(t *testing.T) (string, func()) { } func setupMigrateInstance(tempDir string) (*Migrate, *dStub.Stub) { - m, _ := New("stub://", "stub://") - m.ds = &dirtyStateHandler{ - destPath: tempDir, - isDirty: true, + scheme := "stub://" + m, _ := New(scheme, scheme) + m.dirtyStateConf = &dirtyStateHandler{ + destScheme: scheme, + destPath: tempDir, + enable: true, } return m, m.databaseDrv.(*dStub.Stub) } -func setupSourceStubMigrations() *source.Migrations { - migrations := source.NewMigrations() - migrations.Append(&source.Migration{Version: 1, Direction: source.Up, Identifier: "CREATE 1"}) - migrations.Append(&source.Migration{Version: 1, Direction: source.Down, Identifier: "DROP 1"}) - migrations.Append(&source.Migration{Version: 2, Direction: source.Up, Identifier: "CREATE 2"}) - migrations.Append(&source.Migration{Version: 2, Direction: source.Down, Identifier: "DROP 2"}) - migrations.Append(&source.Migration{Version: 3, Direction: source.Up, Identifier: "CREATE 3"}) - migrations.Append(&source.Migration{Version: 3, Direction: source.Down, Identifier: "DROP 3"}) - migrations.Append(&source.Migration{Version: 4, Direction: source.Up, Identifier: "CREATE 4"}) - migrations.Append(&source.Migration{Version: 4, Direction: source.Down, Identifier: "DROP 4"}) - migrations.Append(&source.Migration{Version: 5, Direction: source.Up, Identifier: "CREATE 5"}) - migrations.Append(&source.Migration{Version: 5, Direction: source.Down, Identifier: "DROP 5"}) - migrations.Append(&source.Migration{Version: 6, Direction: source.Up, Identifier: "CREATE 6"}) - migrations.Append(&source.Migration{Version: 6, Direction: source.Down, Identifier: "DROP 6"}) - migrations.Append(&source.Migration{Version: 7, Direction: source.Up, Identifier: "CREATE 7"}) - migrations.Append(&source.Migration{Version: 7, Direction: source.Down, Identifier: "DROP 7"}) - return migrations -} - func TestHandleDirtyState(t *testing.T) { tempDir, cleanup := setupTempDir(t) defer cleanup() m, dbDrv := setupMigrateInstance(tempDir) - m.sourceDrv.(*sStub.Stub).Migrations = setupSourceStubMigrations() + m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations tests := []struct { - lastSuccessful int - currentVersion int - err error - setupFailure bool + lastSuccessfulVersion int + currentVersion int + err error + setupFailure bool }{ - {lastSuccessful: 1, currentVersion: 2, err: nil, setupFailure: false}, - {lastSuccessful: 4, currentVersion: 5, err: nil, setupFailure: false}, - {lastSuccessful: 3, currentVersion: 4, err: nil, setupFailure: false}, - {lastSuccessful: -3, currentVersion: 4, err: ErrInvalidVersion, setupFailure: false}, - {lastSuccessful: 4, currentVersion: 3, err: fmt.Errorf("open %s: no such file or directory", filepath.Join(tempDir, lastSuccessfulMigrationFile)), setupFailure: true}, + {lastSuccessfulVersion: 1, currentVersion: 3, err: nil, setupFailure: false}, + {lastSuccessfulVersion: 4, currentVersion: 7, err: nil, setupFailure: false}, + {lastSuccessfulVersion: 3, currentVersion: 4, err: nil, setupFailure: false}, + {lastSuccessfulVersion: -3, currentVersion: 4, err: ErrInvalidVersion, setupFailure: false}, + {lastSuccessfulVersion: 4, currentVersion: 3, err: fmt.Errorf("open %s: no such file or directory", filepath.Join(tempDir, lastSuccessfulMigrationFile)), setupFailure: true}, } for _, test := range tests { t.Run("", func(t *testing.T) { var lastSuccessfulMigrationPath string - // setupFailure tests scenario where the 'lastSuccessfulMigrationFile' doesn't exist + // setupFailure flag helps with testing scenario where the 'lastSuccessfulMigrationFile' doesn't exist if !test.setupFailure { lastSuccessfulMigrationPath = filepath.Join(tempDir, lastSuccessfulMigrationFile) - if err := os.WriteFile(lastSuccessfulMigrationPath, []byte(strconv.Itoa(test.lastSuccessful)), 0644); err != nil { + if err := os.WriteFile(lastSuccessfulMigrationPath, []byte(strconv.Itoa(test.lastSuccessfulVersion)), 0644); err != nil { t.Fatal(err) } } @@ -1504,11 +1487,11 @@ func TestHandleDirtyState(t *testing.T) { } if !b { - t.Fatalf("expected false, got true") + t.Fatalf("expected DB to be dirty, got false") } // Handle dirty state - if err = m.HandleDirtyState(); err != nil { + if err = m.handleDirtyState(); err != nil { if strings.Contains(err.Error(), test.err.Error()) { t.Logf("expected error %v, got %v", test.err, err) if !test.setupFailure { @@ -1526,10 +1509,10 @@ func TestHandleDirtyState(t *testing.T) { t.Fatalf("expected dirty to be false, got true") } // Check 2: Current version should be the last successful version - if dbDrv.CurrentVersion != test.lastSuccessful { - t.Fatalf("expected version %d, got %d", test.lastSuccessful, dbDrv.CurrentVersion) + if dbDrv.CurrentVersion != test.lastSuccessfulVersion { + t.Fatalf("expected version %d, got %d", test.lastSuccessfulVersion, dbDrv.CurrentVersion) } - // Check 3: The lastSuccessfulMigration file shouldn't exists + // Check 3: The lastSuccessfulMigration file shouldn't exist if _, err = os.Stat(lastSuccessfulMigrationPath); !os.IsNotExist(err) { t.Fatalf("expected file to be deleted, but it still exists") } @@ -1541,55 +1524,35 @@ func TestHandleMigrationFailure(t *testing.T) { tempDir, cleanup := setupTempDir(t) defer cleanup() - m, dbDrv := setupMigrateInstance(tempDir) - m.sourceDrv.(*sStub.Stub).Migrations = setupSourceStubMigrations() + m, _ := setupMigrateInstance(tempDir) tests := []struct { - curVersion int - targetVersion uint - dirtyVersion int + lastSuccessFulVersion int }{ - {curVersion: 1, targetVersion: 7, dirtyVersion: 4}, - {curVersion: 4, targetVersion: 6, dirtyVersion: 5}, - {curVersion: 3, targetVersion: 7, dirtyVersion: 6}, + {lastSuccessFulVersion: 3}, + {lastSuccessFulVersion: 4}, + {lastSuccessFulVersion: 5}, } for _, test := range tests { t.Run("", func(t *testing.T) { - t.Cleanup(func() { - m.sourceDrv.(*sStub.Stub).Migrations = setupSourceStubMigrations() - dbDrv = m.databaseDrv.(*dStub.Stub) - }) - - // Setup: Simulate a migration failure by setting the dirty version in the DB - if err := dbDrv.SetVersion(test.dirtyVersion, true); err != nil { - t.Fatal(err) - } - - // Test - if err := m.HandleMigrationFailure(test.curVersion, test.targetVersion); err != nil { + if err := m.handleMigrationFailure(test.lastSuccessFulVersion); err != nil { t.Fatal(err) } - - // Check 1: Should no longer be dirty - if !dbDrv.IsDirty { - t.Fatalf("expected dirty to be true, got false") - } - - // Check 2: last successful Migration version should be stored in a file + // Check 1: last successful Migration version should be stored in a file lastSuccessfulMigrationPath := filepath.Join(tempDir, lastSuccessfulMigrationFile) if _, err := os.Stat(lastSuccessfulMigrationPath); os.IsNotExist(err) { t.Fatalf("expected file to be created, but it does not exist") } - // Check 3: Check if the content of last successful migration has the correct version + // Check 2: Check if the content of last successful migration has the correct version content, err := os.ReadFile(lastSuccessfulMigrationPath) if err != nil { t.Fatal(err) } - if string(content) != strconv.Itoa(test.dirtyVersion-1) { - t.Fatalf("expected %d, got %s", test.dirtyVersion-1, string(content)) + if string(content) != strconv.Itoa(test.lastSuccessFulVersion) { + t.Fatalf("expected %d, got %s", test.lastSuccessFulVersion, string(content)) } }) } @@ -1600,6 +1563,7 @@ func TestCleanupFiles(t *testing.T) { defer cleanup() m, _ := setupMigrateInstance(tempDir) + m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations tests := []struct { migrationFiles []string @@ -1608,14 +1572,14 @@ func TestCleanupFiles(t *testing.T) { emptyDestPath bool }{ { - migrationFiles: []string{"1_up.sql", "2_up.sql", "3_up.sql"}, + migrationFiles: []string{"1_name.up.sql", "2_name.up.sql", "3_name.up.sql"}, targetVersion: 2, - remainingFiles: []string{"1_up.sql", "2_up.sql"}, + remainingFiles: []string{"1_name.up.sql", "2_name.up.sql"}, }, { - migrationFiles: []string{"1_up.sql", "2_up.sql", "3_up.sql", "4_up.sql", "5_up.sql"}, + migrationFiles: []string{"1_name.up.sql", "2_name.up.sql", "3_name.up.sql", "4_name.up.sql", "5_name.up.sql"}, targetVersion: 3, - remainingFiles: []string{"1_up.sql", "2_up.sql", "3_up.sql"}, + remainingFiles: []string{"1_name.up.sql", "2_name.up.sql", "3_name.up.sql"}, }, { migrationFiles: []string{}, @@ -1634,10 +1598,10 @@ func TestCleanupFiles(t *testing.T) { } if test.emptyDestPath { - m.ds.destPath = "" + m.dirtyStateConf.destPath = "" } - if err := m.CleanupFiles(test.targetVersion); err != nil { + if err := m.cleanupFiles(test.targetVersion); err != nil { t.Fatal(err) } @@ -1666,11 +1630,8 @@ func TestCopyFiles(t *testing.T) { destDir, cleanupDest := setupTempDir(t) defer cleanupDest() - m, _ := New("stub://", "stub://") - m.ds = &dirtyStateHandler{ - srcPath: srcDir, - destPath: destDir, - } + m, _ := setupMigrateInstance(destDir) + m.dirtyStateConf.srcPath = srcDir tests := []struct { migrationFiles []string @@ -1678,12 +1639,12 @@ func TestCopyFiles(t *testing.T) { emptyDestPath bool }{ { - migrationFiles: []string{"1_up.sql", "2_up.sql", "3_up.sql"}, - copiedFiles: []string{"1_up.sql", "2_up.sql", "3_up.sql"}, + migrationFiles: []string{"1_name.up.sql", "2_name.up.sql", "3_name.up.sql"}, + copiedFiles: []string{"1_name.up.sql", "2_name.up.sql", "3_name.up.sql"}, }, { - migrationFiles: []string{"1_up.sql", "2_up.sql", "3_up.sql", "4_up.sql", "current.sql"}, - copiedFiles: []string{"1_up.sql", "2_up.sql", "3_up.sql", "4_up.sql"}, + migrationFiles: []string{"1_name.up.sql", "2_name.up.sql", "3_name.up.sql", "4_name.up.sql", "current.sql"}, + copiedFiles: []string{"1_name.up.sql", "2_name.up.sql", "3_name.up.sql", "4_name.up.sql"}, }, { emptyDestPath: true, @@ -1698,10 +1659,10 @@ func TestCopyFiles(t *testing.T) { } } if test.emptyDestPath { - m.ds.destPath = "" + m.dirtyStateConf.destPath = "" } - if err := m.CopyFiles(); err != nil { + if err := m.copyFiles(); err != nil { t.Fatal(err) }