gormigrate

repository·master·Indexed 22 days ago

https://github.com/go-gormigrate/gormigrate

A minimalistic migration helper for Gorm that adds schema versioning and rollback support. It allows developers to define migrations with unique IDs, apply changes via Migrate functions, and undo them via Rollback functions. It supports any Gorm-compatible database, including MySQL, PostgreSQL, SQLite, and SQL Server, and provides utilities like InitSchema for bulk initialization of empty databases.

Tokens
3.4K
Snippets
11
Records
15
Agent score
78%

What's inside gormigrate

  1. Important considerations for distributed environments

    master
    Gormigrate does not include a built-in locking mechanism. If you are running migrations automatically in a distributed setup (e.g., multiple instances of your application running simultaneously), you must implement a distributed lock/mutex mechanism (such as using Redis) to prevent race conditions during the migration process.
  2. Run migrations with Gormigrate

    master

    Gormigrate provides a minimalistic way to handle schema versioning and rollbacks for Gorm. You initialize a migration manager using gormigrate.New, passing your *gorm.DB instance, an options struct, and a slice of *gormigrate.Migration objects. Each migration requires a unique ID, a Migrate function to apply changes, and a Rollback function to undo them.

    Best Practice: Define your model structs inside the Migrate and Rollback functions. This prevents side effects if your global application structs change over time, which would otherwise break old migrations.

    package main
    
    import (
    	"log"
    
    	"github.com/go-gormigrate/gormigrate/v2"
    	"github.com/google/uuid"
    	"gorm.io/driver/sqlite"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    )
    
    func main() {
    	db, err := gorm.Open(sqlite.Open("./data.db"), &gorm.Config{
    		Logger: logger.Default.LogMode(logger.Info),
    	})
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	m := gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{{
    		// create `users` table
    		ID: "201608301400",
    		Migrate: func(tx *gorm.DB) error {
    			type user struct {
    				ID   uuid.UUID `gorm:"type:uuid;primaryKey;uniqueIndex"`
    				Name string
    			}
    			return tx.Migrator().CreateTable(&user{})
    		},
    		Rollback: func(tx *gorm.DB) error {
    			return tx.Migrator().DropTable("users")
    		},
    	}}, {
    		// add `age` column to `users` table
    		ID: "201608301415",
    		Migrate: func(tx *gorm.DB) error {
    			type user struct {
    				Age int
    			}
    			return tx.Migrator().AddColumn(&user{}, "Age")
    		},
    		Rollback: func(tx *gorm.DB) error {
    			type user struct {
    				Age int
    			}
    			return tx.Migrator().DropColumn(&user{}, "Age")
    		},
    	}}) 
    
    	if err := m.Migrate(); err != nil {
    		log.Fatalf("Migration failed: %v", err)
    	}
    	log.Println("Migration did run successfully")
    }
  3. Install Gormigrate for Gorm v2

    master

    To use Gormigrate with Gorm v2, import the package using the following path:

    github.com/go-gormigrate/gormigrate/v2

    If you are using the older Gorm v1 (which uses github.com/jinzhu/gorm), you must use the gopkg.in/gormigrate.v1 import path instead.

    import "github.com/go-gormigrate/gormigrate/v2"
  4. Set up integration testing environments with Docker Compose

    master

    The integration-test/docker-compose.yml file provides a pre-configured environment for running integration tests against multiple supported databases. You can use this file to spin up local instances of PostgreSQL, MySQL, MariaDB, and Microsoft SQL Server to verify your migrations.

    services:
      postgres:
        image: postgres:18-alpine
        ports:
          - 5432:5432
        environment:
          POSTGRES_DB: gormigrate
          POSTGRES_USER: gormigrate
          POSTGRES_PASSWORD: gormigrate
    
      mysql:
        image: mysql:9
        ports:
          - 3306:3306
        environment:
          MYSQL_DATABASE: gormigrate
          MYSQL_ROOT_PASSWORD: gormigrate
          MYSQL_USER: gormigrate
          MYSQL_PASSWORD: gormigrate
    
      mariadb:
        image: mariadb:12
        ports:
          - 3307:3306
        environment:
          MARIADB_DATABASE: gormigrate
          MARIADB_ROOT_PASSWORD: gormigrate
          MARIADB_USER: gormigrate
          MARIADB_PASSWORD: gormigrate
    
      sqlserver:
        image: mcr.microsoft.com/mssql/server:2025-latest
        ports:
          - 1433:1433
        environment:
          ACCEPT_EULA: Y
          MSSQL_SA_PASSWORD: LoremIpsum86
  5. Initialize a clean schema using InitSchema

    master

    When deploying to a new, empty database, running a long list of individual migrations can be inefficient. You can use m.InitSchema(func(tx *gorm.DB) error { ... }) to define a single function that runs only if no previous migrations have been recorded in the database. This is ideal for performing a bulk AutoMigrate or setting up complex constraints and foreign keys in one step.

    m.InitSchema(func(tx *gorm.DB) error {
    	err := tx.AutoMigrate(
    		&Organization{},
    		&User{},
    	)
    	if err != nil {
    		return err
    	}
    
    	if err := tx.Exec("ALTER TABLE users ADD CONSTRAINT fk_users_organizations FOREIGN KEY (organization_id) REFERENCES organizations (id)").Error; err != nil {
    		return err
    	}
    	return nil
    })
  6. Configure Gormigrate with Options

    master

    Use the Options struct to customize how migrations are stored and executed. If you don't provide options, DefaultOptions is used.

    FieldTypeDescription
    TableNamestringThe name of the table where migration IDs are stored (default: "migrations")
    IDColumnNamestringThe name of the column storing the migration ID (default: "id")
    IDColumnSizeintThe length of the ID column (default: 255)
    UseTransactionboolIf true, executes migrations inside a single transaction (Note: not all DBs support DDL in transactions)
    ValidateUnknownMigrationsboolIf true, Migrate() fails if IDs exist in the database that are not present in your code.
    options := &gormigrate.Options{
    	TableName:     "my_migrations",
    	UseTransaction: true,
    }
  7. Configure Gormigrate via Options

    master

    You can customize the behavior of the migration manager by providing a custom Options struct to gormigrate.New.

    type Options struct {
    	// TableName is the migration table.
    	TableName string
    	// IDColumnName is the name of column where the migration id will be stored.
    	IDColumnName string
    	// IDColumnSize is the length of the migration id column
    	IDColumnSize int
    	// UseTransaction makes Gormigrate execute migrations inside a single transaction.
    	// Keep in mind that not all databases support DDL commands inside transactions.
    	UseTransaction bool
    	// ValidateUnknownMigrations will cause migrate to fail if there's unknown migration
    	// IDs in the database
    	ValidateUnknownMigrations bool
    }
  8. Initialize a clean schema with InitSchema()

    master

    If you are working with a completely empty database, you can use InitSchema(fn) to define a function that runs once to set up the initial state (e.g., creating all base tables and foreign keys).

    InitSchema is only executed if no migrations have been recorded in the migration table yet. This prevents running a full migration suite on an existing database that was already initialized.

    g.InitSchema(func(tx *gorm.DB) error {
    	return tx.AutoMigrate(&User{}, &Account{})
    })
    
    // When Migrate() is called on a fresh DB, it runs InitSchema first
    err := g.Migrate()
  9. Initialize Gormigrate with New()

    master

    To use Gormigrate, call New() with a *gorm.DB instance, an optional *Options pointer, and a slice of *Migration objects. If options is nil, it defaults to DefaultOptions.

    import (
    	"gorm.io/gorm"
    	"github.com/go-gormigrate/go-gormigrate/gormigrate"
    )
    
    // ... setup db ...
    
    migrations := []*gormigrate.Migration{
    	{
    		ID: "20230101000000",
    		Migrate: func(tx *gorm.DB) error {
    			return tx.AutoMigrate(&User{})
    		},
    	},
    }
    
    g := gormigrate.New(db, nil, migrations)
  10. Rollback migrations with RollbackLast() and RollbackTo()

    master

    To undo changes, use RollbackLast() to undo the single most recent migration, or RollbackTo(migrationID) to undo all migrations occurring after the specified migrationID (the target ID itself is NOT rolled back).

    Note: A migration can only be rolled back if a Rollback function was provided in its definition. If missing, it returns ErrRollbackImpossible.

    // Undo the very last migration that was run
    err := g.RollbackLast()
    
    // Undo migrations until we reach this ID
    err := g.RollbackTo("20230101000000")
  11. Run migrations with Migrate() and MigrateTo()

    master

    Use Migrate() to execute all pending migrations in the order they are defined. Use MigrateTo(migrationID) to execute all pending migrations up to and including the specified migrationID.

    // Run all pending migrations
    err := g.Migrate()
    
    // Run migrations up to a specific ID
    err := g.MigrateTo("202301021504")