Documentation
¶
Overview ¶
Package migrate is part of the GoFastr framework. See https://github.com/DonaldMurillo/gofastr for documentation.
Index ¶
- Constants
- Variables
- func EnsureDatabase(driver, dsn string) (bool, error)
- func WithAdvisoryLock(ctx context.Context, db *sql.DB, dialect Dialect, ...) error
- func WithAdvisoryLockKey(ctx context.Context, db *sql.DB, dialect Dialect, key int64, ...) error
- type ChecksumMismatchError
- type Dialect
- type Migration
- type MigrationRecord
- type Migrator
- func (m *Migrator) CreateMigrationsTable(ctx context.Context) error
- func (m *Migrator) Down(ctx context.Context, n int) error
- func (m *Migrator) Force(ctx context.Context, version uint64, applied bool) error
- func (m *Migrator) Register(mig Migration)
- func (m *Migrator) RegisterFromReader(r io.Reader) error
- func (m *Migrator) Status(ctx context.Context) (*Status, error)
- func (m *Migrator) Up(ctx context.Context) error
- type Option
- type Status
Constants ¶
const AdvisoryLockKey int64 = 6724469554113028193
AdvisoryLockKey is the fixed 64-bit key used for the migration advisory lock on Postgres. It MUST stay stable across releases: during a rolling deploy an old and a new instance both try to migrate, and they only mutually exclude if they agree on this key. Changing it would let two instances run DDL concurrently — the exact race the lock exists to prevent.
The value is arbitrary but fixed (derived once from "gofastr.migrate" and frozen). Hosts that run unrelated migration tooling against the same database can override it via WithAdvisoryLockKey to avoid cross-tool contention.
Variables ¶
var ErrDirty = errors.New("migrate: database is in a dirty state from a failed migration — reconcile manually and call Force")
ErrDirty is returned by Up and Down when the tracking table records a migration left in a dirty state by a previously failed no-transaction migration. The database may be partially migrated; an operator must reconcile it by hand and then call Force to clear the state before migrations can proceed again.
Functions ¶
func EnsureDatabase ¶
EnsureDatabase creates the target database if it does not already exist, returning whether it had to create it. This is the "first migration can create the DB" capability — call it before running migrations.
- SQLite (and any file/embedded driver): a no-op. The database file is created automatically when the runner opens it, so there is nothing to do; returns (false, nil).
- Postgres: connects to the maintenance `postgres` database (you cannot CREATE a database while connected to the one being created), checks pg_database, and issues CREATE DATABASE when absent. Requires the driver to be linked into the binary and the role to have CREATEDB.
dsn is the normal target DSN (URL form `postgres://…/dbname` or keyword form `host=… dbname=…`); EnsureDatabase derives the admin DSN from it.
func WithAdvisoryLock ¶
func WithAdvisoryLock(ctx context.Context, db *sql.DB, dialect Dialect, fn func(conn *sql.Conn) error) error
WithAdvisoryLock runs fn while holding a database-level lock that serializes migration across every process pointed at the same database. fn receives the pinned *sql.Conn that holds the lock and MUST do all of its work on that connection — running the migration on the same session as the lock is what keeps the whole thing correct on a single-connection pool (MaxOpenConns(1)), which a separate lock connection would deadlock.
- Postgres: a session-level advisory lock on the pinned connection, acquired via pg_try_advisory_lock in a ctx-aware poll loop. A second instance waits until the first releases, or returns promptly if its ctx is cancelled. (A poll loop is used rather than the blocking pg_advisory_lock because lib/pq does not interrupt a blocked pg_advisory_lock on context cancellation — a stuck holder would otherwise hang boot forever.) This is the guard that makes auto-migrate-on-boot safe across N replicas.
- SQLite: no lock is taken (SQLite serializes writers at the file level and the DDL we emit is idempotent), but fn still gets a pinned connection so callers have one uniform code path.
db == nil runs fn(nil) — callers already treat a nil db as a no-op.
func WithAdvisoryLockKey ¶
func WithAdvisoryLockKey(ctx context.Context, db *sql.DB, dialect Dialect, key int64, fn func(conn *sql.Conn) error) error
WithAdvisoryLockKey is WithAdvisoryLock with an explicit lock key, for hosts that need to namespace the lock away from other migration tooling sharing the database.
Types ¶
type ChecksumMismatchError ¶
ChecksumMismatchError is returned by Up when an already-applied migration's recorded checksum no longer matches the registered migration's Up SQL. That means the migration file was edited after it was applied — a drift the runner refuses to paper over, because the live schema no longer matches what the (edited) file says it should be.
func (*ChecksumMismatchError) Error ¶
func (e *ChecksumMismatchError) Error() string
type Migration ¶
type Migration struct {
Version uint64
Name string
Up string // SQL to apply the migration
Down string // SQL to roll back the migration
// NoTransaction runs Up/Down WITHOUT wrapping them in a transaction. The
// escape hatch for statements that cannot run inside a transaction —
// CREATE INDEX CONCURRENTLY, VACUUM, CREATE DATABASE on Postgres. The cost
// is that a failure mid-statement leaves the database partially migrated:
// the runner records the migration dirty before running it and only clears
// that flag on success, so a later run refuses to proceed (ErrDirty) until
// an operator reconciles and calls Force. Set via the `-- +migrate
// NoTransaction` directive in a SQL migration file.
NoTransaction bool
}
Migration represents a single versioned database migration.
type MigrationRecord ¶
type MigrationRecord struct {
Version uint64
Name string
AppliedAt time.Time
Checksum string // SHA-256 of the Up SQL recorded at apply time
Dirty bool // true if a no-transaction migration failed mid-apply
}
MigrationRecord is a row in the _migrations tracking table.
type Migrator ¶
type Migrator struct {
// contains filtered or unexported fields
}
Migrator manages database migrations.
func New ¶
New creates a new Migrator with the given database connection and options. By default the tracking table is "_migrations".
func (*Migrator) CreateMigrationsTable ¶
CreateMigrationsTable ensures the migrations tracking table exists. Public entry point (used by Status and tests); runs on the pool.
func (*Migrator) Down ¶
Down rolls back the last n applied migrations in reverse version order. Serialized by the same advisory lock as Up.
func (*Migrator) Force ¶
Force reconciles the tracking table by hand, the recovery path out of a dirty state and the way to adopt an existing database (baseline).
- applied == true: mark `version` as cleanly applied without running its Up SQL. Inserts the row if missing (baseline), and clears any dirty flag on it. If the version is registered, its name and checksum are recorded so future drift checks line up.
- applied == false: remove `version` from the tracking table entirely, so it is treated as pending again (e.g. a no-transaction migration that did not actually take effect).
Either way the dirty state for that version is cleared, unblocking Up/Down.
func (*Migrator) RegisterFromReader ¶
RegisterFromReader parses migration SQL from a reader and registers it. The expected format uses comment directives:
-- +migrate Version <n> -- +migrate Name <description> -- +migrate Up <up SQL statements> -- +migrate Down <down SQL statements>
func (*Migrator) Status ¶
Status returns the current migration state: which are applied and which are pending.
func (*Migrator) Up ¶
Up runs all pending migrations in version order. Each migration executes in its own transaction. Already-applied migrations are skipped. The whole run is serialized by a database advisory lock so two instances (e.g. rolling- deploy replicas) cannot apply migrations concurrently.
type Option ¶
type Option func(*Migrator)
Option configures a Migrator.
func WithDialect ¶
WithDialect sets the SQL dialect for the migrator. Defaults to DialectPostgres if not specified.
func WithTableName ¶
WithTableName sets a custom name for the migrations tracking table.
type Status ¶
type Status struct {
Applied []MigrationRecord
Pending []Migration
}
Status holds the result of querying migration state.