migrations

package module
v4.0.1 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 15 Imported by: 0

README

migrations

CI CodeQL Coverage Mutation Documentation Go Reference Release Go License

migrations is an engine-neutral database migration runtime with a PostgreSQL backend. It owns migration identity, planning, status, locking, checksums, baselines, recovery, and the public.migrations ledger. Goose is an internal, pinned SQL execution detail and never appears in the public API.

This source tree contains the github.com/faustbrian/go-migrations/v4 module. V3.0.0 introduces the public.migrations ledger name. Published v1 and v2 releases remain available under their original module paths. Application adoption is a separate deployment boundary. The minimum supported language version is Go 1.27.0; build with Go 1.27.3 or newer, which fixes GO-2026-6809 in crypto/x509.

Install

Install the v4 module with:

go get github.com/faustbrian/go-migrations/v4@v4.0.0

V3 consumers upgrade by replacing /v3 with /v4 in imports and moving service composition to the single Service v3 adapter (see migration). The ledger, checksums and advisory lock are unchanged, so no database alignment is required. V2 consumers upgrade by replacing /v2 with /v3 in migration imports, stopping older runners, and manually aligning the database ledger before starting v3. V1 consumers must also provide the bounded, cancellation-aware SourceFileSystem contract and review the finite PostgreSQL timeout defaults. Laravel history must be relocated out of public.migrations before adoption; see the baseline runbook.

The supported Go and PostgreSQL versions are documented in compatibility.

Quick start

The complete embedded-migrations job is the compiler-built quick start. It opens a caller-owned *sql.DB, constructs the filesystem source, PostgreSQL backend, and runner, inspects the plan, applies pending migrations, and reads final status. From a checkout, point it at a disposable PostgreSQL database:

DATABASE_URL='postgres://localhost:5432/example?sslmode=disable' \
  go run ./examples/job

The example migrations live beside the command in examples/job/migrations. Run this code in a dedicated deployment job; do not run it implicitly in every service process.

Packages

The v4 module uses these import paths.

Import path Use
github.com/faustbrian/go-migrations/v4 Define immutable migrations, load sources, plan, inspect status, apply, roll back, baseline, and recover.
github.com/faustbrian/go-migrations/v4/postgres Persist the owned ledger and execute migrations under a PostgreSQL advisory lock.
github.com/faustbrian/go-migrations/v4/adapters/service Adapt a caller-constructed runner to the standard one-shot Service v3 migrate command.
github.com/faustbrian/go-migrations/v4/conformance Verify an alternative backend against the public engine contract in tests.

examples/job is an executable integration example, not a reusable package.

When to use it

Use this module when an application needs immutable embedded SQL migrations, an owned PostgreSQL ledger, deterministic plans, and explicit dirty-state recovery. Use the conformance package when replacing the execution backend. Do not use it as an ORM, schema declaration language, service-startup hook, or wrapper around Laravel or Goose migration history. Deployment orchestration, retry policy, and ownership of the application database remain outside this module.

Construction and lifecycle

NewFSSource, postgres.New, and NewRunner validate their inputs without opening a migration session or executing SQL. NewRunner defaults lock-release cleanup to 30 seconds. The PostgreSQL backend polls a held advisory lock every 100 milliseconds, defaults lock acquisition to 30 seconds, and defaults each migration statement to five minutes. Primary work in each operation, including waiting for serialized session ownership, defaults to ten minutes. Detached best-effort statement-timeout restoration and lock release each use a separate finite cleanup budget of 30 seconds by default; WithUnlockTimeout can replace the runner release budget. WithLockTimeout, WithStatementTimeout, and WithOperationTimeout replace those defaults with positive finite values; zero and negative overrides are rejected. Invalid options and nil collaborators fail construction.

NewFSSource accepts a caller-owned SourceFileSystem. A complete source load defaults to ten minutes; WithSourceTimeout accepts a positive finite override. Its ReadDir and ReadFile implementations must pass the supplied context to every blocking operation and enforce the supplied count and byte limits before retaining or returning data. The package revalidates returned inventories and file bytes. The embedded example implements this boundary over compiler-owned immutable data; networked or disk-backed providers need native cancellation-aware I/O.

Runner and PostgreSQL backend functional options are applied from left to right. Construction stops at the first nil or failing option, so later options do not run; repeated valid settings use the last applied value. Neither constructor acquires a migration lock, runs SQL, or takes ownership of the caller-provided database while applying options.

Every plan, status, apply, rollback, baseline, and recovery operation accepts the caller's context.Context. It acquires one connection-bound session, validates the complete source and ledger, and releases the session before returning. Cancellation is returned through the ordinary error chain; non-transactional work can instead leave a visible dirty record when the external outcome is not safely reversible. Stable error categories support errors.Is, while wrapped causes retain operational detail.

