Documentation
¶
Overview ¶
Package migrationx is a SQL migration engine on the standard library: ordered SQL files, a history table with checksums, transactions where the database supports them, and a migration lock — with database drivers staying entirely on the consumer's side.
Migrations are pairs of files named <unix-timestamp>_<name>.up.sql and .down.sql, minted by Create and shipped via embed.FS or a directory. The engine runs on the consumer's *sql.DB against sqlite or mysql.
Every mutating run verifies before it acts, and ambiguity aborts: an applied migration whose file changed (checksum mismatch) or disappeared (orphan) stops the run; pending migrations older than the newest applied one — the late-merged branch — are refused unless Config.AllowOutOfOrder acknowledges them.
Concurrent runners are serialized by a database-side lock, and a migration another runner already applied is skipped, never re-executed. The one exception is a no-transaction migration: its statements run outside any transaction, so the concurrent-runner guarantee does not cover it — deploy those serially.
On mysql, a no-transaction migration's statements can fail partway through with no transaction to roll back, leaving the schema changed but the history row absent or stale. The history table's dirty column tracks this: the row is marked dirty before the statements run and cleared on success, so a version a failed run left dirty stays visible afterward. Up and Down refuse to run again while any version is dirty; Status and Version report it (Migration.Dirty) without failing — they take no lock, so a legitimate run in progress must not look like a failure to either. Resolving a dirty version is an operator step this package cannot automate: inspect and repair whatever the failed statements left half-done, then clear the row — UPDATE the history table setting dirty to 0, or delete the row if the migration never really applied — before rerunning.
A migration engine bug is a data incident, not a bug report: when in doubt, this package stops.
Index ¶
- Constants
- func Command(open func(ctx context.Context, allowOutOfOrder bool) (*Migrator, error)) *cli.Command
- func Create(dir, name string) (string, string, error)
- type Config
- type Dialect
- type Migration
- type Migrator
- func (m *Migrator) Down(ctx context.Context) error
- func (m *Migrator) DownTo(ctx context.Context, version int64) error
- func (m *Migrator) Status(ctx context.Context) ([]Migration, error)
- func (m *Migrator) Up(ctx context.Context) error
- func (m *Migrator) UpByOne(ctx context.Context) error
- func (m *Migrator) UpTo(ctx context.Context, version int64) error
- func (m *Migrator) Version(ctx context.Context) (int64, error)
Constants ¶
const ( // DefaultTable is the history table name when Config.Table is empty. DefaultTable = "migration_histories" // DefaultLockTimeout bounds the wait for the migration lock when // Config.LockTimeout is zero. DefaultLockTimeout = 30 * time.Second )
Variables ¶
This section is empty.
Functions ¶
func Command ¶
Command builds the migrate command tree — create, up, down, status — for registration in a cli root. open constructs the Migrator after flags resolve: the DSN, dialect, and filesystem live in the caller's own configuration; allowOutOfOrder carries the --allow-out-of-order flag into Config.
The grammar: the bare verb does the safe default (up applies everything, down rolls back one), --one/--all flip the quantity, --to targets a version, and combining a quantity flag with --to is an error.
Types ¶
type Config ¶
type Config struct {
// Dialect selects the database: DialectSQLite or DialectMySQL.
Dialect Dialect
// Table is the history table name. Default DefaultTable.
Table string
// Log receives progress and the down-audit lines. Nil means
// slog.Default().
Log *slog.Logger
// AllowOutOfOrder applies pending migrations older than the newest
// applied one — the late-merged branch — instead of refusing. A
// deploy-time human decision: never enable it unattended.
AllowOutOfOrder bool
// LockTimeout bounds the wait for the migration lock. Default
// DefaultLockTimeout.
LockTimeout time.Duration
}
Config configures New. Dialect is required; every other field has a safe default.
type Dialect ¶
type Dialect int
Dialect selects the database-specific behavior: history-table DDL, the migration lock, and transaction control. Config.Dialect is required.
type Migration ¶
type Migration struct {
Version int64
Name string
Applied bool
AppliedAt time.Time // zero when not applied, or when the stored value was unreadable
OutOfOrder bool // pending and older than the newest applied migration still present on the filesystem
Orphaned bool // applied but its migration file is missing from the filesystem
Dirty bool // applied but left dirty by a previous no-transaction run; resolve manually, see doc.go
}
Migration is one row of Status: a version known to the filesystem, the history table, or both.
type Migrator ¶
type Migrator struct {
// contains filtered or unexported fields
}
Migrator applies, rolls back, and reports migrations. Create one with New; it is safe for a single caller — migrations are a deploy step, not a concurrent workload.
func New ¶
New validates the configuration, loads and validates every migration file, and creates the history table if it does not exist — the first deploy needs no manual bootstrap.
func (*Migrator) DownTo ¶
DownTo rolls back every applied migration with a version greater than version, highest first; DownTo(ctx, 0) rolls back everything. Negative versions are treated as 0.
func (*Migrator) Status ¶
Status reports every migration the filesystem or the history table knows, in version order. An applied migration whose file is missing is reported with Orphaned set instead of failing the call — Up, Down, and Version still fail closed on the same condition via verify. A checksum mismatch still fails the call, though: the row exists on both sides, so there is no honest single Migration to render for it, and drift between what ran and what's on disk is the more dangerous state — orphans are reported inline, checksum mismatches fail closed. It takes no lock. A version left dirty by a previous no-transaction run is reported with Dirty set, never as an error — Up and Down are the calls that refuse dirty state.