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:
// 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:
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:
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 name | the database (makemigrations, migrate, sqlmigrate); the default otherwise |
--check | fail when a migration (or a generated file) is missing, writing nothing: for CI |
--dry-run | print the migration instead of writing it |
--empty | a migration with an empty RunGo, for a data migration |
--noinput | ask nothing: makemigrations writes a rename as a removal and an addition; migrate unapplies without confirming |
--backwards | sqlmigrate: the SQL unapplying it |
--dialect | postgres, mysql (mysql/9.3.0 for a server version), sqlite: the SQL of another dialect |
--database name=URL | a 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 name | the default database's name, as the models name it |
--main dir | the 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.RenameModel | tables |
m.AddField, m.RemoveField, m.AlterField, m.RenameField | columns |
m.AddIndex, m.RemoveIndex, m.AddConstraint, m.RemoveConstraint | indexes, constraints |
m.CreateExtension{Name} | a Postgres extension (vector, pg_trgm) |
m.CreateFunction{…} | an SQL function |
m.RunSQL, m.RunGo | anything 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:
orm makemigrations --db analytics # migrations/analytics/ with the models routed there
orm migrate # every database's
orm migrate --db analytics 0003_salesRouters registered at init (for the command, which runs before main) route the tables, and every router's AllowMigrate must allow it. See Multiple Databases.