migrate

package
v8.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: AGPL-3.0 Imports: 22 Imported by: 0

Documentation

Overview

Package migrate provides the platform's standard database.Migrator: embedded SQL migrations with the operational discipline consumers otherwise hand-roll — an instance-based provider (no global goose state, so parallel tests never race), and on Postgres a session advisory lock that serializes concurrently booting replicas, with probe timeouts tightened so a waiting replica notices the winner promptly instead of goose's leisurely default. Lock and unlock timeouts are configurable via WithLockTimeout and WithUnlockTimeout.

Writing a migration

Put plain SQL in a numbered file — 00001_add_users.sql — and embed the directory. The leading number orders migrations and must be unique. That is the whole contract: the `-- +goose Up` annotation is inserted for you if you omit it, so nothing in a routine migration has to name the migration library.

Only Up is ever applied; this package exposes no Down, so a Down section is inert if present. goose's remaining annotations still work when you need them: fence a statement whose body contains semicolons (a PL/pgSQL function, a DO block) between `-- +goose StatementBegin` and `-- +goose StatementEnd`, or the splitter will tear it apart. New rejects an unfenced dollar-quoted body rather than let that happen quietly. `-- +goose NO TRANSACTION` and `-- +goose ENVSUB` also work, and stay above the inserted annotation.

Migrations are read and checked once, when New is called, so a malformed file fails construction rather than the first Migrate.

Generated migrations

A platform package that owns a table can render its own DDL, and WithGeneratedMigration splices that text into the sequence as if it were a file — so the table is created by your normal migration run instead of by DDL copied into your repository. outbox/migrations is the first of these. The version stays yours to pick, because numbering belongs to whoever owns the sequence; a version a file on disk already uses fails New rather than the first Migrate.

Locking

The advisory lock ID is derived from a caller-supplied lock key: deployments sharing a database serialize on the same key, while schema-isolated parallel tests pass distinct keys (their schema name) and migrate concurrently instead of queueing on a global ID.

Wire it through database/config by passing the Migrator to NewDatabase with RunMigrations enabled, or call Migrate directly with any *sql.DB.

Index

Constants

View Source
const (
	// DefaultLockProbeInterval is how often a waiting process re-checks the
	// advisory lock.
	DefaultLockProbeInterval = time.Second
	// DefaultLockTimeout is how long Migrate waits to acquire the lock.
	DefaultLockTimeout = time.Minute
	// DefaultUnlockProbeInterval is how often a process re-tries releasing the
	// advisory lock.
	DefaultUnlockProbeInterval = time.Second
	// DefaultUnlockTimeout is how long Migrate waits to release the lock.
	DefaultUnlockTimeout = 30 * time.Second
)

Lock-wait defaults. Migrate probes every second rather than goose's 5s default so a waiting replica notices the winner promptly, and gives up after a minute — long enough for a peer's migrations, short enough that a genuinely stuck lock fails the deploy instead of hanging it.

Variables

This section is empty.

Functions

This section is empty.

Types

type Migrator

type Migrator struct {
	// contains filtered or unexported fields
}

Migrator applies embedded goose SQL migrations. Construct with New; the zero value is not usable.

func New

func New(d dialect.Dialect, migrations fs.FS, opts ...Option) (*Migrator, error)

New builds a Migrator over an fs.FS of SQL migration files, usually an embed.FS subtree. Files are named like 00001_description.sql; the leading number orders them and must be unique.

The `-- +goose Up` annotation is optional: New inserts it into any file that omits one, so a migration can be plain SQL in a numbered file. Files that do carry it are left exactly as written, and goose's other annotations (StatementBegin/StatementEnd, NO TRANSACTION, ENVSUB) work as documented upstream either way. Migrations are read once, here, so a malformed one fails construction rather than the first Migrate.

Only Up sections are ever applied — nothing in this package runs a Down — so a Down section, if present, is inert.

func (*Migrator) Migrate

func (m *Migrator) Migrate(ctx context.Context, db *sql.DB) error

Migrate implements database.Migrator: it applies all pending migrations, and is idempotent — an up-to-date database is a no-op. Concurrent callers against one Postgres database serialize on the session advisory lock, so racing replicas wait for the winner instead of erroring.

type Option

type Option func(*Migrator)

Option configures a Migrator.

func WithGeneratedMigration

func WithGeneratedMigration(version uint64, name, body string) Option

WithGeneratedMigration adds a migration whose SQL comes from code rather than from a file in the migrations filesystem. It exists for platform packages that own a table and can render its DDL — outbox is the first — so a consumer does not have to copy that DDL into their repository and keep it in sync.

The version is the caller's to choose, and that is deliberate: migration numbering belongs to whoever owns the sequence, and a platform-chosen number would sooner or later collide with a consumer's. Pick one in your sequence, then never change it — goose keys applied migrations by version, so renumbering an applied migration makes it look unapplied. A version already claimed by a file on disk fails New rather than the first Migrate.

The SQL is annotated and validated exactly like a file would be, so it may contain several statements separated by semicolons:

ddl, err := outboxmigrations.SQL(dialect.Postgres, outbox.DefaultTableName)
// ...
m, err := migrate.New(dialect.Postgres, myMigrations,
	migrate.WithGeneratedMigration(37, "create_outbox_messages", ddl),
)

func WithLockKey

func WithLockKey(key string) Option

WithLockKey partitions the Postgres advisory lock ID. Deployments sharing a database should share a key (the default empty key is fine); schema-isolated parallel tests pass their schema name so they migrate concurrently instead of queueing on one global lock.

func WithLockTimeout

func WithLockTimeout(probeInterval, timeout time.Duration) Option

WithLockTimeout sets how long Migrate waits to acquire the Postgres advisory lock, re-checking every probeInterval. Raise the timeout for a deployment whose migrations legitimately run longer than a minute, so replicas queued behind the winner do not give up mid-deploy.

goose measures the probe interval in whole seconds, so probeInterval must be a positive whole number of seconds and timeout must be at least one probe interval; New rejects anything else. Defaults are DefaultLockProbeInterval and DefaultLockTimeout.

func WithLogger

func WithLogger(logger logging.Logger) Option

WithLogger attaches a logger. Goose's own progress output is routed through it too, so migration logs are structured and attributable instead of going to the standard library's global logger.

func WithMetricsProvider

func WithMetricsProvider(metricsProvider metrics.Provider) Option

WithMetricsProvider attaches a metrics provider, enabling the database_migrator_* instruments.

func WithTracerProvider

func WithTracerProvider(tracerProvider tracing.TracerProvider) Option

WithTracerProvider attaches a tracer provider. Migrate is worth tracing: it is typically the longest blocking step in service startup, and on Postgres it can spend up to a minute waiting on the advisory lock behind a peer that is migrating.

func WithUnlockTimeout

func WithUnlockTimeout(probeInterval, timeout time.Duration) Option

WithUnlockTimeout sets how long Migrate waits to release the Postgres advisory lock, re-trying every probeInterval. It carries the same whole-seconds constraint as WithLockTimeout. Defaults are DefaultUnlockProbeInterval and DefaultUnlockTimeout.

func WithoutLock

func WithoutLock() Option

WithoutLock disables the Postgres session advisory lock. Only safe when exactly one process can be migrating at a time.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL