queen

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Feb 4, 2026 License: MIT Imports: 13 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, MS SQL Server support with extensible driver interface
  • Lock protection - Prevents concurrent migration runs
  • Checksum validation - Detects when applied migrations have changed
  • CLI tool - Built-in command-line interface for migration management

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
ClickHouse
go get github.com/honeynil/queen
go get github.com/honeynil/queen/drivers/clickhouse
go get github.com/ClickHouse/clickhouse-go/v2
YandexDB (YDB)
go get github.com/honeynil/queen
go get github.com/honeynil/queen/drivers/ydb
go get github.com/ydb-platform/ydb-go-sdk/v3
MS SQL Server
go get github.com/honeynil/queen
go get github.com/honeynil/queen/drivers/mssql
go get github.com/microsoft/go-mssqldb
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!")
}
YandexDB (YDB)
package main

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

    _ "github.com/ydb-platform/ydb-go-sdk/v3"

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

func main() {
    // Connect to YDB with special parameters for database/sql compatibility:
    // - go_query_mode=scripting: enables DDL+DML support
    // - go_fake_tx=scripting: transaction emulation
    // - go_query_bind=declare,numeric: auto-converts $1,$2 to YDB named params
    dsn := "grpc://localhost:2136/local?go_query_mode=scripting&go_fake_tx=scripting&go_query_bind=declare,numeric"
    db, _ := sql.Open("ydb", dsn)
    defer db.Close()

    // Create Queen instance
    driver, _ := ydb.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         Utf8,
                email      Utf8 NOT NULL,
                name       Utf8,
                created_at Timestamp,
                PRIMARY KEY (id)
            )
        `,
        DownSQL: `DROP TABLE users`,
    })

    q.MustAdd(queen.M{
        Version: "002",
        Name:    "add_users_bio",
        UpSQL:   `ALTER TABLE users ADD COLUMN bio Utf8`,
        DownSQL: `ALTER TABLE users DROP COLUMN bio`,
    })

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

    log.Println("Migrations applied successfully!")
}
MS SQL Server
package main

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

    _ "github.com/microsoft/go-mssqldb"

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

func main() {
    // Connect to SQL Server
    db, _ := sql.Open("sqlserver", "sqlserver://user:password@localhost:1433?database=myapp")
    defer db.Close()

    // Create Queen instance
    driver := mssql.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 IDENTITY(1,1) PRIMARY KEY,
                email NVARCHAR(255) NOT NULL UNIQUE,
                created_at DATETIME2 DEFAULT GETUTCDATE()
            )
        `,
        DownSQL: `DROP TABLE users`,
    })

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

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)
Naming Pattern Enforcement

Queen can enforce naming conventions to prevent migration versioning mistakes. This is especially useful for teams to maintain consistency.

config := &queen.Config{
    Naming: &queen.NamingConfig{
        Pattern: queen.NamingPatternSequentialPadded,
        Padding: 3,       // Generates: 001, 002, 003, ...
        Enforce: true,    // Reject non-conforming versions
    },
}

q := queen.NewWithConfig(driver, config)

q.MustAdd(queen.M{Version: "001", Name: "create_users"})  // ✅ OK
q.MustAdd(queen.M{Version: "002", Name: "add_email"})     // ✅ OK
q.MustAdd(queen.M{Version: "1", Name: "invalid"})         // ❌ Error: wrong format
Sequential
config := &queen.Config{
    Naming: &queen.NamingConfig{
        Pattern: queen.NamingPatternSequential, // Generates: 1, 2, 3, ...
        Enforce: true,
    },
}
Semantic Versioning
config := &queen.Config{
    Naming: &queen.NamingConfig{
        Pattern: queen.NamingPatternSemver, // Enforces: 1.0.0, 1.1.0, 2.0.0
        Enforce: true,
    },
}

q.MustAdd(queen.M{Version: "1.0.0", Name: "initial"})  // ✅ OK
q.MustAdd(queen.M{Version: "1.1.0", Name: "feature"})  // ✅ OK
CLI Integration

When using the CLI with .queen.yaml, the naming pattern is automatically applied:

# .queen.yaml
naming:
  pattern: sequential-padded
  padding: 3
  enforce: true

development:
  driver: postgres
  dsn: postgres://localhost/dev
# CLI automatically uses the pattern
migrate create add_users
# Creates: migrations/001_add_users.go with Version: "001"

migrate create add_posts
# Creates: migrations/002_add_posts.go with Version: "002"
Logging

