1use diesel::RunQueryDsl;
2
3use crate::{
4 ConnectionExt, DbConnection, impl_store, schema::remote_commit_log,
5 schema::remote_commit_log::dsl,
6};
7use diesel::{
8 Insertable, Queryable,
9 backend::Backend,
10 deserialize::{self, FromSql, FromSqlRow},
11 expression::AsExpression,
12 prelude::*,
13 serialize::{self, IsNull, Output, ToSql},
14 sql_types::Integer,
15 sqlite::Sqlite,
16};
17
18use serde::{Deserialize, Serialize};
19use xmtp_common::snippet::Snippet;
20use xmtp_proto::xmtp::mls::message_contents::CommitResult as ProtoCommitResult;
21
22use xmtp_proto::types::GroupId;
23#[derive(Insertable, Debug, Clone)]
24#[diesel(table_name = remote_commit_log)]
25pub struct NewRemoteCommitLog {
26 pub log_sequence_id: i64,
27 pub group_id: GroupId,
28 pub commit_sequence_id: i64,
29 pub commit_result: CommitResult,
30 pub applied_epoch_number: i64,
31 pub applied_epoch_authenticator: Vec<u8>,
32}
33
34impl_store!(NewRemoteCommitLog, remote_commit_log);
35
36#[derive(Insertable, Queryable, Clone)]
37#[diesel(table_name = remote_commit_log)]
38#[diesel(primary_key(rowid))]
39pub struct RemoteCommitLog {
40 pub rowid: i32,
41 pub log_sequence_id: i64,
43 pub group_id: GroupId,
45 pub commit_sequence_id: i64,
47 pub commit_result: CommitResult,
50 pub applied_epoch_number: i64,
52 pub applied_epoch_authenticator: Vec<u8>,
54}
55
56impl_store!(RemoteCommitLog, remote_commit_log);
57
58#[repr(i32)]
59#[derive(Copy, Clone, Serialize, Deserialize, Eq, PartialEq, AsExpression, FromSqlRow)]
60#[diesel(sql_type = Integer)]
61pub enum CommitResult {
62 Unknown = 0,
63 Success = 1,
64 WrongEpoch = 2,
65 Undecryptable = 3,
66 Invalid = 4,
67}
68
69impl std::fmt::Debug for CommitResult {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 let s = match self {
72 CommitResult::Unknown => "Unknown",
73 CommitResult::Success => "Success",
74 CommitResult::WrongEpoch => "WrongEpoch",
75 CommitResult::Undecryptable => "Undecryptable",
76 CommitResult::Invalid => "Invalid",
77 };
78 write!(f, "{}", s)
79 }
80}
81
82impl std::fmt::Debug for RemoteCommitLog {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(
85 f,
86 "RemoteCommitLog {{ rowid: {:?}, log_sequence_id: {:?}, group_id {:?}, commit_sequence_id: {:?}, commit_result: {:?}, applied_epoch_number: {:?}, applied_epoch_authenticator: {:?} }}",
87 self.rowid,
88 self.log_sequence_id,
89 self.group_id.as_slice().snippet(),
90 self.commit_sequence_id,
91 self.commit_result,
92 self.applied_epoch_number,
93 self.applied_epoch_authenticator.snippet()
94 )
95 }
96}
97
98impl ToSql<Integer, Sqlite> for CommitResult
99where
100 i32: ToSql<Integer, Sqlite>,
101{
102 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
103 out.set_value(*self as i32);
104 Ok(IsNull::No)
105 }
106}
107
108impl FromSql<Integer, Sqlite> for CommitResult
109where
110 i32: FromSql<Integer, Sqlite>,
111{
112 fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
113 match i32::from_sql(bytes)? {
114 0 => Ok(Self::Unknown),
115 1 => Ok(Self::Success),
116 2 => Ok(Self::WrongEpoch),
117 3 => Ok(Self::Undecryptable),
118 4 => Ok(Self::Invalid),
119 x => Err(format!("Unrecognized variant {}", x).into()),
120 }
121 }
122}
123
124impl From<ProtoCommitResult> for CommitResult {
125 fn from(value: ProtoCommitResult) -> Self {
126 match value {
127 ProtoCommitResult::Applied => Self::Success,
128 ProtoCommitResult::WrongEpoch => Self::WrongEpoch,
129 ProtoCommitResult::Undecryptable => Self::Undecryptable,
130 ProtoCommitResult::Invalid => Self::Invalid,
131 ProtoCommitResult::Unspecified => Self::Unknown,
132 }
133 }
134}
135
136pub enum RemoteCommitLogOrder {
137 AscendingByRowid,
138 DescendingByRowid,
139}
140
141pub trait QueryRemoteCommitLog {
142 fn get_latest_remote_log_for_group(
143 &self,
144 group_id: &GroupId,
145 ) -> Result<Option<RemoteCommitLog>, crate::ConnectionError>;
146
147 fn get_remote_commit_log_after_cursor(
148 &self,
149 group_id: &GroupId,
150 after_cursor: i64,
151 order_by: RemoteCommitLogOrder,
152 ) -> Result<Vec<RemoteCommitLog>, crate::ConnectionError>;
153}
154
155impl<T> QueryRemoteCommitLog for &T
156where
157 T: QueryRemoteCommitLog,
158{
159 fn get_latest_remote_log_for_group(
160 &self,
161 group_id: &GroupId,
162 ) -> Result<Option<RemoteCommitLog>, crate::ConnectionError> {
163 (**self).get_latest_remote_log_for_group(group_id)
164 }
165
166 fn get_remote_commit_log_after_cursor(
167 &self,
168 group_id: &GroupId,
169 after_cursor: i64,
170 order_by: RemoteCommitLogOrder,
171 ) -> Result<Vec<RemoteCommitLog>, crate::ConnectionError> {
172 (**self).get_remote_commit_log_after_cursor(group_id, after_cursor, order_by)
173 }
174}
175
176impl<C: ConnectionExt> QueryRemoteCommitLog for DbConnection<C> {
177 fn get_latest_remote_log_for_group(
178 &self,
179 group_id: &GroupId,
180 ) -> Result<Option<RemoteCommitLog>, crate::ConnectionError> {
181 self.raw_query(|db| {
182 dsl::remote_commit_log
183 .filter(remote_commit_log::group_id.eq(group_id))
184 .order(remote_commit_log::log_sequence_id.desc())
185 .limit(1)
186 .first(db)
187 .optional()
188 })
189 }
190
191 fn get_remote_commit_log_after_cursor(
192 &self,
193 group_id: &GroupId,
194 after_cursor: i64,
195 order: RemoteCommitLogOrder,
196 ) -> Result<Vec<RemoteCommitLog>, crate::ConnectionError> {
197 if after_cursor > i32::MAX as i64 {
200 return Err(crate::ConnectionError::Database(
201 diesel::result::Error::QueryBuilderError("Cursor value exceeds i32::MAX".into()),
202 ));
203 }
204 let after_cursor: i32 = after_cursor as i32;
205
206 let query = dsl::remote_commit_log
207 .filter(dsl::group_id.eq(group_id))
208 .filter(dsl::rowid.gt(after_cursor))
209 .filter(dsl::commit_sequence_id.ne(0));
210
211 self.raw_query(|db| match order {
212 RemoteCommitLogOrder::AscendingByRowid => query.order_by(dsl::rowid.asc()).load(db),
213 RemoteCommitLogOrder::DescendingByRowid => query.order_by(dsl::rowid.desc()).load(db),
214 })
215 }
216}