Current section
Files
Jump to
Current section
Files
src/cigogne@migration.erl
-module(cigogne@migration).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/cigogne/migration.gleam").
-export([to_fullname/1, set_folder/2, compare/2, chunk_by_transaction_option/1, find_unapplied/2, is_zero_migration/1, match_migrations/3, get_error_message/1, check_name/1, new/3, merge/1, create_zero_migration/3]).
-export_type([migration/0, migration_options/0, migration_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type migration() :: {migration,
binary(),
gleam@time@timestamp:timestamp(),
binary(),
list(binary()),
list(binary()),
migration_options(),
binary()}.
-type migration_options() :: {migration_options, boolean()}.
-type migration_error() :: {name_too_long_error, binary()} |
nothing_to_merge_error |
{migration_not_found, binary()} |
{file_hash_changed, binary()} |
{compound_error, list(migration_error())}.
-file("src/cigogne/migration.gleam", 106).
-spec int_to_2_chars_string(integer()) -> binary().
int_to_2_chars_string(N) ->
case N of
N@1 when N@1 < 10 ->
<<"0"/utf8, (erlang:integer_to_binary(N@1))/binary>>;
_ ->
erlang:integer_to_binary(N)
end.
-file("src/cigogne/migration.gleam", 91).
-spec format_timestamp(gleam@time@timestamp:timestamp()) -> binary().
format_timestamp(Timestamp) ->
{Date, Time} = begin
_pipe = Timestamp,
gleam@time@timestamp:to_calendar(_pipe, {duration, 0, 0})
end,
Date_str = begin
_pipe@2 = [erlang:element(2, Date),
begin
_pipe@1 = erlang:element(3, Date),
gleam@time@calendar:month_to_int(_pipe@1)
end,
erlang:element(4, Date)],
_pipe@3 = gleam@list:map(_pipe@2, fun int_to_2_chars_string/1),
gleam@string:join(_pipe@3, <<""/utf8>>)
end,
Time_str = begin
_pipe@4 = [erlang:element(2, Time),
erlang:element(3, Time),
erlang:element(4, Time)],
_pipe@5 = gleam@list:map(_pipe@4, fun int_to_2_chars_string/1),
gleam@string:join(_pipe@5, <<""/utf8>>)
end,
<<Date_str/binary, Time_str/binary>>.
-file("src/cigogne/migration.gleam", 87).
?DOC(" Get the full name of a migration, which is its timestamp followed by its name.\n").
-spec to_fullname(migration()) -> binary().
to_fullname(Migration) ->
<<<<(format_timestamp(erlang:element(3, Migration)))/binary, "-"/utf8>>/binary,
(erlang:element(4, Migration))/binary>>.
-file("src/cigogne/migration.gleam", 71).
?DOC(" Set the folder of a migration, updating its path accordingly.\n").
-spec set_folder(migration(), binary()) -> migration().
set_folder(Migration, Folder) ->
{migration,
<<<<<<Folder/binary, "/"/utf8>>/binary,
(to_fullname(Migration))/binary>>/binary,
".sql"/utf8>>,
erlang:element(3, Migration),
erlang:element(4, Migration),
erlang:element(5, Migration),
erlang:element(6, Migration),
erlang:element(7, Migration),
erlang:element(8, Migration)}.
-file("src/cigogne/migration.gleam", 114).
?DOC(" Compare two migrations.\n").
-spec compare(migration(), migration()) -> gleam@order:order().
compare(Migration_a, Migration_b) ->
_pipe = gleam@time@timestamp:compare(
erlang:element(3, Migration_a),
erlang:element(3, Migration_b)
),
gleam@order:break_tie(
_pipe,
gleam@string:compare(
erlang:element(4, Migration_a),
erlang:element(4, Migration_b)
)
).
-file("src/cigogne/migration.gleam", 139).
-spec do_chunk_by_transaction_option(
list(migration()),
list({boolean(), list(migration())})
) -> list({boolean(), list(migration())}).
do_chunk_by_transaction_option(Migrations, Result_so_far) ->
case {Migrations, Result_so_far} of
{[], _} ->
Result_so_far;
{[First | Rest], []} ->
Disable_transaction = erlang:element(2, erlang:element(7, First)),
do_chunk_by_transaction_option(
Rest,
[{Disable_transaction, [First]}]
);
{[First@1 | Rest@1], [Last_added | Rest_chunks]} ->
case erlang:element(2, erlang:element(7, First@1)) =:= erlang:element(
1,
Last_added
) of
true ->
do_chunk_by_transaction_option(
Rest@1,
[{erlang:element(1, Last_added),
[First@1 | erlang:element(2, Last_added)]} |
Rest_chunks]
);
false ->
New_chunk = {erlang:element(2, erlang:element(7, First@1)),
[First@1]},
do_chunk_by_transaction_option(
Rest@1,
[New_chunk | Result_so_far]
)
end
end.
-file("src/cigogne/migration.gleam", 166).
-spec do_reverse_chunks(
list({boolean(), list(migration())}),
list({boolean(), list(migration())})
) -> list({boolean(), list(migration())}).
do_reverse_chunks(Chunks, Reversed_chunks) ->
case Chunks of
[] ->
Reversed_chunks;
[First | Rest] ->
Reversed_chunk = {erlang:element(1, First),
lists:reverse(erlang:element(2, First))},
do_reverse_chunks(Rest, [Reversed_chunk | Reversed_chunks])
end.
-file("src/cigogne/migration.gleam", 132).
?DOC(" Chunk a list of migrations into multiple lists depending on their disable_transaction option\n").
-spec chunk_by_transaction_option(list(migration())) -> list({boolean(),
list(migration())}).
chunk_by_transaction_option(Migrations) ->
_pipe = do_chunk_by_transaction_option(Migrations, []),
do_reverse_chunks(_pipe, []).
-file("src/cigogne/migration.gleam", 208).
-spec do_merge_contents(list(migration()), {list(binary()), list(binary())}) -> {list(binary()),
list(binary())}.
do_merge_contents(Migrations, Contents) ->
case Migrations of
[] ->
Contents;
[Mig | Rest] ->
{Ups, Downs} = Contents,
Migration_name = to_fullname(Mig),
Mig_ups = [<<"\n--- "/utf8, Migration_name/binary>> |
erlang:element(5, Mig)],
Ups@1 = lists:append(Ups, Mig_ups),
Mig_downs = [<<"\n--- "/utf8, Migration_name/binary>> |
erlang:element(6, Mig)],
Downs@1 = lists:append(Mig_downs, Downs),
do_merge_contents(Rest, {Ups@1, Downs@1})
end.
-file("src/cigogne/migration.gleam", 231).
?DOC(" Find migrations that are in `migrations` but not in `applied`.\n").
-spec find_unapplied(list(migration()), list(migration())) -> list(migration()).
find_unapplied(Migrations, Applied) ->
cigogne@internal@utils:list_difference(Migrations, Applied, fun compare/2).
-file("src/cigogne/migration.gleam", 295).
?DOC(" Checks if a migration is a zero migration (has been created with `create_zero_migration`).\n").
-spec is_zero_migration(migration()) -> boolean().
is_zero_migration(Migration) ->
(erlang:element(2, Migration) =:= <<""/utf8>>) andalso (erlang:element(
3,
Migration
)
=:= cigogne@internal@utils:epoch()).
-file("src/cigogne/migration.gleam", 241).
?DOC(
" Match a list of migrations usually from the database with a list of migration usually from the filesystem\n"
" and ensure their hashes match. This returns migrations from the second list as they are usually more detailed.\n"
" This fails it a migration is not found or if a hash does not match.\n"
).
-spec match_migrations(list(migration()), list(migration()), boolean()) -> {ok,
list(migration())} |
{error, migration_error()}.
match_migrations(Elements, Matches, No_hash_check) ->
{Zeroes, Elements@1} = gleam@list:split_while(
Elements,
fun is_zero_migration/1
),
Matches@1 = cigogne@internal@utils:find_matches(
Elements@1,
Matches,
fun compare/2
),
Results = begin
gleam@list:map(
Matches@1,
fun(_use0) ->
{Migration, Match_res} = _use0,
case Match_res of
{ok, Match} ->
case {erlang:element(8, Migration) =:= erlang:element(
8,
Match
),
No_hash_check} of
{true, _} ->
{ok, Match};
{false, true} ->
gleam_stdlib:println(
<<<<"Warning: Hash of file "/utf8,
(to_fullname(Migration))/binary>>/binary,
" has changed, but hash check is disabled."/utf8>>
),
{ok, Match};
{false, false} ->
{error,
{file_hash_changed,
begin
_pipe = Migration,
to_fullname(_pipe)
end}}
end;
{error, _} ->
{error,
{migration_not_found,
begin
_pipe@1 = Migration,
to_fullname(_pipe@1)
end}}
end
end
)
end,
_pipe@2 = cigogne@internal@utils:get_results_or_errors(Results),
_pipe@3 = gleam@result:map_error(
_pipe@2,
fun(Field@0) -> {compound_error, Field@0} end
),
gleam@result:map(
_pipe@3,
fun(Matches@2) -> lists:append(Zeroes, Matches@2) end
).
-file("src/cigogne/migration.gleam", 299).
-spec get_error_message(migration_error()) -> binary().
get_error_message(Error) ->
case Error of
{compound_error, Errors} ->
<<"Many migration errors happened: \n "/utf8,
(begin
_pipe = Errors,
_pipe@1 = gleam@list:map(_pipe, fun get_error_message/1),
gleam@string:join(_pipe@1, <<",\n "/utf8>>)
end)/binary>>;
{file_hash_changed, Fullname} ->
<<<<"Hash of file "/utf8, Fullname/binary>>/binary,
" has changed ! Did you modify the file after applying the migration ?"/utf8>>;
{migration_not_found, Fullname@1} ->
<<<<<<<<"Could not find a matching migration file for "/utf8,
Fullname@1/binary>>/binary,
". Check there is a file named "/utf8>>/binary,
Fullname@1/binary>>/binary,
".sql in your migrations folder."/utf8>>;
{name_too_long_error, Name} ->
<<<<"Name "/utf8, Name/binary>>/binary,
"is too long ! Migration names shouldn't exceed 255 characters."/utf8>>;
nothing_to_merge_error ->
<<"Could not merge 0 migrations"/utf8>>
end.
-file("src/cigogne/migration.gleam", 79).
?DOC(" Check that the provided name is a valid migration name.\n").
-spec check_name(binary()) -> {ok, binary()} | {error, migration_error()}.
check_name(Name) ->
case string:length(Name) > 255 of
true ->
{error, {name_too_long_error, Name}};
false ->
{ok, Name}
end.
-file("src/cigogne/migration.gleam", 49).
?DOC(
" Create a new migration with the given folder, name and timestamp.\n"
" The migration will have empty up and down queries and an empty sha256 hash.\n"
).
-spec new(binary(), binary(), gleam@time@timestamp:timestamp()) -> {ok,
migration()} |
{error, migration_error()}.
new(Folder, Name, Timestamp) ->
gleam@result:map(
check_name(Name),
fun(Name@1) ->
New_migration = {migration,
<<""/utf8>>,
Timestamp,
Name@1,
[],
[],
{migration_options, false},
<<""/utf8>>},
_pipe = New_migration,
set_folder(_pipe, Folder)
end
).
-file("src/cigogne/migration.gleam", 179).
-spec merge_migration_contents(list(migration())) -> list({list(binary()),
list(binary()),
migration_options()}).
merge_migration_contents(Migrations) ->
Migration_chunks = chunk_by_transaction_option(Migrations),
gleam@list:flat_map(
Migration_chunks,
fun(_use0) ->
{Disable_trx, Chunk} = _use0,
case Disable_trx of
true ->
_pipe = Chunk,
gleam@list:map(
_pipe,
fun(Mig) ->
Migration_name = to_fullname(Mig),
{[<<"\n--- "/utf8, Migration_name/binary>> |
erlang:element(5, Mig)],
[<<"\n--- "/utf8, Migration_name/binary>> |
erlang:element(6, Mig)],
erlang:element(7, Mig)}
end
);
false ->
{Ups, Downs} = do_merge_contents(Chunk, {[], []}),
Options = {migration_options, false},
[{Ups, Downs, Options}]
end
end
).
-file("src/cigogne/migration.gleam", 120).
?DOC(" Merge multiple migrations into a list of queries up, queries down and disable_transaction.\n").
-spec merge(list(migration())) -> {ok,
list({list(binary()), list(binary()), migration_options()})} |
{error, migration_error()}.
merge(Migrations) ->
gleam@bool:guard(
gleam@list:is_empty(Migrations),
{error, nothing_to_merge_error},
fun() -> {ok, merge_migration_contents(Migrations)} end
).
-file("src/cigogne/migration.gleam", 276).
?DOC(" Create a \"zero\" migration that should be applied before the user's migrations.\n").
-spec create_zero_migration(binary(), list(binary()), list(binary())) -> migration().
create_zero_migration(Name, Queries_up, Queries_down) ->
{migration,
<<""/utf8>>,
cigogne@internal@utils:epoch(),
Name,
Queries_up,
Queries_down,
{migration_options, false},
cigogne@internal@utils:make_sha256(
<<(begin
_pipe = Queries_up,
gleam@string:join(_pipe, <<";"/utf8>>)
end)/binary,
(begin
_pipe@1 = Queries_down,
gleam@string:join(_pipe@1, <<";"/utf8>>)
end)/binary>>
)}.