Categorygithub.com/sbowman/migrations/v2
modulepackage
2.1.2
Repository: https://github.com/sbowman/migrations.git
Documentation: pkg.go.dev

# README

Go SQL Migrations v2.0.0

PkgGoDev

The migrations package provides an agile approach to managing database schema revisions for your Go applications. Using versioned files containing SQL commands to modify the schema, you can make small changes to your database schema over time and version the changes using version controller.

The Migrations package is designed to help manage PostgreSQL database schema revisions in Go applications. It works in a similar fashion to Ruby on Rails ActiveRecord migrations: create a directory for your migrations, add numberically ordered SQL files with an "up" and "down" section, then write your "up" SQL commands to update the database, and "down" SQL command to roll back those changes if needed. You may then call functions in the package from your application to apply changes to the database schema on deployment, or leverage the migrate command line application to manage the database schema separately from the application.

Version 2 of the Migrations package trims down a lot of functionality that had crept into the previous version. Note that asynchronous migrations, Cobra/Viper integration, remote S3 migrations, and transactionless migrations have all been removed due to lack of use. If you'd like them added, feel free to post a ticket.

Upgrading From Migrations/v1

If your application was using the v1 version of the migrations package, it will automatically be upgraded to v2. The following changes are made to the database:

  • A new schema, migrations is created in the database.
  • Two tables are created in this schema, migrations.applied and migrations.rollbacks.
  • The migrations from the schema_migrations table are copied to migrations.applied.
  • The schema_migrations table is deleted.

Migrations metadata is now maintained in the migrations schema in an attempt to keep things separate from your database and out of your way. The migrations.applied table is the old schema_migrations table.

The migrations.rollbacks is used to store rollback or "down" SQL migrations in the database.
This allows an application to rollback the database when downgrading an application and the SQL files to rollback the database schema are no longer to the application.

Migrations Integration and CLI

The migrations package may be included in your product binary, or run separately as a standalone application. When embedded in the application, typically you configure your application to apply the database migrations on startup.

It may also be run as a standalone tool, either from the command line or a Docker container.

Installing the Command-Line Tool

To install the command-line tool, run:

$ go install github.com/sbowman/[email protected]

This will install the migrate binary into your $GOPATH/bin directory. If that directory is on your PATH, you should be able to run migrations from anywhere.

If you need help using the command-line tool, run migrate help for information.

Available Options

To apply migrations to the database schema, you need to supply two pieces of information: the path to the SQL migrations, and the database URI to connect to the database. To supply that information to the command-line application, use the migrations and uri parameters:

$ migrate apply --uri='postgres://[email protected]/fourier?sslmode=disable&connect_timeout=5'
--migrations=./sql

By default, the migrations package looks for migrations in the sql directory relative to where you run the binary, so you can leave --migrations off:

$ migrate apply --uri='postgres://[email protected]/fourier?sslmode=disable&connect_timeout=5'

You may additionally apply a revision to migrate the database to the specific version of the schema using the --revision parameter:

$ migrate apply --uri='postgres://[email protected]/fourier?sslmode=disable&connect_timeout=5'
--revision=23

This will either apply the migrations to reach SQL migration file 23-<name>.sql, or rollback the database migrations to step back down to revision 23.

Supported Environment Variables

The migrations package supports supplying configuration options via environment variables.

  • To supply an alternative migrations directory, you can use the MIGRATIONS environment variable.
  • To supply the PostgreSQL URI in the command-line application, you can use the DB_URI or DB environment variables.

Development and Testing Notes

In order to remove the PostgreSQL driver dependency from this package, test cases exist in a subdirectory, tests, and are treated as a standalone package. This allows us to maintain test cases, but utilize the PostgreSQL driver to test migrations against a real database server.

To run these test cases:

cd tests && make

The Makefile tears down any existing migrations_test database, creates a new migrations_test database, runs the tests for migrations in a transaction, followed by tests for migrations outside of a transaction.

The API

Adding Migrations to Your Application

There are a few primary functions to add to your applications to use the migrations package.

Create an Empty Migration File

The migrations package can create empty migration files with the "up" and "down" sections inserted for you (see below).

migrations.Create("/path/to/migrations", "migration name")

This will create a migration file in the migrations path. The name will be modified to remove the space, and a revision number will be inserted. If there were five other SQL migrations in /path/to/migrations, You'd end up with a file like so:

$ ls /path/to/migrations
total 16
-rw-r--r--  1 sbowman  staff   99 May 22 15:39 1-create-sample.sql
-rw-r--r--  1 sbowman  staff  168 May 22 15:40 2-add-email-to-sample.sql
-rw-r--r--  1 sbowman  staff  207 May 22 15:50 3-create-my-proc.sql
-rw-r--r--  1 sbowman  staff   96 May 22 16:32 4-add-username-to-sample.sql
-rw-r--r--  1 sbowman  staff  347 May 22 17:55 5-rename-sample-to-users.sql
-rw-r--r--  1 sbowman  staff   22 May 26 19:03 6-migration-name.sql