Queen supports structured logging compatible with Go's slog package. By default, no logging is performed (noop logger).

Basic Logging with slog
import (
    "log/slog"
    "os"
    "github.com/honeynil/queen"
    "github.com/honeynil/queen/drivers/postgres"
)

// Use default slog logger
logger := slog.Default()
q := queen.New(driver, queen.WithLogger(logger))

// Or create custom logger
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelInfo,
})
logger = slog.New(handler)
q := queen.New(driver, queen.WithLogger(logger))
Custom Logger

Implement the queen.Logger interface to use your own logging library:

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

// Example with zerolog
type ZerologAdapter struct {
    logger zerolog.Logger
}

func (z *ZerologAdapter) InfoContext(ctx context.Context, msg string, args ...any) {
    z.logger.Info().Fields(args).Msg(msg)
}

func (z *ZerologAdapter) WarnContext(ctx context.Context, msg string, args ...any) {
    z.logger.Warn().Fields(args).Msg(msg)
}

func (z *ZerologAdapter) ErrorContext(ctx context.Context, msg string, args ...any) {
    z.logger.Error().Fields(args).Msg(msg)
}

logger := &ZerologAdapter{logger: zerolog.New(os.Stdout)}
q := queen.New(driver, queen.WithLogger(logger))
Logged Events

Queen logs the following events:

  • Migration lifecycle: start, completion, errors, and duration
  • Lock operations: acquisition and release
  • Warnings: checksum mismatches, naming pattern violations
  • Validation errors: migration validation failures

Example log output (JSON format):

{"time":"2024-01-31T10:00:00Z","level":"INFO","msg":"lock acquired","table":"queen_migrations"}
{"time":"2024-01-31T10:00:01Z","level":"INFO","msg":"migration started","version":"001","name":"create_users","direction":"up"}
{"time":"2024-01-31T10:00:02Z","level":"INFO","msg":"migration completed","version":"001","name":"create_users","direction":"up","duration_ms":1234}
{"time":"2024-01-31T10:00:02Z","level":"INFO","msg":"lock released","table":"queen_migrations"}
Transaction Isolation Levels

Control transaction isolation levels for migrations to prevent race conditions and optimize performance.

Global Configuration

Set a default isolation level for all migrations:

import "database/sql"

config := &queen.Config{
    IsolationLevel: sql.LevelSerializable, // Strongest isolation
}
q := queen.NewWithConfig(driver, config)
Per-Migration Configuration

Override the global setting for specific migrations:

q.MustAdd(queen.M{
    Version:        "003",
    Name:           "critical_update",
    IsolationLevel: sql.LevelSerializable, // Override for this migration
    UpSQL:          "UPDATE accounts SET balance = balance - 100 WHERE id = 1",
    DownSQL:        "UPDATE accounts SET balance = balance + 100 WHERE id = 1",
})
Isolation Levels
Level Description PostgreSQL MySQL SQLite
LevelDefault Use database default ✅ READ COMMITTED ✅ REPEATABLE READ ✅ SERIALIZABLE
LevelReadUncommitted Allow dirty reads
LevelReadCommitted Prevent dirty reads ✅ (default)
LevelRepeatableRead Prevent non-repeatable reads ✅ (default)
LevelSerializable Full isolation ✅ (only option)
Use Cases

High Concurrency (READ COMMITTED):

// Fast bulk updates - allows other transactions to proceed
q.MustAdd(queen.M{
    Version:        "004",
    Name:           "bulk_data_migration",
    IsolationLevel: sql.LevelReadCommitted,
    UpSQL:          "UPDATE users SET migrated = true WHERE created_at < '2024-01-01'",
})

Critical Operations (SERIALIZABLE):

// Prevents race conditions in financial operations
q.MustAdd(queen.M{
    Version:        "005",
    Name:           "transfer_funds",
    IsolationLevel: sql.LevelSerializable,
    UpFunc: func(ctx context.Context, tx *sql.Tx) error {
        // Complex multi-table update requiring full isolation
        return nil
    },
})

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
ClickHouse ✅ Ready Latest Table + TTL
YandexDB (YDB) ✅ Ready 23.3+ Table + TTL (optimistic concurrency)
CockroachDB ✅ Ready - Advisory locks (PostgreSQL compatible)
MS SQL Server ✅ Ready 2012+ Application locks (sp_getapplock)
MongoDB 🔄 Planned - TBD
Oracle 🔄 Planned 11g+ DBMS_LOCK

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

