I dont understand how an Agent process is alive during compilation.
It isn't. The macros turn the DSL into code that calls functions in Ecto.Migration and Ecto.Migration.Runner. Here's an example. The following migration:
defmodule M.Repo.Migrations.CreatePosts do
use Ecto.Migration
def change do
create(:posts) do
add :title, :string
add :content, :text
end
end
end
compiles into this Erlang:
-module('Elixir.M.Repo.Migrations.CreatePosts').
...
change() ->
_@1 = #{'__struct__' := 'Elixir.Ecto.Migration.Table'} =
posts,
'Elixir.Ecto.Migration.Runner':start_command({create,
'Elixir.Ecto.Migration':'__prefix__'(_@1)}),
case case _@1 of
#{primary_key := _@2} -> _@2;
_@2 when erlang:is_map(_@2) ->
erlang:error({badkey, primary_key, _@2});
_@2 -> _@2:primary_key()
end
of
_@3 when (_@3 =:= nil) or (_@3 =:= false) ->
_@4 = nil, nil;
_ ->
_@4 =
'Elixir.Ecto.Migration.Runner':repo_config(migration_primary_key,
[]),
'Elixir.Ecto.Migration':add(case
'Elixir.Access':get(_@4, name, nil)
of
_@5
when (_@5 =:= nil) or
(_@5 =:= false) ->
id;
_@6 -> _@6
end,
case 'Elixir.Access':get(_@4, type, nil)
of
_@7
when (_@7 =:= nil) or
(_@7 =:= false) ->
bigserial;
_@8 -> _@8
end,
[{primary_key, true}])
end,
'Elixir.Ecto.Migration':add(title, string),
'Elixir.Ecto.Migration':add(content, text),
'Elixir.Ecto.Migration.Runner':end_command(),
_@1.
Even if you don't read Erlang, you can figure out that it's calling Ecto.Migration.Runner.start_command, and then calls functions that add data to the agent, and then finally calls .end_command. All this happens at runtime when the migration is executed, not compile time. At compile time, only the DSL is expanded to this code.