| 183 |
183 |
|
#[tracing::instrument(skip_all)]
|
| 184 |
184 |
|
pub async fn list_communities(pool: &PgPool, limit: i64, offset: i64) -> Result<Vec<CommunityListRow>, sqlx::Error> {
|
| 185 |
185 |
|
sqlx::query_as::<_, CommunityListRow>(
|
|
186 |
+ |
// Per-community counts as scalar subqueries rather than a
|
|
187 |
+ |
// categories⋈threads join + GROUP BY COUNT(DISTINCT): the join
|
|
188 |
+ |
// multiplied rows (community × categories × threads) and forced
|
|
189 |
+ |
// aggregating *every* community's threads before LIMIT. As scalar
|
|
190 |
+ |
// subqueries in the target list they're evaluated only for the page's
|
|
191 |
+ |
// output rows (after ORDER BY + LIMIT), bounding the work to one page.
|
| 186 |
192 |
|
"SELECT co.name, co.slug, co.description,
|
| 187 |
|
- |
COUNT(DISTINCT c.id) AS category_count,
|
| 188 |
|
- |
COUNT(DISTINCT t.id) AS thread_count
|
|
193 |
+ |
(SELECT COUNT(*) FROM categories c WHERE c.community_id = co.id) AS category_count,
|
|
194 |
+ |
(SELECT COUNT(*) FROM threads t
|
|
195 |
+ |
JOIN categories c2 ON c2.id = t.category_id
|
|
196 |
+ |
WHERE c2.community_id = co.id) AS thread_count
|
| 189 |
197 |
|
FROM communities co
|
| 190 |
|
- |
LEFT JOIN categories c ON c.community_id = co.id
|
| 191 |
|
- |
LEFT JOIN threads t ON t.category_id = c.id
|
| 192 |
198 |
|
WHERE co.suspended_at IS NULL
|
| 193 |
199 |
|
AND co.state <> 'archived'
|
| 194 |
|
- |
GROUP BY co.id
|
| 195 |
200 |
|
ORDER BY co.name
|
| 196 |
201 |
|
LIMIT $1 OFFSET $2",
|
| 197 |
202 |
|
)
|
| 220 |
225 |
|
offset: i64,
|
| 221 |
226 |
|
) -> Result<Vec<CommunityListRow>, sqlx::Error> {
|
| 222 |
227 |
|
sqlx::query_as::<_, CommunityListRow>(
|
|
228 |
+ |
// Scalar-subquery counts (see `list_communities`) — bounded to the page.
|
| 223 |
229 |
|
"SELECT co.name, co.slug, co.description,
|
| 224 |
|
- |
COUNT(DISTINCT c.id) AS category_count,
|
| 225 |
|
- |
COUNT(DISTINCT t.id) AS thread_count
|
|
230 |
+ |
(SELECT COUNT(*) FROM categories c WHERE c.community_id = co.id) AS category_count,
|
|
231 |
+ |
(SELECT COUNT(*) FROM threads t
|
|
232 |
+ |
JOIN categories c2 ON c2.id = t.category_id
|
|
233 |
+ |
WHERE c2.community_id = co.id) AS thread_count
|
| 226 |
234 |
|
FROM communities co
|
| 227 |
|
- |
LEFT JOIN categories c ON c.community_id = co.id
|
| 228 |
|
- |
LEFT JOIN threads t ON t.category_id = c.id
|
| 229 |
235 |
|
WHERE co.suspended_at IS NULL
|
| 230 |
236 |
|
AND co.state = 'archived'
|
| 231 |
|
- |
GROUP BY co.id
|
| 232 |
237 |
|
ORDER BY co.name
|
| 233 |
238 |
|
LIMIT $1 OFFSET $2",
|
| 234 |
239 |
|
)
|
| 1477 |
1482 |
|
limit: i64,
|
| 1478 |
1483 |
|
) -> Result<Vec<SearchResultRow>, sqlx::Error> {
|
| 1479 |
1484 |
|
sqlx::query_as::<_, SearchResultRow>(
|
| 1480 |
|
- |
"WITH thread_matches AS (
|
|
1485 |
+ |
// `q` parses the tsquery once (it was parsed 5× inline); cross-joining
|
|
1486 |
+ |
// the single-row CTE feeds it to every rank/match without re-parsing.
|
|
1487 |
+ |
// post_matches uses NOT EXISTS rather than NOT IN (better anti-join,
|
|
1488 |
+ |
// and NULL-safe).
|
|
1489 |
+ |
"WITH q AS (
|
|
1490 |
+ |
SELECT websearch_to_tsquery('english', $1) AS tsq
|
|
1491 |
+ |
),
|
|
1492 |
+ |
thread_matches AS (
|
| 1481 |
1493 |
|
SELECT t.id AS thread_id,
|
| 1482 |
1494 |
|
t.title AS thread_title,
|
| 1483 |
1495 |
|
COALESCE(u.display_name, u.username) AS author_username,
|
| 1487 |
1499 |
|
c.slug AS category_slug,
|
| 1488 |
1500 |
|
LEFT(t.title, 200) AS snippet,
|
| 1489 |
1501 |
|
t.last_activity_at,
|
| 1490 |
|
- |
(ts_rank(t.search_tsv, websearch_to_tsquery('english', $1)) * 2.0
|
|
1502 |
+ |
(ts_rank(t.search_tsv, q.tsq) * 2.0
|
| 1491 |
1503 |
|
+ similarity(t.title, $2)) AS rank
|
| 1492 |
1504 |
|
FROM threads t
|
| 1493 |
1505 |
|
JOIN categories c ON c.id = t.category_id
|
| 1494 |
1506 |
|
JOIN communities co ON co.id = c.community_id
|
| 1495 |
1507 |
|
JOIN users u ON u.mnw_account_id = t.author_id
|
|
1508 |
+ |
CROSS JOIN q
|
| 1496 |
1509 |
|
WHERE t.deleted_at IS NULL
|
| 1497 |
1510 |
|
AND co.suspended_at IS NULL
|
| 1498 |
|
- |
AND (t.search_tsv @@ websearch_to_tsquery('english', $1)
|
|
1511 |
+ |
AND (t.search_tsv @@ q.tsq
|
| 1499 |
1512 |
|
OR similarity(t.title, $2) > 0.1)
|
| 1500 |
1513 |
|
AND ($3::text IS NULL OR co.slug = $3)
|
| 1501 |
1514 |
|
),
|
| 1510 |
1523 |
|
c.slug AS category_slug,
|
| 1511 |
1524 |
|
LEFT(p.body_markdown, 200) AS snippet,
|
| 1512 |
1525 |
|
t.last_activity_at,
|
| 1513 |
|
- |
ts_rank(p.search_tsv, websearch_to_tsquery('english', $1)) AS rank
|
|
1526 |
+ |
ts_rank(p.search_tsv, q.tsq) AS rank
|
| 1514 |
1527 |
|
FROM posts p
|
| 1515 |
1528 |
|
JOIN threads t ON t.id = p.thread_id
|
| 1516 |
1529 |
|
JOIN categories c ON c.id = t.category_id
|
| 1517 |
1530 |
|
JOIN communities co ON co.id = c.community_id
|
| 1518 |
1531 |
|
JOIN users pu ON pu.mnw_account_id = t.author_id
|
|
1532 |
+ |
CROSS JOIN q
|
| 1519 |
1533 |
|
WHERE t.deleted_at IS NULL
|
| 1520 |
1534 |
|
AND co.suspended_at IS NULL
|
| 1521 |
1535 |
|
AND p.removed_at IS NULL
|
| 1522 |
|
- |
AND p.search_tsv @@ websearch_to_tsquery('english', $1)
|
|
1536 |
+ |
AND p.search_tsv @@ q.tsq
|
| 1523 |
1537 |
|
AND ($3::text IS NULL OR co.slug = $3)
|
| 1524 |
|
- |
AND t.id NOT IN (SELECT thread_id FROM thread_matches)
|
| 1525 |
|
- |
ORDER BY t.id, ts_rank(p.search_tsv, websearch_to_tsquery('english', $1)) DESC
|
|
1538 |
+ |
AND NOT EXISTS (SELECT 1 FROM thread_matches tm WHERE tm.thread_id = t.id)
|
|
1539 |
+ |
ORDER BY t.id, ts_rank(p.search_tsv, q.tsq) DESC
|
| 1526 |
1540 |
|
)
|
| 1527 |
1541 |
|
SELECT * FROM thread_matches
|
| 1528 |
1542 |
|
UNION ALL
|