CLI

Queen includes a command-line interface for managing migrations. Create your own binary that imports your migrations:

// cmd/migrate/main.go
package main

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

    "github.com/honeynil/queen/cli"
    "myapp/migrations"
)

func main() {
    cli.Run(migrations.Register)
}

Then use it:

# Build
go build -o migrate cmd/migrate/main.go

# Configure via environment variables
export QUEEN_DRIVER=postgres
export QUEEN_DSN="postgres://localhost/myapp?sslmode=disable"

# Or create .queen.yaml
cat > .queen.yaml <<EOF
naming:
  pattern: sequential-padded
  padding: 3
  enforce: true

development:
  driver: postgres
  dsn: postgres://localhost/myapp_dev?sslmode=disable

production:
  driver: postgres
  dsn: postgres://localhost/myapp?sslmode=disable
  require_confirmation: true
  require_explicit_unlock: true
EOF

# Use
./migrate up                       # Apply all pending migrations
./migrate down                     # Rollback last migration
./migrate status                   # Show migration status
./migrate create add_foo           # Create new migration (auto-numbered: 001, 002, ...)
./migrate validate                 # Validate checksums
./migrate --env production status  # Use production environment from config

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")
	ErrNameTooLong          = errors.New("migration name exceeds 63 characters")
	ErrInvalidMigrationName = errors.New("invalid migration name")
	ErrAlreadyApplied       = errors.New("migration already applied")
)

Common errors returned by Queen operations.

Functions

func IsValidMigrationName added in v0.2.0

func IsValidMigrationName(name string) bool

IsValidMigrationName checks if a migration name is valid.

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

	// Naming configures migration version naming validation.
	// Default: nil (no validation, for backward compatibility)
	Naming *NamingConfig

	// IsolationLevel sets the default transaction isolation level for all migrations.
	// Default: sql.LevelDefault (uses database default)
	//
	// Supported levels:
	//   - sql.LevelDefault: use database default
	//   - sql.LevelReadUncommitted: allow dirty reads
	//   - sql.LevelReadCommitted: prevent dirty reads (PostgreSQL default)
	//   - sql.LevelRepeatableRead: prevent non-repeatable reads (MySQL default)
	//   - sql.LevelSerializable: full isolation (SQLite default)
	//
	// Individual migrations can override this with their own IsolationLevel.
	IsolationLevel sql.IsolationLevel
}

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 with the specified isolation level.
	// If the function returns an error, the transaction is rolled back.
	// Otherwise, the transaction is committed.
	//
	// The isolationLevel parameter specifies the transaction isolation level:
	//   - sql.LevelDefault: use database default isolation level
	//   - sql.LevelReadUncommitted: allow dirty reads
	//   - sql.LevelReadCommitted: prevent dirty reads
	//   - sql.LevelRepeatableRead: prevent non-repeatable reads
	//   - sql.LevelSerializable: full isolation
	//
	// Note: Not all databases support all isolation levels. The driver should
	// validate compatibility and return an error if unsupported.
	Exec(ctx context.Context, isolationLevel sql.IsolationLevel, 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 Logger added in v0.2.0

type Logger interface {
	// InfoContext logs an informational message with structured fields.
	// Compatible with slog.Logger.InfoContext.
	InfoContext(ctx context.Context, msg string, args ...any)

	// WarnContext logs a warning message with structured fields.
	// Compatible with slog.Logger.WarnContext.
	WarnContext(ctx context.Context, msg string, args ...any)

	// ErrorContext logs an error message with structured fields.
	// Compatible with slog.Logger.ErrorContext.
	ErrorContext(ctx context.Context, msg string, args ...any)
}

Logger defines a structured logging interface compatible with slog.

This interface is intentionally compatible with *slog.Logger from the standard library, allowing direct usage of slog loggers without adapters.

Example using slog:

import "log/slog"

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

Example using custom logger:

type MyLogger struct{}

func (l *MyLogger) InfoContext(ctx context.Context, msg string, args ...any) {
    // Custom implementation
}

func (l *MyLogger) WarnContext(ctx context.Context, msg string, args ...any) {
    // Custom implementation
}

func (l *MyLogger) ErrorContext(ctx context.Context, msg string, args ...any) {
    // Custom implementation
}

q := queen.New(driver, queen.WithLogger(&MyLogger{}))

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

	// IsolationLevel sets the transaction isolation level for this migration.
	// Default: sql.LevelDefault (uses Config.IsolationLevel or database default)
	//
	// This overrides the global Config.IsolationLevel for this specific migration.
	//
	// Use cases:
	//   - Critical migrations requiring SERIALIZABLE isolation
	//   - Bulk data migrations that can use READ COMMITTED for better performance
	//   - Preventing race conditions during schema changes
	//
	// Example:
	//   queen.M{
	//       Version: "003",
	//       Name:    "critical_update",
	//       IsolationLevel: sql.LevelSerializable,
	//       UpSQL:   "UPDATE users SET ...",
	//   }
	IsolationLevel sql.IsolationLevel
	// 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 // Migration version (e.g., "001", "002")
	Name      string // Migration name (e.g., "create_users")
	Operation string // Operation being performed: "up", "down", "validate"
	Driver    string // Database driver name (e.g., "postgres", "mysql", "sqlite")
	Cause     error  // The underlying error that occurred
}

