Current section

Files

Jump to
cigogne src cigogne@migration.erl
Raw

src/cigogne@migration.erl

-module(cigogne@migration).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/cigogne/migration.gleam").
-export([to_fullname/1, set_folder/2, compare/2, find_unapplied/2, create_zero_migration/3, is_zero_migration/1, match_migrations/3, get_error_message/1, check_name/1, new/3, merge/3]).
-export_type([migration/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()),
binary()}.
-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", 94).
-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", 79).
-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", 75).
?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", 59).
?DOC(" Set the folder of a migration, updating its path accordingly.\n").
-spec set_folder(migration(), binary()) -> migration().
set_folder(Migration, Folder) ->
_record = Migration,
{migration,
<<<<<<Folder/binary, "/"/utf8>>/binary,
(to_fullname(Migration))/binary>>/binary,
".sql"/utf8>>,
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
erlang:element(7, _record)}.
-file("src/cigogne/migration.gleam", 102).
?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", 128).
-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] ->
Migration_name = to_fullname(Mig),
{Ups, Downs} = Contents,
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", 124).
-spec merge_migration_contents(list(migration())) -> {list(binary()),
list(binary())}.
merge_migration_contents(Migrations) ->
do_merge_contents(Migrations, {[], []}).
-file("src/cigogne/migration.gleam", 151).
?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", 195).
?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,
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>>
)}.
-file("src/cigogne/migration.gleam", 213).
?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", 161).
?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 No_hash_check orelse (erlang:element(7, Migration)
=:= erlang:element(7, Match)) of
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 ->
{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", 217).
-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", 67).
?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", 38).
?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,
[],
[],
<<""/utf8>>},
_pipe = New_migration,
set_folder(_pipe, Folder)
end
).
-file("src/cigogne/migration.gleam", 110).
?DOC(
" Merge multiple migrations into a single one, concatenating their up and down queries.\n"
" The resulting migration will have an empty path and sha256 hash.\n"
" The provided timestamp and name will be used for the resulting migration.\n"
).
-spec merge(list(migration()), gleam@time@timestamp:timestamp(), binary()) -> {ok,
migration()} |
{error, migration_error()}.
merge(Migrations, Timestamp, Name) ->
gleam@result:'try'(
check_name(Name),
fun(Name@1) ->
gleam@bool:guard(
gleam@list:is_empty(Migrations),
{error, nothing_to_merge_error},
fun() ->
{Ups, Downs} = merge_migration_contents(Migrations),
Hash = <<""/utf8>>,
{ok,
{migration,
<<""/utf8>>,
Timestamp,
Name@1,
Ups,
Downs,
Hash}}
end
)
end
).