Skip to content

API Reference

Go API documentation for Queen v0.5.0.

Queen

Main migration manager.

Constructors

func New(driver Driver, opts ...Option) *Queen
func NewWithConfig(driver Driver, config *Config) *Queen
// Default configuration
q := queen.New(driver)
defer q.Close()

// With functional options
q := queen.New(driver, queen.WithLogger(slog.Default()))

// With custom config
q := queen.NewWithConfig(driver, &queen.Config{
    TableName:   "migrations",
    LockTimeout: 30 * time.Minute,
})

Migration Registration

func (q *Queen) Add(m M) error
func (q *Queen) MustAdd(m M)  // Panics on error
q.MustAdd(queen.M{
    Version: "001",
    Name:    "create_users",
    UpSQL:   `CREATE TABLE users (id SERIAL PRIMARY KEY)`,
    DownSQL: `DROP TABLE users`,
})

Migration Operations

func (q *Queen) Up(ctx context.Context) error
func (q *Queen) UpSteps(ctx context.Context, n int) error
func (q *Queen) Down(ctx context.Context, n int) error
func (q *Queen) Reset(ctx context.Context) error
  • Up applies all pending migrations. Equivalent to UpSteps(ctx, 0).
  • UpSteps applies up to n pending migrations. If n <= 0, applies all.
  • Down rolls back the last n migrations. If n <= 0, rolls back 1.
  • Reset rolls back all applied migrations.

Query Operations

func (q *Queen) Status(ctx context.Context) ([]MigrationStatus, error)
func (q *Queen) Validate(ctx context.Context) error
func (q *Queen) DetectGaps(ctx context.Context) ([]Gap, error)

Planning Operations

func (q *Queen) DryRun(ctx context.Context, direction string, limit int) ([]MigrationPlan, error)
func (q *Queen) Explain(ctx context.Context, version string) (*MigrationPlan, error)
  • DryRun returns an execution plan without applying. Direction is DirectionUp or DirectionDown.
  • Explain returns a detailed plan for a specific migration version.

Lifecycle

func (q *Queen) Close() error
func (q *Queen) Driver() Driver

Migration

type MigrationFunc func(ctx context.Context, tx *sql.Tx) error

type Migration struct {
    Version        string
    Name           string
    UpSQL          string
    DownSQL        string
    UpFunc         MigrationFunc
    DownFunc       MigrationFunc
    ManualChecksum string
    IsolationLevel sql.IsolationLevel
}

type M = Migration

Required: Version, Name, at least one of UpSQL or UpFunc.

Validation rules:

  • Version must contain only letters, digits, dots, dashes, and underscores.
  • Name must contain only lowercase letters, digits, and underscores.
  • Name must be 63 characters or fewer.

Execution priority: UpFunc takes priority over UpSQL. If UpFunc is defined, UpSQL is not executed (and vice versa for Down).

Checksums: SQL migrations get automatic SHA-256 checksums. Go function migrations require ManualChecksum to track changes.

Methods

func (m *Migration) Validate() error
func (m *Migration) Checksum() string
func (m *Migration) HasRollback() bool
func (m *Migration) IsDestructive() bool

IsDestructive checks the DownSQL for destructive keywords: DROP TABLE, DROP DATABASE, DROP SCHEMA, TRUNCATE.

Validation Functions

func IsValidMigrationName(name string) bool
func IsValidMigrationVersion(version string) bool

Config

type Config struct {
    TableName      string             // Default: "queen_migrations"
    LockTimeout    time.Duration      // Default: 30m
    SkipLock       bool               // Default: false
    Naming         *NamingConfig
    IsolationLevel sql.IsolationLevel // Default: sql.LevelDefault
}

func DefaultConfig() *Config

Isolation level priority: Migration-level > Config-level > sql.LevelDefault.


Naming

type NamingPattern string

const (
    NamingPatternNone             NamingPattern = ""
    NamingPatternSequential       NamingPattern = "sequential"
    NamingPatternSequentialPadded NamingPattern = "sequential-padded"
    NamingPatternSemver           NamingPattern = "semver"
)

