go-migrationGo Migration
Changelog
Documentation

Changelog

A version-by-version history of go-migration, including new features, bug fixes, and breaking changes.

Changelog

All notable changes to go-migration are documented on this page. The format follows Keep a Changelog, and this project adheres to Semantic Versioning.

v1.2.0

This release makes migrations safe to run from several processes at once, makes the tracking table and Fresh() work natively on every database, and moves the CLI out of the migrator library. It contains breaking changes for the CLI entry point and for custom Grammar implementations — see Upgrading below.

Added

  • Cross-process migration lock, on by default. Up, Rollback, Reset, Refresh (one lock across both phases), and Fresh (locked before anything is dropped) take it, as do the CLI's migrate:install, db:seed, db:seed:rollback, and db:seed:truncate. PostgreSQL uses pg_try_advisory_lock, MySQL uses GET_LOCK, and SQLite relies on its file lock. See Concurrency and Locking.
  • migrator.WithLockTimeout(d) (default migrator.DefaultLockTimeout, 30s), migrator.WithoutLock(), the public Migrator.Lock(ctx) (release func() error, err error), and the sentinel migrator.ErrLockTimeout.
  • --lock-timeout <duration> and --no-lock flags on every CLI command that changes the database.
  • migrate:status --check exits with status 1 (printing N migration(s) pending to stderr) when any migration is pending — for readiness gates such as a Kubernetes initContainer, or CI.
  • seeder.NewRunner(reg, db, logger, opts ...RunnerOption) with seeder.WithDialect(seeder.DialectMySQL | DialectSQLite | DialectPostgres).
  • migrator.NewTrackerWithGrammar(db, table, grammar) for dialect-aware access to the tracking table.
  • New schema.Grammar methods: CompileListTables, CompileTrackingTable, QuoteIdentifier, CompileHasTableIn, Placeholder, CompileTryLock, and CompileUnlock; helpers schema.QuoteQualified and schema.SplitQualified.
  • "sqlite3" is accepted as an alias of "sqlite" for the config driver.
  • A real-database integration suite (go test -tags integration ./tests/integration/..., configured with GOMIGRATE_TEST_POSTGRES_DSN / GOMIGRATE_TEST_MYSQL_DSN; SQLite always runs) and GitHub Actions CI running go vet, the unit tests, and the integration tests against PostgreSQL 16 and MySQL 8.

Changed

  • Breaking: the CLI entry point moved from pkg/migrator to the new pkg/cli/app package. In cmd/migrator/main.go, import github.com/gopackx/go-migration/pkg/cli/app and call app.Run(). Importing pkg/migrator as a library no longer pulls in the CLI, the config loader, the code generator, or the YAML parser. migrator init scaffolds the new form.
  • Breaking: custom schema.Grammar implementations must be updated. CompileCreate now returns ([]string, error) (the CREATE TABLE followed by separate CREATE INDEX statements, each executed individually), CompileDropAllTables(tables []string) []string receives the table list, and the new methods listed above are required. See Custom Grammars.
  • Breaking: with locking enabled on PostgreSQL/MySQL, the connection pool must allow at least two open connections (MaxOpenConns >= 2, or 0 for unlimited). A pool limited to one connection fails with a clear error suggesting a larger pool or WithoutLock() / --no-lock.
  • Breaking: migrate:install now takes the migration lock (and accepts --lock-timeout / --no-lock), so on PostgreSQL/MySQL it also needs a pool of at least two connections, or --no-lock.
  • The SQLite driver is now modernc.org/sqlite, a pure-Go driver registered with database/sql as "sqlite". The CLI builds with CGO_ENABLED=0, and the release binaries — which are built that way and previously shipped without working SQLite support — now support SQLite. The scaffolded main.go blank-imports _ "modernc.org/sqlite", and SQLite connections need no host: database is the file path or :memory:.
  • The MySQL driver adds parseTime=true to the DSN unless you set parseTime yourself in options, so DATETIME/TIMESTAMP columns scan into time.Time. DSN parameters are sorted for a deterministic DSN.
  • The tracker is dialect-aware: the tracking table DDL and bind placeholders come from the grammar, so MySQL and SQLite no longer receive PostgreSQL-only SERIAL / $N SQL. Without WithGrammar it falls back to PostgreSQL syntax.
  • Each migration's tracking row is written inside the migration's own transaction, so a crash can no longer leave a migration applied but unrecorded. This is atomic on PostgreSQL and SQLite; MySQL commits DDL implicitly, so there only DML is rolled back together with the tracking row.
  • Tracker errors keep the driver cause (migration tracking table error: <cause>).
  • Status() / migrate:status is read-only: it never creates the tracking table (everything is reported pending when the table is absent) and does not take the lock.
  • Dry-run (WithDryRun / --dry-run) never writes, not even the tracking table, and therefore requires the tracking table to exist — run migrate:install first. HasTable / HasColumn report false in dry-run mode.
  • Fresh() works on every database: PostgreSQL drops and recreates the public schema; MySQL and SQLite list the tables and drop each one with foreign key checks off, on a dedicated connection that is discarded afterwards. A schema-qualified tracking table is dropped too. A dry-run Fresh() prints the drops and shows every migration as pending in batch 1.
  • Reset() now runs the before-migrate hooks, like Up() and Rollback().
  • Identifiers are quoted with the delimiter escaped ("a""b", `a``b`). The tracking table name is now quoted in every statement; schema-qualified names such as app.migrations are quoted per part, and the schema must exist. On PostgreSQL, if your tracking table name contains upper-case letters, rename the existing table to the exact configured name (or configure the lower-case name) before upgrading.
  • make:migration, make:seeder, and make:factory reject names that are not lower snake_case (^[a-z][a-z0-9_]*$) instead of generating a file whose init() panics.
  • commands.CommandContext.Migrator (and cli.CLIContext.Migrator) is now the commands.Migrator interface, which *migrator.Migrator satisfies directly; Status() returns []migrator.MigrationStatus.
  • Options passed to migrator.New are applied before the runner and tracker are built, so their order no longer matters.
  • Sentinel errors re-exported by migrator are aliases of the canonical values in pkg/database, pkg/seeder, and the grammars, so errors.Is works with either.

