Documentation
¶
Overview ¶
Package migrate runs SQL migrations for the Dygo sql module, on Postgres and MySQL, with no other dependency.
Migrations are pairs of files named by a version number and a name:
migrations/0001_create_users.up.sql migrations/0001_create_users.down.sql
embedded in the binary with an embed.FS, so production needs nothing but the binary. dygo g migration writes the next pair. A dygo_migrations table records what ran, and a database lock (pg_advisory_lock on Postgres, GET_LOCK on MySQL) makes a second instance wait instead of migrating at the same time.
Each migration runs in a transaction together with its dygo_migrations row, so on Postgres a failing migration leaves nothing behind. MySQL commits every DDL statement on its own, so a migration that fails half way leaves the statements before the failure applied: keep MySQL migrations to one change each. A file whose first line is
-- dygo:no-transaction
runs outside a transaction, for statements that refuse one, such as CREATE INDEX CONCURRENTLY. An up file with
-- dygo:after-deploy
among its first comment lines is the contract half of an expand and contract change (dygo g migration -safe): Migrator.Up stops before it, and before every later migration, unless the Migrator has Contract (dygo db migrate --contract). Migrator.Held reports where it stopped.
Run migrations from the command line with Main (dygo db migrate, or ./app db:migrate in production), from code with Migrator, or at boot with OnBoot for a small single instance service.
Index ¶
- Constants
- Variables
- func Main(ctx context.Context, d sql.Dialect, fsys fs.FS, args []string, ...) (code int, handled bool)
- func OnBoot(fsys fs.FS, opts ...Option) *dygo.Module
- func ServerURL(kind, raw string) (server, name string, err error)
- func Split(src string, mysql bool) []string
- type Applied
- type MainOption
- type Migration
- type Migrator
- func (m *Migrator) Down(ctx context.Context, steps int) ([]Migration, error)
- func (m *Migrator) DownTo(ctx context.Context, version int64) ([]Migration, error)
- func (m *Migrator) Held() (Migration, bool)
- func (m *Migrator) Migrations() []Migration
- func (m *Migrator) Pending(ctx context.Context, to int64) ([]Migration, error)
- func (m *Migrator) Status(ctx context.Context) ([]State, error)
- func (m *Migrator) Up(ctx context.Context, to int64) ([]Migration, error)
- type Option
- type State
Constants ¶
const AfterDeployMarker = "-- dygo:after-deploy"
AfterDeployMarker, among the comment lines at the top of an up file, holds the migration back until the code that no longer needs the old schema is deployed: Up stops before it unless the Migrator was made with Contract (dygo db migrate --contract). dygo g migration -safe writes it on the contract half of an expand and contract change.
const DefaultTable = "dygo_migrations"
DefaultTable records the applied migrations.
Variables ¶
var Commands = []string{"db:create", "db:drop", "db:migrate", "db:rollback", "db:status", "db:schema:create", "db:schema:drop"}
Commands are the names Main handles.
Functions ¶
func Main ¶
func Main(ctx context.Context, d sql.Dialect, fsys fs.FS, args []string, opts ...MainOption) (code int, handled bool)
Main runs the database command named by args[0] (db:migrate, db:status, ...) and reports handled=false, doing nothing, for any other argument, so main can call it first:
if code, ok := migrate.Main(ctx, postgres.Dialect(), migrations.FS, os.Args[1:]); ok {
os.Exit(code)
}
It runs before the application is created: it reads DATABASE_URL from the environment and .env files itself, and opens one connection, so it works on a database the app cannot boot against yet (db:create) and needs no other service the app depends on. dygo db migrate runs it through go run.
func OnBoot ¶
OnBoot returns a module that applies the pending migrations in fsys while the app boots, before it serves traffic. Use it for a small single instance service; with several instances, run db:migrate as a deploy step instead, so a failing migration stops the deploy rather than every instance. The lock still keeps two instances from migrating at once.
Imports: []*dygo.Module{postgres.Module(), migrate.OnBoot(migrations.FS)}
Only providers that inject Applied are sure to be built after the migrations ran; others that query the database in their constructor should inject it too.
func ServerURL ¶
ServerURL returns the URL of the server without the database (Postgres: the postgres maintenance database) and the database name, so the database itself can be created or dropped. kind is "postgres" or "mysql".
func Split ¶
Split cuts a migration file into statements at the semicolons outside strings, quoted identifiers and comments. Statements that hold only comments or white space are dropped. mysql selects MySQL lexing (backslash escapes in strings, # comments, backtick identifiers); otherwise Postgres lexing applies ($tag$ dollar quoted bodies, E'...' escapes).
MySQL's DELIMITER command belongs to the mysql client, not the server, so it is not supported: write a stored routine's body without semicolons, or run it from a -- dygo:no-transaction file with the multiStatements DSN option.
Types ¶
type Applied ¶
type Applied []Migration
Applied is what OnBoot provides: the migrations it applied at boot.
type MainOption ¶
type MainOption func(*mainOptions)
MainOption configures Main.
func Files ¶
func Files(paths ...string) MainOption
Files sets the .env files to read, earlier ones winning. The default is .env.local then .env, as in projects made by dygo new.
func Output ¶
func Output(stdout, stderr io.Writer) MainOption
Output sends the command's output to stdout and its errors to stderr.
func Prefix ¶
func Prefix(p string) MainOption
Prefix reads REPORTS_DATABASE_URL and friends with Prefix("REPORTS_"), as sql.Prefix does for the module.
func WithConfig ¶
func WithConfig(cfg sql.Config) MainOption
WithConfig uses cfg instead of reading the environment.
func WithTable ¶
func WithTable(name string) MainOption
WithTable records migrations in name instead of dygo_migrations.
type Migration ¶
type Migration struct {
Version int64
Name string
Up string
Down string
HasDown bool
// UpNoTx and DownNoTx are set by the -- dygo:no-transaction marker.
UpNoTx, DownNoTx bool
// AfterDeploy is set by AfterDeployMarker in the up file.
AfterDeploy bool
}
Migration is one version: its up SQL and, when there is a down file, its down SQL.
type Migrator ¶
type Migrator struct {
// contains filtered or unexported fields
}
Migrator applies and rolls back the migrations of one database.
func (*Migrator) DownTo ¶
DownTo rolls back every applied migration newer than version, newest first. DownTo(ctx, 0) rolls back everything.
func (*Migrator) Held ¶
Held returns the after-deploy migration that the last Pending or Up stopped before, and false when it stopped at none (see Contract).
func (*Migrator) Migrations ¶
Migrations returns the loaded migrations, oldest first.
func (*Migrator) Pending ¶
Pending returns the migrations Up would apply, up to version to (0 for all), without applying them.
type Option ¶
type Option func(*Migrator)
Option configures a Migrator.
func Contract ¶
func Contract() Option
Contract lets Up apply migrations marked with AfterDeployMarker. Without it, Up stops before the first of them; Held reports which one.
func OutOfOrder ¶
func OutOfOrder() Option
OutOfOrder lets Up apply a pending migration older than the newest applied one, which happens when two branches each add a migration. Without it, Up refuses, so the order the database saw stays the order of the files.
type State ¶
type State struct {
Version int64
Name string
Applied bool
AppliedAt time.Time
// Missing means the database recorded the version but no file has it.
Missing bool
// AfterDeploy marks a migration that waits for Contract (see
// AfterDeployMarker).
AfterDeploy bool
}
State is one migration's status.