1use async_trait::async_trait;
4
5use crate::core::{RunId, Secret, Seq, StoreError};
6use crate::push::{
7 DueBatch, OPERATOR_PREFIX, PushAuthentication, PushConfig, PushNamespace, PushRegistration,
8 PushStore,
9};
10
11use super::postgres::{PostgresStore, amount_of, be};
12
13fn registration_from(row: &tokio_postgres::Row) -> Result<PushRegistration, StoreError> {
16 let task_id: String = row.get(0);
17 Ok(PushRegistration {
18 config: PushConfig {
19 id: row.get(1),
20 task: RunId::parse(&task_id).map_err(|error| StoreError::Backend(error.to_string()))?,
21 url: row.get(2),
22 token: row.get::<_, Option<String>>(3).map(Secret::new),
23 authentication: row
24 .get::<_, Option<String>>(4)
25 .zip(row.get::<_, Option<String>>(5))
26 .map(|(scheme, credentials)| PushAuthentication {
27 scheme,
28 credentials: Secret::new(credentials),
29 }),
30 },
31 next_seq: amount_of(row.get::<_, i64>(6)),
36 attempts: u32::try_from(row.get::<_, i32>(7)).unwrap_or(0),
37 next_attempt_at: amount_of(row.get::<_, i64>(8)),
38 last_error: row.get(9),
39 })
40}
41
42#[async_trait]
43impl PushStore for PostgresStore {
44 fn tenant(&self) -> &str {
45 self.tenant_str()
46 }
47
48 async fn put(&self, config: &PushConfig, next_seq: Seq) -> Result<(), StoreError> {
49 let client = self
50 .pool_ref()
51 .get()
52 .await
53 .map_err(|error| StoreError::Backend(error.to_string()))?;
54 client
55 .execute(
56 "INSERT INTO push_delivery
57 (tenant, task_id, config_id, url, token, auth_scheme, auth_credentials,
58 next_seq, attempts, next_attempt_at, last_error)
59 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 0, 0, NULL)
60 ON CONFLICT (tenant, task_id, config_id) DO UPDATE SET
61 url = EXCLUDED.url,
62 token = EXCLUDED.token,
63 auth_scheme = EXCLUDED.auth_scheme,
64 auth_credentials = EXCLUDED.auth_credentials,
65 next_seq = push_delivery.next_seq,
66 attempts = 0,
67 next_attempt_at = 0,
68 last_error = NULL,
69 parked = FALSE",
70 &[
71 &self.tenant_name(),
72 &config.task.to_string(),
73 &config.id,
74 &config.url,
75 &config.token.as_ref().map(Secret::expose),
76 &config
77 .authentication
78 .as_ref()
79 .map(|auth| auth.scheme.as_str()),
80 &config
81 .authentication
82 .as_ref()
83 .map(|auth| auth.credentials.expose()),
84 &next_seq.cast_signed(),
85 ],
86 )
87 .await
88 .map_err(|error| be(&error))?;
89 Ok(())
90 }
91
92 async fn get(&self, task: RunId, id: &str) -> Result<Option<PushConfig>, StoreError> {
93 let client = self
94 .pool_ref()
95 .get()
96 .await
97 .map_err(|error| StoreError::Backend(error.to_string()))?;
98 let row = client
99 .query_opt(
100 "SELECT url, token, auth_scheme, auth_credentials FROM push_delivery
101 WHERE tenant = $1 AND task_id = $2 AND config_id = $3",
102 &[&self.tenant_name(), &task.to_string(), &id],
103 )
104 .await
105 .map_err(|error| be(&error))?;
106 Ok(row.map(|row| PushConfig {
107 id: id.to_owned(),
108 task,
109 url: row.get(0),
110 token: row.get::<_, Option<String>>(1).map(Secret::new),
111 authentication: row
112 .get::<_, Option<String>>(2)
113 .zip(row.get::<_, Option<String>>(3))
114 .map(|(scheme, credentials)| PushAuthentication {
115 scheme,
116 credentials: Secret::new(credentials),
117 }),
118 }))
119 }
120
121 async fn list(&self, task: RunId) -> Result<Vec<PushConfig>, StoreError> {
122 let client = self
123 .pool_ref()
124 .get()
125 .await
126 .map_err(|error| StoreError::Backend(error.to_string()))?;
127 let rows = client
128 .query(
129 "SELECT config_id, url, token, auth_scheme, auth_credentials FROM push_delivery
130 WHERE tenant = $1 AND task_id = $2 ORDER BY config_id",
131 &[&self.tenant_name(), &task.to_string()],
132 )
133 .await
134 .map_err(|error| be(&error))?;
135 Ok(rows
136 .into_iter()
137 .map(|row| PushConfig {
138 id: row.get(0),
139 task,
140 url: row.get(1),
141 token: row.get::<_, Option<String>>(2).map(Secret::new),
142 authentication: row
143 .get::<_, Option<String>>(3)
144 .zip(row.get::<_, Option<String>>(4))
145 .map(|(scheme, credentials)| PushAuthentication {
146 scheme,
147 credentials: Secret::new(credentials),
148 }),
149 })
150 .collect())
151 }
152
153 async fn due(&self, at: u64, limit: usize) -> Result<Vec<PushRegistration>, StoreError> {
154 let client = self
155 .pool_ref()
156 .get()
157 .await
158 .map_err(|error| StoreError::Backend(error.to_string()))?;
159 let rows = client
160 .query(
161 "SELECT task_id, config_id, url, token, auth_scheme, auth_credentials,
162 next_seq, attempts, next_attempt_at, last_error
163 FROM push_delivery
164 WHERE tenant = $1 AND next_attempt_at <= $2 AND NOT parked
165 ORDER BY next_attempt_at, task_id, config_id
166 LIMIT $3",
167 &[
168 &self.tenant_name(),
169 &at.cast_signed(),
170 &i64::try_from(limit).unwrap_or(i64::MAX),
171 ],
172 )
173 .await
174 .map_err(|error| be(&error))?;
175 rows.iter().map(registration_from).collect()
176 }
177
178 async fn due_in(
179 &self,
180 at: u64,
181 limit: usize,
182 namespace: PushNamespace,
183 ) -> Result<DueBatch, StoreError> {
184 let client = self
185 .pool_ref()
186 .get()
187 .await
188 .map_err(|error| StoreError::Backend(error.to_string()))?;
189 let wants_operator = namespace == PushNamespace::Operator;
197 let pattern = format!("{OPERATOR_PREFIX}%");
198 let rows = client
199 .query(
200 "SELECT task_id, config_id, url, token, auth_scheme, auth_credentials,
201 next_seq, attempts, next_attempt_at, last_error
202 FROM push_delivery
203 WHERE tenant = $1 AND next_attempt_at <= $2 AND NOT parked
204 AND (config_id LIKE $4) = $5
205 ORDER BY next_attempt_at, task_id, config_id
206 LIMIT $3",
207 &[
208 &self.tenant_name(),
209 &at.cast_signed(),
210 &i64::try_from(limit).unwrap_or(i64::MAX),
211 &pattern,
212 &wants_operator,
213 ],
214 )
215 .await
216 .map_err(|error| be(&error))?;
217 let unserved_row = client
223 .query_one(
224 "SELECT COUNT(*) FROM push_delivery
225 WHERE tenant = $1 AND next_attempt_at <= $2 AND NOT parked
226 AND (config_id LIKE $3) <> $4",
227 &[
228 &self.tenant_name(),
229 &at.cast_signed(),
230 &pattern,
231 &wants_operator,
232 ],
233 )
234 .await
235 .map_err(|error| be(&error))?;
236 let unserved = usize::try_from(unserved_row.get::<_, i64>(0)).unwrap_or(usize::MAX);
237 Ok(DueBatch {
238 rows: rows
239 .iter()
240 .map(registration_from)
241 .collect::<Result<_, _>>()?,
242 unserved,
243 })
244 }
245
246 async fn advance(&self, task: RunId, id: &str, next_seq: Seq) -> Result<(), StoreError> {
247 let client = self
248 .pool_ref()
249 .get()
250 .await
251 .map_err(|error| StoreError::Backend(error.to_string()))?;
252 client
253 .execute(
254 "UPDATE push_delivery
255 SET next_seq = GREATEST(next_seq, $4), attempts = 0,
256 next_attempt_at = 0, last_error = NULL, parked = FALSE
257 WHERE tenant = $1 AND task_id = $2 AND config_id = $3",
258 &[
259 &self.tenant_name(),
260 &task.to_string(),
261 &id,
262 &next_seq.cast_signed(),
263 ],
264 )
265 .await
266 .map_err(|error| be(&error))?;
267 Ok(())
268 }
269
270 async fn retry(
271 &self,
272 task: RunId,
273 id: &str,
274 next_attempt_at: u64,
275 error: &str,
276 ) -> Result<(), StoreError> {
277 let client = self
278 .pool_ref()
279 .get()
280 .await
281 .map_err(|pool_error| StoreError::Backend(pool_error.to_string()))?;
282 client
283 .execute(
284 "UPDATE push_delivery
285 SET attempts = LEAST(attempts + 1, 2147483647),
286 next_attempt_at = $4, last_error = $5, parked = FALSE
287 WHERE tenant = $1 AND task_id = $2 AND config_id = $3",
288 &[
289 &self.tenant_name(),
290 &task.to_string(),
291 &id,
292 &next_attempt_at.cast_signed(),
293 &error,
294 ],
295 )
296 .await
297 .map_err(|db_error| be(&db_error))?;
298 Ok(())
299 }
300
301 async fn park(&self, task: RunId, id: &str, error: &str) -> Result<(), StoreError> {
302 let client = self
303 .pool_ref()
304 .get()
305 .await
306 .map_err(|pool_error| StoreError::Backend(pool_error.to_string()))?;
307 client
308 .execute(
309 "UPDATE push_delivery
310 SET attempts = LEAST(attempts + 1, 2147483647),
311 last_error = $4, parked = TRUE
312 WHERE tenant = $1 AND task_id = $2 AND config_id = $3",
313 &[&self.tenant_name(), &task.to_string(), &id, &error],
314 )
315 .await
316 .map_err(|db_error| be(&db_error))?;
317 Ok(())
318 }
319
320 async fn parked(&self, limit: usize) -> Result<Vec<PushRegistration>, StoreError> {
321 let client = self
322 .pool_ref()
323 .get()
324 .await
325 .map_err(|error| StoreError::Backend(error.to_string()))?;
326 let rows = client
327 .query(
328 "SELECT task_id, config_id, url, token, auth_scheme, auth_credentials,
329 next_seq, attempts, next_attempt_at, last_error
330 FROM push_delivery
331 WHERE tenant = $1 AND parked
332 ORDER BY task_id, config_id
333 LIMIT $2",
334 &[
335 &self.tenant_name(),
336 &i64::try_from(limit).unwrap_or(i64::MAX),
337 ],
338 )
339 .await
340 .map_err(|error| be(&error))?;
341 rows.iter().map(registration_from).collect()
342 }
343
344 async fn unpark(&self, task: RunId, id: &str, at: u64) -> Result<bool, StoreError> {
345 let client = self
346 .pool_ref()
347 .get()
348 .await
349 .map_err(|error| StoreError::Backend(error.to_string()))?;
350 let updated = client
351 .execute(
352 "UPDATE push_delivery
353 SET parked = FALSE, attempts = 0, next_attempt_at = $4
354 WHERE tenant = $1 AND task_id = $2 AND config_id = $3 AND parked",
355 &[
356 &self.tenant_name(),
357 &task.to_string(),
358 &id,
359 &at.cast_signed(),
360 ],
361 )
362 .await
363 .map_err(|error| be(&error))?;
364 Ok(updated > 0)
365 }
366
367 async fn delete(&self, task: RunId, id: &str) -> Result<(), StoreError> {
368 let client = self
369 .pool_ref()
370 .get()
371 .await
372 .map_err(|error| StoreError::Backend(error.to_string()))?;
373 client
374 .execute(
375 "DELETE FROM push_delivery
376 WHERE tenant = $1 AND task_id = $2 AND config_id = $3",
377 &[&self.tenant_name(), &task.to_string(), &id],
378 )
379 .await
380 .map_err(|error| be(&error))?;
381 Ok(())
382 }
383}