Documentation
¶
Overview ¶
Package queries is the work queue schema described as data — the canonical table name and the columns its statements touch — together with the statements the queue executes.
It exists because those facts have two consumers that must not disagree. The generator behind `make generate` renders them into the canonical .sql that sqlc is run over; the queue executes the querier sqlc-gen-unison generates from that same file. A column list spelled in both places could differ in one name, and the symptom would be a check that passes over SQL nobody executes.
Why the rendered .sql is committed at all, when the generated Go beside it in workqueue/internal/workqueuedb carries the same statements in executable form, is identity's package comment, under "Where the SQL comes from".
Postgres, and a roster of one ¶
unison's dialect roster is the keys of unison.yaml's schemas map, so a single-dialect package renders a single-dialect corpus and gets exactly the same checked guarantee as a three-dialect one. What the roster does not do is soften the requirement: every statement below is checked against the schema workqueue/migrations renders, with no database running.
workqueue is Postgres-only because the claim is — the single statement that selects due rows, locks them, increments the attempt counter, extends the lease and hands them back is the concurrency contract, and the SELECT-then-UPDATE it becomes without RETURNING is a different failure model rather than a dialect switch. See the workqueue package comment. The consequence here is narrow and worth stating plainly: there is no MySQL rendering to reconcile, so the RETURNING split that a portable corpus would have owed is not a shape this package has, and RETURNING is simply available — the claim reads its rows back in the statement that leased them rather than in a second one.
Everything is written out, and the line is not effort ¶
Not one statement here comes from database/querygen, and the reason is the same one in every case: this table has no id and no listing, and every write assigns an expression rather than a bound value. querygen assigns a column the argument it takes, with last_updated_at stamped by convention — a convention this table does not even carry, since a swept queue has nothing to archive. The claim increments an attempt counter and derives a lease horizon from a duration; the enqueue resolves a conflict through a GREATEST, a LEAST and four CASEs over whether the row it landed on was finished; the release pushes availability forward by an interval; the reap subtracts a retention window from the server's own clock. A generator that could render those would be a generator with an expression language in it, which is the thing querygen's closed comparand set exists to refuse.
What the written-out statements do not give up is the guarantee, which is the whole point of them being here rather than in the queue's own package: each is a complete statement in the committed corpus, checked by sqlc against this package's own schema, and executed through the generated querier. A renamed column is a failed `make unison` with no database running.
A batch is arrays, not tuples ¶
Five of the seven statements act on a batch whose size is decided at the call: the enqueue and the four keyed writes. A tuple list — or a run of placeholders — would make the statement's text a function of the batch size, which is the dynamic SQL this tier exists to replace, so a batch crosses the seam as one bound array per column instead and the statement is one fixed text however many items are in it.
Where a batch is one column wide, that is `= ANY(...)` and nothing more, which is every keyed write: an item is addressed by its key, and the queue-name predicate that accompanies it is a single bound value. The enqueue is the one statement carrying several columns per row — a key, a priority and a delay — so its arrays are unnested WITH ORDINALITY and joined on the position, and the nth element of each is one entry again. The caller's obligation is the one the pairing implies: the arrays are parallel, and the queue's own splitting is what keeps them so.
One clock, and no exception ¶
The database's now() decides everything about time here, without the single exception a timer set has: a work queue names no instants at all. Lease horizons, availability, completion, the retention window a reap subtracts and the age the health read reports are all written and compared server-side, and durations cross the seam as microsecond counts turned into intervals. Nothing in this corpus binds a timestamp, in either direction, which is why the package it serves is the one scheduling component in this module with no clock.Clock option.
That is also why the reap does not come from querygen's bounded prune. Its horizon would be a ceiling the caller computed, which is the right seam for a column the application stamped — and completed_at is stamped by the server, by the statement above it.
Index ¶
Constants ¶
const ( // QueueColumn is the logical queue a row belongs to, and the leading column // of the primary key. Every statement below binds it, because one table // holds every queue in the database and a statement that omitted it would // act on somebody else's work. QueueColumn = "queue_name" // KeyColumn names the unit of work within its queue, and is the second half // of the primary key. It is the encoded rendering of the caller's key type; // this package never parses it. KeyColumn = "item_key" // PriorityColumn orders the queue ahead of waiting time. It only ever rises // on a re-enqueue, which is the whole of what a demand signal is here. PriorityColumn = "priority" // AttemptsColumn counts how many times the item has been claimed. The claim // increments it server-side, so there is one attempt counter and it is the // one the statement that handed the lease out wrote. AttemptsColumn = "attempts" // EnqueuedAtColumn is when the item first joined the queue. A re-enqueue of // an outstanding item leaves it alone — the item has been waiting since it // was first asked for — and a restart of a completed one takes the new // stamp, because that is a new unit of work wearing an old key. EnqueuedAtColumn = "enqueued_at" // AvailableAtColumn is the earliest instant the item may be claimed, which // is what Entry.Delay moves. It is also what the oldest-ready age in // [readQueueStats] is measured from. AvailableAtColumn = "available_at" // LeaseColumn is when the current lease lapses, and the epoch when there is // none. It is NOT NULL and starts at the epoch rather than being nullable, // so the claimable predicate is one comparison instead of a comparison plus // a NULL branch every future writer would have to remember. LeaseColumn = "lease_until" // CompletedAtColumn is when the item was finished, and NULL while it has // not been. It is the column every read excludes on and the column the // retirement assigns, which makes it the one place in this schema a guard // and an assignment meet. CompletedAtColumn = "completed_at" // LastErrorColumn holds why the last attempt handed the item back. It is // the last error rather than a final one: a completion clears it, and a // restart of a completed item clears it too. LastErrorColumn = "last_error" )
The columns this package's statements name, spelled here so the corpus and the queue cannot come to disagree about them.
const ( // QueueArg is the logical queue every statement is scoped to. QueueArg = "queue_name" // KeysArg is the batch of encoded keys a write addresses its rows through, // bound as one array. KeysArg = "item_keys" // PrioritiesArg is the batch of priorities an enqueue writes, bound as one // array and read positionally against [KeysArg]. See [Render] on the three // arrays. PrioritiesArg = "priorities" // CeilingArg is the attempt ceiling the claim and the health read measure // against. Non-positive means unlimited, which is why it is compared rather // than merely subtracted from. CeilingArg = "attempt_ceiling" // ClaimLimitArg caps one claim. ClaimLimitArg = "claim_limit" // LeaseArg is how long a claim's lease runs, as microseconds. LeaseArg = "lease_microseconds" // DelayArg is how long an item is held back before it may be claimed, as // microseconds. // // One name for two bindings, because it is one fact: an enqueue's delay and // a release's delay both land on available_at as an offset from the // server's own clock. The enqueue binds it as an array, one element per // item in the batch and read positionally against [KeysArg]; the release // binds a single value for the whole batch, because a hand-back is one // decision about a set of items rather than a decision apiece. DelayArg = "delay_microseconds" // LastErrorArg is the cause a release records, and it binds nullably: a // plain hand-back has no error to give. LastErrorArg = "last_error" // RetentionArg is how long a completed item is kept, as microseconds. RetentionArg = "retention_microseconds" // ReapLimitArg caps one reaping pass. ReapLimitArg = "reap_limit" )
The sqlc arguments the statements bind, spelled once because the queue binds them and the statements read them, and a name spelled in two places is a name that can differ in one.
const ItemsTable = "work_queue_items"
ItemsTable is the one table this package owns, at its canonical unprefixed spelling — what the emitted .sql names, and what a consumer's own prefix is rendered onto.
Variables ¶
var Columns = []string{ QueueColumn, KeyColumn, PriorityColumn, AttemptsColumn, EnqueuedAtColumn, AvailableAtColumn, LeaseColumn, CompletedAtColumn, LastErrorColumn, }
Columns is every column the table has, in the order the DDL declares it.
Nothing renders a statement from this list. It is here for the cross-check against the shipped DDL, which is the one place a column added to the schema and not to this package stops being invisible.
There is no convention triple to account for, and that is the schema's own decision rather than an omission here: completed items are swept, so archived_at would either do nothing or keep the table growing forever, and enqueued_at and available_at are the schedule a claim reads rather than a creation stamp and a last mutation.
var TableNames = []string{ItemsTable}
TableNames is every table workqueue owns, in the order the DDL creates them.
One, and it is still a list. The registry a consumer reads back to truncate a database has to be fed by the table existing rather than by something choosing to emit its queries — see querygen's own comment on the trap — and a list of one is the shape that survives a second table arriving.
Functions ¶
func FileName ¶
FileName is the canonical .sql file a dialect's queries are written to, beside this file.
func InsertColumns ¶
func InsertColumns() []string
InsertColumns is what an enqueue supplies values for.
Every timestamp among them is written as a server-side expression rather than bound, for the reason every clock note in this file gives: the row's stamps and the comparisons against them have to come from one clock. attempts and lease_until are written as the literals that mean "fresh", and completed_at and last_error are left to the schema's NULL — a newly enqueued item has neither finished nor failed.
func Render ¶
Render returns the canonical sqlc input for one dialect.
It takes the dialect and serves one, which is not a contradiction: the roster is a property of unison.yaml and of the schema workqueue/migrations ships, and this signature is what would make a second dialect a schema question rather than a rewrite. What it will not do is answer for a dialect this package has no schema for — every statement below is written in Postgres and would be handed back unchanged, which is the one failure a generator can have that produces a plausible file.
It panics rather than returning an error, in the manner of the generator it renders through: the argument is a constant in a generator binary. The panic value is an error wrapping dialect.ErrUnsupported.
Types ¶
This section is empty.