MigrationError wraps an error with migration context.

This structured error provides rich context for debugging migration failures, including which migration failed, what operation was being performed, and which database driver was in use.

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 MigrationPlan added in v0.2.0

type MigrationPlan struct {
	// Version is the unique version identifier of the migration.
	Version string `json:"version"`

	// Name is the human-readable name of the migration.
	Name string `json:"name"`

	// Direction indicates the migration direction: "up" or "down".
	Direction string `json:"direction"`

	// Status indicates whether the migration is pending, applied, or modified.
	Status string `json:"status"`

	// Type indicates the migration type: "sql", "go-func", or "mixed".
	Type MigrationType `json:"type"`

	// SQL contains the SQL that will be executed (if applicable).
	// Empty for Go function-only migrations.
	SQL string `json:"sql,omitempty"`

	// HasRollback indicates if the migration has a down migration.
	HasRollback bool `json:"has_rollback"`

	// IsDestructive indicates if the migration contains destructive operations.
	// Only applicable for down migrations.
	IsDestructive bool `json:"is_destructive"`

	// Checksum is the current checksum of the migration.
	Checksum string `json:"checksum"`

	// Warnings contains any warnings about this migration.
	// Examples: "No rollback defined", "Destructive operation", etc.
	Warnings []string `json:"warnings,omitempty"`
}

MigrationPlan represents a migration execution plan for dry-run mode. This is returned by Queen.DryRun() and Queen.Explain().

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 MigrationType added in v0.2.0

type MigrationType string

MigrationType represents the type of migration implementation.

const (
	// MigrationTypeSQL indicates the migration uses SQL (UpSQL/DownSQL).
	MigrationTypeSQL MigrationType = "sql"

	// MigrationTypeGoFunc indicates the migration uses Go functions (UpFunc/DownFunc).
	MigrationTypeGoFunc MigrationType = "go-func"

	// MigrationTypeMixed indicates the migration uses both SQL and Go functions.
	MigrationTypeMixed MigrationType = "mixed"
)

type NamingConfig added in v0.2.0

type NamingConfig struct {
	// Pattern specifies the naming pattern to enforce.
	// Default: NamingPatternNone (no validation).
	Pattern NamingPattern

	// Padding specifies the number of digits for sequential-padded pattern.
	// Only used when Pattern is NamingPatternSequentialPadded.
	// Default: 3 (generates 001, 002, 003, ...)
	Padding int

	// Enforce determines whether to return an error on validation failure.
	// If false, validation failures are logged as warnings but don't prevent migration.
	// Default: true
	Enforce bool
}

NamingConfig configures migration version naming validation.

func DefaultNamingConfig added in v0.2.0

func DefaultNamingConfig() *NamingConfig

DefaultNamingConfig returns the default naming configuration.

func (*NamingConfig) FindNextVersion added in v0.2.0

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

FindNextVersion finds the next version based on the pattern and existing versions. This is primarily used by CLI tools for auto-generating version numbers.

func (*NamingConfig) Validate added in v0.2.0

func (nc *NamingConfig) Validate(version string) error

Validate checks if a version string matches the configured naming pattern.

type NamingPattern added in v0.2.0

type NamingPattern string

NamingPattern defines the migration version naming convention.

const (
	// NamingPatternNone disables naming pattern validation (default for backward compatibility).
	NamingPatternNone NamingPattern = ""

	// NamingPatternSequential enforces sequential numbering: 1, 2, 3, ...
	NamingPatternSequential NamingPattern = "sequential"

	// NamingPatternSequentialPadded enforces padded sequential numbering: 001, 002, 003, ...
	// This is the recommended default for most projects.
	NamingPatternSequentialPadded NamingPattern = "sequential-padded"

	// NamingPatternSemver enforces semantic versioning: 1.0.0, 1.1.0, 2.0.0, ...
	NamingPatternSemver NamingPattern = "semver"
)

