towerops/vendor/oban_pro/lib/oban/pro/unique.ex
Graham McIntire 6c05d45dd6 deps: upgrade oban_pro to 1.7.0
Bumps vendored Oban Pro from 1.6.13 to 1.7.0, which transitively bumps
oban core to 2.22.1 (requires migrations v14) and db_connection to 2.10.0.

- mix.exs constraint: ~> 1.6 → ~> 1.7
- vendor/oban_pro/ replaced with v1.7.0 hex package
- migration 20260430142200: Oban core v12 → v14 (must run first)
- migration 20260430142241: Oban Pro 1.7.0 schema additions

Also corrects a pre-existing constraint name in AgentAssignment that was
left stale by the equipment→devices rename migration (20260117190134).
The unique index is named agent_assignments_device_id_index, but the
schema's unique_constraint/3 still pointed at the old equipment_id name,
causing the test suite to surface Ecto.ConstraintError instead of a
changeset error. Required to get the suite green for this upgrade.
2026-04-30 10:26:18 -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