-module(gorrion). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/gorrion.gleam"). -export([migrate/2, rollback/2, rollback_to/3, status/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( " Gorrion — Ecto-like database migration library for Gleam.\n" "\n" " Reads migration SQL from .sql files on disk, tracks applied migrations\n" " in a _schema_migrations table, and supports forward migration and rollback.\n" "\n" " ## Usage\n" "\n" " ```gleam\n" " // Run all pending migrations\n" " gorrion.migrate(db, \"migrations\")\n" "\n" " // Roll back the most recent migration\n" " gorrion.rollback(db, \"migrations\")\n" "\n" " // Check migration status\n" " gorrion.status(db, \"migrations\")\n" " ```\n" ). -file("src/gorrion.gleam", 137). ?DOC( " Internal: ensure tracking table exists, get applied migrations,\n" " and compute which migrations are pending.\n" ). -spec resolve(pog:connection(), list(gorrion@types:migration())) -> {ok, {list(gorrion@types:migration()), list(gorrion@types:applied_migration())}} | {error, gorrion@types:migration_error()}. resolve(Db, Migrations) -> gleam@result:'try'( gorrion@tracker:ensure_table(Db), fun(_) -> gleam@result:'try'( gorrion@tracker:get_applied(Db), fun(Applied) -> Applied_versions = begin _pipe = Applied, _pipe@1 = gleam@list:map( _pipe, fun(A) -> erlang:element(2, A) end ), gleam@set:from_list(_pipe@1) end, Pending = begin _pipe@2 = Migrations, _pipe@3 = gleam@list:filter( _pipe@2, fun(M) -> not gleam@set:contains( Applied_versions, erlang:element(2, M) ) end ), gleam@list:sort( _pipe@3, fun(A@1, B) -> gleam@int:compare( erlang:element(2, A@1), erlang:element(2, B) ) end ) end, {ok, {Pending, Applied}} end ) end ). -file("src/gorrion.gleam", 33). ?DOC(" Run all pending migrations in version order.\n"). -spec migrate(pog:connection(), binary()) -> {ok, nil} | {error, gorrion@types:migration_error()}. migrate(Db, Dir) -> gleam@result:'try'( gorrion@loader:load_migrations(Dir), fun(Migrations) -> gleam@result:'try'( resolve(Db, Migrations), fun(_use0) -> {Pending, _} = _use0, case Pending of [] -> gleam_stdlib:println( <<"No pending migrations"/utf8>> ), {ok, nil}; _ -> gleam_stdlib:println( <<<<"Applying "/utf8, (erlang:integer_to_binary( erlang:length(Pending) ))/binary>>/binary, " pending migration(s)..."/utf8>> ), gleam@list:try_fold( Pending, nil, fun(_, M) -> gorrion@runner:apply_migration(Db, M) end ) end end ) end ). -file("src/gorrion.gleam", 57). ?DOC(" Roll back the most recently applied migration.\n"). -spec rollback(pog:connection(), binary()) -> {ok, nil} | {error, gorrion@types:migration_error()}. rollback(Db, Dir) -> gleam@result:'try'( gorrion@loader:load_migrations(Dir), fun(Migrations) -> gleam@result:'try'( resolve(Db, Migrations), fun(_use0) -> {_, Applied} = _use0, Applied_versions = begin _pipe = Applied, _pipe@1 = gleam@list:map( _pipe, fun(A) -> erlang:element(2, A) end ), gleam@set:from_list(_pipe@1) end, Latest = begin _pipe@2 = Migrations, _pipe@3 = gleam@list:filter( _pipe@2, fun(M) -> gleam@set:contains( Applied_versions, erlang:element(2, M) ) end ), _pipe@4 = gleam@list:sort( _pipe@3, fun(A@1, B) -> gleam@int:compare( erlang:element(2, B), erlang:element(2, A@1) ) end ), gleam@list:first(_pipe@4) end, case Latest of {error, _} -> {error, no_migrations_to_rollback}; {ok, Migration} -> gleam_stdlib:println( <<"Rolling back 1 migration..."/utf8>> ), gorrion@runner:revert_migration(Db, Migration) end end ) end ). -file("src/gorrion.gleam", 86). ?DOC(" Roll back all migrations down to (but not including) the target version.\n"). -spec rollback_to(pog:connection(), binary(), integer()) -> {ok, nil} | {error, gorrion@types:migration_error()}. rollback_to(Db, Dir, Target) -> gleam@result:'try'( gorrion@loader:load_migrations(Dir), fun(Migrations) -> gleam@result:'try'( resolve(Db, Migrations), fun(_use0) -> {_, Applied} = _use0, Applied_versions = begin _pipe = Applied, _pipe@1 = gleam@list:map( _pipe, fun(A) -> erlang:element(2, A) end ), gleam@set:from_list(_pipe@1) end, To_revert = begin _pipe@2 = Migrations, _pipe@3 = gleam@list:filter( _pipe@2, fun(M) -> gleam@set:contains( Applied_versions, erlang:element(2, M) ) andalso (erlang:element(2, M) > Target) end ), gleam@list:sort( _pipe@3, fun(A@1, B) -> gleam@int:compare( erlang:element(2, B), erlang:element(2, A@1) ) end ) end, case To_revert of [] -> gleam_stdlib:println( <<"No migrations to roll back"/utf8>> ), {ok, nil}; _ -> gleam_stdlib:println( <<<<"Rolling back "/utf8, (erlang:integer_to_binary( erlang:length(To_revert) ))/binary>>/binary, " migration(s)..."/utf8>> ), gleam@list:try_fold( To_revert, nil, fun(_, M@1) -> gorrion@runner:revert_migration(Db, M@1) end ) end end ) end ). -file("src/gorrion.gleam", 126). ?DOC(" Get the current migration status: which are applied and which are pending.\n"). -spec status(pog:connection(), binary()) -> {ok, gorrion@types:migration_status()} | {error, gorrion@types:migration_error()}. status(Db, Dir) -> gleam@result:'try'( gorrion@loader:load_migrations(Dir), fun(Migrations) -> gleam@result:'try'( resolve(Db, Migrations), fun(_use0) -> {Pending, Applied} = _use0, {ok, {migration_status, Applied, Pending}} end ) end ).