Current section
Files
Jump to
Current section
Files
src/migrant@database.erl
-module(migrant@database).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([exec/2, 'query'/4, create_migrations_table/2, filter_applied_migrations/3, apply_migrations/2]).
-spec exec(sqlight:connection(), binary()) -> {ok, nil} |
{error, migrant@types:error()}.
exec(Db, Sql) ->
_pipe = sqlight:exec(Sql, Db),
_pipe@1 = gleam@result:replace(_pipe, nil),
gleam@result:map_error(
_pipe@1,
fun(Field@0) -> {database_error, Field@0} end
).
-spec 'query'(
sqlight:connection(),
binary(),
list(sqlight:value()),
fun((gleam@dynamic:dynamic_()) -> {ok, GAT} |
{error, list(gleam@dynamic:decode_error())})
) -> {ok, list(GAT)} | {error, migrant@types:error()}.
'query'(Db, Sql, Args, Decoder) ->
_pipe = sqlight:'query'(Sql, Db, Args, Decoder),
gleam@result:map_error(_pipe, fun(Field@0) -> {database_error, Field@0} end).
-spec create_migrations_table(
sqlight:connection(),
fun(() -> {ok, nil} | {error, migrant@types:error()})
) -> {ok, nil} | {error, migrant@types:error()}.
create_migrations_table(Db, Next) ->
gleam@io:println(<<"-> Creating migrations table"/utf8>>),
Query = <<"
CREATE TABLE IF NOT EXISTS __migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
"/utf8>>,
case exec(Db, Query) of
{ok, _} ->
Next();
{error, E} ->
{error, E}
end.
-spec pluralise_migration(integer()) -> binary().
pluralise_migration(Count) ->
case Count of
1 ->
<<"migration"/utf8>>;
_ ->
<<"migrations"/utf8>>
end.
-spec filter_applied_migrations(
sqlight:connection(),
gleam@dict:dict(binary(), migrant@types:migration()),
fun((gleam@dict:dict(binary(), migrant@types:migration())) -> {ok, nil} |
{error, migrant@types:error()})
) -> {ok, nil} | {error, migrant@types:error()}.
filter_applied_migrations(Db, Migrations, Next) ->
gleam@io:println(<<"-> Filtering applied migrations"/utf8>>),
Sql = <<"SELECT name FROM __migrations ORDER BY id, name ASC;"/utf8>>,
case 'query'(
Db,
Sql,
[],
gleam@dynamic:element(0, fun gleam@dynamic:string/1)
) of
{ok, Applied} ->
_pipe = Migrations,
_pipe@1 = gleam@dict:drop(_pipe, Applied),
(fun(M) ->
Count = gleam@dict:size(M),
case Count of
0 ->
gleam@io:println(<<"-> No migrations to apply"/utf8>>),
{ok, nil};
_ ->
gleam@io:println(
<<<<<<<<"-> Found "/utf8,
(gleam@int:to_string(
gleam@dict:size(M)
))/binary>>/binary,
" "/utf8>>/binary,
(pluralise_migration(Count))/binary>>/binary,
" to apply"/utf8>>
),
Next(M)
end
end)(_pipe@1);
{error, E} ->
gleam@io:debug(E),
gleam@io:println(<<"-> Failed to filter applied migrations"/utf8>>),
{error, E}
end.
-spec rollback({binary(), migrant@types:migration()}, sqlight:connection()) -> {ok,
nil} |
{error, migrant@types:error()}.
rollback(Migration_tuple, Db) ->
{Name, Migration} = Migration_tuple,
case erlang:element(3, Migration) of
{some, Sql} ->
case exec(Db, Sql) of
{ok, _} ->
{ok, nil};
{error, E} ->
{error, E}
end;
none ->
{error,
{migration_error,
<<"Unable to rollback migration: "/utf8, Name/binary>>,
rollback_error}}
end.
-spec mark_migration_as_applied(
sqlight:connection(),
{binary(), migrant@types:migration()}
) -> {ok, nil} | {error, migrant@types:error()}.
mark_migration_as_applied(Db, Migration_tuple) ->
{Name, _} = Migration_tuple,
Sql = <<"INSERT INTO __migrations (name) VALUES (?) returning id;"/utf8>>,
case 'query'(
Db,
Sql,
[sqlight:text(Name)],
gleam@dynamic:element(0, fun gleam@dynamic:int/1)
) of
{ok, _} ->
{ok, nil};
{error, E} ->
{error, E}
end.
-spec apply({binary(), migrant@types:migration()}, sqlight:connection()) -> {ok,
nil} |
{error, migrant@types:error()}.
apply(Migration_tuple, Db) ->
{Name, Migration} = Migration_tuple,
case erlang:element(2, Migration) of
{some, Sql} ->
gleam@io:println(<<"-> Applying migration: "/utf8, Name/binary>>),
case exec(Db, Sql) of
{ok, _} ->
case mark_migration_as_applied(Db, Migration_tuple) of
{ok, _} ->
{ok, nil};
{error, E} ->
gleam@io:println(
<<"-> Failed to mark migration as applied: "/utf8,
Name/binary>>
),
gleam@io:debug(E),
gleam@io:println(
<<"-> Rolling back migration: "/utf8,
Name/binary>>
),
case rollback(Migration_tuple, Db) of
{ok, _} ->
{error,
{migration_error,
<<"Rollback complete, failed to mark migration as applied: "/utf8,
Name/binary>>,
E}};
{error, E@1} ->
gleam@io:println(
<<"-> Failed to rollback migration: "/utf8,
Name/binary>>
),
{error, E@1}
end
end,
{ok, nil};
{error, E@2} ->
gleam@io:println(
<<"-> Failed to apply migration: "/utf8, Name/binary>>
),
gleam@io:debug(E@2),
gleam@io:println(
<<"-> Rolling back migration: "/utf8, Name/binary>>
),
case rollback(Migration_tuple, Db) of
{ok, _} ->
{error,
{migration_error,
<<"Rollback complete, failed to apply migration: "/utf8,
Name/binary>>,
E@2}};
{error, E@3} ->
{error, E@3}
end
end;
none ->
gleam@io:println(
<<<<"-> Skipping migration: "/utf8, Name/binary>>/binary,
" no `up` query"/utf8>>
),
{ok, nil}
end.
-spec run_migration(
list({binary(), migrant@types:migration()}),
sqlight:connection(),
integer()
) -> {ok, integer()} | {error, migrant@types:error()}.
run_migration(Migrations, Db, Count) ->
case Migrations of
[] ->
{ok, Count};
[Migration | Rest] ->
case apply(Migration, Db) of
{ok, _} ->
run_migration(Rest, Db, Count + 1);
{error, E} ->
{error, E}
end
end.
-spec sort_migrations(gleam@dict:dict(binary(), migrant@types:migration())) -> list({binary(),
migrant@types:migration()}).
sort_migrations(Migrations) ->
_pipe = Migrations,
_pipe@1 = gleam@dict:to_list(_pipe),
gleam@list:sort(
_pipe@1,
fun(A, B) ->
{Name_a, _} = A,
{Name_b, _} = B,
gleam@string:compare(Name_a, Name_b)
end
).
-spec apply_migrations(
sqlight:connection(),
gleam@dict:dict(binary(), migrant@types:migration())
) -> {ok, nil} | {error, migrant@types:error()}.
apply_migrations(Db, Migrations) ->
gleam@io:println(<<"-> Applying migrations"/utf8>>),
Count = begin
_pipe = Migrations,
_pipe@1 = sort_migrations(_pipe),
run_migration(_pipe@1, Db, 0)
end,
case Count of
{ok, Count@1} ->
gleam@io:println(
<<<<"-> Applied "/utf8, (gleam@int:to_string(Count@1))/binary>>/binary,
" migrations"/utf8>>
),
{ok, nil};
{error, E} ->
{error, E}
end.