diff --git a/src/migresia.app.src b/src/migresia.app.src index 2c3a8b1..af40734 100644 --- a/src/migresia.app.src +++ b/src/migresia.app.src @@ -27,7 +27,7 @@ {application, migresia, [ {description, "Simple mnesia database migration tool"}, - {vsn, "0.0.3"}, + {vsn, "0.0.4"}, {modules, [migresia, migresia_migrations]}, {registered, []}, {applications, [kernel, stdlib]} diff --git a/src/migresia.appup.src b/src/migresia.appup.src index a179e8a..8a6d2be 100644 --- a/src/migresia.appup.src +++ b/src/migresia.appup.src @@ -1,11 +1,19 @@ %% -*- mode: erlang -*- -{"0.0.3", - [{"0.0.2", +{"0.0.4", + [ + {"0.0.3", [{load_module, migresia_migrations}, - {load_module, migresia} - ]}], - [{"0.0.2", + {load_module, migresia}]}, + {"0.0.2", [{load_module, migresia_migrations}, - {load_module, migresia} - ]}] + {load_module, migresia}]} + ], + [ + {"0.0.3", + [{load_module, migresia_migrations}, + {load_module, migresia}]}, + {"0.0.2", + [{load_module, migresia_migrations}, + {load_module, migresia}]} + ] }. diff --git a/src/migresia.erl b/src/migresia.erl index 9e53c12..4164f27 100644 --- a/src/migresia.erl +++ b/src/migresia.erl @@ -25,13 +25,16 @@ -module(migresia). -export([start_all_mnesia/0, + list_nodes/0, + list_migrations/0, ensure_started/1, check/1, migrate/0, migrate/1, rollback/1, rollback/2, - list_nodes/0]). + rollback_last/0, + rollback_last/1]). %%------------------------------------------------------------------------------ @@ -49,6 +52,9 @@ start_all_mnesia() -> list_nodes() -> mnesia:table_info(schema, disc_copies). +list_migrations() -> + migresia_migrations:list_migrations(). + ensure_started_on_remotes(Nodes) -> io:format("Ensuring Mnesia is running on nodes:~n~p~n", [Nodes]), {ResL, BadNodes} = rpc:multicall(Nodes, migresia, ensure_started, [mnesia]), @@ -131,14 +137,21 @@ rollback(Srcs, Time) -> rollback1(Srcs, Time) -> io:format("Waiting for tables (max timeout 2 minutes)...~n", []), ok = mnesia:wait_for_tables(mnesia:system_info(tables), 120000), - case migresia_migrations:list_all_ups(Srcs) of + case migresia_migrations:list_applied_ups(Srcs, Time) of {error, _} = Err -> Err; Ups -> apply_downs(Srcs, Ups, Time) end. -apply_downs(Srcs, Ups, Time) -> +apply_downs(Srcs, Loaded, Time) -> %% Load the transform function on all nodes, see: %% http://toddhalfpenny.com/2012/05/21/possible-erlang-bad-transform-function-solution/ - rpc:multicall(nodes(), migresia_migrations, list_all_ups, [Srcs]), - ToRollBack = lists:reverse([X || {_, Ts} = X <- Ups, Ts > Time]), - lists:foreach(fun migresia_migrations:execute_down/1, ToRollBack). + rpc:multicall(nodes(), migresia_migrations, list_applied_ups, [Srcs, Time]), + lists:foreach(fun migresia_migrations:execute_down/1, Loaded). + +%%------------------------------------------------------------------------------ + +-spec rollback_last() -> ok | {error, any()}. +rollback_last() -> rollback(migresia_migrations:get_ts_before_last()). + +-spec rollback_last(migration_sources()) -> ok | {error, any()}. +rollback_last(Srcs) -> rollback(Srcs, migresia_migrations:get_ts_before_last()). diff --git a/src/migresia_migrations.erl b/src/migresia_migrations.erl index 36f25ab..31244fa 100644 --- a/src/migresia_migrations.erl +++ b/src/migresia_migrations.erl @@ -25,15 +25,19 @@ -module(migresia_migrations). -export([init_migrations/0, + list_migrations/0, list_unapplied_ups/1, - list_all_ups/1, + list_applied_ups/2, get_default_dir/0, get_priv_dir/1, + get_ts_before_last/0, execute_up/1, execute_down/1]). -define(DIR, <<"migrations">>). -define(TABLE, schema_migrations). +%% Surely no migrations before the first commit in migresia +-define(FIRST_TS, 20130404041545). -type mod_bin_list() :: [{module(), binary()}]. @@ -53,24 +57,27 @@ init_migrations() -> end end. +list_migrations() -> + mnesia:dirty_all_keys(?TABLE). + %%------------------------------------------------------------------------------ --spec list_unapplied_ups(migresia:migration_sources()) - -> mod_bin_list(). +-spec list_unapplied_ups(migresia:migration_sources()) -> mod_bin_list(). list_unapplied_ups({rel_relative_dir, default}) -> list_unapplied_ups({rel_relative_dir, get_default_dir()}); list_unapplied_ups({rel_relative_dir, DirName}) -> - get_migrations(get_release_dir(DirName)); + get_unapplied(get_release_dir(DirName)); list_unapplied_ups(App) when is_atom(App) -> - get_migrations(get_priv_dir(App)). + get_unapplied(get_priv_dir(App)). --spec list_all_ups(migresia:migration_sources()) -> mod_bin_list(). -list_all_ups({rel_relative_dir, default}) -> - list_all_ups({rel_relative_dir, get_default_dir()}); -list_all_ups({rel_relative_dir, DirName}) -> - get_all_migrations(get_release_dir(DirName)); -list_all_ups(App) when is_atom(App) -> - get_all_migrations(get_priv_dir(App)). +-spec list_applied_ups(migresia:migration_sources(), integer()) -> + mod_bin_list(). +list_applied_ups({rel_relative_dir, default}, Time) -> + list_applied_ups({rel_relative_dir, get_default_dir()}, Time); +list_applied_ups({rel_relative_dir, DirName}, Time) -> + get_applied(get_release_dir(DirName), Time); +list_applied_ups(App, Time) when is_atom(App) -> + get_applied(get_priv_dir(App), Time). get_default_dir() -> case application:get_env(migresia, rel_relative_dir) of @@ -111,23 +118,37 @@ check_priv_dir(App) -> false -> throw({error, enoent}) end. --spec get_migrations(binary()) -> mod_bin_list(). -get_migrations(Dir) -> - ToApply = check_dir(file:list_dir(Dir)), +-spec get_unapplied(binary()) -> mod_bin_list(). +get_unapplied(Dir) -> + load_migrations(Dir, fun list_unapplied/2). + +-spec get_applied(binary(), integer()) -> mod_bin_list(). +get_applied(Dir, Time) -> + load_migrations(Dir, fun(X, Y) -> list_applied(X, Y, Time) end). + +list_unapplied(FromDir, FromDB) -> + Unapplied = [X || {Ts, _} = X <- FromDir, + length(FromDB) =:= 0 orelse Ts > lists:max(FromDB)], + lists:keysort(1, Unapplied). + +list_applied(FromDir, FromDB, Time) -> + Applied = [X || {Ts, _} = X <- FromDir, + lists:member(Ts, FromDB), Ts > Time], + lists:reverse(lists:keysort(1, Applied)). + +load_migrations(Dir, FilterFun) -> + Migrations = check_dir(file:list_dir(Dir)), case check_table() of {error, _} = Err -> throw(Err); - Applied -> compile_and_load(Dir, ToApply, Applied) + Applied -> compile_and_load(Dir, FilterFun, Migrations, Applied) end. -get_all_migrations(Dir) -> - ToApply = lists:sort(check_dir(file:list_dir(Dir))), - compile_and_load(Dir, ToApply, []). - check_dir({error, _} = Err) -> throw(Err); check_dir({ok, Filenames}) -> normalize_names(Filenames, []). normalize_names([<>|T], Acc) -> - normalize_names(T, [{Short, Short}|Acc]); + Int = list_to_integer(binary_to_list(Short)), + normalize_names(T, [{Int, Short}|Acc]); normalize_names([<> = Name|T], Acc) when size(R) >= 4 andalso erlang:binary_part(R, size(R) - 4, 4) == <<".erl">> -> @@ -159,30 +180,16 @@ check_table() -> end end. -compile_and_load(Dir, ToApply, Applied) -> - ToExecute = compile_unapplied(Dir, ToApply, Applied, []), - Fun = fun({Module, Short, Binary}) -> - load_migration(Module, Short, Binary) - end, - lists:map(Fun, ToExecute). +compile_and_load(Dir, FilterFun, ToApply, Applied) -> + ToLoad = FilterFun(ToApply, Applied), + lists:map(fun(X) -> compile_and_load1(Dir, X) end, ToLoad). + +compile_and_load1(Dir, {Short, Name}) -> + {Module, Short, Binary} = compile_file(Dir, Short, Name), + load_migration(Module, Short, Binary). %%------------------------------------------------------------------------------ -compile_unapplied(Dir, [{Short, Module}|TN], [] = Old, Acc) -> - compile_unapplied(Dir, TN, Old, [compile_file(Dir, Short, Module)|Acc]); -compile_unapplied(Dir, [{Last, _}|TN], [Last], Acc) -> - compile_unapplied(Dir, TN, [], Acc); -compile_unapplied(Dir, [{Last, _}], [Last|_TO], Acc) -> - compile_unapplied(Dir, [], [], Acc); -compile_unapplied(Dir, [{Short, _}|TN], [Short|TO], Acc) -> - compile_unapplied(Dir, TN, TO, Acc); -compile_unapplied(Dir, [{New, _}|TN], [Old|_] = Applied, Acc) when New < Old -> - compile_unapplied(Dir, TN, Applied, Acc); -compile_unapplied(Dir, [{New, _}|_] = ToApply, [Old|TO], Acc) when New > Old -> - compile_unapplied(Dir, ToApply, TO, Acc); -compile_unapplied(_Dir, [], _, Acc) -> - lists:reverse(Acc). - compile_file(Dir, Short, Name) -> File = filename:join(Dir, Name), io:format("Compiling: ~s~n", [File]), @@ -196,7 +203,7 @@ compile_file(Dir, Short, Name) -> io:format("Errors: ~p~nWarnings: ~p~nAborting...~n", [Err, Warn]), throw({error, compile_error}); error -> - io:format("Errors encoutered, Aborting...~n", []), + io:format("Errors encountered, Aborting...~n", []), throw({error, compile_error}) end. @@ -211,6 +218,18 @@ load_migration(Module, Short, Binary) -> %%------------------------------------------------------------------------------ +get_ts_before_last() -> + case lists:member(?TABLE, mnesia:system_info(tables)) of + true -> get_ts_bef_last1(mnesia:dirty_all_keys(?TABLE)); + false -> ?FIRST_TS + end. + +get_ts_bef_last1([]) -> ?FIRST_TS; +get_ts_bef_last1([TS]) -> TS - 1; +get_ts_bef_last1(List) -> hd(tl(lists:reverse(List))). + +%%------------------------------------------------------------------------------ + execute_up({Module, Ts}) -> io:format("Executing up in ~s...~n", [Module]), Module:up(),