imigrate

package module
v0.0.0-...-5c5e768 Latest Latest
Warning

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

Go to latest
Published: Apr 9, 2024 License: MIT Imports: 16 Imported by: 2

README

I Migrate

Interface-driven migrations for Go.

I Migrate is an interface-driven approach to managing database migrations. It was created out of a need for a migration tool that can be used in a Go project that doesn't conform to database/sql, and can execute migrations embedded in a single binary.

This project aims to fulfill the following needs:

  1. Migrations written in pure SQL, stored in flat files.
  2. CLI runner for up, down, redo, rollback, etc.
  3. CLI template generator for timestamp-prefixed migrations.
  4. http.FileSystem support, allowing migration files to be embedded in a Go Binary.
  5. Database driver agnostic via a sql Exec interface.
  6. No config files. (But config in code)

Read the docs

Motivation

I didn't want to write this code, I really didn't. Migrations should be commodity code, and there are already dozens of libraries available. Further, after finding shmig, I had written off needing a migration tool ever again, but that was before I needed to ship an actual migration to prod.

These days, prod is different. It's docker container running a single binary. And my image doesn't need a migrations folder, nor does it need a shell, or a SQLite client, all of which shmig require. So what to do? Should I create a new docker container that only has shmig, SQLite, and a migrations folder? That seems terribly redundant and a waste of space and deployment overhead. Too many moving parts!

Great, so instead I decided to use the most popular migration tool with embedded sql migration support. But that's when I hit the interface problem. You see, one of my projects uses a non-standard SQLite driver that doesn't conform to database/sql, while another project uses the standard driver. I checked a few libraries and though some looked very promising (gloat) by offering interfaces for migrations, they eventually relied on sql.Rows or another database struct. And that's when I decided to use my Saturday to create this simple library.

But with increased flexibility comes an increased cost. This tool requires configuration through code. Remember, it's interface-driven which means the tool provides an interface while you provide the logic. Not to worry though, it's a fairly straight forward interface: allow the tool to Exec sql and GetVersions (return an array of ids) and it'll manage your migrations for you! A wholesome give and take.

Usage

Unfortunately, some assembly required.

I Migrate has a command line interface, but you have to provide the glue to make it work. I know it's bummer when code doesn't just work out of the box, but if that's what you needed, you wouldn't be here. On the upside, you can name the migration binary whatever you want, or skip it all together.

// MyDB conforms to the Executor interface by defining Exec and GetVersions
type MyDB struct {
  *sql.DB
}

func (o MyDB) GetVersions(query string, args ...interface{}) (versions []int64, err error) {
  rows, err := o.Query(query, args...)
  if err != nil {
    return
  }
  defer rows.Close()
  for rows.Next() {
    var version int64
    if err = rows.Scan(&version); err != nil {
      return
    }
    versions = append(versions, version)
  }
  err = rows.Err()
  return
}

db, err := sql.Open("sqlite3", "db.sqlite3")
if err != nil {
  log.Panic(err)
}
defer db.Close()

myDB := MyDB{DB: db}
fs := http.Dir("")
migrator := imigrate.NewIMigrator(myDB, fs)
imigrate.CLI(migrator)

Example CLI usage for a tool name "migrate"

migrate create create_users_table

migrate up
migrate up --steps 1

migrate down
migrate down --steps 1

migrate redo
migrate redo --steps 2

migrate rollback
migrate rollback --steps 3

Documentation

Index

Constants

View Source
const HelpText = "Please specify up, down, redo, rollback, status, or create."

HelpText is printed when no command is specified.

Variables

CLIErr is returned when no command is specified.

View Source
var DiscardLogger = log.New(io.Discard, "", log.LstdFlags)
View Source
var Logger = log.Default()

Functions

func CLI

func CLI(migrator Migrator) error

