Chapter 3: postjevsql/src, the judge
Two files, and one of them is long. lib.rs is about two thousand lines,
but it reads well from top to bottom once you know that it has exactly one
job: it is the Judge the scan (chapter 7) calls. The scan hands it one
row's calls at a time and gets back one answer per call. Everything between
those two moments is here.
Follow one call, jev_prob(t, 'Urgent?') on one row:
flowchart TD
C["a call, with the row as canonical JSON"] --> K["its cache key: the hash of the exact bytes it would send"]
K --> S{"an equal call already asked in this statement?"}
S -->|yes| W["wait on that one's answer (dedupe_hits)"]
S -->|no| L{"in jev_cache?"}
L -->|yes| H["the kept answer, for nothing"]
L -->|no| B{"within jev.max_rows and jev.max_cost?"}
B -->|no| R["refuse: 54000"]
B -->|yes| Q["one request: the row as the state, each distinct question about it once"]
Q --> J["jev-client: send, retry, classify"]
J --> A["the answer, checked against the pinned model"]
A --> St["settle: stored in jev_cache, on the backend"]
The last box is the subtle one. A judgment runs as a future on the
backend's executor (chapter 6), polled from inside the executor's wait in
the middle of the scan's own work, and the Judge contract says plainly
that such a future must not call Postgres. So answers pile up as receipts, and the scan
calls settle between waits, on the backend proper, which writes them to
jev_cache with ordinary SQL.
Aside. The same trick, run backwards, is how a label-tree search works. It takes several rounds, and each round's Choices should be looked up in the cache before anything is sent. But the cache is SQL, and the search is a future. So the future hands a closure to the backend (
scan::on_backend), the scan runs it between waits, and the future resumes with the result. A future asking a database for a favour, and waiting politely for it.
lib.rs, in the order it is written
- The settings.
_PG_initregisters everyjev.*GUC, with who may set it and why, then registers the scan (scan::register::<JevScan>()).PinnedModelrefuses an alias whenjev.modelis set. - The SQL around the functions. Three
extension_sql!blocks: thejev_cachetable, the five result types (jev_noul_result,jev_score_result,jev_choice_result,jev_tree_result,jev_shortlist_result), and theREVOKE … FROM PUBLICof every function. - The functions. One
#[pg_extern]each, allvolatile,parallel_restrictedand planned withjev_support, all with a body that refuses (chapter 2 says why). Thenjev_support, the planner support function that prices them, andjev_stats. JevScan, theJudge.beginreads the settings once per statement (a prepared plan outlives aSET) and builds the client;affordrefuses a statement whose estimate is over budget;explainis what EXPLAIN shows;windowisjev.concurrencyclamped to the server's stream limit;judgeanswers one row;settlestores what was answered.- Sharing.
AskingandSharedare the statement's registry of judgments in flight, so an equal call waits instead of sending.Owedunregisters a key whose request was dropped, so its waiters ask again themselves.BudgetandCommittedhold each request's worst case against the spend guards until its realusagereplaces it. - The searches.
TreeCalldrivespostjevsql-core's label-tree search forjev_choice_tree, andShortlistCallits chunk-and-shortlist search forjev_choice_shortlist, a round at a time, each round looked up throughon_backend.tree_outturns a search's outcome into the SQL result. - Asking.
Lookupfinds calls in the cache;Judgedis a judgment's answer and how each function reads it;Sendersends the misses within the budget, serves their waiters and queues their receipts. - Odds and ends.
api_key(exactly one source, never both),learned_ratio(characters per token, learned fromjev_cachefor the pinned model), andsetting.
Try it.
cargo test -p postjevsql-coreexercises the planner, key and searches this file drives. The judge itself needs a running Postgres, so its tests are the integration tests:nix develop -c buck2 test //tests:cacheruns the cache's.
For the people who maintain it
| Path | What |
|---|---|
| lib.rs | Settings, the SQL objects, the functions, and JevScan: begin, afford, explain, judge, settle; sharing, budgets, the searches, the sender. |
| failure.rs | Failure: every way a call fails, the one place each becomes an ERROR with its SQLSTATE and DETAIL, and is_failed_judgment, which decides what jev.on_error = unsure may turn into NULL. |
| bin/ | The schema binary cargo-pgrx runs. Chapter 4. |
← Previous: Chapter 2, postjevsql/ · Up: postjevsql · Next: Chapter 4, src/bin/ →