Skip to content

Commit 322044b

Browse files
committed
[REF] treat repeatable migrations as versioned migrations with name as version
1 parent 34169f2 commit 322044b

File tree

6 files changed

+48
-85
lines changed

6 files changed

+48
-85
lines changed

internal/migration/apply/apply.go

-5
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ func MigrateAndSeed(ctx context.Context, version string, conn *pgx.Conn, fsys af
1515
if err != nil {
1616
return err
1717
}
18-
repeatableMigrations, err := list.LoadRepeatableMigrations(fsys)
19-
if err != nil {
20-
return err
21-
}
22-
migrations = append(migrations, repeatableMigrations...)
2318
if err := migration.ApplyMigrations(ctx, migrations, conn, afero.NewIOFS(fsys)); err != nil {
2419
return err
2520
}

internal/migration/list/list.go

+36-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"math"
77
"strconv"
8+
"strings"
89

910
"github.com/charmbracelet/glamour"
1011
"github.com/go-errors/errors"
@@ -68,6 +69,40 @@ func makeTable(remoteMigrations, localMigrations []string) string {
6869
j++
6970
}
7071
}
72+
73+
for i, j := 0, 0; i < len(remoteMigrations) || j < len(localMigrations); {
74+
if i < len(remoteMigrations) && !strings.HasPrefix(remoteMigrations[i], "r_") {
75+
i++
76+
continue
77+
}
78+
79+
if j < len(localMigrations) && !strings.HasPrefix(localMigrations[j], "r_") {
80+
j++
81+
continue
82+
}
83+
84+
// Append repeatable migrations to table
85+
if i >= len(remoteMigrations) {
86+
table += fmt.Sprintf("|`%s`|` `|` `|\n", localMigrations[j])
87+
j++
88+
} else if j >= len(localMigrations) {
89+
table += fmt.Sprintf("|` `|`%s`|` `|\n", remoteMigrations[i])
90+
i++
91+
} else {
92+
if localMigrations[j] < remoteMigrations[i] {
93+
table += fmt.Sprintf("|`%s`|` `|` `|\n", localMigrations[j])
94+
j++
95+
} else if remoteMigrations[i] < localMigrations[j] {
96+
table += fmt.Sprintf("|` `|`%s`|` `|\n", remoteMigrations[i])
97+
i++
98+
} else {
99+
table += fmt.Sprintf("|`%s`|`%s`|` `|\n", localMigrations[j], remoteMigrations[i])
100+
i++
101+
j++
102+
}
103+
}
104+
}
105+
71106
return table
72107
}
73108

@@ -99,11 +134,7 @@ func LoadLocalVersions(fsys afero.Fs) ([]string, error) {
99134

100135
func LoadPartialMigrations(version string, fsys afero.Fs) ([]string, error) {
101136
filter := func(v string) bool {
102-
return version == "" || v <= version
137+
return version == "" || strings.HasPrefix(version, "r_") || v <= version
103138
}
104139
return migration.ListLocalMigrations(utils.MigrationsDir, afero.NewIOFS(fsys), filter)
105140
}
106-
107-
func LoadRepeatableMigrations(fsys afero.Fs) ([]string, error) {
108-
return migration.ListRepeatableMigrations(utils.MigrationsDir, afero.NewIOFS(fsys))
109-
}

internal/migration/up/up.go

-8
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,6 @@ func GetPendingMigrations(ctx context.Context, includeAll bool, conn *pgx.Conn,
4646
}
4747
utils.CmdSuggestion = suggestIgnoreFlag(diff)
4848
}
49-
if err != nil {
50-
return diff, err
51-
}
52-
repeatableMigrations, err := migration.ListRepeatableMigrations(utils.MigrationsDir, afero.NewIOFS(fsys))
53-
if err != nil {
54-
return nil, err
55-
}
56-
diff = append(diff, repeatableMigrations...)
5749
return diff, err
5850
}
5951

pkg/migration/file.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type MigrationFile struct {
2323
Statements []string
2424
}
2525

26-
var migrateFilePattern = regexp.MustCompile(`^([0-9]+)_(.*)\.sql$`)
26+
var migrateFilePattern = regexp.MustCompile(`^([0-9]+|r)_(.*)\.sql$`)
2727

2828
func NewMigrationFromFile(path string, fsys fs.FS) (*MigrationFile, error) {
2929
lines, err := parseFile(path, fsys)
@@ -38,6 +38,10 @@ func NewMigrationFromFile(path string, fsys fs.FS) (*MigrationFile, error) {
3838
file.Version = matches[1]
3939
file.Name = matches[2]
4040
}
41+
// Repeatable migration version => r_name
42+
if file.Version == "r" {
43+
file.Version += "_" + file.Name
44+
}
4145
return &file, nil
4246
}
4347

