queen

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jan 10, 2026 License: MIT Imports: 11 Imported by: 0

README

Queen

Database migrations for Go.

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 built-in testing helpers and a simple, idiomatic API.

Go Reference Go Report Card GitHub release License

Features

  • Migrations in code - Define migrations as Go code, not separate .sql files
  • Flexible syntax - Use SQL strings in code, Go functions, or mix both
  • Testing helpers - Built-in support for testing your migrations
  • Natural sorting - Smart version ordering: "1" < "2" < "10" < "100", "user_1" < "user_10"
  • Flexible versioning - Use sequential numbers, prefixes, or any naming scheme
  • Type-safe - Full Go type safety for programmatic migrations
  • Multiple databases - PostgreSQL, MySQL, SQLite support with extensible driver interface
  • Lock protection - Prevents concurrent migration runs
  • Checksum validation - Detects when applied migrations have changed

Quick Start

Installation
PostgreSQL
go get github.com/honeynil/queen
go get github.com/honeynil/queen/drivers/postgres
go get github.com/jackc/pgx/v5/stdlib
MySQL
go get github.com/honeynil/queen
go get github.com/honeynil/queen/drivers/mysql
go get github.com/go-sql-driver/mysql
SQLite
go get github.com/honeynil/queen
go get github.com/honeynil/queen/drivers/sqlite
go get github.com/mattn/go-sqlite3
Basic Usage
PostgreSQL
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!")
}
MySQL
package main

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

    _ "github.com/go-sql-driver/mysql"

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

func main() {
    // Connect to MySQL (parseTime=true is required!)
    db, _ := sql.Open("mysql", "user:password@tcp(localhost:3306)/myapp?parseTime=true")
    defer db.Close()

    // Create Queen instance
    driver := mysql.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 INT AUTO_INCREMENT PRIMARY KEY,
                email VARCHAR(255) NOT NULL UNIQUE,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        `,
        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!")
}
SQLite
package main

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

    _ "github.com/mattn/go-sqlite3"

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

func main() {
    // Connect to SQLite (WAL mode recommended for better concurrency)
    db, _ := sql.Open("sqlite3", "myapp.db?_journal_mode=WAL&_foreign_keys=on")
    defer db.Close()

    // Create Queen instance
    driver := sqlite.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 INTEGER PRIMARY KEY AUTOINCREMENT,
                email TEXT NOT NULL UNIQUE,
                created_at TEXT DEFAULT (datetime('now'))
            )
        `,
        DownSQL: `DROP TABLE users`,
    })

    q.MustAdd(queen.M{
        Version: "002",
        Name:    "add_users_name",
        UpSQL:   `ALTER TABLE users ADD COLUMN name TEXT`,
        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)
}

Philosophy

Queen follows the principle: migrations are code, not files. This approach enables:

  • Type safety and IDE support
  • Easier testing and refactoring
  • No file organization overhead
  • Full programmatic control when needed

Queen is designed for developers who want clean, testable migrations without the ceremony.

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

Supported Databases

Database Status Version Locking Mechanism
PostgreSQL ✅ Ready 9.6+ Advisory locks
MySQL ✅ Ready 5.7+ Named locks (GET_LOCK)
MariaDB ✅ Ready 10.2+ Named locks (GET_LOCK)
SQLite ✅ Ready 3.8+ Exclusive transactions
CockroachDB 🔄 Planned - Advisory locks (PostgreSQL compatible)
ClickHouse 🔄 Planned - TBD
Oracle 🔄 Planned 11g+ DBMS_LOCK

See the drivers directory for database-specific documentation and examples.

License

MIT License - see LICENSE for details.

Author

Created by honeynil

Documentation

Overview

Package queen provides a lightweight database migration library for Go.

Queen follows the principle "migrations are code, not files". Instead of managing separate .sql files, you define migrations directly in Go code. This approach provides type safety, better IDE support, and easier testing compared to traditional file-based migration tools.

Basic Usage

Create a Queen instance with a database driver and register migrations:

db, _ := sql.Open("pgx", "postgres://localhost/myapp?sslmode=disable")
driver := postgres.New(db)
q := queen.New(driver)
defer q.Close()

q.MustAdd(queen.M{
    Version: "001",
    Name:    "create_users",
    UpSQL:   "CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(255))",
    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",
})

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

SQL Migrations

SQL migrations use UpSQL and DownSQL fields:

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

Go Function Migrations

