pomegranate

package module
v0.0.10 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2019 License: MIT Imports: 19 Imported by: 0

README

Pomegranate Build Status Coverage Status

Pomegranate is a tool for creating and running schema migrations for the Postgres database, emphasizing safety and transparency. All migrations are run in transactions that will be automatically rolled back if an exception is raised, ensuring the database is not left in a half-migrated state. Further, the .sql migration files created by Pomegranate are the exact SQL that will be run at migration time; you will get the same result whether you call pmg forward, or feed a forward.sql file to psql, or attach the forward.sql file to a change control ticket and have your DBA run it. Postgres's best-in-class support for transactional DDL makes this safety and transparency possible.

Go projects can also use pmg to convert their .sql migrations into a migrations.go file that will be compiled into their project and run with the pomegranate package.

Installation

For now, pomegranate has to be built from source:

$ git clone git@github.com:btubbs/pomegranate.git
$ cd pomegranate
$ go install cmd/pmg.go

Usage

You can use Pomegranate in two ways:

  1. As a standalone command line tool (pmg).
  2. As a library (github.com/btubbs/pomegranate) in your own Go project.
Using pmg
Create initial migration

Use the pmg init command to create your first migration, which will be responsible for creating the migration_state table. It will create the migration in the current directory, or you can specify a different one with the --dir option. You can also use the --ts option to use timestamps.

$ pmg init
Migration stubs written to 00001_init

The 00001_init directory should now exist, and contain forward.sql and backward.sql files. You don't need to edit these initial migrations.

Create more migrations

Migrations containing your own custom changes should be made with the pmg new command:

$ pmg new add_customers_table
Migration stubs written to 00002_add_customers_table

Note that the 00002 prefix has been prepended to the name you provided. Migrations are run in the order they appear in the file system. The auto numbering ensures that this ordering is consistent. You can also use the --ts option to use timestamps.

As with init, the new command creates forward.sql and backward.sql files. Unlike init, these are just stubs. You will need to edit these files and add your own commands (e.g. CREATE TABLE...). The stub files try to make it obvious where your commands should go:

$ cat 00002_add_customers_table/forward.sql 
BEGIN;
-- vvvvvvvv PUT FORWARD MIGRATION CODE BELOW HERE vvvvvvvv

SELECT 1 / 0; -- delete this line

-- ^^^^^^^^ PUT FORWARD MIGRATION CODE ABOVE HERE ^^^^^^^^
INSERT INTO migration_state(name) VALUES ('00002_add_customers_table');
COMMIT;

The SELECT 1 / 0; line in the stub is a safeguard against accidentally running an empty migration. You should replace it with your own commands.

Be sure to also add the necessary commands to backward.sql to safely roll back the changes in forward.sql, in case you decide they were a bad idea.

Run migrations

Use the forward command to run all migrations not yet recorded in the migration_state table. Pomegranate will connect to the database specified in the DATABASE_URL environment variable, or you can supply a database URL with the --dburl option.

$ pmg forward
Connecting to database 'readme' on host ''
Forward migrations that will be run:
00001_init
00002_add_customers_table
Run these migrations? (y/n) y
Running 00001_init... Success!
Running 00002_add_customers_table... Success!
Done

If you don't want to run all the migrations, you can use the forwardto command instead:

$ pmg forwardto 00001_init
Connecting to database 'readme' on host ''
Forward migrations that will be run:
00001_init
Run these migrations? (y/n) y
Running 00001_init... Success!
Done

If a migration fails, DON'T PANIC. Your database should still be in the same state it was in before that forward.sql script was executed. (Unless you put commands outside the BEGIN and COMMIT lines.) Fix the problem in your script, and run pmg forward again.

Roll back migrations

Rolling back is done with the backwardto command. This will run the backward.sql file for all migrations that have already been run, up to and including the one specified in the command.

$ pmg backwardto 00002_add_customers_table
Connecting to database 'readme' on host ''
Backward migrations that will be run:
00002_add_customers_table
Run these migrations? (y/n) y
Running 00002_add_customers_table... Success!
Done