PostgreSQL operation and SQL execution categories match through errors.Is without becoming extra branches in the cause tree. SQL execution redacts each non-context failure to ErrExecutionFailed, preserving joined cancellation and independent failures without exposing driver diagnostics. A context match alone does not prove caller interruption: check the caller's context and all remaining causes before treating a failure as a clean cancellation.

Go's standard database/sql transaction finalization and cleanup calls cannot accept a context once driver cleanup begins. Use maintained drivers with finite server and network timeouts plus a supervised deployment-process hard deadline, and inspect database and ledger state before retry after expiry.

The runner starts no goroutines and has no Close or Shutdown method. The caller retains the source, observer, and *sql.DB and must close the database. Concurrent operations are serialized by the backend's advisory lock. An observer may be called concurrently when callers share a runner. Events have no SQL field, but custom dependency errors can contain sensitive text, and traversing PostgreSQL error causes exposes driver details. Observer panics are contained. Observers are trusted synchronous callbacks and must perform bounded, nonblocking work. Honor the supplied context when applicable, including the caller's own finite bound for cleanup events whose context is detached from cancellation. The runner cannot preempt a blocked callback; a callback can delay lock release and the operation's return. Use a bounded, nonblocking application-owned handoff for slow exporters.

Service migrate command

adapters/service.New adapts a caller-constructed Runner to the standard one-shot service migrate role. The caller loads typed configuration, prepares only migration dependencies, and explicitly selects the runner operation. The adapter adds no migration policy, HTTP listener, management server, readiness check, retries, or long-lived resource ownership.

The adapter uses github.com/faustbrian/go-service/v3 types: New(Options[C]{...}) returns an adapter whose Prepare callback yields an Execution. It does not translate migration SQL, ledger rows or checksums.

Migration-only components start before the task and stop in reverse order after it finishes or fails. A missing runner fails during plan construction. Use a dedicated deployment job and select Runner.Up, Plan, Status, Down, or recovery behavior explicitly according to the reviewed operation.

Integrations and companion packages

Safety properties

  • Independent migration owners can derive backend.ForNamespace("outbox"). The selected ledger is exactly public.golib_migrations_outbox; default runners still use public.migrations. All owners share the native lock.

  • ReadLedger performs bounded read-only discovery without creating a ledger. Its snapshot is not execution ownership; revalidate under the native lock.

  • InstallFresh installs a complete transactional history and optional package-owned preflight atomically. Existing history is not adopted/replayed.

  • AcquireCommand retains one connection and migration ownership across independent histories. Execute commits each transactional batch, native ledger rows and command receipts together. Exact replay checks the original source/base/key and resumes only its durable prefix. Release ownership on every path and reconstruct it after an uncertain commit acknowledgement. See independent migration owners for limits, recovery and adoption requirements.

  • Loaded source is validated against complete ledger history under the advisory lock; source loading happens before lock acquisition. Keep artifacts immutable.

  • Applied files cannot be changed, renamed, removed, or reordered.

  • Transactional migrations update schema and ledger atomically.

  • Explicit no-transaction migrations persist dirty state before executing SQL.

  • Dirty outcomes require a checksum-bound operator recovery decision.

  • Existing Laravel databases are adopted through a reviewed selected-catalog fingerprint after relocating Laravel history out of public.migrations. Grants/ownership, RLS flags/policy roles and other omitted properties need separate operator review.

  • Status, plans, records and migration values protect their retained state. Events have no SQL field, but custom dependency errors may retain sensitive causes. A failed invocation can follow durable changes; inspect before retry.

Read the migration format, operations guide, and Laravel baseline runbook before production use.

Documentation

Start with the documentation index. It organizes migration formats, PostgreSQL integration, production operations, Laravel adoption, and maintainer references. The v4 API reference, executable example, backend testing helper, FAQ and troubleshooting, performance baselines, compatibility policy, changelog, support guide, and private security-reporting process cover the remaining adoption paths.

For ecosystem-wide selection and ownership guidance, see the versioned Golib ecosystem index and its Persistence and durability family.

Development

make check and make ci use the published tool pinned in .golib.yaml for bounded verification with PostgreSQL fixtures, the integration tag and deterministic operation-gate cancellation/session-ownership assertions. The unchanged cheap engine-boundary guard remains required locally and in CI. Base-free checks run fuzz seeds; changed-source CI selects active smoke. make check-full retains all explicit full campaigns. Scheduled CI uses the shared bounded route. See contributor verification for standalone tests, public installation, disposable-cache sessions and explicit release rehearsal; local gate tests do not prove live PostgreSQL locks.

License

migrations is open-source software licensed under the MIT License.

Documentation navigation

Complete documentation map · Current source package map

Use the versioned ecosystem entry for package selection. The frozen compatibility cohort is separate from current package releases and unpublished source changes. Adopting them does not silently update that cohort.

Documentation

Overview

Package migrations loads immutable SQL histories, validates checksum-bound ledger state, and coordinates plans, execution, baselines and dirty recovery. Construct a Source and Backend, then use NewRunner for database operations; PlanUp, PlanDown and BuildStatus are database-independent validation helpers.