type NamingConfig struct {
    Pattern NamingPattern
    Padding int   // Default: 3 (for sequential-padded)
    Enforce bool  // Reject non-matching versions
}

func DefaultNamingConfig() *NamingConfig
func (nc *NamingConfig) Validate(version string) error
func (nc *NamingConfig) FindNextVersion(existingVersions []string) (string, error)

Pattern examples:

Pattern Valid versions
sequential 1, 2, 3 (no leading zeros)
sequential-padded 001, 002, 003 (fixed width)
semver 1.0.0, 1.1.0, 2.0.0

Option

type Option func(*Queen)

func WithLogger(logger Logger) Option
q := queen.New(driver, queen.WithLogger(slog.Default()))

Logger

type Logger interface {
    InfoContext(ctx context.Context, msg string, args ...any)
    WarnContext(ctx context.Context, msg string, args ...any)
    ErrorContext(ctx context.Context, msg string, args ...any)
}

Compatible with *slog.Logger. Pass any structured logger that implements these methods.


Status Types

type Status int

const (
    StatusPending  Status = iota
    StatusApplied
    StatusModified
)

func (s Status) String() string
type MigrationStatus struct {
    Version     string
    Name        string
    Status      Status
    AppliedAt   *time.Time
    Checksum    string
    HasRollback bool
    Destructive bool
}
type MigrationType string

const (
    MigrationTypeSQL    MigrationType = "sql"
    MigrationTypeGoFunc MigrationType = "go-func"
    MigrationTypeMixed  MigrationType = "mixed"
)
type MigrationPlan struct {
    Version       string        `json:"version"`
    Name          string        `json:"name"`
    Direction     string        `json:"direction"`
    Status        string        `json:"status"`
    Type          MigrationType `json:"type"`
    SQL           string        `json:"sql,omitempty"`
    HasRollback   bool          `json:"has_rollback"`
    IsDestructive bool          `json:"is_destructive"`
    Checksum      string        `json:"checksum"`
    Warnings      []string      `json:"warnings,omitempty"`
}

Gap Types

type GapType string

const (
    GapTypeNumbering    GapType = "numbering"
    GapTypeApplication  GapType = "application"
    GapTypeUnregistered GapType = "unregistered"
)

type Gap struct {
    Type        GapType  `json:"type"`
    Version     string   `json:"version"`
    Name        string   `json:"name,omitempty"`
    Description string   `json:"description"`
    Severity    string   `json:"severity"`
    AppliedAt   *string  `json:"applied_at,omitempty"`
    BlockedBy   []string `json:"blocked_by,omitempty"`
}

Applied

type Applied struct {
    Version      string
    Name         string
    AppliedAt    time.Time
    Checksum     string
    AppliedBy    string
    DurationMS   int64
    Hostname     string
    Environment  string
    Action       string
    Status       string
    ErrorMessage string
}

Represents a migration record stored in the database. Metadata (AppliedBy, Hostname, Environment) is collected automatically during execution.


Driver Interface

type Driver interface {
    Init(ctx context.Context) error
    GetApplied(ctx context.Context) ([]Applied, error)
    Record(ctx context.Context, m *Migration, meta *MigrationMetadata) error
    Remove(ctx context.Context, version string) error
    Lock(ctx context.Context, timeout time.Duration) error
    Unlock(ctx context.Context) error
    Exec(ctx context.Context, isolationLevel sql.IsolationLevel, fn func(*sql.Tx) error) error
    Close() error
}

MigrationMetadata

type MigrationMetadata struct {
    AppliedBy    string
    DurationMS   int64
    Hostname     string
    Environment  string
    Action       string
    Status       string
    ErrorMessage string
}

Collected automatically by Queen during migration execution. Includes username, hostname, QUEEN_ENV environment variable, and timing.


QueenIgnore

type QueenIgnore struct { /* unexported */ }

func LoadQueenIgnore() (*QueenIgnore, error)
func LoadQueenIgnoreFrom(path string) (*QueenIgnore, error)

