From 36ed8224533f86af6acafbabdc0cd20c2265dc82 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Thu, 21 Nov 2013 09:42:56 -0500 Subject: [PATCH] Massive overhaul to allow for multinode scenarios, and predictable return types. --- src/migresia.erl | 74 +++++++++++++++++++++----------- src/migresia_migrations.erl | 84 ++++++++++++++++--------------------- 2 files changed, 87 insertions(+), 71 deletions(-) diff --git a/src/migresia.erl b/src/migresia.erl index 1c8f969..ec79409 100644 --- a/src/migresia.erl +++ b/src/migresia.erl @@ -24,41 +24,46 @@ -module(migresia). --export([create_new_migration/1, create_new_migration/2, check/0, check/1, migrate/0, migrate/1, list_nodes/0]). +-export([create_new_migration/2, check/1, migrate/1, list_disc_copy_nodes/0]). --spec create_new_migration(string()) -> any(). -create_new_migration(Description) -> - create_new_migration(undefined, Description). +-define(TABLE, schema_migrations). -spec create_new_migration(atom(), string()) -> any(). create_new_migration(App, Description) -> {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), Filename = lists:flatten(io_lib:format("~w~2.2.0w~2.2.0w~2.2.0w~2.2.0w~2.2.0w_", [Year, Month, Day, Hour, Minute, Second]) ++ Description), - FullPathAndExtension = filename:join(migresia_migrations:get_priv_dir(App), Filename ++ ".erl"), + Path = migresia_migrations:get_priv_dir(App), + FullPathAndExtension = filename:join(Path, Filename ++ ".erl"), io:format("Creating new migration: ~p~n", [FullPathAndExtension]), - file:write_file(FullPathAndExtension, io_lib:fwrite("-module(~p).~n-behavior(db_migration).~n-export([up/0, down/0]). ~n~nup() -> ok.~n~ndown() -> throw(<<\"Downgraders not implemented.\">>).", [list_to_atom(Filename)])), + ok = filelib:ensure_dir(<>/binary>>), + ok = file:write_file(FullPathAndExtension, io_lib:fwrite("-module(~p).~n-behavior(db_migration).~n-export([up/0, down/0]). ~n~nup() -> ok.~n~ndown() -> throw(<<\"Downgraders not implemented.\">>).", [list_to_atom(Filename)])), io:format("Migration written.~n~n"). --spec check(atom()) -> any(). +-spec check(atom()) -> ok | {error, any()}. check(App) -> - Loaded = migresia_migrations:list_unapplied_ups(App), - if Loaded == [] -> - io:format("No migrations to apply.~n", []); - true -> - io:format("Migrations to apply: ~p~n", [ [X||{X,_} <- Loaded] ]) + case start_mnesia(false) of + ok -> + Loaded = migresia_migrations:list_unapplied_ups(App), + if Loaded == [] -> + []; + true -> + [ [X||{X,_} <- Loaded] ] + end; + {error, Error} -> {error, Error} end. --spec check() -> any(). -check() -> check(undefined). - --spec migrate(atom()) -> any(). +-spec migrate(atom()) -> ok | {error, any()}. migrate(App) -> - Loaded = migresia_migrations:list_unapplied_ups(App), - lists:foreach(fun execute_up/1, Loaded). - --spec migrate() -> any(). -migrate() -> - migrate(undefined). + case start_mnesia(true) of + ok -> + case migresia_migrations:ensure_schema_table_exists() of + ok -> + Loaded = migresia_migrations:list_unapplied_ups(App), + lists:foreach(fun execute_up/1, Loaded); + {error, Error} -> Error + end; + {error, Error} -> {error, Error} + end. execute_up({Module, Short}) -> io:format("Executing up in ~s...~n", [Module]), @@ -66,6 +71,27 @@ execute_up({Module, Short}) -> mnesia:dirty_write(schema_migrations, {schema_migrations, Short, true}), io:format(" => done~n", []). -list_nodes() -> - mnesia:table_info(schema, disc_copies). + +-spec start_mnesia(boolean()) -> ok | {error, any()}. +start_mnesia(RemoteToo) -> + io:format("Starting Mnesia...~n", []), + case application:ensure_started(mnesia) of + Other when Other /= ok -> io:format(" => Error:~p~n", [Other]), Other; + ok -> + case RemoteToo of + false -> ok; + true -> {ResultList, BadNodes} = rpc:multicall(list_all_nodes(), application, ensure_started, [mnesia]), + BadStatuses = [X || X <- ResultList, X /= ok], + if BadNodes /= [] -> io:format(" => Error:~p~n", [not_all_nodes_running]), {error, not_all_nodes_running}; + BadStatuses /= [] -> io:format(" => Error:~p~n", [BadStatuses]), {error, BadStatuses}; + true -> io:format(" => started~n", []), ok + end + end + end. + +list_all_nodes() -> + mnesia:table_info(schema, all_nodes). + +list_disc_copy_nodes() -> + mnesia:table_info(schema, disc_copies). \ No newline at end of file diff --git a/src/migresia_migrations.erl b/src/migresia_migrations.erl index e9bda77..c9ba2ad 100644 --- a/src/migresia_migrations.erl +++ b/src/migresia_migrations.erl @@ -24,37 +24,47 @@ -module(migresia_migrations). --include_lib("eunit/include/eunit.hrl"). +-export([list_unapplied_ups/1, get_priv_dir/1, ensure_schema_table_exists/0]). --export([list_unapplied_ups/1, get_priv_dir/1]). - --define(APP, migresia). -define(DIR, <<"migrate">>). -define(TABLE, schema_migrations). -spec list_unapplied_ups(atom()) -> [{module(), binary()}]. -list_unapplied_ups(App) when App == undefined -> - list_unapplied_ups(?APP); list_unapplied_ups(App) -> get_migrations(get_priv_dir(App)). - -get_priv_dir(App) when App == undefined -> - get_priv_dir(?APP); +-spec get_priv_dir(atom()) -> binary(). get_priv_dir(App) -> - application:load(App), - filename:join(code:priv_dir(App), ?DIR). + case application:load(App) of + ok -> filename:join(code:priv_dir(App), ?DIR); + Error -> Error + end. + +-spec ensure_schema_table_exists() -> ok | {error, any()}. +ensure_schema_table_exists() -> + case lists:member(?TABLE, mnesia:system_info(tables)) of + true -> ok; + false -> io:format("Table schema_migration not found, creating...~n", []), + Attr = [{type, ordered_set}, {disc_copies, migresia:list_disc_copy_nodes()}], + case mnesia:create_table(?TABLE, Attr) of + {atomic, ok} -> io:format(" => created~n", []), ok; + {aborted, Reason} -> {error, Reason} + end + end. + +-spec get_migrations({error, any()} | binary()) -> list(). get_migrations({error, _} = Err) -> - exit(Err); + {error, Err}; get_migrations(Dir) -> - io:format("DIR: ~p~n~n", [Dir]), ToApply = check_dir(file:list_dir(Dir)), - start_mnesia(), - Applied = check_table(), - ToExecute = compile_unapplied(Dir, ToApply, Applied, []), - Fun = fun({Module, Short, Binary}) -> load_migration(Module, Short, Binary) end, - lists:map(Fun, ToExecute). + case check_table() of + {error, Error} -> {error, Error}; + Applied when is_list(Applied) -> + ToExecute = compile_unapplied(Dir, ToApply, Applied, []), + Fun = fun({Module, Short, Binary}) -> load_migration(Module, Short, Binary) end, + lists:map(Fun, ToExecute) + end. check_dir({error, Reason}) -> exit({file, list_dir, Reason}); @@ -74,33 +84,23 @@ normalize_names([Name|_], _Acc) -> normalize_names([], Acc) -> lists:sort(Acc). -start_mnesia() -> - io:format("Starting Mnesia...~n", []), - case mnesia:start() of - ok -> io:format(" => started~n", []); - Other -> io:format(" => Error:~p~nExiting...~n", [Other]), exit(Other) - end. check_table() -> case lists:member(?TABLE, mnesia:system_info(tables)) of false -> - create_migration_table(); + []; true -> io:format("Waiting for tables...~n", []), - ok = mnesia:wait_for_tables([?TABLE], 5000), - io:format(" => done~n", []), - Select = [{{?TABLE,'_','_'},[],['$_']}], - List = mnesia:dirty_select(?TABLE, Select), - [ X || {schema_migrations, X, true} <- List ] + case mnesia:wait_for_tables([?TABLE], 5000) of + ok -> + io:format(" => done~n", []), + Select = [{{?TABLE,'_','_'},[],['$_']}], + List = mnesia:dirty_select(?TABLE, Select), + [ X || {schema_migrations, X, true} <- List ]; + Error -> {error, Error} + end end. -create_migration_table() -> - io:format("Table schema_migration not found, creating...~n", []), - Attr = [{type, ordered_set}, {disc_copies, migresia:list_nodes()}], - case mnesia:create_table(?TABLE, Attr) of - {atomic, ok} -> io:format(" => created~n", []), []; - {aborted, Reason} -> exit({mnesia, create_table, Reason}) - end. compile_unapplied(Dir, [{Short, Module}|TN], [] = Old, Acc) -> compile_unapplied(Dir, TN, Old, [compile_file(Dir, Short, Module)|Acc]); @@ -142,13 +142,3 @@ load_migration(Module, Short, Binary) -> exit({code, load_binary, What}) end. -%%% Some unit tests - -normalize_test() -> - [{<<"20130401120000">>, <<"20130401120000">>}, - {<<"20130402000000">>, <<"20130402000000_">>}, - {<<"20130403080910">>, <<"20130403080910_test">>}] = - normalize_names( - [<<"20130402000000_.erl">>, - "20130403080910_test.erl", - <<"20130401120000.erl">>], []).