Skip to content

Commit

Permalink
Merge dd0244f into dc8318c
Browse files Browse the repository at this point in the history
  • Loading branch information
bearmug committed Feb 11, 2019
2 parents dc8318c + dd0244f commit b071790
Show file tree
Hide file tree
Showing 25 changed files with 367 additions and 39 deletions.
17 changes: 15 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,26 @@ cache:

services:
- postgresql
- mysql

addons:
postgresql: "9.6"
apt:
sources:
- mysql-5.7-trusty
packages:
- mysql-server
- mysql-client

before_script:
- psql -c "CREATE DATABASE migration;" -U postgres
- psql -c "CREATE USER migration WITH PASSWORD 'migration';" -U postgres
- psql -c "CREATE DATABASE puremigration;" -U postgres
- psql -c "CREATE USER puremigration WITH PASSWORD 'puremigration';" -U postgres

before_install:
- sudo mysql_upgrade --force
- mysql -e "CREATE DATABASE puremigration;"
- mysql -e "CREATE USER 'puremigration'@'localhost' IDENTIFIED BY 'puremigration'";
- mysql -e "GRANT ALL PRIVILEGES ON puremigration.* TO 'puremigration'@'localhost';"

language: erlang

Expand Down
37 changes: 29 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
REBAR = ./rebar3
DOCKER = docker
CONTAINER_NAME = postgres-migration-test-container
CONTAINER_POSTGRES = postgres-migration-test-container
CONTAINER_MYSQL = mysql-migration-test-container

all: clean code-checks test cover

travis: all coveralls

local: format postgres-bounce all postgres-down
local: format db-bounce all db-down

clean:
$(REBAR) clean
Expand All @@ -33,14 +34,34 @@ format:
$(REBAR) fmt

postgres-up:
$(DOCKER) run --name $(CONTAINER_NAME) \
$(DOCKER) run --name $(CONTAINER_POSTGRES) \
-p 5432:5432 \
-e POSTGRES_PASSWORD=migration \
-e POSTGRES_USER=migration \
-e POSTGRES_DB=migration \
-e POSTGRES_PASSWORD=puremigration \
-e POSTGRES_USER=puremigration \
-e POSTGRES_DB=puremigration \
-d postgres:9.6-alpine

postgres-down:
-$(DOCKER) rm -f $(CONTAINER_NAME)
-$(DOCKER) rm -f $(CONTAINER_POSTGRES)

postgres-bounce: postgres-down postgres-up
mysql-up:
$(DOCKER) run --name $(CONTAINER_MYSQL) \
-p 3306:3306 \
-e MYSQL_ALLOW_EMPTY_PASSWORD=true \
-e MYSQL_USER=puremigration \
-e MYSQL_PASSWORD=puremigration \
-e MYSQL_DATABASE=puremigration \
-d mysql:5.7

mysql-wait:
while ! docker exec -it mysql-migration-test-container mysqladmin ping --silent; do \
echo "mysql image starting, wait for 1 second..."; \
sleep 1; \
done; done

mysql-down:
-$(DOCKER) rm -f $(CONTAINER_MYSQL)

db-bounce: postgres-down mysql-down postgres-up mysql-up mysql-wait

