Files
go/migrate/README.md
T

119 lines
5.3 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.
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.
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
CREATE TABLE users (id BIGINT PRIMARY KEY);
-- +goose Down
DROP TABLE users;
```
Goose runs each migration in a transaction by default. Its annotations support
statement blocks and explicit `NO TRANSACTION` where required; such migrations
and nontransactional database DDL can leave partial effects on failure. Down
migrations execute application-authored SQL and may delete data.
| Operation | Provider method |
| --- | --- |
| Apply all pending migrations | `Up(ctx)` |
| Apply pending versions through a target, inclusive | `UpTo(ctx, version)` |
| Roll back versions above a target; zero removes all | `DownTo(ctx, version)` |
| Inspect each migration's state | `Status(ctx)` |
| Current database version | `GetDBVersion(ctx)` |
| Current and latest available versions | `GetVersions(ctx)` |
| Detect pending migrations | `HasPending(ctx)` |
Targets are numeric version bounds, so gaps and timestamps work without special
handling. Up on a fully migrated database succeeds with no results. Operations
may create Goose's `goose_db_version` table even when only inspecting state.
Execution errors retain Goose's details; use `errors.As` with
`*goose.PartialError` to inspect applied and failed migrations. Transactional
failure leaves the database at the last committed migration version. Operation
logs use the injected slog logger; callers handle and log returned errors.
For Go functions, pass migrations created with `goose.NewGoMigration(version,
up, down)` as the trailing arguments to `migrate.New`. Use `goose.GoFunc.RunTx`
for transactional work or `RunDB` for work requiring its own connections. Each
provider should receive its own migration instances. Versions must be unique
across SQL and Go migrations. Global registration is disabled; no `init` or
`goose.AddMigration` calls are needed.
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; lock acquisition retries for about five minutes and unlock retries for
about one minute, subject to the operation context. 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.