Files
go/migrate/README.md
T
end f0439000cb
Documentation / docs (push) Successful in 17s
ci / check (push) Successful in 27s
feat(migrate): configure embedded Goose providers (#14)
Add embedded SQL and provider-local Go migrations with slog logging and
Postgres session locking, returning Goose's Provider directly.

Cover timestamp and sequential versions, failure recovery, and concurrent
startup with SQLite and Postgres tests. Reuse page's Postgres CI service.
Document authoring conventions and the Go template compatibility gate.

Closes #7
Refs #9
2026-10-27 23:07:22 -07:00

98 lines
4.6 KiB
Markdown

# migrate
`go.auvem.com/corestack/migrate` sets the house configuration for
[Goose's Provider API](https://pressly.github.io/goose/documentation/provider/).
It returns `*goose.Provider` directly: Goose owns execution, version tracking,
status, and errors. There is no lifecycle dependency or background work.
Create SQL migration files with Goose's
[`create` command](https://pressly.github.io/goose/documentation/cli-commands/#create).
For timestamp-to-sequential reconciliation before deployment, use its
[`fix` command](https://pressly.github.io/goose/documentation/cli-commands/#fix)
as described in [hybrid versioning](https://github.com/pressly/goose#hybrid-versioning).
These commands edit source files before embedding; `fix` does not update database
history. Never renumber migrations already applied to shared databases.
**Goose's default Go template is not compatible unchanged with this package.**
It registers migrations globally from `init`, while this package uses explicit
provider-local registration. Until the compatible authoring CLI planned in
[#9](https://gitea.auvem.com/corestack/go/issues/9) is available, Go migrations
require the adaptation described below; do not treat stock `goose create ... go`
output as ready to use. That follow-up must supply and document the supported
creation/reconciliation workflow before it is complete.
Embed migrations in the application's binary and run them before serving traffic:
```go
package app
import (
"context"
"database/sql"
"embed"
"log/slog"
"github.com/pressly/goose/v3"
"go.auvem.com/corestack/migrate"
)
//go:embed migrations/*.sql
var migrations embed.FS
func migrateDatabase(ctx context.Context, db *sql.DB, logger *slog.Logger) error {
provider, err := migrate.New(db, goose.DialectPostgres, migrations, "migrations", logger)
if err != nil {
return err
}
_, err = provider.Up(ctx)
return err
}
```
The caller opens and closes `db`, selects a matching database driver and dialect,
supplies a nonnil logger, and controls the operation context/deadline. Construction
is offline; SQL is parsed lazily by Goose. `Provider.Close` also closes `db`, so do
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.
Preserve Goose's creator/fix filenames and ship the complete migration history.
The pinned Goose SQL-only provider relies on lexical filename order matching
numeric version order. Timestamp versions and gaps are supported; never derive
versions from file counts. For operations, errors, and SQL annotations, use the
[Provider API](https://pkg.go.dev/github.com/pressly/goose/v3#Provider) and
[SQL migration docs](https://pressly.github.io/goose/documentation/annotations/).
For Go functions, use Goose's
[provider-local registration](https://pressly.github.io/goose/documentation/provider/#withgomigrations):
pass fresh `goose.NewGoMigration` values as the trailing arguments to `migrate.New`.
The creator's default Go template instead calls `goose.AddMigrationContext` from
`init`; replace that registration with provider-local construction. Global
registration is disabled to keep each provider's migration set isolated and avoid
import side effects. If `fix` renames Go migrations, update their explicit
registration versions to match. Versions must be unique across SQL and Go files.
Postgres providers automatically use Goose's default session advisory locker.
Concurrent startup calls from separate providers/processes serialize migration
execution and recheck applied versions under the lock. The default lock ID
serializes Goose migrations throughout the same database, including different
schemas. Use a direct connection or a session-pooling proxy: transaction pooling
cannot preserve session locks.
`RunDB` Go migrations need spare pool connections while the locked session is
held; avoid a one-connection pool for them.
Other dialects have no cross-provider/process lock configured by this package.
Serialize migration runs externally, for example with a single deployment job.
SQLite tests validate the SQL convention, not concurrent daemon startup safety.
Run the integration suite against an existing Postgres database with schema
creation privileges:
```sh
MIGRATE_POSTGRES_DSN='postgres://user:password@localhost/testdb?sslmode=disable' go test -race ./migrate
```
Tests create and clean only random schemas they own. Without a DSN, Postgres
tests skip locally; in CI a missing DSN fails, and the CI job supplies a Postgres
service. SQLite tests always run in process using temporary databases.