1use std::collections::HashSet;
2
3use diesel::prelude::*;
4
5use super::{
6 DbConnection,
7 schema::readd_status::{self},
8};
9use crate::{ConnectionExt, impl_store};
10
11use xmtp_proto::types::GroupId;
12#[derive(Identifiable, Queryable, Selectable, Insertable, Debug, Clone, PartialEq, Eq)]
13#[diesel(table_name = readd_status)]
14#[diesel(primary_key(group_id, installation_id))]
15pub struct ReaddStatus {
16 pub group_id: GroupId,
17 pub installation_id: Vec<u8>,
18 pub requested_at_sequence_id: Option<i64>,
19 pub responded_at_sequence_id: Option<i64>,
20}
21
22impl_store!(ReaddStatus, readd_status);
23
24pub trait QueryReaddStatus {
25 fn get_readd_status(
26 &self,
27 group_id: &GroupId,
28 installation_id: &[u8],
29 ) -> Result<Option<ReaddStatus>, crate::ConnectionError>;
30
31 fn is_awaiting_readd(
32 &self,
33 group_id: &GroupId,
34 installation_id: &[u8],
35 ) -> Result<bool, crate::ConnectionError>;
36
37 fn update_requested_at_sequence_id(
41 &self,
42 group_id: &GroupId,
43 installation_id: &[u8],
44 sequence_id: i64,
45 ) -> Result<(), crate::ConnectionError>;
46
47 fn update_responded_at_sequence_id(
51 &self,
52 group_id: &GroupId,
53 installation_id: &[u8],
54 sequence_id: i64,
55 ) -> Result<(), crate::ConnectionError>;
56
57 fn delete_other_readd_statuses(
58 &self,
59 group_id: &GroupId,
60 self_installation_id: &[u8],
61 ) -> Result<(), crate::ConnectionError>;
62
63 fn delete_readd_statuses(
64 &self,
65 group_id: &GroupId,
66 installation_ids: HashSet<Vec<u8>>,
67 ) -> Result<(), crate::ConnectionError>;
68
69 fn get_readds_awaiting_response(
70 &self,
71 group_id: &GroupId,
72 self_installation_id: &[u8],
73 ) -> Result<Vec<ReaddStatus>, crate::ConnectionError>;
74}
75
76impl<C: ConnectionExt> QueryReaddStatus for DbConnection<C> {
77 fn get_readd_status(
78 &self,
79 group_id: &GroupId,
80 installation_id: &[u8],
81 ) -> Result<Option<ReaddStatus>, crate::ConnectionError> {
82 use super::schema::readd_status::dsl as readd_dsl;
83 use diesel::QueryDsl;
84
85 self.raw_query(|conn| {
86 readd_dsl::readd_status
87 .filter(readd_dsl::group_id.eq(group_id))
88 .filter(readd_dsl::installation_id.eq(installation_id))
89 .first::<ReaddStatus>(conn)
90 .optional()
91 })
92 }
93
94 fn is_awaiting_readd(
95 &self,
96 group_id: &GroupId,
97 installation_id: &[u8],
98 ) -> Result<bool, crate::ConnectionError> {
99 let readd_status = self.get_readd_status(group_id, installation_id)?;
100 if let Some(readd_status) = readd_status
101 && let Some(requested_at) = readd_status.requested_at_sequence_id
102 && requested_at >= readd_status.responded_at_sequence_id.unwrap_or(0)
103 {
104 return Ok(true);
105 }
106 Ok(false)
107 }
108
109 fn update_requested_at_sequence_id(
110 &self,
111 group_id: &GroupId,
112 installation_id: &[u8],
113 sequence_id: i64,
114 ) -> Result<(), crate::ConnectionError> {
115 use super::schema::readd_status::dsl as readd_dsl;
116 use diesel::query_dsl::methods::FilterDsl;
117
118 let new_status = super::readd_status::ReaddStatus {
119 group_id: *group_id,
120 installation_id: installation_id.to_vec(),
121 requested_at_sequence_id: Some(sequence_id),
122 responded_at_sequence_id: None,
123 };
124
125 self.raw_query(|conn| {
126 diesel::insert_into(readd_dsl::readd_status)
127 .values(&new_status)
128 .on_conflict((readd_dsl::group_id, readd_dsl::installation_id))
129 .do_update()
130 .set(readd_dsl::requested_at_sequence_id.eq(sequence_id))
131 .filter(
132 readd_dsl::requested_at_sequence_id
133 .is_null()
134 .or(readd_dsl::requested_at_sequence_id.lt(sequence_id)),
135 )
136 .execute(conn)
137 })?;
138
139 Ok(())
140 }
141
142 fn update_responded_at_sequence_id(
143 &self,
144 group_id: &GroupId,
145 installation_id: &[u8],
146 sequence_id: i64,
147 ) -> Result<(), crate::ConnectionError> {
148 use super::schema::readd_status::dsl as readd_dsl;
149 use diesel::query_dsl::methods::FilterDsl;
150
151 let new_status = super::readd_status::ReaddStatus {
152 group_id: *group_id,
153 installation_id: installation_id.to_vec(),
154 requested_at_sequence_id: None,
155 responded_at_sequence_id: Some(sequence_id),
156 };
157
158 self.raw_query(|conn| {
159 diesel::insert_into(readd_dsl::readd_status)
160 .values(&new_status)
161 .on_conflict((readd_dsl::group_id, readd_dsl::installation_id))
162 .do_update()
163 .set(readd_dsl::responded_at_sequence_id.eq(sequence_id))
164 .filter(
165 readd_dsl::responded_at_sequence_id
166 .is_null()
167 .or(readd_dsl::responded_at_sequence_id.lt(sequence_id)),
168 )
169 .execute(conn)
170 })?;
171
172 Ok(())
173 }
174
175 fn delete_other_readd_statuses(
176 &self,
177 group_id: &GroupId,
178 self_installation_id: &[u8],
179 ) -> Result<(), crate::ConnectionError> {
180 use super::schema::readd_status::dsl as readd_dsl;
181 use diesel::{ExpressionMethods, QueryDsl};
182
183 self.raw_query(|conn| {
184 diesel::delete(
185 readd_dsl::readd_status
186 .filter(readd_dsl::group_id.eq(group_id))
187 .filter(readd_dsl::installation_id.ne(self_installation_id)),
188 )
189 .execute(conn)?;
190 Ok(())
191 })
192 }
193
194 fn delete_readd_statuses(
195 &self,
196 group_id: &GroupId,
197 installation_ids: HashSet<Vec<u8>>,
198 ) -> Result<(), crate::ConnectionError> {
199 use super::schema::readd_status::dsl as readd_dsl;
200 use diesel::{ExpressionMethods, QueryDsl};
201
202 self.raw_query(|conn| {
203 diesel::delete(
204 readd_dsl::readd_status
205 .filter(readd_dsl::group_id.eq(group_id))
206 .filter(readd_dsl::installation_id.eq_any(installation_ids)),
207 )
208 .execute(conn)?;
209 Ok(())
210 })
211 }
212
213 fn get_readds_awaiting_response(
214 &self,
215 group_id: &GroupId,
216 self_installation_id: &[u8],
217 ) -> Result<Vec<ReaddStatus>, crate::ConnectionError> {
218 use super::schema::readd_status::dsl as readd_dsl;
219 use diesel::{ExpressionMethods, QueryDsl};
220
221 self.raw_query(|conn| {
222 readd_dsl::readd_status
223 .filter(readd_dsl::group_id.eq(group_id))
224 .filter(readd_dsl::installation_id.ne(self_installation_id))
225 .filter(readd_dsl::requested_at_sequence_id.is_not_null())
226 .filter(
227 readd_dsl::requested_at_sequence_id
228 .ge(readd_dsl::responded_at_sequence_id)
229 .or(readd_dsl::responded_at_sequence_id.is_null()),
230 )
231 .load::<ReaddStatus>(conn)
232 })
233 }
234}
235
236impl<T> QueryReaddStatus for &T
237where
238 T: QueryReaddStatus,
239{
240 fn get_readd_status(
241 &self,
242 group_id: &GroupId,
243 installation_id: &[u8],
244 ) -> Result<Option<ReaddStatus>, crate::ConnectionError> {
245 (**self).get_readd_status(group_id, installation_id)
246 }
247
248 fn is_awaiting_readd(
249 &self,
250 group_id: &GroupId,
251 installation_id: &[u8],
252 ) -> Result<bool, crate::ConnectionError> {
253 (**self).is_awaiting_readd(group_id, installation_id)
254 }
255
256 fn update_requested_at_sequence_id(
257 &self,
258 group_id: &GroupId,
259 installation_id: &[u8],
260 sequence_id: i64,
261 ) -> Result<(), crate::ConnectionError> {
262 (**self).update_requested_at_sequence_id(group_id, installation_id, sequence_id)
263 }
264
265 fn update_responded_at_sequence_id(
266 &self,
267 group_id: &GroupId,
268 installation_id: &[u8],
269 sequence_id: i64,
270 ) -> Result<(), crate::ConnectionError> {
271 (**self).update_responded_at_sequence_id(group_id, installation_id, sequence_id)
272 }
273
274 fn delete_other_readd_statuses(
275 &self,
276 group_id: &GroupId,
277 self_installation_id: &[u8],
278 ) -> Result<(), crate::ConnectionError> {
279 (**self).delete_other_readd_statuses(group_id, self_installation_id)
280 }
281
282 fn delete_readd_statuses(
283 &self,
284 group_id: &GroupId,
285 installation_ids: HashSet<Vec<u8>>,
286 ) -> Result<(), crate::ConnectionError> {
287 (**self).delete_readd_statuses(group_id, installation_ids)
288 }
289
290 fn get_readds_awaiting_response(
291 &self,
292 group_id: &GroupId,
293 self_installation_id: &[u8],
294 ) -> Result<Vec<ReaddStatus>, crate::ConnectionError> {
295 (**self).get_readds_awaiting_response(group_id, self_installation_id)
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 use crate::{Store, test_utils::with_connection};
303
304 #[xmtp_common::test]
305 fn test_get_readd_status_not_found() {
306 with_connection(|conn| {
307 let group_id = GroupId::ONE;
308 let installation_id = vec![4, 5, 6];
309
310 let result = conn.get_readd_status(&group_id, &installation_id).unwrap();
311 assert!(result.is_none());
312 })
313 }
314
315 #[xmtp_common::test]
316 fn test_store_and_get_readd_status() {
317 with_connection(|conn| {
318 let group_id = GroupId::ONE;
319 let installation_id = vec![4, 5, 6];
320
321 let status = ReaddStatus {
322 group_id,
323 installation_id: installation_id.clone(),
324 requested_at_sequence_id: Some(100),
325 responded_at_sequence_id: Some(50),
326 };
327
328 status.store(conn).unwrap();
330
331 let retrieved = conn.get_readd_status(&group_id, &installation_id).unwrap();
333 assert!(retrieved.is_some());
334 let retrieved_status = retrieved.unwrap();
335 assert_eq!(retrieved_status.requested_at_sequence_id, Some(100));
336 assert_eq!(retrieved_status.responded_at_sequence_id, Some(50));
337 })
338 }
339
340 #[xmtp_common::test]
341 fn test_update_requested_at_sequence_id_creates_new() {
342 with_connection(|conn| {
343 let group_id = GroupId::ONE;
344 let installation_id = vec![4, 5, 6];
345 let sequence_id = 100;
346
347 conn.update_requested_at_sequence_id(&group_id, &installation_id, sequence_id)
349 .unwrap();
350
351 let status = conn.get_readd_status(&group_id, &installation_id).unwrap();
353 assert!(status.is_some());
354 let status = status.unwrap();
355 assert_eq!(status.requested_at_sequence_id, Some(sequence_id));
356 assert_eq!(status.responded_at_sequence_id, None);
357 })
358 }
359
360 #[xmtp_common::test]
361 fn test_update_requested_at_sequence_id_updates_existing() {
362 with_connection(|conn| {
363 let group_id = GroupId::ONE;
364 let installation_id = vec![4, 5, 6];
365
366 let initial_status = ReaddStatus {
368 group_id,
369 installation_id: installation_id.clone(),
370 requested_at_sequence_id: Some(50),
371 responded_at_sequence_id: Some(25),
372 };
373 initial_status.store(conn).unwrap();
374
375 conn.update_requested_at_sequence_id(&group_id, &installation_id, 100)
377 .unwrap();
378
379 let status = conn.get_readd_status(&group_id, &installation_id).unwrap();
381 assert!(status.is_some());
382 let status = status.unwrap();
383 assert_eq!(status.requested_at_sequence_id, Some(100));
384 assert_eq!(status.responded_at_sequence_id, Some(25)); })
386 }
387
388 #[xmtp_common::test]
389 fn test_update_requested_at_sequence_id_only_updates_if_higher() {
390 with_connection(|conn| {
391 let group_id = GroupId::ONE;
392 let installation_id = vec![4, 5, 6];
393
394 let initial_status = ReaddStatus {
396 group_id,
397 installation_id: installation_id.clone(),
398 requested_at_sequence_id: Some(100),
399 responded_at_sequence_id: Some(50),
400 };
401 initial_status.store(conn).unwrap();
402
403 conn.update_requested_at_sequence_id(&group_id, &installation_id, 75)
405 .unwrap();
406
407 let status = conn.get_readd_status(&group_id, &installation_id).unwrap();
409 assert!(status.is_some());
410 let status = status.unwrap();
411 assert_eq!(status.requested_at_sequence_id, Some(100)); assert_eq!(status.responded_at_sequence_id, Some(50)); })
414 }
415
416 #[xmtp_common::test]
417 fn test_update_requested_at_sequence_id_updates_from_null() {
418 with_connection(|conn| {
419 let group_id = GroupId::ONE;
420 let installation_id = vec![4, 5, 6];
421
422 let initial_status = ReaddStatus {
424 group_id,
425 installation_id: installation_id.clone(),
426 requested_at_sequence_id: None,
427 responded_at_sequence_id: Some(25),
428 };
429 initial_status.store(conn).unwrap();
430
431 conn.update_requested_at_sequence_id(&group_id, &installation_id, 50)
433 .unwrap();
434
435 let status = conn.get_readd_status(&group_id, &installation_id).unwrap();
437 assert!(status.is_some());
438 let status = status.unwrap();
439 assert_eq!(status.requested_at_sequence_id, Some(50));
440 assert_eq!(status.responded_at_sequence_id, Some(25)); })
442 }
443
444 #[xmtp_common::test]
445 async fn test_update_responded_at_sequence_id_creates_new() {
446 with_connection(|conn| {
447 let group_id = GroupId::ONE;
448 let installation_id = vec![4, 5, 6];
449 let sequence_id = 100;
450
451 conn.update_responded_at_sequence_id(&group_id, &installation_id, sequence_id)
453 .unwrap();
454
455 let status = conn.get_readd_status(&group_id, &installation_id).unwrap();
457 assert!(status.is_some());
458 let status = status.unwrap();
459 assert_eq!(status.responded_at_sequence_id, Some(sequence_id));
460 assert_eq!(status.requested_at_sequence_id, None);
461 })
462 }
463
464 #[xmtp_common::test]
465 fn test_update_responded_at_sequence_id_only_updates_if_higher() {
466 with_connection(|conn| {
467 let group_id = GroupId::ONE;
468 let installation_id = vec![4, 5, 6];
469
470 let initial_status = ReaddStatus {
472 group_id,
473 installation_id: installation_id.clone(),
474 requested_at_sequence_id: Some(50),
475 responded_at_sequence_id: Some(100),
476 };
477 initial_status.store(conn).unwrap();
478
479 conn.update_responded_at_sequence_id(&group_id, &installation_id, 75)
481 .unwrap();
482
483 let status = conn.get_readd_status(&group_id, &installation_id).unwrap();
485 assert!(status.is_some());
486 let status = status.unwrap();
487 assert_eq!(status.responded_at_sequence_id, Some(100)); assert_eq!(status.requested_at_sequence_id, Some(50)); conn.update_responded_at_sequence_id(&group_id, &installation_id, 125)
492 .unwrap();
493
494 let status = conn.get_readd_status(&group_id, &installation_id).unwrap();
496 assert!(status.is_some());
497 let status = status.unwrap();
498 assert_eq!(status.responded_at_sequence_id, Some(125)); assert_eq!(status.requested_at_sequence_id, Some(50)); })
501 }
502
503 #[xmtp_common::test]
504 fn test_is_awaiting_readd_no_status() {
505 with_connection(|conn| {
506 let group_id = GroupId::ONE;
507 let installation_id = vec![4, 5, 6];
508
509 let result = conn.is_awaiting_readd(&group_id, &installation_id).unwrap();
511 assert!(!result);
512 })
513 }
514
515 #[xmtp_common::test]
516 fn test_is_awaiting_readd_no_request() {
517 with_connection(|conn| {
518 let group_id = GroupId::ONE;
519 let installation_id = vec![4, 5, 6];
520
521 ReaddStatus {
523 group_id,
524 installation_id: installation_id.clone(),
525 requested_at_sequence_id: None,
526 responded_at_sequence_id: Some(5),
527 }
528 .store(conn)
529 .unwrap();
530
531 let result = conn.is_awaiting_readd(&group_id, &installation_id).unwrap();
533 assert!(!result);
534 })
535 }
536
537 #[xmtp_common::test]
538 fn test_is_awaiting_readd_request_pending() {
539 with_connection(|conn| {
540 let group_id = GroupId::ONE;
541 let installation_id = vec![4, 5, 6];
542
543 ReaddStatus {
545 group_id,
546 installation_id: installation_id.clone(),
547 requested_at_sequence_id: Some(10),
548 responded_at_sequence_id: Some(5),
549 }
550 .store(conn)
551 .unwrap();
552
553 let result = conn.is_awaiting_readd(&group_id, &installation_id).unwrap();
555 assert!(result);
556 })
557 }
558
559 #[xmtp_common::test]
560 fn test_is_awaiting_readd_request_fulfilled() {
561 with_connection(|conn| {
562 let group_id = GroupId::ONE;
563 let installation_id = vec![4, 5, 6];
564
565 ReaddStatus {
567 group_id,
568 installation_id: installation_id.clone(),
569 requested_at_sequence_id: Some(5),
570 responded_at_sequence_id: Some(10),
571 }
572 .store(conn)
573 .unwrap();
574
575 let result = conn.is_awaiting_readd(&group_id, &installation_id).unwrap();
577 assert!(!result);
578 })
579 }
580
581 #[xmtp_common::test]
582 fn test_is_awaiting_readd_equal_sequence_ids() {
583 with_connection(|conn| {
584 let group_id = GroupId::ONE;
585 let installation_id = vec![4, 5, 6];
586
587 ReaddStatus {
589 group_id,
590 installation_id: installation_id.clone(),
591 requested_at_sequence_id: Some(10),
592 responded_at_sequence_id: Some(10),
593 }
594 .store(conn)
595 .unwrap();
596
597 let result = conn.is_awaiting_readd(&group_id, &installation_id).unwrap();
601 assert!(result);
602 })
603 }
604
605 #[xmtp_common::test]
606 fn test_is_awaiting_readd_no_responded_at() {
607 with_connection(|conn| {
608 let group_id = GroupId::ONE;
609 let installation_id = vec![4, 5, 6];
610
611 ReaddStatus {
613 group_id,
614 installation_id: installation_id.clone(),
615 requested_at_sequence_id: Some(5),
616 responded_at_sequence_id: None,
617 }
618 .store(conn)
619 .unwrap();
620
621 let result = conn.is_awaiting_readd(&group_id, &installation_id).unwrap();
623 assert!(result);
624 })
625 }
626
627 #[xmtp_common::test]
628 fn test_delete_other_readd_statuses() {
629 with_connection(|conn| {
630 let group_id = GroupId::ONE;
631 let keep_installation_id = vec![10, 11, 12];
632 let delete_installation_id_1 = vec![20, 21, 22];
633 let delete_installation_id_2 = vec![30, 31, 32];
634
635 let status_to_keep = ReaddStatus {
637 group_id,
638 installation_id: keep_installation_id.clone(),
639 requested_at_sequence_id: Some(10),
640 responded_at_sequence_id: Some(5),
641 };
642 status_to_keep.store(conn).unwrap();
643
644 let status_to_delete_1 = ReaddStatus {
645 group_id,
646 installation_id: delete_installation_id_1.clone(),
647 requested_at_sequence_id: Some(15),
648 responded_at_sequence_id: Some(8),
649 };
650 status_to_delete_1.store(conn).unwrap();
651
652 let status_to_delete_2 = ReaddStatus {
653 group_id,
654 installation_id: delete_installation_id_2.clone(),
655 requested_at_sequence_id: Some(20),
656 responded_at_sequence_id: None,
657 };
658 status_to_delete_2.store(conn).unwrap();
659
660 let different_group_status = ReaddStatus {
662 group_id: GroupId::FOUR,
663 installation_id: vec![40, 41, 42],
664 requested_at_sequence_id: Some(25),
665 responded_at_sequence_id: Some(12),
666 };
667 different_group_status.store(conn).unwrap();
668
669 conn.delete_other_readd_statuses(&group_id, &keep_installation_id)
671 .unwrap();
672
673 let kept_status = conn
675 .get_readd_status(&group_id, &keep_installation_id)
676 .unwrap();
677 assert!(kept_status.is_some());
678
679 let deleted_status_1 = conn
681 .get_readd_status(&group_id, &delete_installation_id_1)
682 .unwrap();
683 assert!(deleted_status_1.is_none());
684
685 let deleted_status_2 = conn
686 .get_readd_status(&group_id, &delete_installation_id_2)
687 .unwrap();
688 assert!(deleted_status_2.is_none());
689
690 let different_group_check = conn
692 .get_readd_status(&GroupId::FOUR, &[40, 41, 42])
693 .unwrap();
694 assert!(different_group_check.is_some());
695 })
696 }
697
698 #[xmtp_common::test]
699 fn test_get_readds_awaiting_response() {
700 with_connection(|conn| {
701 let group_id = GroupId::ONE;
702 let self_installation_id = vec![10, 11, 12];
703 let other_installation_id_1 = vec![20, 21, 22];
704 let other_installation_id_2 = vec![30, 31, 32];
705
706 let pending_status_1 = ReaddStatus {
710 group_id,
711 installation_id: other_installation_id_1.clone(),
712 requested_at_sequence_id: Some(10),
713 responded_at_sequence_id: Some(5),
714 };
715 pending_status_1.store(conn).unwrap();
716
717 let pending_status_2 = ReaddStatus {
719 group_id,
720 installation_id: other_installation_id_2.clone(),
721 requested_at_sequence_id: Some(15),
722 responded_at_sequence_id: None,
723 };
724 pending_status_2.store(conn).unwrap();
725
726 let fulfilled_status = ReaddStatus {
728 group_id,
729 installation_id: vec![40, 41, 42],
730 requested_at_sequence_id: Some(8),
731 responded_at_sequence_id: Some(12),
732 };
733 fulfilled_status.store(conn).unwrap();
734
735 let self_status = ReaddStatus {
737 group_id,
738 installation_id: self_installation_id.clone(),
739 requested_at_sequence_id: Some(20),
740 responded_at_sequence_id: Some(10),
741 };
742 self_status.store(conn).unwrap();
743
744 let no_request_status = ReaddStatus {
746 group_id,
747 installation_id: vec![50, 51, 52],
748 requested_at_sequence_id: None,
749 responded_at_sequence_id: Some(5),
750 };
751 no_request_status.store(conn).unwrap();
752
753 let different_group_status = ReaddStatus {
755 group_id: GroupId::FOUR,
756 installation_id: vec![60, 61, 62],
757 requested_at_sequence_id: Some(25),
758 responded_at_sequence_id: Some(15),
759 };
760 different_group_status.store(conn).unwrap();
761
762 let result = conn
764 .get_readds_awaiting_response(&group_id, &self_installation_id)
765 .unwrap();
766
767 assert_eq!(result.len(), 2);
769
770 let returned_installations: Vec<Vec<u8>> =
772 result.iter().map(|r| r.installation_id.clone()).collect();
773 assert!(returned_installations.contains(&other_installation_id_1));
774 assert!(returned_installations.contains(&other_installation_id_2));
775
776 for status in result {
778 assert_eq!(status.group_id.as_slice(), group_id.as_slice());
779 assert_ne!(status.installation_id, self_installation_id);
780 assert!(status.requested_at_sequence_id.is_some());
781
782 let requested_at = status.requested_at_sequence_id.unwrap();
784 let responded_at = status.responded_at_sequence_id.unwrap_or(0);
785 assert!(requested_at >= responded_at);
786 }
787 })
788 }
789}