Concepts
Target

Target

A Target is what the migrations are transforming. It tracks which migrations have been applied, what the current state is, and provides locking to prevent concurrent runs.

Interface

type Target interface {
    Current(ctx context.Context) (string, error)
    Create(ctx context.Context) error
    Destroy(ctx context.Context) error
    Done(ctx context.Context) ([]string, error)
    Add(ctx context.Context, id string) error
    Remove(ctx context.Context, id string) error
    FinishMigration(ctx context.Context, id string) error
    StartMigration(ctx context.Context, id string) error
    Lock(ctx context.Context) (Unlocker, error)
}
MethodPurpose
CurrentReturns the ID of the most recently applied migration. Returns ErrNoCurrentMigration if none applied.
CreateCreates the tracking storage (e.g., _migrations table). Safe to call multiple times.
DestroyRemoves the tracking storage entirely.
DoneReturns all applied migration IDs.
AddRecords a migration as applied.
RemoveRemoves a migration from the applied list.
StartMigrationMarks a migration as in-progress (dirty).
FinishMigrationMarks a migration as complete (clears dirty flag).
LockAcquires an exclusive lock. Returns an Unlocker to release it.

Dirty State

When a migration starts executing, StartMigration marks it as dirty. Once it completes successfully, FinishMigration clears the flag. If the process crashes mid-migration, the dirty flag remains set.

On the next run, Done detects the dirty migration and returns ErrDirtyMigration. This requires manual intervention — you need to either complete the migration manually or clean up the dirty state before retrying.

Locking

The Lock method prevents multiple processes from running migrations concurrently. The implementation varies by driver:

DriverLocking Mechanism
PostgreSQL (sql)pg_advisory_lock
PostgreSQL (pgx)pg_advisory_lock
MongoDBDocument-based lock in _migrations_lock collection
DynamoDBConditional put in _migrations-lock table
Generic SQLNo-op (no locking)

Lock is acquired at the start of Migrate() and released when it returns, even on error.

Built-in Targets

See the Drivers section for available Target implementations:

Custom Targets

You can implement Target for any storage backend. The contract is straightforward:

  1. Create should be idempotent — safe to call on every run.
  2. Done must return IDs sorted in the same order as the source.
  3. StartMigration/FinishMigration must track dirty state.
  4. Lock should block or fail if another process holds the lock.

A JSON file, Redis key, or even an in-memory map can serve as a target — as long as it correctly tracks applied migrations and their dirty state.