pkg/migration/list.go

+6-25
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"path/filepath"
99
"regexp"
1010
"strconv"
11-
"strings"
1211

1312
"github.com/go-errors/errors"
1413
"github.com/jackc/pgconn"
@@ -46,43 +45,25 @@ func ListLocalMigrations(migrationsDir string, fsys fs.FS, filter ...func(string
4645
fmt.Fprintf(os.Stderr, "Skipping migration %s... (replace \"init\" with a different file name to apply this migration)\n", filename)
4746
continue
4847
}
49-
if strings.HasPrefix(filename, "r_") {
50-
// silently skip repeatable migrations
51-
continue
52-
}
5348
matches := migrateFilePattern.FindStringSubmatch(filename)
5449
if len(matches) == 0 {
55-
fmt.Fprintf(os.Stderr, "Skipping migration %s... (file name must match pattern \"<timestamp>_name.sql\")\n", filename)
50+
fmt.Fprintf(os.Stderr, "Skipping migration %s... (file name must match pattern \"<timestamp>_name.sql\" or \"r_name.sql\")\n", filename)
5651
continue
5752
}
5853
path := filepath.Join(migrationsDir, filename)
5954
for _, keep := range filter {
60-
if version := matches[1]; keep(version) {
55+
version := matches[1]
56+
if version == "r" && len(matches) > 2 {
57+
version += "_" + matches[2]
58+
}
59+
if keep(version) {
6160
clean = append(clean, path)
6261
}
6362
}
6463
}
6564
return clean, nil
6665
}
6766

68-
func ListRepeatableMigrations(migrationsDir string, fsys fs.FS) ([]string, error) {
69-
localMigrations, err := fs.ReadDir(fsys, migrationsDir)
70-
if err != nil && !errors.Is(err, os.ErrNotExist) {
71-
return nil, errors.Errorf("failed to read directory: %w", err)
72-
}
73-
var repeatable []string
74-
75-
for _, migration := range localMigrations {
76-
filename := migration.Name()
77-
if strings.HasPrefix(filename, "r_") && strings.HasSuffix(filename, ".sql") {
78-
path := filepath.Join(migrationsDir, filename)
79-
repeatable = append(repeatable, path)
80-
}
81-
}
82-
83-
return repeatable, nil
84-
}
85-
8667
var initSchemaPattern = regexp.MustCompile(`([0-9]{14})_init\.sql`)
8768

8869
func shouldSkip(name string) bool {

pkg/migration/list_test.go

+1-41
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func TestLocalMigrations(t *testing.T) {
7373
fsys := fs.MapFS{
7474
"20211208000000_init.sql": &fs.MapFile{},
7575
"20211208000001_invalid.ts": &fs.MapFile{},
76+
"r_invalid.ts": &fs.MapFile{},
7677
}
7778
// Run test
7879
versions, err := ListLocalMigrations(".", fsys)
@@ -90,44 +91,3 @@ func TestLocalMigrations(t *testing.T) {
9091
assert.ErrorContains(t, err, "failed to read directory:")
9192
})
9293
}
93-
94-
func TestRepeatableMigrations(t *testing.T) {
95-
t.Run("loads repeatable migrations", func(t *testing.T) {
96-
// Setup in-memory fs
97-
files := []string{
98-
"r_test_view.sql",
99-
"r_test_function.sql",
100-
}
101-
fsys := fs.MapFS{}
102-
for _, name := range files {
103-
fsys[name] = &fs.MapFile{}
104-
}
105-
// Run test
106-
versions, err := ListRepeatableMigrations(".", fsys)
107-
// Check error
108-
assert.NoError(t, err)
109-
assert.ElementsMatch(t, files, versions)
110-
})
111-
112-
t.Run("ignores files without 'r_' prefix", func(t *testing.T) {
113-
// Setup in-memory fs
114-
fsys := fs.MapFS{
115-
"20211208000000_init.sql": &fs.MapFile{},
116-
"r_invalid.ts": &fs.MapFile{},
117-
}
118-
// Run test
119-
versions, err := ListRepeatableMigrations(".", fsys)
120-
// Check error
121-
assert.NoError(t, err)
122-
assert.Empty(t, versions)
123-
})
124-
125-
t.Run("throws error on open failure", func(t *testing.T) {
126-
// Setup in-memory fs
127-
fsys := fs.MapFS{"migrations": &fs.MapFile{}}
128-
// Run test
129-
_, err := ListRepeatableMigrations("migrations", fsys)
130-
// Check error
131-
assert.ErrorContains(t, err, "failed to read directory:")
132-
})
133-
}

0 commit comments

Comments
 (0)