Help us shape the future of Go! Take the 2026 Go Developer Survey

migrate

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: Apache-2.0 Imports: 14 Imported by: 3

Documentation

Overview

Package migrate is schema versioning over authored SQL: ordered sets of migrations, each migration SQL text plus a version, applied against a history table on one pinned connection under the dialect's lock capability. It designs for the cases a mature library has met: dirty state after a failed non-transactional migration (recorded, reported, cleared only by Force), engines without transactional DDL (a migration headed "--| transaction: none" runs outside a transaction and contains exactly one statement by convention), and concurrent starters (the lock; a dialect without one fails unless the consumer opts into an unlocked run). The NNNN_name.{up,down}.sql file layout is a helper, never the contract.

A run waits for the lock without holding a statement open when the dialect implements sqlate.TryLocker: it polls TryLock, backing off between refusals, and stops with the context's error when the context ends. A holder's non-transactional CREATE INDEX CONCURRENTLY waits for every open transaction, so a waiter blocked in a lock statement would deadlock with it. A dialect with sqlate.Locker alone waits in its blocking Lock.

A program installed under an earlier single-set migrator adopts sets without migrating its history: the default table and the history's columns are the same.

Force is the operator repair for a dirty set. The repair is: fix the failed migration's objects by hand, Force to the version that is applied, then Up.

The package exports:

Index

Constants

View Source
const DefaultTable = "schema_version"

DefaultTable is the history table a Set uses when its Table is empty. At most one of a Migrator's sets may leave Table empty.

Variables

View Source
var (
	// ErrNoLocker reports a run that needs the lock on a dialect without the
	// capability; Options.Unlocked opts out.
	ErrNoLocker = errors.New("migrate: dialect has no lock capability")
	// ErrDirty is the class a *DirtyError unwraps to.
	ErrDirty = errors.New("migrate: schema is dirty")
	// ErrPending is the class a *PendingError unwraps to.
	ErrPending = errors.New("migrate: migrations pending")
	// ErrUnknownVersion is the class an *UnknownVersionError unwraps to.
	ErrUnknownVersion = errors.New("migrate: history does not match the migration set")
	// ErrNoDown reports a Down of a migration that has no down text.
	ErrNoDown = errors.New("migrate: migration has no down")
	// ErrVersionNotFound reports a Force to a version outside the set.
	ErrVersionNotFound = errors.New("migrate: version not in the migration set")
	// ErrAboveApplied reports a revert of a layer refused because a layer
	// above it still has applied migrations; revert that layer first.
	ErrAboveApplied = errors.New("migrate: a layer above has applied migrations")
	// ErrBelowPending reports an apply on a layer refused because a layer
	// below it still has pending migrations; Up applies every layer in order.
	ErrBelowPending = errors.New("migrate: a layer below has pending migrations")
)

Functions

This section is empty.

Types

type Catalog

type Catalog interface {
	// CreateHistory returns the DDL that creates table when it does not
	// exist, with the columns the protocol reads and writes:
	//
	//   - version (integer, primary key)
	//   - name (text, not null)
	//   - applied_at (the instant the row was written, not null,
	//     defaulting to the current time; a time-zone-aware type where the
	//     engine has one, so the instant does not depend on the session's
	//     zone)
	//   - dirty (boolean, not null, defaulting to false)
	CreateHistory(table string) string
	// HistoryExists returns the query that yields one row whose first
	// column is nonzero when the history table exists. The table name is
	// the query's one argument, bound at param, the dialect's placeholder.
	HistoryExists(param string) string
}

Catalog is the engine-specific half of the history protocol: the two statements standard SQL cannot express the same way on every engine. A dialect provides it by implementing the methods; a dialect that does not gets StandardCatalog. Everything else the migrator runs is standard DML with bound parameters.

type DirtyError

type DirtyError struct {
	Version int
	Err     error
}

