1use std::{collections::HashMap, hash::Hash};
19
20use ruma::{OwnedEventId, OwnedRoomId, RoomId};
21
22use super::{ChunkContent, ChunkIdentifierGenerator, RawChunk};
23use crate::{
24 deserialized_responses::TimelineEvent,
25 linked_chunk::{ChunkIdentifier, Position, Update},
26};
27
28#[derive(Debug, PartialEq)]
30struct ChunkRow {
31 room_id: OwnedRoomId,
32 previous_chunk: Option<ChunkIdentifier>,
33 chunk: ChunkIdentifier,
34 next_chunk: Option<ChunkIdentifier>,
35}
36
37#[derive(Debug, PartialEq)]
39struct ItemRow<ItemId, Gap> {
40 room_id: OwnedRoomId,
41 position: Position,
42 item: Either<ItemId, Gap>,
43}
44
45#[derive(Debug, PartialEq)]
47enum Either<Item, Gap> {
48 Item(Item),
50
51 Gap(Gap),
53}
54
55#[derive(Debug)]
74pub struct RelationalLinkedChunk<ItemId, Item, Gap> {
75 chunks: Vec<ChunkRow>,
77
78 items_chunks: Vec<ItemRow<ItemId, Gap>>,
80
81 items: HashMap<OwnedRoomId, HashMap<ItemId, Item>>,
83}
84
85pub trait IndexableItem {
88 type ItemId: Hash + PartialEq + Eq + Clone;
89
90 fn id(&self) -> Self::ItemId;
92}
93
94impl IndexableItem for TimelineEvent {
95 type ItemId = OwnedEventId;
96
97 fn id(&self) -> Self::ItemId {
98 self.event_id()
99 .expect("all events saved into a relational linked chunk must have a valid event id")
100 }
101}
102
103impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
104where
105 Item: IndexableItem<ItemId = ItemId>,
106 ItemId: Hash + PartialEq + Eq + Clone,
107{
108 pub fn new() -> Self {
110 Self { chunks: Vec::new(), items_chunks: Vec::new(), items: HashMap::new() }
111 }
112
113 pub fn clear(&mut self) {
115 self.chunks.clear();
116 self.items_chunks.clear();
117 self.items.clear();
118 }
119
120 pub fn apply_updates(&mut self, room_id: &RoomId, updates: Vec<Update<Item, Gap>>) {
123 for update in updates {
124 match update {
125 Update::NewItemsChunk { previous, new, next } => {
126 insert_chunk(&mut self.chunks, room_id, previous, new, next);
127 }
128
129 Update::NewGapChunk { previous, new, next, gap } => {
130 insert_chunk(&mut self.chunks, room_id, previous, new, next);
131 self.items_chunks.push(ItemRow {
132 room_id: room_id.to_owned(),
133 position: Position::new(new, 0),
134 item: Either::Gap(gap),
135 });
136 }
137
138 Update::RemoveChunk(chunk_identifier) => {
139 remove_chunk(&mut self.chunks, room_id, chunk_identifier);
140
141 let indices_to_remove = self
142 .items_chunks
143 .iter()
144 .enumerate()
145 .filter_map(
146 |(nth, ItemRow { room_id: room_id_candidate, position, .. })| {
147 (room_id == room_id_candidate
148 && position.chunk_identifier() == chunk_identifier)
149 .then_some(nth)
150 },
151 )
152 .collect::<Vec<_>>();
153
154 for index_to_remove in indices_to_remove.into_iter().rev() {
155 self.items_chunks.remove(index_to_remove);
156 }
157 }
158
159 Update::PushItems { mut at, items } => {
160 for item in items {
161 let item_id = item.id();
162 self.items
163 .entry(room_id.to_owned())
164 .or_default()
165 .insert(item_id.clone(), item);
166 self.items_chunks.push(ItemRow {
167 room_id: room_id.to_owned(),
168 position: at,
169 item: Either::Item(item_id),
170 });
171 at.increment_index();
172 }
173 }
174
175 Update::ReplaceItem { at, item } => {
176 let existing = self
177 .items_chunks
178 .iter_mut()
179 .find(|item| item.position == at)
180 .expect("trying to replace at an unknown position");
181 assert!(
182 matches!(existing.item, Either::Item(..)),
183 "trying to replace a gap with an item"
184 );
185 let item_id = item.id();
186 self.items.entry(room_id.to_owned()).or_default().insert(item_id.clone(), item);
187 existing.item = Either::Item(item_id);
188 }
189
190 Update::RemoveItem { at } => {
191 let mut entry_to_remove = None;
192
193 for (nth, ItemRow { room_id: room_id_candidate, position, .. }) in
194 self.items_chunks.iter_mut().enumerate()
195 {
196 if room_id != room_id_candidate {
198 continue;
199 }
200
201 if *position == at {
203 debug_assert!(entry_to_remove.is_none(), "Found the same entry twice");
204
205 entry_to_remove = Some(nth);
206 }
207
208 if position.chunk_identifier() == at.chunk_identifier()
210 && position.index() > at.index()
211 {
212 position.decrement_index();
213 }
214 }
215
216 self.items_chunks.remove(entry_to_remove.expect("Remove an unknown item"));
217 }
219
220 Update::DetachLastItems { at } => {
221 let indices_to_remove = self
222 .items_chunks
223 .iter()
224 .enumerate()
225 .filter_map(
226 |(nth, ItemRow { room_id: room_id_candidate, position, .. })| {
227 (room_id == room_id_candidate
228 && position.chunk_identifier() == at.chunk_identifier()
229 && position.index() >= at.index())
230 .then_some(nth)
231 },
232 )
233 .collect::<Vec<_>>();
234
235 for index_to_remove in indices_to_remove.into_iter().rev() {
236 self.items_chunks.remove(index_to_remove);
237 }
238 }
239
240 Update::StartReattachItems | Update::EndReattachItems => { }
241
242 Update::Clear => {
243 self.chunks.retain(|chunk| chunk.room_id != room_id);
244 self.items_chunks.retain(|chunk| chunk.room_id != room_id);
245 }
247 }
248 }
249
250 fn insert_chunk(
251 chunks: &mut Vec<ChunkRow>,
252 room_id: &RoomId,
253 previous: Option<ChunkIdentifier>,
254 new: ChunkIdentifier,
255 next: Option<ChunkIdentifier>,
256 ) {
257 if let Some(previous) = previous {
259 let entry_for_previous_chunk = chunks
260 .iter_mut()
261 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
262 room_id == room_id_candidate && *chunk == previous
263 })
264 .expect("Previous chunk should be present");
265
266 entry_for_previous_chunk.next_chunk = Some(new);
268 }
269
270 if let Some(next) = next {
272 let entry_for_next_chunk = chunks
273 .iter_mut()
274 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
275 room_id == room_id_candidate && *chunk == next
276 })
277 .expect("Next chunk should be present");
278
279 entry_for_next_chunk.previous_chunk = Some(new);
281 }
282
283 chunks.push(ChunkRow {
285 room_id: room_id.to_owned(),
286 previous_chunk: previous,
287 chunk: new,
288 next_chunk: next,
289 });
290 }
291
292 fn remove_chunk(
293 chunks: &mut Vec<ChunkRow>,
294 room_id: &RoomId,
295 chunk_to_remove: ChunkIdentifier,
296 ) {
297 let entry_nth_to_remove = chunks
298 .iter()
299 .enumerate()
300 .find_map(|(nth, ChunkRow { room_id: room_id_candidate, chunk, .. })| {
301 (room_id == room_id_candidate && *chunk == chunk_to_remove).then_some(nth)
302 })
303 .expect("Remove an unknown chunk");
304
305 let ChunkRow { room_id, previous_chunk: previous, next_chunk: next, .. } =
306 chunks.remove(entry_nth_to_remove);
307
308 if let Some(previous) = previous {
310 let entry_for_previous_chunk = chunks
311 .iter_mut()
312 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
313 &room_id == room_id_candidate && *chunk == previous
314 })
315 .expect("Previous chunk should be present");
316
317 entry_for_previous_chunk.next_chunk = next;
319 }
320
321 if let Some(next) = next {
323 let entry_for_next_chunk = chunks
324 .iter_mut()
325 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
326 &room_id == room_id_candidate && *chunk == next
327 })
328 .expect("Next chunk should be present");
329
330 entry_for_next_chunk.previous_chunk = previous;
332 }
333 }
334 }
335
336 pub fn unordered_room_items<'a>(
339 &'a self,
340 room_id: &'a RoomId,
341 ) -> impl Iterator<Item = (&'a Item, Position)> {
342 self.items_chunks.iter().filter_map(move |item_row| {
343 if item_row.room_id == room_id {
344 match &item_row.item {
345 Either::Item(item_id) => {
346 Some((self.items.get(room_id)?.get(item_id)?, item_row.position))
347 }
348 Either::Gap(..) => None,
349 }
350 } else {
351 None
352 }
353 })
354 }
355
356 pub fn items(&self) -> impl Iterator<Item = (&Item, &RoomId)> {
361 self.items
362 .iter()
363 .flat_map(|(room_id, items)| items.values().map(|item| (item, room_id.as_ref())))
364 }
365
366 pub fn save_item(&mut self, room_id: OwnedRoomId, item: Item) {
368 let id = item.id();
369 self.items.entry(room_id).or_default().insert(id, item);
370 }
371}
372
373impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
374where
375 Gap: Clone,
376 Item: Clone,
377 ItemId: Hash + PartialEq + Eq,
378{
379 #[doc(hidden)]
384 pub fn load_all_chunks(&self, room_id: &RoomId) -> Result<Vec<RawChunk<Item, Gap>>, String> {
385 self.chunks
386 .iter()
387 .filter(|chunk| chunk.room_id == room_id)
388 .map(|chunk_row| load_raw_chunk(self, chunk_row, room_id))
389 .collect::<Result<Vec<_>, String>>()
390 }
391
392 pub fn load_last_chunk(
393 &self,
394 room_id: &RoomId,
395 ) -> Result<(Option<RawChunk<Item, Gap>>, ChunkIdentifierGenerator), String> {
396 let chunk_identifier_generator = match self
398 .chunks
399 .iter()
400 .filter_map(|chunk_row| (chunk_row.room_id == room_id).then_some(chunk_row.chunk))
401 .max()
402 {
403 Some(last_chunk_identifier) => {
404 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier)
405 }
406 None => ChunkIdentifierGenerator::new_from_scratch(),
407 };
408
409 let mut number_of_chunks = 0;
411 let mut chunk_row = None;
412
413 for chunk_row_candidate in &self.chunks {
414 if chunk_row_candidate.room_id == room_id {
415 number_of_chunks += 1;
416
417 if chunk_row_candidate.next_chunk.is_none() {
418 chunk_row = Some(chunk_row_candidate);
419
420 break;
421 }
422 }
423 }
424
425 let chunk_row = match chunk_row {
426 Some(chunk_row) => chunk_row,
428
429 None if number_of_chunks == 0 => {
432 return Ok((None, chunk_identifier_generator));
433 }
434
435 None => {
440 return Err(
441 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
442 .to_owned(),
443 );
444 }
445 };
446
447 load_raw_chunk(self, chunk_row, room_id)
449 .map(|raw_chunk| (Some(raw_chunk), chunk_identifier_generator))
450 }
451
452 pub fn load_previous_chunk(
453 &self,
454 room_id: &RoomId,
455 before_chunk_identifier: ChunkIdentifier,
456 ) -> Result<Option<RawChunk<Item, Gap>>, String> {
457 let Some(chunk_row) = self.chunks.iter().find(|chunk_row| {
459 chunk_row.room_id == room_id && chunk_row.next_chunk == Some(before_chunk_identifier)
460 }) else {
461 return Ok(None);
463 };
464
465 load_raw_chunk(self, chunk_row, room_id).map(Some)
467 }
468}
469
470impl<ItemId, Item, Gap> Default for RelationalLinkedChunk<ItemId, Item, Gap>
471where
472 Item: IndexableItem<ItemId = ItemId>,
473 ItemId: Hash + PartialEq + Eq + Clone,
474{
475 fn default() -> Self {
476 Self::new()
477 }
478}
479
480fn load_raw_chunk<ItemId, Item, Gap>(
481 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
482 chunk_row: &ChunkRow,
483 room_id: &RoomId,
484) -> Result<RawChunk<Item, Gap>, String>
485where
486 Item: Clone,
487 Gap: Clone,
488 ItemId: Hash + PartialEq + Eq,
489{
490 let mut items = relational_linked_chunk
492 .items_chunks
493 .iter()
494 .filter(|item_row| {
495 item_row.room_id == room_id && item_row.position.chunk_identifier() == chunk_row.chunk
496 })
497 .peekable();
498
499 let Some(first_item) = items.peek() else {
500 return Ok(RawChunk {
502 content: ChunkContent::Items(Vec::new()),
503 previous: chunk_row.previous_chunk,
504 identifier: chunk_row.chunk,
505 next: chunk_row.next_chunk,
506 });
507 };
508
509 Ok(match first_item.item {
510 Either::Item(_) => {
512 let mut collected_items = Vec::new();
514
515 for item_row in items {
516 match &item_row.item {
517 Either::Item(item_id) => {
518 collected_items.push((item_id, item_row.position.index()))
519 }
520
521 Either::Gap(_) => {
522 return Err(format!(
523 "unexpected gap in items chunk {}",
524 chunk_row.chunk.index()
525 ));
526 }
527 }
528 }
529
530 collected_items.sort_unstable_by_key(|(_item, index)| *index);
532
533 RawChunk {
534 content: ChunkContent::Items(
535 collected_items
536 .into_iter()
537 .filter_map(|(item_id, _index)| {
538 relational_linked_chunk.items.get(room_id)?.get(item_id).cloned()
539 })
540 .collect(),
541 ),
542 previous: chunk_row.previous_chunk,
543 identifier: chunk_row.chunk,
544 next: chunk_row.next_chunk,
545 }
546 }
547
548 Either::Gap(ref gap) => {
549 assert!(items.next().is_some(), "we just peeked the gap");
550
551 if items.next().is_some() {
553 return Err(format!(
554 "there shouldn't be more than one item row attached in gap chunk {}",
555 chunk_row.chunk.index()
556 ));
557 }
558
559 RawChunk {
560 content: ChunkContent::Gap(gap.clone()),
561 previous: chunk_row.previous_chunk,
562 identifier: chunk_row.chunk,
563 next: chunk_row.next_chunk,
564 }
565 }
566 })
567}
568
569#[cfg(test)]
570mod tests {
571 use assert_matches::assert_matches;
572 use ruma::room_id;
573
574 use super::{super::lazy_loader::from_all_chunks, ChunkIdentifier as CId, *};
575
576 impl IndexableItem for char {
577 type ItemId = char;
578
579 fn id(&self) -> Self::ItemId {
580 *self
581 }
582 }
583
584 #[test]
585 fn test_new_items_chunk() {
586 let room_id = room_id!("!r0:matrix.org");
587 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
588
589 relational_linked_chunk.apply_updates(
590 room_id,
591 vec![
592 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
594 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
596 Update::NewItemsChunk { previous: None, new: CId::new(2), next: Some(CId::new(0)) },
598 Update::NewItemsChunk {
600 previous: Some(CId::new(2)),
601 new: CId::new(3),
602 next: Some(CId::new(0)),
603 },
604 ],
605 );
606
607 assert_eq!(
609 relational_linked_chunk.chunks,
610 &[
611 ChunkRow {
612 room_id: room_id.to_owned(),
613 previous_chunk: Some(CId::new(3)),
614 chunk: CId::new(0),
615 next_chunk: Some(CId::new(1))
616 },
617 ChunkRow {
618 room_id: room_id.to_owned(),
619 previous_chunk: Some(CId::new(0)),
620 chunk: CId::new(1),
621 next_chunk: None
622 },
623 ChunkRow {
624 room_id: room_id.to_owned(),
625 previous_chunk: None,
626 chunk: CId::new(2),
627 next_chunk: Some(CId::new(3))
628 },
629 ChunkRow {
630 room_id: room_id.to_owned(),
631 previous_chunk: Some(CId::new(2)),
632 chunk: CId::new(3),
633 next_chunk: Some(CId::new(0))
634 },
635 ],
636 );
637 assert!(relational_linked_chunk.items_chunks.is_empty());
639 }
640
641 #[test]
642 fn test_new_gap_chunk() {
643 let room_id = room_id!("!r0:matrix.org");
644 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
645
646 relational_linked_chunk.apply_updates(
647 room_id,
648 vec![
649 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
651 Update::NewGapChunk {
653 previous: Some(CId::new(0)),
654 new: CId::new(1),
655 next: None,
656 gap: (),
657 },
658 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
660 ],
661 );
662
663 assert_eq!(
665 relational_linked_chunk.chunks,
666 &[
667 ChunkRow {
668 room_id: room_id.to_owned(),
669 previous_chunk: None,
670 chunk: CId::new(0),
671 next_chunk: Some(CId::new(1))
672 },
673 ChunkRow {
674 room_id: room_id.to_owned(),
675 previous_chunk: Some(CId::new(0)),
676 chunk: CId::new(1),
677 next_chunk: Some(CId::new(2))
678 },
679 ChunkRow {
680 room_id: room_id.to_owned(),
681 previous_chunk: Some(CId::new(1)),
682 chunk: CId::new(2),
683 next_chunk: None
684 },
685 ],
686 );
687 assert_eq!(
689 relational_linked_chunk.items_chunks,
690 &[ItemRow {
691 room_id: room_id.to_owned(),
692 position: Position::new(CId::new(1), 0),
693 item: Either::Gap(())
694 }],
695 );
696 }
697
698 #[test]
699 fn test_remove_chunk() {
700 let room_id = room_id!("!r0:matrix.org");
701 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
702
703 relational_linked_chunk.apply_updates(
704 room_id,
705 vec![
706 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
708 Update::NewGapChunk {
710 previous: Some(CId::new(0)),
711 new: CId::new(1),
712 next: None,
713 gap: (),
714 },
715 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
717 Update::RemoveChunk(CId::new(1)),
719 ],
720 );
721
722 assert_eq!(
724 relational_linked_chunk.chunks,
725 &[
726 ChunkRow {
727 room_id: room_id.to_owned(),
728 previous_chunk: None,
729 chunk: CId::new(0),
730 next_chunk: Some(CId::new(2))
731 },
732 ChunkRow {
733 room_id: room_id.to_owned(),
734 previous_chunk: Some(CId::new(0)),
735 chunk: CId::new(2),
736 next_chunk: None
737 },
738 ],
739 );
740 assert!(relational_linked_chunk.items_chunks.is_empty());
742 }
743
744 #[test]
745 fn test_push_items() {
746 let room_id = room_id!("!r0:matrix.org");
747 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
748
749 relational_linked_chunk.apply_updates(
750 room_id,
751 vec![
752 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
754 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
756 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
758 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
760 Update::PushItems { at: Position::new(CId::new(0), 3), items: vec!['d', 'e'] },
762 ],
763 );
764
765 assert_eq!(
767 relational_linked_chunk.chunks,
768 &[
769 ChunkRow {
770 room_id: room_id.to_owned(),
771 previous_chunk: None,
772 chunk: CId::new(0),
773 next_chunk: Some(CId::new(1))
774 },
775 ChunkRow {
776 room_id: room_id.to_owned(),
777 previous_chunk: Some(CId::new(0)),
778 chunk: CId::new(1),
779 next_chunk: None
780 },
781 ],
782 );
783 assert_eq!(
785 relational_linked_chunk.items_chunks,
786 &[
787 ItemRow {
788 room_id: room_id.to_owned(),
789 position: Position::new(CId::new(0), 0),
790 item: Either::Item('a')
791 },
792 ItemRow {
793 room_id: room_id.to_owned(),
794 position: Position::new(CId::new(0), 1),
795 item: Either::Item('b')
796 },
797 ItemRow {
798 room_id: room_id.to_owned(),
799 position: Position::new(CId::new(0), 2),
800 item: Either::Item('c')
801 },
802 ItemRow {
803 room_id: room_id.to_owned(),
804 position: Position::new(CId::new(1), 0),
805 item: Either::Item('x')
806 },
807 ItemRow {
808 room_id: room_id.to_owned(),
809 position: Position::new(CId::new(1), 1),
810 item: Either::Item('y')
811 },
812 ItemRow {
813 room_id: room_id.to_owned(),
814 position: Position::new(CId::new(1), 2),
815 item: Either::Item('z')
816 },
817 ItemRow {
818 room_id: room_id.to_owned(),
819 position: Position::new(CId::new(0), 3),
820 item: Either::Item('d')
821 },
822 ItemRow {
823 room_id: room_id.to_owned(),
824 position: Position::new(CId::new(0), 4),
825 item: Either::Item('e')
826 },
827 ],
828 );
829 }
830
831 #[test]
832 fn test_remove_item() {
833 let room_id = room_id!("!r0:matrix.org");
834 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
835
836 relational_linked_chunk.apply_updates(
837 room_id,
838 vec![
839 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
841 Update::PushItems {
843 at: Position::new(CId::new(0), 0),
844 items: vec!['a', 'b', 'c', 'd', 'e'],
845 },
846 Update::RemoveItem { at: Position::new(CId::new(0), 0) },
848 Update::RemoveItem { at: Position::new(CId::new(0), 2) },
850 ],
851 );
852
853 assert_eq!(
855 relational_linked_chunk.chunks,
856 &[ChunkRow {
857 room_id: room_id.to_owned(),
858 previous_chunk: None,
859 chunk: CId::new(0),
860 next_chunk: None
861 }],
862 );
863 assert_eq!(
865 relational_linked_chunk.items_chunks,
866 &[
867 ItemRow {
868 room_id: room_id.to_owned(),
869 position: Position::new(CId::new(0), 0),
870 item: Either::Item('b')
871 },
872 ItemRow {
873 room_id: room_id.to_owned(),
874 position: Position::new(CId::new(0), 1),
875 item: Either::Item('c')
876 },
877 ItemRow {
878 room_id: room_id.to_owned(),
879 position: Position::new(CId::new(0), 2),
880 item: Either::Item('e')
881 },
882 ],
883 );
884 }
885
886 #[test]
887 fn test_detach_last_items() {
888 let room_id = room_id!("!r0:matrix.org");
889 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
890
891 relational_linked_chunk.apply_updates(
892 room_id,
893 vec![
894 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
896 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
898 Update::PushItems {
900 at: Position::new(CId::new(0), 0),
901 items: vec!['a', 'b', 'c', 'd', 'e'],
902 },
903 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
905 Update::DetachLastItems { at: Position::new(CId::new(0), 2) },
907 ],
908 );
909
910 assert_eq!(
912 relational_linked_chunk.chunks,
913 &[
914 ChunkRow {
915 room_id: room_id.to_owned(),
916 previous_chunk: None,
917 chunk: CId::new(0),
918 next_chunk: Some(CId::new(1))
919 },
920 ChunkRow {
921 room_id: room_id.to_owned(),
922 previous_chunk: Some(CId::new(0)),
923 chunk: CId::new(1),
924 next_chunk: None
925 },
926 ],
927 );
928 assert_eq!(
930 relational_linked_chunk.items_chunks,
931 &[
932 ItemRow {
933 room_id: room_id.to_owned(),
934 position: Position::new(CId::new(0), 0),
935 item: Either::Item('a')
936 },
937 ItemRow {
938 room_id: room_id.to_owned(),
939 position: Position::new(CId::new(0), 1),
940 item: Either::Item('b')
941 },
942 ItemRow {
943 room_id: room_id.to_owned(),
944 position: Position::new(CId::new(1), 0),
945 item: Either::Item('x')
946 },
947 ItemRow {
948 room_id: room_id.to_owned(),
949 position: Position::new(CId::new(1), 1),
950 item: Either::Item('y')
951 },
952 ItemRow {
953 room_id: room_id.to_owned(),
954 position: Position::new(CId::new(1), 2),
955 item: Either::Item('z')
956 },
957 ],
958 );
959 }
960
961 #[test]
962 fn test_start_and_end_reattach_items() {
963 let room_id = room_id!("!r0:matrix.org");
964 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
965
966 relational_linked_chunk
967 .apply_updates(room_id, vec![Update::StartReattachItems, Update::EndReattachItems]);
968
969 assert!(relational_linked_chunk.chunks.is_empty());
971 assert!(relational_linked_chunk.items_chunks.is_empty());
972 }
973
974 #[test]
975 fn test_clear() {
976 let r0 = room_id!("!r0:matrix.org");
977 let r1 = room_id!("!r1:matrix.org");
978 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
979
980 relational_linked_chunk.apply_updates(
981 r0,
982 vec![
983 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
985 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
987 ],
988 );
989
990 relational_linked_chunk.apply_updates(
991 r1,
992 vec![
993 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
995 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x'] },
997 ],
998 );
999
1000 assert_eq!(
1002 relational_linked_chunk.chunks,
1003 &[
1004 ChunkRow {
1005 room_id: r0.to_owned(),
1006 previous_chunk: None,
1007 chunk: CId::new(0),
1008 next_chunk: None,
1009 },
1010 ChunkRow {
1011 room_id: r1.to_owned(),
1012 previous_chunk: None,
1013 chunk: CId::new(0),
1014 next_chunk: None,
1015 }
1016 ],
1017 );
1018
1019 assert_eq!(
1021 relational_linked_chunk.items_chunks,
1022 &[
1023 ItemRow {
1024 room_id: r0.to_owned(),
1025 position: Position::new(CId::new(0), 0),
1026 item: Either::Item('a')
1027 },
1028 ItemRow {
1029 room_id: r0.to_owned(),
1030 position: Position::new(CId::new(0), 1),
1031 item: Either::Item('b')
1032 },
1033 ItemRow {
1034 room_id: r0.to_owned(),
1035 position: Position::new(CId::new(0), 2),
1036 item: Either::Item('c')
1037 },
1038 ItemRow {
1039 room_id: r1.to_owned(),
1040 position: Position::new(CId::new(0), 0),
1041 item: Either::Item('x')
1042 },
1043 ],
1044 );
1045
1046 relational_linked_chunk.apply_updates(r0, vec![Update::Clear]);
1048
1049 assert_eq!(
1051 relational_linked_chunk.chunks,
1052 &[ChunkRow {
1053 room_id: r1.to_owned(),
1054 previous_chunk: None,
1055 chunk: CId::new(0),
1056 next_chunk: None,
1057 }],
1058 );
1059
1060 assert_eq!(
1061 relational_linked_chunk.items_chunks,
1062 &[ItemRow {
1063 room_id: r1.to_owned(),
1064 position: Position::new(CId::new(0), 0),
1065 item: Either::Item('x')
1066 },],
1067 );
1068 }
1069
1070 #[test]
1071 fn test_load_empty_linked_chunk() {
1072 let room_id = room_id!("!r0:matrix.org");
1073
1074 let relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1076 let result = relational_linked_chunk.load_all_chunks(room_id).unwrap();
1077 assert!(result.is_empty());
1078 }
1079
1080 #[test]
1081 fn test_load_all_chunks_with_empty_items() {
1082 let room_id = room_id!("!r0:matrix.org");
1083
1084 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1085
1086 relational_linked_chunk.apply_updates(
1088 room_id,
1089 vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }],
1090 );
1091
1092 let lc =
1094 from_all_chunks::<3, _, _>(relational_linked_chunk.load_all_chunks(room_id).unwrap())
1095 .expect("building succeeds")
1096 .expect("this leads to a non-empty linked chunk");
1097
1098 assert_items_eq!(lc, []);
1099 }
1100
1101 #[test]
1102 fn test_rebuild_linked_chunk() {
1103 let room_id = room_id!("!r0:matrix.org");
1104 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1105
1106 relational_linked_chunk.apply_updates(
1107 room_id,
1108 vec![
1109 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1111 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1113 Update::NewGapChunk {
1115 previous: Some(CId::new(0)),
1116 new: CId::new(1),
1117 next: None,
1118 gap: 'g',
1119 },
1120 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
1122 Update::PushItems { at: Position::new(CId::new(2), 0), items: vec!['d', 'e', 'f'] },
1124 ],
1125 );
1126
1127 let lc =
1128 from_all_chunks::<3, _, _>(relational_linked_chunk.load_all_chunks(room_id).unwrap())
1129 .expect("building succeeds")
1130 .expect("this leads to a non-empty linked chunk");
1131
1132 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']);
1134 }
1135
1136 #[test]
1137 fn test_replace_item() {
1138 let room_id = room_id!("!r0:matrix.org");
1139 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1140
1141 relational_linked_chunk.apply_updates(
1142 room_id,
1143 vec![
1144 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1146 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1148 Update::ReplaceItem { at: Position::new(CId::new(0), 1), item: 'B' },
1150 ],
1151 );
1152
1153 assert_eq!(
1155 relational_linked_chunk.chunks,
1156 &[ChunkRow {
1157 room_id: room_id.to_owned(),
1158 previous_chunk: None,
1159 chunk: CId::new(0),
1160 next_chunk: None,
1161 },],
1162 );
1163
1164 assert_eq!(
1166 relational_linked_chunk.items_chunks,
1167 &[
1168 ItemRow {
1169 room_id: room_id.to_owned(),
1170 position: Position::new(CId::new(0), 0),
1171 item: Either::Item('a')
1172 },
1173 ItemRow {
1174 room_id: room_id.to_owned(),
1175 position: Position::new(CId::new(0), 1),
1176 item: Either::Item('B')
1177 },
1178 ItemRow {
1179 room_id: room_id.to_owned(),
1180 position: Position::new(CId::new(0), 2),
1181 item: Either::Item('c')
1182 },
1183 ],
1184 );
1185 }
1186
1187 #[test]
1188 fn test_unordered_events() {
1189 let room_id = room_id!("!r0:matrix.org");
1190 let other_room_id = room_id!("!r1:matrix.org");
1191 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1192
1193 relational_linked_chunk.apply_updates(
1194 room_id,
1195 vec![
1196 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1197 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1198 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
1199 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['d', 'e', 'f'] },
1200 ],
1201 );
1202
1203 relational_linked_chunk.apply_updates(
1204 other_room_id,
1205 vec![
1206 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1207 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x', 'y', 'z'] },
1208 ],
1209 );
1210
1211 let mut events = relational_linked_chunk.unordered_room_items(room_id);
1212
1213 assert_eq!(events.next().unwrap(), (&'a', Position::new(CId::new(0), 0)));
1214 assert_eq!(events.next().unwrap(), (&'b', Position::new(CId::new(0), 1)));
1215 assert_eq!(events.next().unwrap(), (&'c', Position::new(CId::new(0), 2)));
1216 assert_eq!(events.next().unwrap(), (&'d', Position::new(CId::new(1), 0)));
1217 assert_eq!(events.next().unwrap(), (&'e', Position::new(CId::new(1), 1)));
1218 assert_eq!(events.next().unwrap(), (&'f', Position::new(CId::new(1), 2)));
1219 assert!(events.next().is_none());
1220 }
1221
1222 #[test]
1223 fn test_load_last_chunk() {
1224 let room_id = room_id!("!r0:matrix.org");
1225 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1226
1227 {
1229 let (last_chunk, chunk_identifier_generator) =
1230 relational_linked_chunk.load_last_chunk(room_id).unwrap();
1231
1232 assert!(last_chunk.is_none());
1233 assert_eq!(chunk_identifier_generator.current(), 0);
1234 }
1235
1236 {
1238 relational_linked_chunk.apply_updates(
1239 room_id,
1240 vec![
1241 Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1242 Update::PushItems { at: Position::new(CId::new(42), 0), items: vec!['a', 'b'] },
1243 ],
1244 );
1245
1246 let (last_chunk, chunk_identifier_generator) =
1247 relational_linked_chunk.load_last_chunk(room_id).unwrap();
1248
1249 assert_matches!(last_chunk, Some(last_chunk) => {
1250 assert_eq!(last_chunk.identifier, 42);
1251 assert!(last_chunk.previous.is_none());
1252 assert!(last_chunk.next.is_none());
1253 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1254 assert_eq!(items.len(), 2);
1255 assert_eq!(items, &['a', 'b']);
1256 });
1257 });
1258 assert_eq!(chunk_identifier_generator.current(), 42);
1259 }
1260
1261 {
1263 relational_linked_chunk.apply_updates(
1264 room_id,
1265 vec![
1266 Update::NewItemsChunk {
1267 previous: Some(CId::new(42)),
1268 new: CId::new(7),
1269 next: None,
1270 },
1271 Update::PushItems {
1272 at: Position::new(CId::new(7), 0),
1273 items: vec!['c', 'd', 'e'],
1274 },
1275 ],
1276 );
1277
1278 let (last_chunk, chunk_identifier_generator) =
1279 relational_linked_chunk.load_last_chunk(room_id).unwrap();
1280
1281 assert_matches!(last_chunk, Some(last_chunk) => {
1282 assert_eq!(last_chunk.identifier, 7);
1283 assert_matches!(last_chunk.previous, Some(previous) => {
1284 assert_eq!(previous, 42);
1285 });
1286 assert!(last_chunk.next.is_none());
1287 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1288 assert_eq!(items.len(), 3);
1289 assert_eq!(items, &['c', 'd', 'e']);
1290 });
1291 });
1292 assert_eq!(chunk_identifier_generator.current(), 42);
1293 }
1294 }
1295
1296 #[test]
1297 fn test_load_last_chunk_with_a_cycle() {
1298 let room_id = room_id!("!r0:matrix.org");
1299 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1300
1301 relational_linked_chunk.apply_updates(
1302 room_id,
1303 vec![
1304 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1305 Update::NewItemsChunk {
1306 previous: Some(CId::new(0)),
1310 new: CId::new(1),
1311 next: Some(CId::new(0)),
1312 },
1313 ],
1314 );
1315
1316 relational_linked_chunk.load_last_chunk(room_id).unwrap_err();
1317 }
1318
1319 #[test]
1320 fn test_load_previous_chunk() {
1321 let room_id = room_id!("!r0:matrix.org");
1322 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1323
1324 {
1327 let previous_chunk =
1328 relational_linked_chunk.load_previous_chunk(room_id, CId::new(153)).unwrap();
1329
1330 assert!(previous_chunk.is_none());
1331 }
1332
1333 {
1336 relational_linked_chunk.apply_updates(
1337 room_id,
1338 vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
1339 );
1340
1341 let previous_chunk =
1342 relational_linked_chunk.load_previous_chunk(room_id, CId::new(42)).unwrap();
1343
1344 assert!(previous_chunk.is_none());
1345 }
1346
1347 {
1349 relational_linked_chunk.apply_updates(
1350 room_id,
1351 vec![
1352 Update::NewItemsChunk {
1354 previous: None,
1355 new: CId::new(7),
1356 next: Some(CId::new(42)),
1357 },
1358 Update::PushItems {
1359 at: Position::new(CId::new(7), 0),
1360 items: vec!['a', 'b', 'c'],
1361 },
1362 ],
1363 );
1364
1365 let previous_chunk =
1366 relational_linked_chunk.load_previous_chunk(room_id, CId::new(42)).unwrap();
1367
1368 assert_matches!(previous_chunk, Some(previous_chunk) => {
1369 assert_eq!(previous_chunk.identifier, 7);
1370 assert!(previous_chunk.previous.is_none());
1371 assert_matches!(previous_chunk.next, Some(next) => {
1372 assert_eq!(next, 42);
1373 });
1374 assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
1375 assert_eq!(items.len(), 3);
1376 assert_eq!(items, &['a', 'b', 'c']);
1377 });
1378 });
1379 }
1380 }
1381}