Each runner operation loads source before acquiring a backend session and validates that loaded history against the locked ledger. Callers must keep deployment sources immutable; the database lock does not freeze a filesystem. The runner borrows its dependencies, starts no goroutines and does not close the caller's database. Shared sources, backends and observers must support the concurrency with which callers invoke them.

SQL is trusted deployment code, not a sandboxed or authorized request. A checksum detects identity changes; it is neither a signature nor proof that SQL is safe or reversible. Recovery records an operator-reviewed assertion, not an automatic inspection or repair of database effects. See the owned v1 format in docs/migration-format.md and lifecycle in docs/operations.md.

Index

Constants

View Source
const (

	// MaxMigrationFiles is the largest complete source inventory retained by
	// one load operation.
	MaxMigrationFiles = 4096
	// MaxMigrationSourceRootBytes is the largest source root path accepted by
	// NewFSSource.
	MaxMigrationSourceRootBytes = 4096
	// MaxMigrationSourceNameBytes bounds aggregate filename data returned by a
	// source directory operation.
	MaxMigrationSourceNameBytes = 1 << 20
	// MaxMigrationSourceBytes bounds aggregate migration-file content read by
	// one load operation.
	MaxMigrationSourceBytes = 16 << 20
)
View Source
const MaxMigrationNameBytes = 255

MaxMigrationNameBytes is the largest canonical migration, baseline, or ledger-record name accepted by the public contracts.

View Source
const (
	// MaxMigrationRecords is the largest complete ledger history accepted by
	// planning and status operations: one baseline plus MaxMigrationFiles.
	MaxMigrationRecords = MaxMigrationFiles + 1
)

Variables

View Source
var (
	// ErrInvalidBaseline indicates malformed reviewed baseline metadata.
	ErrInvalidBaseline = errors.New("invalid schema baseline")
	// ErrBaselineMismatch indicates live schema drift from the reviewed contract.
	ErrBaselineMismatch = errors.New("schema does not match reviewed baseline")
	// ErrBaselineExists indicates that owned migration history is not empty.
	ErrBaselineExists = errors.New("migration baseline already exists")
	// ErrBaselineVersionConflict indicates Go migrations at or below the
	// baseline cutoff.
	ErrBaselineVersionConflict = errors.New("go migration conflicts with baseline version")
	// ErrBaselineUnsupported indicates a backend without baseline conformance.
	ErrBaselineUnsupported = errors.New("migration backend does not support baselines")
)
View Source
var (
	// ErrInvalidVersion indicates that a migration version is zero or malformed.
	ErrInvalidVersion = errors.New("invalid migration version")
	// ErrInvalidName indicates that a migration name is not canonical snake case.
	ErrInvalidName = errors.New("invalid migration name")
	// ErrInvalidTransactionMode indicates an unknown transaction mode.
	ErrInvalidTransactionMode = errors.New("invalid transaction mode")
	// ErrEmptyUpSQL indicates that a migration has no forward operation.
	ErrEmptyUpSQL = errors.New("migration up SQL is empty")
	// ErrInvalidChecksum indicates malformed or unsupported checksum text.
	ErrInvalidChecksum = errors.New("invalid migration checksum")
)
View Source
var (
	// ErrInvalidRecord indicates malformed owned-ledger data.
	ErrInvalidRecord = errors.New("invalid migration record")
	// ErrHistoryLimit indicates source or ledger history beyond the public
	// planning and status retention budget.
	ErrHistoryLimit = errors.New("migration history limit exceeded")
	// ErrDirty indicates an interrupted or partially applied migration.
	ErrDirty = errors.New("dirty migration history")
	// ErrChecksumMismatch indicates an applied migration was modified.
	ErrChecksumMismatch = errors.New("migration checksum mismatch")
	// ErrRenamedMigration indicates an applied version now has another name.
	ErrRenamedMigration = errors.New("applied migration was renamed")
	// ErrDeletedMigration indicates an applied migration is absent from source.
	ErrDeletedMigration = errors.New("applied migration was deleted")
	// ErrReorderedHistory indicates non-monotonic or non-prefix history.
	ErrReorderedHistory = errors.New("migration history was reordered")
	// ErrInvalidTarget indicates an impossible or ambiguous plan target.
	ErrInvalidTarget = errors.New("invalid migration target")
	// ErrIrreversible indicates that rollback SQL was not provided.
	ErrIrreversible = errors.New("migration is irreversible")
)
View Source
var (
	// ErrInvalidRecovery indicates malformed recovery input.
	ErrInvalidRecovery = errors.New("invalid dirty migration recovery")
	// ErrRecoveryMismatch indicates that source identity differs from the
	// operator-reviewed recovery request.
	ErrRecoveryMismatch = errors.New("dirty migration recovery identity mismatch")
	// ErrNoDirtyMigration indicates there is no matching unresolved outcome.
	ErrNoDirtyMigration = errors.New("dirty migration not found")
	// ErrRecoveryUnsupported indicates a backend without recovery conformance.
	ErrRecoveryUnsupported = errors.New("migration backend does not support recovery")
	// ErrRecoveryConflict indicates more than one dirty migration in the ledger.
	ErrRecoveryConflict = errors.New("multiple dirty migrations require recovery")
)
View Source
var (
	// ErrInvalidRunner indicates missing or inconsistent runner dependencies.
	ErrInvalidRunner = errors.New("invalid migration runner")
	// ErrBackendResult indicates an engine violated the public backend contract.
	ErrBackendResult = errors.New("invalid migration backend result")
	// ErrExecutionFailed identifies migration SQL execution failure without
	// exposing database-driver diagnostics or migration contents.
	ErrExecutionFailed = errors.New("migration SQL execution failed")
)
View Source
var (
	// ErrInvalidSource indicates an unusable filesystem source configuration.
	ErrInvalidSource = errors.New("invalid migration source")
	// ErrInvalidFilename indicates a non-canonical migration filename.
	ErrInvalidFilename = errors.New("invalid migration filename")
	// ErrInvalidFormat indicates malformed or ambiguous migration directives.
	ErrInvalidFormat = errors.New("invalid migration format")
	// ErrInvalidEncoding indicates non-UTF-8, NUL-containing, or oversized input.
	ErrInvalidEncoding = errors.New("invalid migration encoding")
	// ErrUnexpectedSourceEntry indicates a non-migration entry in the source.
	ErrUnexpectedSourceEntry = errors.New("unexpected migration source entry")
	// ErrDuplicateVersion indicates that two source files claim one identity.
	ErrDuplicateVersion = errors.New("duplicate migration version")
	// ErrSourceLimit indicates a source inventory whose count, filename data, or
	// aggregate file content exceeds the finite load budget.
	ErrSourceLimit = errors.New("migration source limit exceeded")
)

