migrate

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Feb 26, 2025 License: MIT Imports: 10 Imported by: 0

README

migrate

GoDoc Widget

migrate package provides functionality for applying database migrations in your Go applications. It leverages github.com/rubenv/sql-migrate under the hood, ensuring a reliable and consistent approach to managing database schema changes.

It offers two primary approaches for defining your migrations:

  • Embedded SQL Migrations: Store your migrations as plain SQL files (with separate .up.sql and .down.sql files) and embed them into your Go binary using Go's built-in embed package. This approach is straightforward and keeps your SQL scripts separate from your application code.
  • Programmatic SQL Migrations: Define your migrations directly in Go code. This method is more suitable when you require additional customization or more control over your migrations. It lets you write migrations as Go functions, while still leveraging SQL commands.

Usage

The examples below show how to define migrations for creating a "users" table and a "notes" table.

Running Embedded SQL Migrations

You can embed your SQL migration files into your binary with Go's embed package. The following example (from the examples/embedded-sql-migrations directory) demonstrates how to load and execute embedded migrations:

package main

import (
	"database/sql"
	"embed"
	"flag"
	"fmt"
	stdlog "log"
	"os"

	"github.com/acronis/go-appkit/log"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/jackc/pgx/v5/stdlib"
	_ "github.com/lib/pq"

	"github.com/acronis/go-dbkit"
	"github.com/acronis/go-dbkit/migrate"
)

//go:embed mysql/*.sql
//go:embed postgres/*.sql
var migrationFS embed.FS

func main() {
	if err := runMigrations(); err != nil {
		stdlog.Fatal(err)
	}
}

func runMigrations() error {
	var migrateDown bool
	flag.BoolVar(&migrateDown, "down", false, "migrate down")
	var driverName string
	flag.StringVar(&driverName, "driver", "", "driver name, supported values: mysql, postgres, pgx")
	flag.Parse()

	migrationDirection := migrate.MigrationsDirectionUp
	if migrateDown {
		migrationDirection = migrate.MigrationsDirectionDown
	}

	dialect, migrationDirName, err := parseDialectFromDriver(driverName)
	if err != nil {
		return fmt.Errorf("parse dialect: %w", err)
	}

	dbConn, err := sql.Open(driverName, os.Getenv("DB_DSN"))
	if err != nil {
		return fmt.Errorf("open database: %w", err)
	}

	logger, loggerClose := log.NewLogger(&log.Config{Output: log.OutputStderr, Level: log.LevelInfo})
	defer loggerClose()

	migrationManager, err := migrate.NewMigrationsManager(dbConn, dialect, logger)
	if err != nil {
		return err
	}
	migrations, err := migrate.LoadAllEmbedFSMigrations(migrationFS, migrationDirName)
	if err != nil {
		return fmt.Errorf("make embed fs migrations: %w", err)
	}
	return migrationManager.Run(migrations, migrationDirection)
}

func parseDialectFromDriver(driverName string) (dialect dbkit.Dialect, migrationDirName string, err error) {
	switch driverName {
	case "mysql":
		return dbkit.DialectMySQL, "mysql", nil
	case "postgres":
		return dbkit.DialectPostgres, "postgres", nil
	case "pgx":
		return dbkit.DialectPgx, "postgres", nil
	default:
		return "", "", fmt.Errorf("unknown driver name: %s", driverName)
	}
}
Defining SQL Migrations in Go Files

For greater control or when you need to include custom logic, you can define your migrations directly in Go. This approach is demonstrated in the following example from the examples/go-sql-migrations directory. The example includes two migration files that define the creation and deletion of the "users" and "notes" tables.

Main Application

package main

import (
	"database/sql"
	"flag"
	"fmt"
	stdlog "log"
	"os"

	"github.com/acronis/go-appkit/log"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/jackc/pgx/v5/stdlib"
	_ "github.com/lib/pq"

	"github.com/acronis/go-dbkit"
	"github.com/acronis/go-dbkit/migrate"
)

func main() {
	if err := runMigrations(); err != nil {
		stdlog.Fatal(err)
	}
}

func runMigrations() error {
	var migrateDown bool
	flag.BoolVar(&migrateDown, "down", false, "migrate down")
	var driverName string
	flag.StringVar(&driverName, "driver", "", "driver name, supported values: mysql, postgres, pgx")
	flag.Parse()

	migrationDirection := migrate.MigrationsDirectionUp
	if migrateDown {
		migrationDirection = migrate.MigrationsDirectionDown
	}

	dialect, err := parseDialectFromDriver(driverName)
	if err != nil {
		return fmt.Errorf("parse dialect: %w", err)
	}

	dbConn, err := sql.Open(driverName, os.Getenv("DB_DSN"))
	if err != nil {
		return fmt.Errorf("open database: %w", err)
	}

	logger, loggerClose := log.NewLogger(&log.Config{Output: log.OutputStderr, Level: log.LevelInfo})
	defer loggerClose()

	migrationManager, err := migrate.NewMigrationsManager(dbConn, dialect, logger)
	if err != nil {
		return err
	}
	return migrationManager.Run([]migrate.Migration{
		NewMigration0001CreateUsersTable(dialect),
		NewMigration0002CreateNotesTable(dialect),
	}, migrationDirection)
}

