Skip to main content

max / makenotwork

2.4 KB · 79 lines History Blame Raw
1 //! endorsement reads, batched over a post set rather than per post
2
3 use super::{PgPool, Uuid};
4
5 #[derive(sqlx::FromRow)]
6 pub struct EndorsementRow {
7 pub post_id: Uuid,
8 pub endorser_id: Uuid,
9 }
10
11 /// One post's total endorsement count (from an aggregated `GROUP BY`).
12 #[derive(sqlx::FromRow)]
13 pub struct EndorsementCount {
14 pub post_id: Uuid,
15 pub count: i64,
16 }
17
18 /// Batch-fetch endorsements for a set of post IDs.
19 ///
20 /// Loads one row per endorsement, fine when the caller needs the endorser ids,
21 /// but the thread view must NOT use this: a hot post with thousands of
22 /// endorsements would stream every row on every (unauthenticated, unthrottled)
23 /// page load. That path uses [`count_endorsements_for_posts`] +
24 /// [`list_user_endorsed_posts`] instead.
25 #[tracing::instrument(skip_all)]
26 pub async fn list_endorsements_for_posts(
27 pool: &PgPool,
28 post_ids: &[Uuid],
29 ) -> Result<Vec<EndorsementRow>, sqlx::Error> {
30 sqlx::query_as!(
31 EndorsementRow,
32 "SELECT post_id, endorser_id FROM post_endorsements WHERE post_id = ANY($1)",
33 post_ids,
34 )
35 .fetch_all(pool)
36 .await
37 }
38
39 /// Per-post endorsement counts, aggregated in Postgres.
40 ///
41 /// A `GROUP BY` returns at most one row per visible post regardless of how many
42 /// endorsements that post has, rather than streaming every endorsement row into
43 /// the app to `len()` it. Posts with zero endorsements are absent; the caller
44 /// defaults them to 0.
45 #[tracing::instrument(skip_all)]
46 pub async fn count_endorsements_for_posts(
47 pool: &PgPool,
48 post_ids: &[Uuid],
49 ) -> Result<Vec<EndorsementCount>, sqlx::Error> {
50 sqlx::query_as!(
51 EndorsementCount,
52 r#"SELECT post_id, COUNT(*) AS "count!"
53 FROM post_endorsements
54 WHERE post_id = ANY($1)
55 GROUP BY post_id"#,
56 post_ids,
57 )
58 .fetch_all(pool)
59 .await
60 }
61
62 /// The subset of `post_ids` the given user has endorsed. Bounded by the page's
63 /// post count (not the endorsement total), so the thread view can mark the
64 /// viewer's own endorsements without loading every endorser id.
65 #[tracing::instrument(skip_all)]
66 pub async fn list_user_endorsed_posts(
67 pool: &PgPool,
68 post_ids: &[Uuid],
69 user_id: Uuid,
70 ) -> Result<Vec<Uuid>, sqlx::Error> {
71 sqlx::query_scalar!(
72 "SELECT post_id FROM post_endorsements WHERE post_id = ANY($1) AND endorser_id = $2",
73 post_ids,
74 user_id,
75 )
76 .fetch_all(pool)
77 .await
78 }
79