core_crypto_keystore/transaction/
mod.rs1use std::{
2 borrow::Cow,
3 collections::{HashMap, HashSet, hash_map::Entry},
4 sync::Arc,
5};
6
7use async_lock::{RwLock, SemaphoreGuardArc};
8use itertools::Itertools;
9
10use crate::{
11 CryptoKeystoreError, CryptoKeystoreResult, Database,
12 entities::{MlsPendingMessage, PersistedMlsGroup},
13 traits::{BorrowPrimaryKey, Entity, EntityDatabaseMutation, EntityDeleteBorrowed, KeyType, SearchableEntity},
14 transaction::dynamic_dispatch::EntityId,
15};
16
17pub(crate) mod dynamic_dispatch;
18
19type InMemoryTable = HashMap<EntityId, dynamic_dispatch::Entity>;
21type InMemoryCollection = Arc<RwLock<HashMap<&'static str, InMemoryTable>>>;
23
24#[derive(Debug, Clone)]
27pub struct KeystoreTransaction {
28 cache: InMemoryCollection,
29 deleted: Arc<RwLock<HashSet<EntityId>>>,
30 _semaphore_guard: Arc<SemaphoreGuardArc>,
31}
32
33impl KeystoreTransaction {
34 pub(crate) async fn new(semaphore_guard: SemaphoreGuardArc) -> CryptoKeystoreResult<Self> {
38 Ok(Self {
39 cache: Default::default(),
40 deleted: Arc::new(Default::default()),
41 _semaphore_guard: Arc::new(semaphore_guard),
42 })
43 }
44
45 pub(crate) async fn save<E>(&self, mut entity: E) -> CryptoKeystoreResult<E::AutoGeneratedFields>
54 where
55 E: Entity + EntityDatabaseMutation + Send + Sync,
56 {
57 let auto_generated_fields = entity.pre_save()?;
58
59 let entity_id =
60 EntityId::from_entity(&entity).ok_or(CryptoKeystoreError::UnknownCollectionName(E::COLLECTION_NAME))?;
61 {
62 let mut cache_guard = self.cache.write().await;
64 let table = cache_guard.entry(E::COLLECTION_NAME).or_default();
65 table.insert(entity_id.clone(), entity.into());
66 }
67 {
68 let mut cache_guard = self.deleted.write().await;
71 cache_guard.remove(&entity_id);
72 }
73
74 Ok(auto_generated_fields)
75 }
76
77 async fn remove_by_entity_id<E>(&self, entity_id: EntityId) -> CryptoKeystoreResult<()>
78 where
79 E: Entity + EntityDatabaseMutation,
80 {
81 let mut cache_guard = self.cache.write().await;
84 if let Entry::Occupied(mut table) = cache_guard.entry(E::COLLECTION_NAME)
85 && let Entry::Occupied(cached_record) = table.get_mut().entry(entity_id.clone())
86 {
87 cached_record.remove_entry();
88 };
89
90 let mut deleted_set = self.deleted.write().await;
92 deleted_set.insert(entity_id);
93 Ok(())
94 }
95
96 pub(crate) async fn remove<E>(&self, id: &E::PrimaryKey) -> CryptoKeystoreResult<()>
103 where
104 E: Entity + EntityDatabaseMutation,
105 {
106 let entity_id = EntityId::from_primary_key::<E>(id)
107 .ok_or(CryptoKeystoreError::UnknownCollectionName(E::COLLECTION_NAME))?;
108 self.remove_by_entity_id::<E>(entity_id).await
109 }
110
111 pub(crate) async fn remove_borrowed<E>(&self, id: &E::BorrowedPrimaryKey) -> CryptoKeystoreResult<()>
116 where
117 E: EntityDeleteBorrowed + BorrowPrimaryKey,
118 {
119 let entity_id = EntityId::from_borrowed_primary_key::<E>(id)
120 .ok_or(CryptoKeystoreError::UnknownCollectionName(E::COLLECTION_NAME))?;
121 self.remove_by_entity_id::<E>(entity_id).await
122 }
123
124 pub(crate) async fn restore<E>(&self, id: &E::BorrowedPrimaryKey) -> CryptoKeystoreResult<()>
130 where
131 E: EntityDeleteBorrowed + BorrowPrimaryKey,
132 {
133 let entity_id = EntityId::from_borrowed_primary_key::<E>(id)
134 .ok_or(CryptoKeystoreError::UnknownCollectionName(E::COLLECTION_NAME))?;
135 let mut deleted = self.deleted.write().await;
136 deleted.remove(&entity_id);
137 Ok(())
138 }
139
140 pub(crate) async fn child_groups(
141 &self,
142 entity: PersistedMlsGroup,
143 persisted_records: impl IntoIterator<Item = PersistedMlsGroup>,
144 ) -> CryptoKeystoreResult<Vec<PersistedMlsGroup>> {
145 let cached_records = self.find_all_in_cache::<PersistedMlsGroup>().await;
147 let cached_records = cached_records
148 .iter()
149 .filter(|maybe_child| {
150 maybe_child
151 .parent_id
152 .as_deref()
153 .map(|parent_id| parent_id == entity.borrow_primary_key().bytes().as_ref())
154 .unwrap_or_default()
155 })
156 .map(Arc::as_ref)
157 .map(Cow::Borrowed);
158
159 let persisted_records = persisted_records.into_iter().map(Cow::Owned);
160
161 Ok(self.merge_records(cached_records, persisted_records).await)
162 }
163
164 pub(crate) async fn remove_pending_messages_by_conversation_id(&self, conversation_id: impl AsRef<[u8]> + Send) {
165 let conversation_id = conversation_id.as_ref();
166
167 let mut cache_guard = self.cache.write().await;
168 if let Entry::Occupied(mut table) = cache_guard.entry(MlsPendingMessage::COLLECTION_NAME) {
169 table.get_mut().retain(|_key, entity| {
170 let pending_message = entity
171 .downcast::<MlsPendingMessage>()
172 .expect("table for MlsPendingMessage contains only that type");
173 pending_message.foreign_id != conversation_id
174 });
175 }
176 drop(cache_guard);
177
178 let mut deleted_set = self.deleted.write().await;
179 deleted_set.insert(
180 EntityId::from_key::<MlsPendingMessage>(conversation_id.into())
181 .expect("mls pending messages are proper entities which can be parsed"),
182 );
183 }
184
185 pub(crate) async fn find_pending_messages_by_conversation_id(
186 &self,
187 conversation_id: &[u8],
188 persisted_records: impl IntoIterator<Item = MlsPendingMessage>,
189 ) -> CryptoKeystoreResult<Vec<MlsPendingMessage>> {
190 let persisted_records = persisted_records.into_iter().map(Cow::Owned);
191
192 let cached_records = self.find_all_in_cache::<MlsPendingMessage>().await;
193 let cached_records = cached_records
194 .iter()
195 .filter(|pending_message| pending_message.foreign_id == conversation_id)
196 .map(Arc::as_ref)
197 .map(Cow::Borrowed);
198
199 let merged_records = self.merge_records(cached_records, persisted_records).await;
200 Ok(merged_records)
201 }
202
203 async fn find_in_cache<E>(&self, entity_id: &EntityId) -> Option<Arc<E>>
204 where
205 E: 'static + Entity + Send + Sync,
206 {
207 let cache_guard = self.cache.read().await;
208 cache_guard
209 .get(E::COLLECTION_NAME)
210 .and_then(|table| table.get(entity_id).and_then(|entity| entity.downcast()))
211 }
212
213 async fn get_by_entity_id<E>(&self, entity_id: &EntityId) -> Option<Option<Arc<E>>>
218 where
219 E: 'static + Entity + Send + Sync,
220 {
221 let deleted_list = self.deleted.read().await;
224 if deleted_list.contains(entity_id) {
225 return Some(None);
226 }
227
228 self.find_in_cache::<E>(entity_id).await.map(Some)
229 }
230
231 pub(crate) async fn get<E>(&self, id: &E::PrimaryKey) -> Option<Option<Arc<E>>>
236 where
237 E: 'static + Entity + Send + Sync,
238 {
239 let entity_id = EntityId::from_primary_key::<E>(id)?;
240 self.get_by_entity_id(&entity_id).await
241 }
242
243 pub(crate) async fn get_borrowed<E>(&self, id: &E::BorrowedPrimaryKey) -> Option<Option<Arc<E>>>
248 where
249 E: 'static + Entity + BorrowPrimaryKey + Send + Sync,
250 {
251 let entity_id = EntityId::from_borrowed_primary_key::<E>(id)?;
252 self.get_by_entity_id(&entity_id).await
253 }
254
255 async fn find_all_in_cache<E>(&self) -> Vec<Arc<E>>
256 where
257 E: 'static + Entity + Send + Sync,
258 {
259 let cache_guard = self.cache.read().await;
260 cache_guard
261 .get(E::COLLECTION_NAME)
262 .map(|table| {
263 table
264 .values()
265 .map(|record: &dynamic_dispatch::Entity| {
266 record
267 .downcast::<E>()
268 .expect("all entries in this table are of this type")
269 .clone()
270 })
271 .collect::<Vec<_>>()
272 })
273 .unwrap_or_default()
274 }
275
276 async fn search_in_cache<E, SearchKey>(&self, search_key: &SearchKey) -> Vec<Arc<E>>
277 where
278 E: 'static + Entity + SearchableEntity<SearchKey> + Send + Sync,
279 SearchKey: KeyType,
280 {
281 let cache_guard = self.cache.read().await;
282 cache_guard
283 .get(E::COLLECTION_NAME)
284 .map(|table| {
285 table
286 .values()
287 .filter_map(|record: &dynamic_dispatch::Entity| {
288 let entity = record
289 .downcast::<E>()
290 .expect("all entries in this table are of this type")
291 .clone();
292 entity.matches(search_key).then_some(entity)
293 })
294 .collect()
295 })
296 .unwrap_or_default()
297 }
298
299 pub(crate) async fn find_all<E>(&self, persisted_records: Vec<E>) -> CryptoKeystoreResult<Vec<E>>
300 where
301 E: 'static + Clone + Entity + Send + Sync,
302 {
303 let cached_records = self.find_all_in_cache().await;
304 let merged_records = self
305 .merge_records(
306 cached_records.iter().map(Arc::as_ref).map(Cow::Borrowed),
307 persisted_records.into_iter().map(Cow::Owned),
308 )
309 .await;
310 Ok(merged_records)
311 }
312
313 pub(crate) async fn search<E, SearchKey>(
314 &self,
315 persisted_records: Vec<E>,
316 search_key: &SearchKey,
317 ) -> CryptoKeystoreResult<Vec<E>>
318 where
319 E: 'static + Clone + Entity + SearchableEntity<SearchKey> + Send + Sync,
320 SearchKey: KeyType,
321 {
322 let cached_records = self.search_in_cache(search_key).await;
323 let merged_records = self
324 .merge_records(
325 cached_records.iter().map(Arc::as_ref).map(Cow::Borrowed),
326 persisted_records.into_iter().map(Cow::Owned),
327 )
328 .await;
329 Ok(merged_records)
330 }
331
332 async fn merge_records<'a, E>(
340 &self,
341 records_a: impl IntoIterator<Item = Cow<'a, E>>,
342 records_b: impl IntoIterator<Item = Cow<'a, E>>,
343 ) -> Vec<E>
344 where
345 E: 'static + Clone + Entity,
346 {
347 let deleted_records = self.deleted.read().await;
348
349 records_a
350 .into_iter()
351 .chain(records_b)
352 .unique_by(|e| e.primary_key().bytes().into_owned())
353 .filter_map(|record| {
354 let id = EntityId::from_entity(record.as_ref())?;
355 (!deleted_records.contains(&id)).then_some(record.into_owned())
356 })
357 .collect()
358 }
359
360 pub(crate) async fn commit(self, db: &Database) -> Result<(), CryptoKeystoreError> {
363 let cache = self.cache.read().await;
364 let deleted_ids = self.deleted.read().await;
365
366 let table_names_with_deletion = deleted_ids.iter().map(|entity_id| entity_id.collection_name());
367 let table_names_with_save = cache
368 .values()
369 .flat_map(|table| table.keys())
370 .map(|entity_id| entity_id.collection_name());
371 let any_tables_are_modified = table_names_with_deletion.chain(table_names_with_save).next().is_some();
372
373 if !any_tables_are_modified {
374 log::debug!("Empty transaction was committed.");
375 return Ok(());
376 }
377
378 let mut conn = db.conn().await;
382 let tx = conn.transaction()?;
383
384 for entity in cache.values().flat_map(|table| table.values()) {
385 entity.execute_save(&tx)?;
386 }
387
388 for deleted_id in deleted_ids.iter() {
389 deleted_id.execute_delete(&tx)?;
390 }
391
392 tx.commit()?;
394
395 Ok(())
396 }
397}