DirtyError reports a version whose non-transactional migration failed midway: the history row is marked dirty and every run refuses until Force clears it. Err is the failure that dirtied it, when this run caused it.

func (*DirtyError) Error

func (e *DirtyError) Error() string

func (*DirtyError) Unwrap

func (e *DirtyError) Unwrap() []error

type HistoryUpgrader added in v0.5.0

type HistoryUpgrader interface {
	// HistoryOutdated returns the query that yields one row whose first
	// column is nonzero when the history table has an earlier release's
	// shape. The table name is the query's one argument, bound at param,
	// the dialect's placeholder.
	HistoryOutdated(param string) string
	// UpgradeHistory returns the DDL that converts table to the current
	// shape in place, keeping every row and the instant each records.
	UpgradeHistory(table string) string
}

HistoryUpgrader is the Catalog capability that brings a history table an earlier release created to the shape CreateHistory creates now. A dialect provides it by implementing the two methods alongside Catalog's. Each locked run (Up, Steps, Down, Reset, Force) runs HistoryOutdated after CreateHistory, inside the lock and before it reads the history, and runs UpgradeHistory only when the query reports the table outdated, so a current table is read and never rewritten, and the upgrade is idempotent however the engine's DDL behaves on a second run.

type Layer added in v0.2.0

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

Layer is a handle on one of a Migrator's sets. Its verbs act on that set alone, under the migrator's lock and its ordering rules: a revert is refused while a layer above has applied migrations, and an apply is refused while a layer below has pending ones. The zero Layer is not usable; Migrator.Layers and Migrator.Layer return usable ones.

func (Layer) Down added in v0.2.0

func (l Layer) Down(ctx context.Context, n int) error

Down reverts the set's n most recently applied migrations; n larger than the applied count reverts them all. A caller that wants every set reverted without dropping any history table iterates Layers in reverse and calls Down(ctx, len(l.Migrations())) on each; Migrator.Reset reverts every set and drops the tables as well.

func (Layer) Force added in v0.2.0

func (l Layer) Force(ctx context.Context, version int) error

Force sets the set's history to version as an operator override: rows above it are deleted, and every migration up to and including it has its row marked clean, inserted where absent, so the history is the set's prefix through version however far below it the history stood. Version 0 empties the history. Nothing runs against the schema itself, and no set's history is checked first, since Force is the repair for a dirty one.

func (Layer) Migrations added in v0.2.0

func (l Layer) Migrations() []Migration

Migrations returns a copy of the set's migrations.

func (Layer) Name added in v0.2.0

func (l Layer) Name() string

Name returns the set's name.

func (Layer) Status added in v0.2.0

func (l Layer) Status(ctx context.Context) (SetStatus, error)

Status reads the set's state without the lock.

func (Layer) Steps added in v0.2.0

func (l Layer) Steps(ctx context.Context, n int) error

Steps applies the set's next n pending migrations when n is positive, or reverts its last -n applied when negative; fewer remaining is not an error, so -len(Migrations()) reverts the whole set. Every set's history is checked first. A positive n is refused with ErrBelowPending while a layer below has pending migrations; a negative n is refused with ErrAboveApplied while a layer above has applied ones.

func (Layer) Table added in v0.2.0

func (l Layer) Table() string

Table returns the set's history table, after DefaultTable is applied.

func (Layer) Verify added in v0.2.0

func (l Layer) Verify(ctx context.Context) error

Verify checks, without the lock, that the set's history is a clean, complete prefix of its migrations. Every fault is a *SetError naming the set: a dirty row unwraps to a *DirtyError, a row the set does not carry to an *UnknownVersionError, and unapplied migrations to a *PendingError.

func (Layer) Version added in v0.2.0

func (l Layer) Version(ctx context.Context) (Version, error)

Version reads the set's history head without taking the lock. A missing history table is the zero Version.

type Migration

type Migration struct {
	Version       int
	Name          string
	Up            string
	Down          string
	Transactional bool
}

