Currently Listening and Pinned start from the person's own rows (#249)

coalesce(s.position, 0) > 5 and coalesce(s.flagged, false) hid from Postgres that a match needs
a row of entry_state, so it kept the outer join and scanned every item: Currently Listening read
all 574,000 (74,733 blocks from disk), 970 ms of its 1.3 s. s.position > 5 and s.flagged mean the
same, a missing row being neither, and let it start from the person's own rows: 2.2 ms, and
Pinned 32 ms to 2.3 (EXPLAIN ANALYZE, BUFFERS on production, read only). Unread keeps its
coalesce: no row is unread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 15:27:25 +00:00
parent cdf2c98f9f
commit 6fde4b73cd
2 changed files with 8 additions and 2 deletions

View File

@@ -1219,14 +1219,18 @@ impl Filter {
// true and false, not 1 and 0: the columns are booleans on Postgres, and SQLite
// reads true and false as 1 and 0.
Self::All => "true",
// An item with no row of state is unread, so this one keeps its coalesce.
Self::Unread => "NOT coalesce(s.read, false)",
Self::Flagged => "coalesce(s.flagged, false)",
// Pinned and in progress need a row of state, said without coalesce so the database
// can tell, and starts from this person's few rows instead of every item: Currently
// Listening read all 574,000, 970 ms, where this takes 2 (#249).
Self::Flagged => "s.flagged",
Self::Downloaded => {
"EXISTS (SELECT 1 FROM enclosures x
WHERE x.feed_id = e.feed_id AND x.guid = e.guid AND x.path IS NOT NULL)"
}
Self::InProgress => {
"coalesce(s.position, 0) > 5
"s.position > 5
AND (coalesce(s.duration, e.duration, 0) = 0
OR s.position * 10 < coalesce(s.duration, e.duration) * 9)"
}