Current section
Files
Jump to
Current section
Files
src/dbschema.erl
-module(dbschema).
-compile({parse_transform, do}).
-export([
up/1,
list/0,
down/2
]).
-include("migrations.hrl").
-type migration() :: #migration{}.
up(MigrationsFolder) ->
do([error_m ||
dbschema_pg_driver:init(),
FileMigrations = get_migrations(MigrationsFolder),
Ids = [Id || #migration{id=Id} <- FileMigrations],
dbschema_utils:check_monotonic(Ids),
LastId <- dbschema_pg_driver:get_last_id(),
ActualMigrations =
[
M || #migration{id=Id}=M <- FileMigrations,
Id > LastId
],
dbschema_utils:success_foreach(
fun(Migration) ->
perform_(up, MigrationsFolder, Migration)
end, ActualMigrations)
]).
list() ->
dbschema_pg_driver:list(0).
down(MigrationsFolder, MigrationId) ->
do([error_m ||
ActualMigrations <- dbschema_pg_driver:list(MigrationId),
dbschema_utils:success_foreach(
fun(Migration) ->
perform_(down, MigrationsFolder, Migration)
end, lists:reverse(ActualMigrations))
]).
%% =============================================================================
%%% Internal functions
%% =============================================================================
perform_(Type, MigrationsFolder, #migration{filename=Filename, type = ExtType}=Migration) ->
MigrationFile = filename:join([MigrationsFolder, Filename]),
case ExtType of
<<"erl">> ->
{ok, Module} = compile:file(binary_to_list(MigrationFile)),
Up = fun Module:up/1,
Down = fun Module:down/1;
<<"sql">> ->
{ok, FileContent} = read_file(MigrationFile),
{Up, Down} = split_sql(FileContent)
end,
case Type of
up ->
lager:info("Performing migration: [UP] \"~s\"", [Filename]),
dbschema_pg_driver:up(Migration, Up);
down ->
lager:info("Performing migration: [DOWN] \"~s\"", [Filename]),
dbschema_pg_driver:down(Migration, Down)
end.
-spec get_migrations(Folder :: file:name()) -> [migration()].
get_migrations(Folder) ->
Migrations =
filelib:fold_files(
Folder,
".*", true,
fun(F, Acc) ->
F2 = filename:basename(F),
case migration_file(F2) of
{ok, Migration} -> [Migration|Acc];
{error, _Reason} ->
Acc
end
end,
[]),
lists:keysort(#migration.id, Migrations).
-spec migration_file(file:name()) -> {ok, migration()} | {error, Reason :: _}.
migration_file(Filename) ->
[$.|Ext] = filename:extension(Filename),
case get_id(Filename) of
{ok, Id} ->
migration_file(
Id,
list_to_binary(Ext),
iolist_to_binary(Filename));
{error, _Reason} = Err -> Err
end.
-spec migration_file(Id, Ext, Filename) -> {ok, migration()} | {error, Reason} when
Id :: non_neg_integer(),
Ext :: binary(),
Filename :: binary(),
Reason :: unknown_extension.
migration_file(Id, Type, Filename) when Type =:= <<"sql">>;
Type =:= <<"erl">> ->
{ok, #migration{id = Id, filename = Filename, type = Type}};
migration_file(_, _, _) ->
{error, unknown_extension}.
-spec get_id(file:name()) -> {ok, Id :: non_neg_integer()} | {error, no_id}.
get_id(Filename) ->
case re:run(Filename, "(\\d+)-.*", [{capture, [1], list}]) of
{match, [Id]} -> {ok, list_to_integer(Id)};
_ -> {error, no_id}
end.
read_file(Filename) ->
case file:read_file(Filename) of
{ok, _Data} = Ok -> Ok;
{error, Reason} ->
{error, {file_error, Filename, Reason}}
end.
split_sql(FileContent) ->
Rows = binary:split(FileContent, <<"\n">>, [global]),
{ok, UpP} = re:compile(<<"^--\\s*up\\s*">>, [caseless]),
{ok, DownP} = re:compile(<<"^--\\s*down\\s*">>, [caseless]),
{_, UpR, DownR} = lists:foldl(fun(Row, {St, UpRows, DownRows}) ->
case re:run(Row, UpP) of
{match, _} -> {up, UpRows, DownRows};
nomatch ->
case re:run(Row, DownP) of
{match, _} -> {down, UpRows, DownRows};
nomatch ->
case St of
up -> {St, [Row,$\n|UpRows], DownRows};
down -> {St, UpRows, [Row,$\n|DownRows]}
end
end
end
end, {up, [], []}, Rows),
{lists:reverse(UpR), lists:reverse(DownR)}.