schemer

package
v0.16.0 Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

README

Schemer Package

The schemer package provides a clean API for managing database migrations with automatic schema injection and tracking.

Overview

The schemer package offers a simplified migration management system that:

  • Automatic Schema Injection: No need to manually register migrations with schema
  • Clean Interface-based API: Uses SchemerInterface and SchemerImplementation pattern
  • Context Support: All operations support context for cancellation and timeout handling
  • Migration Tracking: Automatically tracks executed migrations in a migration_tracker table
  • Flexible Rollback: Support for rolling back by steps or by batch

Installation

import "github.com/dracory/neat/database/schemer"

Quick Start

package main

import (
    "context"
    "github.com/dracory/neat"
    contractsschema "github.com/dracory/neat/contracts/database/schema"
    "github.com/dracory/neat/database/schemer"
)

func main() {
    db, _ := neat.NewFromDSN("sqlite://./app.db")
    defer db.Close()

    // Create schemer instance
    schemer := schemer.NewSchemer(db)

    // Add migrations
    schemer.AddMigration(&CreateUsersTable{})
    schemer.AddMigration(&CreatePostsTable{})

    // Run migrations
    ctx := context.Background()
    if err := schemer.Up(ctx); err != nil {
        log.Fatal(err)
    }
}

API Reference

SchemerInterface
type SchemerInterface interface {
    AddMigration(migration contractsschema.MigrationInterface) error
    AddMigrations(migrations []contractsschema.MigrationInterface) error
    Up(ctx context.Context) error
    Down(ctx context.Context) error
    RollbackSteps(ctx context.Context, steps int) error
    RollbackToBatch(ctx context.Context, batch int) error
    Status() ([]MigrationStatus, error)
    Fresh(ctx context.Context) error
    Reset(ctx context.Context) error
    SetTransactionsEnabled(enabled bool)
    SetTransactionIsolationLevel(level string)
}
Methods
AddMigration

Adds a single migration to the schemer instance.

schemer.AddMigration(&CreateUsersTable{})
AddMigrations

Adds multiple migrations at once.

schemer.AddMigrations([]contractsschema.MigrationInterface{
    &CreateUsersTable{},
    &CreatePostsTable{},
})
Up

Runs all pending migrations. Automatically creates the migration_tracker table if it doesn't exist.

ctx := context.Background()
err := schemer.Up(ctx)
Down

Rolls back the last migration.

ctx := context.Background()
err := schemer.Down(ctx)
RollbackSteps

Rolls back the specified number of migrations.

ctx := context.Background()
err := schemer.RollbackSteps(ctx, 3) // Rollback last 3 migrations
RollbackToBatch

Rolls back all migrations to the specified batch.

ctx := context.Background()
err := schemer.RollbackToBatch(ctx, 20240615120000)
Status

Returns the current status of all migrations.

status, err := schemer.Status()
for _, s := range status {
    fmt.Printf("Migration: %s - State: %s\n", s.ID, s.State)
}
Fresh

Drops all tables except migration_tracker and clears the tracker.

ctx := context.Background()
err := schemer.Fresh(ctx)
Reset

Rolls back all migrations.

ctx := context.Background()
err := schemer.Reset(ctx)
SetTransactionsEnabled

Enables or disables transaction wrapping for migration operations. Transactions are enabled by default for safety.

schemer.SetTransactionsEnabled(true)  // Enable transactions (default)
schemer.SetTransactionsEnabled(false) // Disable transactions for large migrations
SetTransactionIsolationLevel

Sets the transaction isolation level for migration operations.

schemer.SetTransactionIsolationLevel("SERIALIZABLE")
schemer.SetTransactionIsolationLevel("READ COMMITTED")

Supported isolation levels:

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE
  • SNAPSHOT

Migration Implementation

Migrations must implement the MigrationInterface from the contracts package:

import (
    contractsschema "github.com/dracory/neat/contracts/database/schema"
    "github.com/dracory/neat/database/schema"
)

type CreateUsersTable struct {
    schema.BaseMigration
}

func (m *CreateUsersTable) Signature() string {
    return "2024_06_15_120000_create_users_table"
}

func (m *CreateUsersTable) Description() string {
    return "Creates users table"
}

func (m *CreateUsersTable) Up() error {
    return m.GetSchema().Create("users", func(blueprint contractsschema.Blueprint) {
        blueprint.ID()
        blueprint.String("name")
        blueprint.String("email")
        blueprint.Timestamps()
    })
}