Migration is one schema step: the version and name that identify it in the history, the SQL that applies and reverts it, and whether it runs in a transaction. Down may be empty.

func Files

func Files(fsys fs.FS, dir string) ([]Migration, error)

Files reads the NNNN_name.{up,down}.sql layout under dir in fsys into a version-ordered set. The up file's "--|" header decides Transactional: the "transaction" declaration, "none" opting out and "required" or absence keeping the transaction; a down file that declares differently is an error. Up and Down are the files' bodies; the engine never sees a header. Versions must be unique. A down without its up is an error; an up without its down is allowed.

type Migrator

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

Migrator runs one or more migration sets against a database: the sets in declared order, each over its own history table, every run on one pinned connection under one lock. Its methods without a set act on the top set, except Up, Verify, Reset, and Status, which cover every set.

func New

func New(db *sqlate.DB, sets []Set, opts Options) (*Migrator, error)

New validates the sets and prepares the migrator: at least one set, each with a name no other set uses and a history table (DefaultTable when empty, so at most one set may leave it empty) no other set uses, and each set's migrations with positive, strictly increasing versions and names and up texts present. The lock capability and the Catalog are taken from the dialect when it has them. New performs no I/O.

func (*Migrator) Down

func (m *Migrator) Down(ctx context.Context, n int) error

Down reverts the top set's n most recently applied migrations. A layer above with applied migrations is ErrAboveApplied; Layer.Down reverts one named set.

func (*Migrator) Force

func (m *Migrator) Force(ctx context.Context, version int) error

Force sets the top set's history to version as an operator override. Layer.Force forces one named set.

func (*Migrator) Layer added in v0.2.0

func (m *Migrator) Layer(name string) (Layer, bool)

Layer returns a handle on the set called name, reporting whether the migrator holds one.

func (*Migrator) Layers added in v0.2.0

func (m *Migrator) Layers() []Layer

Layers returns a handle on every set, in declared order.

func (*Migrator) Migrations

func (m *Migrator) Migrations() []Migration

Migrations returns a copy of the top set's migrations.

func (*Migrator) Reset added in v0.2.0

func (m *Migrator) Reset(ctx context.Context) error

Reset returns the database to its state before the first Up: under one lock, after every set's history is checked, it reverts every set in reverse declared order and drops each set's history table once that set is reverted, so a set above never blocks the revert of the set it references. A later Up replays every set from zero. A caller that wants every set reverted with the tables left in place iterates Layers in reverse and calls Down(ctx, len(l.Migrations())) on each.

func (*Migrator) Status added in v0.2.0

func (m *Migrator) Status(ctx context.Context) ([]SetStatus, error)

Status reads every set's state in declared order without the lock. It runs on the pool, so it never waits on a running Up, and a report read while a run is in progress can show a set mid-run.

func (*Migrator) Steps

func (m *Migrator) Steps(ctx context.Context, n int) error

Steps applies the top set's next n pending migrations when n is positive, or reverts its last -n applied when negative; fewer remaining is not an error.

func (*Migrator) Up

func (m *Migrator) Up(ctx context.Context) error

Up applies every pending migration of every set, sets in declared order, under one lock on one connection. Every set's history is checked first, so a dirty set or a history that does not match its set refuses the run before any migration runs. The first failure stops the run; the sets before it stay applied.

func (*Migrator) Verify

func (m *Migrator) Verify(ctx context.Context) error

Verify checks every set in declared order, without the lock, that its history is a clean, complete prefix of its migrations, and returns the first fault as a *SetError naming the set.

func (*Migrator) Version

func (m *Migrator) Version(ctx context.Context) (Version, error)

Version reads the top set's history head without taking the lock. A missing history table is the zero Version.

type Options

