reliar_store_postgres/outbox/
outbox_store_dead_letters.rs1use sqlx::PgConnection;
6
7use reliar_core::Serializer;
8use reliar_outbox::{
9 DeadCursor, DeadLetterPage, DeadQuery, OutboxDeadLetters, OutboxRecordId, PoisonedRow,
10 RecordRef,
11};
12
13use super::dead_letters as repo;
14use super::error::PostgresOutboxError;
15use crate::records::{RawRow, decode_row};
16
17use super::PostgresOutboxStore;
18
19const MAX_LIST_DEAD_LIMIT: u32 = 1000;
23
24impl<Ser: Serializer + Send + Sync + 'static> OutboxDeadLetters for PostgresOutboxStore<Ser> {
25 type Error = PostgresOutboxError;
26
27 async fn list_dead(&self, query: DeadQuery) -> Result<DeadLetterPage, Self::Error> {
32 let capped_limit = query.limit.min(MAX_LIST_DEAD_LIMIT);
35 let limit = i64::from(capped_limit);
36 let (after_dead_at, after_id) = query.after.map_or((None, None), |cursor| {
37 (Some(cursor.dead_at()), Some(cursor.id().as_uuid()))
38 });
39
40 let rows: Vec<RawRow> = self
41 .session
42 .run(async |conn: &mut PgConnection| {
43 repo::list_dead_rows(
44 &mut *conn,
45 repo::ListDeadRowsParams {
46 message_type: query.message_type.as_deref(),
47 tenant_id: query.tenant_id.as_deref(),
48 dead_before: query.dead_before,
49 after_dead_at,
50 after_id,
51 limit,
52 },
53 )
54 .await
55 })
56 .await
57 .map_err(|e| self.session.map_err::<PostgresOutboxError>(e))?;
58
59 let scanned = rows.len();
60 let mut records = Vec::with_capacity(scanned);
61 let mut poisoned = Vec::new();
62 let mut last_cursor: Option<DeadCursor> = None;
63
64 for raw in rows {
65 debug_assert!(raw.dead_at.is_some(), "list_dead selects only dead rows");
66
67 if let Some(dead_at) = raw.dead_at {
68 last_cursor = Some(DeadCursor::new(dead_at, OutboxRecordId::from_uuid(raw.id)));
69 }
70
71 match decode_row(raw) {
72 Ok(record) => records.push(record),
73 Err(err) => poisoned.push(PoisonedRow::new(err.id, err.message_id, err.detail)),
74 }
75 }
76
77 let next_after = if scanned == capped_limit as usize {
80 last_cursor
81 } else {
82 None
83 };
84
85 Ok(DeadLetterPage::new(records, poisoned, next_after))
86 }
87
88 async fn retry_dead(&self, refs: &[RecordRef]) -> Result<u64, Self::Error> {
95 if refs.is_empty() {
96 return Ok(0);
97 }
98
99 let ids: Vec<uuid::Uuid> = refs.iter().map(|r| r.id.as_uuid()).collect();
100 let affected = self
101 .session
102 .run(async |conn: &mut PgConnection| {
103 repo::retry_dead_rows(&mut *conn, repo::RetryDeadRowsParams { ids: &ids }).await
104 })
105 .await
106 .map_err(|e| self.session.map_err::<PostgresOutboxError>(e))?;
107
108 Ok(affected)
109 }
110
111 async fn purge_dead(&self, refs: &[RecordRef]) -> Result<u64, Self::Error> {
114 if refs.is_empty() {
115 return Ok(0);
116 }
117
118 let ids: Vec<uuid::Uuid> = refs.iter().map(|r| r.id.as_uuid()).collect();
119 let affected = self
120 .session
121 .run(async |conn: &mut PgConnection| {
122 repo::purge_dead_rows(&mut *conn, repo::PurgeDeadRowsParams { ids: &ids }).await
123 })
124 .await
125 .map_err(|e| self.session.map_err::<PostgresOutboxError>(e))?;
126
127 Ok(affected)
128 }
129}