Functions

This section is empty.

Types

type Action

type Action uint8

Action identifies the operation represented by a plan step.

const (
	// ActionApply executes a migration's up SQL.
	ActionApply Action = iota + 1
	// ActionRollback executes a migration's down SQL.
	ActionRollback
)

type Backend

type Backend interface {
	// Acquire returns a nonnil exclusively owned session or an error. The
	// backend must serialize cooperating jobs, honor cancellation and ensure an
	// unsuccessful acquisition does not leave an unmanaged locked connection.
	Acquire(context.Context) (Session, error)
}

Backend is the replaceable migration-engine conformance boundary.

Session.Apply must return only after both execution and owned-ledger persistence have reached an explicit recoverable outcome. Implementations must leave a dirty record when non-transactional execution has an uncertain or partial result. Every operation must honor its context and return bounded results required by Session.

type Baseline

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

Baseline is an immutable reviewed schema assertion for adopting an existing database without replaying historical migrations.

func NewBaseline

func NewBaseline(version Version, name string, fingerprint Checksum) (Baseline, error)

NewBaseline validates version, canonical name and nonzero fingerprint. It does not inspect a database or establish that a human reviewed the schema; Runner.Baseline asks the supporting backend to compare the fingerprint.

func (Baseline) Fingerprint

func (baseline Baseline) Fingerprint() Checksum

Fingerprint returns the expected canonical schema digest.

func (Baseline) Name

func (baseline Baseline) Name() string

Name returns the reviewed baseline contract name.

func (Baseline) Version

func (baseline Baseline) Version() Version

Version returns the immutable cutoff before all Go-owned migrations.

type Checksum

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

Checksum is a SHA-256 digest used for migration identity or a package-defined schema fingerprint. Its zero value is invalid for persisted contracts.

func ChecksumData

func ChecksumData(canonical []byte) Checksum

ChecksumData hashes the supplied bytes with SHA-256 without normalization, validation or authentication. Callers define any canonical framing; migration identity instead uses the built-in versioned, byte-length-prefixed format.

func ParseChecksum

func ParseChecksum(value string) (Checksum, error)

ParseChecksum accepts exactly sha256: followed by 64 lowercase hexadecimal digits, rejecting the all-zero digest. Other spellings are not normalized.

func (Checksum) GoString

func (checksum Checksum) GoString() string

GoString prevents checksum internals from becoming an accidental public serialization contract while keeping diagnostics useful.

func (Checksum) String

func (checksum Checksum) String() string

String returns the algorithm-qualified ledger representation.

type Event

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

Event is an immutable structured diagnostic suitable for logging and tracing adapters. There is no SQL text field, but an error from a custom dependency can contain sensitive data. The runner does not sanitize every error cause.

func (Event) Duration

func (event Event) Duration() time.Duration

Duration returns elapsed time for completed or failed operations.

func (Event) Err

func (event Event) Err() error

Err returns the operation error, except that baseline-mismatch observer events use the classification alone. PostgreSQL operation errors redact ordinary formatting but can expose driver causes through error traversal; custom backend errors are retained. Do not log unwrapped causes.

