queen

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 29, 2025 License: MIT Imports: 11 Imported by: 0

README

Queen

Database migrations for Go, with royal treatment.

Queen is a database migration library that lets you define migrations in code, not separate files. It supports both SQL and Go function migrations, with excellent testing helpers and a clean, chainable API.

Go Reference Go Report Card GitHub release License

Features

  • Migrations in code - No separate .sql files cluttering your repo
  • Hybrid approach - Use SQL strings, Go functions, or mix both
  • Testing helpers - Built-in support for testing your migrations
  • Natural sorting - Smart version ordering: "1" < "2" < "10" < "100"
  • Flexible versioning - Use sequential numbers, prefixes, or any naming scheme
  • Type-safe - Full Go type safety for programmatic migrations
  • PostgreSQL first - Excellent Postgres support, more databases coming soon
  • Lock protection - Prevents concurrent migration runs
  • Checksum validation - Detects when applied migrations have changed

Quick Start

Installation
go get github.com/honeynil/queen
go get github.com/honeynil/queen/drivers/postgres
go get github.com/jackc/pgx/v5/stdlib
Basic Usage
package main

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

    _ "github.com/jackc/pgx/v5/stdlib"

    "github.com/honeynil/queen"
    "github.com/honeynil/queen/drivers/postgres"
)

func main() {
    // Connect to database
    db, _ := sql.Open("pgx", "postgres://localhost/myapp?sslmode=disable")
    defer db.Close()

    // Create Queen instance
    driver := postgres.New(db)
    q := queen.New(driver)
    defer q.Close()

    // Register migrations
    q.MustAdd(queen.M{
        Version: "001",
        Name:    "create_users_table",
        UpSQL: `
            CREATE TABLE users (
                id SERIAL PRIMARY KEY,
                email VARCHAR(255) NOT NULL UNIQUE,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
            )
        `,
        DownSQL: `DROP TABLE users`,
    })

    q.MustAdd(queen.M{
        Version: "002",
        Name:    "add_users_name",
        UpSQL:   `ALTER TABLE users ADD COLUMN name VARCHAR(255)`,
        DownSQL: `ALTER TABLE users DROP COLUMN name`,
    })

    // Apply all pending migrations
    ctx := context.Background()
    if err := q.Up(ctx); err != nil {
        log.Fatal(err)
    }

    log.Println("Migrations applied successfully!")
}

Usage Examples

Modular Migrations (Registry Pattern)

For large projects, organize migrations by domain:

// users/migrations.go
package users

func Register(q *queen.Queen) {
    q.MustAdd(queen.M{
        Version: "users_001",
        Name:    "create_users",
        UpSQL:   `CREATE TABLE users (...)`,
        DownSQL: `DROP TABLE users`,
    })
}

// posts/migrations.go
package posts

func Register(q *queen.Queen) {
    q.MustAdd(queen.M{
        Version: "posts_001",
        Name:    "create_posts",
        UpSQL:   `CREATE TABLE posts (...)`,
        DownSQL: `DROP TABLE posts`,
    })
}

// main.go
func main() {
    q := queen.New(driver)

    users.Register(q)
    posts.Register(q)

    q.Up(ctx)
}
Go Function Migrations

For complex migrations that need programmatic logic:

q.MustAdd(queen.M{
    Version:        "003",
    Name:           "normalize_emails",
    ManualChecksum: "v1", // Important: track function changes!
    UpFunc: func(ctx context.Context, tx *sql.Tx) error {
        // Fetch users
        rows, err := tx.QueryContext(ctx, "SELECT id, email FROM users")
        if err != nil {
            return err
        }
        defer rows.Close()

        // Process each user
        for rows.Next() {
            var id int
            var email string
            if err := rows.Scan(&id, &email); err != nil {
                return err
            }

            // Normalize email
            normalized := strings.ToLower(strings.TrimSpace(email))

            _, err = tx.ExecContext(ctx,
                "UPDATE users SET email = $1 WHERE id = $2",
                normalized, id)
            if err != nil {
                return err
            }
        }

        return rows.Err()
    },
    DownFunc: func(ctx context.Context, tx *sql.Tx) error {
        // Rollback logic (if possible)
        return nil
    },
})
Testing Migrations

Queen makes it easy to test your migrations:

func TestMigrations(t *testing.T) {
    // Setup test database
    db := setupTestDB(t)
    driver := postgres.New(db)

    // Create test instance (auto-cleanup on test end)
    q := queen.NewTest(t, driver)

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

    // Test both up and down migrations
    q.TestUpDown()
}
Migration Operations
// Apply all pending migrations
q.Up(ctx)