For complex logic that can't be expressed in SQL, use UpFunc and DownFunc:

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

        for rows.Next() {
            var id int
            var email string
            rows.Scan(&id, &email)

            normalized := strings.ToLower(strings.TrimSpace(email))
            tx.ExecContext(ctx, "UPDATE users SET email = $1 WHERE id = $2",
                normalized, id)
        }
        return rows.Err()
    },
})

When using Go functions, always set ManualChecksum to track changes. Update it whenever you modify the function (e.g., "v1" -> "v2").

Testing

Queen provides built-in testing helpers:

func TestMigrations(t *testing.T) {
    db := setupTestDB(t)
    driver := postgres.New(db)
    q := queen.NewTest(t, driver) // Auto-cleanup on test end

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

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

Natural Sorting

Queen uses natural sorting for migration versions, so "1" < "2" < "10" < "100". You can use any versioning scheme: sequential numbers ("001", "002"), prefixes ("users_001", "posts_001"), or semantic versions ("v1.0.0").

Migration Operations

Common operations include:

q.Up(ctx)              // Apply all pending migrations
q.UpSteps(ctx, 3)      // Apply next 3 migrations
q.Down(ctx, 1)         // Rollback last migration
q.Reset(ctx)           // Rollback all migrations
statuses, _ := q.Status(ctx)  // Get migration status
q.Validate(ctx)        // Validate migrations
Example

Example demonstrates basic usage of Queen migrations.

package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	driver := mock.New()
	q := queen.New(driver)
	defer q.Close()

	// Register migrations
	q.MustAdd(queen.M{
		Version: "001",
		Name:    "create_users",
		UpSQL:   `CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(255))`,
		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)
	}

	fmt.Println("Migrations applied successfully!")
}
Example (Configuration)

Example_configuration demonstrates custom configuration.

package main

import (
	"context"
	"time"

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

func main() {
	driver := mock.New()

	config := &queen.Config{
		TableName:   "custom_migrations", // Custom table name
		LockTimeout: 30 * time.Minute,
		SkipLock:    false, // Enable lock protection
	}

	q := queen.NewWithConfig(driver, config)
	defer q.Close()

	q.Up(context.Background())
}
Example (GoFunctionMigration)

Example_goFunctionMigration demonstrates using Go functions for complex migrations.

package main

import (
	"context"
	"database/sql"
	"strings"

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

func main() {
	driver := mock.New()
	q := queen.New(driver)
	defer q.Close()

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

	// Go function migration for complex data transformation
	q.MustAdd(queen.M{
		Version:        "002",
		Name:           "normalize_emails",
		ManualChecksum: "v1", // Track function changes
		UpFunc: func(ctx context.Context, tx *sql.Tx) error {
			rows, err := tx.QueryContext(ctx, "SELECT id, email FROM users")
			if err != nil {
				return err
			}
			defer rows.Close()

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

				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()
		},
	})

	q.Up(context.Background())
}
Example (ModularMigrations)

Example_modularMigrations demonstrates organizing migrations by domain.

package main

import (
	"context"

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

func main() {
	driver := mock.New()
	q := queen.New(driver)
	defer q.Close()

	// Register migrations from different modules
	registerUserMigrations(q)
	registerPostMigrations(q)

	q.Up(context.Background())
}

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

func registerPostMigrations(q *queen.Queen) {
	q.MustAdd(queen.M{
		Version: "posts_001",
		Name:    "create_posts",
		UpSQL:   `CREATE TABLE posts (id SERIAL PRIMARY KEY)`,
		DownSQL: `DROP TABLE posts`,
	})
}
Example (Status)

Example_status demonstrates checking migration status.

package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	driver := mock.New()
	q := queen.New(driver)
	defer q.Close()

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

	ctx := context.Background()

	// Check status of all migrations
	statuses, err := q.Status(ctx)
	if err != nil {
		log.Fatal(err)
	}

	for _, s := range statuses {
		fmt.Printf("Version: %s, Name: %s, Status: %s\n",
			s.Version, s.Name, s.Status)
	}
}
Example (Testing)

Example_testing demonstrates testing migrations.

package main

import (
	"testing"

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

func main() {
	// In your test
	testFunc := func(t *testing.T) {
		driver := setupTestDB(t) // Your test DB setup

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

		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()
	}

	// Run the test (in real code, use go test)
	t := &testing.T{}
	testFunc(t)
}

func setupTestDB(_ *testing.T) queen.Driver {

	return mock.New()
}

Index

Examples

Constants

This section is empty.

Variables

View Source
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")
	ErrAlreadyApplied    = errors.New("migration already applied")
)

Common errors 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 for migration tracking. Default: "queen_migrations"
	TableName string

	// LockTimeout for acquiring migration lock. Default: 30 minutes
	LockTimeout time.Duration

	// SkipLock disables locking (not recommended for production). Default: false
	SkipLock bool
}

Config configures Queen behavior.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns default settings: "queen_migrations" table, 30min lock timeout.

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 must be held until Unlock() is called.
	//
	// Implementation notes:
	// - Use database-specific locking (PostgreSQL advisory locks, MySQL named locks, etc.)
	// - The lock should be exclusive to prevent concurrent migration runs
	// - Consider using a unique lock identifier based on the migrations table name
	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.

Driver abstracts database-specific migration tracking, locking, and transaction management. This allows Queen to support multiple databases (PostgreSQL, MySQL, SQLite, etc.) without changing the core library.

Implementing a Driver

To implement a driver for a new database:

  1. Implement all Driver interface methods
  2. Create a migrations tracking table in Init()
  3. Use database-specific locking (advisory locks, named locks, etc.)
  4. Handle transactions properly in Exec()

See drivers/postgres/postgres.go for a reference implementation.

Thread Safety

Driver implementations must be safe for concurrent use by multiple goroutines. The Queen instance will handle locking to prevent concurrent migrations, but the driver should still be thread-safe for Status() and Validate() operations.

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 uniquely identifies this migration.
	// Examples: "001", "002", "user_001", "v1.0.0"
	Version string

	// Name describes what this migration does.
	// Examples: "create_users", "add_email_index"
	Name string

	// UpSQL applies the migration using SQL.
	// Leave empty when using UpFunc.
	UpSQL string

	// DownSQL rolls back the migration using SQL.
	// Optional but recommended for safe rollbacks.
	DownSQL string

	// UpFunc applies the migration using Go code.
	// Use for complex logic that can't be expressed in SQL.
	UpFunc MigrationFunc

	// DownFunc rolls back the migration using Go code.
	// Optional but recommended for safe rollbacks.
	DownFunc MigrationFunc

	// ManualChecksum tracks changes to function migrations.
	// Required when using UpFunc/DownFunc for validation.
	// Examples: "v1", "v2", "normalize-emails-v1"
	// Update this whenever you modify the function.
	ManualChecksum string
	// contains filtered or unexported fields
}

