Packages

Ecto-like database migration library for Gleam + PostgreSQL

Current section

Files

Jump to
gorrion src gorrion@runner.erl
Raw

src/gorrion@runner.erl

-module(gorrion@runner).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gorrion/runner.gleam").
-export([apply_migration/2, revert_migration/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(" Executes migration SQL within transactions.\n").
-file("src/gorrion/runner.gleam", 18).
?DOC(
" Apply a single migration: execute the up SQL, then record it.\n"
" Both steps run inside the same database transaction so that a crash or\n"
" rollback between them can never leave the schema mutated without a\n"
" matching `_schema_migrations` row (or vice versa).\n"
).
-spec apply_migration(pog:connection(), gorrion@types:migration()) -> {ok, nil} |
{error, gorrion@types:migration_error()}.
apply_migration(Db, Migration) ->
gleam_stdlib:println(
<<<<<<" Applying migration "/utf8,
(erlang:integer_to_binary(erlang:element(2, Migration)))/binary>>/binary,
": "/utf8>>/binary,
(erlang:element(3, Migration))/binary>>
),
Decoder = gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_dynamic/1},
fun(_) -> nil end
),
Tx_result = pog:transaction(
Db,
fun(Tx) ->
gleam@result:'try'(
begin
_pipe = erlang:element(4, Migration),
_pipe@1 = pog:'query'(_pipe),
_pipe@2 = pog:returning(_pipe@1, Decoder),
_pipe@3 = pog:execute(_pipe@2, Tx),
_pipe@4 = gleam@result:map(_pipe@3, fun(_) -> nil end),
gleam@result:map_error(
_pipe@4,
fun(E) ->
{migration_failed,
erlang:element(2, Migration),
erlang:element(3, Migration),
gleam@string:inspect(E)}
end
)
end,
fun(_) ->
gorrion@tracker:record(
Tx,
erlang:element(2, Migration),
erlang:element(3, Migration)
)
end
)
end
),
case Tx_result of
{ok, nil} ->
{ok, nil};
{error, {transaction_rolled_back, Migration_error}} ->
{error, Migration_error};
{error, {transaction_query_error, Query_error}} ->
{error,
{migration_failed,
erlang:element(2, Migration),
erlang:element(3, Migration),
gleam@string:inspect(Query_error)}}
end.
-file("src/gorrion/runner.gleam", 67).
?DOC(
" Revert a single migration: execute the down SQL, then remove the record.\n"
" If no down SQL was provided (empty string), just removes the tracking record.\n"
" Both steps run inside the same transaction for the same atomicity reason\n"
" described on `apply_migration`.\n"
).
-spec revert_migration(pog:connection(), gorrion@types:migration()) -> {ok, nil} |
{error, gorrion@types:migration_error()}.
revert_migration(Db, Migration) ->
gleam_stdlib:println(
<<<<<<" Rolling back migration "/utf8,
(erlang:integer_to_binary(erlang:element(2, Migration)))/binary>>/binary,
": "/utf8>>/binary,
(erlang:element(3, Migration))/binary>>
),
case erlang:element(5, Migration) of
<<""/utf8>> ->
gleam_stdlib:println(
<<" (no down migration — removing record only)"/utf8>>
),
gorrion@tracker:remove(Db, erlang:element(2, Migration));
Down_sql ->
Decoder = gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_dynamic/1},
fun(_) -> nil end
),
Tx_result = pog:transaction(
Db,
fun(Tx) ->
gleam@result:'try'(
begin
_pipe = Down_sql,
_pipe@1 = pog:'query'(_pipe),
_pipe@2 = pog:returning(_pipe@1, Decoder),
_pipe@3 = pog:execute(_pipe@2, Tx),
_pipe@4 = gleam@result:map(
_pipe@3,
fun(_) -> nil end
),
gleam@result:map_error(
_pipe@4,
fun(E) ->
{rollback_failed,
erlang:element(2, Migration),
erlang:element(3, Migration),
gleam@string:inspect(E)}
end
)
end,
fun(_) ->
gorrion@tracker:remove(
Tx,
erlang:element(2, Migration)
)
end
)
end
),
case Tx_result of
{ok, nil} ->
{ok, nil};
{error, {transaction_rolled_back, Migration_error}} ->
{error, Migration_error};
{error, {transaction_query_error, Query_error}} ->
{error,
{rollback_failed,
erlang:element(2, Migration),
erlang:element(3, Migration),
gleam@string:inspect(Query_error)}}
end
end.