// Apply next N migrations
q.UpSteps(ctx, 3)

// Rollback last migration
q.Down(ctx, 1)

// Rollback last N migrations
q.Down(ctx, 3)

// Rollback all migrations
q.Reset(ctx)

// Get migration status
statuses, _ := q.Status(ctx)
for _, s := range statuses {
    fmt.Printf("%s: %s (%s)\n", s.Version, s.Name, s.Status)
}

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

Why Queen?

vs golang-migrate
Feature Queen golang-migrate
Migrations in code Yes No (separate files)
No file clutter Yes No
Go function migrations Yes Limited
Testing helpers Yes No
Natural sorting Yes No
Type safety Yes No (SQL strings)
Flexible versioning Yes Timestamps only
vs goose
Feature Queen goose
Migrations in code Yes Partial
Chainable API Yes No
No init() required Yes No
Testing helpers Yes No
Clean API Yes Inconsistent
vs GORM AutoMigrate
Feature Queen GORM AutoMigrate
Production safe Yes Unpredictable
Rollback support Yes No
Migration history Yes No
ORM independent Yes Tied to GORM
Control Full Limited

Configuration

config := &queen.Config{
    TableName:   "custom_migrations", // Default: "queen_migrations"
    LockTimeout: 30 * time.Minute,    // Default: 30 minutes
    SkipLock:    false,               // Default: false (recommended)
}

q := queen.NewWithConfig(driver, config)

API Documentation

See pkg.go.dev for complete API documentation.

Key Types
Migration / M
type Migration struct {
    Version        string        // Unique version identifier
    Name           string        // Human-readable name
    UpSQL          string        // SQL to apply migration
    DownSQL        string        // SQL to rollback migration
    UpFunc         MigrationFunc // Go function to apply
    DownFunc       MigrationFunc // Go function to rollback
    ManualChecksum string        // Manual checksum for Go functions
}

type M = Migration // Convenient alias
Queen
type Queen struct { /* ... */ }

func New(driver Driver) *Queen
func NewWithConfig(driver Driver, config *Config) *Queen
func NewTest(t *testing.T, driver Driver) *TestHelper

func (q *Queen) Add(m M) error
func (q *Queen) MustAdd(m M)
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
func (q *Queen) Status(ctx context.Context) ([]MigrationStatus, error)
func (q *Queen) Validate(ctx context.Context) error
func (q *Queen) Close() error

Roadmap

v0.x (Current)
  • Core library
  • PostgreSQL driver
  • Testing helpers
  • Natural sorting
  • Checksum validation
v1.0 (Planned)
  • CLI tool (queen up, queen down, etc.)
  • LoadFromDir (for compatibility with existing migrations)
  • MySQL driver
  • SQLite driver
v2.0 (Future)
  • Migration dependencies
  • Web UI
  • Metrics/observability
  • More database drivers

License

MIT License - see LICENSE for details.

Author

Created by honeynil

Documentation

Overview

Package queen provides a database migration library for Go.

Queen allows you to define migrations in code (not separate files), supports both SQL and Go function migrations, and provides excellent testing helpers for validating your migrations.

Basic usage:

db, _ := sql.Open("postgres", "...")
driver := postgres.New(db)
q := queen.New(driver)

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

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

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoMigrations is returned when no migrations are registered.
	ErrNoMigrations = errors.New("no migrations registered")

	// ErrVersionConflict is returned when duplicate version is detected.
	ErrVersionConflict = errors.New("version conflict")

	// ErrMigrationNotFound is returned when a migration version doesn't exist.
	ErrMigrationNotFound = errors.New("migration not found")

	// ErrChecksumMismatch is returned when a migration's checksum doesn't match.
	ErrChecksumMismatch = errors.New("checksum mismatch")

	// ErrLockTimeout is returned when unable to acquire lock within timeout.
	ErrLockTimeout = errors.New("lock timeout")

	// ErrNoDriver is returned when driver is not initialized.
	ErrNoDriver = errors.New("driver not initialized")

	// ErrInvalidMigration is returned when migration validation fails.
	ErrInvalidMigration = errors.New("invalid migration")

	// ErrAlreadyApplied is returned when trying to apply already applied migration.
	ErrAlreadyApplied = errors.New("migration already applied")
)

Common errors that can be returned by Queen operations.

Functions

This section is empty.

Types

type Applied

type Applied struct {
	// Version is the unique version identifier of the migration.
	Version string

	// Name is the human-readable name of the migration.
	Name string

	// AppliedAt is when the migration was applied.
	AppliedAt time.Time

	// Checksum is the hash of the migration content at the time it was applied.
	Checksum string
}

