Clamp an unlimited download queue's LIMIT for Postgres (#98)

With max_new_per_check at 0 and no per-subscription limit, the budget is usize::MAX, and
pending() bound it `as i64`: -1. SQLite reads LIMIT -1 as no limit; Postgres refuses it, so a
feed's downloads failed. The new test fails with "LIMIT must not be negative" on Postgres
without the clamp and passes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 18:37:39 +00:00
parent 98ed9b6498
commit 4a26c73b82
3 changed files with 15 additions and 15 deletions

View File

@@ -1,14 +0,0 @@
{
"permissions": {
"allow": [
"Bash(rtk grep *)",
"Bash(rtk read *)",
"Bash(rtk git *)"
],
"additionalDirectories": [
"/config/.claude/skills/security-audit",
"/config/security-audit-skill",
"/config/.cargo/registry"
]
}
}

View File

@@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- With no limit on new downloads per check (`max_new_per_check = 0`), downloads work on Postgres; they failed.
- Checking a feed with a long history is much quicker: items already stored are no longer written again on every check. - Checking a feed with a long history is much quicker: items already stored are no longer written again on every check.
- A scan no longer asks a feed's website for its icon every time; it asks again when the artwork changes or you refresh the feed. - A scan no longer asks a feed's website for its icon every time; it asks again when the artwork changes or you refresh the feed.
- The feed list loads several times faster: it was asking the database six questions per feed. - The feed list loads several times faster: it was asking the database six questions per feed.

View File

@@ -635,7 +635,9 @@ impl Db {
WHERE x.feed_id = $1 AND x.state = 'pending' WHERE x.feed_id = $1 AND x.state = 'pending'
ORDER BY coalesce(e.published, e.first_seen) DESC, x.id DESC ORDER BY coalesce(e.published, e.first_seen) DESC, x.id DESC
LIMIT $2", LIMIT $2",
vec![feed_id.into(), (limit as i64).into()], // Unlimited is usize::MAX, which `as i64` makes -1: SQLite reads LIMIT -1 as no
// limit, Postgres refuses it, and the feed's downloads failed (#98).
vec![feed_id.into(), (limit.min(i64::MAX as usize) as i64).into()],
) )
.await? .await?
.iter() .iter()
@@ -1934,6 +1936,17 @@ mod tests {
assert_eq!((list["f"].unread, list["g"].unread), (1, 1)); // a read, c hidden; sam's read is sam's assert_eq!((list["f"].unread, list["g"].unread), (1, 1)); // a read, c hidden; sam's read is sam's
} }
#[tokio::test]
async fn an_unlimited_queue_takes_everything_waiting() {
let db = Db::memory().await.unwrap();
db.exec_for_test(
"INSERT INTO entries (feed_id, guid, first_seen) VALUES ('f','a',0),('f','b',0);
INSERT INTO enclosures (id, feed_id, guid, url, state) VALUES (1,'f','a','u1','pending'),(2,'f','b','u2','pending');",
).await
.unwrap();
assert_eq!(db.pending("f", usize::MAX).await.unwrap().len(), 2);
}
#[tokio::test] #[tokio::test]
async fn a_scan_knows_a_feeds_stored_items_and_files() { async fn a_scan_knows_a_feeds_stored_items_and_files() {
let db = Db::memory().await.unwrap(); let db = Db::memory().await.unwrap();