func (Event) Operation

func (event Event) Operation() Operation

Operation returns the lifecycle operation.

func (Event) Phase

func (event Event) Phase() Phase

Phase returns the lifecycle phase.

func (Event) Version

func (event Event) Version() Version

Version returns the affected migration version, or zero for job operations.

type FSSource

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

FSSource loads canonical SQL migrations from one cancellation-aware source directory and rejects unrelated entries so packaging mistakes fail closed. It borrows the provider and does not freeze directory/file reads into an atomic snapshot. Use immutable deployment artifacts, not a changing directory.

func NewFSSource

func NewFSSource(sourceFS SourceFileSystem, root string, options ...FSSourceOption) (*FSSource, error)

NewFSSource constructs a source rooted at a valid fs.ValidPath directory of at most MaxMigrationSourceRootBytes. It borrows sourceFS without invoking it itself; caller options run left-to-right until the first nil or failing option.

func (*FSSource) Load

func (source *FSSource) Load(ctx context.Context) ([]Migration, error)

Load reads, validates and sorts the complete migration history using a finite load context and inclusive provider budgets. It returns no partial history on error. Filename versions may have leading zeroes; their numeric identities must be unique. SQL file directives use the owned v1 format, not SQL grammar. Provider errors are reduced to context/budget/encoding classifications or ErrInvalidSource; this does not sanitize arbitrary custom Source errors.

type FSSourceOption

type FSSourceOption func(*FSSource) error

FSSourceOption configures a filesystem source during construction only.

func WithSourceTimeout

func WithSourceTimeout(timeout time.Duration) FSSourceOption

WithSourceTimeout replaces the ten-minute complete-load timeout with a positive duration. This bounds cooperative provider work, not arbitrary code.

type Migration

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

Migration is an immutable canonical SQL migration.

Fields are intentionally private so callers and execution engines cannot mutate identity or persisted checksum state after validation.

func NewMigration

func NewMigration(
	version Version,
	name string,
	transactionMode TransactionMode,
	upSQL string,
	downSQL string,
) (Migration, error)

NewMigration validates identity, transaction mode and UTF-8, NUL-free SQL totaling at most 16 MiB. Names contain lowercase ASCII letters/digits with single separating underscores and at most MaxMigrationNameBytes bytes. Up SQL must not be blank; empty down SQL denotes irreversibility, whereas nonempty whitespace-only down SQL is rejected. SQL bytes and line endings are preserved, not parsed for grammar or normalized before the v1 checksum. Unlike FSSource file validation, this constructor does not reject a BOM.

func (Migration) Checksum

func (migration Migration) Checksum() Checksum

Checksum returns the immutable content digest.

func (Migration) DownSQL

func (migration Migration) DownSQL() string

DownSQL returns the canonical rollback SQL, or an empty string when the migration is irreversible.

func (Migration) Name

func (migration Migration) Name() string

Name returns the canonical migration name without version or extension.

func (Migration) TransactionMode

func (migration Migration) TransactionMode() TransactionMode

TransactionMode returns the execution transaction policy.

func (Migration) UpSQL

func (migration Migration) UpSQL() string

UpSQL returns the exact validated forward SQL, including whitespace.

func (Migration) Version

func (migration Migration) Version() Version

Version returns the immutable migration version.

type Observer

type Observer interface {
	// Observe receives an event inline. Panics are contained, but blocking is
	// not preempted. Cleanup events use a context detached from cancellation;
	// observers need their own finite bound for that work.
	Observe(context.Context, Event)
}

Observer consumes synchronous migration events. Shared observers must be concurrency-safe and bounded/nonblocking; they are trusted caller code.

type Operation

type Operation uint8

Operation identifies an observable migration lifecycle operation.

const (
	// OperationLock acquires exclusive migration-job ownership.
	OperationLock Operation = iota + 1
	// OperationApply applies one migration and persists its outcome.
	OperationApply
	// OperationRollback rolls back one migration and removes its ledger record.
	OperationRollback
	// OperationBaseline validates and records one reviewed existing schema.
	OperationBaseline
	// OperationRecover resolves one explicitly reviewed dirty outcome.
	OperationRecover
	// OperationUnlock releases exclusive migration-job ownership.
	OperationUnlock
)

type Option

type Option func(*Runner) error

Option configures a Runner during construction only.

func WithObserver

func WithObserver(observer Observer) Option

WithObserver installs a structured event observer. Observer panics are contained; callbacks still run synchronously and can delay work or cleanup. A nil interface is rejected; the caller must ensure any typed value is usable.

func WithUnlockTimeout

func WithUnlockTimeout(timeout time.Duration) Option

WithUnlockTimeout replaces the default 30-second, best-effort release deadline with a positive duration. Release uses context.WithoutCancel so caller cancellation does not skip its attempt. It cannot forcibly bound a blocking observer, uncooperative backend or context-free driver finalizer.

type Phase

type Phase uint8

Phase identifies the lifecycle phase of an emitted operation.

