MIGRATE ALL THE THINGS!
A general migration framework, with an implementation for database migrations.
Designed to be compatible with a git based work flow where multiple topic branches may exist simultaneously, and be merged into a master branch in unpredictable order.
This is accomplished two ways:
- Migration ids are not assumed to be incremented integers. It is recommended that they be timestamps (e.g. ‘20111202091200’).
- Migrations are considered for completion independently.
Using a 14 digit timestamp will accomodate migrations granularity to a second, reducing the chance of collisions for a distributed team.
In contrast, using a single global version for a store and incremented integers for migration versions, it is possible for a higher numbered migration to get merged to master and deployed before a lower numbered migration, in which case the lower numbered migration would never get run, unless it is renumbered.
Migratus does not use a single global version for a store. It considers each migration independently, and runs all uncompleted migrations in sorted order.
- Add the following code to
resources/migrations/20111206154000-create-foo-table.up.sql
CREATE TABLE IF NOT EXISTS foo(id BIGINT);
- Add the following code to
resources/migrations/20111206154000-create-foo-table.down.sql
DROP TABLE IF EXISTS foo;
If you would like to run multiple statements in your migration, then
separate them with --;;
. For example:
CREATE TABLE IF NOT EXISTS quux(id bigint, name varchar(255)); --;; CREATE INDEX quux_name on quux(name);
This is necessary because JDBC does not have a method that allows you to send multiple SQL commands for execution. Migratus will split your commands, and send the each to the database inside of a transaction.
- Add Migratus as a dependency to your
project.clj
:dependencies [[migratus "0.8.0"]]
Next, create a namespace to manage the migrations:
(my-migrations (:require [migratus.core :as migratus]))
(def config {:store :database :migration-dir "migrations/" :migration-table-name "foo_bar" :db {:classname "org.h2.Driver" :subprotocol "h2" :subname "site.db"}})
;apply pending migrations (migratus/migrate config)
;rollback the last migration applied (migratus/rollback config)
;bring up migrations matching the ids (migratus/up config 20111206154000)
;bring down migrations matching the ids (migratus/down config 20111206154000)
Migratus also provides a convenience function for creating migration files:
;bring down migrations matching the ids (migratus/create config "create-user")
This will result with up/down migration files being created prefixed with the current timestamp, e.g:
20150701134958-create-user.up.sql 20150701134958-create-user.down.sql
- Add migratus-lein as a plugin in addition to the Migratus dependency:
:plugins [[migratus-lein "0.1.1"]]
- Add the following key and value to your project.clj:
:migratus {:store :database :migration-dir "migrations" :db {:classname "com.mysql.jdbc.Driver" :subprotocol "mysql" :subname "//localhost/migratus" :user "root" :password ""}}
To apply pending migrations:
- Run
lein migratus migrate
To rollback the last migration that was applied run:
- Run
lein migratus rollback
Then follow the rest of the above instructions.
Migratus is configured via a configuration map that you pass in as its first parameter. The :store key describes the type of store against which migrations should be run. All other keys/values in the configuration map are store specific.
To run migrations against a database use a :store of :database, and specify the database connection configuration in the :db key of the configuration map. This connection information is passed directly to clojure.java.jdbc. For example:
{:store :database :migration-dir "migrations" :db {:classname "com.mysql.jdbc.Driver" :subprotocol "mysql" :subname "//localhost/migratus" :user "root" :password ""}}
or:
{:store :database :migration-dir "migrations" :db ~(get (System/getenv) "DATABASE_URL")}
The :migration-dir key specifies the directory on the classpath in which to find SQL migration files. Each file should be named with the following pattern “[id]-[name].[direction].sql” where id is a unique integer id (ideally it should be a timestamp) for the migration, name is some human readable description of the migration, and direction is either ‘up’ or ‘down’.
If Migratus is trying to run either the up or down migration and it does not exist, then an Exception will be thrown.
See test/migrations in this repository for an example of how database migrations work.
Migratus can be used programmatically by calling one of the following functions:
Function | Description |
---|---|
migratus.core/migrate | Run ‘up’ for any migrations that have not been run. |
migratus.core/rollback | Run ‘down’ for the last migration that was run. |
migratus.core/up | Run ‘up’ for the specified migration ids. Will skip any migration that is already up. |
migratus.core/down | Run ‘down’ for the specified migration ids. Will skip any migration that is already down. |
See the docstrings of each function for more details.
Migratus can also be used from leiningen if you add it as a plugin dependency.
:plugins [[migratus-lein "0.1.0"]]
And add a configuration :migratus key to your project.clj.
:migratus {:store :database :migration-dir "migrations" :db {:classname "com.mysql.jdbc.Driver" :subprotocol "mysql" :subname "//localhost/migratus" :user "root" :password ""}}
You can then run the following tasks:
Task | Description |
---|---|
lein migratus migrate | Run ‘up’ for any migrations that have not been run. |
lein migratus rollback | Run ‘down’ for the last migration that was run. |
lein migratus up & ids | Run ‘up’ for the specified migration ids. Will skip any migration that is already up. |
lein migratus down & ids | Run ‘down’ for the specified migration ids. Will skip any migration that is already down. |
Copyright © 2012 Paul Stadig
:
Licensed under the Apache License, Version 2.0.