migrate

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package migrate applies versioned SQL migrations, driven by icpt.json.

It is the migration half of the icpt CLI, and it keeps the file and ledger conventions already in use so an existing repository needs no change:

migrations/000012_add_product_sku.up.sql
migrations/000012_add_product_sku.down.sql

and a `schema_migrations` table the application checks for at boot.

Four properties this implementation insists on, each because its absence has cost somebody a database:

  • DUPLICATE VERSIONS ARE REFUSED. Given 000027_a and 000027_b a ledger keyed on the version applies one of them and records the other's name. Nothing warns, and the migration that did not run simply does not exist. This refuses to start.
  • ONE TRANSACTION PER MIGRATION, with the ledger row written inside it. A migration that fails half way leaves neither schema change nor ledger row, so a re-run is a clean retry rather than an archaeology exercise.
  • AN ADVISORY LOCK around the whole run. Two deploys rolling at once otherwise both see version 11 as the latest and both apply 12.
  • THE DOWN SCRIPT IS STORED, and rollback runs the STORED one. The file on disk belongs to whatever is checked out now; the database was built by what ran then.

Index

Constants

View Source
const (
	// StatusSuccess marks an applied migration. A service checking whether
	// its schema is current counts rows with this status.
	StatusSuccess = "success"
	// StatusRolledBack marks a migration that was reversed. The version is
	// then pending again, and will be re-applied by the next run.
	StatusRolledBack = "rollback"
	// StatusFailed marks an attempt that did not complete.
	StatusFailed = "failed"
	// StatusRunning marks a migration in flight on an engine without
	// transactional DDL, so a crash leaves evidence rather than silence.
	StatusRunning = "running"
)

Ledger statuses.

A reversed migration becomes StatusRolledBack; its row is KEPT, not deleted. Deleting it would throw away the up and down scripts that were actually run, which are the only record of what the database once was — and the only thing a later investigation has to go on.

View Source
const DefaultLedgerTable = "schema_migrations"

DefaultLedgerTable is the table recording what has been applied.

Variables

This section is empty.

Functions

func Create

func Create(dir, name string) (up, down string, err error)

Create writes an empty up/down pair with the next version number.

func DialectNames

func DialectNames() []string

DialectNames lists the supported dialects, sorted.

Types

type Applied

type Applied struct {
	Version   string
	Name      string
	Status    string
	Up        string
	Down      string
	AppliedAt time.Time
}

Applied is one ledger row.

type Dialect

type Dialect interface {
	// Name is the value written in icpt.json.
	Name() string
	// Driver is the database/sql driver to open with.
	Driver() string
	// Placeholder renders the nth bind parameter, 1-based.
	Placeholder(n int) string
	// LedgerDDL returns the statements that create or upgrade the ledger.
	// Each must be safe to run on every invocation.
	LedgerDDL(table string) []string
	// TransactionalDDL reports whether a schema change can be rolled back.
	TransactionalDDL() bool
	// Lock takes a cross-process advisory lock, returning a release
	// function. A dialect without one returns a no-op release and no error;
	// see the note on each implementation for what that costs.
	Lock(ctx context.Context, conn *sql.Conn) (release func(context.Context), err error)
	// QuoteIdent quotes an identifier.
	QuoteIdent(s string) string
}

Dialect is everything about a database engine that migration has to know.

There is more variation here than "the placeholder character". The one that changes the DESIGN rather than the SQL is transactional DDL: PostgreSQL can run a schema change and record it in one transaction, so a failure leaves nothing behind. MySQL cannot — every DDL statement commits implicitly — so the same code there would leave a half-applied migration with no ledger row. Pretending otherwise is how a framework corrupts a MySQL database and blames the user.

func DialectFor

func DialectFor(name string) (Dialect, error)

DialectFor resolves a dialect by name.

type DownOptions

type DownOptions struct {
	// Steps is how many migrations to reverse, most recent first.
	Steps int
	// DryRun prints what would run and changes nothing.
	DryRun bool
}

DownOptions configures Down.

type File

type File struct {
	Version  string
	Name     string
	UpPath   string
	DownPath string
	Up       string
	Down     string
}

File is one migration on disk.

func Load

func Load(dir string) ([]File, error)

Load reads the migration directory, sorted by version.

It refuses a directory containing two migrations with one version, and a migration with no down script — a migration you cannot reverse is one you cannot deploy on a Friday.

type Migrator

type Migrator struct {

	// Out receives progress lines. Defaults to discarding them.
	Out func(format string, args ...any)
	// contains filtered or unexported fields
}

Migrator runs migrations against one database.

func Open

func Open(ctx context.Context, opt Options) (*Migrator, error)

Open connects to a database and prepares a migrator for it.

func (*Migrator) Close

func (m *Migrator) Close() error

Close releases the connection.

func (*Migrator) Dialect

func (m *Migrator) Dialect() Dialect

Dialect returns the engine this migrator targets.

func (*Migrator) Down

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

Down reverses the most recently applied migrations.

It runs the down script STORED IN THE LEDGER, not the file on disk. The database was built by what ran at the time; the checked-out file may belong to a later version of that migration, or to a squash, and running it would reverse something that was never applied.

func (*Migrator) Status

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

Status reports every migration's position, on disk and in the ledger.

func (*Migrator) Up

func (m *Migrator) Up(ctx context.Context, opt UpOptions) (int, error)

Up applies every pending migration in version order.

type Options

type Options struct {
	// Name identifies the target in messages.
	Name string
	// DSN is the connection string. It comes from the environment, never
	// from a file in the repository.
	DSN string
	// Dir is the migration directory.
	Dir string
	// Dialect is "postgres", "mysql" or "sqlite". Empty means postgres.
	Dialect string
	// LedgerTable overrides the ledger's name, for a repository that
	// already has a table called schema_migrations belonging to something
	// else.
	LedgerTable string
	// NoLock skips the advisory lock. Only for a dialect or a deployment
	// where it cannot work; without it, two concurrent deploys can both
	// apply the same migration.
	NoLock bool
}

Options configures one migrator.

type Status

type Status struct {
	Version string
	Name    string
	// Applied is true when a successful ledger row exists.
	Applied bool
	// Missing is true for a ledger row with no file: the migration ran once
	// and the file has since been deleted or renamed, so the database cannot
	// be rebuilt from this checkout.
	Missing bool
	// Drifted is true when the file's SQL differs from what was recorded.
	Drifted    bool
	AppliedAt  time.Time
	StatusText string
}

Status is one migration's position.

type UpOptions

type UpOptions struct {
	// To stops after this version. Empty applies everything pending.
	To string
	// DryRun prints what would run and changes nothing.
	DryRun bool
	// AllowDrift proceeds although an applied migration's file has changed.
	// Off by default: drift usually means somebody edited an applied
	// migration, which leaves the ledger unable to rebuild the database.
	AllowDrift bool
}

UpOptions configures Up.

Jump to

Keyboard shortcuts

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