const (
	// PhaseStarted is emitted immediately before an operation.
	PhaseStarted Phase = iota + 1
	// PhaseCompleted is emitted after a successful operation.
	PhaseCompleted
	// PhaseFailed is emitted after a failed operation.
	PhaseFailed
)

type Plan

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

Plan is a deterministic execution plan derived from source and ledger state.

func PlanDown

func PlanDown(available []Migration, records []Record, count uint64) (Plan, error)

PlanDown validates the complete persisted history and returns exactly count rollback steps in descending version order. Count must be positive and fit the applied history; every selected migration must have down SQL. It never crosses a baseline or proves that down SQL restores the original schema.

func PlanUp

func PlanUp(available []Migration, records []Record) (Plan, error)

PlanUp validates the complete persisted history and returns pending migrations in ascending version order. Any ambiguity fails closed. Inputs must be strictly ordered, within MaxMigrationFiles/MaxMigrationRecords and form an unchanged clean source prefix above any baseline. It performs no I/O and does not retain the caller's slices.

func (Plan) Steps

func (plan Plan) Steps() []Step

Steps returns a copy so callers cannot mutate a validated plan.

type Record

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

Record is an immutable entry read from public.migrations.

func NewRecord

func NewRecord(
	kind RecordKind,
	version Version,
	name string,
	checksum Checksum,
	appliedAt time.Time,
	duration time.Duration,
	dirty bool,
) (Record, error)

NewRecord validates kind, signed-ledger-range version, canonical name, nonzero checksum/time and nonnegative duration. It preserves the supplied timestamp and dirty flag; history ordering, baseline placement and source agreement are separately checked by planners and status builders.

func (Record) AppliedAt

func (record Record) AppliedAt() time.Time

AppliedAt returns the persisted record time. PostgreSQL uses completion time for clean records and attempt start time for dirty records.

func (Record) Checksum

func (record Record) Checksum() Checksum

Checksum returns the persisted migration or schema fingerprint.

func (Record) Dirty

func (record Record) Dirty() bool

Dirty reports whether the operation has an unresolved partial outcome.

func (Record) Duration

func (record Record) Duration() time.Duration

Duration returns the measured operation duration.

func (Record) Kind

func (record Record) Kind() RecordKind

Kind returns whether this record is a migration or baseline.

func (Record) Name

func (record Record) Name() string

Name returns the persisted canonical name.

func (Record) Version

func (record Record) Version() Version

Version returns the record's immutable version.

type RecordKind

type RecordKind uint8

RecordKind distinguishes an executed migration from an explicit schema baseline in the owned ledger.

const (
	// RecordKindMigration is a migration executed by this package.
	RecordKindMigration RecordKind = iota + 1
	// RecordKindBaseline is a reviewed pre-existing schema assertion.
	RecordKindBaseline
)

type Recovery

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

Recovery is an immutable, checksum-bound operator decision.

func NewRecovery

func NewRecovery(version Version, checksum Checksum, action RecoveryAction) (Recovery, error)

NewRecovery validates a nonzero version/checksum and recognized action; it does not inspect SQL effects or enforce the signed ledger version ceiling. Runner.Recover must still match this identity to its validated source.

func (Recovery) Action

func (recovery Recovery) Action() RecoveryAction

Action returns the operator's asserted outcome, not a runtime verification.

func (Recovery) Checksum

func (recovery Recovery) Checksum() Checksum

Checksum returns the reviewed source checksum.

func (Recovery) Version

func (recovery Recovery) Version() Version

Version returns the reviewed migration version.

type RecoveryAction

type RecoveryAction uint8

RecoveryAction is an explicit operator assertion about a dirty migration.

const (
	// RecoveryMarkApplied asserts that the operator verified all up SQL effects.
	RecoveryMarkApplied RecoveryAction = iota + 1
	// RecoveryMarkRolledBack asserts that the operator removed all partial effects.
	RecoveryMarkRolledBack
)

type RecoveryResult

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

RecoveryResult is the persisted outcome of resolving one dirty migration.

func (RecoveryResult) Action

func (result RecoveryResult) Action() RecoveryAction

Action returns the applied recovery decision.

func (RecoveryResult) Record

func (result RecoveryResult) Record() Record

Record returns the resolved ledger record. MarkRolledBack returns the dirty record that was removed; MarkApplied returns its new clean representation.

type Result

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

Result describes the successful acknowledged prefix of one runner Up/Down invocation, including on error. It is not a whole-batch transaction receipt: the failing step or cleanup may also have changed durable database state.

func (Result) Records

func (result Result) Records() []Record

Records returns a copy of acknowledged records in execution order. Down returns the historical clean records that were removed, not surviving rows.

type Runner

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

Runner coordinates source validation, exclusive locking, history revalidation, planning, and backend execution.

func NewRunner

func NewRunner(source Source, backend Backend, options ...Option) (*Runner, error)

