defmodule Oban.Migrations.Postgres do @moduledoc false @behaviour Oban.Migration use Ecto.Migration @initial_version 1 @current_version 12 @default_prefix "public" @doc false def initial_version, do: @initial_version @doc false def current_version, do: @current_version @impl Oban.Migration def up(opts) do opts = with_defaults(opts, @current_version) initial = migrated_version(opts) cond do initial == 0 -> change(@initial_version..opts.version, :up, opts) initial < opts.version -> change((initial + 1)..opts.version, :up, opts) true -> :ok end end @impl Oban.Migration def down(opts) do opts = with_defaults(opts, @initial_version) initial = max(migrated_version(opts), @initial_version) if initial >= opts.version do change(initial..opts.version, :down, opts) end end @impl Oban.Migration def migrated_version(opts) do opts = with_defaults(opts, @initial_version) repo = Map.get_lazy(opts, :repo, fn -> repo() end) escaped_prefix = Map.fetch!(opts, :escaped_prefix) query = """ SELECT description FROM pg_class LEFT JOIN pg_description ON pg_description.objoid = pg_class.oid LEFT JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace WHERE pg_class.relname = 'oban_jobs' AND pg_namespace.nspname = '#{escaped_prefix}' """ case repo.query(query, [], log: false) do {:ok, %{rows: [[version]]}} when is_binary(version) -> String.to_integer(version) _ -> 0 end end defp change(range, direction, opts) do for index <- range do pad_idx = String.pad_leading(to_string(index), 2, "0") [__MODULE__, "V#{pad_idx}"] |> Module.concat() |> apply(direction, [opts]) end case direction do :up -> record_version(opts, Enum.max(range)) :down -> record_version(opts, Enum.min(range) - 1) end end defp record_version(_opts, 0), do: :ok defp record_version(%{prefix: prefix}, version) do execute "COMMENT ON TABLE #{inspect(prefix)}.oban_jobs IS '#{version}'" end defp with_defaults(opts, version) do opts = Enum.into(opts, %{prefix: @default_prefix, version: version}) opts |> Map.put_new(:create_schema, opts.prefix != @default_prefix) |> Map.put_new(:quoted_prefix, inspect(opts.prefix)) |> Map.put_new(:escaped_prefix, String.replace(opts.prefix, "'", "\\'")) end end