Changelog
DocumentationChangelog
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), andFresh(locked before anything is dropped) take it, as do the CLI'smigrate:install,db:seed,db:seed:rollback, anddb:seed:truncate. PostgreSQL usespg_try_advisory_lock, MySQL usesGET_LOCK, and SQLite relies on its file lock. See Concurrency and Locking. migrator.WithLockTimeout(d)(defaultmigrator.DefaultLockTimeout, 30s),migrator.WithoutLock(), the publicMigrator.Lock(ctx) (release func() error, err error), and the sentinelmigrator.ErrLockTimeout.--lock-timeout <duration>and--no-lockflags on every CLI command that changes the database.migrate:status --checkexits with status 1 (printingN migration(s) pendingto stderr) when any migration is pending — for readiness gates such as a Kubernetes initContainer, or CI.seeder.NewRunner(reg, db, logger, opts ...RunnerOption)withseeder.WithDialect(seeder.DialectMySQL | DialectSQLite | DialectPostgres).migrator.NewTrackerWithGrammar(db, table, grammar)for dialect-aware access to the tracking table.- New
schema.Grammarmethods:CompileListTables,CompileTrackingTable,QuoteIdentifier,CompileHasTableIn,Placeholder,CompileTryLock, andCompileUnlock; helpersschema.QuoteQualifiedandschema.SplitQualified. "sqlite3"is accepted as an alias of"sqlite"for the configdriver.- A real-database integration suite (
go test -tags integration ./tests/integration/..., configured withGOMIGRATE_TEST_POSTGRES_DSN/GOMIGRATE_TEST_MYSQL_DSN; SQLite always runs) and GitHub Actions CI runninggo vet, the unit tests, and the integration tests against PostgreSQL 16 and MySQL 8.
Changed
- Breaking: the CLI entry point moved from
pkg/migratorto the newpkg/cli/apppackage. Incmd/migrator/main.go, importgithub.com/gopackx/go-migration/pkg/cli/appand callapp.Run(). Importingpkg/migratoras a library no longer pulls in the CLI, the config loader, the code generator, or the YAML parser.migrator initscaffolds the new form. - Breaking: custom
schema.Grammarimplementations must be updated.CompileCreatenow returns([]string, error)(theCREATE TABLEfollowed by separateCREATE INDEXstatements, each executed individually),CompileDropAllTables(tables []string) []stringreceives 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, or0for unlimited). A pool limited to one connection fails with a clear error suggesting a larger pool orWithoutLock()/--no-lock. - Breaking:
migrate:installnow 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/sqlas"sqlite". The CLI builds withCGO_ENABLED=0, and the release binaries — which are built that way and previously shipped without working SQLite support — now support SQLite. The scaffoldedmain.goblank-imports_ "modernc.org/sqlite", and SQLite connections need nohost:databaseis the file path or:memory:. - The MySQL driver adds
parseTime=trueto the DSN unless you setparseTimeyourself inoptions, soDATETIME/TIMESTAMPcolumns scan intotime.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/$NSQL. WithoutWithGrammarit 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:statusis 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 — runmigrate:installfirst.HasTable/HasColumnreportfalsein dry-run mode. Fresh()works on every database: PostgreSQL drops and recreates thepublicschema; 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-runFresh()prints the drops and shows every migration as pending in batch 1.Reset()now runs the before-migrate hooks, likeUp()andRollback().- 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 asapp.migrationsare 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, andmake:factoryreject names that are not lower snake_case (^[a-z][a-z0-9_]*$) instead of generating a file whoseinit()panics.commands.CommandContext.Migrator(andcli.CLIContext.Migrator) is now thecommands.Migratorinterface, which*migrator.Migratorsatisfies directly;Status()returns[]migrator.MigrationStatus.- Options passed to
migrator.Neware applied before the runner and tracker are built, so their order no longer matters. - Sentinel errors re-exported by
migratorare aliases of the canonical values inpkg/database,pkg/seeder, and the grammars, soerrors.Isworks with either.
Fixed
- MySQL and SQLite tracking tables were created with PostgreSQL syntax.
CompileCreatejoinedCREATE TABLEandCREATE INDEXstatements 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:installruns could race onCREATE TABLE IF NOT EXISTS, which is not atomic on PostgreSQL.
Removed
- Breaking:
migrator.Run()— useapp.Run()fromgithub.com/gopackx/go-migration/pkg/cli/app. - Breaking:
migrator.ErrConfigValidation— useconfig.ErrConfigValidation. The migrator never returned it. - Breaking:
commands.MigratorRunnerandcommands.MigrationStatusInfo—*migrator.Migratoris used directly. - The CGO-based SQLite driver
github.com/mattn/go-sqlite3, replaced bymodernc.org/sqlite. - The unused root
templates/directory; the generator embedsinternal/generator/templates.
Upgrading to v1.2.0
- In
cmd/migrator/main.go, replace thegithub.com/gopackx/go-migration/pkg/migratorimport withgithub.com/gopackx/go-migration/pkg/cli/app, replacemigrator.Run()withapp.Run(), and replace the blank import_ "github.com/mattn/go-sqlite3"with_ "modernc.org/sqlite". - Make sure every pool the migrator uses on PostgreSQL/MySQL allows at least two open connections, or pass
--no-lock/migrator.WithoutLock(). Behind PgBouncer intransactionmode, connect the migrator directly or use session pooling. - Run
migrate:installonce before using--dry-runon a fresh database. - If your PostgreSQL tracking table name has upper-case letters, rename the table to the exact configured name before upgrading.
- Replace
migrator.ErrConfigValidationwithconfig.ErrConfigValidation. - If you implement
schema.Grammar, updateCompileCreateandCompileDropAllTablesand add the new methods. - Always pass
migrator.WithGrammar(...)for MySQL and SQLite.
v1.1.0
Added
IndexDefinition.Named("...")— chain it onbp.Index(),bp.UniqueIndex(),bp.FulltextIndex(), orbp.SpatialIndex()to override the auto-generated index name.
Fixed
make:seedernow scaffolds aninit()that callsseeder.AutoRegister, so generated seeders are auto-discovered bydb:seed(parity withmake:migration).- The generated
packagedeclaration now follows the output directory's base name (e.g. aseeder_dirofdb/dataproducespackage data) instead of a hardcoded name that would not compile in a custom folder.
v1.0.0
Initial stable release of go-migration.