fix(buzz-db): return an empty page from get_channel_window when limit is 0 - #3536
fix(buzz-db): return an empty page from get_channel_window when limit is 0#3536hasky00 wants to merge 2 commits into
Conversation
b0ec12a to
031424a
Compare
Chessing234
left a comment
There was a problem hiding this comment.
Clear contract fix. The early limit == 0 return restores the documented next_cursor.is_some() iff has_more invariant that the limit + 1 probe otherwise breaks (probe row → has_more=true → truncate to empty → next_cursor=None). The ignored Postgres test is the right shape for this crate. One nit: consider whether any caller currently depends on the broken behavior as a "sentinel" — if not, this is merge-ready.
Chessing234
left a comment
There was a problem hiding this comment.
Approving the limit==0 pagination contract fix.
|
i checked and no caller can currently depend on the limit 'limit ==0' behaviour as sentinel : let limit = filter
.limit
.map(|l| (l as u32).min(BRIDGE_WINDOW_MAX_LIMIT))
.unwrap_or(BRIDGE_WINDOW_DEFAULT_LIMIT)
.max(1);So
So the fix only changes behavior for direct library consumers calling with |
|
it should me merged by now |
c93818e to
4221f1e
Compare
🔐 Codex Security Review
|
hasky00
left a comment
There was a problem hiding this comment.
Rebased onto current main and re-scoped the guard.
Since the last push, main split this function: get_channel_window is now a
thin pool wrapper over get_channel_window_on, and Db::get_channel_window_with_session
(the relay bridge's read path) calls get_channel_window_on directly on its
routed reader connection. A guard in the wrapper would have left that
production path still returning has_more = true with next_cursor = None
at limit 0, so the guard now sits in get_channel_window_on and the
regression test asserts both entry points.
@Chessing234 — flagging since this is a different diff than you approved.
Also: the Docker/CI checks here are red because the previous runs expired
unapproved after 30 days, not because anything failed. They need a maintainer
to approve the workflow runs.
4221f1e to
eed74bd
Compare
Summary
get_channel_windowinbuzz-dbcan violate its own documented paginationcontract when called with
limit == 0.ChannelWindow::next_cursoris documented asSomeiffhas_more(
crates/buzz-db/src/thread.rs). The function deriveshas_morefrom alimit + 1probe row before truncation, but derivesnext_cursorfrom thetruncated row set:
With
limit == 0and at least one matching row, this returnshas_more = trueandnext_cursor = None, breaking the invariant. Acaller that trusts the contract (
window.next_cursor.expect("has_more implies next_cursor")— exactly what the module's own pagination test does) panics,and a keyset-pagination loop would spin forever returning empty pages.
The sole current production caller clamps the limit with
.max(1), so thisisn't reachable from the HTTP surface today — but it's a latent contract
violation in a public
DatabaseAPI that any future caller can trip.Fix
Guard
limit == 0at the top ofget_channel_windowand return an empty,exhausted page (
rows: [],has_more: false,next_cursor: None). Thishonors the invariant and also skips a pointless database round-trip for a
zero-row request.
Test
Adds
channel_window_zero_limit_reports_empty_exhausted_page, mirroring theexisting
channel_window_*Postgres tests: it inserts rows, requests azero-limit window, and asserts the page is empty,
has_moreis false, andnext_cursorisNone. (Marked#[ignore = "requires Postgres"]like itssiblings.)
Verification
cargo fmt -p buzz-db -- --check— cleancargo check -p buzz-db --tests— cleancargo clippy -p buzz-db --tests— cleanThe new test requires Postgres and follows the repo's existing
#[ignore = "requires Postgres"]convention.🤖 Generated with Claude Code