NewRunner borrows source/backend without loading or acquiring them itself. Options are caller code. It rejects nil interfaces/options, applies options left-to-right until failure and starts no goroutines. Typed-nil dependencies and arbitrary callback/backend panics are not generally validated or contained. Operations require a nonnil context and usable dependencies.

func (*Runner) Baseline

func (runner *Runner) Baseline(ctx context.Context, baseline Baseline) (record Record, err error)

Baseline validates and records an existing schema without replaying any historical framework migrations. The owned ledger must be empty, and every Go-owned source migration must have a version strictly above the baseline. The session must implement Baseline(context.Context, Baseline) (Record, error), otherwise ErrBaselineUnsupported is returned. PostgreSQL compares its selected catalog fingerprint transactionally; matching it does not verify every schema or security property. Cleanup errors can follow a committed baseline.

func (*Runner) Down

func (runner *Runner) Down(ctx context.Context, count uint64) (result Result, err error)

Down plans exactly count clean applied migrations, newest first, validating loaded source against locked ledger history. Count must be positive; planning fails before SQL if any selected migration is irreversible or crosses a baseline. Execution can stop partway: Result contains acknowledged removals, and an error does not establish that the failing step made no durable change.

func (*Runner) Plan

func (runner *Runner) Plan(ctx context.Context) (plan Plan, err error)

Plan loads source before locking and validates that history against the locked ledger without executing migration SQL. It can initialize the ledger; it is not an I/O-free operation or a reservation for a later Up call.

func (*Runner) Recover

func (runner *Runner) Recover(
	ctx context.Context,
	recovery Recovery,
) (result RecoveryResult, err error)

Recover resolves one checksum-matched dirty migration after the operator has verified that its effects are either fully applied or fully removed. The session must support the Recover method taking context.Context, Migration and RecoveryAction and returning (Record, error), otherwise ErrRecoveryUnsupported is returned. The runner checks source and dirty-record identity but does not execute SQL or verify the asserted effects. A release error can follow a persisted recovery decision.

func (*Runner) Status

func (runner *Runner) Status(ctx context.Context) (status Status, err error)

Status loads source before locking, prepares/reads the ledger under the session lock and returns their validated status. It does not inspect live SQL effects or freeze source changes; ledger preparation may execute SQL.

func (*Runner) Up

func (runner *Runner) Up(ctx context.Context) (result Result, err error)

Up loads source, acquires a session and validates that loaded history against the locked ledger before applying pending steps in ascending order. The lock coordinates backend jobs, not source mutations or unrelated database writers. Each step has its own backend transaction policy, not a batch transaction. On failure Result contains only earlier acknowledged steps; inspect database and ledger state before retry because the failing step or cleanup may have followed durable changes. The runner does not retry automatically.

type Session

type Session interface {
	// Prepare initializes the owned ledger on the lock-owning connection. It
	// may execute SQL even for a plan/status operation.
	Prepare(context.Context) error
	// Records returns the complete, version-ordered ledger within
	// MaxMigrationRecords, with returned data stable for the caller to validate.
	Records(context.Context) ([]Record, error)
	// Apply executes up SQL and persists its matching clean record on success.
	// Transactional SQL and ledger writes must be atomic; nontransactional work
	// must persist dirty state before executing SQL. Errors can follow durable
	// changes and must not be interpreted as proof that nothing ran.
	Apply(context.Context, Migration) (Record, error)
	// Rollback executes down SQL and removes the matching record on success,
	// returning the removed clean record. It has the same transactional and
	// explicit dirty-outcome obligations as Apply.
	Rollback(context.Context, Migration) (Record, error)
	// Release relinquishes the session's connection and exclusive ownership.
	// Runner attempts it once after acquisition using a detached finite context;
	// cleanup errors are joined with any operation error.
	Release(context.Context) error
}

Session represents exclusive migration-job ownership bound to one physical database connection. Ledger preparation, reads, and execution all happen through this value so connection loss and lock loss cannot be separated from execution and a one-connection pool cannot deadlock during preparation. Records must return at most MaxMigrationRecords entries.

type Source

type Source interface {
	// Load returns a complete history or an error, not a usable partial history.
	// It runs before the runner acquires the database lock.
	Load(context.Context) ([]Migration, error)
}

Source loads a complete immutable migration history. Implementations must honor the supplied context, return at most MaxMigrationFiles entries in strictly increasing version order, and keep returned data stable during use. Runner retains source errors without removing their causes; custom sources must keep their own diagnostics safe.

type SourceDirectoryLimits

type SourceDirectoryLimits struct {
	// MaxEntries is the largest complete entry inventory.
	MaxEntries int
	// MaxNameBytes is the largest individual entry name.
	MaxNameBytes int
	// MaxTotalNameBytes is the largest aggregate entry-name payload.
	MaxTotalNameBytes int
}

SourceDirectoryLimits are inclusive budgets that a SourceFileSystem must enforce before retaining or returning directory metadata.

type SourceEntry

