1use arminius_lib_db::{Database, DatabaseError, ResultExt as _, TransactedDb};
46use arminius_lib_model::types::StreamId;
47use ruma::{
48 OwnedRoomId, OwnedUserId, RoomId, UserId,
49 api::client::filter::{Filter, RoomEventFilter},
50 events::{
51 GlobalAccountDataEventType, RoomAccountDataEvent, RoomAccountDataEventContent,
52 RoomAccountDataEventType, room::member::MembershipState,
53 },
54};
55use serde::de::DeserializeOwned;
56use serde_json::json;
57use sqlx::postgres::PgQueryResult;
58
59#[derive(Debug)]
61pub struct SyncRoomAccountDataEvent {
62 pub room_id: OwnedRoomId,
64 pub membership: MembershipState,
66 pub stream_id: StreamId,
68 pub event: serde_json::Value,
70}
71
72#[derive(Debug, PartialEq)]
74pub struct SyncGlobalAccountDataEvent {
75 pub stream_id: StreamId,
77 pub event: serde_json::Value,
79}
80
81pub trait AccountDataLock: AccountDataQueries {
84 #[tracing::instrument(skip(self))]
120 async fn lock_room_account_data(&mut self, user_id: &UserId) -> Result<(), DatabaseError> {
121 sqlx::query_scalar!(
122 "SELECT pg_advisory_xact_lock(
123 (SELECT oid::int FROM pg_class WHERE relname = 'room_account_data' AND relkind = 'r'),
124 nid
125 )
126 FROM users WHERE user_id = $1",
127 user_id.as_str(),
128 )
129 .fetch_optional(self.connection().await?)
130 .await?;
131
132 Ok(())
133 }
134
135 #[tracing::instrument(skip(self))]
143 async fn fetch_room_account_data_for_update<C>(
144 &mut self,
145 user_id: &UserId,
146 room_id: &RoomId,
147 event_type: &RoomAccountDataEventType,
148 ) -> Result<Option<RoomAccountDataEvent<C>>, DatabaseError>
149 where
150 C: RoomAccountDataEventContent + DeserializeOwned,
151 Self: Sized,
152 {
153 self.lock_room_account_data(user_id).await?;
154
155 self.fetch_room_account_data(user_id, room_id, event_type).await
156 }
157}
158
159impl AccountDataLock for TransactedDb<'_> {}
164
165#[rust_analyzer::prefer_underscore_import]
168pub trait AccountDataQueries: Database {
169 #[tracing::instrument(skip(self))]
171 async fn upsert_room_account_data(
172 &mut self,
173 user_id: &UserId,
174 room_id: &RoomId,
175 event_type: &RoomAccountDataEventType,
176 data: serde_json::Value,
177 ) -> Result<PgQueryResult, DatabaseError> {
178 sqlx::query!(
179 "
180 INSERT INTO room_account_data (user_nid, room_nid, data_type, data)
181 VALUES (
182 (SELECT nid FROM users WHERE user_id = $1),
183 (SELECT nid FROM rooms WHERE room_id = $2),
184 $3,
185 $4
186 )
187 ON CONFLICT (user_nid, room_nid, data_type)
188 DO UPDATE SET
189 data = EXCLUDED.data,
190 stream_id = EXCLUDED.stream_id
191 ",
192 user_id.as_str(),
193 room_id.as_str(),
194 event_type.to_string(),
195 data,
196 )
197 .execute(self.connection().await?)
198 .await
199 .map_err(Into::into)
200 }
201
202 #[tracing::instrument(skip(self))]
204 async fn upsert_global_account_data(
205 &mut self,
206 user_id: &UserId,
207 event_type: &GlobalAccountDataEventType,
208 data: serde_json::Value,
209 ) -> Result<PgQueryResult, DatabaseError> {
210 sqlx::query!(
211 "
212 INSERT INTO global_account_data (user_nid, data_type, data)
213 VALUES (
214 (SELECT nid FROM users WHERE user_id = $1),
215 $2,
216 $3
217 )
218 ON CONFLICT (user_nid, data_type)
219 DO UPDATE SET
220 data = EXCLUDED.data,
221 stream_id = EXCLUDED.stream_id
222 ",
223 user_id.as_str(),
224 event_type.to_string(),
225 data,
226 )
227 .execute(self.connection().await?)
228 .await
229 .map_err(Into::into)
230 }
231
232 #[tracing::instrument(skip(self))]
234 async fn fetch_room_account_data<C: RoomAccountDataEventContent + DeserializeOwned>(
235 &mut self,
236 user_id: &UserId,
237 room_id: &RoomId,
238 event_type: &RoomAccountDataEventType,
239 ) -> Result<Option<RoomAccountDataEvent<C>>, DatabaseError> {
240 self.fetch_room_account_data_untyped(user_id, room_id, event_type)
241 .await?
242 .map(|data| {
243 let event_content = serde_json::from_value::<C>(data)
244 .invalid_data("Invalid room account data event in database")?;
245
246 Ok(RoomAccountDataEvent::new(event_content))
247 })
248 .transpose()
249 }
250
251 #[tracing::instrument(skip(self))]
257 async fn fetch_room_account_data_untyped(
258 &mut self,
259 user_id: &UserId,
260 room_id: &RoomId,
261 event_type: &RoomAccountDataEventType,
262 ) -> Result<Option<serde_json::Value>, DatabaseError> {
263 Ok(sqlx::query!(
264 "
265 SELECT
266 room_account_data.data
267 FROM
268 room_account_data
269 JOIN users ON room_account_data.user_nid = users.nid
270 JOIN rooms ON room_account_data.room_nid = rooms.nid
271 WHERE
272 users.user_id = $1
273 AND room_id = $2
274 AND data_type = $3
275 ",
276 user_id.as_str(),
277 room_id.as_str(),
278 event_type.to_string(),
279 )
280 .fetch_optional(self.connection().await?)
281 .await?
282 .map(|row| row.data))
283 }
284
285 #[tracing::instrument(skip(self))]
294 async fn import_global_account_data(
295 &mut self,
296 user_id: &UserId,
297 data_type: &str,
298 data: serde_json::Value,
299 stream_id: i64,
300 ) -> Result<bool, DatabaseError> {
301 let result = sqlx::query!(
302 r#"
303 INSERT INTO global_account_data (user_nid, data_type, data, stream_id)
304 VALUES ((
305 SELECT
306 nid
307 FROM
308 users
309 WHERE
310 user_id = $1), $2, $3, $4)
311 ON CONFLICT (user_nid,
312 data_type)
313 DO NOTHING
314 "#,
315 user_id.as_str(),
316 data_type,
317 data,
318 stream_id,
319 )
320 .execute(self.connection().await?)
321 .await?;
322 advance_account_data_sequence(self, stream_id).await?;
323 Ok(result.rows_affected() > 0)
324 }
325
326 #[tracing::instrument(skip(self))]
334 async fn import_room_account_data(
335 &mut self,
336 user_id: &UserId,
337 room_id: &RoomId,
338 data_type: &str,
339 data: serde_json::Value,
340 stream_id: i64,
341 ) -> Result<bool, DatabaseError> {
342 let result = sqlx::query!(
343 r#"
344 INSERT INTO room_account_data (user_nid, room_nid, data_type, data, stream_id)
345 VALUES
346 ((
347 SELECT
348 nid
349 FROM
350 users
351 WHERE
352 user_id = $1), (
353 SELECT
354 nid
355 FROM
356 rooms
357 WHERE
358 room_id = $2), $3, $4, $5)
359 ON CONFLICT (user_nid,
360 room_nid,
361 data_type)
362 DO NOTHING
363 "#,
364 user_id.as_str(),
365 room_id.as_str(),
366 data_type,
367 data,
368 stream_id,
369 )
370 .execute(self.connection().await?)
371 .await?;
372 advance_account_data_sequence(self, stream_id).await?;
373 Ok(result.rows_affected() > 0)
374 }
375
376 #[tracing::instrument(skip(self))]
378 async fn fetch_global_account_data_untyped(
379 &mut self,
380 user_id: &UserId,
381 event_type: &GlobalAccountDataEventType,
382 ) -> Result<Option<serde_json::Value>, DatabaseError> {
383 Ok(sqlx::query!(
384 "
385 SELECT
386 global_account_data.data
387 FROM
388 global_account_data
389 JOIN users ON global_account_data.user_nid = users.nid
390 WHERE
391 users.user_id = $1
392 AND data_type = $2
393 ",
394 user_id.as_str(),
395 event_type.to_string(),
396 )
397 .fetch_optional(self.connection().await?)
398 .await?
399 .map(|row| row.data))
400 }
401
402 #[tracing::instrument(skip(self))]
405 async fn fetch_sync_global_account_data_events(
406 &mut self,
407 user_id: &UserId,
408 from_stream_id: StreamId,
409 filter: Option<&Filter>,
410 ) -> Result<Vec<SyncGlobalAccountDataEvent>, DatabaseError> {
411 let types = filter.and_then(|filter| filter.types.clone());
412 let not_types = filter.map(|filter| filter.not_types.clone()).unwrap_or_default();
413 Ok(sqlx::query!(
414 "
415 SELECT
416 stream_id,
417 data_type,
418 data
419 FROM
420 global_account_data
421 JOIN users ON global_account_data.user_nid = users.nid
422 WHERE
423 users.user_id = $1
424 AND stream_id > $2
425 AND ($3::text[] IS NULL OR data_type::text = ANY ($3::text[]))
426 AND NOT (data_type = ANY ($4::text[]))
427 AND stream_id <= max_safe_seq ('account_data_streamids')
428 ",
429 user_id.to_string(),
430 from_stream_id,
431 types.as_deref(),
432 ¬_types
433 )
434 .fetch_all(self.connection().await?)
435 .await?
436 .into_iter()
437 .map(|row| SyncGlobalAccountDataEvent {
438 stream_id: row.stream_id,
439 event: json!({
440 "type": row.data_type,
441 "content": row.data
442 }),
443 })
444 .collect())
445 }
446
447 #[tracing::instrument(skip(self))]
450 async fn fetch_sync_room_account_data_events(
451 &mut self,
452 user_id: &OwnedUserId,
453 from_stream_id: StreamId,
454 filter: Option<&RoomEventFilter>,
455 ) -> Result<Vec<SyncRoomAccountDataEvent>, DatabaseError> {
456 let types = filter.and_then(|filter| filter.types.clone());
457 let not_types = filter.map(|filter| filter.not_types.clone()).unwrap_or_default();
458 sqlx::query!(
459 r#"
460 SELECT
461 room_membership_state.room_id AS "room_id!",
462 room_membership_state.membership AS "membership!",
463 stream_id,
464 data_type,
465 data
466 FROM
467 room_account_data
468 JOIN room_membership_state ($1) ON room_account_data.room_nid = room_membership_state.room_nid
469 WHERE
470 room_membership_state.membership IN ('join', 'leave')
471 AND stream_id > $2
472 AND ($3::text[] IS NULL
473 OR data_type::text = ANY ($3::text[]))
474 AND NOT (data_type = ANY ($4::text[]))
475 AND stream_id <= max_safe_seq ('account_data_streamids')
476 "#,
477 user_id.to_string(),
478 from_stream_id,
479 types.as_deref(),
480 ¬_types
481 )
482 .fetch_all(self.connection().await?)
483 .await?
484 .into_iter()
485 .map(|row| {
486 let room_id = RoomId::parse(row.room_id).invalid_data("Invalid room ID in database")?;
487
488 Ok(SyncRoomAccountDataEvent {
489 room_id,
490 membership: MembershipState::from(row.membership),
491 stream_id: row.stream_id,
492 event: json!({
493 "type": row.data_type,
494 "content": row.data
495 }),
496 })
497 })
498 .collect::<Result<Vec<_>, _>>()
499 }
500}
501
502impl<T> AccountDataQueries for T where T: Database {}
503
504async fn advance_account_data_sequence<T: Database + ?Sized>(
513 db: &mut T,
514 stream_id: i64,
515) -> Result<(), DatabaseError> {
516 sqlx::query_scalar!(
517 "SELECT setval(
518 'account_data_streamids',
519 GREATEST((SELECT last_value FROM account_data_streamids), $1),
520 true)",
521 stream_id,
522 )
523 .fetch_one(db.connection().await?)
524 .await?;
525 Ok(())
526}