Skip to content

go-gormigrate/gormigrate

Repository files navigation

Gormigrate

Latest Release Go Reference Go Report Card CI | Lint CI | Test

Gormigrate is a minimalistic migration helper forGorm. Gorm already has usefulmigrate functions,just misses proper schema versioning and migration rollback support.

IMPORTANT: If you need support to Gorm v1 (which uses github /jinzhu/gormas its import path), please import Gormigrate by using thegopkg.in/gormigrate.v1import path.

The current Gorm version (v2) is supported by using the github /go-gormigrate/gormigrate/v2import path as described in the documentation below.

Supported databases

It supports any of thedatabases Gorm supports:

  • MySQL
  • MariaDB
  • PostgreSQL
  • SQLite
  • Microsoft SQL Server
  • TiDB
  • Clickhouse

Usage

packagemain

import(
"log"

"github /go-gormigrate/gormigrate/v2"
"github /google/uuid"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)

funcmain() {
db,err:=gorm.Open(sqlite.Open("./data.db"),&gorm.Config{
Logger:logger.Default.LogMode(logger.Info),
})
iferr!=nil{
log.Fatal(err)
}

m:=gormigrate.New(db,gormigrate.DefaultOptions,[]*gormigrate.Migration{{
// create `users` table
ID:"201608301400",
Migrate:func(tx*gorm.DB)error{
// it's a good pratice to copy the struct inside the function,
// so side effects are prevented if the original struct changes during the time
typeuserstruct{
IDuuid.UUID`gorm: "type:uuid;primaryKey;uniqueIndex" `
Namestring
}
returntx.Migrator().CreateTable(&user{})
},
Rollback:func(tx*gorm.DB)error{
returntx.Migrator().DropTable("users")
},
}, {
// add `age` column to `users` table
ID:"201608301415",
Migrate:func(tx*gorm.DB)error{
// when table already exists, define only columns that are about to change
typeuserstruct{
Ageint
}
returntx.Migrator().AddColumn(&user{},"Age")
},
Rollback:func(tx*gorm.DB)error{
typeuserstruct{
Ageint
}
returndb.Migrator().DropColumn(&user{},"Age")
},
}, {
// create `organizations` table where users belong to
ID:"201608301430",
Migrate:func(tx*gorm.DB)error{
typeorganizationstruct{
IDuuid.UUID`gorm: "type:uuid;primaryKey;uniqueIndex" `
Namestring
Addressstring
}
iferr:=tx.Migrator().CreateTable(&organization{});err!=nil{
returnerr
}
typeuserstruct{
OrganizationIDuuid.UUID`gorm: "type:uuid" `
}
returntx.Migrator().AddColumn(&user{},"OrganizationID")
},
Rollback:func(tx*gorm.DB)error{
typeuserstruct{
OrganizationIDuuid.UUID`gorm: "type:uuid" `
}
iferr:=db.Migrator().DropColumn(&user{},"OrganizationID");err!=nil{
returnerr
}
returntx.Migrator().DropTable("organizations")
},
}})

iferr=m.Migrate();err!=nil{
log.Fatalf("Migration failed: %v",err)
}
log.Println("Migration did run successfully")
}

Having a separate function for initializing the schema

If you have a lot of migrations, it can be a pain to run all them, as example, when you are deploying a new instance of the app, in a clean database. To prevent this, you can set a function that will run if no migration was run before (in a new clean database). Remember to create everything here, all tables, foreign keys and what more you need in your app.

typeOrganizationstruct{
gorm.Model
Namestring
Addressstring
}

typeUserstruct{
gorm.Model
Namestring
Ageint
OrganizationIDuint
}

m:=gormigrate.New(db,gormigrate.DefaultOptions,[]*gormigrate.Migration{
// your migrations here
})

m.InitSchema(func(tx*gorm.DB)error{
err:=tx.AutoMigrate(
&Organization{},
&User{},
// all other tables of you app
)
iferr!=nil{
returnerr
}

iferr:=tx.Exec("ALTER TABLE users ADD CONSTRAINT fk_users_organizations FOREIGN KEY (organization_id) REFERENCES organizations (id)").Error;err!=nil{
returnerr
}
// all other constraints, indexes, etc...
returnnil
})

Options

This is the options struct, in case you don't want the defaults:

typeOptionsstruct{
// TableName is the migration table.
TableNamestring
// IDColumnName is the name of column where the migration id will be stored.
IDColumnNamestring
// IDColumnSize is the length of the migration id column
IDColumnSizeint
// UseTransaction makes Gormigrate execute migrations inside a single transaction.
// Keep in mind that not all databases support DDL commands inside transactions.
UseTransactionbool
// ValidateUnknownMigrations will cause migrate to fail if there's unknown migration
// IDs in the database
ValidateUnknownMigrationsbool
}

Who is Gormigrate for?

Gormigrate was born to be a simple and minimalistic migration tool for small projects that usesGorm.You may want to take a look at more advanced solutions likegolang-migrate/migrate if you plan to scale.

Be aware that Gormigrate has no builtin lock mechanism, so if you're running it automatically and have a distributed setup (i.e. more than one executable running at the same time), you might want to use a distributed lock/mutex mechanismto prevent race conditions while running migrations.

Contributing

To run integration tests, some preparations are needed. Please ensure you havetaskanddockerinstalled. Then:

  1. Ensure target or all databases are available and ready to accept connections. You can start databases locally withtask docker:compose:up
  2. Copyintegration-test/.example.envasintegration-test/.envand adjust the database connection ports and credentials when needed.
  3. Run integration test for single database or for all
#run test for MySQL
task test:mysql

#run test for MariaDB
task test:mariadb

#run test for PostgreSQL
task test:postgres

#run test for SQLite
task test:sqlite

#run test for Microsoft SQL Server
task test:sqlserver

#run test for all databases
task test:all

Alternatively, you can run everything in one step:task docker:test