CLI parses os.Args and runs the appropriate migration command. Commands available are up, down, redo, rollback, status, and create. Most commands accept a "steps" flag which is parsed as an int. Use -steps=1 to set it. Up, down, and redo accept a "version" flag which is parsed as int64. Use --version=1610069160 to set it.

Types

type Executor

type Executor interface {
	Exec(query string, args ...interface{}) (sql.Result, error)
	GetVersions(query string, args ...interface{}) ([]int64, error)
}

Executor is the interface to executing SQL

Exec executes a SQL query returning a sql.Result. Use this for mutation queries like CREATE, INSERT, UPDATE, DELETE, etc.

GetVersions returns the timestamped versions that have been migrated thus far. Timestamps are stored in Unix time, that is seconds since epoch, stored in an int64.

type IMigrator

type IMigrator struct {
	DB                Executor
	FS                http.FileSystem
	Dirname           string         // The directory where migrations are stored.
	UpKey             *regexp.Regexp // The Regexp to detecth the up migration SQL.
	DnKey             *regexp.Regexp // The Regexp to detecth the down migration SQL.
	TableName         string         // The table where migration info is stored.
	VersionColumn     string         // The version column in the migrations table.
	CreateTableSQL    string         // The SQL to create the migrations table.
	Migrations        []Migration
	FileVersionRegexp *regexp.Regexp // The Regexp to detect a migration file.
	TemplateUp        string         // The SQL to place in the UP section of a generated file.
	TemplateDn        string         // The SQL to place in the DOWN section of a generated file.
	// contains filtered or unexported fields
}

IMigrator is the default migrator that satisfies the Migrator interface.

func NewIMigrator

func NewIMigrator(db Executor, fs http.FileSystem) *IMigrator

NewIMigrator returns a default migrator with the SQLite dialect.

func (IMigrator) Create

func (o IMigrator) Create(name string)

Create generates a new migration file in the Dirname directory. The file is prefixed with the current time as a unix timestamp, followed by the provided name. It will insert the provided TemplateUp and TemplateDn strings into the appropriate sections of the migration file.

func (*IMigrator) Down

func (o *IMigrator) Down(steps int, version int64)

Down runs all migrations in descending order. If steps is greater than -1, it will step down that many migrations. If version is greater than 0, it will only migrate down that specific version.

func (*IMigrator) Redo

func (o *IMigrator) Redo(steps int, version int64)

Redo runs Down, then Up

func (*IMigrator) Rollback

func (o *IMigrator) Rollback(steps int)

Rollback runs the down SQL for the most recent migration. If steps is greater than 1, it will run that many migrations down.

func (*IMigrator) Status

func (o *IMigrator) Status()

Status prints out which migrations have been run and which are pending.

func (*IMigrator) Up

func (o *IMigrator) Up(steps int, version int64)

Up runs all migrations that have not been run. If steps is greater than -1, it will run that many migrations in ascending order. If version is greater than 0, it will migrate up that specific version.

type Migration

type Migration struct {
	Version  int64
	Time     time.Time
	FileInfo os.FileInfo
	Up       string
	Dn       string
}

Migration represents a single migration file

func (*Migration) Valid

func (o *Migration) Valid(file http.File, upKey, dnKey *regexp.Regexp) (valid bool)

Valid reads and stores the UP and DOWN SQL queries, and returns true if both are found.

type Migrator

type Migrator interface {
	Create(string)
	Up(int, int64)
	Down(int, int64)
	Redo(int, int64)
	Rollback(int)
	Status()
}

Migrator is the interface for running migrations.

Create is used to create a new migration file. The file should be prefixed with a unix timestamp and stored in the migrations directory.

Up runs the UP migration for every migration file.

Down runs the DOWN migration for every migration file.

Redo runs the DOWN then UP migration for the most recently created migration.

Rollback runs the DOWN migration for the most recenlty created migration.

Status prints out which migrations have been run thus far.

Jump to

Keyboard shortcuts

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