1use crate::{
4 begin_read, commit_transaction,
5 error::PageError,
6 hydrate::{DurableRow, hydrate_page},
7 install_foreign_keys,
8};
9use dovecote::{Limit, PagedEvent, RowId};
10use sqlx::{Sqlite, SqlitePool, Transaction, query_as, query_scalar};
11use std::marker::PhantomData;
12
13pub async fn page(
15 pool: &SqlitePool,
16 after_row_id: Option<RowId>,
17 limit: Limit,
18) -> Result<Vec<PagedEvent>, PageError> {
19 let mut connection = pool
20 .acquire()
21 .await
22 .map_err(|source| PageError::sql("acquire live page connection", source))?;
23 install_foreign_keys(&mut connection)
24 .await
25 .map_err(|source| PageError::sql("enable live-page foreign keys", source))?;
26 read_page(
27 &mut *connection,
28 after_row_id.map_or(0, RowId::get),
29 None,
30 limit,
31 )
32 .await
33}
34
35pub async fn begin_snapshot(pool: &SqlitePool) -> Result<SnapshotPager, PageError> {
38 let mut transaction = begin_read(pool)
39 .await
40 .map_err(|source| PageError::sql("begin snapshot transaction", source))?;
41 let upper_bound =
42 match query_scalar::<_, Option<i64>>("SELECT MAX(row_id) FROM dovecote_events")
43 .fetch_one(&mut *transaction)
44 .await
45 {
46 Ok(value) => value,
47 Err(source) => {
48 let _ = transaction.rollback().await;
49 return Err(PageError::sql("read snapshot upper row ID", source));
50 }
51 };
52 let upper_bound = match upper_bound
53 .map(|value| RowId::new(value).map_err(|error| PageError::serialization(error.to_string())))
54 .transpose()
55 {
56 Ok(value) => value,
57 Err(error) => {
58 let _ = transaction.rollback().await;
59 return Err(error);
60 }
61 };
62 Ok(SnapshotPager {
63 transaction: Some(transaction),
64 upper_bound,
65 cursor: None,
66 exhausted: upper_bound.is_none(),
67 _not_send: PhantomData,
68 })
69}
70
71pub struct SnapshotPager {
84 transaction: Option<Transaction<'static, Sqlite>>,
85 upper_bound: Option<RowId>,
86 cursor: Option<RowId>,
87 exhausted: bool,
88 _not_send: PhantomData<*mut ()>,
89}
90
91impl SnapshotPager {
92 pub const fn cursor(&self) -> Option<RowId> {
93 self.cursor
94 }
95 pub const fn upper_bound(&self) -> Option<RowId> {
96 self.upper_bound
97 }
98 pub const fn is_exhausted(&self) -> bool {
99 self.exhausted
100 }
101
102 pub async fn next_page(&mut self, limit: Limit) -> Result<Vec<PagedEvent>, PageError> {
103 if self.exhausted {
104 return Ok(Vec::new());
105 }
106
107 let transaction = self.transaction.as_mut().ok_or(PageError::Closed)?;
108 let upper = self
109 .upper_bound
110 .expect("non-exhausted pager has an upper bound");
111 let result = read_page(
112 &mut **transaction,
113 self.cursor.map_or(0, RowId::get),
114 Some(upper.get()),
115 limit,
116 )
117 .await;
118 let rows = match result {
119 Ok(rows) => rows,
120 Err(error) => {
121 if let Some(transaction) = self.transaction.take() {
122 let _ = transaction.rollback().await;
123 }
124
125 return Err(error);
126 }
127 };
128 if let Some(last) = rows.last() {
129 self.cursor = Some(last.row_id());
130
131 if rows.len() < limit.get() as usize || self.cursor == self.upper_bound {
132 self.exhausted = true;
133 }
134 } else {
135 self.exhausted = true;
136 }
137
138 Ok(rows)
139 }
140
141 pub async fn finish(mut self) -> Result<(), PageError> {
142 let Some(transaction) = self.transaction.take() else {
143 return Ok(());
144 };
145 commit_transaction(transaction)
146 .await
147 .map_err(|source| PageError::sql("finish snapshot transaction", source))
148 }
149 pub async fn rollback(mut self) -> Result<(), PageError> {
150 let Some(transaction) = self.transaction.take() else {
151 return Ok(());
152 };
153 transaction
154 .rollback()
155 .await
156 .map_err(|source| PageError::sql("rollback snapshot transaction", source))
157 }
158 pub async fn close(self) -> Result<(), PageError> {
159 self.rollback().await
160 }
161}
162
163async fn read_page<'c, E>(
164 executor: E,
165 after_row_id: i64,
166 upper_bound: Option<i64>,
167 limit: Limit,
168) -> Result<Vec<PagedEvent>, PageError>
169where
170 E: sqlx::Executor<'c, Database = Sqlite>,
171{
172 let rows = query_as::<_, DurableRow>(PAGE_SQL)
173 .bind(after_row_id)
174 .bind(upper_bound)
175 .bind(upper_bound)
176 .bind(i64::from(limit.get()))
177 .fetch_all(executor)
178 .await
179 .map_err(|source| PageError::sql("read event page", source))?;
180 rows.into_iter()
181 .map(hydrate_page)
182 .collect::<Result<Vec<_>, _>>()
183 .map_err(PageError::serialization)
184}
185
186const PAGE_SQL: &str = "SELECT e.row_id, e.stream, e.specversion, e.event_id, e.source, e.event_type, e.subject, e.occurred_at, e.enqueued_at, e.datacontenttype, e.dataschema, e.partitionkey, e.extensions, e.data_kind, e.data, d.state, d.available_at, d.attempts, d.claim_token, d.claimed_by, d.claim_expires_at, d.last_failure_code, d.last_failure_detail, d.delivered_at, d.quarantined_at, d.quarantine_reason FROM dovecote_events AS e LEFT JOIN dovecote_deliveries AS d ON d.event_row_id = e.row_id WHERE e.row_id > ? AND (? IS NULL OR e.row_id <= ?) ORDER BY e.row_id ASC LIMIT ?";