Skip to main content

Migration Format

Each migration is a queen.M value:

q.MustAdd(queen.M{
Version: "001",
Name: "create_users",
UpSQL: `CREATE TABLE users (id SERIAL PRIMARY KEY);`,
DownSQL: `DROP TABLE users;`,
})

Fields

FieldPurpose
VersionUnique migration version. Common choices are 001, 002, or timestamp versions.
NameLowercase name using letters, digits, and underscores.
UpSQLSQL applied during Up.
DownSQLSQL applied during rollback.
UpFuncGo function applied during Up.
DownFuncGo function applied during rollback.
ManualChecksumRequired discipline for Go-function migrations. Bump when function logic changes.
IsolationLevelOptional per-migration transaction isolation level.

Validation rules:

  • Version is required and may contain letters, digits, dots, dashes, and underscores.
  • Name is required, must be at most 63 characters, and uses lowercase letters, digits, and underscores.
  • Each migration needs UpSQL or UpFunc.
  • Duplicate versions are rejected at registration.

SQL migrations

queen.M{
Version: "002",
Name: "add_user_index",
UpSQL: `CREATE INDEX users_email_idx ON users (email);`,
DownSQL: `DROP INDEX users_email_idx;`,
}

SQL migrations are checksummed from UpSQL and DownSQL. If an applied SQL migration changes later, Queen reports it as modified and blocks migration commands until you handle the drift.

Whitespace is normalized before hashing, so basic SQL formatting changes do not cause a checksum mismatch.

Go-function migrations

queen.M{
Version: "003",
Name: "normalize_emails",
ManualChecksum: "normalize-emails-v1",
UpFunc: func(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, `UPDATE users SET email = LOWER(TRIM(email))`)
return err
},
}

Go functions cannot be hashed from source at runtime. Use ManualChecksum as a stable version marker and bump it whenever the function changes.

If a Go-function migration has no SQL and no ManualChecksum, Queen stores a no-checksum marker. For production migrations, prefer setting ManualChecksum so changes are explicit in review.

Mixed migrations

You can combine SQL and Go:

queen.M{
Version: "004",
Name: "create_profiles_and_backfill",
UpSQL: `CREATE TABLE profiles (user_id BIGINT PRIMARY KEY, display_name TEXT);`,
UpFunc: func(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, `
INSERT INTO profiles (user_id, display_name)
SELECT id, name FROM users
`)
return err
},
DownFunc: func(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, `DELETE FROM profiles`)
return err
},
DownSQL: `DROP TABLE profiles;`,
}

Queen runs mixed migrations in this order:

  • Up: UpSQL, then UpFunc
  • Down: DownFunc, then DownSQL

That lets rollback cleanup data before removing schema objects.

Naming

Use sequential padded versions when you want readable review order:

naming:
pattern: sequential-padded
padding: 3
enforce: true

Queen also supports sequential and semver naming policies.

Isolation level

Set a per-migration isolation level when one migration needs stronger or weaker transaction behavior:

queen.M{
Version: "005",
Name: "critical_backfill",
ManualChecksum: "critical-backfill-v1",
IsolationLevel: sql.LevelSerializable,
UpFunc: func(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, `UPDATE users SET migrated = TRUE`)
return err
},
}

Driver support depends on the database. PostgreSQL is the reference path for transactional behavior.

Rollback policy

Queen does not require every migration to have rollback SQL or a rollback function, but commands that roll back a migration will fail if rollback is missing. For production migrations, decide explicitly:

  • provide DownSQL or DownFunc for reversible schema changes;
  • document irreversible migrations in review and avoid workflows that depend on automatic rollback;
  • use check --rollback-test against disposable databases when you want to verify rollback behavior.