type Options struct {
	// LockName names the lock a run holds; default "migrate.<table>" over
	// the top set's history table, so a program's older single-set binary
	// and its multi-set successor contend for the same lock mid-rollout,
	// and no lock of the program's own can collide with it by number.
	LockName string
	// Unlocked allows runs on a dialect without the lock capability, or with
	// it, without taking the lock. Its intended shape is a caller that holds
	// a lock of its own around every run; concurrent starters without one
	// are unsafe.
	Unlocked bool
	// Logger records each applied and reverted migration, each forced
	// version, and each history table Reset drops; nil is silent.
	Logger *slog.Logger
}

Options configures a Migrator. Every field has a default.

type PendingError

type PendingError struct{ Versions []int }

PendingError reports migrations in the set the history has not applied.

func (*PendingError) Error

func (e *PendingError) Error() string

func (*PendingError) Unwrap

func (e *PendingError) Unwrap() error

type Set added in v0.2.0

type Set struct {
	Name       string
	Table      string
	Migrations []Migration
}

Set is one migration layer as a library ships it or a program declares it: a name, the history table its migrations are recorded in (empty for DefaultTable), and its migrations in version order. A Migrator runs several, declared bottom-first: a set's migrations may reference the objects of the sets declared before it, never after.

type SetError added in v0.2.0

type SetError struct {
	Set string
	Err error
}

SetError is an error from one set's history or migrations, naming the set. It unwraps to the set's own error, so errors.Is classifies it (ErrDirty, ErrPending, ErrUnknownVersion) and errors.As reaches the detail (*DirtyError, *PendingError, *UnknownVersionError).

func (*SetError) Error added in v0.2.0

func (e *SetError) Error() string

func (*SetError) Unwrap added in v0.2.0

func (e *SetError) Unwrap() error

type SetStatus added in v0.2.0

type SetStatus struct {
	Name    string
	Table   string
	Version int
	Latest  int
	Pending []Migration
	Dirty   bool
}

SetStatus is one layer's state as Status reads it, without the lock: the layer's name and history table, the highest applied version (zero when nothing is applied), the layer's latest version, the migrations the history has not applied, and whether the history holds a dirty row.

type StandardCatalog

type StandardCatalog struct{}

StandardCatalog is the Catalog for engines that accept CREATE TABLE IF NOT EXISTS with text and boolean columns and expose information_schema: MySQL and MariaDB as they ship, and PostgreSQL as a fallback for a dialect that does not implement its own (see HistoryExists's limitation). SQL Server (no IF NOT EXISTS, no boolean), Oracle (no information_schema), and SQLite (sqlite_master) provide their own.

Its applied_at is a plain timestamp, the one spelling all its engines accept. On MySQL and MariaDB a timestamp is stored as an instant, so the column is time-zone-aware there; on PostgreSQL it is a wall clock without zone, which is one more reason the postgres dialect implements its own Catalog, with timestamp with time zone. StandardCatalog has no HistoryUpgrader, since there is nothing for its engines to upgrade.

func (StandardCatalog) CreateHistory

func (StandardCatalog) CreateHistory(table string) string

func (StandardCatalog) HistoryExists

func (StandardCatalog) HistoryExists(param string) string

HistoryExists checks information_schema.tables by name alone, which spans every schema on the search path: a same-named table in an unrelated schema satisfies the check even though the current schema's own history table does not exist. A dialect for an engine where this matters — as PostgreSQL's own Dialect does — implements Catalog itself with the engine's own schema-qualified form instead of falling back to this one.

type UnknownVersionError

type UnknownVersionError struct {
	Version int
	Name    string
}

UnknownVersionError reports an applied row the migration set does not contain at that position: a version the set lacks, or a name that differs.

func (*UnknownVersionError) Error

func (e *UnknownVersionError) Error() string

func (*UnknownVersionError) Unwrap

func (e *UnknownVersionError) Unwrap() error

type Version

type Version struct {
	Version int
	Dirty   bool
}

Version is the history's head: the highest applied version and whether its row is dirty. A zero Version means nothing is applied.

Jump to

Keyboard shortcuts

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