1use super::ConnectionExt;
2use super::group::ConversationType;
3use super::schema::groups;
4use super::{
5 Sqlite,
6 db_connection::DbConnection,
7 schema::{
8 group_messages::{self, dsl},
9 groups::dsl as groups_dsl,
10 },
11};
12use crate::impl_fetch;
13use derive_builder::Builder;
14use diesel::{
15 backend::Backend,
16 deserialize::{self, FromSql, FromSqlRow},
17 dsl::sql as diesel_sql,
18 expression::AsExpression,
19 prelude::*,
20 serialize::{self, IsNull, Output, ToSql},
21 sql_types::Integer,
22};
23use serde::{Deserialize, Serialize};
24use std::collections::HashMap;
25use xmtp_common::{NS_IN_DAY, time::now_ns};
26use xmtp_content_types::{
27 actions, attachment, delete_message, group_updated, intent, leave_request, markdown,
28 membership_change, multi_remote_attachment, reaction, read_receipt, remote_attachment, reply,
29 text, transaction_reference, wallet_send_calls,
30};
31use xmtp_proto::types::{Cursor, GroupId};
32
33mod convert;
34#[cfg(test)]
35pub mod messages_newer_than_tests;
36#[cfg(test)]
37pub mod tests;
38
39#[derive(
40 Debug, Clone, Serialize, Deserialize, Queryable, Selectable, Identifiable, Eq, PartialEq,
41)]
42#[diesel(table_name = group_messages)]
43#[diesel(primary_key(id))]
44#[diesel(check_for_backend(Sqlite))]
45pub struct StoredGroupMessage {
47 pub id: Vec<u8>,
49 pub group_id: GroupId,
51 pub decrypted_message_bytes: Vec<u8>,
53 pub sent_at_ns: i64,
55 pub kind: GroupMessageKind,
57 pub sender_installation_id: Vec<u8>,
59 pub sender_inbox_id: String,
61 pub delivery_status: DeliveryStatus,
63 pub content_type: ContentType,
65 pub version_major: i32,
67 pub version_minor: i32,
69 pub authority_id: String,
71 pub reference_id: Option<Vec<u8>>,
73 pub sequence_id: i64,
75 pub envelope_hash: Option<Vec<u8>>,
77 pub expiry_ns: Option<i64>,
79 pub inserted_at_ns: i64,
82 pub expire_at_ns: Option<i64>,
84 pub should_push: bool,
86 pub idempotency_key: String,
89}
90
91impl StoredGroupMessage {
92 pub fn cursor(&self) -> Cursor {
93 Cursor(self.sequence_id as u64)
94 }
95}
96
97#[derive(Debug, Clone, Insertable)]
99#[diesel(table_name = group_messages)]
100struct NewStoredGroupMessage {
101 pub id: Vec<u8>,
102 pub group_id: GroupId,
103 pub decrypted_message_bytes: Vec<u8>,
104 pub sent_at_ns: i64,
105 pub kind: GroupMessageKind,
106 pub sender_installation_id: Vec<u8>,
107 pub sender_inbox_id: String,
108 pub delivery_status: DeliveryStatus,
109 pub content_type: ContentType,
110 pub version_major: i32,
111 pub version_minor: i32,
112 pub authority_id: String,
113 pub reference_id: Option<Vec<u8>>,
114 pub sequence_id: i64,
115 pub envelope_hash: Option<Vec<u8>>,
117 pub expiry_ns: Option<i64>,
119 pub expire_at_ns: Option<i64>,
121 pub should_push: bool,
122 pub idempotency_key: String,
123}
124
125impl From<&StoredGroupMessage> for NewStoredGroupMessage {
126 fn from(msg: &StoredGroupMessage) -> Self {
127 Self {
128 id: msg.id.clone(),
129 group_id: msg.group_id,
130 decrypted_message_bytes: msg.decrypted_message_bytes.clone(),
131 sent_at_ns: msg.sent_at_ns,
132 kind: msg.kind,
133 sender_installation_id: msg.sender_installation_id.clone(),
134 sender_inbox_id: msg.sender_inbox_id.clone(),
135 delivery_status: msg.delivery_status,
136 content_type: msg.content_type,
137 version_major: msg.version_major,
138 version_minor: msg.version_minor,
139 authority_id: msg.authority_id.clone(),
140 reference_id: msg.reference_id.clone(),
141 sequence_id: msg.sequence_id,
142 envelope_hash: msg.envelope_hash.clone(),
143 expiry_ns: msg.expiry_ns,
144 expire_at_ns: msg.expire_at_ns,
145 should_push: msg.should_push,
146 idempotency_key: msg.idempotency_key.clone(),
147 }
148 }
149}
150
151pub struct StoredGroupMessageWithReactions {
152 pub message: StoredGroupMessage,
153 pub reactions: Vec<StoredGroupMessage>,
155}
156
157#[derive(Clone, Debug, PartialEq, Default)]
158pub enum SortDirection {
159 #[default]
160 Ascending,
161 Descending,
162}
163
164#[derive(Clone, Debug, PartialEq, Default)]
165pub enum SortBy {
166 #[default]
167 SentAt,
168 InsertedAt,
169}
170
171#[repr(i32)]
172#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq, AsExpression, FromSqlRow)]
173#[diesel(sql_type = Integer)]
174pub enum GroupMessageKind {
175 Application = 1,
176 MembershipChange = 2,
177}
178
179impl ToSql<Integer, Sqlite> for GroupMessageKind
180where
181 i32: ToSql<Integer, Sqlite>,
182{
183 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
184 out.set_value(*self as i32);
185 Ok(IsNull::No)
186 }
187}
188
189impl FromSql<Integer, Sqlite> for GroupMessageKind
190where
191 i32: FromSql<Integer, Sqlite>,
192{
193 fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
194 match i32::from_sql(bytes)? {
195 1 => Ok(GroupMessageKind::Application),
196 2 => Ok(GroupMessageKind::MembershipChange),
197 x => Err(format!("Unrecognized variant {}", x).into()),
198 }
199 }
200}
201
202pub trait Deletable {
204 fn is_deletable(&self) -> bool;
206}
207
208impl Deletable for GroupMessageKind {
209 fn is_deletable(&self) -> bool {
210 match self {
211 GroupMessageKind::Application => true,
213 GroupMessageKind::MembershipChange => false,
215 }
216 }
217}
218
219#[repr(i32)]
221#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, FromSqlRow, AsExpression)]
222#[diesel(sql_type = diesel::sql_types::Integer)]
223pub enum ContentType {
224 Unknown = 0,
225 Text = 1,
226 GroupMembershipChange = 2,
227 GroupUpdated = 3,
228 Reaction = 4,
229 ReadReceipt = 5,
230 Reply = 6,
231 Attachment = 7,
232 RemoteAttachment = 8,
233 TransactionReference = 9,
234 WalletSendCalls = 10,
235 LeaveRequest = 11,
236 Markdown = 12,
237 Actions = 13,
238 Intent = 14,
239 MultiRemoteAttachment = 15,
240 DeleteMessage = 16,
241}
242
243impl ContentType {
244 pub fn all() -> Vec<ContentType> {
245 vec![
246 ContentType::Unknown,
247 ContentType::Text,
248 ContentType::GroupMembershipChange,
249 ContentType::GroupUpdated,
250 ContentType::Reaction,
251 ContentType::ReadReceipt,
252 ContentType::Reply,
253 ContentType::Attachment,
254 ContentType::RemoteAttachment,
255 ContentType::TransactionReference,
256 ContentType::WalletSendCalls,
257 ContentType::LeaveRequest,
258 ContentType::Markdown,
259 ContentType::Actions,
260 ContentType::Intent,
261 ContentType::MultiRemoteAttachment,
262 ContentType::DeleteMessage,
263 ]
264 }
265}
266
267impl Deletable for ContentType {
268 fn is_deletable(&self) -> bool {
269 match self {
270 ContentType::GroupMembershipChange
271 | ContentType::GroupUpdated
272 | ContentType::LeaveRequest
273 | ContentType::Reaction
274 | ContentType::ReadReceipt
275 | ContentType::Actions
276 | ContentType::Intent
277 | ContentType::DeleteMessage
278 |ContentType::Unknown => false,
280
281 ContentType::Text
282 | ContentType::Markdown
283 | ContentType::Reply
284 | ContentType::Attachment
285 | ContentType::RemoteAttachment
286 | ContentType::TransactionReference
287 | ContentType::MultiRemoteAttachment
288 | ContentType::WalletSendCalls => true,
289 }
290 }
291}
292
293impl std::fmt::Display for ContentType {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 let as_string = match self {
296 Self::Unknown => "unknown",
297 Self::Text => text::TextCodec::TYPE_ID,
298 Self::Markdown => markdown::MarkdownCodec::TYPE_ID,
299 Self::GroupMembershipChange => membership_change::GroupMembershipChangeCodec::TYPE_ID,
300 Self::GroupUpdated => group_updated::GroupUpdatedCodec::TYPE_ID,
301 Self::Reaction => reaction::ReactionCodec::TYPE_ID,
302 Self::ReadReceipt => read_receipt::ReadReceiptCodec::TYPE_ID,
303 Self::Attachment => attachment::AttachmentCodec::TYPE_ID,
304 Self::RemoteAttachment => remote_attachment::RemoteAttachmentCodec::TYPE_ID,
305 Self::Reply => reply::ReplyCodec::TYPE_ID,
306 Self::TransactionReference => transaction_reference::TransactionReferenceCodec::TYPE_ID,
307 Self::WalletSendCalls => wallet_send_calls::WalletSendCallsCodec::TYPE_ID,
308 Self::LeaveRequest => leave_request::LeaveRequestCodec::TYPE_ID,
309 Self::Actions => actions::ActionsCodec::TYPE_ID,
310 Self::Intent => intent::IntentCodec::TYPE_ID,
311 Self::MultiRemoteAttachment => {
312 multi_remote_attachment::MultiRemoteAttachmentCodec::TYPE_ID
313 }
314 Self::DeleteMessage => delete_message::DeleteMessageCodec::TYPE_ID,
315 };
316
317 write!(f, "{}", as_string)
318 }
319}
320
321impl From<String> for ContentType {
322 fn from(type_id: String) -> Self {
323 match type_id.as_str() {
324 text::TextCodec::TYPE_ID => Self::Text,
325 markdown::MarkdownCodec::TYPE_ID => Self::Markdown,
326 membership_change::GroupMembershipChangeCodec::TYPE_ID => Self::GroupMembershipChange,
327 group_updated::GroupUpdatedCodec::TYPE_ID => Self::GroupUpdated,
328 reaction::ReactionCodec::TYPE_ID => Self::Reaction,
329 read_receipt::ReadReceiptCodec::TYPE_ID => Self::ReadReceipt,
330 reply::ReplyCodec::TYPE_ID => Self::Reply,
331 attachment::AttachmentCodec::TYPE_ID => Self::Attachment,
332 remote_attachment::RemoteAttachmentCodec::TYPE_ID => Self::RemoteAttachment,
333 transaction_reference::TransactionReferenceCodec::TYPE_ID => Self::TransactionReference,
334 wallet_send_calls::WalletSendCallsCodec::TYPE_ID => Self::WalletSendCalls,
335 leave_request::LeaveRequestCodec::TYPE_ID => Self::LeaveRequest,
336 actions::ActionsCodec::TYPE_ID => Self::Actions,
337 intent::IntentCodec::TYPE_ID => Self::Intent,
338 multi_remote_attachment::MultiRemoteAttachmentCodec::TYPE_ID => {
339 Self::MultiRemoteAttachment
340 }
341 delete_message::DeleteMessageCodec::TYPE_ID => Self::DeleteMessage,
342 _ => Self::Unknown,
343 }
344 }
345}
346
347impl ToSql<Integer, Sqlite> for ContentType
348where
349 i32: ToSql<Integer, Sqlite>,
350{
351 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
352 out.set_value(*self as i32);
353 Ok(IsNull::No)
354 }
355}
356
357impl FromSql<Integer, Sqlite> for ContentType
358where
359 i32: FromSql<Integer, Sqlite>,
360{
361 fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
362 match i32::from_sql(bytes)? {
363 0 => Ok(ContentType::Unknown),
364 1 => Ok(ContentType::Text),
365 2 => Ok(ContentType::GroupMembershipChange),
366 3 => Ok(ContentType::GroupUpdated),
367 4 => Ok(ContentType::Reaction),
368 5 => Ok(ContentType::ReadReceipt),
369 6 => Ok(ContentType::Reply),
370 7 => Ok(ContentType::Attachment),
371 8 => Ok(ContentType::RemoteAttachment),
372 9 => Ok(ContentType::TransactionReference),
373 10 => Ok(ContentType::WalletSendCalls),
374 11 => Ok(ContentType::LeaveRequest),
375 12 => Ok(ContentType::Markdown),
376 13 => Ok(ContentType::Actions),
377 14 => Ok(ContentType::Intent),
378 15 => Ok(ContentType::MultiRemoteAttachment),
379 16 => Ok(ContentType::DeleteMessage),
380 x => Err(format!("Unrecognized variant {}", x).into()),
381 }
382 }
383}
384
385#[repr(i32)]
386#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq, FromSqlRow, AsExpression)]
387#[diesel(sql_type = Integer)]
388pub enum DeliveryStatus {
389 Unpublished = 1,
390 Published = 2,
391 Failed = 3,
392}
393
394impl ToSql<Integer, Sqlite> for DeliveryStatus
395where
396 i32: ToSql<Integer, Sqlite>,
397{
398 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
399 out.set_value(*self as i32);
400 Ok(IsNull::No)
401 }
402}
403
404impl FromSql<Integer, Sqlite> for DeliveryStatus
405where
406 i32: FromSql<Integer, Sqlite>,
407{
408 fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
409 match i32::from_sql(bytes)? {
410 1 => Ok(DeliveryStatus::Unpublished),
411 2 => Ok(DeliveryStatus::Published),
412 3 => Ok(DeliveryStatus::Failed),
413 x => Err(format!("Unrecognized variant {}", x).into()),
414 }
415 }
416}
417
418impl_fetch!(StoredGroupMessage, group_messages, Vec<u8>, select);
419
420impl<C> crate::Store<C> for StoredGroupMessage
422where
423 C: crate::ConnectionExt,
424{
425 type Output = ();
426 fn store(&self, into: &C) -> Result<(), crate::StorageError> {
427 let new_msg = NewStoredGroupMessage::from(self);
428 super::stream_storage::stream_transaction(into, |conn| {
429 diesel::insert_into(group_messages::table)
430 .values(&new_msg)
431 .execute(conn)?;
432 super::delivery::assign_sequence(conn, &self.id)?;
433 Ok(())
434 })
435 }
436}
437
438impl<C> crate::StoreOrIgnore<C> for StoredGroupMessage
440where
441 C: crate::ConnectionExt,
442{
443 type Output = ();
444
445 fn store_or_ignore(&self, into: &C) -> Result<(), crate::StorageError> {
446 let new_msg = NewStoredGroupMessage::from(self);
447 super::stream_storage::stream_transaction(into, |conn| {
448 diesel::insert_or_ignore_into(group_messages::table)
449 .values(&new_msg)
450 .execute(conn)?;
451 super::delivery::assign_sequence(conn, &self.id)?;
452 Ok(())
453 })
454 }
455}
456
457#[derive(Default, Clone, Builder, Debug)]
458#[builder(setter(into))]
459pub struct MsgQueryArgs {
460 #[builder(default = None)]
461 pub sent_after_ns: Option<i64>,
462 #[builder(default = None)]
463 pub sent_before_ns: Option<i64>,
464 #[builder(default = None)]
465 pub kind: Option<GroupMessageKind>,
466 #[builder(default = None)]
467 pub delivery_status: Option<DeliveryStatus>,
468 #[builder(default = None)]
469 pub limit: Option<i64>,
470 #[builder(default = None)]
471 pub direction: Option<SortDirection>,
472 #[builder(default = None)]
473 pub content_types: Option<Vec<ContentType>>,
474 #[builder(default = None)]
475 pub exclude_content_types: Option<Vec<ContentType>>,
476 #[builder(default = None)]
477 pub exclude_sender_inbox_ids: Option<Vec<String>>,
478 #[builder(default = None)]
479 pub sort_by: Option<SortBy>,
480 #[builder(default = None)]
481 pub inserted_after_ns: Option<i64>,
482 #[builder(default = None)]
483 pub inserted_before_ns: Option<i64>,
484 #[builder(default = false)]
485 pub exclude_disappearing: bool,
486}
487
488impl MsgQueryArgs {
489 pub fn builder() -> MsgQueryArgsBuilder {
490 MsgQueryArgsBuilder::default()
491 }
492}
493
494#[derive(Default, Clone, Builder)]
495pub struct RelationQuery {
496 #[builder(default = None)]
497 pub content_types: Option<Vec<ContentType>>,
498 #[builder(default = None)]
499 pub limit: Option<i64>,
500 #[builder(default = SortDirection::Ascending)]
501 pub direction: SortDirection,
502}
503
504impl RelationQuery {
505 pub fn builder() -> RelationQueryBuilder {
506 RelationQueryBuilder::default()
507 }
508}
509
510pub type InboundRelations = HashMap<Vec<u8>, Vec<StoredGroupMessage>>;
511pub type OutboundRelations = HashMap<Vec<u8>, StoredGroupMessage>;
512pub type RelationCounts = HashMap<Vec<u8>, usize>;
513
514pub struct MessagesWithRelations {
515 pub messages: Vec<StoredGroupMessage>,
516 pub outbound_relations: HashMap<Vec<u8>, StoredGroupMessage>,
518 pub inbound_relations: HashMap<Vec<u8>, Vec<StoredGroupMessage>>,
520}
521
522pub type LatestMessageTimeBySender = HashMap<String, i64>;
523
524pub trait QueryGroupMessage {
525 fn get_group_messages(
527 &self,
528 group_id: &GroupId,
529 args: &MsgQueryArgs,
530 ) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError>;
531
532 fn count_group_messages(
534 &self,
535 group_id: &GroupId,
536 args: &MsgQueryArgs,
537 ) -> Result<i64, crate::ConnectionError>;
538
539 fn missing_messages(
544 &self,
545 group_id: &GroupId,
546 sequence_ids: &[u64],
547 ) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError>;
548
549 fn group_messages_paged(
550 &self,
551 args: &MsgQueryArgs,
552 offset: i64,
553 ) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError>;
554
555 fn get_group_messages_with_reactions(
557 &self,
558 group_id: &GroupId,
559 args: &MsgQueryArgs,
560 ) -> Result<Vec<StoredGroupMessageWithReactions>, crate::ConnectionError>;
561
562 fn get_inbound_relations(
563 &self,
564 group_id: &GroupId,
565 message_ids: &[&[u8]],
566 relation_query: RelationQuery,
567 ) -> Result<InboundRelations, crate::ConnectionError>;
568
569 fn get_outbound_relations(
570 &self,
571 group_id: &GroupId,
572 message_ids: &[&[u8]],
573 ) -> Result<OutboundRelations, crate::ConnectionError>;
574
575 fn get_inbound_relation_counts(
576 &self,
577 group_id: &GroupId,
578 message_ids: &[&[u8]],
579 relation_query: RelationQuery,
580 ) -> Result<RelationCounts, crate::ConnectionError>;
581
582 fn get_group_message<MessageId: AsRef<[u8]>>(
584 &self,
585 id: MessageId,
586 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError>;
587
588 fn get_latest_message_times_by_sender<Id: AsRef<[u8]>>(
589 &self,
590 group_id: Id,
591 allowed_content_types: &[ContentType],
592 ) -> Result<LatestMessageTimeBySender, crate::ConnectionError>;
593
594 fn write_conn_get_group_message<MessageId: AsRef<[u8]>>(
596 &self,
597 id: MessageId,
598 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError>;
599
600 fn get_group_message_by_timestamp<Id: AsRef<[u8]>>(
601 &self,
602 group_id: Id,
603 timestamp: i64,
604 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError>;
605
606 fn get_group_message_by_cursor<Id: AsRef<[u8]>>(
607 &self,
608 group_id: Id,
609 sequence_id: Cursor,
610 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError>;
611
612 fn set_delivery_status_to_published<MessageId: AsRef<[u8]>>(
613 &self,
614 msg_id: &MessageId,
615 timestamp: u64,
616 cursor: Cursor,
617 message_expire_at_ns: Option<i64>,
618 ) -> Result<usize, crate::StorageError>;
619
620 fn set_delivery_status_to_failed<MessageId: AsRef<[u8]>>(
621 &self,
622 msg_id: &MessageId,
623 ) -> Result<usize, crate::ConnectionError>;
624
625 fn delete_expired_messages(&self) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError>;
626
627 fn min_expire_at_ns(&self) -> Result<Option<i64>, crate::ConnectionError>;
634
635 fn delete_message_by_id<MessageId: AsRef<[u8]>>(
636 &self,
637 message_id: MessageId,
638 ) -> Result<usize, crate::ConnectionError>;
639
640 fn messages_newer_than(
644 &self,
645 cursors_by_group: &HashMap<Vec<u8>, xmtp_proto::types::Cursor>,
646 ) -> Result<Vec<(GroupId, Cursor)>, crate::ConnectionError>;
647
648 fn clear_messages(
657 &self,
658 group_ids: Option<&[GroupId]>,
659 retention_days: Option<u32>,
660 ) -> Result<usize, crate::ConnectionError>;
661}
662
663impl<T> QueryGroupMessage for &T
664where
665 T: QueryGroupMessage,
666{
667 fn get_group_messages(
669 &self,
670 group_id: &GroupId,
671 args: &MsgQueryArgs,
672 ) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError> {
673 (**self).get_group_messages(group_id, args)
674 }
675
676 fn count_group_messages(
678 &self,
679 group_id: &GroupId,
680 args: &MsgQueryArgs,
681 ) -> Result<i64, crate::ConnectionError> {
682 (**self).count_group_messages(group_id, args)
683 }
684
685 fn missing_messages(
686 &self,
687 group_id: &GroupId,
688 sequence_ids: &[u64],
689 ) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError> {
690 (**self).missing_messages(group_id, sequence_ids)
691 }
692
693 fn group_messages_paged(
694 &self,
695 args: &MsgQueryArgs,
696 offset: i64,
697 ) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError> {
698 (**self).group_messages_paged(args, offset)
699 }
700
701 fn get_group_messages_with_reactions(
703 &self,
704 group_id: &GroupId,
705 args: &MsgQueryArgs,
706 ) -> Result<Vec<StoredGroupMessageWithReactions>, crate::ConnectionError> {
707 (**self).get_group_messages_with_reactions(group_id, args)
708 }
709
710 fn get_inbound_relations(
711 &self,
712 group_id: &GroupId,
713 message_ids: &[&[u8]],
714 relation_query: RelationQuery,
715 ) -> Result<InboundRelations, crate::ConnectionError> {
716 (**self).get_inbound_relations(group_id, message_ids, relation_query)
717 }
718
719 fn get_outbound_relations(
720 &self,
721 group_id: &GroupId,
722 message_ids: &[&[u8]],
723 ) -> Result<OutboundRelations, crate::ConnectionError> {
724 (**self).get_outbound_relations(group_id, message_ids)
725 }
726
727 fn get_inbound_relation_counts(
728 &self,
729 group_id: &GroupId,
730 message_ids: &[&[u8]],
731 relation_query: RelationQuery,
732 ) -> Result<RelationCounts, crate::ConnectionError> {
733 (**self).get_inbound_relation_counts(group_id, message_ids, relation_query)
734 }
735
736 fn get_latest_message_times_by_sender<Id: AsRef<[u8]>>(
737 &self,
738 group_id: Id,
739 allowed_content_types: &[ContentType],
740 ) -> Result<LatestMessageTimeBySender, crate::ConnectionError> {
741 (**self).get_latest_message_times_by_sender(group_id, allowed_content_types)
742 }
743
744 fn get_group_message<MessageId: AsRef<[u8]>>(
746 &self,
747 id: MessageId,
748 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError> {
749 (**self).get_group_message(id)
750 }
751
752 fn write_conn_get_group_message<MessageId: AsRef<[u8]>>(
754 &self,
755 id: MessageId,
756 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError> {
757 (**self).write_conn_get_group_message(id)
758 }
759
760 fn get_group_message_by_timestamp<Id: AsRef<[u8]>>(
761 &self,
762 group_id: Id,
763 timestamp: i64,
764 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError> {
765 (**self).get_group_message_by_timestamp(group_id, timestamp)
766 }
767
768 fn get_group_message_by_cursor<Id: AsRef<[u8]>>(
769 &self,
770 group_id: Id,
771 cursor: Cursor,
772 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError> {
773 (**self).get_group_message_by_cursor(group_id, cursor)
774 }
775
776 fn set_delivery_status_to_published<MessageId: AsRef<[u8]>>(
777 &self,
778 msg_id: &MessageId,
779 timestamp: u64,
780 cursor: Cursor,
781 message_expire_at_ns: Option<i64>,
782 ) -> Result<usize, crate::StorageError> {
783 (**self).set_delivery_status_to_published(msg_id, timestamp, cursor, message_expire_at_ns)
784 }
785
786 fn set_delivery_status_to_failed<MessageId: AsRef<[u8]>>(
787 &self,
788 msg_id: &MessageId,
789 ) -> Result<usize, crate::ConnectionError> {
790 (**self).set_delivery_status_to_failed(msg_id)
791 }
792
793 fn delete_expired_messages(&self) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError> {
794 (**self).delete_expired_messages()
795 }
796
797 fn min_expire_at_ns(&self) -> Result<Option<i64>, crate::ConnectionError> {
798 (**self).min_expire_at_ns()
799 }
800
801 fn delete_message_by_id<MessageId: AsRef<[u8]>>(
802 &self,
803 message_id: MessageId,
804 ) -> Result<usize, crate::ConnectionError> {
805 (**self).delete_message_by_id(message_id)
806 }
807
808 fn messages_newer_than(
809 &self,
810 cursors_by_group: &HashMap<Vec<u8>, xmtp_proto::types::Cursor>,
811 ) -> Result<Vec<(GroupId, Cursor)>, crate::ConnectionError> {
812 (**self).messages_newer_than(cursors_by_group)
813 }
814
815 fn clear_messages(
816 &self,
817 group_ids: Option<&[GroupId]>,
818 retention_days: Option<u32>,
819 ) -> Result<usize, crate::ConnectionError> {
820 (**self).clear_messages(group_ids, retention_days)
821 }
822}
823
824macro_rules! apply_message_filters {
826 ($query:expr, $args:expr) => {{
827 let mut query = $query;
828
829 if let Some(sent_after) = $args.sent_after_ns {
830 query = query.filter(dsl::sent_at_ns.gt(sent_after));
831 }
832
833 if let Some(sent_before) = $args.sent_before_ns {
834 query = query.filter(dsl::sent_at_ns.lt(sent_before));
835 }
836
837 if let Some(kind) = $args.kind {
838 query = query.filter(dsl::kind.eq(kind));
839 }
840
841 if let Some(status) = $args.delivery_status {
842 query = query.filter(dsl::delivery_status.eq(status));
843 }
844
845 if let Some(content_types) = &$args.content_types {
846 query = query.filter(dsl::content_type.eq_any(content_types));
847 }
848
849 if let Some(exclude_content_types) = &$args.exclude_content_types {
850 query = query.filter(dsl::content_type.ne_all(exclude_content_types));
851 }
852
853 if let Some(exclude_sender_inbox_ids) = &$args.exclude_sender_inbox_ids {
854 query = query.filter(dsl::sender_inbox_id.ne_all(exclude_sender_inbox_ids));
855 }
856
857 if let Some(inserted_after_ns) = $args.inserted_after_ns {
858 query = query.filter(dsl::inserted_at_ns.gt(inserted_after_ns));
859 }
860
861 if let Some(inserted_before_ns) = $args.inserted_before_ns {
862 query = query.filter(dsl::inserted_at_ns.lt(inserted_before_ns));
863 }
864
865 let current_time = now_ns();
867 query = query.filter(
868 dsl::expire_at_ns
869 .is_null()
870 .or(dsl::expire_at_ns.gt(current_time)),
871 );
872
873 query
874 }};
875}
876
877impl<C: ConnectionExt> QueryGroupMessage for DbConnection<C> {
878 #[xmtp_common::db_span]
880 fn get_group_messages(
881 &self,
882 group_id: &GroupId,
883 args: &MsgQueryArgs,
884 ) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError> {
885 use crate::schema::group_messages::dsl;
886
887 let mut query = dsl::group_messages
889 .filter(group_id_filter(group_id.as_ref()))
890 .into_boxed();
891
892 query = apply_message_filters!(query, args);
894
895 query = match (
897 args.sort_by.clone().unwrap_or_default(),
898 args.direction.clone().unwrap_or_default(),
899 ) {
900 (SortBy::SentAt, SortDirection::Ascending) => {
901 query.order((dsl::sent_at_ns.asc(), diesel_sql::<Integer>("rowid").asc()))
902 }
903 (SortBy::SentAt, SortDirection::Descending) => query.order((
904 dsl::sent_at_ns.desc(),
905 diesel_sql::<Integer>("rowid").desc(),
906 )),
907 (SortBy::InsertedAt, SortDirection::Ascending) => query.order((
908 dsl::inserted_at_ns.asc(),
909 diesel_sql::<Integer>("rowid").asc(),
910 )),
911 (SortBy::InsertedAt, SortDirection::Descending) => query.order((
912 dsl::inserted_at_ns.desc(),
913 diesel_sql::<Integer>("rowid").desc(),
914 )),
915 };
916
917 if let Some(limit) = args.limit {
918 query = query.limit(limit);
919 }
920
921 self.raw_query(|conn| {
922 query
923 .select(StoredGroupMessage::as_select())
924 .load::<StoredGroupMessage>(conn)
925 })
926 }
927
928 #[xmtp_common::db_span]
930 fn count_group_messages(
931 &self,
932 group_id: &GroupId,
933 args: &MsgQueryArgs,
934 ) -> Result<i64, crate::ConnectionError> {
935 use crate::schema::{group_messages::dsl, groups::dsl as groups_dsl};
936
937 let is_dm = self.raw_query(|conn| {
939 groups_dsl::groups
940 .filter(groups_dsl::id.eq(group_id))
941 .select(groups_dsl::conversation_type)
942 .first::<ConversationType>(conn)
943 })? == ConversationType::Dm;
944
945 let include_group_updated = args
946 .content_types
947 .as_ref()
948 .map(|types| types.contains(&ContentType::GroupUpdated))
949 .unwrap_or(false);
950
951 let mut query = dsl::group_messages
953 .filter(group_id_filter(group_id.as_ref()))
954 .into_boxed();
955
956 if is_dm && !include_group_updated {
963 query = query.filter(dsl::content_type.ne(ContentType::GroupUpdated));
964 }
965
966 query = apply_message_filters!(query, args);
968
969 let count =
970 self.raw_query(|conn| query.select(diesel::dsl::count_star()).first::<i64>(conn))?;
971
972 Ok(count)
973 }
974
975 #[xmtp_common::db_span]
976 fn missing_messages(
977 &self,
978 group_id: &GroupId,
979 sequence_ids: &[u64],
980 ) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError> {
981 use crate::schema::group_messages::{self, dsl};
982 use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
983
984 let sequence_ids: Vec<i64> = sequence_ids.iter().copied().map(|id| id as i64).collect();
985 let query = dsl::group_messages
986 .filter(dsl::group_id.eq(group_id))
987 .filter(dsl::sequence_id.is_not_null())
988 .filter(group_messages::sequence_id.ne_all(sequence_ids))
989 .filter(group_messages::kind.eq(GroupMessageKind::Application))
990 .order(group_messages::sequence_id.asc());
991
992 self.raw_query(|conn| query.select(StoredGroupMessage::as_select()).load(conn))
993 }
994
995 #[xmtp_common::db_span]
996 fn group_messages_paged(
997 &self,
998 args: &MsgQueryArgs,
999 offset: i64,
1000 ) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError> {
1001 let MsgQueryArgs {
1002 sent_after_ns,
1003 sent_before_ns,
1004 limit,
1005 exclude_disappearing,
1006 ..
1007 } = args;
1008
1009 let mut query = group_messages::table
1010 .left_join(groups::table)
1011 .filter(groups::conversation_type.ne_all(ConversationType::virtual_types()))
1012 .filter(group_messages::kind.eq(GroupMessageKind::Application))
1013 .order_by(group_messages::id)
1014 .into_boxed();
1015
1016 if let Some(start_ns) = sent_after_ns {
1017 query = query.filter(group_messages::sent_at_ns.gt(start_ns));
1018 }
1019 if let Some(end_ns) = sent_before_ns {
1020 query = query.filter(group_messages::sent_at_ns.le(end_ns));
1021 }
1022 if *exclude_disappearing {
1023 query = query.filter(group_messages::expire_at_ns.is_null());
1024 } else {
1025 let current_time = now_ns();
1027 query = query.filter(
1028 group_messages::expire_at_ns
1029 .is_null()
1030 .or(group_messages::expire_at_ns.gt(current_time)),
1031 );
1032 }
1033
1034 query = query.limit(limit.unwrap_or(100)).offset(offset);
1035
1036 self.raw_query(|conn| {
1037 query
1038 .select(StoredGroupMessage::as_select())
1039 .load::<StoredGroupMessage>(conn)
1040 })
1041 }
1042
1043 #[xmtp_common::db_span]
1045 fn get_group_messages_with_reactions(
1046 &self,
1047 group_id: &GroupId,
1048 args: &MsgQueryArgs,
1049 ) -> Result<Vec<StoredGroupMessageWithReactions>, crate::ConnectionError> {
1050 let mut modified_args = args.clone();
1052 let content_types = match modified_args.content_types.clone() {
1054 Some(content_types) => {
1055 let mut content_types = content_types.clone();
1056 content_types.retain(|content_type| *content_type != ContentType::Reaction);
1057 Some(content_types)
1058 }
1059 None => Some(vec![
1060 ContentType::Text,
1061 ContentType::GroupMembershipChange,
1062 ContentType::GroupUpdated,
1063 ContentType::ReadReceipt,
1064 ContentType::Reply,
1065 ContentType::Attachment,
1066 ContentType::RemoteAttachment,
1067 ContentType::TransactionReference,
1068 ContentType::Unknown,
1069 ]),
1070 };
1071
1072 modified_args.content_types = content_types;
1073 let messages = self.get_group_messages(group_id, &modified_args)?;
1074
1075 let message_ids: Vec<&[u8]> = messages.iter().map(|m| m.id.as_slice()).collect();
1077
1078 let mut reactions_query = dsl::group_messages
1079 .filter(group_id_filter(group_id.as_ref()))
1080 .filter(dsl::reference_id.is_not_null())
1081 .filter(dsl::reference_id.eq_any(message_ids))
1082 .into_boxed();
1083
1084 reactions_query = match args.direction.as_ref().unwrap_or(&SortDirection::Ascending) {
1086 SortDirection::Ascending => reactions_query.order(dsl::sent_at_ns.asc()),
1087 SortDirection::Descending => reactions_query.order(dsl::sent_at_ns.desc()),
1088 };
1089
1090 let reactions: Vec<StoredGroupMessage> = self.raw_query(|conn| {
1091 reactions_query
1092 .select(StoredGroupMessage::as_select())
1093 .load::<StoredGroupMessage>(conn)
1094 })?;
1095
1096 let mut reactions_by_reference: HashMap<Vec<u8>, Vec<StoredGroupMessage>> = HashMap::new();
1098
1099 for reaction in reactions {
1100 if let Some(reference_id) = &reaction.reference_id {
1101 reactions_by_reference
1102 .entry(reference_id.clone())
1103 .or_default()
1104 .push(reaction);
1105 }
1106 }
1107
1108 let messages_with_reactions: Vec<StoredGroupMessageWithReactions> = messages
1110 .into_iter()
1111 .map(|message| {
1112 let message_clone = message.clone();
1113 StoredGroupMessageWithReactions {
1114 message,
1115 reactions: reactions_by_reference
1116 .remove(&message_clone.id)
1117 .unwrap_or_default(),
1118 }
1119 })
1120 .collect();
1121
1122 Ok(messages_with_reactions)
1123 }
1124
1125 #[xmtp_common::db_span]
1126 fn get_inbound_relations(
1127 &self,
1128 group_id: &GroupId,
1129 message_ids: &[&[u8]],
1130 relation_query: RelationQuery,
1131 ) -> Result<InboundRelations, crate::ConnectionError> {
1132 let mut inbound_relations: HashMap<Vec<u8>, Vec<StoredGroupMessage>> = HashMap::new();
1133
1134 let mut inbound_relations_query = dsl::group_messages
1135 .filter(group_id_filter(group_id.as_ref()))
1136 .filter(dsl::reference_id.is_not_null())
1137 .filter(dsl::reference_id.eq_any(message_ids))
1138 .into_boxed();
1139
1140 if relation_query.direction == SortDirection::Descending {
1141 inbound_relations_query = inbound_relations_query.order(dsl::sent_at_ns.desc());
1142 } else {
1143 inbound_relations_query = inbound_relations_query.order(dsl::sent_at_ns.asc());
1144 }
1145
1146 if let Some(content_types) = relation_query.content_types {
1147 inbound_relations_query =
1148 inbound_relations_query.filter(dsl::content_type.eq_any(content_types));
1149 }
1150
1151 if let Some(limit) = relation_query.limit {
1152 inbound_relations_query = inbound_relations_query.limit(limit);
1153 }
1154
1155 let raw_inbound_relations: Vec<StoredGroupMessage> = self.raw_query(|conn| {
1156 inbound_relations_query
1157 .select(StoredGroupMessage::as_select())
1158 .load::<StoredGroupMessage>(conn)
1159 })?;
1160
1161 for inbound_reference in raw_inbound_relations {
1162 if let Some(reference_id) = &inbound_reference.reference_id {
1163 inbound_relations
1164 .entry(reference_id.clone())
1165 .or_default()
1166 .push(inbound_reference);
1167 }
1168 }
1169
1170 Ok(inbound_relations)
1171 }
1172
1173 #[xmtp_common::db_span]
1174 fn get_outbound_relations(
1175 &self,
1176 group_id: &GroupId,
1177 reference_ids: &[&[u8]],
1178 ) -> Result<OutboundRelations, crate::ConnectionError> {
1179 let outbound_references_query = dsl::group_messages
1180 .filter(group_id_filter(group_id.as_ref()))
1181 .filter(dsl::id.eq_any(reference_ids))
1182 .into_boxed();
1183
1184 let raw_outbound_references: Vec<StoredGroupMessage> = self.raw_query(|conn| {
1185 outbound_references_query
1186 .select(StoredGroupMessage::as_select())
1187 .load::<StoredGroupMessage>(conn)
1188 })?;
1189
1190 Ok(raw_outbound_references
1191 .into_iter()
1192 .map(|outbound| (outbound.id.clone(), outbound))
1193 .collect())
1194 }
1195
1196 #[xmtp_common::db_span]
1197 fn get_inbound_relation_counts(
1198 &self,
1199 group_id: &GroupId,
1200 message_ids: &[&[u8]],
1201 relation_query: RelationQuery,
1202 ) -> Result<RelationCounts, crate::ConnectionError> {
1203 let mut count_query = dsl::group_messages
1204 .filter(group_id_filter(group_id.as_ref()))
1205 .filter(dsl::reference_id.is_not_null())
1206 .filter(dsl::reference_id.eq_any(message_ids))
1207 .group_by(dsl::reference_id)
1208 .select((dsl::reference_id, diesel::dsl::count_star()))
1209 .into_boxed();
1210
1211 if let Some(content_types) = relation_query.content_types {
1212 count_query = count_query.filter(dsl::content_type.eq_any(content_types));
1213 }
1214
1215 let raw_counts: Vec<(Option<Vec<u8>>, i64)> =
1216 self.raw_query(|conn| count_query.load(conn))?;
1217
1218 Ok(raw_counts
1219 .into_iter()
1220 .filter_map(|(reference_id, count)| reference_id.map(|id| (id, count as usize)))
1221 .collect())
1222 }
1223
1224 #[xmtp_common::db_span]
1225 fn get_latest_message_times_by_sender<Id: AsRef<[u8]>>(
1226 &self,
1227 group_id: Id,
1228 allowed_content_types: &[ContentType],
1229 ) -> Result<LatestMessageTimeBySender, crate::ConnectionError> {
1230 let query = dsl::group_messages
1231 .filter(group_id_filter(group_id.as_ref()))
1232 .filter(dsl::content_type.eq_any(allowed_content_types))
1233 .group_by(dsl::sender_inbox_id)
1234 .select((dsl::sender_inbox_id, diesel::dsl::max(dsl::sent_at_ns)))
1235 .into_boxed();
1236
1237 let raw_results: Vec<(String, Option<i64>)> = self.raw_query(|conn| query.load(conn))?;
1238
1239 Ok(raw_results
1240 .into_iter()
1241 .filter_map(|(sender_inbox_id, max_sent_at_ns)| {
1242 max_sent_at_ns.map(|sent_at_ns| (sender_inbox_id, sent_at_ns))
1243 })
1244 .collect())
1245 }
1246
1247 fn get_group_message<MessageId: AsRef<[u8]>>(
1249 &self,
1250 id: MessageId,
1251 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError> {
1252 self.raw_query(|conn| {
1253 dsl::group_messages
1254 .filter(dsl::id.eq(id.as_ref()))
1255 .select(StoredGroupMessage::as_select())
1256 .first::<StoredGroupMessage>(conn)
1257 .optional()
1258 })
1259 }
1260
1261 fn write_conn_get_group_message<MessageId: AsRef<[u8]>>(
1263 &self,
1264 id: MessageId,
1265 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError> {
1266 self.raw_query(|conn| {
1267 dsl::group_messages
1268 .filter(dsl::id.eq(id.as_ref()))
1269 .select(StoredGroupMessage::as_select())
1270 .first::<StoredGroupMessage>(conn)
1271 .optional()
1272 })
1273 }
1274
1275 fn get_group_message_by_timestamp<Id: AsRef<[u8]>>(
1276 &self,
1277 group_id: Id,
1278 timestamp: i64,
1279 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError> {
1280 self.raw_query(|conn| {
1281 dsl::group_messages
1282 .filter(dsl::group_id.eq(group_id.as_ref()))
1283 .filter(dsl::sent_at_ns.eq(×tamp))
1284 .select(StoredGroupMessage::as_select())
1285 .first::<StoredGroupMessage>(conn)
1286 .optional()
1287 })
1288 }
1289
1290 fn get_group_message_by_cursor<Id: AsRef<[u8]>>(
1291 &self,
1292 group_id: Id,
1293 cursor: Cursor,
1294 ) -> Result<Option<StoredGroupMessage>, crate::ConnectionError> {
1295 self.raw_query(|conn| {
1296 dsl::group_messages
1297 .filter(dsl::group_id.eq(group_id.as_ref()))
1298 .filter(dsl::sequence_id.eq(cursor.0 as i64))
1299 .select(StoredGroupMessage::as_select())
1300 .first::<StoredGroupMessage>(conn)
1301 .optional()
1302 })
1303 }
1304
1305 fn set_delivery_status_to_published<MessageId: AsRef<[u8]>>(
1306 &self,
1307 msg_id: &MessageId,
1308 timestamp: u64,
1309 cursor: Cursor,
1310 message_expire_at_ns: Option<i64>,
1311 ) -> Result<usize, crate::StorageError> {
1312 tracing::info!(
1313 "Message [{}] published with cursor = {}",
1314 hex::encode(msg_id),
1315 cursor
1316 );
1317 super::stream_storage::stream_transaction(self, |conn| {
1318 let Some((group_id, previous_sent_at_ns)) = dsl::group_messages
1319 .filter(dsl::id.eq(msg_id.as_ref()))
1320 .select((dsl::group_id, dsl::sent_at_ns))
1321 .first::<(GroupId, i64)>(conn)
1322 .optional()?
1323 else {
1324 return Ok(0);
1325 };
1326 let changed = diesel::update(dsl::group_messages)
1327 .filter(dsl::id.eq(msg_id.as_ref()))
1328 .set((
1329 dsl::delivery_status.eq(DeliveryStatus::Published),
1330 dsl::sent_at_ns.eq(timestamp as i64),
1331 dsl::sequence_id.eq(cursor.0 as i64),
1332 dsl::expire_at_ns.eq(message_expire_at_ns),
1333 ))
1334 .execute(conn)?;
1335 if changed > 0 {
1336 let latest_sent_at_ns = dsl::group_messages
1337 .filter(dsl::group_id.eq(group_id))
1338 .order(dsl::sent_at_ns.desc())
1339 .select(dsl::sent_at_ns)
1340 .first::<i64>(conn)?;
1341 diesel::update(groups_dsl::groups)
1344 .filter(groups_dsl::id.eq(group_id))
1345 .filter(
1346 groups_dsl::last_message_ns
1347 .is_null()
1348 .or(groups_dsl::last_message_ns.eq(previous_sent_at_ns))
1349 .or(groups_dsl::last_message_ns.lt(latest_sent_at_ns)),
1350 )
1351 .set(groups_dsl::last_message_ns.eq(latest_sent_at_ns))
1352 .execute(conn)?;
1353 super::delivery::assign_sequence(conn, msg_id.as_ref())?;
1354 }
1355 Ok(changed)
1356 })
1357 }
1358
1359 fn set_delivery_status_to_failed<MessageId: AsRef<[u8]>>(
1360 &self,
1361 msg_id: &MessageId,
1362 ) -> Result<usize, crate::ConnectionError> {
1363 self.raw_query(|conn| {
1364 diesel::update(dsl::group_messages)
1365 .filter(dsl::id.eq(msg_id.as_ref()))
1366 .set((dsl::delivery_status.eq(DeliveryStatus::Failed),))
1367 .execute(conn)
1368 })
1369 }
1370
1371 #[xmtp_common::db_span]
1372 fn delete_expired_messages(&self) -> Result<Vec<StoredGroupMessage>, crate::ConnectionError> {
1373 self.raw_query(|conn| {
1374 use diesel::prelude::*;
1375 let now = now_ns();
1376
1377 diesel::delete(
1378 dsl::group_messages
1379 .filter(dsl::delivery_status.eq(DeliveryStatus::Published))
1380 .filter(dsl::kind.eq(GroupMessageKind::Application))
1381 .filter(dsl::expire_at_ns.is_not_null())
1382 .filter(dsl::expire_at_ns.le(now)),
1383 )
1384 .returning(StoredGroupMessage::as_returning())
1385 .load::<StoredGroupMessage>(conn)
1386 })
1387 }
1388
1389 #[xmtp_common::db_span]
1390 fn min_expire_at_ns(&self) -> Result<Option<i64>, crate::ConnectionError> {
1391 self.raw_query(|conn| {
1392 use diesel::dsl::min;
1393 use diesel::prelude::*;
1394 dsl::group_messages
1395 .filter(dsl::delivery_status.eq(DeliveryStatus::Published))
1396 .filter(dsl::kind.eq(GroupMessageKind::Application))
1397 .filter(dsl::expire_at_ns.is_not_null())
1398 .select(min(dsl::expire_at_ns))
1399 .first::<Option<i64>>(conn)
1400 })
1401 }
1402
1403 fn delete_message_by_id<MessageId: AsRef<[u8]>>(
1404 &self,
1405 message_id: MessageId,
1406 ) -> Result<usize, crate::ConnectionError> {
1407 self.raw_query(|conn| {
1408 use diesel::prelude::*;
1409 diesel::delete(dsl::group_messages.filter(dsl::id.eq(message_id.as_ref())))
1410 .execute(conn)
1411 })
1412 }
1413
1414 #[xmtp_common::db_span]
1415 fn messages_newer_than(
1416 &self,
1417 cursors_by_group: &HashMap<Vec<u8>, xmtp_proto::types::Cursor>,
1418 ) -> Result<Vec<(GroupId, Cursor)>, crate::ConnectionError> {
1419 const GROUPS_PER_QUERY: usize = 100;
1421 let groups: Vec<_> = cursors_by_group.iter().collect();
1422 let mut result = Vec::new();
1423 for batch in groups.chunks(GROUPS_PER_QUERY) {
1424 let mut filter = Box::new(dsl::group_id.eq(&[] as &[u8]))
1425 as Box<
1426 dyn BoxableExpression<
1427 group_messages::table,
1428 Sqlite,
1429 SqlType = diesel::sql_types::Bool,
1430 >,
1431 >;
1432 for (group, cursor) in batch {
1433 filter = Box::new(
1434 filter.or(dsl::group_id
1435 .eq(group)
1436 .and(dsl::sequence_id.gt(cursor.0 as i64))),
1437 );
1438 }
1439 let rows: Vec<(GroupId, i64)> = self.raw_query(|conn| {
1440 dsl::group_messages
1441 .select((dsl::group_id, dsl::sequence_id))
1442 .filter(filter)
1443 .load(conn)
1444 })?;
1445 result.extend(
1446 rows.into_iter()
1447 .map(|(group, sequence)| (group, Cursor(sequence as u64))),
1448 );
1449 }
1450 Ok(result)
1451 }
1452
1453 #[xmtp_common::db_span]
1454 fn clear_messages(
1455 &self,
1456 group_ids: Option<&[GroupId]>,
1457 retention_days: Option<u32>,
1458 ) -> Result<usize, crate::ConnectionError> {
1459 let mut query = diesel::delete(dsl::group_messages).into_boxed();
1460
1461 if let Some(group_ids) = group_ids {
1462 query = query.filter(dsl::group_id.eq_any(group_ids));
1463 }
1464
1465 if let Some(days) = retention_days {
1466 let limit = now_ns().saturating_sub(NS_IN_DAY.saturating_mul(i64::from(days)));
1467 query = query.filter(dsl::sent_at_ns.lt(limit));
1468 }
1469
1470 self.raw_query(|conn| query.execute(conn))
1471 }
1472}
1473
1474fn group_id_filter(
1475 group_id: &[u8],
1476) -> impl diesel::expression::BoxableExpression<
1477 group_messages::table,
1478 diesel::sqlite::Sqlite,
1479 SqlType = diesel::sql_types::Bool,
1480> + diesel::expression::NonAggregate {
1481 dsl::group_id.eq_any(
1482 groups_dsl::groups
1483 .filter(
1484 groups_dsl::id.eq(group_id).or(groups_dsl::dm_id.eq_any(
1485 groups_dsl::groups
1486 .select(groups_dsl::dm_id)
1487 .filter(groups_dsl::id.eq(group_id))
1488 .into_boxed(),
1489 )),
1490 )
1491 .select(groups_dsl::id),
1492 )
1493}