Unlike going forward, pmg does NOT provide a backward command that will migrate all the way back. You must use backwardto and provide an explicit migration name.

View migration state

The state command will show all migrations recorded in the migration_state table:

$ pmg state 
Connecting to database 'readme' on host ''
NAME       | WHEN                                 | WHO
00001_init | 2018-02-11 20:48:51.827197 -0700 MST | postgres
Using the pomegranate package in Go

If your project is written in Go, Pomegranate may also be integrated into your project so that migrations can be included inside your binary program and executed by it.

The full public interface is documented at https://godoc.org/github.com/btubbs/pomegranate.

Ingest migrations

Use the pmg ingest command to turn your .sql migrations into a .go file that can be compiled into your project. Run it in the same directory as your migrations, or use the --dir option.

$ pmg ingest
Migrations written to migrations.go

By default, migrations are written to migrations.go with package migrations at the top. You can customize the name of the .go file and the package name inside it with the --gofile and --package options, respectively.

The file created will have an All variable in it containing all your migrations.

The file will also have a //go:generate... tag inside it that will allow to to re-generate your .go file by running go generate in your migrations directory.

Run migrations from your code

Use Pomegranate's MigrateForwardTo function to run migrations forward. It takes four arguments:

  • the name that you want to migrate to
  • a DB connection
  • your ingested migrations
  • a boolean flag indicating whether to ask for "y/n" confirmation on the command line
pomegranate.MigrateForwardTo(name, db, migrations.All, true)

MigrateBackwardTo and GetMigrationState functions are also available.

A complete example

Here's the complete file layout of an extremely simple project that uses Pomegranate:

$ tree
.
├── migrations
│   ├── 00001_init
│   │   ├── backward.sql
│   │   └── forward.sql
│   ├── 00002_add_customers_table
│   │   ├── backward.sql
│   │   └── forward.sql
│   └── 00003_add_address_column
│       ├── backward.sql
│       └── forward.sql
└── my_awesome_app.go

In that example, my_awesome_app.go looks like this:

package main

import (
  "fmt"
  "os"

  "github.com/btubbs/my_awesome_app/migrations"
  "github.com/btubbs/pomegranate"
)

func main() {
  db, err := pomegranate.Connect(
    "postgres://postgres@/awesome_app?sslmode=disable")
  if err != nil {
    fmt.Println(err)
    os.Exit(1)
  }
  // passing an empty string as name will run to the latest migration
  pomegranate.MigrateForwardTo("", db, migrations.All, true)
}

Given the above, you can build my_awesome_app like so:

$ cd migrations
$ pmg ingest
Migrations written to migrations.go
$ cd ..
$ go build

And run it like so:

$ psql -c "CREATE DATABASE awesome_app"
CREATE DATABASE
$ ./my_awesome_app 
Connecting to database 'awesome_app' on host ''
Forward migrations that will be run:
00001_init
00002_add_customers_table
00003_add_address_column
Run these migrations? (y/n) y
Running 00001_init... Success!
Running 00002_add_customers_table... Success!
Running 00003_add_address_column... Success!

Documentation

Overview

Package pomegranate implements helper functions and a CLI (pmg) for creating and safely running SQL migrations.

Go projects can use pomegranate to ingest .sql files into a Go binary, and run them from there. See the README.md on Github for examples and explanations.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Connect

func Connect(dburl string) (*sql.DB, error)

Connect calls sql.Open for you, specifying the Postgres driver and printing the DB name and host to stdout so you can check that you're connecting to the right place before continuing.

func FakeMigrateForwardTo

func FakeMigrateForwardTo(name string, db *sql.DB, allMigrations []Migration, confirm bool) error

FakeMigrateForwardTo will record all forward migrations that have not yet been run in the migration_state table, up to and including the one specified by `name`, without actually running their ForwardSQL. To fake all un-run migrations, set `name` to an empty string.

func IngestMigrations

func IngestMigrations(dir, goFile, packageName string, generateTag bool) error