Apply the Migrations

Once you've created your migration files and added the appropriate SQL to generate your database schema and seed any data, you'll want to apply the migrations to the database by calling:

migrations.Apply(conn)

Where conn is a *sql.Conn database connection (from the database/sql Go package).

This will attempt to run the migrations to the latest version as defined in the default ./sql directory, relative to where the binary was run.

To indicate another directory:

migrations.WithDirectory("/etc/app/sql").Apply(conn)

To migrate to a specific revision:

migrations.WithDirectory("/etc/app/sql").WithRevision(33).Apply(conn)

The revision number allows you to apply just the migrations appropriate for the current version of your application. Day to day, you'll likely just use the default value -1, which applies any and all existing migrations, in order of their revision number.

But suppose you just want to apply the first four migrations to your database, but not the last two yet? Set revisionNum to 4 and call migrations.Migrate. The migrations package will apply just the SQL from the migration files from 1 to 4, in order.

What if you've already run all six migrations listed above? When you call migrations.Apply with WithRevision set to 4, the migrations package applies the SQL from the "down" section in migration 6-migration-name.sql, followed by the SQL in the "down" section of 5-rename-sample-to-users.sql.

We call this "rolling back" a migration, and it allows you to develop your application, make some changes, apply them, then roll them back if they don't work.

Note: make sure to load the database driver before applying migrations! For example:

import _ "github.com/jackc/pgx/v5/stdlib"

See tests/migrations_test.go for an example.

Migration Files

Typically you'll deploy your migration files to a directory when you deploy your application.

Create a directory in your application for the SQL migration files:

$ mkdir ./sql
$ cd sql

Now create a SQL migration. The filename must start with a number, followed by a dash, followed by some description of the migration. For example:

$ vi 1-create_users.sql

