Skip to content

Migrations ​

Introduction ​

Migrations keep your database's tables in step with your models. They are Go files in the program's migrations package, each registering its operations from init, and you review and edit them like any other code.

The schema a database has is the replay of its migrations. makemigrations compares the replay with the models the program declares and writes the difference as the next file.

Writing Migrations ​

A migration file looks like this:

go
// migrations/0001_initial.go — generated by orm makemigrations.
package migrations

import m "github.com/vuka-lang/orm/v2/migration"

func init() {
	m.Register(m.Migration{
		Name: "0001_initial",
		Operations: []m.Operation{
			m.CreateModel{
				Table: "users",
				Fields: []m.NamedField{
					m.F("id", m.BigAuto()),
					m.F("name", m.Varchar(100).Unique()),
					m.F("email", m.Varchar(200)),
				},
			},
		},
	})
}

The program imports the package, so orm.Migrate (and the commands) see the migrations:

go
import (
	"context"
	"os"

	"github.com/vuka-lang/orm/v2"
	_ "github.com/vuka-lang/orm/v2/postgres"
	// _ "example.com/blog/migrations": the migrations makemigrations wrote
)

func run(ctx context.Context) error {
	db, err := orm.ConnectURL(os.Getenv("DATABASE_URL"))
	if err != nil {
		return err
	}
	orm.AddDatabase("main", db, orm.AsDefault())
	return orm.Migrate(ctx) // pending migrations, under a lock
}

The orm Command ​

Run the command with go run, or add it as a tool:

sh
go run github.com/vuka-lang/orm/v2/cmd/orm makemigrations [name]
go get -tool github.com/vuka-lang/orm/v2/cmd/orm    # then: go tool orm …

The commands are:

Command
orm makemigrations [name]write the next migration from the models' changes
orm migrate [target|zero]apply every database's pending migrations, or take one database to target, unapplying later ones (zero: all)
orm showmigrations[X]/[ ] per migration, per database
orm sqlmigrate <name>a migration's SQL for the database's dialect
orm generate [dir]write orm_scanners_gen.go: the models' registrations and reflection-free row scanners

They take these flags:

Flag
--db namethe database (makemigrations, migrate, sqlmigrate); the default otherwise
--checkfail when a migration (or a generated file) is missing, writing nothing: for CI
--dry-runprint the migration instead of writing it
--emptya migration with an empty RunGo, for a data migration
--noinputask nothing: makemigrations writes a rename as a removal and an addition; migrate unapplies without confirming
--backwardssqlmigrate: the SQL unapplying it
--dialectpostgres, mysql (mysql/9.3.0 for a server version), sqlite: the SQL of another dialect
--database name=URLa database's URL (repeatable; default= for the default); else DATABASE_URL and DATABASE_URL_<NAME> from the environment, else from the module's .env
--default namethe default database's name, as the models name it
--main dirthe program's main package, when the module has several

Prompts ​

On a terminal makemigrations asks about renames — "Did you rename users.mail to users.email? [y/N]".

WARNING

On a terminal migrate asks before it unapplies anything: a target (or zero) runs the backwards operations of the migrations after it, which may drop tables and their data, so it lists them and runs them only on a yes. Any other answer changes nothing. --noinput doesn't ask, for CI; neither does a migrate whose input isn't a terminal.

How the Commands Run ​

The commands build the program's main package with an init overlaid that answers them before main runs; nothing is written to the tree but the migration file. The program links the drivers it opens.

Migrating Vuka Models ​

A module with .vuka files is built through vuka gen (the vuka command on the PATH, or VUKA), and the tool's init runs after the main package's own, so its models' field attributes and registrations are in. Nothing else changes: orm makemigrations in a Vuka project reads @orm.Char{Max: 200} as it would read a tag.

orm generate is optional — without it rows are read by reflection — and in a Vuka project @orm.Entity registers a model without it.

Operations ​

A migration is a list of operations:

Operation
m.CreateModel{Table, Fields, Indexes}, m.DeleteModel, m.RenameModeltables
m.AddField, m.RemoveField, m.AlterField, m.RenameFieldcolumns
m.AddIndex, m.RemoveIndex, m.AddConstraint, m.RemoveConstraintindexes, constraints
m.CreateExtension{Name}a Postgres extension (vector, pg_trgm)
m.CreateFunction{…}an SQL function
m.RunSQL, m.RunGoanything else; a data migration

Field Builders ​

Fields are written with builders — m.BigAuto(), m.Varchar(n), m.Text(), m.BigInt().FK("users", "id").OnDelete(m.Cascade), m.Time().Null().Index(), m.Vector(3), m.SmallInt(), m.Date(), m.UUID(), and .Default(v), .DefaultSQL(sql), .Comment(s), .Collation(name), .Precision(n), .Unsigned(), .Types(m.Dialects{…}) for what the column attributes set — and per-dialect SQL with m.Dialects{Postgres, MySQL, SQLite}.

orm.CreateExtension("vector") declares an extension for the next migration from the program.

Migrating at Run Time ​

orm.Migrate(ctx, opts…) applies pending migrations under a lock (a Postgres advisory lock, MySQL's GET_LOCK), recording them in orm_migrations (orm.MigrationsTable names another table):

orm.MigrateOn("main")one database
orm.MigrateLogger(l)log what is applied
orm.ApplyMigrations(ctx, db, target, migs…)a given list (tools, tests)
orm.Schema{}.Verify(ctx, models…)check the models against a database (a schema of it) at start-up

In a web app, ormweb's .MigrateOnStart(true) runs them as the databases open (by default it does only in development).

Several Databases ​

Each database has its own migrations: the default's in migrations/, another database's in migrations/<db>/ (its own package, imported too). A model's table is migrated on the database its writes go to, and on its Mirror. Pass --db to work on one database:

sh
orm makemigrations --db analytics    # migrations/analytics/ with the models routed there
orm migrate                          # every database's
orm migrate --db analytics 0003_sales

Routers registered at init (for the command, which runs before main) route the tables, and every router's AllowMigrate must allow it. See Multiple Databases.

Released under the MIT License. Vuka builds on Go and its tools, which are made by the Go Authors. Vuka is not affiliated with Google or the Go project. Go is a trademark of Google LLC.