diff --git a/lib/migration_compile_cache.ex b/lib/migration_compile_cache.ex new file mode 100644 index 00000000..7c79dcc2 --- /dev/null +++ b/lib/migration_compile_cache.ex @@ -0,0 +1,38 @@ +defmodule AshPostgres.MigrationCompileCache do + @moduledoc """ + A cache for the compiled migrations. + + This is used to avoid recompiling the migration files + every time a migration is run, as well as ensuring that + migrations are compiled sequentially. + + This is important because otherwise there is a race condition + where two invocations could be compiling the same migration at + once, which would error out. + """ + + def start_link(opts \\ %{}) do + Agent.start_link(fn -> opts end, name: __MODULE__) + end + + @doc """ + Compile a file, caching the result for future calls. + """ + def compile_file(file) do + Agent.get_and_update(__MODULE__, fn state -> + new_state = ensure_compiled(state, file) + {Map.get(new_state, file), new_state} + end) + end + + defp ensure_compiled(state, file) do + case Map.get(state, file) do + nil -> + compiled = Code.compile_file(file) + Map.put(state, file, compiled) + _ -> + state + end + end + +end diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index ad4bbfbb..2f7fd0db 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -61,7 +61,7 @@ defmodule AshPostgres.MultiTenancy do end defp load_migration!({version, _, file}) when is_binary(file) do - loaded_modules = file |> Code.compile_file() |> Enum.map(&elem(&1, 0)) + loaded_modules = file |> compile_file() |> Enum.map(&elem(&1, 0)) if mod = Enum.find(loaded_modules, &migration?/1) do {version, mod} @@ -70,6 +70,11 @@ defmodule AshPostgres.MultiTenancy do "file #{Path.relative_to_cwd(file)} does not define an Ecto.Migration" end end + + defp compile_file(file) do + AshPostgres.MigrationCompileCache.start_link() + AshPostgres.MigrationCompileCache.compile_file(file) + end defp migration?(mod) do function_exported?(mod, :__migration__, 0)