Oban. Pro. Backfill. Cursor
(Oban Pro v1.8.0-rc.0)
Process a backfill query in ordered, limited windows.
Each helper runs an operation over the next batch of rows in a query and returns either:
:halt— nothing matched, so the chain stops{:cont, next, count}—nextis the advanced cursor andcountis the number of affected rows
Pass a filtered query and the current cursor to a helper. Use update_all/4 or delete_all/3
for bulk database operations, each/4 for per-row work, or fetch/3 when you need the rows for
batching or concurrent processing. Each helper limits and orders the query according to the
backfill's options, then returns the cursor value for the next window.
See Oban.Pro.Backfill for how to define and run a backfill.
Options
Every helper accepts:
:repo— the Ecto repo to run against. Defaults to the executing backfill job's configured repo. Pass an explicit repo for cross-database backfills.
Summary
Functions
Delete every row in the cursor's window, then advance.
Run a function over each row in the cursor's window, then advance.
Fetch the cursor's window of rows and the cursor for the next window.
Update every row in the cursor's window, then advance.
Types
@type advance() :: :halt | {:cont, next :: cursor(), count :: non_neg_integer()}
@type cursor() :: Oban.Pro.Backfill.cursor()
Functions
@spec delete_all(Ecto.Queryable.t(), cursor(), keyword()) :: advance()
Delete every row in the cursor's window, then advance.
Returns :halt when nothing matched, or {:cont, next, count} with the cursor for the next
window and the number of rows deleted.
Example
MyApp.Event
|> where([e], e.expired)
|> Cursor.delete_all(cursor)
Run a function over each row in the cursor's window, then advance.
Calls fun once per row and returns :halt when the window is empty, or
{:cont, next, count} with the cursor for the next window and the number of rows processed. Use
it for per-row work that isn't a single database operation, such as an external request.
Example
Cursor.each(MyApp.User, cursor, fn user ->
MyApp.Crm.sync(user)
end)
@spec fetch(Ecto.Queryable.t(), cursor(), keyword()) :: :halt | {[term()], cursor()}
Fetch the cursor's window of rows and the cursor for the next window.
Returns :halt when nothing matched, otherwise {rows, next_cursor}. Reach for this over
each/4 when you want the rows in hand—to batch them through a single request, or fan them out
with Task.async_stream/3.
Example
with {users, next} <- Cursor.fetch(MyApp.User, cursor) do
MyApp.Crm.sync_all(users)
{:cont, next, length(users)}
end
@spec update_all(Ecto.Queryable.t(), cursor(), keyword(), keyword()) :: advance()
Update every row in the cursor's window, then advance.
Returns :halt when nothing matched, or {:cont, next, count} with the cursor for the next
window and the number of rows updated.
Example
MyApp.User
|> where([u], is_nil(u.migrated_at))
|> Cursor.update_all(cursor, set: [migrated_at: ^DateTime.utc_now()])