If you're using the Cobra commands, there's a "db create" function that creates the directory and numbers the migration file correctly for you (if migrations already exist, it'll figure out the next number):

$ ./myapp db create create_users
Created new migration ./sql/1-create_users.sql

An empty migration file looks like this:

--- !Up

--- !Down

Under the "up" section, add the changes you'd like to make to the database in this migration. You may insert as many database commands as you like, but ideally each migration carries out the simplest, smallest unit of work that makes for a useful database, e.g. create a database table and indexes; make a modification to a table; or create a stored procedure.

Note the above line formats are required, including the exclamation points, or migrations won't be able to tell "up" from "down."

The "down" section should contain the code necessary to rollback the "up" changes.

So our "create_users" migration may look something like this:

--- !Up
create table users (
    id serial primary key,
    username varchar(64) not null,
    email varchar(1024) not null,
    password varchar(40) not null,
    enabled bool not null default true
);

create unique index idx_enabled_users on users (username) where enabled;
create unique index idx_enabled_emails on users (email) where enabled;

--- !Down
drop table users;

The migrations package simply passes the raw SQL in the appropriate section ("up" or "down"), to the database. The SQL calls are wrapped in a single transaction, so that if there's a problem with the SQL, the database can rollback the entire migration (see below to disable these transactions for special cases).

Some databases, such as PostgreSQL, support nearly all schema modification commands (CREATE TABLE, ALTER TABLE, etc.) in a transaction. Databases like MySQL have some support for this. Your mileage may vary, but if your database doesn't support transactioned schema modifications, you may have to manually repair your databases should a migration partially fail. This is another reason to keep each migration modification small and targeted to a specific change, and not put everything in one revision file: if the migration fails for whatever reason, it's easier to clean up.

Using the Migrations command-line tool

The Migrations v2 includes a CLI tool to run migrations standalone, without needing to embed them in your application. Simply install the CLI tool and make sure the Go bin directory is in your path:

$ go install github.com/sbowman/migrations/v2/migrate
$ migrate --revision=12 --migrations=./sql --uri=postgres://localhost/myapp_db?sslmode=disable 

Use migrate --help for details on the available commands and parameters.

Embedded Rollbacks

Migrations/v2 stores each rollback ("down") SQL migration in the database. With this the migrations package doesn't need the SQL files to be present to rollback, which makes it easier to rollback an application's database migrations when using deployment tools like Ansible or Terraform. You can simply deploy a previous version of the application, and the migrations package can apply the rollbacks stored in the database to restore the database to its previous schema version.

For example, you could deploy version 1.3 of your application, realize there is a bug, then redeploy version 1.2. The migrations package can recognize the highest version of SQL files available is lower than the migrations applied to the database, and can run the rollback using the SQL embedded in the database table migrations.rollbacks.

The migrations package manages this with no additional work on the developer's part.

The /stop Annotation

Some migrations can't be rolled back. For example, if you delete data from the database, you're not going to be able to rollback and restore that data. If you'd like to indicate a migration can't be rolled back, you can use the /stop annotation:

--- !Up
<some irreversible SQL>

--- !Down /stop

By adding /stop to the "down" migration, you indicate to the migrations library that this migration cannot be rolled back and a rollback will stop when it reaches this migration. Without this flag, the "down" migration would simply be skipped for a lack of SQL and the rollback would continue.

Use this functionality sparingly. Migrations should be small, and they should be reversable. If you do need to make an irreversible change to a database, best to take a deprecation step: create the schema changes, but keep the old schema (and data) around for a while, then finally deprecate in a subsequent, future revision.

Logging

The migrations package uses a simple Logger interface to expose migration information to the user. By default, this goes to stdout. You're welcome to implement your own logger and wire migrations logging into your own log output.

Here's the interface:

// Logger is a simple interface for logging in migrations.
type Logger interface {
    Debugf(format string, args ...interface{})
    Infof(format string, args ...interface{})
}

Just assign your logger to migrations.Log before running any migration functions.

There's also a NilLogger available, if you'd like to hide all migrations output.

migrations.Log = new(migrations.NilLogger)

# Functions

Applied returns the list of migrations that have already been applied to this database.
Apply any SQL migrations to the database using the default options.
ApplyRollbacks collects any migrations stored in the database that are higher than the desired revision and runs the "down" migration to roll them back.
Available returns the list of SQL migration paths in order.
CopyMigrations copies the migrations from the schema_migrations table to the migrations.applied table.
Create a new migration from the template.
CreateMigrationsApplied creates the migrations.applied table in the database if it doesn't already exist.
CreateMigrationsRollbacks creates the migrations.rollbacks table in the database if it doesn't already exist.
CreateMigrationsSchema creates the "migrations" schema for storing the migrations state.
CreateSchemaMigrations creates the schema_migrations table in the database if it doesn't already exist.
DefaultOptions returns the defaults for the migrations package.
DisableEmbeddedRollbacks disables the embedded rollbacks functionality.
Downgrade rolls your database back from migrations/v2 to a migrations/v1-compatible database, or specifically, recreate schema_migrations and copy migrations.applied into the schema_migrations table and drop the "migrations" schema.
Filename returns just the filename from the full path.
HandleAsync should be run in the background to listen for asynchronous migration requests.
HandleEmbeddedRollbacks updates the rollbacks and then applies any missing and necessary rollbacks to get the database to the implied versions.
InitializeDB prepares the tables in the database required to manage migrations.
IsDown returns true if the migration must rollback to reach the desired version.
IsMigrated checks the migration has been applied to the database, i.e.
IsUp returns true if the migration must roll up to reach the desired version.
LatestMigration returns the name of the latest migration run against the database.
LatestRevision returns the latest revision available from the SQL files in the migrations directory.
Migrate runs the indicated SQL migration files against the database.
Migrated adds or removes the migration record from migrations.applied.
MissingMigrationsApplied returns true if there is no migrations.applied table in the database.
MissingMigrationsRollbacks returns true if there is no migrations.rollbacks table in the database.
MissingMigrationsSchema returns true if there's no "migrations" schema in the database.
MissingSchemaMigrations returns true if there is no schema_migrations table in the database.
Moving determines the direction we're moving to reach the version.
NewSQLParser creats a new SQL parser.
ParseSQL breaks the SQL document apart into individual commands, so we can submit them to the database one at a time.
ReadSQL reads the migration and filters for the up or down SQL commands.
Revision extracts the revision number from a migration filename.
Rollback a number of migrations.
RunIsolated breaks apart a SQL migration into separate commands and runs each in a single transaction.
ShouldRun decides if the migration should be applied or removed, based on the direction and desired version to reach.
UpdateRollback adds the migration's "down" SQL to the rollbacks table.
UpdateRollbacks copies all the "down" parts of the migrations into the migrations.rollbacks table for any migrations missing from that table.
Upgrade from migrations/v1 to migrations/v2.
WithDirectory points to the directory of SQL migrations files that should be used to migrate the database schema.
WithRevision manually indicates the revision to migrate the database to.

# Constants

Down direction.
EnvMigrations is the environment variable that can be used to point to the directory of SQL migrations.
Latest migrates to the latest migration.
None direction.
Up direction.

# Variables

ErrNameRequired returned if the user failed to supply a name for the migration.
No description provided by the author
No description provided by the author
ErrStopped returned if the migration couldn't rollback due to a /stop modifier.
IO defaults to writing to disk.
Log outputs log messages to any object supporting the Logger interface.

# Structs

AsyncRequest is submitted to the background asynchronous migration processor.
AsyncResult is returned by asynchronous SQL migration commands on the Results channel.
DefaultLogger implements Logger.
DiskReader outputs to disk, the Migrations default.
NilLogger hides migration logging output.
No description provided by the author
SQLParser breaks apart a document of SQL commands into their individual commands.

# Interfaces

Logger is a simple interface for logging in migrations.
No description provided by the author
Reader interface allows the migrations to be read from different sources, such as an S3 bucket or another data store.

# Type aliases

Direction is the direction to migrate.
Modifiers collects the modification flags passed in from the SQL "up" and "down" lines.
No description provided by the author
No description provided by the author
SQL contains SQL commands or a migration.