Skip to main content
Version: v1.17.0

Migrations API Reference

Everything that changes a schema: the reflective helpers (Migrate, Sync, CreateIndex, AddForeignKey) and the versioned migrator in the migrate package.

For which path to use when — quick reflective setup versus reviewable versioned migrations — see the Migrations guide.

SymbolSignaturePurpose
Client.MigrateMigrate(ctx, models ...any) errorCreates missing tables (and m2m join tables).
Client.SyncSync(ctx, opts SyncOptions, models ...any) errorReconciles existing tables with model metadata.
Client.CreateIndexCreateIndex(ctx, table, indexName string, columns []string, unique bool) errorCreates a simple index.
Client.AddForeignKeyAddForeignKey(ctx, table, constraintName string, columns []string, refTable string, refColumns []string, onDelete, onUpdate string) errorAdds a foreign key constraint.
migrate.RegisterRegister(m *Migration)Registers a migration in the global registry.
migrate.ResetReset()Clears the registry (tests).
migrate.NewMigratorNewMigrator(client *quark.Client, opts ...MigratorOption) *MigratorBinds a migrator to a client. Options: WithoutLock(), WithLockTimeout(d), WithLockName(name), WithLogger(l).
Migration.UpTx / Migration.DownTxfunc(ctx, tx *sql.Tx) errorTransactional forms: migration and ledger row commit together where the engine rolls DDL back.
Migrator.InitInit(ctx) errorCreates the quark_migrations tracking table.
Migrator.UpUp(ctx, steps int) errorApplies pending migrations (ID order).
Migrator.UpDryRunUpDryRun(ctx, steps int) errorPrints pending migrations without running them.
Migrator.DownDown(ctx, steps int) errorReverts applied migrations (reverse ID order).

Client Schema Helpers​

Migrate(ctx context.Context, models ...any) error​

Creates missing tables for the given models.

err := client.Migrate(ctx, &User{}, &Order{}, &Product{})

Migrate also creates many-to-many join tables for relations tagged rel:"many_to_many" with an m2m tag.

Sync(ctx context.Context, opts SyncOptions, models ...any) error​

Synchronizes existing tables with model metadata.

err := client.Sync(ctx, quark.SyncOptions{}, &User{})

Supported sync operations:

ChangeSupport
Missing tableCalls Migrate unless DryRun is true.
New columnAdds the column.
Renamed columnUses quark:"rename:old_col".
Removed columnDrops only when the client's Limits.SafeMigrations is false.
type SyncOptions struct {
DryRun bool // Log the SQL but don't execute it.
NoTransaction bool // Don't wrap the sync in a transaction.
}

Dropping a removed column is gated by Limits.SafeMigrations, not by SyncOptions. DefaultLimits() sets SafeMigrations: true, so removed columns are left in place until you opt into destructive sync:

limits := quark.DefaultLimits()
limits.SafeMigrations = false // allow DROP COLUMN
client, _ := quark.New("postgres", dsn, quark.WithLimits(limits))

CreateIndex(ctx, table, indexName string, columns []string, unique bool) error​

Creates a simple index.

err := client.CreateIndex(
ctx,
"users",
"idx_users_email",
[]string{"email"},
true,
)

AddForeignKey(ctx, table, constraintName string, columns []string, refTable string, refColumns []string, onDelete, onUpdate string) error​

Adds a foreign key constraint.

err := client.AddForeignKey(
ctx,
"orders",
"fk_orders_user",
[]string{"user_id"},
"users",
[]string{"id"},
"CASCADE",
"",
)

onDelete and onUpdate are referential actions — CASCADE, SET NULL, SET DEFAULT, RESTRICT, NO ACTION — and an empty one writes no clause, so the engine applies its default, NO ACTION. They are written as the engine takes them: Oracle has no clause for NO ACTION and Quark leaves it out, and an action the engine does not have — RESTRICT on SQL Server; on Oracle anything but ON DELETE CASCADE and ON DELETE SET NULL — returns an error wrapping ErrUnsupportedFeature that names it, without sending the statement. ApplyPlan treats the foreign keys of a plan the same way, and refuses the whole plan before its first operation. The table per engine is in the dialects reference.

Versioned Migrations​

migrate.Register(m *Migration)​

Registers a migration in the global migration registry.

migrate.Register(&migrate.Migration{
ID: "202605050001_add_email_index",
Name: "add email index",
Up: func(ctx context.Context, client *quark.Client) error {
return client.CreateIndex(ctx, "users", "idx_users_email", []string{"email"}, true)
},
Down: func(ctx context.Context, client *quark.Client) error {
return client.Exec(ctx, "DROP INDEX idx_users_email")
},
})

Migration IDs are sorted lexicographically. Use timestamp-like IDs.

type Migration struct {
ID string
Name string
Message string
Up func(ctx context.Context, client *quark.Client) error
Down func(ctx context.Context, client *quark.Client) error
}

migrate.Reset()​

Clears the migration registry. Intended for tests.

Migrator​

migrate.NewMigrator(client *quark.Client, opts ...MigratorOption) *Migrator​

Creates a migrator bound to a client.

migrator := migrate.NewMigrator(client)
migrator = migrate.NewMigrator(client, migrate.WithLockTimeout(time.Minute), migrate.WithLogger(logger))

The migrator stores applied migration IDs in quark_migrations. Up and Down take the engine's cluster-wide migration lock by default (quark:schema, 30s timeout); WithoutLock() skips it, WithLockName and WithLockTimeout tune it, and WithLogger routes the progress lines (the default is the client's logger). SQLite has no distributed lock and proceeds without one.

Init(ctx context.Context) error​

Creates the migration tracking table if needed.

err := migrator.Init(ctx)

Up(ctx context.Context, steps int) error​

Applies pending migrations in ID order.

err := migrator.Up(ctx, 0) // apply all pending
err = migrator.Up(ctx, 2) // apply at most two

UpDryRun(ctx context.Context, steps int) error​

Prints pending migrations without executing their Up functions.

err := migrator.UpDryRun(ctx, 0)

Down(ctx context.Context, steps int) error​

Reverts applied migrations in reverse ID order.

err := migrator.Down(ctx, 1)

Raw SQL Requirement​

migrate.Migrator uses client.Exec for its tracking table changes. Configure the migration client with AllowRawQueries: true:

limits := quark.DefaultLimits()
limits.AllowRawQueries = true

client, err := quark.New("postgres", dsn,
quark.WithLimits(limits),
)

High-level helpers such as Migrate, Sync, CreateIndex, and AddForeignKey do not require AllowRawQueries.