func parseDialectFromDriver(driverName string) (dbkit.Dialect, error) {
	switch driverName {
	case "mysql":
		return dbkit.DialectMySQL, nil
	case "postgres":
		return dbkit.DialectPostgres, nil
	case "pgx":
		return dbkit.DialectPgx, nil
	default:
		return "", fmt.Errorf("unknown driver name: %s", driverName)
	}
}

Migration for Creating the Users Table

package main

import (
	"github.com/acronis/go-dbkit"
	"github.com/acronis/go-dbkit/migrate"
)

const migration0001CreateUsersTableUpMySQL = `
CREATE TABLE users (
    id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL
);
`

const migration0001CreateUsersTableUpPostgres = `
CREATE TABLE users (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name VARCHAR(255) NOT NULL
);
`

const migration0001CreateUsersTableDown = `
DROP TABLE users;
`

type Migration0001CreateUsersTable struct {
	*migrate.NullMigration
}

func NewMigration0001CreateUsersTable(dialect dbkit.Dialect) *Migration0001CreateUsersTable {
	return &Migration0001CreateUsersTable{&migrate.NullMigration{Dialect: dialect}}
}

func (m *Migration0001CreateUsersTable) ID() string {
	return "0001_create_users_table"
}

func (m *Migration0001CreateUsersTable) UpSQL() []string {
	switch m.Dialect {
	case dbkit.DialectMySQL:
		return []string{migration0001CreateUsersTableUpMySQL}
	case dbkit.DialectPgx, dbkit.DialectPostgres:
		return []string{migration0001CreateUsersTableUpPostgres}
	}
	return nil
}

func (m *Migration0001CreateUsersTable) DownSQL() []string {
	switch m.Dialect {
	case dbkit.DialectMySQL, dbkit.DialectPgx, dbkit.DialectPostgres:
		return []string{migration0001CreateUsersTableDown}
	}
	return nil
}

Migration for Creating the Notes Table

package main

import (
	"github.com/acronis/go-dbkit"
	"github.com/acronis/go-dbkit/migrate"
)

const migration0002CreateNotesTableUpMySQL = `
CREATE TABLE notes (
    id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
    content TEXT,
    user_id BIGINT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
`

const migration0002CreateNotesTableUpPostgres = `
CREATE TABLE notes (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    content TEXT,
    user_id BIGINT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
`

const migration0002CreateNotesTableDown = `
DROP TABLE notes;
`

func NewMigration0002CreateNotesTable(dialect dbkit.Dialect) *migrate.CustomMigration {
	var upSQL []string
	var downSQL []string
	switch dialect {
	case dbkit.DialectMySQL:
		upSQL = []string{migration0002CreateNotesTableUpMySQL}
		downSQL = []string{migration0002CreateNotesTableDown}
	case dbkit.DialectPgx, dbkit.DialectPostgres:
		upSQL = []string{migration0002CreateNotesTableUpPostgres}
		downSQL = []string{migration0002CreateNotesTableDown}
	}
	return migrate.NewCustomMigration("0002_create_notes_table", upSQL, downSQL, nil, nil)
}

License

Copyright © 2025 Acronis International GmbH.

Licensed under MIT License.

Documentation

Overview

Package migrate provides functionality for applying database migrations.

Index

Constants

View Source
const MigrationsNoLimit = 0

MigrationsNoLimit contains a special value that will not limit the number of migrations to apply.

View Source
const MigrationsTableName = "migrations"

MigrationsTableName contains the name of table in a database that stores applied migrations.

Variables

This section is empty.

Functions

This section is empty.

Types

type AppliedMigration

type AppliedMigration struct {
	ID        string
	AppliedAt time.Time
}

AppliedMigration represent a single already applied migration.

type CustomMigration

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

CustomMigration represents simplified but customizable migration

func NewCustomMigration

func NewCustomMigration(id string, upSQL, downSQL []string, upFn, downFn func(tx *sql.Tx) error) *CustomMigration

NewCustomMigration creates simplified but customizable migration.

func (*CustomMigration) DownFn

func (m *CustomMigration) DownFn() func(tx *sql.Tx) error

DownFn returns a function that will be called during rolling back the migration

func (*CustomMigration) DownSQL

func (m *CustomMigration) DownSQL() []string

DownSQL returns a slice of SQL statements that will be executed during rolling back the migration.

func (*CustomMigration) ID

