Skip to content

The Migrations Framework

In the User Guide we saw how to declare a schema for an already created database and use it to perform queries. Beam can also manage a database schema based on Haskell datatypes you feed it.

The migrations framework is meant to be a robust and modular way of managing schema changes. It is an optional part of beam provided in the beam-migrate package.

Install the migrations framework by running.

$ cabal install beam-migrate
# or
$ stack install beam-migrate

Basic concepts

In the user guide, we saw how we can use defaultDbSettings to generate default metadata that can be used to access the database. This default metadata is enough to query, but not enough for beam-migrate. beam-migrate offers the defaultMigratableDbSettings function, which annotates the database schema with additional information. Whereas defaultDbSettings yields a value of type DatabaseSettings be db, defaultMigratableDbSettings yields a value of type CheckedDatabaseSettings be db. You can recover a DatabaseSettings be db from a CheckedDatabaseSettings be db value by applying the unCheckDatabase function.

The CheckedDatabaseSettings value contains the original DatabaseSettings along with a series of predicates. Each predicate describes one aspect of the database. As far as beam-migrate is concerned, each database schema is fully specified by the set of predicates that apply to it. beam-migrate calls this the checked type of the database.

For example, a database schema that consists of one table named table with no fields is represented uniquely by the checked type of [TableExistsPredicate "table"]. If you add a field field1 of type INT to the table, then the checked type becomes [TableExistsPredicate "table", TableHasColumn "table" "field1" intType].

Note

The types are a bit more complicated than what they appear. In particular, a predicate can be of any type that satisfies the DatabasePredicate type class. The predicates can be stored in a (heterogenous) list because they are wrapped in the SomeDatabasePredicate GADT that holds the type class instance as well.

Usage modes

beam-migrate can be used as a library or a command-line tool in managed or unmanaged mode.

The beam-migrate library

The beam-migrate library provides syntax definitions for common SQL DDL tasks. It also provides types for expressing migrations as transformations of one or more schemas to another. beam-migrate offers a built-in way to apply these migrations to a production database, running only those migrations that are necessary. You can also directly interpret the beam-migrate DSL to hook your Haskell migrations into your own system.

beam-migrate is described in more detail in the beam-migrate migrations guide

The beam-migrate tool

There is an optional beam-migrate command line tool, available in the beam-migrate-cli package.

The beam-migrate tool can generate a beam schema from a pre-existing database, manage migrations for several production databases, automatically generate migrations between two schemas, and much more. It is rather opinionated, and is described in more detail in the beam-migrate CLI guide

Automatic migration generation

Given two CheckedDatabaseSettings values, beam-migrate can generate a set of SQL steps that will transform one schema to another. The generation of such steps is an exceedingly difficult problem in general. beam-migrate can automatically handle most common cases, but it will not always succeed. In this case, it can present to you a list of steps it thinks are best as well as what remains to be solved.

The migration generation is implemented as a proof search in linear logic1. In particular, beam-migrate views a migration as a linear logic proof of the form a ⊸ b, where a is the set of predicates of the original schema and b is the set of predicates in the target schema. beam-migrate ships with a set of default proof steps. Backends can add to these steps for backend-specific predicates.

Note

At this time, Haskell does not allow the expression of linear programs (this will change with the introduction of linear types). Thus, migrations written in Haskell are not checked by GHC for linear-ness, but beam-migrate will validate such migrations at run-time to the best of its ability.

The migration prover may not be able to find a migration in a sufficiently short period of time. beam-migrate's algorithm is designed to terminate, but this may take a while. Additionally, the prover will not automatically generate steps for some migrations. For example, beam-migrate will never rename a table without explicit instructions.

Advantages of checked migrations

Unlike other database migration frameworks, the checking process allows beam-migrate to be sure that the migration you specify will result in the database type you want. Also, checked migrations allow the programmer to verify that the database they are accessing indeed matches what their schema expects.


  1. Linear logic is a type of logic first described by Jean-Yves Gerard. In particular, it constrains the weakening and strengthening rules of classical logic. Intuitively, you can think of it as forcing the rule that each assumption is used exactly once to produce the result. Read more on Wikipedia