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 ¶
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.
const DefaultLedgerTable = "schema_migrations"
DefaultLedgerTable is the table recording what has been applied.
Variables ¶
This section is empty.
Functions ¶
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 ¶
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 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 (*Migrator) Down ¶
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.
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.