ObanPowertools.Jobs (oban_powertools v0.5.0)

Copy Markdown View Source

Native job query context for the read-only job browse surface.

This module is the single owner of all oban_jobs queries for the Phase 43 job browse surface. LiveView never queries the oban_jobs table directly — all reads go through this module (D-10).

Tags Filtering and GIN Index (D-04)

Filtering by tags uses the Postgres array containment operator via:

fragment("? @> ?", j.tags, ^tags)

Oban does not create a GIN index on the tags column by default. Without this index, tags-filtered queries perform a sequential scan on the state-filtered result set. To enable efficient tags filtering, the host application must create the index:

CREATE INDEX CONCURRENTLY oban_jobs_tags_gin ON oban_jobs USING gin(tags);

Oban does not create this index. The host application owns it.

The state-leading composite index oban_jobs_state_queue_priority_scheduled_at_id_index (created by Oban's standard migration) still applies to every query in this module. The sequential scan is bounded to state-filtered rows — never an unfiltered full-table scan.

Keyset Pagination Upgrade Path (D-03)

list/3 currently uses offset-based pagination via limit/offset. To upgrade to keyset pagination, replace the offset(^offset) clause with a cursor-based where clause:

where: j.scheduled_at < ^cursor_scheduled_at or
       (j.scheduled_at == ^cursor_scheduled_at and j.id < ^cursor_id)

This is a single-function change in Jobs.list/3. The %JobFilter{} struct would gain cursor_scheduled_at and cursor_id fields in place of page.

Query Ownership Boundary

Summary

Types

t()

Filter struct for the job browse query layer.

Functions

Returns a map of job counts keyed by all 7 Oban state strings (D-13).

Returns the %Oban.Job{} with the given job_id, or nil if not found.

Lists jobs matching the given filter, ordered by scheduled_at DESC, id DESC (D-11).

Types

t()

@type t() :: %ObanPowertools.Jobs{
  page: pos_integer(),
  page_size: pos_integer(),
  queue: String.t() | nil,
  state: atom(),
  tags: [String.t()] | nil,
  worker: String.t() | nil
}

Filter struct for the job browse query layer.

  • state — required; the atom state to browse (e.g. :available). Converted to a string at the WHERE boundary via to_string/1.
  • queue, worker, tags — optional narrowing filters; nil means "all".
  • page, page_size — offset pagination controls (D-03).

Functions

count_by_state(repo, base_filter)

Returns a map of job counts keyed by all 7 Oban state strings (D-13).

The state field of base_filter is ignored — this function iterates all 7 states and returns a count for each. Non-state filters (queue, worker, tags) from base_filter narrow each per-state count.

This issues 7 round-trips per filter change, which is acceptable for Phase 43 because each query uses the state-leading composite index oban_jobs_state_queue_priority_scheduled_at_id_index.

A single GROUP BY state query would miss states with zero counts (D-13) — the map must always include all 7 keys, even for states with no matching jobs.

get(repo, job_id)

Returns the %Oban.Job{} with the given job_id, or nil if not found.

list(repo, filter, opts \\ [])

Lists jobs matching the given filter, ordered by scheduled_at DESC, id DESC (D-11).

State is always the first WHERE predicate (D-05), ensuring the composite index oban_jobs_state_queue_priority_scheduled_at_id_index applies to every query.

Optional filters queue, worker, and tags narrow the result set when non-nil. Results are paginated using offset-based pagination (D-03); see the module doc for the keyset upgrade path.