-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]). -file("src/migrant/database.gleam", 16). -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 ). -file("src/migrant/database.gleam", 22). -spec 'query'( sqlight:connection(), binary(), list(sqlight:value()), gleam@dynamic@decode:decoder(GBI) ) -> {ok, list(GBI)} | {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). -file("src/migrant/database.gleam", 32). -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_stdlib: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. -file("src/migrant/database.gleam", 106). -spec construct_sqlight_error_message(sqlight:error()) -> binary(). construct_sqlight_error_message(Error) -> Message = <<<<"\""/utf8, (erlang:element(3, Error))/binary>>/binary, "\""/utf8>>, Message@1 = case erlang:element(4, Error) of -1 -> Message; _ -> <<<>/binary, (erlang:integer_to_binary(erlang:element(4, Error)))/binary>> end, Code = begin _pipe = sqlight:error_code_to_int(erlang:element(2, Error)), erlang:integer_to_binary(_pipe) end, <<<<<>/binary, Code/binary>>/binary, ")"/utf8>>. -file("src/migrant/database.gleam", 97). -spec print_filter_error_message(sqlight:error()) -> migrant@types:error(). print_filter_error_message(E) -> Message = <<"-> Failed to query applied migrations: "/utf8, (construct_sqlight_error_message(E))/binary>>, gleam_stdlib:println_error(Message), {database_error, E}. -file("src/migrant/database.gleam", 120). -spec pluralise_migration(integer()) -> binary(). pluralise_migration(Count) -> case Count of 1 -> <<"migration"/utf8>>; _ -> <<"migrations"/utf8>> end. -file("src/migrant/database.gleam", 77). -spec print_count(gleam@dict:dict(binary(), migrant@types:migration())) -> {gleam@dict:dict(binary(), migrant@types:migration()), integer()}. print_count(M) -> Count = maps:size(M), case Count of 0 -> gleam_stdlib:println(<<"-> No migrations to apply"/utf8>>), {maps:new(), 0}; _ -> gleam_stdlib:println( <<<<<<<<"-> Found "/utf8, (erlang:integer_to_binary(maps:size(M)))/binary>>/binary, " "/utf8>>/binary, (pluralise_migration(Count))/binary>>/binary, " to apply"/utf8>> ), {M, Count} end. -file("src/migrant/database.gleam", 53). -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_stdlib:println(<<"-> Filtering applied migrations"/utf8>>), Decoder = gleam@dynamic@decode:at( [0], {decoder, fun gleam@dynamic@decode:decode_string/1} ), gleam@result:'try'( begin _pipe = <<"SELECT name FROM __migrations ORDER BY id, name ASC;"/utf8>>, _pipe@1 = sqlight:'query'(_pipe, Db, [], Decoder), gleam@result:map_error(_pipe@1, fun print_filter_error_message/1) end, fun(Applied) -> _pipe@2 = Migrations, _pipe@3 = gleam@dict:drop(_pipe@2, Applied), _pipe@4 = print_count(_pipe@3), (fun(M) -> gleam@bool:guard( erlang:element(2, M) =:= 0, {ok, nil}, fun() -> Next(erlang:element(1, M)) end ) end)(_pipe@4) end ). -file("src/migrant/database.gleam", 164). -spec with_err_message({ok, GEC} | {error, migrant@types:error()}, binary()) -> {ok, GEC} | {error, migrant@types:error()}. with_err_message(E, Msg) -> _pipe = E, gleam@result:map_error( _pipe, fun(E@1) -> Message = case E@1 of {database_error, Sqlight_error} -> <<<>/binary, (construct_sqlight_error_message(Sqlight_error))/binary>>; _ -> Msg end, gleam_stdlib:println(<<"-> "/utf8, Message/binary>>), E@1 end ). -file("src/migrant/database.gleam", 251). -spec rollback_with_transaction(sqlight:connection(), migrant@types:error()) -> {ok, nil} | {error, migrant@types:error()}. rollback_with_transaction(Db, Err) -> gleam_stdlib:println( <<"-> Migration failed, no down query, rolling back transaction"/utf8>> ), case exec(Db, <<"ROLLBACK;"/utf8>>) of {ok, _} -> {error, {migration_error, <<"Migration failed and no down query provided"/utf8>>, Err}}; {error, E} -> {error, E} end. -file("src/migrant/database.gleam", 266). -spec rollback_with_user_migration( binary(), binary(), sqlight:connection(), migrant@types:error() ) -> {ok, any()} | {error, migrant@types:error()}. rollback_with_user_migration(Name, Sql, Db, Err) -> gleam_stdlib:println(<<"-> Rolling back migration: "/utf8, Name/binary>>), case exec(Db, Sql) of {ok, _} -> gleam_stdlib:println( <<"-> Rollback migration succeeded: "/utf8, Name/binary>> ), gleam@result:'try'( begin _pipe = Db, _pipe@1 = exec(_pipe, <<"COMMIT;"/utf8>>), _pipe@3 = gleam@result:map_error( _pipe@1, fun(E) -> _ = begin _pipe@2 = Db, exec(_pipe@2, <<"ROLLBACK;"/utf8>>) end, E end ), with_err_message( _pipe@3, <<"Failed to commit down migration: "/utf8, Name/binary>> ) end, fun(_) -> gleam_stdlib:println( <<"-> Rollback complete: "/utf8, Name/binary>> ), {error, {migration_error, <<"Migration failed, but down query succeeded"/utf8>>, Err}} end ); {error, E@1} -> gleam_stdlib:println( <<"-> Failed to rollback migration: "/utf8, Name/binary>> ), gleam_stdlib:println(<<"-> Rolling back transaction"/utf8>>), gleam@result:'try'( begin _pipe@4 = Db, _pipe@5 = exec(_pipe@4, <<"ROLLBACK;"/utf8>>), with_err_message( _pipe@5, <<"Failed to rollback transaction"/utf8>> ) end, fun(_) -> gleam_stdlib:println( <<"-> Rollback complete: "/utf8, Name/binary>> ), {error, E@1} end ) end. -file("src/migrant/database.gleam", 311). -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>>, Decoder = gleam@dynamic@decode:at( [0], {decoder, fun gleam@dynamic@decode:decode_int/1} ), case 'query'(Db, Sql, [sqlight:text(Name)], Decoder) of {ok, _} -> {ok, nil}; {error, E} -> {error, E} end. -file("src/migrant/database.gleam", 178). -spec apply({binary(), migrant@types:migration()}, sqlight:connection()) -> {ok, nil} | {error, migrant@types:error()}. apply(Migration_tuple, Db) -> {Name, Migration} = Migration_tuple, gleam@bool:lazy_guard( gleam@option:is_none(erlang:element(2, Migration)), fun() -> gleam_stdlib:println( <<<<"-> Skipping migration: "/utf8, Name/binary>>/binary, " no `up` query"/utf8>> ), {ok, nil} end, fun() -> gleam_stdlib:println( <<"-> Applying migration: "/utf8, Name/binary>> ), gleam@result:'try'( begin _pipe = Db, _pipe@1 = exec(_pipe, <<"BEGIN TRANSACTION;"/utf8>>), with_err_message( _pipe@1, <<"Failed to begin transaction"/utf8>> ) end, fun(_) -> Res = begin gleam@result:'try'( begin _pipe@3 = exec( Db, begin _pipe@2 = erlang:element(2, Migration), gleam@option:unwrap( _pipe@2, <<""/utf8>> ) end ), with_err_message( _pipe@3, <<<<"Failed to apply migration `"/utf8, Name/binary>>/binary, "`"/utf8>> ) end, fun(_) -> gleam@result:'try'( begin _pipe@4 = mark_migration_as_applied( Db, Migration_tuple ), with_err_message( _pipe@4, <<<<"Failed to mark migration as applied `"/utf8, Name/binary>>/binary, "`"/utf8>> ) end, fun(_) -> gleam@result:'try'( begin _pipe@5 = Db, _pipe@6 = exec( _pipe@5, <<"COMMIT;"/utf8>> ), with_err_message( _pipe@6, <<"Failed to commit transaction"/utf8>> ) end, fun(_) -> {ok, nil} end ) end ) end ) end, gleam@bool:lazy_guard( gleam@result:is_ok(Res), fun() -> gleam_stdlib:println( <<"-> Migration applied successfully: "/utf8, Name/binary>> ), {ok, nil} end, fun() -> {error, Err} = case Res of {error, _} -> Res; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"migrant/database"/utf8>>, function => <<"apply"/utf8>>, line => 230}) end, gleam@bool:lazy_guard( gleam@option:is_none( erlang:element(3, Migration) ), fun() -> _pipe@7 = Err, rollback_with_transaction(Db, _pipe@7) end, fun() -> gleam@result:'try'( begin _pipe@8 = Err, _pipe@10 = rollback_with_user_migration( Name, begin _pipe@9 = erlang:element( 3, Migration ), gleam@option:unwrap( _pipe@9, <<""/utf8>> ) end, Db, _pipe@8 ), with_err_message( _pipe@10, <<<<"Failed to rollback migration `"/utf8, Name/binary>>/binary, "`"/utf8>> ) end, fun(_) -> {ok, nil} end ) end ) end ) end ) end ). -file("src/migrant/database.gleam", 148). -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. -file("src/migrant/database.gleam", 325). -spec sort_migrations(gleam@dict:dict(binary(), migrant@types:migration())) -> list({binary(), migrant@types:migration()}). sort_migrations(Migrations) -> _pipe = Migrations, _pipe@1 = maps: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 ). -file("src/migrant/database.gleam", 127). -spec apply_migrations( sqlight:connection(), gleam@dict:dict(binary(), migrant@types:migration()) ) -> {ok, nil} | {error, migrant@types:error()}. apply_migrations(Db, Migrations) -> gleam_stdlib: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_stdlib:println( <<<<"-> Applied "/utf8, (erlang:integer_to_binary(Count@1))/binary>>/binary, " migrations"/utf8>> ), {ok, nil}; {error, E} -> {error, E} end.