defmodule Oban.Pro.Queue do @moduledoc false use Ecto.Schema import Ecto.Changeset alias Oban.Pro.{Producer, Utils} @allowed ~w(ack_async local_limit paused refresh_interval)a @primary_key {:name, :string, autogenerate: false} schema "oban_queues" do field :lock_version, :integer, default: 1 field :hash, :string embeds_one :only, Only, on_replace: :update, primary_key: false do @moduledoc false field :mode, Ecto.Enum, values: [:node, :sys_env] field :op, Ecto.Enum, values: [:==, :!=, :=~] field :key, :string field :value, :string end embeds_one :opts, Opts, on_replace: :update, primary_key: false do @moduledoc false field :ack_async, :boolean field :local_limit, :integer field :paused, :boolean field :refresh_interval, :integer field :xact_delay, :integer field :xact_retry, :integer embeds_one :global_limit, GlobalLimit, on_replace: :update, primary_key: false do @moduledoc false field :allowed, :integer field :burst, :boolean, default: false embeds_one :partition, Partition, on_replace: :update, primary_key: false do @moduledoc false field :fields, {:array, Ecto.Enum}, values: [:args, :meta, :worker] field :keys, {:array, :string} end end embeds_one :rate_limit, RateLimit, on_replace: :update, primary_key: false do @moduledoc false field :algorithm, Ecto.Enum, values: ~w(sliding_window fixed_window token_bucket)a, default: :sliding_window field :allowed, :integer field :period, :integer field :window_time, :integer, virtual: true, skip_default_validation: true embeds_one :partition, Partition, on_replace: :update, primary_key: false do @moduledoc false field :fields, {:array, Ecto.Enum}, values: [:args, :meta, :worker] field :keys, {:array, :string} end end end timestamps(inserted_at: :inserted_at, updated_at: :updated_at, type: :utc_datetime_usec) end def normalize(params) do case params do [_ | _] -> Map.new(params) {name, [_ | _] = opts} -> %{name: name, opts: Map.new(opts)} {name, limit} -> %{name: name, opts: %{local_limit: limit}} end end def changeset(params) do changeset(%__MODULE__{}, normalize(params)) end def changeset(schema, params) do params = params |> Map.replace_lazy(:name, fn _ -> to_string(params[:name]) end) |> extract_only() schema |> cast(params, [:name]) |> cast_embed(:only, with: &only_changeset/2) |> cast_embed(:opts, required: true, with: &opts_changeset/2) |> validate_required([:name]) |> validate_length(:name, min: 1) |> optimistic_lock(:lock_version) |> inject_hash(params) |> Utils.enforce_keys(params, __MODULE__) end defp extract_only(params) do case params do %{opts: %{only: _}} -> {only, params} = pop_in(params, [:opts, :only]) Map.put(params, :only, cast_only(only)) _ -> params end end defp only_changeset(schema, params) do schema |> cast(params, ~w(mode op key value)a) |> validate_required(~w(mode op value)a) |> Utils.enforce_keys(params, __MODULE__.Only) end defp opts_changeset(schema, params) do params = Map.new(params, fn {key, val} -> {Utils.maybe_to_atom(key), val} end) Producer.meta_changeset(schema, params, @allowed) end defp inject_hash(changeset, params) do hash = params |> Map.take(~w(only opts)a) |> Enum.map_join(&:erlang.phash2/1) |> Utils.hash64() put_change(changeset, :hash, hash) end @spec to_keyword_opts(%{opts: map()} | map()) :: Keyword.t() def to_keyword_opts(%__MODULE__{name: queue, opts: opts}) do opts |> Ecto.embedded_dump(:json) |> Map.put(:queue, queue) |> to_keyword_opts() end def to_keyword_opts(opts) do for {key, val} <- opts, not is_nil(val), do: {Utils.maybe_to_atom(key), val} end # Helpers defp cast_only({:node, value}), do: cast_only({:node, :==, value}) defp cast_only({:node, op, value}), do: %{mode: :node, op: op, value: to_string(value)} defp cast_only({:sys_env, key, value}), do: cast_only({:sys_env, key, :==, value}) defp cast_only({:sys_env, key, op, value}) do %{mode: :sys_env, op: op, key: key, value: to_string(value)} end defp cast_only(other), do: other end