Add reset and setup

This commit is contained in:
Marc Nijdam 2019-12-27 09:22:05 -07:00
parent 651b622773
commit 91c48c985b
No known key found for this signature in database
GPG Key ID: 1C84AFC593B1B79D

View File

@ -1,7 +1,7 @@
-module(psql_migration).
%% API exports
-export([main/1, open_connection/0, open_connection/1]).
-export([main/1, open_connection/1, connection_opts/1]).
%%====================================================================
%% API functions
@ -25,7 +25,9 @@ usage(ExitCode) ->
{"new <name>", "Create a new migration"},
{"list", "List migrations indicating which have been applied"},
{"run", "Run all migrations"},
{"revert", "Revert the last migration"}
{"revert", "Revert the last migration"},
{"reset", "Resets your database by dropping the database in your DATABASE_URL and then runs `setup`"},
{"setup", "Creates the database specified in your DATABASE_URL, and runs any existing migrations."}
]),
halt(ExitCode).
@ -57,41 +59,63 @@ handle_command({ok, {Args, ["new", Name]}}) ->
"-- Down migration\n"
],
file:write_file(Filename, list_to_binary(C), [exclusive]),
io:format("Created migration: ~s~n", [Filename]);
handle_command_result({ok, "Created migration: ~s~n", [Filename]});
handle_command({ok, {Args, ["run"]}}) ->
Available = available_migrations(Args),
case open_connection(Args) of
{ok, Conn} ->
Applied = applied_migrations(Conn),
ToApply = lists:filter(fun ({Mig, _}) -> not lists:member(Mig, Applied) end, Available),
Results = apply_migrations(up, ToApply, Conn),
report_migrations(up, Results);
{error, Error} ->
io:format("Failed to connect to database: ~p~n", [Error]),
halt(1)
end;
Result = with_connection(Args,
fun(Conn) ->
Applied = applied_migrations(Conn),
ToApply = lists:filter(fun ({Mig, _}) -> not lists:member(Mig, Applied) end, Available),
Results = apply_migrations(up, ToApply, Conn),
report_migrations(up, Results)
end),
handle_command_result(Result);
handle_command({ok, {Args, ["revert"]}}) ->
Available = available_migrations(Args),
case open_connection(Args) of
{ok, Conn} ->
case applied_migrations(Conn) of
[] ->
io:format("No applied migrations to revert"),
halt(1);
Applied ->
LastApplied = lists:last(Applied),
case lists:keyfind(LastApplied, 1, Available) of
false ->
io:format("Migration ~p can not be found~n", [LastApplied]),
halt(1);
Migration ->
Results = apply_migrations(down, [Migration], Conn),
report_migrations(down, Results)
end
end;
{error, Error} ->
io:format("Failed to connect to database: ~p~n", [Error]),
halt(1)
Result = with_connection(Args,
fun(Conn) ->
case applied_migrations(Conn) of
[] ->
{error, "No applied migrations to revert"};
Applied ->
LastApplied = lists:last(Applied),
case lists:keyfind(LastApplied, 1, Available) of
false ->
{error, "Migration ~p can not be found~n", [LastApplied]};
Migration ->
Results = apply_migrations(down, [Migration], Conn),
report_migrations(down, Results)
end
end
end),
handle_command_result(Result);
handle_command({ok, {Args, ["reset"]}}) ->
{ok, Opts} = connection_opts(Args),
case maps:take(database, Opts) of
error ->
handle_command_result({error, "No database to reset~n"});
{Database, Opts1} ->
ok = with_connection(Opts1,
fun(Conn) ->
if_ok(epgsql:squery(Conn, "drop database if exists " ++ Database))
end),
handle_command({ok, {Args, ["setup"]}})
end;
handle_command({ok, {Args, ["setup"]}}) ->
{ok, Opts} = connection_opts(Args),
case maps:take(database, Opts) of
error ->
handle_command_result({error, "No database to reset~n"});
{Database, Opts1} ->
case with_connection(Opts1,
fun(Conn) ->
if_ok(epgsql:squery(Conn, "create database " ++ Database))
end) of
ok ->
handle_command({ok, {Args, ["run"]}});
Other ->
handle_command_result(Other)
end
end;
handle_command({ok, {_, _}}) ->
usage(1).
@ -100,8 +124,36 @@ handle_command({ok, {_, _}}) ->
%% Utils
-spec open_connection() -> {ok, epgsql:connection()} | {error, term()}.
open_connection() ->
-type command_result() ::ok |
{ok, io:format(), [term()]} |
{error, string()} |
{error, io:format(), [term()]}.
-spec handle_command_result(command_result()) -> no_return().
handle_command_result(ok) ->
halt(0);
handle_command_result({ok, Fmt, Args}) ->
io:format(Fmt, Args),
halt(0);
handle_command_result({error, Str}) ->
handle_command_result({error, Str, []});
handle_command_result({error, Fmt, Args}) ->
io:format(Fmt, Args),
halt(1).
-spec with_connection(list() | map(), fun((epgsql:connnection()) -> command_result())) -> command_result().
with_connection(Args, Fun) ->
case open_connection(Args) of
{ok, Conn} ->
Fun(Conn);
{error, Error} ->
{error, "Failed to connect to database: ~p~n", [Error]}
end.
connection_opts(Args) ->
envloader:load(dot_env(Args)),
URL = os:getenv("DATABASE_URL"),
ParseOpts = [{scheme_defaults, [{postgres, 5432}, {postgresql, 5432}]}],
case http_uri:parse(URL, ParseOpts) of
@ -114,17 +166,20 @@ open_connection() ->
[[], []] -> {"postgres", ""};
[U, P] -> {U, P}
end,
OpenOpts = #{ port => Port,
username => User,
password => Pass,
host =>Host,
database => string:slice(Database, 1)},
epgsql:connect(OpenOpts)
{ok, #{ port => Port,
username => User,
password => Pass,
host =>Host,
database => string:slice(Database, 1)}}
end.
open_connection(Args) ->
envloader:load(dot_env(Args)),
open_connection().
-spec open_connection(list() | map()) -> {ok, epgsql:connection()} | {error, term()}.
open_connection(Args) when is_list(Args) ->
{ok, Opts} = connection_opts(Args),
open_connection(Opts);
open_connection(Opts) ->
epgsql:connect(Opts).
target_dir(Args) ->
case lists:keyfind(dir, 1, Args) of
@ -144,9 +199,11 @@ dot_env(Args) ->
report_migrations(_, L) when length(L) == 0 ->
io:format("No migrations were run~n");
report_migrations(up, Results) ->
[io:format("Applied ~s: ~p~n", [V, R]) || {V, R} <- Results];
[io:format("Applied ~s: ~p~n", [V, R]) || {V, R} <- Results],
ok;
report_migrations(down, Results) ->
[io:format("Reverted ~s: ~p~n", [V, R]) || {V, R} <- Results].
[io:format("Reverted ~s: ~p~n", [V, R]) || {V, R} <- Results],
ok.
-define(DRIVER, epgsql).
@ -188,7 +245,7 @@ if_ok(Rs) when is_list(Rs) ->
if_ok({ok, _}) -> ok;
if_ok({ok, _, _}) -> ok;
if_ok({ok, _, _, _}) -> ok;
if_ok({error, {error,error,_,_,Descr,_}}) -> {error, Descr};
if_ok({error, {error,error,_,_,Descr,_}}) -> {error, binary_to_list(Descr)};
if_ok(Error) -> {error, Error}.
available_migrations(Args) ->
@ -200,13 +257,11 @@ available_migrations(Args) ->
end, lists:usort(Files)).
applied_migrations(Args) when is_list(Args) ->
case open_connection(Args) of
{ok, Conn} ->
applied_migrations(Conn);
{error, Error} ->
{error, Error}
end;
applied_migrations(Conn) ->
with_connection(Args,
fun(Conn) ->
applied_migrations(Conn)
end);
applied_migrations(Conn) when is_pid(Conn) ->
case ?DRIVER:squery(Conn, "SELECT id FROM __migrations ORDER by id ASC") of
{ok, _, Migs} -> [binary_to_list(Mig) || {Mig} <- Migs];
{error, {error, error, <<"42P01">>, _, _, _}} ->