type Option added in v0.2.0

type Option func(*Queen)

Option configures a Queen instance.

func WithLogger added in v0.2.0

func WithLogger(logger Logger) Option

WithLogger sets a custom logger for the Queen instance.

The logger interface is compatible with *slog.Logger from the standard library, so you can pass slog loggers directly:

import "log/slog"

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

If no logger is configured, a no-op logger is used (no logging).

type Queen

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

Queen manages database migrations.

func New

func New(driver Driver, opts ...Option) *Queen

New creates a Queen instance with default configuration and optional settings.

Example:

q := queen.New(driver)

With logger:

import "log/slog"
q := queen.New(driver, queen.WithLogger(slog.Default()))

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) DryRun added in v0.2.0

func (q *Queen) DryRun(ctx context.Context, direction string, limit int) ([]MigrationPlan, error)

DryRun returns a migration execution plan without applying migrations.

Direction can be "up" or "down":

  • "up": shows pending migrations that would be applied
  • "down": shows applied migrations that could be rolled back

This is useful for:

  • Previewing what migrations will be applied before running them
  • CI/CD validation and checks
  • Understanding the current migration state

Example:

plans, err := q.DryRun(ctx, "up")
for _, plan := range plans {
    fmt.Printf("Will apply: %s - %s\n", plan.Version, plan.Name)
    if len(plan.Warnings) > 0 {
        fmt.Printf("  Warnings: %v\n", plan.Warnings)
    }
}

func (*Queen) Explain added in v0.2.0

func (q *Queen) Explain(ctx context.Context, version string) (*MigrationPlan, error)

Explain returns a detailed migration plan for a specific version.

This provides comprehensive information about a single migration, including its SQL, type, warnings, and current status.

Example:

plan, err := q.Explain(ctx, "001")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Migration: %s - %s\n", plan.Version, plan.Name)
fmt.Printf("Status: %s\n", plan.Status)
if plan.SQL != "" {
    fmt.Printf("SQL:\n%s\n", plan.SQL)
}

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
}

For thorough testing of each down migration, use TestRollback:

func TestMigrations(t *testing.T) {
    q := queen.NewTest(t, driver)
    q.MustAdd(queen.M{...})
    q.TestRollback() // Up -> Down (one by one) -> Up
}

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) TestRollback added in v0.2.0

func (th *TestHelper) TestRollback()

TestRollback verifies each down migration works correctly.

This test performs a complete migration cycle:

  1. Applies all migrations (Up)
  2. Rolls back each migration one by one (Down)
  3. Reapplies all migrations (Up)

This ensures:

  • Each Down migration executes without errors
  • Each Down migration properly undoes its Up counterpart
  • The database returns to a clean state after rollback

If any step fails, the test reports exactly which migration caused the failure.

Usage:

func TestMigrations(t *testing.T) {
    q := queen.NewTest(t, driver)
    q.MustAdd(queen.M{...})
    q.TestRollback() // Tests full Up -> Down -> Up cycle
}
Example

ExampleTestHelper_TestRollback demonstrates thorough testing of 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`,
		})

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

		// TestRollback performs a full cycle: Up -> Down (one by one) -> Up
		// This catches broken Down migrations that don't properly undo their Up
		q.TestRollback()

		fmt.Println("All down migrations work correctly")
	}

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

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

	return mock.New()
}

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
Package cli provides a command-line interface for Queen migrations.
Package cli provides a command-line interface for Queen migrations.
drivers
base
Package base provides common functionality for Queen database drivers.
Package base provides common functionality for Queen database drivers.
clickhouse
Package clickhouse provides a ClickHouse driver for Queen migrations.
Package clickhouse provides a ClickHouse driver for Queen migrations.
cockroachdb
Package cockroachdb provides a CockroachDB driver for Queen migrations.
Package cockroachdb provides a CockroachDB driver for Queen migrations.
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.
mssql
Package mssql provides a MS SQL Server driver for Queen migrations.
Package mssql provides a MS SQL Server driver for Queen migrations.
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.
ydb
Package ydb provides a YandexDB (YDB) driver for Queen migrations.
Package ydb provides a YandexDB (YDB) 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