func (m *CustomMigration) ID() string

ID returns migration identifier.

func (*CustomMigration) UpFn

func (m *CustomMigration) UpFn() func(tx *sql.Tx) error

UpFn returns a function that will be called during applying the migration

func (*CustomMigration) UpSQL

func (m *CustomMigration) UpSQL() []string

UpSQL returns a slice of SQL statements that will be executed during applying the migration.

type Migration

type Migration interface {
	ID() string
	UpSQL() []string
	DownSQL() []string
	UpFn() func(tx *sql.Tx) error   // Not supported yet.
	DownFn() func(tx *sql.Tx) error // Not supported yet.
}

Migration is an interface for all database migrations. Migration may implement RawMigrator interface for full control. Migration may implement TxDisabler interface to control transactions.

func LoadAllEmbedFSMigrations added in v0.4.0

func LoadAllEmbedFSMigrations(fs embed.FS, dirName string) ([]Migration, error)

LoadAllEmbedFSMigrations loads all migrations from the embed.FS directory.

func LoadEmbedFSMigrations added in v0.4.0

func LoadEmbedFSMigrations(fs embed.FS, dirName string, migrationIDs []string) ([]Migration, error)

LoadEmbedFSMigrations loads migrations with specified IDs from the embed.FS directory.

type MigrationStatus

type MigrationStatus struct {
	AppliedMigrations []AppliedMigration
}

MigrationStatus is the migration status.

func (*MigrationStatus) LastAppliedMigration

func (ms *MigrationStatus) LastAppliedMigration() (appliedMig AppliedMigration, exist bool)

LastAppliedMigration returns last applied migration if it exists.

type MigrationsDirection

type MigrationsDirection string

MigrationsDirection defines possible values for direction of database migrations.

const (
	MigrationsDirectionUp   MigrationsDirection = "up"
	MigrationsDirectionDown MigrationsDirection = "down"
)

Directions of database migrations.

type MigrationsManager

type MigrationsManager struct {
	Dialect dbkit.Dialect
	// contains filtered or unexported fields
}

MigrationsManager is an object for running migrations.

func NewMigrationsManager

func NewMigrationsManager(dbConn *sql.DB, dialect dbkit.Dialect, logger log.FieldLogger) (*MigrationsManager, error)

NewMigrationsManager creates a new MigrationsManager.

func NewMigrationsManagerWithOpts

func NewMigrationsManagerWithOpts(
	dbConn *sql.DB,
	dialect dbkit.Dialect,
	logger log.FieldLogger,
	opts MigrationsManagerOpts,
) (*MigrationsManager, error)

NewMigrationsManagerWithOpts creates a new MigrationsManager with custom options

func (*MigrationsManager) Run

func (mm *MigrationsManager) Run(migrations []Migration, direction MigrationsDirection) error

Run runs all passed migrations.

func (*MigrationsManager) RunLimit

func (mm *MigrationsManager) RunLimit(migrations []Migration, direction MigrationsDirection, limit int) error

RunLimit runs at most `limit` migrations. Pass 0 (or MigrationsNoLimit const) for no limit (or use Run).

func (*MigrationsManager) Status

func (mm *MigrationsManager) Status() (MigrationStatus, error)

Status returns the current migration status.

type MigrationsManagerOpts

type MigrationsManagerOpts struct {
	TableName string
}

MigrationsManagerOpts holds the Migration Manager options to be used in NewMigrationsManagerWithOpts

type NullMigration

type NullMigration struct {
	Dialect dbkit.Dialect
}

NullMigration represents an empty basic migration that may be embedded in regular migrations in order to write less code for satisfying the Migration interface.

func (*NullMigration) DownFn

func (m *NullMigration) DownFn() func(tx *sql.Tx) error

DownFn is a stub that returns an empty function that implied to be called during rolling back the migration.

func (*NullMigration) DownSQL

func (m *NullMigration) DownSQL() []string

DownSQL is a stub that returns an empty slice of SQL statements that implied to be executed during rolling back the migration.

func (*NullMigration) ID

func (m *NullMigration) ID() string

ID is a stub that returns empty migration identifier.

func (*NullMigration) UpFn

func (m *NullMigration) UpFn() func(tx *sql.Tx) error

UpFn is a stub that returns an empty function that implied to be called during applying the migration.

func (*NullMigration) UpSQL

func (m *NullMigration) UpSQL() []string

UpSQL is a stub that returns an empty slice of SQL statements that implied to be executed during applying the migration.

type RawMigrator

type RawMigrator interface {
	RawMigration(m Migration) (*migrate.Migration, error)
}

RawMigrator is an interface that allows to overwrite default generate mechanism for full control on migrations. Uses sql-migrate migration structure.

type TxDisabler

type TxDisabler interface {
	DisableTx() bool
}

TxDisabler is an interface for Migration for controlling transaction

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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