test(migrate): verify canonical SQL-only version ordering
This commit is contained in:
1 parent
badeefb9a9
commit
d912b475b5
5 files changed
+77
-9
No files matched your search
+13
-5
@@ -40,11 +40,19 @@ not defer it when sharing the pool with the application. Use `"."` for SQL files
|
||||
at the embedded root. Files in the selected directory are discovered directly,
|
||||
without recursively searching subdirectories.
|
||||
|
||||
Use unique positive versions in filenames such as
|
||||
`20261027114500_create_users.sql`; sequential versions such as `00007_users.sql`
|
||||
also work. Never derive a version from a file count or add one to the current
|
||||
version. Keep applied migrations immutable and ship the complete history.
|
||||
Goose rejects newly introduced migrations below the current version by default.
|
||||
Use unique positive versions and Goose's migration creator naming convention:
|
||||
zero-padded sequential names such as `00007_users.sql` and `00010_events.sql`, or
|
||||
fixed-width timestamps such as `20261027114500_create_users.sql`. Filenames must
|
||||
sort lexically in numeric version order, including when mixing naming schemes;
|
||||
choose padding wide enough for the complete history. Goose v3.29.0 preserves
|
||||
lexical source order for SQL-only providers, so arbitrary unpadded names such as
|
||||
`7_users.sql` and `10_events.sql` are unsupported by this house convention: they
|
||||
can produce incorrect latest versions and source/status ordering. This package
|
||||
does not rename or normalize migration files.
|
||||
|
||||
Never derive a version from a file count or add one to the current version.
|
||||
Keep applied migrations immutable and ship the complete history. Goose rejects
|
||||
newly introduced migrations below the current version by default.
|
||||
|
||||
```sql
|
||||
-- +goose Up
|
||||
|
||||
+3
-2
@@ -23,8 +23,9 @@ import (
|
||||
// affinity (transaction-pooling proxies cannot safely hold session locks).
|
||||
//
|
||||
// Use the returned Provider's Up, UpTo, DownTo, Status, GetDBVersion, GetVersions,
|
||||
// and HasPending methods directly. Versions may be sequential or timestamps;
|
||||
// Goose orders and tracks them. Global Go migration registration is ignored.
|
||||
// and HasPending methods directly. Filenames must sort lexically in numeric
|
||||
// version order: use zero-padded sequential or fixed-width timestamp prefixes,
|
||||
// as produced by Goose's migration creator. Global Go registration is ignored.
|
||||
func New(db *sql.DB, dialect goose.Dialect, files embed.FS, dir string, logger *slog.Logger, migrations ...*goose.Migration) (*goose.Provider, error) {
|
||||
for i, migration := range migrations {
|
||||
if migration == nil {
|
||||
|
||||
+50
-2
@@ -34,7 +34,7 @@ func TestSQLite(t *testing.T) {
|
||||
name string
|
||||
run func(*testing.T, *sql.DB, goose.Dialect)
|
||||
}{
|
||||
{"flow", testFlow}, {"failure", testFailure},
|
||||
{"flow", testFlow}, {"sql-only", testSQLOnly}, {"failure", testFailure},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
db, err := sql.Open("sqlite", filepath.Join(t.TempDir(), "migrate.db"))
|
||||
@@ -56,7 +56,7 @@ func TestPostgres(t *testing.T) {
|
||||
name string
|
||||
run func(*testing.T, *sql.DB, goose.Dialect)
|
||||
}{
|
||||
{"flow", testFlow}, {"failure", testFailure},
|
||||
{"flow", testFlow}, {"sql-only", testSQLOnly}, {"failure", testFailure},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) { test.run(t, postgresDatabase(t)(), goose.DialectPostgres) })
|
||||
}
|
||||
@@ -133,6 +133,47 @@ func testFlow(t *testing.T, db *sql.DB, dialect goose.Dialect) {
|
||||
}
|
||||
}
|
||||
|
||||
func testSQLOnly(t *testing.T, db *sql.DB, dialect goose.Dialect) {
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithTimeout(t.Context(), time.Minute)
|
||||
defer cancel()
|
||||
for _, test := range []struct {
|
||||
dir string
|
||||
versions []int64
|
||||
}{
|
||||
{"testdata/numeric", []int64{7, 10}},
|
||||
{"testdata/migrations", []int64{7, 20261026093000, 20261027114500}},
|
||||
} {
|
||||
p := newProvider(t, db, dialect, test.dir)
|
||||
assertState(t, ctx, p, 0, test.versions, 0)
|
||||
results, err := p.UpTo(ctx, test.versions[1]-1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertResults(t, results, test.versions[:1])
|
||||
assertState(t, ctx, p, test.versions[0], test.versions, 1)
|
||||
results, err = p.Up(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertResults(t, results, test.versions[1:])
|
||||
assertState(t, ctx, p, test.versions[len(test.versions)-1], test.versions, len(test.versions))
|
||||
assertEvents(t, ctx, db, test.versions)
|
||||
results, err = p.DownTo(ctx, test.versions[1]-1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantDown := slices.Clone(test.versions[1:])
|
||||
slices.Reverse(wantDown)
|
||||
assertResults(t, results, wantDown)
|
||||
assertState(t, ctx, p, test.versions[0], test.versions, 1)
|
||||
if _, err := p.DownTo(ctx, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertState(t, ctx, p, 0, test.versions, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func testFailure(t *testing.T, db *sql.DB, dialect goose.Dialect) {
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithTimeout(t.Context(), time.Minute)
|
||||
@@ -165,6 +206,13 @@ func newProvider(t *testing.T, db *sql.DB, dialect goose.Dialect, dir string, mi
|
||||
|
||||
func assertState(t *testing.T, ctx context.Context, p *goose.Provider, current int64, versions []int64, applied int) {
|
||||
t.Helper()
|
||||
var sources []int64
|
||||
for _, source := range p.ListSources() {
|
||||
sources = append(sources, source.Version)
|
||||
}
|
||||
if !slices.Equal(sources, versions) {
|
||||
t.Fatalf("ListSources = %v; want %v", sources, versions)
|
||||
}
|
||||
got, err := p.GetDBVersion(ctx)
|
||||
if err != nil || got != current {
|
||||
t.Fatalf("GetDBVersion = %d, %v; want %d", got, err, current)
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE migration_events (version BIGINT PRIMARY KEY);
|
||||
INSERT INTO migration_events (version) VALUES (7);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE migration_events;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- +goose Up
|
||||
INSERT INTO migration_events (version) VALUES (10);
|
||||
|
||||
-- +goose Down
|
||||
DELETE FROM migration_events WHERE version = 10;
|
||||
Reference in new issue
Block a user