Add limit/offset to cog_gov_search() and cog_balances() #57

Closed
opened 2026-08-09 11:20:43 -04:00 by jared · 0 comments
Owner

cog_spending() and cog_revenue() gained limit/offset in #39, which pushes the page
into SQL and returns the full count as total_rows. cog_gov_search() and
cog_balances() did not, so every consumer of those two is back to materialize-then-slice.

Why it matters downstream

cog-api feature-detects the pushdown ("limit" %in% names(formals(verb))) and uses it on
/spending and /revenue. For /governments and /governments/{govid}/balances it has
no choice but to fetch everything and slice in R:

  • cog_gov_search(name = NULL, state = NULL, type = NULL) returns all 40,336 rows.
    cog-api now memoizes that frame, which hides the cost for the identity route, but a
    paged /governments search still builds the whole result before returning 100 rows.
  • cog_balances() has the same shape as the money verbs and none of the pagination.

Ask

Add limit / offset to both, matching the semantics already established in #39:

  • NULL default, so every existing call site is unchanged.
  • The page applied in SQL behind the existing deterministic ORDER BY.
  • The unpaginated count returned as the total_rows attribute via COUNT(*) OVER() in
    the same scan.
  • Same classed refusals where the combination cannot work.

Additive, so cog-api can adopt it behind the same formals probe with no lockstep deploy.

cog_gov_search() currently has no LIMIT at all, so an unfiltered call is also the one
verb that can return the entire crosswalk by accident.

`cog_spending()` and `cog_revenue()` gained `limit`/`offset` in #39, which pushes the page into SQL and returns the full count as `total_rows`. `cog_gov_search()` and `cog_balances()` did not, so every consumer of those two is back to materialize-then-slice. ## Why it matters downstream cog-api feature-detects the pushdown (`"limit" %in% names(formals(verb))`) and uses it on `/spending` and `/revenue`. For `/governments` and `/governments/{govid}/balances` it has no choice but to fetch everything and slice in R: - `cog_gov_search(name = NULL, state = NULL, type = NULL)` returns all **40,336** rows. cog-api now memoizes that frame, which hides the cost for the identity route, but a paged `/governments` search still builds the whole result before returning 100 rows. - `cog_balances()` has the same shape as the money verbs and none of the pagination. ## Ask Add `limit` / `offset` to both, matching the semantics already established in #39: - `NULL` default, so every existing call site is unchanged. - The page applied in SQL behind the existing deterministic `ORDER BY`. - The unpaginated count returned as the `total_rows` attribute via `COUNT(*) OVER()` in the same scan. - Same classed refusals where the combination cannot work. Additive, so cog-api can adopt it behind the same formals probe with no lockstep deploy. `cog_gov_search()` currently has no `LIMIT` at all, so an unfiltered call is also the one verb that can return the entire crosswalk by accident.
jared closed this issue 2026-08-10 19:28:10 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Civilytics/uscogdata#57