-
Notifications
You must be signed in to change notification settings - Fork 85
SQL Migrations
SQL migrations can be written in two ways: either as .sql
files, or as .edn
files.
An edn migration file consists of one or more migration maps. A migration map consists of three keys:
-
:id
- a unique identifier for the migration -
:up
- a vector of SQL statements that apply the migration -
:down
- a vector of SQL statements that roll back the migration
So a basic migration file may contain:
[{:id "create-table-foo"
:up ["CREATE TABLE foo (name VARCHAR(100))"]
:down ["DROP TABLE foo"]}]
If there is only one migration in the file, the outer vector may be omitted. The :id
key may also be omitted, in which case the ID will be taken from the filename. For example, a file create-table-foo.edn
could contain:
{:up ["CREATE TABLE foo (name VARCHAR(100))"]
:down ["DROP TABLE foo"]}
The ragtime.sql/load-migrations
function will load one or more migrations from an edn file. If you want to split your migrations into multiple files or resources, then use the ragtime.sql/load-directory
and ragtime.sql/load-resources
.
These sorts of migrations are common, and since version 0.10.0, Ragtime provides a further advance in the form of the :do
key:
[{:id "create-table-foo"
:do [[:create-table foo [name "VARCHAR(100)"]]]}]
The :do
key contains a collection of vectors that represent reversible commands. The above example is equivalent to the previous examples that used :up
and :down
.
You can also omit the migration map entirely:
[[:create-table foo [name "VARCHAR(100)"]]]
For migrations defined solely by a vector, the :id
is generated from the command. In the above case, it would be create-table-foo
- again, equivalent to all our previous examples.
Ragtime currently supports the following commands for the :do
key:
command | description |
---|---|
[:create-table name & columns] |
Create a table with the supplied name and columns. Each column is a vector in the form: [name definition]
|
[:drop-table name] |
Drop the table with the supplied name. |
[:add-column table name definition] |
Add a new column to an existing table with the supplied name and definition. |
[:rename-column table old-name new-name] |
Rename an existing column in an existing table. |
[:drop-column table name] |
Drop an existing column on a table with the supplied name. |
[:create-index index-name table columns] |
Create a new index with the supplied name given a table and a vector of column names. |
[:drop-index index-name] |
Drop an existing index. |
It's possible to have some migrations that use :do
and some migrations that use :up
and :down
instead. This can be necessary if more complex migrations are required. However, be careful about mixing them, as the :drop-*
commands rely on searching for their equivalent :create-*
commands in order to determine how to generate their rollback SQL.
For example:
[[:create-table foo [name "VARCHAR(100)"]]
[:drop-table foo]]
Will produce:
[{:id "create-table-foo"
:up ["CREATE TABLE foo (name VARCHAR(100))"]
:down ["DROP TABLE foo"]}
{:id "drop-table-foo"
:up ["DROP TABLE foo"]
:down ["CREATE TABLE foo (name VARCHAR(100))"]}]
Note that in order to generate the :down
key of the second migration, we need to know the original definition of the table. Ragtime is smart enough to account for column changes, but can't parse custom SQL. This means a migration file like this will fail:
[{:id "create-table-foo"
:up ["CREATE TABLE foo (name VARCHAR(100))"]
:down ["DROP TABLE foo"]}
[:drop-table foo]] ;; will fail, as it doesn't know how to recreate the table
Alternatively, we can write the SQL directly. Each migration is split into two files, an 'up' and a 'down'. For example, you might have an 'up' migration located at migrations/001-create-foo.up.sql
:
CREATE TABLE foo (name VARCHAR(100));
And a 'down' migration at migrations/001-create-foo.down.sql
:
DROP TABLE foo;
Ragtime uses the extension (.up.sql
or .down.sql
) to determine whether the file represents the 'up' or 'down' part of the migration.
Different SQL databases have different restrictions on the commands that can be sent in a single message, so sometimes we need to split up a migration in order to get the database to accept it. This can be done in two ways, the first being to insert a marker --;;
between statements. For example:
CREATE TABLE foo (name VARCHAR(100));
--;;
INSERT INTO foo VALUES ('alice'), ('bob');
The second way is to split the migration file into several numbered files to indicate the order they should be applied. For example, we could split up 001-create-foo.up.sql
into 001-create-foo.up.1.sql
:
CREATE TABLE foo (name VARCHAR(100));
And 001-create-foo.up.2.sql
:
INSERT INTO foo VALUES ('alice'), ('bob');
When splitting up a migration like this, the files should end with up.<n>.sql
or down.<n>.sql
, where <n>
is sequence of numeric digits. Note that alphanumeric ordering is used, so if a single migration has more than 9 parts, then start off with 01
, 02
, etc.
Because SQL notation requires multiple files, you'll need to use ragtime.sql/load-directory
or ragtime.sql/load-resources
to load them.