Migration represents a single database migration.

A migration can be defined using SQL strings (UpSQL/DownSQL) or Go functions (UpFunc/DownFunc). You can also mix both approaches in the same migration.

Version and Name

Version must be unique across all migrations. Queen uses natural sorting, so "1", "2", "10" sort correctly. You can use prefixes for organization: "users_001", "posts_001".

Name should be a human-readable description like "create_users" or "add_email_index".

SQL Migrations

For simple schema changes, use UpSQL and DownSQL:

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

Go Function Migrations

For complex logic that can't be expressed in SQL, use UpFunc and DownFunc:

queen.M{
    Version:        "002",
    Name:           "migrate_data",
    ManualChecksum: "v1",
    UpFunc: func(ctx context.Context, tx *sql.Tx) error {
        // Your migration logic here
        return nil
    },
}

IMPORTANT: When using UpFunc/DownFunc, always set ManualChecksum to track changes. Update it whenever you modify the function (e.g., "v1" -> "v2").

Checksums

Queen automatically calculates checksums for SQL migrations. For Go function migrations, you must provide ManualChecksum. This detects when applied migrations have been modified, which can indicate a problem.

func (*Migration) Checksum

func (m *Migration) Checksum() string

Checksum returns a hash for validation. Uses ManualChecksum if set, calculates from SQL otherwise, or returns a marker for Go functions.

func (*Migration) HasRollback

func (m *Migration) HasRollback() bool

HasRollback checks if DownSQL or DownFunc is defined.

func (*Migration) IsDestructive

func (m *Migration) IsDestructive() bool

IsDestructive checks DownSQL for destructive keywords: DROP TABLE, DROP DATABASE, TRUNCATE, etc. Up migrations are assumed constructive and not checked.

func (*Migration) Validate

func (m *Migration) Validate() error

Validate ensures Version, Name, and at least one Up method are 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 manages database migrations.

func New

func New(driver Driver) *Queen

New creates a Queen instance with default configuration.

func NewWithConfig

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

NewWithConfig creates a Queen instance with custom settings.

func (*Queen) Add

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

Add registers a migration after validation. Returns ErrVersionConflict if version already exists.

func (*Queen) Close

func (q *Queen) Close() error

Close releases database resources.

func (*Queen) Down

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