db-down: postgres-down mysql-down
81 changes: 70 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Erlang ❤ pure database migrations
> Database version control engine. Effects-free.
> PostgreSQL and MySQL version control engine. Effects-free.
[![Build Status](https://travis-ci.org/bearmug/erlang-pure-migrations.svg?branch=master)](https://travis-ci.org/bearmug/erlang-pure-migrations)
[![Coverage Status](https://coveralls.io/repos/github/bearmug/erlang-pure-migrations/badge.svg?branch=master)](https://coveralls.io/github/bearmug/erlang-pure-migrations?branch=master)
[![Hex.pm](https://img.shields.io/hexpm/v/pure_migrations.svg)](https://hex.pm/packages/pure_migrations)

Migrate your Erlang application PostgreSQL database with no effort.
Migrate your PostgreSQL or MySQL database from Erlang code with no effort.
This amazing toolkit has [one and only](https://en.wikipedia.org/wiki/Unix_philosophy)
purpose - consistently upgrade database schema, using Erlang stack and
plain SQL. Feel free to run it with any PostgreSQL Erlang driver (and see
plain SQL. Feel free to run it with any PostgreSQL/MySQL Erlang library (and see
several ready-to-use examples below). As an extra - do this in
"no side-effects" mode.

Expand All @@ -26,7 +26,10 @@ several ready-to-use examples below). As an extra - do this in
+ [PostgreSQL and processone/p1_pgsql](#postgresql-and--processone-p1-pgsql--https---githubcom-processone-p1-pgsql-)
- [Onboarding comments](#onboarding-comments-2)
- [Code sample](#code-sample-2)
- [No-effects approach and tools used to achieve it](#no-effects-approach-and-tools-used-to-achieve-it)
+ [MySQL and mysql-otp/mysql-otp](#mysql-and--mysql-otp-mysql-otp--https---githubcom-mysql-otp-mysql-otp-)
- [Onboarding comments](#onboarding-comments-3)
- [Code sample](#code-sample-3)
- ["No-effects" approach and tools used to achieve it](#-no-effects--approach-and-tools-used-to-achieve-it)
* [Tool #1: effects externalization](#tool--1--effects-externalization)
* [Tool #2: make effects explicit](#tool--2--make-effects-explicit)
- [Functional programming abstractions used](#functional-programming-abstractions-used)
Expand All @@ -36,11 +39,17 @@ several ready-to-use examples below). As an extra - do this in

# Current limitations
* **up** transactional migration available only. No **downgrade**
or **rollback** possible. Either whole **up** migration completes OK
or failed and rolled back to the state before migration.
calls available. Either whole **up** migration completes OK
or failed and rolled back to the state before migration.
* Validated MySQL implementation obviously featured with
[**implicit commit**](https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html)
behavior, which means that truly transactional MySQL upgrades limited
in scope. At the same time you free to use another MySQL library or
have your own transaction callback, as it is proposed by [API](#quick-start).
* migrations engine **deliberately isolated from any specific
database library**. This way engine user is free to choose from variety
of frameworks (see tested combinations [here](#compatibility-table)) and so on.
database library**. This way engine user is free to choose from variety
of frameworks (see tested combinations [here](#compatibility-table))
and so on.

# Quick start
Just call `pure_migrations:migrate/3` (see specification [here](src/engine.erl#L9)), providing:
Expand All @@ -51,8 +60,8 @@ Just call `pure_migrations:migrate/3` (see specification [here](src/engine.erl#L
Migration logic is idempotent and could be executed multiple times
against the same database with the same migration scripts set. Moreover,
it is safe to migrate your database concurrently (as a part of nodes
startup in scalable environments, for example). Please see verified
integrations and live code snippets below.
startup in scalable environments and if you providing proper transaction
handler). Please see verified integrations and live code snippets below.

## Compatibility table
All integrations validated against PostgreSQL 9.4/9.6
Expand All @@ -62,6 +71,7 @@ All integrations validated against PostgreSQL 9.4/9.6
| postgres | [epgsql/epgsql:4.2.1](https://github.com/epgsql/epgsql/releases/tag/4.2.1) | [epgsql test](test/epgsql_migrations_SUITE.erl)
| postgres | [semiocast/pgsql:v26.0.2](https://github.com/semiocast/pgsql/releases/tag/v26.0.2) | [spgsql test](test/spgsql_migrations_SUITE.erl)
| postgres | [processone/p1_pgsql:1.1.6](https://github.com/processone/p1_pgsql/releases/tag/1.1.6) | [p1pgsql test](test/p1pgsql_migrations_SUITE.erl)
| mysql | [mysql-otp/mysql-otp:1.4.0](https://github.com/mysql-otp/mysql-otp/releases/tag/1.4.0) | [otp_mysql test](test/otp_mysql_migrations_SUITE.erl)
| postgres | any library with basic sql functional | [generic test](test/pure_migrations_SUITE.erl)

## Live integrations
Expand Down Expand Up @@ -211,7 +221,56 @@ Also see examples from live epgsql integration tests
[here](test/p1pgsql_migrations_SUITE.erl)
</details>

# No-effects approach and tools used to achieve it
### MySQL and [mysql-otp/mysql-otp](https://github.com/mysql-otp/mysql-otp)
#### Onboarding comments
+ almost no result-set parsing required
- [implicit commit](https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html)
specifics a kind an obstacle for simple and safe migration
- mysql docker tooling should be operated carefully and ensured for
proper startup before any use
#### Code sample
<details>
<summary>Click to expand</summary>

```erlang
Conn = ?config(conn, Opts),
MigrationCall =
pure_migrations:migrate(
"scripts/folder/path",
fun(F) ->
%% no full-scope tx API available here
%% or use mysql:transaction/2, but please be aware about
%% mysql implicit transactions commit behavior
try F() of
Res -> Res
catch
_:Problem -> {rollback_unavailable, Problem}
end
end,
fun(Q) ->
case mysql:query(Conn, Q) of
{error, Details} -> {error, Details};
{ok,[<<"version">>,<<"filename">>],[]} -> [];
{ok,[<<"version">>,<<"filename">>], Data} ->
[{V, binary_to_list(F)} || [V, F] <- Data];
{ok,[<<"max(version)">>],[[null]]} -> -1;
{ok,[<<"max(version)">>],[[V]]} -> V;
{ok, _} -> ok;
ok -> ok
end
end),
...
%% more preparation steps if needed
...
%% migration call
ok = MigrationCall(),

```
Also see examples from live epgsql integration tests
[here](test/otp_mysql_migrations_SUITE.erl)
</details>

# "No-effects" approach and tools used to achieve it
Oh, **there is more!** Library implemented in the [way](https://en.wikipedia.org/wiki/Pure_function),
that all side-effects either externalized or deferred explicitly. Reasons
are quite common:
Expand Down
1 change: 1 addition & 0 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
{epgsql, {git, "https://github.com/epgsql/epgsql", {tag, "4.2.1"}}},
{spgsql, {git, "https://github.com/semiocast/pgsql", {tag, "v26.0.2"}}},
{p1pgsql, {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.6"}}},
{mysqlotp, {git, "https://github.com/mysql-otp/mysql-otp", {tag, "1.4.0"}}},
{coveralls, {git, "https://github.com/markusn/coveralls-erl", {tag, "v2.0.1"}}}
]},
{eunit_opts , [verbose]},
Expand Down
8 changes: 6 additions & 2 deletions src/dialect_postgres.erl → src/db_dialect.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
-module(dialect_postgres).
-module(db_dialect).

-export([init/0, migrations_done/0, save_migration/2, latest_existing_version/0]).
-export([ init/0
, migrations_done/0
, save_migration/2
, latest_existing_version/0
]).

init() ->
"CREATE TABLE IF NOT EXISTS database_migrations_history (
Expand Down
8 changes: 4 additions & 4 deletions src/pure_migrations.erl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
F :: fun(() -> ok | error()),
R :: fun(() -> ok | error()).
migrate(Path, FTx, FQuery) ->
ok = FQuery(dialect_postgres:init()),
ok = FQuery(db_dialect:init()),
fun() ->
FTx(flatten(map(
find_migrations(Path, FQuery),
Expand All @@ -26,11 +26,11 @@ do_migration(Path, FQuery, {V, F}) ->
compose(
fun() -> file:read_file(ScriptPath) end,
fun({ok, ScriptBody}) ->
ExpectedVersion = FQuery(dialect_postgres:latest_existing_version()) + 1,
ExpectedVersion = FQuery(db_dialect:latest_existing_version()) + 1,
case V of
ExpectedVersion ->
ok = FQuery(ScriptBody),
ok = FQuery(dialect_postgres:save_migration(V, F));
ok = FQuery(db_dialect:save_migration(V, F));
_ -> {
error,
unexpected_version,
Expand All @@ -45,7 +45,7 @@ find_migrations(ScriptsLocation, FQuery) ->
fun(Files) -> filter_script_files(Files, ScriptsLocation, FQuery) end).

filter_script_files({ok, Files}, _Folder, FQuery) ->
MigrationsDone = sets:from_list(FQuery(dialect_postgres:migrations_done())),
MigrationsDone = sets:from_list(FQuery(db_dialect:migrations_done())),
lists:filter(
fun(N) -> not sets:is_element(N, MigrationsDone) end,
lists:keysort(1, [version_and_filename(F) || F <- Files]));
Expand Down
31 changes: 20 additions & 11 deletions test/config/test.config
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
[{postgres, [{
config, [
{host, "localhost"},
{port, 5432},
{database, "migration"},
{username, "migration"},
{secret, "migration"},
{timeout, 10000}
]
}]
}].
[ {postgres, [{
config, [
{host, "localhost"},
{port, 5432},
{database, "puremigration"},
{username, "puremigration"},
{secret, "puremigration"},
{timeout, 10000}
]
}]}
, {mysql, [{
config, [
{host, "localhost"},
{port, 3306},
{database, "puremigration"},
{username, "puremigration"},
{secret, "puremigration"},
{timeout, 10000}
]
}]}].

0 comments on commit b071790

Please sign in to comment.