119 lines
5.3 KiB
Markdown
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.
|