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¶
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
Upapplies all pending migrations. Equivalent toUpSteps(ctx, 0).UpStepsapplies up to n pending migrations. If n <= 0, applies all.Downrolls back the last n migrations. If n <= 0, rolls back 1.Resetrolls 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)
DryRunreturns an execution plan without applying. Direction isDirectionUporDirectionDown.Explainreturns a detailed plan for a specific migration version.
Lifecycle¶
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:
Versionmust contain only letters, digits, dots, dashes, and underscores.Namemust contain only lowercase letters, digits, and underscores.Namemust 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¶
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¶
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
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¶
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)
}
}