Skip to main content

keepsake_sqlx/repository/
query.rs

1use keepsake::{
2    ActiveRelation, ActiveRelationSource, Keepsake, RelationId, RelationKey, SubjectRef,
3};
4
5use super::{
6    ActiveRelationRow, AppliedKeepsakeRow, KeepsakeRepository, MembershipCursor, RelationCache,
7    RepositoryError, RepositoryResult, validate_limit,
8};
9
10impl<C> KeepsakeRepository<C>
11where
12    C: RelationCache,
13{
14    /// Returns active keepsakes for a subject.
15    pub async fn active_for_subject(
16        &self,
17        subject: &SubjectRef,
18    ) -> RepositoryResult<Vec<Keepsake>> {
19        let rows = sqlx::query_as::<_, AppliedKeepsakeRow>(
20            r"
21            select id, subject_kind, subject_id, relation_id, state, expiry_policy, applied_at,
22                expires_at, fulfilled_at, revoked_at, metadata
23            from keepsakes
24            where subject_kind = $1 and subject_id = $2 and state = 'applied'
25            order by relation_id, id
26            ",
27        )
28        .bind(&subject.kind)
29        .bind(&subject.id)
30        .fetch_all(&self.pool)
31        .await?;
32
33        rows.into_iter()
34            .map(AppliedKeepsakeRow::try_into_keepsake)
35            .collect()
36    }
37
38    /// Returns active keepsakes for a subject with their relation definitions.
39    pub async fn active_relations_for_subject(
40        &self,
41        subject: &SubjectRef,
42    ) -> RepositoryResult<Vec<ActiveRelation>> {
43        let rows = sqlx::query_as::<_, ActiveRelationRow>(
44            r"
45            select
46                k.id,
47                k.subject_kind,
48                k.subject_id,
49                k.relation_id,
50                k.state,
51                k.expiry_policy,
52                k.applied_at,
53                k.expires_at,
54                k.fulfilled_at,
55                k.revoked_at,
56                k.metadata,
57                r.id as relation_definition_id,
58                r.kind as relation_kind,
59                r.key as relation_key,
60                r.enabled as relation_enabled,
61                r.expiry_policy as relation_expiry_policy
62            from keepsakes k
63            join keepsake_relation_definitions r on r.id = k.relation_id
64            where k.subject_kind = $1 and k.subject_id = $2 and k.state = 'applied'
65            order by k.relation_id, k.id
66            ",
67        )
68        .bind(&subject.kind)
69        .bind(&subject.id)
70        .fetch_all(&self.pool)
71        .await?;
72
73        let mut active = Vec::with_capacity(rows.len());
74        for row in rows {
75            let active_relation = row.try_into_active_relation()?;
76            self.relation_cache.store(active_relation.relation()).await;
77            active.push(active_relation);
78        }
79        Ok(active)
80    }
81
82    /// Returns active keepsakes for a subject, filtered by relation ids.
83    ///
84    /// This is the bounded variant of [`Self::active_relations_for_subject`] for
85    /// request paths that use typed relation specs or another stable relation-id
86    /// catalogue. Missing ids are ignored, duplicate requested ids do not
87    /// duplicate output rows, and disabled relation definitions are still
88    /// returned when their keepsake is active.
89    pub async fn active_relations_for_subject_by_ids(
90        &self,
91        subject: &SubjectRef,
92        relation_ids: &[RelationId],
93    ) -> RepositoryResult<Vec<ActiveRelation>> {
94        if relation_ids.is_empty() {
95            return Ok(Vec::new());
96        }
97
98        let requested_relation_ids = relation_ids.to_vec();
99        let rows = sqlx::query_as::<_, ActiveRelationRow>(
100            r"
101            with requested_relation_ids(id) as (
102                select distinct id
103                from unnest($3::uuid[]) as requested(id)
104            )
105            select
106                k.id,
107                k.subject_kind,
108                k.subject_id,
109                k.relation_id,
110                k.state,
111                k.expiry_policy,
112                k.applied_at,
113                k.expires_at,
114                k.fulfilled_at,
115                k.revoked_at,
116                k.metadata,
117                r.id as relation_definition_id,
118                r.kind as relation_kind,
119                r.key as relation_key,
120                r.enabled as relation_enabled,
121                r.expiry_policy as relation_expiry_policy
122            from requested_relation_ids requested
123            join keepsake_relation_definitions r
124              on r.id = requested.id
125            join keepsakes k
126              on k.relation_id = r.id
127             and k.subject_kind = $1
128             and k.subject_id = $2
129             and k.state = 'applied'
130            order by k.relation_id, k.id
131            ",
132        )
133        .bind(&subject.kind)
134        .bind(&subject.id)
135        .bind(&requested_relation_ids)
136        .fetch_all(&self.pool)
137        .await?;
138
139        let mut active = Vec::with_capacity(rows.len());
140        for row in rows {
141            let active_relation = row.try_into_active_relation()?;
142            self.relation_cache.store(active_relation.relation()).await;
143            active.push(active_relation);
144        }
145        Ok(active)
146    }
147
148    /// Returns active keepsakes for a subject, filtered by relation keys.
149    ///
150    /// This is the bounded variant of [`Self::active_relations_for_subject`] for
151    /// request paths that know the small set of relation keys they care about.
152    /// Missing keys are ignored, and disabled relation definitions are still
153    /// returned when their keepsake is active.
154    pub async fn active_relations_for_subject_by_keys(
155        &self,
156        subject: &SubjectRef,
157        keys: &[RelationKey],
158    ) -> RepositoryResult<Vec<ActiveRelation>> {
159        if keys.is_empty() {
160            return Ok(Vec::new());
161        }
162
163        let kinds = keys
164            .iter()
165            .map(|key| key.kind().to_owned())
166            .collect::<Vec<String>>();
167        let names = keys
168            .iter()
169            .map(|key| key.name().to_owned())
170            .collect::<Vec<String>>();
171
172        let rows = sqlx::query_as::<_, ActiveRelationRow>(
173            r"
174            with requested_relation_keys(kind, key) as (
175                select distinct kind, key
176                from unnest($3::text[], $4::text[]) as requested(kind, key)
177            )
178            select
179                k.id,
180                k.subject_kind,
181                k.subject_id,
182                k.relation_id,
183                k.state,
184                k.expiry_policy,
185                k.applied_at,
186                k.expires_at,
187                k.fulfilled_at,
188                k.revoked_at,
189                k.metadata,
190                r.id as relation_definition_id,
191                r.kind as relation_kind,
192                r.key as relation_key,
193                r.enabled as relation_enabled,
194                r.expiry_policy as relation_expiry_policy
195            from requested_relation_keys requested
196            join keepsake_relation_definitions r
197              on r.kind = requested.kind and r.key = requested.key
198            join keepsakes k
199              on k.relation_id = r.id
200             and k.subject_kind = $1
201             and k.subject_id = $2
202             and k.state = 'applied'
203            order by k.relation_id, k.id
204            ",
205        )
206        .bind(&subject.kind)
207        .bind(&subject.id)
208        .bind(&kinds)
209        .bind(&names)
210        .fetch_all(&self.pool)
211        .await?;
212
213        let mut active = Vec::with_capacity(rows.len());
214        for row in rows {
215            let active_relation = row.try_into_active_relation()?;
216            self.relation_cache.store(active_relation.relation()).await;
217            active.push(active_relation);
218        }
219        Ok(active)
220    }
221
222    /// Scans active memberships for a relation in stable order.
223    pub async fn active_membership_scan(
224        &self,
225        relation_id: RelationId,
226        limit: i64,
227    ) -> RepositoryResult<Vec<Keepsake>> {
228        self.active_membership_scan_after(relation_id, None, limit)
229            .await
230    }
231
232    /// Scans active memberships after a keyset cursor in stable order.
233    pub async fn active_membership_scan_after(
234        &self,
235        relation_id: RelationId,
236        after: Option<&MembershipCursor>,
237        limit: i64,
238    ) -> RepositoryResult<Vec<Keepsake>> {
239        let limit = validate_limit(limit)?;
240        let rows = sqlx::query_as::<_, AppliedKeepsakeRow>(
241            r"
242            select id, subject_kind, subject_id, relation_id, state, expiry_policy, applied_at,
243                expires_at, fulfilled_at, revoked_at, metadata
244            from keepsakes
245            where relation_id = $1
246              and state = 'applied'
247              and (
248                $2::text is null
249                or (subject_kind, subject_id, id) > ($2, $3, $4)
250              )
251            order by subject_kind, subject_id, id
252            limit $5
253            ",
254        )
255        .bind(relation_id)
256        .bind(after.map(|cursor| cursor.subject_kind.as_str()))
257        .bind(after.map(|cursor| cursor.subject_id.as_str()))
258        .bind(after.map(|cursor| cursor.keepsake_id))
259        .bind(limit)
260        .fetch_all(&self.pool)
261        .await?;
262
263        rows.into_iter()
264            .map(AppliedKeepsakeRow::try_into_keepsake)
265            .collect()
266    }
267}
268
269impl<C> ActiveRelationSource for KeepsakeRepository<C>
270where
271    C: RelationCache,
272{
273    type Error = RepositoryError;
274
275    async fn active_relations_for_subject<'a>(
276        &'a self,
277        subject: &'a SubjectRef,
278    ) -> RepositoryResult<Vec<ActiveRelation>> {
279        self.active_relations_for_subject(subject).await
280    }
281
282    async fn active_relations_for_subject_by_ids<'a>(
283        &'a self,
284        subject: &'a SubjectRef,
285        relation_ids: &'a [RelationId],
286    ) -> RepositoryResult<Vec<ActiveRelation>> {
287        self.active_relations_for_subject_by_ids(subject, relation_ids)
288            .await
289    }
290
291    async fn active_relations_for_subject_by_keys<'a>(
292        &'a self,
293        subject: &'a SubjectRef,
294        keys: &'a [RelationKey],
295    ) -> RepositoryResult<Vec<ActiveRelation>> {
296        self.active_relations_for_subject_by_keys(subject, keys)
297            .await
298    }
299}