func (m *CreateUsersTable) Down() error {
    return m.GetSchema().DropIfExists("users")
}

Migration Tracking

The schemer automatically tracks migrations in a migration_tracker table with the following structure:

type MigrationTracker struct {
    ID          string    // Migration signature
    Batch       int       // Batch number (timestamp)
    Description string    // Migration description
    StartedAt   time.Time // When migration started
    CompletedAt time.Time // When migration finished
}

Transaction Support

The schemer package supports transaction wrapping for safe migration execution. Transactions are enabled by default to ensure atomic execution.

Enabling/Disabling Transactions
schemer := schemer.NewSchemer(db)

// Transactions are enabled by default
schemer.SetTransactionsEnabled(true)

// Disable for large migrations or specific needs
schemer.SetTransactionsEnabled(false)
Transaction Isolation Levels
schemer.SetTransactionIsolationLevel("SERIALIZABLE")

Supported isolation levels:

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE
  • SNAPSHOT
Note on Current Implementation

Transaction wrapping is currently disabled by default pending verification of schema transaction detection. The infrastructure is in place and can be enabled once schema transaction behavior is properly tested.

See examples/schemer-transactions for a complete example of transaction control usage.

Migration Status

The Status() method returns MigrationStatus objects:

type MigrationStatus struct {
    ID          string    `json:"id"`
    Description string    `json:"description"`
    Batch       int       `json:"batch"`
    StartedAt   time.Time `json:"started_at"`
    CompletedAt time.Time `json:"completed_at"`
    State       string    `json:"state"` // "pending", "completed", "failed"
}

Best Practices

  1. Migration Naming: Use timestamp-based signatures for ordering

    "2024_06_15_120000_create_users_table"
    
  2. Idempotent Up Methods: Check if resources exist before creating

    func (m *CreateUsersTable) Up() error {
        if m.GetSchema().HasTable("users") {
            return nil
        }
        // Create table
    }
    
  3. Context Usage: Always use context for production applications

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    err := schemer.Up(ctx)
    
  4. Error Handling: Handle migration errors appropriately

    if err := schemer.Up(ctx); err != nil {
        log.Fatalf("Migration failed: %v", err)
    }
    

Migration from Old System

If you're migrating from the old schema.NewMigrationManager:

Before:

schema := db.Schema()
schema.Register(migrations)
manager := schema.NewMigrationManager(db)
manager.Run(migrations)

After:

schemer := schemer.NewSchemer(db)
schemer.AddMigrations(migrations)
schemer.Up(context.Background())

Examples

See the examples/schemer-migrations directory for complete examples of using the schemer package.

Testing

The schemer package includes comprehensive tests. Run them with:

go test ./database/schemer/...

Notes

  • The schemer automatically creates the migration_tracker table on first run
  • Migrations are executed in the order they are added
  • Already-run migrations are automatically skipped
  • Schema is automatically injected into each migration before execution

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ValidateMigrationSignature

func ValidateMigrationSignature(signature string, format SignatureFormat) error

ValidateMigrationSignature validates that a migration signature follows the specified format.

Supported formats:

  • YYYY_MM_DD_HHMM_description (for datetime format)
  • YYYY_MM_DD_NNN_description (for date format)
  • unix_timestamp_description (for unix format)
  • custom (no prefix format restriction, only length and non-empty)

Business Logic:

  • Enforces maximum length of 255 characters
  • Rejects empty signatures
  • For "custom" format: only validates length and non-empty
  • For other formats: requires at least 5 underscore-separated parts (or 2 for unix)
  • Validates date part (YYYY_MM_DD) is a valid calendar date
  • Validates time part (HHMM) or sequence part (NNN) based on format
  • Validates description exists and is within length limits

func ValidateTableName

func ValidateTableName(name string) error

ValidateTableName ensures the table name contains only safe characters. Exported to allow external validation of table names before creating a schemer instance.

Types

type MigrationStatus

type MigrationStatus struct {
	ID          string    `json:"id"`
	Description string    `json:"description"`
	Batch       int       `json:"batch"`
	StartedAt   time.Time `json:"started_at"`
	CompletedAt time.Time `json:"completed_at"`
	State       string    `json:"state"` // "pending", "completed", "failed"
}

MigrationStatus represents the status of a migration returned to users This is a DTO/response type derived from MigrationTracker data