Fixed

  • MySQL and SQLite tracking tables were created with PostgreSQL syntax.
  • CompileCreate joined CREATE TABLE and CREATE INDEX statements with ;, which pgx stdlib and the MySQL driver reject; each statement now runs separately.
  • runner.Truncate() failed on MySQL because it always used double-quoted identifiers.
  • Fresh() left a schema-qualified tracking table behind, so nothing was re-applied after the drop.
  • Concurrent migrate:install runs could race on CREATE TABLE IF NOT EXISTS, which is not atomic on PostgreSQL.

Removed

  • Breaking: migrator.Run() — use app.Run() from github.com/gopackx/go-migration/pkg/cli/app.
  • Breaking: migrator.ErrConfigValidation — use config.ErrConfigValidation. The migrator never returned it.
  • Breaking: commands.MigratorRunner and commands.MigrationStatusInfo — *migrator.Migrator is used directly.
  • The CGO-based SQLite driver github.com/mattn/go-sqlite3, replaced by modernc.org/sqlite.
  • The unused root templates/ directory; the generator embeds internal/generator/templates.

Upgrading to v1.2.0

  1. In cmd/migrator/main.go, replace the github.com/gopackx/go-migration/pkg/migrator import with github.com/gopackx/go-migration/pkg/cli/app, replace migrator.Run() with app.Run(), and replace the blank import _ "github.com/mattn/go-sqlite3" with _ "modernc.org/sqlite".
  2. Make sure every pool the migrator uses on PostgreSQL/MySQL allows at least two open connections, or pass --no-lock / migrator.WithoutLock(). Behind PgBouncer in transaction mode, connect the migrator directly or use session pooling.
  3. Run migrate:install once before using --dry-run on a fresh database.
  4. If your PostgreSQL tracking table name has upper-case letters, rename the table to the exact configured name before upgrading.
  5. Replace migrator.ErrConfigValidation with config.ErrConfigValidation.
  6. If you implement schema.Grammar, update CompileCreate and CompileDropAllTables and add the new methods.
  7. Always pass migrator.WithGrammar(...) for MySQL and SQLite.

v1.1.0

Added

  • IndexDefinition.Named("...") — chain it on bp.Index(), bp.UniqueIndex(), bp.FulltextIndex(), or bp.SpatialIndex() to override the auto-generated index name.

Fixed

  • make:seeder now scaffolds an init() that calls seeder.AutoRegister, so generated seeders are auto-discovered by db:seed (parity with make:migration).
  • The generated package declaration now follows the output directory's base name (e.g. a seeder_dir of db/data produces package data) instead of a hardcoded name that would not compile in a custom folder.

v1.0.0

Initial stable release of go-migration.