-
Notifications
You must be signed in to change notification settings - Fork 4
/
engine.erl
63 lines (55 loc) · 2.41 KB
/
engine.erl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
-module(engine).
-export([migrate/3]).
-type version() :: non_neg_integer().
-type migration() :: {version(), Filename :: nonempty_string()}.
-type error() :: {error, Type :: atom(), Details :: any()}.
-spec migrate(
Path :: nonempty_string(),
FTx :: fun((F) -> ok | error()),
FQuery :: fun((nonempty_string()) -> ok | [migration()] | version() | error())) -> R
when
F :: fun(() -> ok | error()),
R :: fun(() -> ok | error()).
migrate(Path, FTx, FQuery) ->
ok = FQuery(dialect_postgres:init()),
fun() ->
FTx(flatten(map(
find_migrations(Path, FQuery),
fun(V_F) -> do_migration(Path, FQuery, V_F) end)))
end.
do_migration(Path, FQuery, {V, F}) ->
ScriptPath = filename:join(Path, F),
compose(
fun() -> file:read_file(ScriptPath) end,
fun({ok, ScriptBody}) ->
ExpectedVersion = FQuery(dialect_postgres:latest_existing_version()) + 1,
case V of
ExpectedVersion ->
ok = FQuery(ScriptBody),
ok = FQuery(dialect_postgres:save_migration(V, F));
_ -> {
error,
unexpected_version,
{expected, ExpectedVersion, supplied, V}}
end
end);
do_migration(_Path, _FQuery, Error) -> Error.
find_migrations(ScriptsLocation, FQuery) ->
compose(
fun() -> file:list_dir_all(ScriptsLocation) end,
fun(Files) -> filter_script_files(Files, ScriptsLocation, FQuery) end).
filter_script_files({ok, Files}, _Folder, FQuery) ->
MigrationsDone = sets:from_list(FQuery(dialect_postgres:migrations_done())),
lists:filter(
fun(N) -> not sets:is_element(N, MigrationsDone) end,
lists:keysort(1, [version_and_filename(F) || F <- Files]));
filter_script_files(Error, Folder, _FQuery)->
[{error, invalid_folder, {folder_supplied, Folder, error, Error}}].
version_and_filename(F) ->
case string:to_integer(F) of
{Value, "_" ++ _} -> {Value, F};
_ -> {error, invalid_filename, {expected, "<number>_<description>.sql", supplied, F}}
end.
compose(F1, F2) -> fun() -> F2(F1()) end.
map(Generate, Map) -> fun() -> [Map(R) || R <- Generate()] end.
flatten(Generate) -> fun() -> [ok = R() || R <- Generate()], ok end.