1use std::{
19 collections::{BTreeMap, HashMap},
20 hash::Hash,
21};
22
23use ruma::{OwnedEventId, OwnedRoomId};
24
25use super::{ChunkContent, ChunkIdentifierGenerator, RawChunk};
26use crate::{
27 deserialized_responses::TimelineEvent,
28 linked_chunk::{
29 ChunkIdentifier, ChunkMetadata, LinkedChunkId, OwnedLinkedChunkId, Position, Update,
30 },
31};
32
33#[derive(Debug, PartialEq)]
35struct ChunkRow {
36 linked_chunk_id: OwnedLinkedChunkId,
37 previous_chunk: Option<ChunkIdentifier>,
38 chunk: ChunkIdentifier,
39 next_chunk: Option<ChunkIdentifier>,
40}
41
42#[derive(Debug, PartialEq)]
44struct ItemRow<ItemId, Gap> {
45 linked_chunk_id: OwnedLinkedChunkId,
46 position: Position,
47 item: Either<ItemId, Gap>,
48}
49
50#[derive(Debug, PartialEq)]
52enum Either<Item, Gap> {
53 Item(Item),
55
56 Gap(Gap),
58}
59
60#[derive(Debug)]
79pub struct RelationalLinkedChunk<ItemId, Item, Gap> {
80 chunks: Vec<ChunkRow>,
82
83 items_chunks: Vec<ItemRow<ItemId, Gap>>,
85
86 items: HashMap<OwnedLinkedChunkId, BTreeMap<ItemId, (Item, Option<Position>)>>,
88}
89
90pub trait IndexableItem {
93 type ItemId: Hash + PartialEq + Eq + Clone;
94
95 fn id(&self) -> Self::ItemId;
97}
98
99impl IndexableItem for TimelineEvent {
100 type ItemId = OwnedEventId;
101
102 fn id(&self) -> Self::ItemId {
103 self.event_id()
104 .expect("all events saved into a relational linked chunk must have a valid event id")
105 }
106}
107
108impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
109where
110 Item: IndexableItem<ItemId = ItemId>,
111 ItemId: Hash + PartialEq + Eq + Clone + Ord,
112{
113 pub fn new() -> Self {
115 Self { chunks: Vec::new(), items_chunks: Vec::new(), items: HashMap::new() }
116 }
117
118 pub fn clear(&mut self) {
120 self.chunks.clear();
121 self.items_chunks.clear();
122 self.items.clear();
123 }
124
125 pub fn apply_updates(
128 &mut self,
129 linked_chunk_id: LinkedChunkId<'_>,
130 updates: Vec<Update<Item, Gap>>,
131 ) {
132 for update in updates {
133 match update {
134 Update::NewItemsChunk { previous, new, next } => {
135 Self::insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next);
136 }
137
138 Update::NewGapChunk { previous, new, next, gap } => {
139 Self::insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next);
140 self.items_chunks.push(ItemRow {
141 linked_chunk_id: linked_chunk_id.to_owned(),
142 position: Position::new(new, 0),
143 item: Either::Gap(gap),
144 });
145 }
146
147 Update::RemoveChunk(chunk_identifier) => {
148 Self::remove_chunk(&mut self.chunks, linked_chunk_id, chunk_identifier);
149
150 let indices_to_remove = self
151 .items_chunks
152 .iter()
153 .enumerate()
154 .filter_map(
155 |(
156 nth,
157 ItemRow {
158 linked_chunk_id: linked_chunk_id_candidate,
159 position,
160 ..
161 },
162 )| {
163 (linked_chunk_id == linked_chunk_id_candidate
164 && position.chunk_identifier() == chunk_identifier)
165 .then_some(nth)
166 },
167 )
168 .collect::<Vec<_>>();
169
170 for index_to_remove in indices_to_remove.into_iter().rev() {
171 self.items_chunks.remove(index_to_remove);
172 }
173 }
174
175 Update::PushItems { mut at, items } => {
176 for item in items {
177 let item_id = item.id();
178 self.items
179 .entry(linked_chunk_id.to_owned())
180 .or_default()
181 .insert(item_id.clone(), (item, Some(at)));
182 self.items_chunks.push(ItemRow {
183 linked_chunk_id: linked_chunk_id.to_owned(),
184 position: at,
185 item: Either::Item(item_id),
186 });
187 at.increment_index();
188 }
189 }
190
191 Update::ReplaceItem { at, item } => {
192 let existing = self
193 .items_chunks
194 .iter_mut()
195 .find(|item| item.position == at)
196 .expect("trying to replace at an unknown position");
197 assert!(
198 matches!(existing.item, Either::Item(..)),
199 "trying to replace a gap with an item"
200 );
201 let item_id = item.id();
202 self.items
203 .entry(linked_chunk_id.to_owned())
204 .or_default()
205 .insert(item_id.clone(), (item, Some(at)));
206 existing.item = Either::Item(item_id);
207 }
208
209 Update::RemoveItem { at } => {
210 let mut entry_to_remove = None;
211
212 for (
213 nth,
214 ItemRow { linked_chunk_id: linked_chunk_id_candidate, position, .. },
215 ) in self.items_chunks.iter_mut().enumerate()
216 {
217 if linked_chunk_id != &*linked_chunk_id_candidate {
219 continue;
220 }
221
222 if *position == at {
224 debug_assert!(entry_to_remove.is_none(), "Found the same entry twice");
225
226 entry_to_remove = Some(nth);
227 }
228
229 if position.chunk_identifier() == at.chunk_identifier()
231 && position.index() > at.index()
232 {
233 position.decrement_index();
234 }
235 }
236
237 self.items_chunks.remove(entry_to_remove.expect("Remove an unknown item"));
238 }
240
241 Update::DetachLastItems { at } => {
242 let indices_to_remove = self
243 .items_chunks
244 .iter()
245 .enumerate()
246 .filter_map(
247 |(
248 nth,
249 ItemRow {
250 linked_chunk_id: linked_chunk_id_candidate,
251 position,
252 ..
253 },
254 )| {
255 (linked_chunk_id == linked_chunk_id_candidate
256 && position.chunk_identifier() == at.chunk_identifier()
257 && position.index() >= at.index())
258 .then_some(nth)
259 },
260 )
261 .collect::<Vec<_>>();
262
263 for index_to_remove in indices_to_remove.into_iter().rev() {
264 self.items_chunks.remove(index_to_remove);
265 }
266 }
267
268 Update::StartReattachItems | Update::EndReattachItems => { }
269
270 Update::Clear => {
271 self.chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id);
272 self.items_chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id);
273 }
275 }
276 }
277 }
278
279 fn insert_chunk(
280 chunks: &mut Vec<ChunkRow>,
281 linked_chunk_id: LinkedChunkId<'_>,
282 previous: Option<ChunkIdentifier>,
283 new: ChunkIdentifier,
284 next: Option<ChunkIdentifier>,
285 ) {
286 if let Some(previous) = previous {
288 let entry_for_previous_chunk = chunks
289 .iter_mut()
290 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
291 linked_chunk_id == linked_chunk_id_candidate && *chunk == previous
292 })
293 .expect("Previous chunk should be present");
294
295 entry_for_previous_chunk.next_chunk = Some(new);
297 }
298
299 if let Some(next) = next {
301 let entry_for_next_chunk = chunks
302 .iter_mut()
303 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
304 linked_chunk_id == linked_chunk_id_candidate && *chunk == next
305 })
306 .expect("Next chunk should be present");
307
308 entry_for_next_chunk.previous_chunk = Some(new);
310 }
311
312 chunks.push(ChunkRow {
314 linked_chunk_id: linked_chunk_id.to_owned(),
315 previous_chunk: previous,
316 chunk: new,
317 next_chunk: next,
318 });
319 }
320
321 fn remove_chunk(
322 chunks: &mut Vec<ChunkRow>,
323 linked_chunk_id: LinkedChunkId<'_>,
324 chunk_to_remove: ChunkIdentifier,
325 ) {
326 let entry_nth_to_remove = chunks
327 .iter()
328 .enumerate()
329 .find_map(
330 |(nth, ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. })| {
331 (linked_chunk_id == linked_chunk_id_candidate && *chunk == chunk_to_remove)
332 .then_some(nth)
333 },
334 )
335 .expect("Remove an unknown chunk");
336
337 let ChunkRow { linked_chunk_id, previous_chunk: previous, next_chunk: next, .. } =
338 chunks.remove(entry_nth_to_remove);
339
340 if let Some(previous) = previous {
342 let entry_for_previous_chunk = chunks
343 .iter_mut()
344 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
345 &linked_chunk_id == linked_chunk_id_candidate && *chunk == previous
346 })
347 .expect("Previous chunk should be present");
348
349 entry_for_previous_chunk.next_chunk = next;
351 }
352
353 if let Some(next) = next {
355 let entry_for_next_chunk = chunks
356 .iter_mut()
357 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
358 &linked_chunk_id == linked_chunk_id_candidate && *chunk == next
359 })
360 .expect("Next chunk should be present");
361
362 entry_for_next_chunk.previous_chunk = previous;
364 }
365 }
366
367 pub fn unordered_linked_chunk_items<'a>(
370 &'a self,
371 target: &OwnedLinkedChunkId,
372 ) -> impl 'a + Iterator<Item = (&'a Item, Position)> {
373 self.items.get(target).into_iter().flat_map(|items| {
374 items.values().filter_map(|(item, pos)| pos.map(|pos| (item, pos)))
376 })
377 }
378
379 pub fn items(
387 &self,
388 target: &OwnedLinkedChunkId,
389 ) -> impl Iterator<Item = (&Item, Option<Position>)> {
390 self.items
391 .get(target)
392 .into_iter()
393 .flat_map(|items| items.values().map(|(item, pos)| (item, *pos)))
394 }
395
396 pub fn save_item(&mut self, room_id: OwnedRoomId, item: Item) {
398 let id = item.id();
399 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id);
400
401 let map = self.items.entry(linked_chunk_id).or_default();
402 if let Some(prev_value) = map.get_mut(&id) {
403 prev_value.0 = item;
405 } else {
406 map.insert(id, (item, None));
407 }
408 }
409}
410
411impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
412where
413 Gap: Clone,
414 Item: Clone,
415 ItemId: Hash + PartialEq + Eq + Ord,
416{
417 #[doc(hidden)]
422 pub fn load_all_chunks(
423 &self,
424 linked_chunk_id: LinkedChunkId<'_>,
425 ) -> Result<Vec<RawChunk<Item, Gap>>, String> {
426 self.chunks
427 .iter()
428 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
429 .map(|chunk_row| load_raw_chunk(self, chunk_row, linked_chunk_id))
430 .collect::<Result<Vec<_>, String>>()
431 }
432
433 #[doc(hidden)]
438 pub fn load_all_chunks_metadata(
439 &self,
440 linked_chunk_id: LinkedChunkId<'_>,
441 ) -> Result<Vec<ChunkMetadata>, String> {
442 self.chunks
443 .iter()
444 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
445 .map(|chunk_row| load_raw_chunk_metadata(self, chunk_row, linked_chunk_id))
446 .collect::<Result<Vec<_>, String>>()
447 }
448
449 pub fn load_last_chunk(
450 &self,
451 linked_chunk_id: LinkedChunkId<'_>,
452 ) -> Result<(Option<RawChunk<Item, Gap>>, ChunkIdentifierGenerator), String> {
453 let chunk_identifier_generator = match self
455 .chunks
456 .iter()
457 .filter_map(|chunk_row| {
458 (chunk_row.linked_chunk_id == linked_chunk_id).then_some(chunk_row.chunk)
459 })
460 .max()
461 {
462 Some(last_chunk_identifier) => {
463 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier)
464 }
465 None => ChunkIdentifierGenerator::new_from_scratch(),
466 };
467
468 let mut number_of_chunks = 0;
470 let mut chunk_row = None;
471
472 for chunk_row_candidate in &self.chunks {
473 if chunk_row_candidate.linked_chunk_id == linked_chunk_id {
474 number_of_chunks += 1;
475
476 if chunk_row_candidate.next_chunk.is_none() {
477 chunk_row = Some(chunk_row_candidate);
478
479 break;
480 }
481 }
482 }
483
484 let chunk_row = match chunk_row {
485 Some(chunk_row) => chunk_row,
487
488 None if number_of_chunks == 0 => {
491 return Ok((None, chunk_identifier_generator));
492 }
493
494 None => {
499 return Err(
500 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
501 .to_owned(),
502 );
503 }
504 };
505
506 load_raw_chunk(self, chunk_row, linked_chunk_id)
508 .map(|raw_chunk| (Some(raw_chunk), chunk_identifier_generator))
509 }
510
511 pub fn load_previous_chunk(
512 &self,
513 linked_chunk_id: LinkedChunkId<'_>,
514 before_chunk_identifier: ChunkIdentifier,
515 ) -> Result<Option<RawChunk<Item, Gap>>, String> {
516 let Some(chunk_row) = self.chunks.iter().find(|chunk_row| {
518 chunk_row.linked_chunk_id == linked_chunk_id
519 && chunk_row.next_chunk == Some(before_chunk_identifier)
520 }) else {
521 return Ok(None);
523 };
524
525 load_raw_chunk(self, chunk_row, linked_chunk_id).map(Some)
527 }
528}
529
530impl<ItemId, Item, Gap> Default for RelationalLinkedChunk<ItemId, Item, Gap>
531where
532 Item: IndexableItem<ItemId = ItemId>,
533 ItemId: Hash + PartialEq + Eq + Clone + Ord,
534{
535 fn default() -> Self {
536 Self::new()
537 }
538}
539
540fn load_raw_chunk<ItemId, Item, Gap>(
545 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
546 chunk_row: &ChunkRow,
547 linked_chunk_id: LinkedChunkId<'_>,
548) -> Result<RawChunk<Item, Gap>, String>
549where
550 Item: Clone,
551 Gap: Clone,
552 ItemId: Hash + PartialEq + Eq + Ord,
553{
554 let mut items = relational_linked_chunk
556 .items_chunks
557 .iter()
558 .filter(|item_row| {
559 item_row.linked_chunk_id == linked_chunk_id
560 && item_row.position.chunk_identifier() == chunk_row.chunk
561 })
562 .peekable();
563
564 let Some(first_item) = items.peek() else {
565 return Ok(RawChunk {
567 content: ChunkContent::Items(Vec::new()),
568 previous: chunk_row.previous_chunk,
569 identifier: chunk_row.chunk,
570 next: chunk_row.next_chunk,
571 });
572 };
573
574 Ok(match first_item.item {
575 Either::Item(_) => {
577 let mut collected_items = Vec::new();
579
580 for item_row in items {
581 match &item_row.item {
582 Either::Item(item_id) => {
583 collected_items.push((item_id, item_row.position.index()))
584 }
585
586 Either::Gap(_) => {
587 return Err(format!(
588 "unexpected gap in items chunk {}",
589 chunk_row.chunk.index()
590 ));
591 }
592 }
593 }
594
595 collected_items.sort_unstable_by_key(|(_item, index)| *index);
597
598 RawChunk {
599 content: ChunkContent::Items(
600 collected_items
601 .into_iter()
602 .filter_map(|(item_id, _index)| {
603 Some(
604 relational_linked_chunk
605 .items
606 .get(&linked_chunk_id.to_owned())?
607 .get(item_id)?
608 .0
609 .clone(),
610 )
611 })
612 .collect(),
613 ),
614 previous: chunk_row.previous_chunk,
615 identifier: chunk_row.chunk,
616 next: chunk_row.next_chunk,
617 }
618 }
619
620 Either::Gap(ref gap) => {
621 assert!(items.next().is_some(), "we just peeked the gap");
622
623 if items.next().is_some() {
625 return Err(format!(
626 "there shouldn't be more than one item row attached in gap chunk {}",
627 chunk_row.chunk.index()
628 ));
629 }
630
631 RawChunk {
632 content: ChunkContent::Gap(gap.clone()),
633 previous: chunk_row.previous_chunk,
634 identifier: chunk_row.chunk,
635 next: chunk_row.next_chunk,
636 }
637 }
638 })
639}
640
641fn load_raw_chunk_metadata<ItemId, Item, Gap>(
646 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
647 chunk_row: &ChunkRow,
648 linked_chunk_id: LinkedChunkId<'_>,
649) -> Result<ChunkMetadata, String>
650where
651 Item: Clone,
652 Gap: Clone,
653 ItemId: Hash + PartialEq + Eq,
654{
655 let mut items = relational_linked_chunk
657 .items_chunks
658 .iter()
659 .filter(|item_row| {
660 item_row.linked_chunk_id == linked_chunk_id
661 && item_row.position.chunk_identifier() == chunk_row.chunk
662 })
663 .peekable();
664
665 let Some(first_item) = items.peek() else {
666 return Ok(ChunkMetadata {
668 num_items: 0,
669 previous: chunk_row.previous_chunk,
670 identifier: chunk_row.chunk,
671 next: chunk_row.next_chunk,
672 });
673 };
674
675 Ok(match first_item.item {
676 Either::Item(_) => {
678 let mut num_items = 0;
682 for item in items {
683 match &item.item {
684 Either::Item(_) => num_items += 1,
685 Either::Gap(_) => {
686 return Err(format!(
687 "unexpected gap in items chunk {}",
688 chunk_row.chunk.index()
689 ));
690 }
691 }
692 }
693
694 ChunkMetadata {
695 num_items,
696 previous: chunk_row.previous_chunk,
697 identifier: chunk_row.chunk,
698 next: chunk_row.next_chunk,
699 }
700 }
701
702 Either::Gap(..) => {
703 assert!(items.next().is_some(), "we just peeked the gap");
704
705 if items.next().is_some() {
707 return Err(format!(
708 "there shouldn't be more than one item row attached in gap chunk {}",
709 chunk_row.chunk.index()
710 ));
711 }
712
713 ChunkMetadata {
714 num_items: 0,
716 previous: chunk_row.previous_chunk,
717 identifier: chunk_row.chunk,
718 next: chunk_row.next_chunk,
719 }
720 }
721 })
722}
723
724#[cfg(test)]
725mod tests {
726 use std::collections::BTreeMap;
727
728 use assert_matches::assert_matches;
729 use ruma::room_id;
730
731 use super::{super::lazy_loader::from_all_chunks, ChunkIdentifier as CId, *};
732
733 impl IndexableItem for char {
734 type ItemId = char;
735
736 fn id(&self) -> Self::ItemId {
737 *self
738 }
739 }
740
741 #[test]
742 fn test_new_items_chunk() {
743 let room_id = room_id!("!r0:matrix.org");
744 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
745
746 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
747
748 relational_linked_chunk.apply_updates(
749 linked_chunk_id.as_ref(),
750 vec![
751 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
753 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
755 Update::NewItemsChunk { previous: None, new: CId::new(2), next: Some(CId::new(0)) },
757 Update::NewItemsChunk {
759 previous: Some(CId::new(2)),
760 new: CId::new(3),
761 next: Some(CId::new(0)),
762 },
763 ],
764 );
765
766 assert_eq!(
768 relational_linked_chunk.chunks,
769 &[
770 ChunkRow {
771 linked_chunk_id: linked_chunk_id.clone(),
772 previous_chunk: Some(CId::new(3)),
773 chunk: CId::new(0),
774 next_chunk: Some(CId::new(1))
775 },
776 ChunkRow {
777 linked_chunk_id: linked_chunk_id.clone(),
778 previous_chunk: Some(CId::new(0)),
779 chunk: CId::new(1),
780 next_chunk: None
781 },
782 ChunkRow {
783 linked_chunk_id: linked_chunk_id.clone(),
784 previous_chunk: None,
785 chunk: CId::new(2),
786 next_chunk: Some(CId::new(3))
787 },
788 ChunkRow {
789 linked_chunk_id,
790 previous_chunk: Some(CId::new(2)),
791 chunk: CId::new(3),
792 next_chunk: Some(CId::new(0))
793 },
794 ],
795 );
796
797 assert!(relational_linked_chunk.items_chunks.is_empty());
799 }
800
801 #[test]
802 fn test_new_gap_chunk() {
803 let room_id = room_id!("!r0:matrix.org");
804 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
805
806 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
807
808 relational_linked_chunk.apply_updates(
809 linked_chunk_id.as_ref(),
810 vec![
811 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
813 Update::NewGapChunk {
815 previous: Some(CId::new(0)),
816 new: CId::new(1),
817 next: None,
818 gap: (),
819 },
820 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
822 ],
823 );
824
825 assert_eq!(
827 relational_linked_chunk.chunks,
828 &[
829 ChunkRow {
830 linked_chunk_id: linked_chunk_id.clone(),
831 previous_chunk: None,
832 chunk: CId::new(0),
833 next_chunk: Some(CId::new(1))
834 },
835 ChunkRow {
836 linked_chunk_id: linked_chunk_id.clone(),
837 previous_chunk: Some(CId::new(0)),
838 chunk: CId::new(1),
839 next_chunk: Some(CId::new(2))
840 },
841 ChunkRow {
842 linked_chunk_id: linked_chunk_id.clone(),
843 previous_chunk: Some(CId::new(1)),
844 chunk: CId::new(2),
845 next_chunk: None
846 },
847 ],
848 );
849 assert_eq!(
851 relational_linked_chunk.items_chunks,
852 &[ItemRow {
853 linked_chunk_id,
854 position: Position::new(CId::new(1), 0),
855 item: Either::Gap(())
856 }],
857 );
858 }
859
860 #[test]
861 fn test_remove_chunk() {
862 let room_id = room_id!("!r0:matrix.org");
863 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
864
865 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
866
867 relational_linked_chunk.apply_updates(
868 linked_chunk_id.as_ref(),
869 vec![
870 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
872 Update::NewGapChunk {
874 previous: Some(CId::new(0)),
875 new: CId::new(1),
876 next: None,
877 gap: (),
878 },
879 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
881 Update::RemoveChunk(CId::new(1)),
883 ],
884 );
885
886 assert_eq!(
888 relational_linked_chunk.chunks,
889 &[
890 ChunkRow {
891 linked_chunk_id: linked_chunk_id.clone(),
892 previous_chunk: None,
893 chunk: CId::new(0),
894 next_chunk: Some(CId::new(2))
895 },
896 ChunkRow {
897 linked_chunk_id,
898 previous_chunk: Some(CId::new(0)),
899 chunk: CId::new(2),
900 next_chunk: None
901 },
902 ],
903 );
904
905 assert!(relational_linked_chunk.items_chunks.is_empty());
907 }
908
909 #[test]
910 fn test_push_items() {
911 let room_id = room_id!("!r0:matrix.org");
912 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
913
914 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
915
916 relational_linked_chunk.apply_updates(
917 linked_chunk_id.as_ref(),
918 vec![
919 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
921 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
923 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
925 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
927 Update::PushItems { at: Position::new(CId::new(0), 3), items: vec!['d', 'e'] },
929 ],
930 );
931
932 assert_eq!(
934 relational_linked_chunk.chunks,
935 &[
936 ChunkRow {
937 linked_chunk_id: linked_chunk_id.clone(),
938 previous_chunk: None,
939 chunk: CId::new(0),
940 next_chunk: Some(CId::new(1))
941 },
942 ChunkRow {
943 linked_chunk_id: linked_chunk_id.clone(),
944 previous_chunk: Some(CId::new(0)),
945 chunk: CId::new(1),
946 next_chunk: None
947 },
948 ],
949 );
950 assert_eq!(
952 relational_linked_chunk.items_chunks,
953 &[
954 ItemRow {
955 linked_chunk_id: linked_chunk_id.clone(),
956 position: Position::new(CId::new(0), 0),
957 item: Either::Item('a')
958 },
959 ItemRow {
960 linked_chunk_id: linked_chunk_id.clone(),
961 position: Position::new(CId::new(0), 1),
962 item: Either::Item('b')
963 },
964 ItemRow {
965 linked_chunk_id: linked_chunk_id.clone(),
966 position: Position::new(CId::new(0), 2),
967 item: Either::Item('c')
968 },
969 ItemRow {
970 linked_chunk_id: linked_chunk_id.clone(),
971 position: Position::new(CId::new(1), 0),
972 item: Either::Item('x')
973 },
974 ItemRow {
975 linked_chunk_id: linked_chunk_id.clone(),
976 position: Position::new(CId::new(1), 1),
977 item: Either::Item('y')
978 },
979 ItemRow {
980 linked_chunk_id: linked_chunk_id.clone(),
981 position: Position::new(CId::new(1), 2),
982 item: Either::Item('z')
983 },
984 ItemRow {
985 linked_chunk_id: linked_chunk_id.clone(),
986 position: Position::new(CId::new(0), 3),
987 item: Either::Item('d')
988 },
989 ItemRow {
990 linked_chunk_id: linked_chunk_id.clone(),
991 position: Position::new(CId::new(0), 4),
992 item: Either::Item('e')
993 },
994 ],
995 );
996 }
997
998 #[test]
999 fn test_remove_item() {
1000 let room_id = room_id!("!r0:matrix.org");
1001 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1002
1003 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1004
1005 relational_linked_chunk.apply_updates(
1006 linked_chunk_id.as_ref(),
1007 vec![
1008 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1010 Update::PushItems {
1012 at: Position::new(CId::new(0), 0),
1013 items: vec!['a', 'b', 'c', 'd', 'e'],
1014 },
1015 Update::RemoveItem { at: Position::new(CId::new(0), 0) },
1017 Update::RemoveItem { at: Position::new(CId::new(0), 2) },
1019 ],
1020 );
1021
1022 assert_eq!(
1024 relational_linked_chunk.chunks,
1025 &[ChunkRow {
1026 linked_chunk_id: linked_chunk_id.clone(),
1027 previous_chunk: None,
1028 chunk: CId::new(0),
1029 next_chunk: None
1030 }],
1031 );
1032 assert_eq!(
1034 relational_linked_chunk.items_chunks,
1035 &[
1036 ItemRow {
1037 linked_chunk_id: linked_chunk_id.clone(),
1038 position: Position::new(CId::new(0), 0),
1039 item: Either::Item('b')
1040 },
1041 ItemRow {
1042 linked_chunk_id: linked_chunk_id.clone(),
1043 position: Position::new(CId::new(0), 1),
1044 item: Either::Item('c')
1045 },
1046 ItemRow {
1047 linked_chunk_id: linked_chunk_id.clone(),
1048 position: Position::new(CId::new(0), 2),
1049 item: Either::Item('e')
1050 },
1051 ],
1052 );
1053 }
1054
1055 #[test]
1056 fn test_detach_last_items() {
1057 let room_id = room_id!("!r0:matrix.org");
1058 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1059
1060 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1061
1062 relational_linked_chunk.apply_updates(
1063 linked_chunk_id.as_ref(),
1064 vec![
1065 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1067 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
1069 Update::PushItems {
1071 at: Position::new(CId::new(0), 0),
1072 items: vec!['a', 'b', 'c', 'd', 'e'],
1073 },
1074 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
1076 Update::DetachLastItems { at: Position::new(CId::new(0), 2) },
1078 ],
1079 );
1080
1081 assert_eq!(
1083 relational_linked_chunk.chunks,
1084 &[
1085 ChunkRow {
1086 linked_chunk_id: linked_chunk_id.clone(),
1087 previous_chunk: None,
1088 chunk: CId::new(0),
1089 next_chunk: Some(CId::new(1))
1090 },
1091 ChunkRow {
1092 linked_chunk_id: linked_chunk_id.clone(),
1093 previous_chunk: Some(CId::new(0)),
1094 chunk: CId::new(1),
1095 next_chunk: None
1096 },
1097 ],
1098 );
1099 assert_eq!(
1101 relational_linked_chunk.items_chunks,
1102 &[
1103 ItemRow {
1104 linked_chunk_id: linked_chunk_id.clone(),
1105 position: Position::new(CId::new(0), 0),
1106 item: Either::Item('a')
1107 },
1108 ItemRow {
1109 linked_chunk_id: linked_chunk_id.clone(),
1110 position: Position::new(CId::new(0), 1),
1111 item: Either::Item('b')
1112 },
1113 ItemRow {
1114 linked_chunk_id: linked_chunk_id.clone(),
1115 position: Position::new(CId::new(1), 0),
1116 item: Either::Item('x')
1117 },
1118 ItemRow {
1119 linked_chunk_id: linked_chunk_id.clone(),
1120 position: Position::new(CId::new(1), 1),
1121 item: Either::Item('y')
1122 },
1123 ItemRow {
1124 linked_chunk_id: linked_chunk_id.clone(),
1125 position: Position::new(CId::new(1), 2),
1126 item: Either::Item('z')
1127 },
1128 ],
1129 );
1130 }
1131
1132 #[test]
1133 fn test_start_and_end_reattach_items() {
1134 let room_id = room_id!("!r0:matrix.org");
1135 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1136
1137 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1138
1139 relational_linked_chunk.apply_updates(
1140 linked_chunk_id.as_ref(),
1141 vec![Update::StartReattachItems, Update::EndReattachItems],
1142 );
1143
1144 assert!(relational_linked_chunk.chunks.is_empty());
1146 assert!(relational_linked_chunk.items_chunks.is_empty());
1147 }
1148
1149 #[test]
1150 fn test_clear() {
1151 let r0 = room_id!("!r0:matrix.org");
1152 let linked_chunk_id0 = OwnedLinkedChunkId::Room(r0.to_owned());
1153
1154 let r1 = room_id!("!r1:matrix.org");
1155 let linked_chunk_id1 = OwnedLinkedChunkId::Room(r1.to_owned());
1156
1157 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1158
1159 relational_linked_chunk.apply_updates(
1160 linked_chunk_id0.as_ref(),
1161 vec![
1162 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1164 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1166 ],
1167 );
1168
1169 relational_linked_chunk.apply_updates(
1170 linked_chunk_id1.as_ref(),
1171 vec![
1172 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1174 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x'] },
1176 ],
1177 );
1178
1179 assert_eq!(
1181 relational_linked_chunk.chunks,
1182 &[
1183 ChunkRow {
1184 linked_chunk_id: linked_chunk_id0.to_owned(),
1185 previous_chunk: None,
1186 chunk: CId::new(0),
1187 next_chunk: None,
1188 },
1189 ChunkRow {
1190 linked_chunk_id: linked_chunk_id1.to_owned(),
1191 previous_chunk: None,
1192 chunk: CId::new(0),
1193 next_chunk: None,
1194 }
1195 ],
1196 );
1197
1198 assert_eq!(
1200 relational_linked_chunk.items_chunks,
1201 &[
1202 ItemRow {
1203 linked_chunk_id: linked_chunk_id0.to_owned(),
1204 position: Position::new(CId::new(0), 0),
1205 item: Either::Item('a')
1206 },
1207 ItemRow {
1208 linked_chunk_id: linked_chunk_id0.to_owned(),
1209 position: Position::new(CId::new(0), 1),
1210 item: Either::Item('b')
1211 },
1212 ItemRow {
1213 linked_chunk_id: linked_chunk_id0.to_owned(),
1214 position: Position::new(CId::new(0), 2),
1215 item: Either::Item('c')
1216 },
1217 ItemRow {
1218 linked_chunk_id: linked_chunk_id1.to_owned(),
1219 position: Position::new(CId::new(0), 0),
1220 item: Either::Item('x')
1221 },
1222 ],
1223 );
1224
1225 relational_linked_chunk.apply_updates(linked_chunk_id0.as_ref(), vec![Update::Clear]);
1227
1228 assert_eq!(
1230 relational_linked_chunk.chunks,
1231 &[ChunkRow {
1232 linked_chunk_id: linked_chunk_id1.to_owned(),
1233 previous_chunk: None,
1234 chunk: CId::new(0),
1235 next_chunk: None,
1236 }],
1237 );
1238
1239 assert_eq!(
1240 relational_linked_chunk.items_chunks,
1241 &[ItemRow {
1242 linked_chunk_id: linked_chunk_id1.to_owned(),
1243 position: Position::new(CId::new(0), 0),
1244 item: Either::Item('x')
1245 },],
1246 );
1247 }
1248
1249 #[test]
1250 fn test_load_empty_linked_chunk() {
1251 let room_id = room_id!("!r0:matrix.org");
1252 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1253
1254 let relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1256 let result = relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap();
1257 assert!(result.is_empty());
1258 }
1259
1260 #[test]
1261 fn test_load_all_chunks_with_empty_items() {
1262 let room_id = room_id!("!r0:matrix.org");
1263 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1264
1265 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1266
1267 relational_linked_chunk.apply_updates(
1269 linked_chunk_id.as_ref(),
1270 vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }],
1271 );
1272
1273 let lc = from_all_chunks::<3, _, _>(
1275 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1276 )
1277 .expect("building succeeds")
1278 .expect("this leads to a non-empty linked chunk");
1279
1280 assert_items_eq!(lc, []);
1281 }
1282
1283 #[test]
1284 fn test_rebuild_linked_chunk() {
1285 let room_id = room_id!("!r0:matrix.org");
1286 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1287
1288 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1289
1290 relational_linked_chunk.apply_updates(
1291 linked_chunk_id.as_ref(),
1292 vec![
1293 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1295 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1297 Update::NewGapChunk {
1299 previous: Some(CId::new(0)),
1300 new: CId::new(1),
1301 next: None,
1302 gap: 'g',
1303 },
1304 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
1306 Update::PushItems { at: Position::new(CId::new(2), 0), items: vec!['d', 'e', 'f'] },
1308 ],
1309 );
1310
1311 let lc = from_all_chunks::<3, _, _>(
1312 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1313 )
1314 .expect("building succeeds")
1315 .expect("this leads to a non-empty linked chunk");
1316
1317 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']);
1319 }
1320
1321 #[test]
1322 fn test_replace_item() {
1323 let room_id = room_id!("!r0:matrix.org");
1324 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1325
1326 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1327
1328 relational_linked_chunk.apply_updates(
1329 linked_chunk_id.as_ref(),
1330 vec![
1331 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1333 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1335 Update::ReplaceItem { at: Position::new(CId::new(0), 1), item: 'B' },
1337 ],
1338 );
1339
1340 assert_eq!(
1342 relational_linked_chunk.chunks,
1343 &[ChunkRow {
1344 linked_chunk_id: linked_chunk_id.clone(),
1345 previous_chunk: None,
1346 chunk: CId::new(0),
1347 next_chunk: None,
1348 },],
1349 );
1350
1351 assert_eq!(
1353 relational_linked_chunk.items_chunks,
1354 &[
1355 ItemRow {
1356 linked_chunk_id: linked_chunk_id.clone(),
1357 position: Position::new(CId::new(0), 0),
1358 item: Either::Item('a')
1359 },
1360 ItemRow {
1361 linked_chunk_id: linked_chunk_id.clone(),
1362 position: Position::new(CId::new(0), 1),
1363 item: Either::Item('B')
1364 },
1365 ItemRow {
1366 linked_chunk_id,
1367 position: Position::new(CId::new(0), 2),
1368 item: Either::Item('c')
1369 },
1370 ],
1371 );
1372 }
1373
1374 #[test]
1375 fn test_unordered_events() {
1376 let room_id = room_id!("!r0:matrix.org");
1377 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1378
1379 let other_room_id = room_id!("!r1:matrix.org");
1380 let other_linked_chunk_id = OwnedLinkedChunkId::Room(other_room_id.to_owned());
1381
1382 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1383
1384 relational_linked_chunk.apply_updates(
1385 linked_chunk_id.as_ref(),
1386 vec![
1387 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1388 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1389 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
1390 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['d', 'e', 'f'] },
1391 ],
1392 );
1393
1394 relational_linked_chunk.apply_updates(
1395 other_linked_chunk_id.as_ref(),
1396 vec![
1397 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1398 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x', 'y', 'z'] },
1399 ],
1400 );
1401
1402 let events = BTreeMap::from_iter(
1403 relational_linked_chunk.unordered_linked_chunk_items(&linked_chunk_id),
1404 );
1405
1406 assert_eq!(events.len(), 6);
1407 assert_eq!(*events.get(&'a').unwrap(), Position::new(CId::new(0), 0));
1408 assert_eq!(*events.get(&'b').unwrap(), Position::new(CId::new(0), 1));
1409 assert_eq!(*events.get(&'c').unwrap(), Position::new(CId::new(0), 2));
1410 assert_eq!(*events.get(&'d').unwrap(), Position::new(CId::new(1), 0));
1411 assert_eq!(*events.get(&'e').unwrap(), Position::new(CId::new(1), 1));
1412 assert_eq!(*events.get(&'f').unwrap(), Position::new(CId::new(1), 2));
1413 }
1414
1415 #[test]
1416 fn test_load_last_chunk() {
1417 let room_id = room_id!("!r0:matrix.org");
1418 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1419
1420 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1421
1422 {
1424 let (last_chunk, chunk_identifier_generator) =
1425 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1426
1427 assert!(last_chunk.is_none());
1428 assert_eq!(chunk_identifier_generator.current(), 0);
1429 }
1430
1431 {
1433 relational_linked_chunk.apply_updates(
1434 linked_chunk_id.as_ref(),
1435 vec![
1436 Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1437 Update::PushItems { at: Position::new(CId::new(42), 0), items: vec!['a', 'b'] },
1438 ],
1439 );
1440
1441 let (last_chunk, chunk_identifier_generator) =
1442 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1443
1444 assert_matches!(last_chunk, Some(last_chunk) => {
1445 assert_eq!(last_chunk.identifier, 42);
1446 assert!(last_chunk.previous.is_none());
1447 assert!(last_chunk.next.is_none());
1448 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1449 assert_eq!(items.len(), 2);
1450 assert_eq!(items, &['a', 'b']);
1451 });
1452 });
1453 assert_eq!(chunk_identifier_generator.current(), 42);
1454 }
1455
1456 {
1458 relational_linked_chunk.apply_updates(
1459 linked_chunk_id.as_ref(),
1460 vec![
1461 Update::NewItemsChunk {
1462 previous: Some(CId::new(42)),
1463 new: CId::new(7),
1464 next: None,
1465 },
1466 Update::PushItems {
1467 at: Position::new(CId::new(7), 0),
1468 items: vec!['c', 'd', 'e'],
1469 },
1470 ],
1471 );
1472
1473 let (last_chunk, chunk_identifier_generator) =
1474 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1475
1476 assert_matches!(last_chunk, Some(last_chunk) => {
1477 assert_eq!(last_chunk.identifier, 7);
1478 assert_matches!(last_chunk.previous, Some(previous) => {
1479 assert_eq!(previous, 42);
1480 });
1481 assert!(last_chunk.next.is_none());
1482 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1483 assert_eq!(items.len(), 3);
1484 assert_eq!(items, &['c', 'd', 'e']);
1485 });
1486 });
1487 assert_eq!(chunk_identifier_generator.current(), 42);
1488 }
1489 }
1490
1491 #[test]
1492 fn test_load_last_chunk_with_a_cycle() {
1493 let room_id = room_id!("!r0:matrix.org");
1494 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1495 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1496
1497 relational_linked_chunk.apply_updates(
1498 linked_chunk_id.as_ref(),
1499 vec![
1500 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1501 Update::NewItemsChunk {
1502 previous: Some(CId::new(0)),
1506 new: CId::new(1),
1507 next: Some(CId::new(0)),
1508 },
1509 ],
1510 );
1511
1512 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap_err();
1513 }
1514
1515 #[test]
1516 fn test_load_previous_chunk() {
1517 let room_id = room_id!("!r0:matrix.org");
1518 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1519 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1520
1521 {
1524 let previous_chunk = relational_linked_chunk
1525 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(153))
1526 .unwrap();
1527
1528 assert!(previous_chunk.is_none());
1529 }
1530
1531 {
1534 relational_linked_chunk.apply_updates(
1535 linked_chunk_id.as_ref(),
1536 vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
1537 );
1538
1539 let previous_chunk = relational_linked_chunk
1540 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1541 .unwrap();
1542
1543 assert!(previous_chunk.is_none());
1544 }
1545
1546 {
1548 relational_linked_chunk.apply_updates(
1549 linked_chunk_id.as_ref(),
1550 vec![
1551 Update::NewItemsChunk {
1553 previous: None,
1554 new: CId::new(7),
1555 next: Some(CId::new(42)),
1556 },
1557 Update::PushItems {
1558 at: Position::new(CId::new(7), 0),
1559 items: vec!['a', 'b', 'c'],
1560 },
1561 ],
1562 );
1563
1564 let previous_chunk = relational_linked_chunk
1565 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1566 .unwrap();
1567
1568 assert_matches!(previous_chunk, Some(previous_chunk) => {
1569 assert_eq!(previous_chunk.identifier, 7);
1570 assert!(previous_chunk.previous.is_none());
1571 assert_matches!(previous_chunk.next, Some(next) => {
1572 assert_eq!(next, 42);
1573 });
1574 assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
1575 assert_eq!(items.len(), 3);
1576 assert_eq!(items, &['a', 'b', 'c']);
1577 });
1578 });
1579 }
1580 }
1581}