DB migrations в Golang

github.com/golang-migrate/migrate/v4. Работает как cli и как библиотека для Go.

CLI

Создать новую миграцию:

migrate create -ext sql -seq -dir db/migrations create_token_table

Где -seq нужен чтобы миграции создавались с номерами вместо дат.

Применить миграцию к БД:

migrate --database sqlite3://auth.db --path db/migrations up

Golang

import (
	"github.com/golang-migrate/migrate/v4"
	_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
	_ "github.com/golang-migrate/migrate/v4/source/file"
)
 
type SQLiteStore struct{}
 
func NewSQLiteStore() (*SQLiteStore, error) {
	m, err := migrate.New("file://db/migrations", "sqlite3://auth.db")
	if err != nil {
		return nil, err
	}
 
	err = m.Up()
	if err != nil && err.Error() != "no change" {
		return nil, err
	}
	var ss SQLiteStore
	return &ss, nil
}

Следует помнить, что метод Up возвращает “no change” как ошибку! Её надо обрабатывать в явном виде.

Пример с embed миграциями

Так же в этом примере показано как использовать существующее соединение с БД, вместо формирования новой строки подключения.

//go:embed migrations/pg/*
var migrations embed.FS
 
type PgStorage struct {
	*sqlx.DB
}
 
func NewPgStorage(conf config.Db) *PgStorage {
	db, err := otelsqlx.Open("pgx", fmt.Sprintf("user=%s password=%s host=%s port=%d dbname=%s", conf.User, conf.Pass, conf.Host, conf.Port, conf.Name), otelsql.WithAttributes(semconv.DBSystemPostgreSQL))
	if err != nil {
		log.Fatal(err)
	}
	d, err := iofs.New(migrations, "migrations/pg")
	if err != nil {
		log.Fatal(err)
	}
 
	driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
	if err != nil {
		log.Fatal(err)
	}
	m, err := migrate.NewWithInstance("iofs", d, "postgres", driver)
	if err != nil {
		log.Fatal(err)
	}
	err = m.Up()
	if err != nil && err.Error() != "no change" {
		log.Fatal(err)
	}
	return &PgStorage{db}
}