From d1672112514fdeed0fcd118c6a46fc119f5e019b Mon Sep 17 00:00:00 2001 From: bearmug Date: Sun, 10 Feb 2019 21:33:06 +0100 Subject: [PATCH 01/20] #10 initial mysql artefacts --- .travis.yml | 7 +++++++ Makefile | 11 +++++++++++ rebar.config | 1 + 3 files changed, 19 insertions(+) diff --git a/.travis.yml b/.travis.yml index 13bd43d..eba504d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,16 @@ 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 diff --git a/Makefile b/Makefile index 43954e0..5458371 100644 --- a/Makefile +++ b/Makefile @@ -43,4 +43,15 @@ postgres-up: postgres-down: -$(DOCKER) rm -f $(CONTAINER_NAME) +mysql-up: + $(DOCKER) run --name $(CONTAINER_NAME) \ + -p 5432:5432 \ + -e POSTGRES_PASSWORD=migration \ + -e POSTGRES_USER=migration \ + -e POSTGRES_DB=migration \ + -d postgres:9.6-alpine + +mysql-down: + -$(DOCKER) rm -f $(CONTAINER_NAME) + postgres-bounce: postgres-down postgres-up \ No newline at end of file diff --git a/rebar.config b/rebar.config index cb0173a..96456a3 100644 --- a/rebar.config +++ b/rebar.config @@ -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]}, From 71fdfe8791f500c23bd033e8b4b0b86360565c78 Mon Sep 17 00:00:00 2001 From: bearmug Date: Sun, 10 Feb 2019 21:51:47 +0100 Subject: [PATCH 02/20] #10 run seamless local docker integration --- Makefile | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 5458371..6868482 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -33,7 +34,7 @@ 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 \ @@ -41,17 +42,20 @@ postgres-up: -d postgres:9.6-alpine postgres-down: - -$(DOCKER) rm -f $(CONTAINER_NAME) + -$(DOCKER) rm -f $(CONTAINER_POSTGRES) mysql-up: - $(DOCKER) run --name $(CONTAINER_NAME) \ - -p 5432:5432 \ - -e POSTGRES_PASSWORD=migration \ - -e POSTGRES_USER=migration \ - -e POSTGRES_DB=migration \ - -d postgres:9.6-alpine + $(DOCKER) run --name $(CONTAINER_MYSQL) \ + -p 3306:3306 \ + -e MYSQL_ROOT_PASSWORD=puremigration \ + -e MYSQL_USER=puremigration \ + -e MYSQL_PASSWORD=puremigration \ + -e MYSQL_DATABASE=puremigration \ + -d mysql:5.7 mysql-down: - -$(DOCKER) rm -f $(CONTAINER_NAME) + -$(DOCKER) rm -f $(CONTAINER_MYSQL) + +db-bounce: postgres-down mysql-down postgres-up mysql-up -postgres-bounce: postgres-down postgres-up \ No newline at end of file +db-down: postgres-down mysql-down From 0ca392d8c365eabf3fb6f1a941bd2745bf460fd6 Mon Sep 17 00:00:00 2001 From: bearmug Date: Sun, 10 Feb 2019 21:53:42 +0100 Subject: [PATCH 03/20] #10 adjust databases configuration --- .travis.yml | 4 ++-- Makefile | 6 +++--- test/config/test.config | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index eba504d..a061b62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,8 @@ addons: - 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 language: erlang diff --git a/Makefile b/Makefile index 6868482..d4f8b20 100644 --- a/Makefile +++ b/Makefile @@ -36,9 +36,9 @@ format: postgres-up: $(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: diff --git a/test/config/test.config b/test/config/test.config index 04811d9..42cf1b1 100644 --- a/test/config/test.config +++ b/test/config/test.config @@ -2,9 +2,9 @@ config, [ {host, "localhost"}, {port, 5432}, - {database, "migration"}, - {username, "migration"}, - {secret, "migration"}, + {database, "puremigration"}, + {username, "puremigration"}, + {secret, "puremigration"}, {timeout, 10000} ] }] From 5d4438734776f1322b11d7530407a2d8f566ecc4 Mon Sep 17 00:00:00 2001 From: bearmug Date: Sun, 10 Feb 2019 22:03:30 +0100 Subject: [PATCH 04/20] #10 travis mysql database --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index a061b62..4edd3dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,11 @@ before_script: - psql -c "CREATE DATABASE puremigration;" -U postgres - psql -c "CREATE USER puremigration WITH PASSWORD 'puremigration';" -U postgres +before_install: + - mysql -e "CREATE DATABASE puremigration;" + - mysql -e "CREATE USER 'puremigration@localhost' IDENTIFIED BY 'puremigration'"; + - mysql -e "GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP ON puremigration.* TO 'puremigration@localhost';" + language: erlang otp_release: From 3d13ab202f08e0dea4816ae5a5cca5a9195aa32d Mon Sep 17 00:00:00 2001 From: bearmug Date: Sun, 10 Feb 2019 22:05:31 +0100 Subject: [PATCH 05/20] #10 mysql tests connectivity configuration --- test/config/test.config | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/test/config/test.config b/test/config/test.config index 42cf1b1..8017f6a 100644 --- a/test/config/test.config +++ b/test/config/test.config @@ -1,11 +1,20 @@ -[{postgres, [{ - config, [ - {host, "localhost"}, - {port, 5432}, - {database, "puremigration"}, - {username, "puremigration"}, - {secret, "puremigration"}, - {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} + ] + }]}]. From e2ebd285f4f2b032dc6cfa06a9718b9324488b23 Mon Sep 17 00:00:00 2001 From: bearmug Date: Sun, 10 Feb 2019 22:15:20 +0100 Subject: [PATCH 06/20] #10 upgrade travis mysql --- .travis.yml | 1 + src/dialect_mysql.erl | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/dialect_mysql.erl diff --git a/.travis.yml b/.travis.yml index 4edd3dc..86b2b4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ before_script: - 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 SELECT,INSERT,UPDATE,DELETE,CREATE,DROP ON puremigration.* TO 'puremigration@localhost';" diff --git a/src/dialect_mysql.erl b/src/dialect_mysql.erl new file mode 100644 index 0000000..199edbe --- /dev/null +++ b/src/dialect_mysql.erl @@ -0,0 +1,21 @@ +-module(dialect_mysql). + +-export([init/0, migrations_done/0, save_migration/2, latest_existing_version/0]). + +init() -> + "CREATE TABLE IF NOT EXISTS database_migrations_history ( + version INTEGER NOT NULL PRIMARY KEY, + filename TEXT NOT NULL, + creation_timestamp TIMESTAMP NOT NULL DEFAULT NOW() + )". + +migrations_done() -> + "SELECT version, filename FROM database_migrations_history". + +save_migration(Version, Filename) -> + lists:flatten(io_lib:format( + "INSERT INTO database_migrations_history(version, filename) VALUES (~w, '~s')", + [Version, Filename])). + +latest_existing_version() -> + "SELECT max(version) FROM database_migrations_history". From 489796aa49f47cc5be9616c11c1412faddf93a15 Mon Sep 17 00:00:00 2001 From: bearmug Date: Sun, 10 Feb 2019 22:32:06 +0100 Subject: [PATCH 07/20] #10 redesign database dialect a little --- src/{dialect_mysql.erl => db_dialect.erl} | 8 ++++++-- src/dialect_postgres.erl | 21 --------------------- src/pure_migrations.erl | 8 ++++---- test/config/test.config | 18 +++++++++--------- 4 files changed, 19 insertions(+), 36 deletions(-) rename src/{dialect_mysql.erl => db_dialect.erl} (80%) delete mode 100644 src/dialect_postgres.erl diff --git a/src/dialect_mysql.erl b/src/db_dialect.erl similarity index 80% rename from src/dialect_mysql.erl rename to src/db_dialect.erl index 199edbe..c6c7685 100644 --- a/src/dialect_mysql.erl +++ b/src/db_dialect.erl @@ -1,6 +1,10 @@ --module(dialect_mysql). +-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 ( diff --git a/src/dialect_postgres.erl b/src/dialect_postgres.erl deleted file mode 100644 index 15bf272..0000000 --- a/src/dialect_postgres.erl +++ /dev/null @@ -1,21 +0,0 @@ --module(dialect_postgres). - --export([init/0, migrations_done/0, save_migration/2, latest_existing_version/0]). - -init() -> - "CREATE TABLE IF NOT EXISTS database_migrations_history ( - version INTEGER NOT NULL PRIMARY KEY, - filename TEXT NOT NULL, - creation_timestamp TIMESTAMP NOT NULL DEFAULT NOW() - )". - -migrations_done() -> - "SELECT version, filename FROM database_migrations_history". - -save_migration(Version, Filename) -> - lists:flatten(io_lib:format( - "INSERT INTO database_migrations_history(version, filename) VALUES (~w, '~s')", - [Version, Filename])). - -latest_existing_version() -> - "SELECT max(version) FROM database_migrations_history". diff --git a/src/pure_migrations.erl b/src/pure_migrations.erl index b8d0149..a471efb 100644 --- a/src/pure_migrations.erl +++ b/src/pure_migrations.erl @@ -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), @@ -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, @@ -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])); diff --git a/test/config/test.config b/test/config/test.config index 8017f6a..7d503eb 100644 --- a/test/config/test.config +++ b/test/config/test.config @@ -9,12 +9,12 @@ ] }]} , {mysql, [{ - config, [ - {host, "localhost"}, - {port, 3306}, - {database, "puremigration"}, - {username, "puremigration"}, - {secret, "puremigration"}, - {timeout, 10000} - ] - }]}]. + config, [ + {host, "localhost"}, + {port, 3306}, + {database, "puremigration"}, + {username, "puremigration"}, + {secret, "puremigration"}, + {timeout, 10000} + ] + }]}]. From 6c9cb1031ee4ae7da57f1b698004985ae9f133c1 Mon Sep 17 00:00:00 2001 From: bearmug Date: Sun, 10 Feb 2019 22:36:12 +0100 Subject: [PATCH 08/20] #10 add mysql otp test suite --- test/otp_mysql_migrations_SUITE.erl | 174 ++++++++++++++++++ .../00_initial_version.sql | 0 .../00_initial_version.sql | 0 .../01-two-scripts-test/01_next_version.sql | 0 .../20_initial_version.sql | 0 .../03-migration-gap/00_initial_version.sql | 0 .../03-migration-gap/02_gap_version.sql | 0 .../00_initial_version.sql | 0 .../01_faulty_migration.sql | 0 ...UITE.erl => p1_pgsql_migrations_SUITE.erl} | 2 +- .../00_initial_version.sql | 7 + .../00_initial_version.sql | 7 + .../01-two-scripts-test/01_next_version.sql | 5 + .../20_initial_version.sql | 7 + .../03-migration-gap/00_initial_version.sql | 7 + .../03-migration-gap/02_gap_version.sql | 5 + .../00_initial_version.sql | 7 + .../01_faulty_migration.sql | 1 + 18 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 test/otp_mysql_migrations_SUITE.erl rename test/{p1pgsql_migrations_SUITE_data => otp_mysql_migrations_SUITE_data}/00-single-script-test/00_initial_version.sql (100%) rename test/{p1pgsql_migrations_SUITE_data => otp_mysql_migrations_SUITE_data}/01-two-scripts-test/00_initial_version.sql (100%) rename test/{p1pgsql_migrations_SUITE_data => otp_mysql_migrations_SUITE_data}/01-two-scripts-test/01_next_version.sql (100%) rename test/{p1pgsql_migrations_SUITE_data => otp_mysql_migrations_SUITE_data}/02-wrong-initial-version/20_initial_version.sql (100%) rename test/{p1pgsql_migrations_SUITE_data => otp_mysql_migrations_SUITE_data}/03-migration-gap/00_initial_version.sql (100%) rename test/{p1pgsql_migrations_SUITE_data => otp_mysql_migrations_SUITE_data}/03-migration-gap/02_gap_version.sql (100%) rename test/{p1pgsql_migrations_SUITE_data => otp_mysql_migrations_SUITE_data}/04-last-migration-fail/00_initial_version.sql (100%) rename test/{p1pgsql_migrations_SUITE_data => otp_mysql_migrations_SUITE_data}/04-last-migration-fail/01_faulty_migration.sql (100%) rename test/{p1pgsql_migrations_SUITE.erl => p1_pgsql_migrations_SUITE.erl} (99%) create mode 100644 test/p1_pgsql_migrations_SUITE_data/00-single-script-test/00_initial_version.sql create mode 100644 test/p1_pgsql_migrations_SUITE_data/01-two-scripts-test/00_initial_version.sql create mode 100644 test/p1_pgsql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql create mode 100644 test/p1_pgsql_migrations_SUITE_data/02-wrong-initial-version/20_initial_version.sql create mode 100644 test/p1_pgsql_migrations_SUITE_data/03-migration-gap/00_initial_version.sql create mode 100644 test/p1_pgsql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql create mode 100644 test/p1_pgsql_migrations_SUITE_data/04-last-migration-fail/00_initial_version.sql create mode 100644 test/p1_pgsql_migrations_SUITE_data/04-last-migration-fail/01_faulty_migration.sql diff --git a/test/otp_mysql_migrations_SUITE.erl b/test/otp_mysql_migrations_SUITE.erl new file mode 100644 index 0000000..e936267 --- /dev/null +++ b/test/otp_mysql_migrations_SUITE.erl @@ -0,0 +1,174 @@ +-module(otp_mysql_migrations_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile(export_all). + +all() -> [ migrate_one_script_test + , migrate_few_scripts_test + , incremental_migration_test + , wrong_initial_version_test + , migration_gap_test + , transactional_migration_test + ]. + +migrate_one_script_test(Opts) -> + Conn = ?config(conn, Opts), + PreparedCall = pure_migrations:migrate( + filename:join([?config(data_dir, Opts), "00-single-script-test"]), + p1pgsql_tx_fun(Conn), + p1pgsql_query_fun(Conn) + ), + ?assertEqual(ok, PreparedCall()), + ?assertMatch( + {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, + pgsql:squery(Conn, "select max(version) from database_migrations_history")). + +migrate_few_scripts_test(Opts) -> + Conn = ?config(conn, Opts), + PreparedCall = pure_migrations:migrate( + filename:join([?config(data_dir, Opts), "01-two-scripts-test"]), + p1pgsql_tx_fun(Conn), + p1pgsql_query_fun(Conn) + ), + ?assertEqual(ok, PreparedCall()), + ?assertMatch( + {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["1"]]}]}, + pgsql:squery(Conn, "select max(version) from database_migrations_history")), + ?assertMatch( + {ok, [{"SELECT 1", [{"count", text, _, _, _, _, _}], [["1"]]}]}, + pgsql:squery(Conn, "select count(*) from fruit where color = 'yellow'")). + +incremental_migration_test(Opts) -> + Conn = ?config(conn, Opts), + MigrationStep1 = pure_migrations:migrate( + filename:join([?config(data_dir, Opts), "00-single-script-test"]), + p1pgsql_tx_fun(Conn), p1pgsql_query_fun(Conn) + ), + MigrationStep2 = pure_migrations:migrate( + filename:join([?config(data_dir, Opts), "01-two-scripts-test"]), + p1pgsql_tx_fun(Conn), p1pgsql_query_fun(Conn) + ), + + %% assert migrations table created and nothing done + ?assertMatch( + {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[null]]}]}, + pgsql:squery(Conn, "select max(version) from database_migrations_history")), + ?assertMatch( + {ok, [{error, [{severity, 'ERROR'}|_]}]}, + pgsql:squery(Conn, "select count(*) from fruit")), + + %% assert step 1 migration + ok = MigrationStep1(), + ?assertMatch( + {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, + pgsql:squery(Conn, "select max(version) from database_migrations_history")), + + %% assert step 2 migration + ok =MigrationStep2(), + ?assertMatch( + {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["1"]]}]}, + pgsql:squery(Conn, "select max(version) from database_migrations_history")), + ?assertMatch( + {ok, [{"SELECT 1", [{"count", text, _, _, _, _, _}], [["1"]]}]}, + pgsql:squery(Conn, "select count(*) from fruit where color = 'yellow'")). + +wrong_initial_version_test(Opts) -> + Conn = ?config(conn, Opts), + PreparedCall = pure_migrations:migrate( + filename:join([?config(data_dir, Opts), "02-wrong-initial-version"]), + p1pgsql_tx_fun(Conn), + p1pgsql_query_fun(Conn) + ), + ?assertEqual( + {rollback, {badmatch, {error, unexpected_version, {expected, 0, supplied, 20}}}}, + PreparedCall()). + +migration_gap_test(Opts) -> + Conn = ?config(conn, Opts), + MigrationStep1 = pure_migrations:migrate( + filename:join([?config(data_dir, Opts), "00-single-script-test"]), + p1pgsql_tx_fun(Conn), p1pgsql_query_fun(Conn) + ), + MigrationStep2 = pure_migrations:migrate( + filename:join([?config(data_dir, Opts), "03-migration-gap"]), + p1pgsql_tx_fun(Conn), p1pgsql_query_fun(Conn) + ), + + %% assert step 1 migration + ok = MigrationStep1(), + ?assertMatch( + {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, + pgsql:squery(Conn, "select max(version) from database_migrations_history")), + + %% assert step 2 failed migration + ?assertEqual( + {rollback, {badmatch, {error, unexpected_version, {expected, 1, supplied, 2}}}}, + MigrationStep2()), + ?assertMatch( + {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, + pgsql:squery(Conn, "select max(version) from database_migrations_history")), + ?assertMatch( + {ok, [{error, [{severity, 'ERROR'}|_]}]}, + pgsql:squery(Conn, "select count(*) from fruit where color = 'yellow'")). + +transactional_migration_test(Opts) -> + Conn = ?config(conn, Opts), + PreparedCall = pure_migrations:migrate( + filename:join([?config(data_dir, Opts), "04-last-migration-fail"]), + p1pgsql_tx_fun(Conn), + p1pgsql_query_fun(Conn) + ), + ?assertMatch( + {rollback, {badmatch, {error, [{severity,'ERROR'}|_]}}}, + PreparedCall()), + ?assertMatch( + {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[null]]}]}, + pgsql:squery(Conn, "select max(version) from database_migrations_history")), + ?assertMatch( + {ok, [{error, [{severity,'ERROR'}|_]}]}, + pgsql:squery(Conn, "select count(*) from fruit")). + +p1pgsql_query_fun(Conn) -> + fun(Q) -> + case pgsql:squery(Conn, Q) of + {ok, [{error, Details}]} -> {error, Details}; + {ok, [{_, [ + {"version", text, _, _, _, _, _}, + {"filename", text, _, _, _, _, _}], Data}]} -> + [{list_to_integer(V), F} || [V, F] <- Data]; + {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[null]]}]} -> -1; + {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[N]]}]} -> + list_to_integer(N); + {ok, _} -> ok + end + end. + +p1pgsql_tx_fun(Conn) -> + fun(F) -> + pgsql:squery(Conn, "BEGIN"), + try F() of + Res -> + pgsql:squery(Conn, "COMMIT"), + Res + catch + _:Problem -> + pgsql:squery(Conn, "ROLLBACK"), + {rollback, Problem} + end + end. + +init_per_testcase(_TestCase, Opts) -> + {ok, [{host, Host}, + {port, _Port}, + {database, Database}, + {username, Username}, + {secret, Secret}, + {timeout, _Timeout}]} = application:get_env(postgres, config), + {ok, C} = pgsql:connect(Host, Database, Username, Secret), + {ok,["DROP TABLE"]} = pgsql:squery(C, "DROP TABLE IF EXISTS database_migrations_history, fruit"), + [{conn, C}|Opts]. + +end_per_testcase(_TestCase, Opts) -> + ok = pgsql:terminate(?config(conn, Opts)). diff --git a/test/p1pgsql_migrations_SUITE_data/00-single-script-test/00_initial_version.sql b/test/otp_mysql_migrations_SUITE_data/00-single-script-test/00_initial_version.sql similarity index 100% rename from test/p1pgsql_migrations_SUITE_data/00-single-script-test/00_initial_version.sql rename to test/otp_mysql_migrations_SUITE_data/00-single-script-test/00_initial_version.sql diff --git a/test/p1pgsql_migrations_SUITE_data/01-two-scripts-test/00_initial_version.sql b/test/otp_mysql_migrations_SUITE_data/01-two-scripts-test/00_initial_version.sql similarity index 100% rename from test/p1pgsql_migrations_SUITE_data/01-two-scripts-test/00_initial_version.sql rename to test/otp_mysql_migrations_SUITE_data/01-two-scripts-test/00_initial_version.sql diff --git a/test/p1pgsql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql b/test/otp_mysql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql similarity index 100% rename from test/p1pgsql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql rename to test/otp_mysql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql diff --git a/test/p1pgsql_migrations_SUITE_data/02-wrong-initial-version/20_initial_version.sql b/test/otp_mysql_migrations_SUITE_data/02-wrong-initial-version/20_initial_version.sql similarity index 100% rename from test/p1pgsql_migrations_SUITE_data/02-wrong-initial-version/20_initial_version.sql rename to test/otp_mysql_migrations_SUITE_data/02-wrong-initial-version/20_initial_version.sql diff --git a/test/p1pgsql_migrations_SUITE_data/03-migration-gap/00_initial_version.sql b/test/otp_mysql_migrations_SUITE_data/03-migration-gap/00_initial_version.sql similarity index 100% rename from test/p1pgsql_migrations_SUITE_data/03-migration-gap/00_initial_version.sql rename to test/otp_mysql_migrations_SUITE_data/03-migration-gap/00_initial_version.sql diff --git a/test/p1pgsql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql b/test/otp_mysql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql similarity index 100% rename from test/p1pgsql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql rename to test/otp_mysql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql diff --git a/test/p1pgsql_migrations_SUITE_data/04-last-migration-fail/00_initial_version.sql b/test/otp_mysql_migrations_SUITE_data/04-last-migration-fail/00_initial_version.sql similarity index 100% rename from test/p1pgsql_migrations_SUITE_data/04-last-migration-fail/00_initial_version.sql rename to test/otp_mysql_migrations_SUITE_data/04-last-migration-fail/00_initial_version.sql diff --git a/test/p1pgsql_migrations_SUITE_data/04-last-migration-fail/01_faulty_migration.sql b/test/otp_mysql_migrations_SUITE_data/04-last-migration-fail/01_faulty_migration.sql similarity index 100% rename from test/p1pgsql_migrations_SUITE_data/04-last-migration-fail/01_faulty_migration.sql rename to test/otp_mysql_migrations_SUITE_data/04-last-migration-fail/01_faulty_migration.sql diff --git a/test/p1pgsql_migrations_SUITE.erl b/test/p1_pgsql_migrations_SUITE.erl similarity index 99% rename from test/p1pgsql_migrations_SUITE.erl rename to test/p1_pgsql_migrations_SUITE.erl index 95a8db7..93c8fc1 100644 --- a/test/p1pgsql_migrations_SUITE.erl +++ b/test/p1_pgsql_migrations_SUITE.erl @@ -1,4 +1,4 @@ --module(p1pgsql_migrations_SUITE). +-module(p1_pgsql_migrations_SUITE). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). diff --git a/test/p1_pgsql_migrations_SUITE_data/00-single-script-test/00_initial_version.sql b/test/p1_pgsql_migrations_SUITE_data/00-single-script-test/00_initial_version.sql new file mode 100644 index 0000000..5853b2e --- /dev/null +++ b/test/p1_pgsql_migrations_SUITE_data/00-single-script-test/00_initial_version.sql @@ -0,0 +1,7 @@ +CREATE TABLE fruit ( + id SERIAL, + name TEXT NOT NULL +); +INSERT INTO fruit (name) VALUES ( + 'apple' +); \ No newline at end of file diff --git a/test/p1_pgsql_migrations_SUITE_data/01-two-scripts-test/00_initial_version.sql b/test/p1_pgsql_migrations_SUITE_data/01-two-scripts-test/00_initial_version.sql new file mode 100644 index 0000000..5853b2e --- /dev/null +++ b/test/p1_pgsql_migrations_SUITE_data/01-two-scripts-test/00_initial_version.sql @@ -0,0 +1,7 @@ +CREATE TABLE fruit ( + id SERIAL, + name TEXT NOT NULL +); +INSERT INTO fruit (name) VALUES ( + 'apple' +); \ No newline at end of file diff --git a/test/p1_pgsql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql b/test/p1_pgsql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql new file mode 100644 index 0000000..95d3af5 --- /dev/null +++ b/test/p1_pgsql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql @@ -0,0 +1,5 @@ +ALTER TABLE fruit + ADD COLUMN color TEXT NOT NULL DEFAULT 'green'; +INSERT INTO fruit (name, color) VALUES ( + 'lemon', 'yellow' +); \ No newline at end of file diff --git a/test/p1_pgsql_migrations_SUITE_data/02-wrong-initial-version/20_initial_version.sql b/test/p1_pgsql_migrations_SUITE_data/02-wrong-initial-version/20_initial_version.sql new file mode 100644 index 0000000..5853b2e --- /dev/null +++ b/test/p1_pgsql_migrations_SUITE_data/02-wrong-initial-version/20_initial_version.sql @@ -0,0 +1,7 @@ +CREATE TABLE fruit ( + id SERIAL, + name TEXT NOT NULL +); +INSERT INTO fruit (name) VALUES ( + 'apple' +); \ No newline at end of file diff --git a/test/p1_pgsql_migrations_SUITE_data/03-migration-gap/00_initial_version.sql b/test/p1_pgsql_migrations_SUITE_data/03-migration-gap/00_initial_version.sql new file mode 100644 index 0000000..5853b2e --- /dev/null +++ b/test/p1_pgsql_migrations_SUITE_data/03-migration-gap/00_initial_version.sql @@ -0,0 +1,7 @@ +CREATE TABLE fruit ( + id SERIAL, + name TEXT NOT NULL +); +INSERT INTO fruit (name) VALUES ( + 'apple' +); \ No newline at end of file diff --git a/test/p1_pgsql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql b/test/p1_pgsql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql new file mode 100644 index 0000000..95d3af5 --- /dev/null +++ b/test/p1_pgsql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql @@ -0,0 +1,5 @@ +ALTER TABLE fruit + ADD COLUMN color TEXT NOT NULL DEFAULT 'green'; +INSERT INTO fruit (name, color) VALUES ( + 'lemon', 'yellow' +); \ No newline at end of file diff --git a/test/p1_pgsql_migrations_SUITE_data/04-last-migration-fail/00_initial_version.sql b/test/p1_pgsql_migrations_SUITE_data/04-last-migration-fail/00_initial_version.sql new file mode 100644 index 0000000..5853b2e --- /dev/null +++ b/test/p1_pgsql_migrations_SUITE_data/04-last-migration-fail/00_initial_version.sql @@ -0,0 +1,7 @@ +CREATE TABLE fruit ( + id SERIAL, + name TEXT NOT NULL +); +INSERT INTO fruit (name) VALUES ( + 'apple' +); \ No newline at end of file diff --git a/test/p1_pgsql_migrations_SUITE_data/04-last-migration-fail/01_faulty_migration.sql b/test/p1_pgsql_migrations_SUITE_data/04-last-migration-fail/01_faulty_migration.sql new file mode 100644 index 0000000..951bb93 --- /dev/null +++ b/test/p1_pgsql_migrations_SUITE_data/04-last-migration-fail/01_faulty_migration.sql @@ -0,0 +1 @@ +GARBAGE scrip4 h3r3!@ \ No newline at end of file From 8ba4283f85a14f12ce612caeace50bd5846cf26c Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 00:22:38 +0100 Subject: [PATCH 09/20] #10 spin up first real mysql test --- Makefile | 10 ++- test/otp_mysql_migrations_SUITE.erl | 97 ++++++++++++++++------------- 2 files changed, 60 insertions(+), 47 deletions(-) diff --git a/Makefile b/Makefile index d4f8b20..6e2f308 100644 --- a/Makefile +++ b/Makefile @@ -47,15 +47,21 @@ postgres-down: mysql-up: $(DOCKER) run --name $(CONTAINER_MYSQL) \ -p 3306:3306 \ - -e MYSQL_ROOT_PASSWORD=puremigration \ + -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 +db-bounce: postgres-down mysql-down postgres-up mysql-up mysql-wait db-down: postgres-down mysql-down diff --git a/test/otp_mysql_migrations_SUITE.erl b/test/otp_mysql_migrations_SUITE.erl index e936267..f965031 100644 --- a/test/otp_mysql_migrations_SUITE.erl +++ b/test/otp_mysql_migrations_SUITE.erl @@ -6,80 +6,80 @@ -compile(export_all). all() -> [ migrate_one_script_test - , migrate_few_scripts_test - , incremental_migration_test - , wrong_initial_version_test - , migration_gap_test - , transactional_migration_test + %% , migrate_few_scripts_test + %% , incremental_migration_test + %% , wrong_initial_version_test + %% , migration_gap_test + %% , transactional_migration_test ]. migrate_one_script_test(Opts) -> Conn = ?config(conn, Opts), PreparedCall = pure_migrations:migrate( filename:join([?config(data_dir, Opts), "00-single-script-test"]), - p1pgsql_tx_fun(Conn), - p1pgsql_query_fun(Conn) + otp_mysql_tx_fun(Conn), + otp_mysql_query_fun(Conn) ), ?assertEqual(ok, PreparedCall()), ?assertMatch( - {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, - pgsql:squery(Conn, "select max(version) from database_migrations_history")). + {ok,[<<"max(version)">>],[[0]]}, + mysql:query(Conn, "select max(version) from database_migrations_history")). migrate_few_scripts_test(Opts) -> Conn = ?config(conn, Opts), PreparedCall = pure_migrations:migrate( filename:join([?config(data_dir, Opts), "01-two-scripts-test"]), - p1pgsql_tx_fun(Conn), - p1pgsql_query_fun(Conn) + otp_mysql_tx_fun(Conn), + otp_mysql_query_fun(Conn) ), ?assertEqual(ok, PreparedCall()), ?assertMatch( {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["1"]]}]}, - pgsql:squery(Conn, "select max(version) from database_migrations_history")), + mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( {ok, [{"SELECT 1", [{"count", text, _, _, _, _, _}], [["1"]]}]}, - pgsql:squery(Conn, "select count(*) from fruit where color = 'yellow'")). + mysql:query(Conn, "select count(*) from fruit where color = 'yellow'")). incremental_migration_test(Opts) -> Conn = ?config(conn, Opts), MigrationStep1 = pure_migrations:migrate( filename:join([?config(data_dir, Opts), "00-single-script-test"]), - p1pgsql_tx_fun(Conn), p1pgsql_query_fun(Conn) + otp_mysql_tx_fun(Conn), otp_mysql_query_fun(Conn) ), MigrationStep2 = pure_migrations:migrate( filename:join([?config(data_dir, Opts), "01-two-scripts-test"]), - p1pgsql_tx_fun(Conn), p1pgsql_query_fun(Conn) + otp_mysql_tx_fun(Conn), otp_mysql_query_fun(Conn) ), %% assert migrations table created and nothing done ?assertMatch( {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[null]]}]}, - pgsql:squery(Conn, "select max(version) from database_migrations_history")), + mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( {ok, [{error, [{severity, 'ERROR'}|_]}]}, - pgsql:squery(Conn, "select count(*) from fruit")), + mysql:query(Conn, "select count(*) from fruit")), %% assert step 1 migration ok = MigrationStep1(), ?assertMatch( {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, - pgsql:squery(Conn, "select max(version) from database_migrations_history")), + mysql:query(Conn, "select max(version) from database_migrations_history")), %% assert step 2 migration ok =MigrationStep2(), ?assertMatch( {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["1"]]}]}, - pgsql:squery(Conn, "select max(version) from database_migrations_history")), + mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( {ok, [{"SELECT 1", [{"count", text, _, _, _, _, _}], [["1"]]}]}, - pgsql:squery(Conn, "select count(*) from fruit where color = 'yellow'")). + mysql:query(Conn, "select count(*) from fruit where color = 'yellow'")). wrong_initial_version_test(Opts) -> Conn = ?config(conn, Opts), PreparedCall = pure_migrations:migrate( filename:join([?config(data_dir, Opts), "02-wrong-initial-version"]), - p1pgsql_tx_fun(Conn), - p1pgsql_query_fun(Conn) + otp_mysql_tx_fun(Conn), + otp_mysql_query_fun(Conn) ), ?assertEqual( {rollback, {badmatch, {error, unexpected_version, {expected, 0, supplied, 20}}}}, @@ -89,18 +89,18 @@ migration_gap_test(Opts) -> Conn = ?config(conn, Opts), MigrationStep1 = pure_migrations:migrate( filename:join([?config(data_dir, Opts), "00-single-script-test"]), - p1pgsql_tx_fun(Conn), p1pgsql_query_fun(Conn) + otp_mysql_tx_fun(Conn), otp_mysql_query_fun(Conn) ), MigrationStep2 = pure_migrations:migrate( filename:join([?config(data_dir, Opts), "03-migration-gap"]), - p1pgsql_tx_fun(Conn), p1pgsql_query_fun(Conn) + otp_mysql_tx_fun(Conn), otp_mysql_query_fun(Conn) ), %% assert step 1 migration ok = MigrationStep1(), ?assertMatch( {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, - pgsql:squery(Conn, "select max(version) from database_migrations_history")), + mysql:query(Conn, "select max(version) from database_migrations_history")), %% assert step 2 failed migration ?assertEqual( @@ -108,67 +108,74 @@ migration_gap_test(Opts) -> MigrationStep2()), ?assertMatch( {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, - pgsql:squery(Conn, "select max(version) from database_migrations_history")), + mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( {ok, [{error, [{severity, 'ERROR'}|_]}]}, - pgsql:squery(Conn, "select count(*) from fruit where color = 'yellow'")). + mysql:query(Conn, "select count(*) from fruit where color = 'yellow'")). transactional_migration_test(Opts) -> Conn = ?config(conn, Opts), PreparedCall = pure_migrations:migrate( filename:join([?config(data_dir, Opts), "04-last-migration-fail"]), - p1pgsql_tx_fun(Conn), - p1pgsql_query_fun(Conn) + otp_mysql_tx_fun(Conn), + otp_mysql_query_fun(Conn) ), ?assertMatch( {rollback, {badmatch, {error, [{severity,'ERROR'}|_]}}}, PreparedCall()), ?assertMatch( {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[null]]}]}, - pgsql:squery(Conn, "select max(version) from database_migrations_history")), + mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( {ok, [{error, [{severity,'ERROR'}|_]}]}, - pgsql:squery(Conn, "select count(*) from fruit")). + mysql:query(Conn, "select count(*) from fruit")). -p1pgsql_query_fun(Conn) -> +otp_mysql_query_fun(Conn) -> fun(Q) -> - case pgsql:squery(Conn, Q) of + case mysql:query(Conn, Q) of {ok, [{error, Details}]} -> {error, Details}; + {ok,[<<"version">>,<<"filename">>],[]} -> []; {ok, [{_, [ {"version", text, _, _, _, _, _}, {"filename", text, _, _, _, _, _}], Data}]} -> [{list_to_integer(V), F} || [V, F] <- Data]; - {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[null]]}]} -> -1; + {ok,[<<"max(version)">>],[[null]]} -> -1; {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[N]]}]} -> list_to_integer(N); - {ok, _} -> ok + {ok, _} -> ok; + Default -> io:format("otp_mysql_query_fun res=~p~n", [Default]), Default end end. -p1pgsql_tx_fun(Conn) -> +otp_mysql_tx_fun(Conn) -> fun(F) -> - pgsql:squery(Conn, "BEGIN"), + mysql:query(Conn, "BEGIN"), try F() of Res -> - pgsql:squery(Conn, "COMMIT"), + mysql:query(Conn, "COMMIT"), Res catch _:Problem -> - pgsql:squery(Conn, "ROLLBACK"), + mysql:query(Conn, "ROLLBACK"), {rollback, Problem} end end. init_per_testcase(_TestCase, Opts) -> {ok, [{host, Host}, - {port, _Port}, + {port, Port}, {database, Database}, {username, Username}, {secret, Secret}, - {timeout, _Timeout}]} = application:get_env(postgres, config), - {ok, C} = pgsql:connect(Host, Database, Username, Secret), - {ok,["DROP TABLE"]} = pgsql:squery(C, "DROP TABLE IF EXISTS database_migrations_history, fruit"), - [{conn, C}|Opts]. + {timeout, _Timeout}]} = application:get_env(mysql, config), + {ok, Conn} = mysql:start_link([ {host, Host} + , {port, Port} + , {user, Username} + , {password, Secret} + , {database, Database}]), + + ok = mysql:query(Conn, "DROP TABLE IF EXISTS database_migrations_history, fruit"), + [{conn, Conn}|Opts]. end_per_testcase(_TestCase, Opts) -> - ok = pgsql:terminate(?config(conn, Opts)). + exit(?config(conn, Opts), normal). From 03cae88573fab6bea71576948450f599b8e25664 Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 00:39:45 +0100 Subject: [PATCH 10/20] #10 spin upeven more tests --- Makefile | 2 +- test/otp_mysql_migrations_SUITE.erl | 31 +++++++++---------- .../01-two-scripts-test/01_next_version.sql | 2 +- .../03-migration-gap/02_gap_version.sql | 2 +- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 6e2f308..a435389 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ code-checks: compile $(REBAR) dialyzer $(REBAR) as lint lint -test: compile +test: format compile $(REBAR) as test do ct -v cover: diff --git a/test/otp_mysql_migrations_SUITE.erl b/test/otp_mysql_migrations_SUITE.erl index f965031..c7ef4c7 100644 --- a/test/otp_mysql_migrations_SUITE.erl +++ b/test/otp_mysql_migrations_SUITE.erl @@ -6,10 +6,10 @@ -compile(export_all). all() -> [ migrate_one_script_test - %% , migrate_few_scripts_test - %% , incremental_migration_test - %% , wrong_initial_version_test - %% , migration_gap_test + , migrate_few_scripts_test + , incremental_migration_test + , wrong_initial_version_test + , migration_gap_test %% , transactional_migration_test ]. @@ -34,10 +34,10 @@ migrate_few_scripts_test(Opts) -> ), ?assertEqual(ok, PreparedCall()), ?assertMatch( - {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["1"]]}]}, + {ok,[<<"max(version)">>],[[1]]}, mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( - {ok, [{"SELECT 1", [{"count", text, _, _, _, _, _}], [["1"]]}]}, + {ok,[<<"count(*)">>],[[1]]}, mysql:query(Conn, "select count(*) from fruit where color = 'yellow'")). incremental_migration_test(Opts) -> @@ -53,25 +53,25 @@ incremental_migration_test(Opts) -> %% assert migrations table created and nothing done ?assertMatch( - {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[null]]}]}, + {ok,[<<"max(version)">>],[[null]]}, mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( - {ok, [{error, [{severity, 'ERROR'}|_]}]}, + {error, _, _}, mysql:query(Conn, "select count(*) from fruit")), %% assert step 1 migration ok = MigrationStep1(), ?assertMatch( - {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, + {ok,[<<"max(version)">>],[[0]]}, mysql:query(Conn, "select max(version) from database_migrations_history")), %% assert step 2 migration ok =MigrationStep2(), ?assertMatch( - {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["1"]]}]}, + {ok,[<<"max(version)">>],[[1]]}, mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( - {ok, [{"SELECT 1", [{"count", text, _, _, _, _, _}], [["1"]]}]}, + {ok,[<<"count(*)">>],[[1]]}, mysql:query(Conn, "select count(*) from fruit where color = 'yellow'")). wrong_initial_version_test(Opts) -> @@ -99,7 +99,7 @@ migration_gap_test(Opts) -> %% assert step 1 migration ok = MigrationStep1(), ?assertMatch( - {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, + {ok,[<<"max(version)">>],[[0]]}, mysql:query(Conn, "select max(version) from database_migrations_history")), %% assert step 2 failed migration @@ -107,7 +107,7 @@ migration_gap_test(Opts) -> {rollback, {badmatch, {error, unexpected_version, {expected, 1, supplied, 2}}}}, MigrationStep2()), ?assertMatch( - {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [["0"]]}]}, + {ok,[<<"max(version)">>],[[0]]}, mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( {ok, [{error, [{severity, 'ERROR'}|_]}]}, @@ -124,7 +124,7 @@ transactional_migration_test(Opts) -> {rollback, {badmatch, {error, [{severity,'ERROR'}|_]}}}, PreparedCall()), ?assertMatch( - {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[null]]}]}, + {ok,[<<"max(version)">>],[[null]]}, mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( {ok, [{error, [{severity,'ERROR'}|_]}]}, @@ -140,8 +140,7 @@ otp_mysql_query_fun(Conn) -> {"filename", text, _, _, _, _, _}], Data}]} -> [{list_to_integer(V), F} || [V, F] <- Data]; {ok,[<<"max(version)">>],[[null]]} -> -1; - {ok, [{"SELECT 1", [{"max", text, _, _, _, _, _}], [[N]]}]} -> - list_to_integer(N); + {ok,[<<"max(version)">>],[[V]]} -> V; {ok, _} -> ok; Default -> io:format("otp_mysql_query_fun res=~p~n", [Default]), Default end diff --git a/test/otp_mysql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql b/test/otp_mysql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql index 95d3af5..30c954d 100644 --- a/test/otp_mysql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql +++ b/test/otp_mysql_migrations_SUITE_data/01-two-scripts-test/01_next_version.sql @@ -1,5 +1,5 @@ ALTER TABLE fruit - ADD COLUMN color TEXT NOT NULL DEFAULT 'green'; + ADD COLUMN color TEXT NOT NULL; INSERT INTO fruit (name, color) VALUES ( 'lemon', 'yellow' ); \ No newline at end of file diff --git a/test/otp_mysql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql b/test/otp_mysql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql index 95d3af5..30c954d 100644 --- a/test/otp_mysql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql +++ b/test/otp_mysql_migrations_SUITE_data/03-migration-gap/02_gap_version.sql @@ -1,5 +1,5 @@ ALTER TABLE fruit - ADD COLUMN color TEXT NOT NULL DEFAULT 'green'; + ADD COLUMN color TEXT NOT NULL; INSERT INTO fruit (name, color) VALUES ( 'lemon', 'yellow' ); \ No newline at end of file From 6d1a182651a23ba286441709a8a47c72cd02dd7b Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 00:44:18 +0100 Subject: [PATCH 11/20] #10 try to fix travis mysql config issue --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 86b2b4a..34d68f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,8 @@ before_script: before_install: - sudo mysql_upgrade --force - mysql -e "CREATE DATABASE puremigration;" - - mysql -e "CREATE USER 'puremigration@localhost' IDENTIFIED BY 'puremigration'"; - - mysql -e "GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP ON puremigration.* TO 'puremigration@localhost';" + - mysql -e "CREATE USER 'puremigration'@'localhost' IDENTIFIED BY 'puremigration'"; + - mysql -e "GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP ON puremigration.* TO 'puremigration'@'localhost';" language: erlang From 041968cc99ba626b278f5fb6ad4723c07efb54fb Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 00:47:50 +0100 Subject: [PATCH 12/20] #10 readme update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index eab0d25..f4d36ec 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,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 From 360d61974cbafef2c925c87158850ff0c22146d4 Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 00:50:49 +0100 Subject: [PATCH 13/20] #10 enhance test user privileges --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 34d68f3..0342075 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ before_install: - sudo mysql_upgrade --force - mysql -e "CREATE DATABASE puremigration;" - mysql -e "CREATE USER 'puremigration'@'localhost' IDENTIFIED BY 'puremigration'"; - - mysql -e "GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP ON puremigration.* TO 'puremigration'@'localhost';" + - mysql -e "GRANT ALL PRIVILEGES ON puremigration.* TO 'puremigration'@'localhost';" language: erlang From 374e13bc45f229d635747f8f8069685c917c9fde Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 00:56:29 +0100 Subject: [PATCH 14/20] #10 better readme --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f4d36ec..1567d08 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # Erlang ❤ pure database migrations -> Database version control engine. Effects-free. +> PostgreSQL / 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 Erlang application PostgreSQL or MySQL database 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 driver (and see several ready-to-use examples below). As an extra - do this in "no side-effects" mode. @@ -212,6 +212,45 @@ Also see examples from live epgsql integration tests [here](test/p1pgsql_migrations_SUITE.erl) +### MySQL and [mysql-otp/mysql-otp](https://github.com/mysql-otp/mysql-otp) +#### Onboarding comments ++ +#### Code sample +
+ Click to expand + + ```erlang + Conn = ?config(conn, Opts), + MigrationCall = + pure_migrations:migrate( + "scripts/folder/path", + fun(F) -> F end, + fun(Q) -> + case mysql:query(Conn, Q) of + {ok, [ + {column, <<"version">>, _, _, _, _, _}, + {column, <<"filename">>, _, _, _, _, _}], Data} -> + [{list_to_integer(binary_to_list(BinV)), binary_to_list(BinF)} || {BinV, BinF} <- Data]; + {ok, [{column, <<"max">>, _, _, _, _, _}], [{null}]} -> -1; + {ok, [{column, <<"max">>, _, _, _, _, _}], [{N}]} -> + list_to_integer(binary_to_list(N)); + [{ok, _, _}, {ok, _}] -> ok; + {ok, _, _} -> ok; + {ok, _} -> ok; + Default -> Default + 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) +
+ # 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 From 9f9f69772f5433b075d441f16445cf499726b952 Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 20:15:27 +0100 Subject: [PATCH 15/20] #10 all non-transaction tests passing --- test/otp_mysql_migrations_SUITE.erl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/otp_mysql_migrations_SUITE.erl b/test/otp_mysql_migrations_SUITE.erl index c7ef4c7..54e81f5 100644 --- a/test/otp_mysql_migrations_SUITE.erl +++ b/test/otp_mysql_migrations_SUITE.erl @@ -56,7 +56,7 @@ incremental_migration_test(Opts) -> {ok,[<<"max(version)">>],[[null]]}, mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( - {error, _, _}, + {error, {1146, <<"42S02">>, <<"Table 'puremigration.fruit' doesn't exist">>}}, mysql:query(Conn, "select count(*) from fruit")), %% assert step 1 migration @@ -110,7 +110,7 @@ migration_gap_test(Opts) -> {ok,[<<"max(version)">>],[[0]]}, mysql:query(Conn, "select max(version) from database_migrations_history")), ?assertMatch( - {ok, [{error, [{severity, 'ERROR'}|_]}]}, + {error, {1054, <<"42S22">>, <<"Unknown column 'color' in 'where clause'">>}}, mysql:query(Conn, "select count(*) from fruit where color = 'yellow'")). transactional_migration_test(Opts) -> @@ -135,10 +135,8 @@ otp_mysql_query_fun(Conn) -> case mysql:query(Conn, Q) of {ok, [{error, Details}]} -> {error, Details}; {ok,[<<"version">>,<<"filename">>],[]} -> []; - {ok, [{_, [ - {"version", text, _, _, _, _, _}, - {"filename", text, _, _, _, _, _}], Data}]} -> - [{list_to_integer(V), F} || [V, F] <- Data]; + {ok,[<<"version">>,<<"filename">>], Data} -> + [{V, binary_to_list(F)} || [V, F] <- Data]; {ok,[<<"max(version)">>],[[null]]} -> -1; {ok,[<<"max(version)">>],[[V]]} -> V; {ok, _} -> ok; From 0359a2004d9e334a7aba29b4a347b18e6a4e0664 Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 20:33:52 +0100 Subject: [PATCH 16/20] #10 all tests converged --- test/otp_mysql_migrations_SUITE.erl | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/test/otp_mysql_migrations_SUITE.erl b/test/otp_mysql_migrations_SUITE.erl index 54e81f5..907ecca 100644 --- a/test/otp_mysql_migrations_SUITE.erl +++ b/test/otp_mysql_migrations_SUITE.erl @@ -10,7 +10,7 @@ all() -> [ migrate_one_script_test , incremental_migration_test , wrong_initial_version_test , migration_gap_test - %% , transactional_migration_test + , transactional_migration_unavailable_for_mysql_test ]. migrate_one_script_test(Opts) -> @@ -82,7 +82,7 @@ wrong_initial_version_test(Opts) -> otp_mysql_query_fun(Conn) ), ?assertEqual( - {rollback, {badmatch, {error, unexpected_version, {expected, 0, supplied, 20}}}}, + {rollback_unavailable, {badmatch, {error, unexpected_version, {expected, 0, supplied, 20}}}}, PreparedCall()). migration_gap_test(Opts) -> @@ -104,7 +104,7 @@ migration_gap_test(Opts) -> %% assert step 2 failed migration ?assertEqual( - {rollback, {badmatch, {error, unexpected_version, {expected, 1, supplied, 2}}}}, + {rollback_unavailable, {badmatch, {error, unexpected_version, {expected, 1, supplied, 2}}}}, MigrationStep2()), ?assertMatch( {ok,[<<"max(version)">>],[[0]]}, @@ -113,7 +113,7 @@ migration_gap_test(Opts) -> {error, {1054, <<"42S22">>, <<"Unknown column 'color' in 'where clause'">>}}, mysql:query(Conn, "select count(*) from fruit where color = 'yellow'")). -transactional_migration_test(Opts) -> +transactional_migration_unavailable_for_mysql_test(Opts) -> Conn = ?config(conn, Opts), PreparedCall = pure_migrations:migrate( filename:join([?config(data_dir, Opts), "04-last-migration-fail"]), @@ -121,13 +121,15 @@ transactional_migration_test(Opts) -> otp_mysql_query_fun(Conn) ), ?assertMatch( - {rollback, {badmatch, {error, [{severity,'ERROR'}|_]}}}, + {rollback_unavailable, {badmatch, {error, {_, _, _}}}}, PreparedCall()), + %% no version values should exist into database ?assertMatch( - {ok,[<<"max(version)">>],[[null]]}, + {ok,[<<"max(version)">>],[[0]]}, mysql:query(Conn, "select max(version) from database_migrations_history")), + %% no fruit table should exist into database ?assertMatch( - {ok, [{error, [{severity,'ERROR'}|_]}]}, + {ok,[<<"count(*)">>],[[1]]}, mysql:query(Conn, "select count(*) from fruit")). otp_mysql_query_fun(Conn) -> @@ -144,17 +146,12 @@ otp_mysql_query_fun(Conn) -> end end. -otp_mysql_tx_fun(Conn) -> +otp_mysql_tx_fun(_Conn) -> fun(F) -> - mysql:query(Conn, "BEGIN"), try F() of - Res -> - mysql:query(Conn, "COMMIT"), - Res + Res -> Res catch - _:Problem -> - mysql:query(Conn, "ROLLBACK"), - {rollback, Problem} + _:Problem -> {rollback_unavailable, Problem} end end. From a474a4bd200d6584502bbf28a71dabfe180ea268 Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 22:42:56 +0100 Subject: [PATCH 17/20] #10 refine test content --- test/otp_mysql_migrations_SUITE.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/otp_mysql_migrations_SUITE.erl b/test/otp_mysql_migrations_SUITE.erl index 907ecca..6bfc33b 100644 --- a/test/otp_mysql_migrations_SUITE.erl +++ b/test/otp_mysql_migrations_SUITE.erl @@ -135,14 +135,14 @@ transactional_migration_unavailable_for_mysql_test(Opts) -> otp_mysql_query_fun(Conn) -> fun(Q) -> case mysql:query(Conn, Q) of - {ok, [{error, Details}]} -> {error, Details}; + {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; - Default -> io:format("otp_mysql_query_fun res=~p~n", [Default]), Default + ok -> ok end end. From 0bc459c52df9a9e4d2969dfe3d392267108ff60c Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 23:01:32 +0100 Subject: [PATCH 18/20] #10 add mysql readme documentation --- README.md | 71 +++++++++++++++++++++++++------------------------------ 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 1567d08..c0a51d2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![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 or MySQL 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/MySQL Erlang driver (and see @@ -13,34 +13,21 @@ several ready-to-use examples below). As an extra - do this in "no side-effects" mode. # Table of content -- [Current limitations](#current-limitations) -- [Quick start](#quick-start) - * [Compatibility table](#compatibility-table) - * [Live integrations](#live-integrations) - + [PostgreSQL and epgsql/epgsql](#postgresql-and--epgsql-epgsql--https---githubcom-epgsql-epgsql-) - - [Onboarding comments](#onboarding-comments) - - [Code sample](#code-sample) - + [PostgreSQL and semiocast/pgsql](#postgresql-and--semiocast-pgsql--https---githubcom-semiocast-pgsql-) - - [Onboarding comments](#onboarding-comments-1) - - [Code sample](#code-sample-1) - + [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) - * [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) - * [Functions composition](#functions-composition) - * [Functor applications](#functor-applications) - * [Partial function applications](#partial-function-applications) + # 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: @@ -51,8 +38,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 @@ -224,20 +211,26 @@ Also see examples from live epgsql integration tests MigrationCall = pure_migrations:migrate( "scripts/folder/path", - fun(F) -> F end, + fun(F) -> + %% no full-scope tx API available here + %% or use mysql:transaction/2, but please be aware about + %% mysql implicit transactions behavior + try F() of + Res -> Res + catch + _:Problem -> {rollback_unavailable, Problem} + end + end, fun(Q) -> case mysql:query(Conn, Q) of - {ok, [ - {column, <<"version">>, _, _, _, _, _}, - {column, <<"filename">>, _, _, _, _, _}], Data} -> - [{list_to_integer(binary_to_list(BinV)), binary_to_list(BinF)} || {BinV, BinF} <- Data]; - {ok, [{column, <<"max">>, _, _, _, _, _}], [{null}]} -> -1; - {ok, [{column, <<"max">>, _, _, _, _, _}], [{N}]} -> - list_to_integer(binary_to_list(N)); - [{ok, _, _}, {ok, _}] -> ok; - {ok, _, _} -> ok; + {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; - Default -> Default + ok -> ok end end), ... @@ -251,7 +244,7 @@ Also see examples from live epgsql integration tests [here](test/otp_mysql_migrations_SUITE.erl) -# No-effects approach and tools used to achieve it +# "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: From 891264a60fd96b239066f6cc2a01bc345017f38c Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 23:08:37 +0100 Subject: [PATCH 19/20] #10 fix mysql readme documentation --- Makefile | 2 +- README.md | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index a435389..6e2f308 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ code-checks: compile $(REBAR) dialyzer $(REBAR) as lint lint -test: format compile +test: compile $(REBAR) as test do ct -v cover: diff --git a/README.md b/README.md index c0a51d2..96a9a0e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Erlang ❤ pure database migrations -> PostgreSQL / MySQL 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) @@ -201,7 +201,11 @@ Also see examples from live epgsql integration tests ### 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
Click to expand @@ -214,7 +218,7 @@ Also see examples from live epgsql integration tests fun(F) -> %% no full-scope tx API available here %% or use mysql:transaction/2, but please be aware about - %% mysql implicit transactions behavior + %% mysql implicit transactions commit behavior try F() of Res -> Res catch From dd0244fd3b57fe259e2636a9b238090d81da4e10 Mon Sep 17 00:00:00 2001 From: bearmug Date: Mon, 11 Feb 2019 23:12:42 +0100 Subject: [PATCH 20/20] #10 submit readme toc --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 96a9a0e..a561788 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,34 @@ 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/MySQL 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. # Table of content - +- [Current limitations](#current-limitations) +- [Quick start](#quick-start) + * [Compatibility table](#compatibility-table) + * [Live integrations](#live-integrations) + + [PostgreSQL and epgsql/epgsql](#postgresql-and--epgsql-epgsql--https---githubcom-epgsql-epgsql-) + - [Onboarding comments](#onboarding-comments) + - [Code sample](#code-sample) + + [PostgreSQL and semiocast/pgsql](#postgresql-and--semiocast-pgsql--https---githubcom-semiocast-pgsql-) + - [Onboarding comments](#onboarding-comments-1) + - [Code sample](#code-sample-1) + + [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) + + [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) + * [Functions composition](#functions-composition) + * [Functor applications](#functor-applications) + * [Partial function applications](#partial-function-applications) # Current limitations * **up** transactional migration available only. No **downgrade**