IngestMigrations reads all the migrations in the given directory and writes them to a Go source file in the same directory. The generateTag argument determines whether the new Go file will contain a "//go:generate" comment to tag it for automatic regeneration by "go generate".

func InitMigration

func InitMigration(dir string) error

InitMigration creates a new 00001_init migration in the given directory. This migration will contain the SQL commands necessary to create the migration_state table.

func InitMigrationTimestamp added in v0.0.5

func InitMigrationTimestamp(dir string, timestamp time.Time) error

InitMigrationTimestamp creates a new {timestamp}_init migration in the given directory. This migration will contain the SQL commands necessary to create the `migration_state` table.

func MigrateBackwardTo

func MigrateBackwardTo(name string, db *sql.DB, allMigrations []Migration, confirm bool) error

MigrateBackwardTo will run backward migrations starting with the most recent in state, and going through the one provided in `name`.

func MigrateForwardTo

func MigrateForwardTo(name string, db *sql.DB, allMigrations []Migration, confirm bool) error

MigrateForwardTo will run all forward migrations that have not yet been run, up to and including the one specified by `name`. To run all un-run migrations, set `name` to an empty string.

func NewMigration

func NewMigration(dir, name string) error

NewMigration creates a new directory containing forward.sql and backward.sql stubs. The directory created will use the name provided to the function, prepended by an auto-incrementing zero-padded number.

func NewMigrationTimestamp added in v0.0.5

func NewMigrationTimestamp(dir, name string, timestamp time.Time) error

NewMigrationTimestamp creates a new directory containing forward.sql and backward.sql stubs. The directory created will use the name provided to the function, prepended by a timestamp formatted with `YYYYMMDDhhmmss` (i.e. `20060102150405`).

Types

type Migration

type Migration struct {
	Name        string
	ForwardSQL  []string
	BackwardSQL []string
}

Migration contains the name and SQL for a migration. Arrays of Migrations are passed between many functions in the Pomegranate source. SeperateForwardStatements runs SQL statements seperately, delinieated by ";"

func ReadMigrationFiles

func ReadMigrationFiles(dir string) ([]Migration, error)

ReadMigrationFiles reads all the migration files in the given directory and returns an array of Migration objects.

func (Migration) QuotedTemplateBackward added in v0.0.5

func (m Migration) QuotedTemplateBackward() []string

QuotedTemplateBackward returns the BackwardSQL field of the Migration, properly escaped for easy injection into a migrations.go template.

func (Migration) QuotedTemplateForward added in v0.0.5

func (m Migration) QuotedTemplateForward() []string

QuotedTemplateForward returns the ForwardSQL field of the Migration, properly escaped for easy injection into a migrations.go template.

type MigrationLogRecord

type MigrationLogRecord struct {
	ID   int       `db:"id"`
	Time time.Time `db:"time"`
	Name string    `db:"name"`
	Op   string    `db:"op"`
	Who  string    `db:"who"`
}

MigrationLogRecord represents a specific migration run at a specific point in time. Unlike MigrationRecord, this is an append-only table, showing the complete history of all forward and backward migrations. It is populated automatically by a Postgres trigger created in the init migration.

func GetMigrationLog

func GetMigrationLog(db *sql.DB) ([]MigrationLogRecord, error)

GetMigrationLog returns the complete history of all migrations, forward and backward. If the migration_log table does not exist, it returns an empty list of MigrationLogRecords

type MigrationRecord

type MigrationRecord struct {
	Name string    `db:"name"`
	Time time.Time `db:"time"`
	Who  string    `db:"who"`
}

MigrationRecord provides information on which migrations are currently in effect. An array of MigrationRecords is referred to as a "state" throughout the Pomegranate source. These are treated as a stack; MigrationRecords are added (inserted into the DB) as migrations run forward, and popped off (deleted from the DB) as migrations are run backward.

func GetMigrationState

func GetMigrationState(db *sql.DB) ([]MigrationRecord, error)

GetMigrationState returns the stack of migration records stored in the database's migration_state table. If that table does not exist, it returns an empty list.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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