Applied represents a migration that has been applied to the database. This is returned by Driver.GetApplied().

type Config

type Config struct {
	// TableName is the name of the table used to track migrations.
	// Default: "queen_migrations"
	TableName string

	// LockTimeout is how long to wait for the migration lock.
	// Default: 30 minutes
	LockTimeout time.Duration

	// SkipLock disables migration locking (not recommended for prod env).
	// Default: false
	SkipLock bool
}

Config holds configuration options for Queen.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns the default configuration.

type Driver

type Driver interface {
	// Init initializes the driver and creates the migrations tracking table if needed.
	// This should be called before any other operations.
	Init(ctx context.Context) error

	// GetApplied returns all migrations that have been applied to the database.
	// The returned slice should be sorted by applied time in ascending order.
	GetApplied(ctx context.Context) ([]Applied, error)

	// Record marks a migration as applied in the database.
	// This should be called after successfully executing a migration.
	Record(ctx context.Context, m *Migration) error

	// Remove removes a migration record from the database.
	// This should be called after successfully rolling back a migration.
	Remove(ctx context.Context, version string) error

	// Lock acquires an exclusive lock to prevent concurrent migrations.
	// If the lock cannot be acquired within the specified timeout, it returns ErrLockTimeout.
	// The lock is automatically released when the context is cancelled.
	Lock(ctx context.Context, timeout time.Duration) error

	// Unlock releases the migration lock.
	// This should be called in a defer statement after acquiring the lock.
	Unlock(ctx context.Context) error

	// Exec executes a function within a transaction.
	// If the function returns an error, the transaction is rolled back.
	// Otherwise, the transaction is committed.
	Exec(ctx context.Context, fn func(*sql.Tx) error) error

	// Close closes the database connection.
	Close() error
}

Driver is the interface that database-specific drivers must implement. It abstracts away database-specific migration tracking and locking.

type M

type M = Migration

M is a convenient alias for Migration, used in registration:

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

type Migration

type Migration struct {
	// Version is a unique identifier for this migration.
	// Examples: "001", "002", "user_001", "v1.0.0"
	Version string

	// Name is a human-readable description of the migration.
	// Examples: "create_users", "add_email_index"
	Name string

	// UpSQL is the SQL statement to apply the migration.
	// Used for simple SQL migrations.
	UpSQL string

	// DownSQL is the SQL statement to rollback the migration.
	// Optional but recommended for safe rollbacks.
	DownSQL string

	// UpFunc is a Go function to apply the migration.
	// Used for complex migrations that need programmatic logic.
	UpFunc MigrationFunc

	// DownFunc is a Go function to rollback the migration.
	// Optional but recommended for safe rollbacks.
	DownFunc MigrationFunc

	// ManualChecksum is an optional manual checksum for Go function migrations.
	// When using UpFunc/DownFunc, set this to track migration changes.
	// Example: "v1", "v2", or descriptive like "normalize-emails-v1"
	// If not set, checksum validation will be skipped for Go functions.
	ManualChecksum string
	// contains filtered or unexported fields
}

Migration represents a single database migration. It can be defined using SQL strings, Go functions, or both.

For SQL migrations, use UpSQL and DownSQL fields. For Go function migrations, use UpFunc and DownFunc fields.

The Version field must be unique across all migrations. Queen uses natural sorting, so "001", "002", "010" work correctly. You can also use prefixes like "user_001", "post_001" for modular organization.

func (*Migration) Checksum

func (m *Migration) Checksum() string

Checksum returns a unique hash of the migration content. For SQL migrations, it hashes UpSQL and DownSQL. For Go function migrations with ManualChecksum, it uses that value. For Go function migrations without ManualChecksum, it returns a special marker.

func (*Migration) HasRollback

func (m *Migration) HasRollback() bool

HasRollback returns true if the migration has a down migration.

func (*Migration) IsDestructive

func (m *Migration) IsDestructive() bool

IsDestructive returns true if the migration contains potentially destructive operations. This checks for DROP TABLE, DROP DATABASE, TRUNCATE, etc. Only checks DownSQL, as Up migrations are assumed to be constructive.

func (*Migration) Validate

func (m *Migration) Validate() error

Validate checks if the migration is valid. A migration must have either UpSQL or UpFunc defined.

type MigrationError

type MigrationError struct {
	Version string
	Name    string
	Err     error
}

MigrationError wraps an error with migration context.

func (*MigrationError) Error

func (e *MigrationError) Error() string

func (*MigrationError) Unwrap

func (e *MigrationError) Unwrap() error

type MigrationFunc

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

MigrationFunc is a function that executes a migration using a transaction. It receives a context and a transaction, and should return an error if the migration fails.

