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
- This module is read-only — it contains no calls to
Obanruntime functions such asOban.cancel_job/1,Oban.retry_job/1, orOban.drain_queue/2. - Callers pass the repo explicitly (first argument) following the convention established in
ObanPowertools.CronandObanPowertools.Lifeline. - No
defp repo/0helper is defined in this module.
Summary
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
@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 viato_string/1.queue,worker,tags— optional narrowing filters;nilmeans "all".page,page_size— offset pagination controls (D-03).
Functions
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.
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).
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.