andrevdm / postgresql-migration

PostgreSQL Schema Migrations for Haskell
Other
15 stars 4 forks source link

PostgreSQL Migrations for Haskell

Forked from postgresql-simple-migration created by Andreas Meingast

Haskell-CI

Welcome to postgresql-migrations, a tool for helping you with PostgreSQL schema migrations.

This project is an open-source database migration tool. It favors simplicity over configuration.

It is implemented in Haskell and uses the (excellent) postgresql-simple library to communicate with PostgreSQL.

It comes in two flavors: a library that features an easy to use Haskell API and as a standalone application.

Database migrations can be written in SQL (in this case PostgreSQL-sql) or in Haskell.

Why?

Database migrations should not be hard. They should be under version control and documented both in your production systems and in your project files.

What?

This library executes SQL/Haskell migration scripts and keeps track of their meta information.

Scripts are executed exactly once and any changes to scripts will cause a run-time error notifying you of a corrupted database.

The meta information consists of:

This library also supports migration validation so you can ensure (some) correctness before your application logic kicks in.

How?

This utility can be used in two ways: embedded in your Haskell program or as a standalone binary.

Standalone

The standalone program supports file-based migrations. To execute all SQL-files in a directory $BASE_DIR, execute the following command to initialize the database in a first step.

CON="host=$host dbname=$db user=$user password=$pw"
cabal run migrate -- init $CON
cabal run migrate -- migrate $CON $BASE_DIR

To validate already executed scripts, execute the following:

CON="host=$host dbname=$db user=$user password=$pw"
cabal run migrate -- init $CON
cabal run migrate -- validate $CON $BASE_DIR

For more information about the PostgreSQL connection string, see: libpq-connect.

Library

The library supports more actions than the standalone program.

Initializing the database:

main :: IO ()
main = do
    let url = "host=$host dbname=$db user=$user password=$pw"
    con <- connectPostgreSQL (BS8.pack url)
    runMigration con defaultOptions MigrationInitialization

For file-based migrations, the following snippet can be used:

main :: IO ()
main = do
    let url = "host=$host dbname=$db user=$user password=$pw"
    let dir = "."
    con <- connectPostgreSQL (BS8.pack url)
    runMigration con defaltOptions $ MigrationDirectory dir

To run Haskell-based migrations, use this:

main :: IO ()
main = do
    let url = "host=$host dbname=$db user=$user password=$pw"
    let name = "my script"
    let script = "create table users (email varchar not null)";
    con <- connectPostgreSQL (BS8.pack url)
    runMigration con defaultOptions $ MigrationScript name script

Validations wrap MigrationCommands. This means that you can re-use all MigrationCommands to perform a read-only validation of your migrations.

To perform a validation on a directory-based migration, you can use the following code:

main :: IO ()
main = do
    let url = "host=$host dbname=$db user=$user password=$pw"
    con <- connectPostgreSQL (BS8.pack url)
    runMigration con default Options $ MigrationValidation (MigrationDirectory dir)

Transactions

Database migrations should always be performed in a transactional context.

The standalone binary and the API default to using a new transaction for the entire set of migrations.

Using the -t argument to the binary will change this to using a new transaction per migration step (script).

When using the library you have full control over the behaviour of transactions by setting optTransactionControl on the MigrationOptions record.

There are three options

1) No new transaction: you manage the transaction, e.g. if you want to run multiple migrations in a single transaction 2) Transaction per run: This is the default. New transaction for the entire migration 3) Transaction per step

The tests make use TransactionPerRun, after executing all migration-tests, the transaction is rolled back.

Options

The runMigration and runMigrations functions take an options record that let you set the following

Compilation and Tests

The program can be built with cabal or stack build systems.

The following command builds the library, the standalone binary and the test package with cabal

cabal configure --enable-tests && cabal build -j

To execute the tests, you need a running PostgreSQL server. You need to set the correct environment varaiables for the psql connection.

Tests are executed through cabal as follows:

cabal configure --enable-tests 
PGHOST=localhost PGDATABASE=test make cabal-test

To build with stack use the following command

stack build

To run the tests with stack use the following command

PGHOST=localhost PGDATABASE=test make stack-test

NB note that the psql test argument must be set for the test suite to run. This is so that other CI environments like stackage or nix that won't have a running test psql instance will not fail on these tests

Changes from the original postgresql-simple-migration (version 0.1)

postgresql-migration is fork of postgresql-simple-migration created when the original postgresql-simple-migration project was archived.

postgresql-migration version 0.2.x introduces some new features that will require some minor changes if you were using a 0.1.x version before

The new features are

There are two ways to move to postgresql-migration

Compatability layer - the simple way, but no new features

1) Replace postgresql-simple-migration with postgresql-migration in your .cabal file 2) Import Database.PostgreSQL.Simple.Migration.V1Compat rather than Database.PostgreSQL.Simple.Migration

All your existing code should work as is

Porting to version 2

The most obvious code change is that you now use a MigrationOptions rather than a MigrationContext.

Version 0.1.x

This what you would have had

 withTransaction con . runMigration $ MigrationContext Pgm.MigrationInitialization True con

Version 0.2.x

Version 2 with the defaultOptions

 runMigration con defaultOptions Pgm.MigrationInitialization

or if you want to change the default options

 let options = defaultOptions { optTransactionControl = TransactionPerRun, optVerbosity = Verbose }
 runMigration con options Pgm.MigrationInitialization

That is all that needs to change. Your migrations scripts etc all remain as is.