type MigrationStatus

type MigrationStatus struct {
	// Version is the unique version identifier of the migration.
	Version string

	// Name is the human-readable name of the migration.
	Name string

	// Status indicates whether the migration is pending, applied, or modified.
	Status Status

	// AppliedAt is when the migration was applied (nil if not applied).
	AppliedAt *time.Time

	// Checksum is the current checksum of the migration.
	Checksum string

	// HasRollback indicates if the migration has a down migration.
	HasRollback bool

	// Destructive indicates if the down migration contains destructive operations.
	Destructive bool
}

MigrationStatus contains detailed information about a migration's current state. This is returned by Queen.Status().

type Queen

type Queen struct {
	// contains filtered or unexported fields
}

Queen is the main migration manager. It holds registered migrations and orchestrates their execution.

func New

func New(driver Driver) *Queen

New creates a new Queen instance with the given driver and default configuration.

func NewWithConfig

func NewWithConfig(driver Driver, config *Config) *Queen

NewWithConfig creates a new Queen instance with custom configuration.

func (*Queen) Add

func (q *Queen) Add(m M) error

Add registers a new migration. If a migration with the same version already exists, it returns ErrVersionConflict. The migration is validated before being added.

func (*Queen) Close

func (q *Queen) Close() error

Close closes the database connection.

func (*Queen) Down

func (q *Queen) Down(ctx context.Context, n int) error

Down rolls back the last n migrations. If n is 0 or negative, only the last migration is rolled back.

func (*Queen) MustAdd

func (q *Queen) MustAdd(m M)

MustAdd is like Add but panics on error. Useful for migration registration at package init time.

func (*Queen) Reset

func (q *Queen) Reset(ctx context.Context) error

Reset rolls back all applied migrations.

func (*Queen) Status

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

Status returns the status of all registered migrations.

func (*Queen) Up

func (q *Queen) Up(ctx context.Context) error

Up applies all pending migrations in order. It acquires a lock, loads applied migrations, and applies any pending ones.

func (*Queen) UpSteps

func (q *Queen) UpSteps(ctx context.Context, n int) error

UpSteps applies up to n pending migrations. If n is 0 or negative, all pending migrations are applied.

func (*Queen) Validate

func (q *Queen) Validate(ctx context.Context) error

Validate validates all registered migrations. It checks for: - Duplicate versions - Invalid migrations - Checksum mismatches with applied migrations

type Status

type Status int

Status represents the current state of a migration.

const (
	// StatusPending indicates the migration has not been applied yet.
	StatusPending Status = iota

	// StatusApplied indicates the migration has been successfully applied.
	StatusApplied

	// StatusModified indicates the migration has been applied,
	// but its content has changed (checksum mismatch).
	StatusModified
)

func (Status) String

func (s Status) String() string

String returns a human-readable representation of the status.

type TestHelper

type TestHelper struct {
	*Queen
	// contains filtered or unexported fields
}

TestHelper provides testing utilities for migrations.

func NewTest

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

NewTest creates a new Queen instance for testing. It uses the provided driver and will clean up automatically when the test ends.

Usage:

func TestMigrations(t *testing.T) {
    db := setupTestDB(t) // Your test DB setup
    driver := postgres.New(db)
    q := queen.NewTest(t, driver)

    q.MustAdd(queen.M{...})

    // Test will automatically clean up
}

func (*TestHelper) MustDown

func (th *TestHelper) MustDown(n int)

MustDown is like Down but fails the test on error.

func (*TestHelper) MustReset

func (th *TestHelper) MustReset()

MustReset is like Reset but fails the test on error.

func (*TestHelper) MustUp

func (th *TestHelper) MustUp()

MustUp is like Up but fails the test on error.

func (*TestHelper) MustValidate

func (th *TestHelper) MustValidate()

MustValidate is like Validate but fails the test on error.

func (*TestHelper) TestUpDown

func (th *TestHelper) TestUpDown()

TestUpDown tests that migrations can be applied and rolled back successfully. This validates that your Down migrations work correctly.

Usage:

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

    q.TestUpDown() // Tests both up and down
}

Directories

Path Synopsis
drivers
mock
Package mock provides an in-memory mock driver for testing Queen without a real database.
Package mock provides an in-memory mock driver for testing Queen without a real database.
postgres
Package postgres provides a PostgreSQL driver for Queen migrations.
Package postgres provides a PostgreSQL driver for Queen migrations.
internal
checksum
Package checksum provides checksum calculation for migrations.
Package checksum provides checksum calculation for migrations.
sort
Package sort provides natural sorting for migration versions.
Package sort provides natural sorting for migration versions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL