-module(erupt). -export([run/1, list/1, revert/1, redo/1, config/2, config/3, timestamp/0, cleanup/1]). -spec config(list() | map(), module()) -> map(). config(Migrations, Driver) -> config(Migrations, Driver, null). -spec config(list() | map(), module(), any()) -> map(). config(#{} = Migrations, Driver, Conn) -> config(maps:to_list(Migrations), Driver, Conn); config(Migrations, Driver, Conn) -> #{migrations => Migrations, driver => Driver, conn => Conn}. -spec run(map()) -> ok | {error, any()}. run(#{driver := Driver, conn := Conn, migrations := Migrations}) -> case Driver:up(Conn) of {error, _Err} = E -> E; ok -> case Driver:list(Conn) of {error, _Err} = E -> E; {ok, AppliedMigrations} -> NewMigrations = filter_applied_migrations(Migrations, AppliedMigrations), run_migrations(Driver, Conn, sort_migrations(NewMigrations)) end end; run(_) -> {error, invalid_config}. -spec run_migrations(module(), any(), list()) -> ok | {error, any()}. run_migrations(Driver, Conn, [{Migration, Timestamp} | T]) -> case Driver:exec(Conn, Migration:up()) of {error, _Err} = E -> E; ok -> case Driver:add(Conn, atom_to_binary(Migration), Timestamp) of {error, _Err} = E -> E; ok -> run_migrations(Driver, Conn, T) end end; run_migrations(_Driver, _Query, []) -> ok. -spec filter_applied_migrations(list(), list()) -> list(). filter_applied_migrations(Migrations, AppliedMigrations) -> FormatAppliedMigrations = lists:map(fun({_Id, Migration, Timestamp}) -> {Migration, Timestamp} end, AppliedMigrations), AppliedMigrationsMap = maps:from_list(FormatAppliedMigrations), MigrationsMap = maps:from_list(Migrations), maps:to_list( maps:without( maps:keys(AppliedMigrationsMap), MigrationsMap)). -spec sort_migrations(list()) -> list(). sort_migrations(Migrations) -> lists:sort(fun({_, A}, {_, B}) -> A =< B end, Migrations). -spec list(map()) -> {ok, list()} | {error, any()}. list(#{driver := Driver, conn := Conn}) -> Driver:list(Conn). -spec revert(map()) -> ok | {error, any()}. revert(#{driver := Driver, conn := Conn}) -> case Driver:last(Conn) of {error, _Err} = E -> E; {ok, {Id, Migration, _Timestamp}} -> case Driver:exec(Conn, Migration:down()) of {error, _Err} = E -> E; ok -> case Driver:delete(Conn, Id) of {error, _Err} = E -> E; ok -> ok end end end. -spec redo(map()) -> ok | {error, any()}. redo(Config) -> case revert(Config) of {error, _Err} = E -> E; ok -> run(Config) end. -spec cleanup(map()) -> ok | {error, any()}. cleanup(#{driver := Driver, conn := Conn}) -> case Driver:list(Conn) of {error, _Err} = E -> E; {ok, AppliedMigrations} -> case cleanup_migrations(Driver, Conn, lists:reverse(AppliedMigrations)) of {error, _Err} = E -> E; ok -> Driver:down(Conn) end end. -spec cleanup_migrations(module(), any(), list()) -> ok | {error, any()}. cleanup_migrations(Driver, Conn, [{Id, _Name, _Timestamp} | T]) -> case Driver:delete(Conn, Id) of {error, _Err} = E -> E; ok -> cleanup_migrations(Driver, Conn, T) end; cleanup_migrations(_Driver, _Conn, []) -> ok. -spec timestamp() -> integer(). timestamp() -> {A, B, C} = erlang:timestamp(), Bin = list_to_binary([integer_to_list(A), integer_to_list(B), integer_to_list(C)]), binary_to_integer(Bin). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). filter_applied_migrations_test() -> Migrations = [{create_todos, 12345}, {create_tags, 123456}], Migrations2 = [{1, create_todos, 12345}], ?assertEqual([{create_tags, 123456}], filter_applied_migrations(Migrations, Migrations2)). sort_migrations_test() -> Migrations = [{create_todos, 12345}, {create_tags, 123456}, {create_stuff, 123}], ?assertEqual([{create_stuff, 123}, {create_todos, 12345}, {create_tags, 123456}], sort_migrations(Migrations)). timestamp_test() -> T = timestamp(), ?assert(is_integer(T)). -endif.