-module(rally_runtime@migrate).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally_runtime/migrate.gleam").
-export([error_to_string/1, run/2]).
-export_type([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.
?MODULEDOC(
" SQL migration runner for SQLite. Reads numbered .sql files from a\n"
" directory, tracks the last applied version in a schema_migrations\n"
" table, and runs pending migrations inside transactions. Failed\n"
" migrations roll back and leave the version at the last success.\n"
).
-type migration_error() :: {table_create_failed, binary()} |
{version_query_failed, binary()} |
{version_init_failed, binary()} |
{dir_read_failed, binary()} |
{file_read_failed, binary(), binary()} |
{migration_failed, binary(), binary()} |
{version_update_failed, binary()} |
{filename_parse_failed, binary()}.
-file("src/rally_runtime/migrate.gleam", 26).
-spec error_to_string(migration_error()) -> binary().
error_to_string(Error) ->
case Error of
{table_create_failed, Message} ->
<<"Failed to create schema_migrations: "/utf8, Message/binary>>;
{version_query_failed, Message@1} ->
<<"Failed to get migration version: "/utf8, Message@1/binary>>;
{version_init_failed, Message@2} ->
<<"Failed to init schema_migrations: "/utf8, Message@2/binary>>;
{dir_read_failed, Message@3} ->
<<"Failed to read migrations directory: "/utf8, Message@3/binary>>;
{file_read_failed, Filename, Message@4} ->
<<<<<<"Failed to read "/utf8, Filename/binary>>/binary, ": "/utf8>>/binary,
Message@4/binary>>;
{migration_failed, Filename@1, Message@5} ->
<<<<<<"Migration "/utf8, Filename@1/binary>>/binary,
" failed: "/utf8>>/binary,
Message@5/binary>>;
{version_update_failed, Message@6} ->
<<"Failed to update migration version: "/utf8, Message@6/binary>>;
{filename_parse_failed, Filename@2} ->
<<"Invalid migration filename (expected NNN_name.sql): "/utf8,
Filename@2/binary>>
end.
-file("src/rally_runtime/migrate.gleam", 170).
-spec run_migration_sql(sqlight:connection(), integer(), binary(), binary()) -> {ok,
nil} |
{error, migration_error()}.
run_migration_sql(Conn, Num, File, Sql) ->
case sqlight:exec(Sql, Conn) of
{ok, _} ->
case sqlight:exec(
<<<<"UPDATE schema_migrations SET last_migration = "/utf8,
(erlang:integer_to_binary(Num))/binary>>/binary,
";"/utf8>>,
Conn
) of
{ok, _} ->
case sqlight:exec(<<"COMMIT"/utf8>>, Conn) of
{ok, _} ->
{ok, nil};
{error, E} ->
{error,
{version_update_failed, erlang:element(3, E)}}
end;
{error, E@1} ->
_ = sqlight:exec(<<"ROLLBACK"/utf8>>, Conn),
{error, {version_update_failed, erlang:element(3, E@1)}}
end;
{error, E@2} ->
_ = sqlight:exec(<<"ROLLBACK"/utf8>>, Conn),
{error, {migration_failed, File, erlang:element(3, E@2)}}
end.
-file("src/rally_runtime/migrate.gleam", 133).
-spec run_pending(sqlight:connection(), binary(), list({integer(), binary()})) -> {ok,
nil} |
{error, migration_error()}.
run_pending(Conn, Dir, Files) ->
case Files of
[] ->
{ok, nil};
[{Num, File} | Rest] ->
Path = <<<<Dir/binary, "/"/utf8>>/binary, File/binary>>,
gleam@result:'try'(
begin
_pipe = simplifile:read(Path),
gleam@result:map_error(
_pipe,
fun(E) ->
{file_read_failed,
File,
simplifile:describe_error(E)}
end
)
end,
fun(Sql) ->
gleam_stdlib:println(
<<<<<<" migration "/utf8,
(erlang:integer_to_binary(Num))/binary>>/binary,
": "/utf8>>/binary,
File/binary>>
),
gleam@result:'try'(
begin
_pipe@1 = sqlight:exec(<<"BEGIN"/utf8>>, Conn),
gleam@result:map_error(
_pipe@1,
fun(E@1) ->
{migration_failed,
File,
erlang:element(3, E@1)}
end
)
end,
fun(_) ->
gleam@result:'try'(
run_migration_sql(Conn, Num, File, Sql),
fun(_) -> run_pending(Conn, Dir, Rest) end
)
end
)
end
)
end.
-file("src/rally_runtime/migrate.gleam", 207).
-spec parse_number(binary()) -> {ok, integer()} | {error, migration_error()}.
parse_number(Filename) ->
case gleam@string:split(Filename, <<"_"/utf8>>) of
[Num_str | _] ->
_pipe = gleam_stdlib:parse_int(Num_str),
gleam@result:replace_error(_pipe, {filename_parse_failed, Filename});
_ ->
{error, {filename_parse_failed, Filename}}
end.
-file("src/rally_runtime/migrate.gleam", 96).
-spec get_current_version(sqlight:connection()) -> {ok, integer()} |
{error, migration_error()}.
get_current_version(Conn) ->
Decoder = begin
gleam@dynamic@decode:field(
0,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Version) -> gleam@dynamic@decode:success(Version) end
)
end,
case sqlight:'query'(
<<"SELECT last_migration FROM schema_migrations LIMIT 1"/utf8>>,
Conn,
[],
Decoder
) of
{ok, [Version@1]} ->
{ok, Version@1};
{ok, []} ->
_pipe = sqlight:exec(
<<"INSERT INTO schema_migrations (last_migration) VALUES (0);"/utf8>>,
Conn
),
_pipe@1 = gleam@result:map_error(
_pipe,
fun(E) -> {version_init_failed, erlang:element(3, E)} end
),
gleam@result:map(_pipe@1, fun(_) -> 0 end);
{ok, _} ->
_ = sqlight:exec(
<<"DELETE FROM schema_migrations; INSERT INTO schema_migrations (last_migration) VALUES (0);"/utf8>>,
Conn
),
{ok, 0};
{error, E@1} ->
{error, {version_query_failed, erlang:element(3, E@1)}}
end.
-file("src/rally_runtime/migrate.gleam", 47).
-spec run(sqlight:connection(), binary()) -> {ok, nil} |
{error, migration_error()}.
run(Conn, Dir) ->
gleam@result:'try'(
begin
_pipe = sqlight:exec(
<<"CREATE TABLE IF NOT EXISTS schema_migrations (
last_migration INTEGER NOT NULL
);"/utf8>>,
Conn
),
gleam@result:map_error(
_pipe,
fun(E) -> {table_create_failed, erlang:element(3, E)} end
)
end,
fun(_) ->
gleam@result:'try'(
get_current_version(Conn),
fun(Current) ->
gleam@result:'try'(
begin
_pipe@1 = simplifile_erl:read_directory(Dir),
gleam@result:map_error(
_pipe@1,
fun(E@1) ->
{dir_read_failed,
simplifile:describe_error(E@1)}
end
)
end,
fun(Files) ->
gleam@result:'try'(
begin
_pipe@2 = Files,
_pipe@3 = gleam@list:filter(
_pipe@2,
fun(F) ->
gleam_stdlib:string_ends_with(
F,
<<".sql"/utf8>>
)
end
),
_pipe@4 = gleam@list:sort(
_pipe@3,
fun gleam@string:compare/2
),
gleam@list:try_map(
_pipe@4,
fun(File) ->
gleam@result:'try'(
parse_number(File),
fun(Number) ->
{ok, {Number, File}}
end
)
end
)
end,
fun(Migrations) ->
Pending = begin
_pipe@5 = Migrations,
gleam@list:filter(
_pipe@5,
fun(F@1) ->
{Number@1, _} = F@1,
Number@1 > Current
end
)
end,
case Pending of
[] ->
gleam_stdlib:println(
<<<<" migrations: up to date (v"/utf8,
(erlang:integer_to_binary(
Current
))/binary>>/binary,
")"/utf8>>
),
{ok, nil};
_ ->
run_pending(Conn, Dir, Pending)
end
end
)
end
)
end
)
end
).