diff --git a/migrate/example/example_test.go b/migrate/example/example_test.go index 51c3f4a..457f132 100644 --- a/migrate/example/example_test.go +++ b/migrate/example/example_test.go @@ -50,6 +50,12 @@ func TestExample(t *testing.T) { reg.Add(migrate.CallComment, "3", log) migrate.Callback = reg.Callback + pending, err := migrate.Pending(context.Background(), session, cql.Files) + if err != nil { + t.Fatal("Pending:", err) + } + t.Log("Pending migrations:", len(pending)) + // First run prints data if err := migrate.FromFS(context.Background(), session, cql.Files); err != nil { t.Fatal("Migrate:", err) diff --git a/migrate/migrate.go b/migrate/migrate.go index b9329b6..b81acb1 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -87,6 +87,48 @@ func List(ctx context.Context, session gocqlx.Session) ([]*Info, error) { return v, nil } +// Pending provides a listing of pending migrations. +func Pending(ctx context.Context, session gocqlx.Session, f fs.FS) ([]*Info, error) { + applied, err := List(ctx, session) + if err != nil { + return nil, err + } + + // Create a set of applied migration names + appliedNames := make(map[string]struct{}, len(applied)) + for _, migration := range applied { + appliedNames[migration.Name] = struct{}{} + } + + fm, err := fs.Glob(f, "*.cql") + if err != nil { + return nil, fmt.Errorf("list migrations: %w", err) + } + + pending := make([]*Info, 0) + + for _, name := range fm { + baseName := filepath.Base(name) + // Check if the migration is not in the applied set + if _, exists := appliedNames[baseName]; !exists { + c, err := fileChecksum(f, name) + if err != nil { + return nil, fmt.Errorf("calculate checksum for %q: %w", name, err) + } + + info := &Info{ + Name: baseName, + StartTime: time.Now(), + Checksum: c, + } + + pending = append(pending, info) + } + } + + return pending, nil +} + func ensureInfoTable(ctx context.Context, session gocqlx.Session) error { return session.ContextQuery(ctx, infoSchema, nil).ExecRelease() } diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index 2aaeb08..f8c5087 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -45,6 +45,54 @@ func recreateTables(tb testing.TB, session gocqlx.Session) { } } +func TestPending(t *testing.T) { + session := gocqlxtest.CreateSession(t) + defer session.Close() + recreateTables(t, session) + + ctx := context.Background() + + t.Run("pending", func(t *testing.T) { + defer recreateTables(t, session) + + f := memfs.New() + writeFile(f, 0, fmt.Sprintf(insertMigrate, 0)+";") + + pending, err := migrate.Pending(ctx, session, f) + if err != nil { + t.Fatal(err) + } + if len(pending) != 1 { + t.Fatal("expected 2 pending migrations got", len(pending)) + } + + err = migrate.FromFS(ctx, session, f) + if err != nil { + t.Fatal(err) + } + + pending, err = migrate.Pending(ctx, session, f) + if err != nil { + t.Fatal(err) + } + if len(pending) != 0 { + t.Fatal("expected no pending migrations got", len(pending)) + } + + for i := 1; i < 3; i++ { + writeFile(f, i, fmt.Sprintf(insertMigrate, i)+";") + } + + pending, err = migrate.Pending(ctx, session, f) + if err != nil { + t.Fatal(err) + } + if len(pending) != 2 { + t.Fatal("expected 2 pending migrations got", len(pending)) + } + }) +} + func TestMigration(t *testing.T) { session := gocqlxtest.CreateSession(t) defer session.Close()