func (qi *QueenIgnore) IsIgnored(version string) bool
func (qi *QueenIgnore) GetReason(version string) string
func (qi *QueenIgnore) AddIgnore(version, reason, ignoredBy string) error
func (qi *QueenIgnore) RemoveIgnore(version string) error
func (qi *QueenIgnore) ListIgnored() []*IgnoredGap
func (qi *QueenIgnore) Save() error
type IgnoredGap struct {
    Version   string
    Reason    string
    IgnoredAt time.Time
    IgnoredBy string
}

TestHelper

type TestHelper struct {
    *Queen
}

func NewTest(t *testing.T, driver Driver) *TestHelper

func (th *TestHelper) MustUp()
func (th *TestHelper) MustDown(n int)
func (th *TestHelper) MustReset()
func (th *TestHelper) MustValidate()
func (th *TestHelper) TestUpDown()
func (th *TestHelper) TestRollback()

NewTest creates a Queen instance with automatic cleanup when the test ends. TestUpDown applies all migrations then rolls them all back. TestRollback performs a full cycle: Up, Down one-by-one, Up again.

func TestMigrations(t *testing.T) {
    driver := setupTestDB(t)
    q := queen.NewTest(t, driver)

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

    q.TestUpDown()
    q.TestRollback()
}

Errors

var (
    ErrNoMigrations         = errors.New("no migrations registered")
    ErrVersionConflict      = errors.New("version conflict")
    ErrMigrationNotFound    = errors.New("migration not found")
    ErrChecksumMismatch     = errors.New("checksum mismatch")
    ErrLockTimeout          = errors.New("lock timeout")
    ErrNoDriver             = errors.New("driver not initialized")
    ErrInvalidMigration     = errors.New("invalid migration")
    ErrNameTooLong          = errors.New("migration name exceeds 63 characters")
    ErrInvalidMigrationName = errors.New("invalid migration name")
    ErrAlreadyApplied       = errors.New("migration already applied")
)

MigrationError

type MigrationError struct {
    Version   string
    Name      string
    Operation string
    Driver    string
    Cause     error
}

func (e *MigrationError) Error() string
func (e *MigrationError) Unwrap() error

Wraps errors with migration context including version, name, operation (up/down), and driver name.

Error Handling

err := q.Up(ctx)
if err != nil {
    switch {
    case errors.Is(err, queen.ErrNoMigrations):
        log.Println("No migrations to apply")
    case errors.Is(err, queen.ErrLockTimeout):
        log.Println("Another migration in progress")
    case errors.Is(err, queen.ErrChecksumMismatch):
        log.Println("Migration modified after application")
    default:
        var migErr *queen.MigrationError
        if errors.As(err, &migErr) {
            log.Printf("Migration %s failed on %s: %v",
                migErr.Version, migErr.Driver, migErr.Cause)
        }
    }
}

Retry Pattern

func migrateWithRetry(q *queen.Queen, ctx context.Context, maxRetries int) error {
    for i := 0; i < maxRetries; i++ {
        err := q.Up(ctx)
        if err == nil {
            return nil
        }
        if !errors.Is(err, queen.ErrLockTimeout) {
            return err
        }
        time.Sleep(time.Duration(i+1) * time.Second)
    }
    return fmt.Errorf("migration failed after %d retries", maxRetries)
}

Constants

const (
    DirectionUp   = "up"
    DirectionDown = "down"
    DriverUnknown = "unknown"
)

Complete Example

package main

import (
    "context"
    "database/sql"
    "log"
    "os"
    "time"

    "github.com/honeynil/queen"
    "github.com/honeynil/queen/drivers/postgres"
    _ "github.com/jackc/pgx/v5/stdlib"
)

func main() {
    db, _ := sql.Open("pgx", os.Getenv("DATABASE_URL"))
    defer db.Close()

    q := queen.NewWithConfig(postgres.New(db), &queen.Config{
        LockTimeout: 30 * time.Minute,
    })
    defer q.Close()

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

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
    defer cancel()

    if err := q.Up(ctx); err != nil {
        log.Fatal(err)
    }

    statuses, _ := q.Status(ctx)
    for _, s := range statuses {
        log.Printf("%s: %s", s.Version, s.Status)
    }
}