Down rolls back the last n migrations. If n <= 0, rolls back only the last migration.

Example

ExampleQueen_Down demonstrates rolling back migrations.

package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	driver := mock.New()
	q := queen.New(driver)
	defer q.Close()

	ctx := context.Background()

	// Rollback last migration
	if err := q.Down(ctx, 1); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Rolled back 1 migration")
}

func (*Queen) MustAdd

func (q *Queen) MustAdd(m M)

MustAdd is like Add but panics on error. Use during initialization when registration must succeed.

func (*Queen) Reset

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

Reset rolls back all applied migrations.

Example

ExampleQueen_Reset demonstrates rolling back all migrations.

package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	driver := mock.New()
	q := queen.New(driver)
	defer q.Close()

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

	fmt.Println("All migrations rolled back")
}

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. Equivalent to UpSteps(ctx, 0).

Example

ExampleQueen_Up demonstrates applying all pending migrations.

package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	driver := mock.New()
	q := queen.New(driver)
	defer q.Close()

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

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

	fmt.Println("All migrations applied")
}

func (*Queen) UpSteps

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

UpSteps applies up to n pending migrations. If n <= 0, applies all pending migrations.

Example

ExampleQueen_UpSteps demonstrates applying a specific number of migrations.

package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	driver := mock.New()
	q := queen.New(driver)
	defer q.Close()

	q.MustAdd(queen.M{Version: "001", Name: "migration_1", UpSQL: "..."})
	q.MustAdd(queen.M{Version: "002", Name: "migration_2", UpSQL: "..."})
	q.MustAdd(queen.M{Version: "003", Name: "migration_3", UpSQL: "..."})

	ctx := context.Background()

	// Apply only the next 2 migrations
	if err := q.UpSteps(ctx, 2); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Applied 2 migrations")
}

func (*Queen) Validate

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

Validate checks for duplicate versions, invalid migrations, and checksum mismatches.

Example

ExampleQueen_Validate demonstrates validating migrations.

package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	driver := mock.New()
	q := queen.New(driver)
	defer q.Close()

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

	ctx := context.Background()
	if err := q.Validate(ctx); err != nil {
		log.Fatalf("Validation failed: %v", err)
	}

	fmt.Println("All migrations valid")
}

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.

TestHelper wraps a Queen instance with test-specific helpers that automatically fail tests on errors instead of returning them. This reduces boilerplate in migration tests.

The TestHelper automatically cleans up (closes the Queen instance) when the test ends using t.Cleanup().

Usage

Create a TestHelper with NewTest and use its Must* methods:

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

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

Or use TestUpDown to test both up and down migrations:

func TestMigrations(t *testing.T) {
    q := queen.NewTest(t, driver)
    q.MustAdd(queen.M{...})
    q.TestUpDown() // Applies then rolls back all migrations
}

func NewTest

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

NewTest creates a Queen instance with automatic cleanup.

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
}
Example

ExampleNewTest demonstrates using the testing helper.

package main

import (
	"fmt"
	"testing"

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

func main() {
	testFunc := func(t *testing.T) {
		driver := setupTestDB(t)

		// NewTest automatically cleans up when test ends
		q := queen.NewTest(t, driver)

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

		// Test migrations
		q.MustUp()
		q.MustValidate()

		fmt.Println("Test passed")
	}

	t := &testing.T{}
	testFunc(t)
}

func setupTestDB(_ *testing.T) queen.Driver {

	return mock.New()
}

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 verifies migrations can be applied and rolled back.

Recommended for testing because it ensures: - Up migrations execute without errors - Down migrations execute without errors - Database returns to original state after rollback

Usage:

func TestMigrations(t *testing.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() // Tests both up and down
}
Example

ExampleTestHelper_TestUpDown demonstrates testing up and down migrations.

package main

import (
	"fmt"
	"testing"

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

func main() {
	testFunc := func(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`,
		})

		// TestUpDown applies all migrations, then rolls them back
		q.TestUpDown()

		fmt.Println("Up and down migrations work correctly")
	}

	t := &testing.T{}
	testFunc(t)
}

func setupTestDB(_ *testing.T) queen.Driver {

	return mock.New()
}

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.
mysql
Package mysql provides a MySQL driver for Queen migrations.
Package mysql provides a MySQL driver for Queen migrations.
postgres
Package postgres provides a PostgreSQL driver for Queen migrations.
Package postgres provides a PostgreSQL driver for Queen migrations.
sqlite
Package sqlite provides a SQLite driver for Queen migrations.
Package sqlite provides a SQLite 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