prop/vendor/oban_pro/lib/oban/pro/unique.ex
Graham McIntire e99bf06eb4
deps: re-vendor oban_pro 1.7.0 (revert hex-repo dep)
Previous commit (3c988a5f) switched oban_pro to the licensed hex
repo at deploy time. Reverting that — keep all three Pro packages
vendored so prod image builds don't depend on oban.pro reachability
or auth on every CI run.

oban_pro 1.7.0 dropped into vendor/oban_pro via
`mix hex.package fetch oban_pro 1.7.0 --repo=oban --unpack`. mix.exs
goes back to `path: "vendor/oban_pro"` (matching oban_met / oban_web,
which were already vendored — and stay vendored since the licensed
repo only has older versions of those: 0.1.11 / 2.10.6 vs the
1.1.0 / 2.12.1 we vendor).

Schema migration from 3c988a5f stays — 1.7.0 tables/indexes are
already applied. No code changes.
2026-04-30 09:29:55 -05:00

88 lines
2.5 KiB
Elixir

defmodule Oban.Pro.Unique do
@moduledoc false
alias Ecto.Changeset
alias Oban.Job
alias Oban.Pro.Utils
@states_to_ints %{
"scheduled" => 0,
"available" => 1,
"executing" => 2,
"retryable" => 3,
"completed" => 4,
"cancelled" => 5,
"discarded" => 6,
"suspended" => 7
}
@spec replace?(Job.changeset()) :: boolean()
def replace?(changeset), do: match?(%{changes: %{replace: _}}, changeset)
@spec unique?(Job.changeset()) :: boolean()
def unique?(changeset), do: match?(%{changes: %{unique: %{}}}, changeset)
@spec get_key(Job.changeset(), any()) :: any()
def get_key(changeset, default \\ nil)
def get_key(%{changes: %{meta: %{uniq_key: uniq_key}}}, _default), do: uniq_key
def get_key(_changeset, default), do: default
@spec get_states(Job.changeset()) :: [atom()]
def get_states(%{changes: %{unique: %{states: states}}}), do: states
# For backward compatibility, `unique` works with non-pro workers, so it can't be injected into
# the meta through a stage.
@spec with_uniq_meta(Job.changeset()) :: Job.changeset()
def with_uniq_meta(%{changes: %{unique: %{period: _}}} = changeset) do
uniq_meta = %{uniq: true, uniq_bmp: gen_bmp(changeset), uniq_key: gen_key(changeset)}
meta =
changeset
|> Changeset.get_change(:meta, %{})
|> Map.merge(uniq_meta)
Changeset.put_change(changeset, :meta, meta)
end
def with_uniq_meta(changeset), do: changeset
@spec gen_bmp(Job.changeset()) :: [integer()]
def gen_bmp(%{changes: %{unique: %{states: states}}}) do
for state <- states, do: Map.fetch!(@states_to_ints, to_string(state))
end
@spec gen_key(Job.changeset()) :: String.t()
def gen_key(%{changes: %{unique: unique}} = changeset) do
%{fields: fields, keys: keys, period: period, timestamp: timestamp} = unique
data =
fields
|> Enum.sort()
|> Enum.map(fn
:args -> Utils.take_keys(changeset, :args, keys)
:meta -> Utils.take_keys(changeset, :meta, keys)
field -> Changeset.get_field(changeset, field)
end)
data =
if period == :infinity do
data
else
[truncate(period, Changeset.get_field(changeset, timestamp)) | data]
end
Utils.hash64(data)
end
defp truncate(period, timestamp) do
now = timestamp || DateTime.utc_now()
now
|> DateTime.to_unix(:second)
|> rem(period)
|> then(&DateTime.add(now, -&1))
|> DateTime.truncate(:second)
|> DateTime.to_iso8601()
end
end