type MigrationTracker

type MigrationTracker struct {
	ID          string    // The migration signature (e.g., "2024_06_15_120000_create_users_table")
	Batch       int       // Timestamp ID (YYYYMMDDHHMMSS). Groups the run
	Description string    // The migration description from Description() method
	StartedAt   time.Time // When the migration started
	CompletedAt time.Time // When the migration finished
}

MigrationTracker represents a migration record stored in the migration_tracker table This is the database model/entity used for persistence

type SchemerImplementation

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

SchemerImplementation handles execution and tracking of interface-based migrations

func (*SchemerImplementation) AddMigration

AddMigration adds a new migration to the list

func (*SchemerImplementation) AddMigrations

func (s *SchemerImplementation) AddMigrations(migrations []contractsschema.MigrationInterface) error

AddMigrations adds multiple migrations to the runner

func (*SchemerImplementation) Down

Down rolls back the last migration

func (*SchemerImplementation) Fresh

Fresh drops all tables and re-runs migrations

func (*SchemerImplementation) Reset

Reset rolls back and re-runs all migrations

func (*SchemerImplementation) RollbackSteps

func (s *SchemerImplementation) RollbackSteps(ctx context.Context, steps int) error

RollbackSteps rolls back the specified number of migrations

func (*SchemerImplementation) RollbackToBatch

func (s *SchemerImplementation) RollbackToBatch(ctx context.Context, batch int) error

RollbackToBatch rolls back all migrations to the specified batch

func (*SchemerImplementation) SetSignatureValidation

func (s *SchemerImplementation) SetSignatureValidation(enabled bool, format SignatureFormat)

SetSignatureValidation enables or disables signature format validation. When enabled, each migration signature is validated against the specified format before execution. Default is disabled.

func (*SchemerImplementation) SetTableName

func (s *SchemerImplementation) SetTableName(name string) error

SetTableName sets the name of the migration tracking table. The name is validated to prevent SQL injection.

func (*SchemerImplementation) SetTransactionIsolationLevel

func (s *SchemerImplementation) SetTransactionIsolationLevel(level string)

SetTransactionIsolationLevel sets the transaction isolation level for migration operations

func (*SchemerImplementation) SetTransactionsEnabled

func (s *SchemerImplementation) SetTransactionsEnabled(enabled bool)

SetTransactionsEnabled enables or disables transaction wrapping for migration operations

func (*SchemerImplementation) Status

func (s *SchemerImplementation) Status() ([]MigrationStatus, error)

Status returns migration status

func (*SchemerImplementation) Up

Up runs all pending migrations Automatically injects schema into each migration before execution

type SchemerInterface

type SchemerInterface interface {
	AddMigration(migration contractsschema.MigrationInterface) error
	AddMigrations(migrations []contractsschema.MigrationInterface) error
	Up(ctx context.Context) error
	Down(ctx context.Context) error
	RollbackSteps(ctx context.Context, steps int) error
	RollbackToBatch(ctx context.Context, batch int) error
	Status() ([]MigrationStatus, error)
	Fresh(ctx context.Context) error
	Reset(ctx context.Context) error
	SetTransactionsEnabled(enabled bool)
	SetTransactionIsolationLevel(level string)
	SetTableName(name string) error
	SetSignatureValidation(enabled bool, format SignatureFormat)
}

SchemerInterface defines the contract for migration management

func NewSchemer

func NewSchemer(db *database.Database) SchemerInterface

NewSchemer creates a new SchemerImplementation instance Takes neat db instance as dependency, extracts schema and orm internally

type SignatureFormat

type SignatureFormat string

SignatureFormat defines the format for migration signatures

const (
	// SignatureFormatDateTime uses timestamp-based format (default)
	// Example: 2026_06_14_1200_create_users_table
	SignatureFormatDateTime SignatureFormat = "datetime"

	// SignatureFormatDate uses sequence-based format
	// Example: 2026_06_14_001_create_users_table
	SignatureFormatDate SignatureFormat = "date"

	// SignatureFormatUnix uses unix timestamp format (legacy)
	// Example: 1717080000_create_users_table
	SignatureFormatUnix SignatureFormat = "unix"

	// SignatureFormatCustom uses no prefix format restriction
	SignatureFormatCustom SignatureFormat = "custom"
)

Jump to

Keyboard shortcuts

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