arminius_lib_db_todevice/
lib.rs1use arminius_lib_db::{Database, DatabaseError, ResultExt as _};
8use arminius_lib_model::types::StreamId;
9use itertools::multiunzip;
10use ruma::{
11 DeviceId, OwnedUserId, UserId, events::ToDeviceEventType, to_device::DeviceIdOrAllDevices,
12};
13use sqlx::postgres::PgQueryResult;
14
15#[derive(Debug)]
17pub struct ToDeviceMessage {
18 pub user: OwnedUserId,
20 pub device: DeviceIdOrAllDevices,
22 pub message: serde_json::Value,
24}
25
26#[derive(Debug)]
28pub struct SyncToDeviceEvent {
29 pub sender: OwnedUserId,
31 pub event_type: ToDeviceEventType,
33 pub content: serde_json::Value,
35 pub stream_id: StreamId,
37}
38
39#[rust_analyzer::prefer_underscore_import]
42pub trait ToDeviceQueries: Database {
43 #[tracing::instrument(skip(self))]
45 async fn insert_to_device_messages(
46 &mut self,
47 event_type: ToDeviceEventType,
48 sender: &UserId,
49 messages: Vec<ToDeviceMessage>,
50 ) -> Result<PgQueryResult, DatabaseError> {
51 let (users, all_devices, devices, messages): (
54 Vec<String>,
55 Vec<bool>,
56 Vec<String>,
57 Vec<serde_json::Value>,
58 ) = multiunzip(messages.into_iter().map(|msg| {
59 (
60 msg.user.to_string(),
61 matches!(msg.device, DeviceIdOrAllDevices::AllDevices),
62 msg.device.to_string(),
63 msg.message,
64 )
65 }));
66 Ok(sqlx::query!(
67 "
68 WITH raw_rows AS (
69 SELECT
70 *
71 FROM
72 UNNEST($1::text[], $2::bool[], $3::text[], $4::json[]) AS t (user_id,
73 all_devices,
74 device_id,
75 message)
76 ),
77 inserting_rows AS (
78 SELECT
79 d.nid,
80 message,
81 $5 AS event_type,
82 $6 AS sender
83 FROM
84 raw_rows r
85 LEFT JOIN users u ON u.user_id = r.user_id
86 LEFT JOIN devices d ON d.user_nid = u.nid
87 AND r.all_devices
88 OR (NOT r.all_devices
89 AND d.device_id = r.device_id))
90 INSERT INTO to_device_messages (device_nid, event_content, event_type, sender) (
91 SELECT
92 *
93 FROM
94 inserting_rows);
95 ",
96 &users,
97 &all_devices,
98 &devices,
99 &messages,
100 &event_type.to_string(),
101 sender.as_str(),
102 )
103 .execute(self.connection().await?)
104 .await?)
105 }
106
107 #[tracing::instrument(skip(self))]
109 async fn insert_to_device_transaction(
110 &mut self,
111 origin: &ruma::ServerName,
112 txnid: &ruma::TransactionId,
113 ) -> Result<(), DatabaseError> {
114 sqlx::query!(
115 r#"
116 INSERT INTO federated_to_device_transactions (origin, txnid)
117 VALUES ($1, $2)
118 "#,
119 origin.as_str(),
120 txnid.as_str()
121 )
122 .execute(self.connection().await?)
123 .await?;
124 Ok(())
125 }
126
127 #[tracing::instrument(skip(self))]
129 async fn to_device_transaction_exists(
130 &mut self,
131 origin: &ruma::ServerName,
132 txnid: &ruma::TransactionId,
133 ) -> Result<bool, DatabaseError> {
134 let result = sqlx::query!(
135 r#"
136 SELECT
137 *
138 FROM
139 federated_to_device_transactions
140 WHERE
141 origin = $1
142 AND txnid = $2
143 "#,
144 origin.as_str(),
145 txnid.as_str()
146 )
147 .fetch_optional(self.connection().await?)
148 .await?;
149 Ok(result.is_some())
150 }
151
152 #[tracing::instrument(skip(self))]
154 async fn fetch_sync_todevice_messages(
155 &mut self,
156 user_id: &UserId,
157 device_id: &DeviceId,
158 from: StreamId,
159 ) -> Result<Vec<SyncToDeviceEvent>, DatabaseError> {
160 let rows = sqlx::query!(
161 "
162 SELECT
163 sender,
164 m.event_type,
165 m.event_content,
166 m.stream_id
167 FROM
168 to_device_messages m
169 JOIN devices d ON d.nid = m.device_nid
170 JOIN users u ON u.nid = d.user_nid
171 WHERE
172 user_id = $1
173 AND d.device_id = $2
174 AND m.stream_id > $3
175 AND m.stream_id <= max_safe_seq ('device_message_streamids')
176 ",
177 user_id.as_str(),
178 device_id.as_str(),
179 from,
180 )
181 .fetch_all(self.connection().await?)
182 .await?;
183
184 rows.into_iter()
185 .map(|row| {
186 Ok(SyncToDeviceEvent {
187 sender: OwnedUserId::try_from(row.sender)
188 .invalid_data("Invalid user ID in database")?,
189 event_type: ToDeviceEventType::from(row.event_type),
190 content: row.event_content,
191 stream_id: row.stream_id,
192 })
193 })
194 .collect()
195 }
196
197 #[tracing::instrument(skip(self, content))]
209 async fn import_to_device_message(
210 &mut self,
211 user_id: &UserId,
212 device_id: &DeviceId,
213 sender: &UserId,
214 event_type: &ToDeviceEventType,
215 content: serde_json::Value,
216 ) -> Result<bool, DatabaseError> {
217 let result = sqlx::query!(
218 r#"
219 INSERT INTO to_device_messages (device_nid, event_content, event_type, sender)
220 SELECT
221 d.nid,
222 $4::json,
223 $5,
224 $3
225 FROM
226 devices d
227 JOIN users u ON d.user_nid = u.nid
228 WHERE
229 u.user_id = $1
230 AND d.device_id = $2
231 AND NOT EXISTS (
232 SELECT
233 1
234 FROM
235 to_device_messages existing
236 WHERE
237 existing.device_nid = d.nid
238 AND existing.sender = $3
239 AND existing.event_type = $5
240 AND existing.event_content::text = $4::json::text)
241 "#,
242 user_id.as_str(), device_id.as_str(), sender.as_str(), content, event_type.to_string(), )
248 .execute(self.connection().await?)
249 .await?;
250 Ok(result.rows_affected() > 0)
251 }
252
253 #[tracing::instrument(skip(self))]
258 async fn delete_synced_todevice_messages(
259 &mut self,
260 user_id: &UserId,
261 device_id: &DeviceId,
262 delete_to_inclusive: StreamId,
263 ) -> Result<PgQueryResult, DatabaseError> {
264 Ok(sqlx::query!(
265 r#"
266 DELETE FROM to_device_messages m USING devices d, users u
267 WHERE u.nid = d.user_nid
268 AND d.nid = m.device_nid
269 AND u.user_id = $1
270 AND d.device_id = $2
271 AND m.stream_id <= $3
272 AND m.stream_id <= max_safe_seq ('device_message_streamids')
273 "#,
274 user_id.as_str(),
275 device_id.as_str(),
276 delete_to_inclusive,
277 )
278 .execute(self.connection().await?)
279 .await?)
280 }
281}
282
283impl<T> ToDeviceQueries for T where T: Database {}