type SourceEntry struct {
	// Name is the base filename within the source root.
	Name string
	// Directory reports whether the entry is a directory.
	Directory bool
}

SourceEntry is the bounded directory metadata needed to discover a migration.

type SourceFileSystem

type SourceFileSystem interface {
	// ReadDir returns the complete directory inventory, including unrelated
	// entries for FSSource to reject. It must bound metadata before retention.
	ReadDir(ctx context.Context, root string, limits SourceDirectoryLimits) ([]SourceEntry, error)
	// ReadFile returns at most maxBytes of stable file content, enforcing the
	// bound before retaining it and closing any provider-owned file handles.
	ReadFile(ctx context.Context, name string, maxBytes int) ([]byte, error)
}

SourceFileSystem provides cancellation-aware, bounded migration file access. Implementations must pass ctx to every blocking operation, stop promptly on cancellation, and enforce the supplied inclusive limits before retaining input data. They return context errors for cancellation, ErrSourceLimit or ErrInvalidEncoding for exceeded budgets, and a private implementation error for other failures; FSSource redacts those private errors as ErrInvalidSource.

type State

type State uint8

State is the deterministic relationship between source and owned ledger.

const (
	// StateBaseline is the reviewed adoption boundary for an existing schema.
	StateBaseline State = iota + 1
	// StateApplied is a clean, checksum-matched applied migration.
	StateApplied
	// StateDirty is an explicitly unresolved partial or uncertain outcome.
	StateDirty
	// StatePending is a source migration not yet represented in the ledger.
	StatePending
)

type Status

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

Status is an immutable, version-ordered snapshot.

func BuildStatus

func BuildStatus(available []Migration, records []Record) (Status, error)

BuildStatus validates source and persisted identity while preserving dirty entries as explicit status instead of hiding them behind a generic error. Inputs obey the same ordering and retention limits as PlanUp. It performs no I/O, retains no input slices and does not inspect live SQL effects or schema.

func (Status) Entries

func (status Status) Entries() []StatusEntry

Entries returns a copy so callers cannot mutate the validated snapshot.

type StatusEntry

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

StatusEntry is one immutable source or ledger status item.

func (StatusEntry) AppliedAt

func (entry StatusEntry) AppliedAt() time.Time

AppliedAt returns the persisted record time, or zero for pending work. For PostgreSQL dirty entries this is attempt start, not completion time.

func (StatusEntry) Checksum

func (entry StatusEntry) Checksum() Checksum

Checksum returns the source checksum or persisted baseline fingerprint.

func (StatusEntry) Duration

func (entry StatusEntry) Duration() time.Duration

Duration returns the persisted execution duration, or zero for pending work.

func (StatusEntry) Name

func (entry StatusEntry) Name() string

Name returns the canonical migration or baseline name.

func (StatusEntry) State

func (entry StatusEntry) State() State

State returns the item's deterministic state.

func (StatusEntry) Version

func (entry StatusEntry) Version() Version

Version returns the migration or baseline version.

type Step

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

Step is one immutable action in a deterministic plan.

func (Step) Action

func (step Step) Action() Action

Action returns the step operation.

func (Step) Migration

func (step Step) Migration() Migration

Migration returns the immutable migration for this step.

type TransactionMode

type TransactionMode uint8

TransactionMode controls whether an engine wraps a migration in a database transaction.

const (
	// TransactionModeDefault requests one transaction for SQL and ledger writes.
	TransactionModeDefault TransactionMode = iota
	// TransactionModeNone permits operations PostgreSQL cannot run in a
	// transaction. An unresolved dirty outcome requires explicit recovery.
	TransactionModeNone
)

type Version

type Version uint64

Version is a numeric migration identity. NewMigration, NewBaseline and NewRecord accept only 1 through the maximum signed 64-bit integer; source and ledger histories must be strictly increasing, not necessarily contiguous.

func (Version) String

func (version Version) String() string

String returns unpadded decimal text without validating the version.

Directories

Path Synopsis
adapters
service
Package migrationsservice adapts an explicit migrations.Runner to the standard Service v3 migrate command.
Package migrationsservice adapts an explicit migrations.Runner to the standard Service v3 migrate command.
Package conformance provides the reusable behavioral test suite every migration backend must pass without depending on its execution engine.
Package conformance provides the reusable behavioral test suite every migration backend must pass without depending on its execution engine.
examples
job command
Command job runs embedded migrations as a dedicated deployment task with a caller-owned database and immutable compiler-embedded source.
Command job runs embedded migrations as a dedicated deployment task with a caller-owned database and immutable compiler-embedded source.
internal
goose
Package goose contains the replaceable, hidden Goose execution adapter pinned by go.mod.
Package goose contains the replaceable, hidden Goose execution adapter pinned by go.mod.
Package postgres implements the public.migrations ledger and connection-bound advisory-lock backend for migrations.Runner.
Package postgres implements the public.migrations ledger and connection-bound advisory-lock backend for migrations.Runner.

Jump to

Keyboard shortcuts

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