1use ruma::{OwnedRoomId, RoomId};
19
20use super::{ChunkContent, ChunkIdentifierGenerator, RawChunk};
21use crate::linked_chunk::{ChunkIdentifier, Position, Update};
22
23#[derive(Debug, PartialEq)]
25struct ChunkRow {
26 room_id: OwnedRoomId,
27 previous_chunk: Option<ChunkIdentifier>,
28 chunk: ChunkIdentifier,
29 next_chunk: Option<ChunkIdentifier>,
30}
31
32#[derive(Debug, PartialEq)]
34struct ItemRow<Item, Gap> {
35 room_id: OwnedRoomId,
36 position: Position,
37 item: Either<Item, Gap>,
38}
39
40#[derive(Debug, PartialEq)]
42enum Either<Item, Gap> {
43 Item(Item),
45
46 Gap(Gap),
48}
49
50#[derive(Debug)]
69pub struct RelationalLinkedChunk<Item, Gap> {
70 chunks: Vec<ChunkRow>,
72
73 items: Vec<ItemRow<Item, Gap>>,
75}
76
77impl<Item, Gap> RelationalLinkedChunk<Item, Gap> {
78 pub fn new() -> Self {
80 Self { chunks: Vec::new(), items: Vec::new() }
81 }
82
83 pub fn clear(&mut self) {
85 self.chunks.clear();
86 self.items.clear();
87 }
88
89 pub fn apply_updates(&mut self, room_id: &RoomId, updates: Vec<Update<Item, Gap>>) {
92 for update in updates {
93 match update {
94 Update::NewItemsChunk { previous, new, next } => {
95 insert_chunk(&mut self.chunks, room_id, previous, new, next);
96 }
97
98 Update::NewGapChunk { previous, new, next, gap } => {
99 insert_chunk(&mut self.chunks, room_id, previous, new, next);
100 self.items.push(ItemRow {
101 room_id: room_id.to_owned(),
102 position: Position::new(new, 0),
103 item: Either::Gap(gap),
104 });
105 }
106
107 Update::RemoveChunk(chunk_identifier) => {
108 remove_chunk(&mut self.chunks, room_id, chunk_identifier);
109
110 let indices_to_remove = self
111 .items
112 .iter()
113 .enumerate()
114 .filter_map(
115 |(nth, ItemRow { room_id: room_id_candidate, position, .. })| {
116 (room_id == room_id_candidate
117 && position.chunk_identifier() == chunk_identifier)
118 .then_some(nth)
119 },
120 )
121 .collect::<Vec<_>>();
122
123 for index_to_remove in indices_to_remove.into_iter().rev() {
124 self.items.remove(index_to_remove);
125 }
126 }
127
128 Update::PushItems { mut at, items } => {
129 for item in items {
130 self.items.push(ItemRow {
131 room_id: room_id.to_owned(),
132 position: at,
133 item: Either::Item(item),
134 });
135 at.increment_index();
136 }
137 }
138
139 Update::ReplaceItem { at, item } => {
140 let existing = self
141 .items
142 .iter_mut()
143 .find(|item| item.position == at)
144 .expect("trying to replace at an unknown position");
145 assert!(
146 matches!(existing.item, Either::Item(..)),
147 "trying to replace a gap with an item"
148 );
149 existing.item = Either::Item(item);
150 }
151
152 Update::RemoveItem { at } => {
153 let mut entry_to_remove = None;
154
155 for (nth, ItemRow { room_id: room_id_candidate, position, .. }) in
156 self.items.iter_mut().enumerate()
157 {
158 if room_id != room_id_candidate {
160 continue;
161 }
162
163 if *position == at {
165 debug_assert!(entry_to_remove.is_none(), "Found the same entry twice");
166
167 entry_to_remove = Some(nth);
168 }
169
170 if position.chunk_identifier() == at.chunk_identifier()
172 && position.index() > at.index()
173 {
174 position.decrement_index();
175 }
176 }
177
178 self.items.remove(entry_to_remove.expect("Remove an unknown item"));
179 }
180
181 Update::DetachLastItems { at } => {
182 let indices_to_remove = self
183 .items
184 .iter()
185 .enumerate()
186 .filter_map(
187 |(nth, ItemRow { room_id: room_id_candidate, position, .. })| {
188 (room_id == room_id_candidate
189 && position.chunk_identifier() == at.chunk_identifier()
190 && position.index() >= at.index())
191 .then_some(nth)
192 },
193 )
194 .collect::<Vec<_>>();
195
196 for index_to_remove in indices_to_remove.into_iter().rev() {
197 self.items.remove(index_to_remove);
198 }
199 }
200
201 Update::StartReattachItems | Update::EndReattachItems => { }
202
203 Update::Clear => {
204 self.chunks.retain(|chunk| chunk.room_id != room_id);
205 self.items.retain(|chunk| chunk.room_id != room_id);
206 }
207 }
208 }
209
210 fn insert_chunk(
211 chunks: &mut Vec<ChunkRow>,
212 room_id: &RoomId,
213 previous: Option<ChunkIdentifier>,
214 new: ChunkIdentifier,
215 next: Option<ChunkIdentifier>,
216 ) {
217 if let Some(previous) = previous {
219 let entry_for_previous_chunk = chunks
220 .iter_mut()
221 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
222 room_id == room_id_candidate && *chunk == previous
223 })
224 .expect("Previous chunk should be present");
225
226 entry_for_previous_chunk.next_chunk = Some(new);
228 }
229
230 if let Some(next) = next {
232 let entry_for_next_chunk = chunks
233 .iter_mut()
234 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
235 room_id == room_id_candidate && *chunk == next
236 })
237 .expect("Next chunk should be present");
238
239 entry_for_next_chunk.previous_chunk = Some(new);
241 }
242
243 chunks.push(ChunkRow {
245 room_id: room_id.to_owned(),
246 previous_chunk: previous,
247 chunk: new,
248 next_chunk: next,
249 });
250 }
251
252 fn remove_chunk(
253 chunks: &mut Vec<ChunkRow>,
254 room_id: &RoomId,
255 chunk_to_remove: ChunkIdentifier,
256 ) {
257 let entry_nth_to_remove = chunks
258 .iter()
259 .enumerate()
260 .find_map(|(nth, ChunkRow { room_id: room_id_candidate, chunk, .. })| {
261 (room_id == room_id_candidate && *chunk == chunk_to_remove).then_some(nth)
262 })
263 .expect("Remove an unknown chunk");
264
265 let ChunkRow { room_id, previous_chunk: previous, next_chunk: next, .. } =
266 chunks.remove(entry_nth_to_remove);
267
268 if let Some(previous) = previous {
270 let entry_for_previous_chunk = chunks
271 .iter_mut()
272 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
273 &room_id == room_id_candidate && *chunk == previous
274 })
275 .expect("Previous chunk should be present");
276
277 entry_for_previous_chunk.next_chunk = next;
279 }
280
281 if let Some(next) = next {
283 let entry_for_next_chunk = chunks
284 .iter_mut()
285 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
286 &room_id == room_id_candidate && *chunk == next
287 })
288 .expect("Next chunk should be present");
289
290 entry_for_next_chunk.previous_chunk = previous;
292 }
293 }
294 }
295
296 pub fn unordered_events<'a>(&'a self, room_id: &'a RoomId) -> impl Iterator<Item = &'a Item> {
299 self.items.iter().filter_map(move |item_row| {
300 if item_row.room_id == room_id {
301 match &item_row.item {
302 Either::Item(item) => Some(item),
303 Either::Gap(..) => None,
304 }
305 } else {
306 None
307 }
308 })
309 }
310}
311
312impl<Item, Gap> RelationalLinkedChunk<Item, Gap>
313where
314 Gap: Clone,
315 Item: Clone,
316{
317 #[doc(hidden)]
322 pub fn load_all_chunks(&self, room_id: &RoomId) -> Result<Vec<RawChunk<Item, Gap>>, String> {
323 self.chunks
324 .iter()
325 .filter(|chunk| chunk.room_id == room_id)
326 .map(|chunk_row| load_raw_chunk(self, chunk_row, room_id))
327 .collect::<Result<Vec<_>, String>>()
328 }
329
330 pub fn load_last_chunk(
331 &self,
332 room_id: &RoomId,
333 ) -> Result<(Option<RawChunk<Item, Gap>>, ChunkIdentifierGenerator), String> {
334 let chunk_identifier_generator = match self
336 .chunks
337 .iter()
338 .filter_map(|chunk_row| (chunk_row.room_id == room_id).then_some(chunk_row.chunk))
339 .max()
340 {
341 Some(last_chunk_identifier) => {
342 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier)
343 }
344 None => ChunkIdentifierGenerator::new_from_scratch(),
345 };
346
347 let mut number_of_chunks = 0;
349 let mut chunk_row = None;
350
351 for chunk_row_candidate in &self.chunks {
352 if chunk_row_candidate.room_id == room_id {
353 number_of_chunks += 1;
354
355 if chunk_row_candidate.next_chunk.is_none() {
356 chunk_row = Some(chunk_row_candidate);
357
358 break;
359 }
360 }
361 }
362
363 let chunk_row = match chunk_row {
364 Some(chunk_row) => chunk_row,
366
367 None if number_of_chunks == 0 => {
370 return Ok((None, chunk_identifier_generator));
371 }
372
373 None => {
378 return Err(
379 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
380 .to_owned(),
381 );
382 }
383 };
384
385 load_raw_chunk(self, chunk_row, room_id)
387 .map(|raw_chunk| (Some(raw_chunk), chunk_identifier_generator))
388 }
389
390 pub fn load_previous_chunk(
391 &self,
392 room_id: &RoomId,
393 before_chunk_identifier: ChunkIdentifier,
394 ) -> Result<Option<RawChunk<Item, Gap>>, String> {
395 let Some(chunk_row) = self.chunks.iter().find(|chunk_row| {
397 chunk_row.room_id == room_id && chunk_row.next_chunk == Some(before_chunk_identifier)
398 }) else {
399 return Ok(None);
401 };
402
403 load_raw_chunk(self, chunk_row, room_id).map(Some)
405 }
406}
407
408impl<Item, Gap> Default for RelationalLinkedChunk<Item, Gap> {
409 fn default() -> Self {
410 Self::new()
411 }
412}
413
414fn load_raw_chunk<Item, Gap>(
415 relational_linked_chunk: &RelationalLinkedChunk<Item, Gap>,
416 chunk_row: &ChunkRow,
417 room_id: &RoomId,
418) -> Result<RawChunk<Item, Gap>, String>
419where
420 Item: Clone,
421 Gap: Clone,
422{
423 let mut items = relational_linked_chunk
425 .items
426 .iter()
427 .filter(|item_row| {
428 item_row.room_id == room_id && item_row.position.chunk_identifier() == chunk_row.chunk
429 })
430 .peekable();
431
432 let Some(first_item) = items.peek() else {
433 return Ok(RawChunk {
435 content: ChunkContent::Items(Vec::new()),
436 previous: chunk_row.previous_chunk,
437 identifier: chunk_row.chunk,
438 next: chunk_row.next_chunk,
439 });
440 };
441
442 Ok(match first_item.item {
443 Either::Item(_) => {
445 let mut collected_items = Vec::new();
447
448 for item_row in items {
449 match &item_row.item {
450 Either::Item(item_value) => {
451 collected_items.push((item_value.clone(), item_row.position.index()))
452 }
453
454 Either::Gap(_) => {
455 return Err(format!(
456 "unexpected gap in items chunk {}",
457 chunk_row.chunk.index()
458 ));
459 }
460 }
461 }
462
463 collected_items.sort_unstable_by_key(|(_item, index)| *index);
465
466 RawChunk {
467 content: ChunkContent::Items(
468 collected_items.into_iter().map(|(item, _index)| item).collect(),
469 ),
470 previous: chunk_row.previous_chunk,
471 identifier: chunk_row.chunk,
472 next: chunk_row.next_chunk,
473 }
474 }
475
476 Either::Gap(ref gap) => {
477 assert!(items.next().is_some(), "we just peeked the gap");
478
479 if items.next().is_some() {
481 return Err(format!(
482 "there shouldn't be more than one item row attached in gap chunk {}",
483 chunk_row.chunk.index()
484 ));
485 }
486
487 RawChunk {
488 content: ChunkContent::Gap(gap.clone()),
489 previous: chunk_row.previous_chunk,
490 identifier: chunk_row.chunk,
491 next: chunk_row.next_chunk,
492 }
493 }
494 })
495}
496
497#[cfg(test)]
498mod tests {
499 use assert_matches::assert_matches;
500 use ruma::room_id;
501
502 use super::{super::lazy_loader::from_all_chunks, ChunkIdentifier as CId, *};
503
504 #[test]
505 fn test_new_items_chunk() {
506 let room_id = room_id!("!r0:matrix.org");
507 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
508
509 relational_linked_chunk.apply_updates(
510 room_id,
511 vec![
512 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
514 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
516 Update::NewItemsChunk { previous: None, new: CId::new(2), next: Some(CId::new(0)) },
518 Update::NewItemsChunk {
520 previous: Some(CId::new(2)),
521 new: CId::new(3),
522 next: Some(CId::new(0)),
523 },
524 ],
525 );
526
527 assert_eq!(
529 relational_linked_chunk.chunks,
530 &[
531 ChunkRow {
532 room_id: room_id.to_owned(),
533 previous_chunk: Some(CId::new(3)),
534 chunk: CId::new(0),
535 next_chunk: Some(CId::new(1))
536 },
537 ChunkRow {
538 room_id: room_id.to_owned(),
539 previous_chunk: Some(CId::new(0)),
540 chunk: CId::new(1),
541 next_chunk: None
542 },
543 ChunkRow {
544 room_id: room_id.to_owned(),
545 previous_chunk: None,
546 chunk: CId::new(2),
547 next_chunk: Some(CId::new(3))
548 },
549 ChunkRow {
550 room_id: room_id.to_owned(),
551 previous_chunk: Some(CId::new(2)),
552 chunk: CId::new(3),
553 next_chunk: Some(CId::new(0))
554 },
555 ],
556 );
557 assert!(relational_linked_chunk.items.is_empty());
559 }
560
561 #[test]
562 fn test_new_gap_chunk() {
563 let room_id = room_id!("!r0:matrix.org");
564 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
565
566 relational_linked_chunk.apply_updates(
567 room_id,
568 vec![
569 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
571 Update::NewGapChunk {
573 previous: Some(CId::new(0)),
574 new: CId::new(1),
575 next: None,
576 gap: (),
577 },
578 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
580 ],
581 );
582
583 assert_eq!(
585 relational_linked_chunk.chunks,
586 &[
587 ChunkRow {
588 room_id: room_id.to_owned(),
589 previous_chunk: None,
590 chunk: CId::new(0),
591 next_chunk: Some(CId::new(1))
592 },
593 ChunkRow {
594 room_id: room_id.to_owned(),
595 previous_chunk: Some(CId::new(0)),
596 chunk: CId::new(1),
597 next_chunk: Some(CId::new(2))
598 },
599 ChunkRow {
600 room_id: room_id.to_owned(),
601 previous_chunk: Some(CId::new(1)),
602 chunk: CId::new(2),
603 next_chunk: None
604 },
605 ],
606 );
607 assert_eq!(
609 relational_linked_chunk.items,
610 &[ItemRow {
611 room_id: room_id.to_owned(),
612 position: Position::new(CId::new(1), 0),
613 item: Either::Gap(())
614 }],
615 );
616 }
617
618 #[test]
619 fn test_remove_chunk() {
620 let room_id = room_id!("!r0:matrix.org");
621 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
622
623 relational_linked_chunk.apply_updates(
624 room_id,
625 vec![
626 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
628 Update::NewGapChunk {
630 previous: Some(CId::new(0)),
631 new: CId::new(1),
632 next: None,
633 gap: (),
634 },
635 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
637 Update::RemoveChunk(CId::new(1)),
639 ],
640 );
641
642 assert_eq!(
644 relational_linked_chunk.chunks,
645 &[
646 ChunkRow {
647 room_id: room_id.to_owned(),
648 previous_chunk: None,
649 chunk: CId::new(0),
650 next_chunk: Some(CId::new(2))
651 },
652 ChunkRow {
653 room_id: room_id.to_owned(),
654 previous_chunk: Some(CId::new(0)),
655 chunk: CId::new(2),
656 next_chunk: None
657 },
658 ],
659 );
660 assert!(relational_linked_chunk.items.is_empty());
662 }
663
664 #[test]
665 fn test_push_items() {
666 let room_id = room_id!("!r0:matrix.org");
667 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
668
669 relational_linked_chunk.apply_updates(
670 room_id,
671 vec![
672 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
674 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
676 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
678 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
680 Update::PushItems { at: Position::new(CId::new(0), 3), items: vec!['d', 'e'] },
682 ],
683 );
684
685 assert_eq!(
687 relational_linked_chunk.chunks,
688 &[
689 ChunkRow {
690 room_id: room_id.to_owned(),
691 previous_chunk: None,
692 chunk: CId::new(0),
693 next_chunk: Some(CId::new(1))
694 },
695 ChunkRow {
696 room_id: room_id.to_owned(),
697 previous_chunk: Some(CId::new(0)),
698 chunk: CId::new(1),
699 next_chunk: None
700 },
701 ],
702 );
703 assert_eq!(
705 relational_linked_chunk.items,
706 &[
707 ItemRow {
708 room_id: room_id.to_owned(),
709 position: Position::new(CId::new(0), 0),
710 item: Either::Item('a')
711 },
712 ItemRow {
713 room_id: room_id.to_owned(),
714 position: Position::new(CId::new(0), 1),
715 item: Either::Item('b')
716 },
717 ItemRow {
718 room_id: room_id.to_owned(),
719 position: Position::new(CId::new(0), 2),
720 item: Either::Item('c')
721 },
722 ItemRow {
723 room_id: room_id.to_owned(),
724 position: Position::new(CId::new(1), 0),
725 item: Either::Item('x')
726 },
727 ItemRow {
728 room_id: room_id.to_owned(),
729 position: Position::new(CId::new(1), 1),
730 item: Either::Item('y')
731 },
732 ItemRow {
733 room_id: room_id.to_owned(),
734 position: Position::new(CId::new(1), 2),
735 item: Either::Item('z')
736 },
737 ItemRow {
738 room_id: room_id.to_owned(),
739 position: Position::new(CId::new(0), 3),
740 item: Either::Item('d')
741 },
742 ItemRow {
743 room_id: room_id.to_owned(),
744 position: Position::new(CId::new(0), 4),
745 item: Either::Item('e')
746 },
747 ],
748 );
749 }
750
751 #[test]
752 fn test_remove_item() {
753 let room_id = room_id!("!r0:matrix.org");
754 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
755
756 relational_linked_chunk.apply_updates(
757 room_id,
758 vec![
759 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
761 Update::PushItems {
763 at: Position::new(CId::new(0), 0),
764 items: vec!['a', 'b', 'c', 'd', 'e'],
765 },
766 Update::RemoveItem { at: Position::new(CId::new(0), 0) },
768 Update::RemoveItem { at: Position::new(CId::new(0), 2) },
770 ],
771 );
772
773 assert_eq!(
775 relational_linked_chunk.chunks,
776 &[ChunkRow {
777 room_id: room_id.to_owned(),
778 previous_chunk: None,
779 chunk: CId::new(0),
780 next_chunk: None
781 }],
782 );
783 assert_eq!(
785 relational_linked_chunk.items,
786 &[
787 ItemRow {
788 room_id: room_id.to_owned(),
789 position: Position::new(CId::new(0), 0),
790 item: Either::Item('b')
791 },
792 ItemRow {
793 room_id: room_id.to_owned(),
794 position: Position::new(CId::new(0), 1),
795 item: Either::Item('c')
796 },
797 ItemRow {
798 room_id: room_id.to_owned(),
799 position: Position::new(CId::new(0), 2),
800 item: Either::Item('e')
801 },
802 ],
803 );
804 }
805
806 #[test]
807 fn test_detach_last_items() {
808 let room_id = room_id!("!r0:matrix.org");
809 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
810
811 relational_linked_chunk.apply_updates(
812 room_id,
813 vec![
814 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
816 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
818 Update::PushItems {
820 at: Position::new(CId::new(0), 0),
821 items: vec!['a', 'b', 'c', 'd', 'e'],
822 },
823 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
825 Update::DetachLastItems { at: Position::new(CId::new(0), 2) },
827 ],
828 );
829
830 assert_eq!(
832 relational_linked_chunk.chunks,
833 &[
834 ChunkRow {
835 room_id: room_id.to_owned(),
836 previous_chunk: None,
837 chunk: CId::new(0),
838 next_chunk: Some(CId::new(1))
839 },
840 ChunkRow {
841 room_id: room_id.to_owned(),
842 previous_chunk: Some(CId::new(0)),
843 chunk: CId::new(1),
844 next_chunk: None
845 },
846 ],
847 );
848 assert_eq!(
850 relational_linked_chunk.items,
851 &[
852 ItemRow {
853 room_id: room_id.to_owned(),
854 position: Position::new(CId::new(0), 0),
855 item: Either::Item('a')
856 },
857 ItemRow {
858 room_id: room_id.to_owned(),
859 position: Position::new(CId::new(0), 1),
860 item: Either::Item('b')
861 },
862 ItemRow {
863 room_id: room_id.to_owned(),
864 position: Position::new(CId::new(1), 0),
865 item: Either::Item('x')
866 },
867 ItemRow {
868 room_id: room_id.to_owned(),
869 position: Position::new(CId::new(1), 1),
870 item: Either::Item('y')
871 },
872 ItemRow {
873 room_id: room_id.to_owned(),
874 position: Position::new(CId::new(1), 2),
875 item: Either::Item('z')
876 },
877 ],
878 );
879 }
880
881 #[test]
882 fn test_start_and_end_reattach_items() {
883 let room_id = room_id!("!r0:matrix.org");
884 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
885
886 relational_linked_chunk
887 .apply_updates(room_id, vec![Update::StartReattachItems, Update::EndReattachItems]);
888
889 assert!(relational_linked_chunk.chunks.is_empty());
891 assert!(relational_linked_chunk.items.is_empty());
892 }
893
894 #[test]
895 fn test_clear() {
896 let r0 = room_id!("!r0:matrix.org");
897 let r1 = room_id!("!r1:matrix.org");
898 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
899
900 relational_linked_chunk.apply_updates(
901 r0,
902 vec![
903 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
905 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
907 ],
908 );
909
910 relational_linked_chunk.apply_updates(
911 r1,
912 vec![
913 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
915 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x'] },
917 ],
918 );
919
920 assert_eq!(
922 relational_linked_chunk.chunks,
923 &[
924 ChunkRow {
925 room_id: r0.to_owned(),
926 previous_chunk: None,
927 chunk: CId::new(0),
928 next_chunk: None,
929 },
930 ChunkRow {
931 room_id: r1.to_owned(),
932 previous_chunk: None,
933 chunk: CId::new(0),
934 next_chunk: None,
935 }
936 ],
937 );
938
939 assert_eq!(
941 relational_linked_chunk.items,
942 &[
943 ItemRow {
944 room_id: r0.to_owned(),
945 position: Position::new(CId::new(0), 0),
946 item: Either::Item('a')
947 },
948 ItemRow {
949 room_id: r0.to_owned(),
950 position: Position::new(CId::new(0), 1),
951 item: Either::Item('b')
952 },
953 ItemRow {
954 room_id: r0.to_owned(),
955 position: Position::new(CId::new(0), 2),
956 item: Either::Item('c')
957 },
958 ItemRow {
959 room_id: r1.to_owned(),
960 position: Position::new(CId::new(0), 0),
961 item: Either::Item('x')
962 },
963 ],
964 );
965
966 relational_linked_chunk.apply_updates(r0, vec![Update::Clear]);
968
969 assert_eq!(
971 relational_linked_chunk.chunks,
972 &[ChunkRow {
973 room_id: r1.to_owned(),
974 previous_chunk: None,
975 chunk: CId::new(0),
976 next_chunk: None,
977 }],
978 );
979
980 assert_eq!(
981 relational_linked_chunk.items,
982 &[ItemRow {
983 room_id: r1.to_owned(),
984 position: Position::new(CId::new(0), 0),
985 item: Either::Item('x')
986 },],
987 );
988 }
989
990 #[test]
991 fn test_load_empty_linked_chunk() {
992 let room_id = room_id!("!r0:matrix.org");
993
994 let relational_linked_chunk = RelationalLinkedChunk::<char, char>::new();
996 let result = relational_linked_chunk.load_all_chunks(room_id).unwrap();
997 assert!(result.is_empty());
998 }
999
1000 #[test]
1001 fn test_load_all_chunks_with_empty_items() {
1002 let room_id = room_id!("!r0:matrix.org");
1003
1004 let mut relational_linked_chunk = RelationalLinkedChunk::<char, char>::new();
1005
1006 relational_linked_chunk.apply_updates(
1008 room_id,
1009 vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }],
1010 );
1011
1012 let lc =
1014 from_all_chunks::<3, _, _>(relational_linked_chunk.load_all_chunks(room_id).unwrap())
1015 .expect("building succeeds")
1016 .expect("this leads to a non-empty linked chunk");
1017
1018 assert_items_eq!(lc, []);
1019 }
1020
1021 #[test]
1022 fn test_rebuild_linked_chunk() {
1023 let room_id = room_id!("!r0:matrix.org");
1024 let mut relational_linked_chunk = RelationalLinkedChunk::<char, char>::new();
1025
1026 relational_linked_chunk.apply_updates(
1027 room_id,
1028 vec![
1029 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1031 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1033 Update::NewGapChunk {
1035 previous: Some(CId::new(0)),
1036 new: CId::new(1),
1037 next: None,
1038 gap: 'g',
1039 },
1040 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
1042 Update::PushItems { at: Position::new(CId::new(2), 0), items: vec!['d', 'e', 'f'] },
1044 ],
1045 );
1046
1047 let lc =
1048 from_all_chunks::<3, _, _>(relational_linked_chunk.load_all_chunks(room_id).unwrap())
1049 .expect("building succeeds")
1050 .expect("this leads to a non-empty linked chunk");
1051
1052 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']);
1054 }
1055
1056 #[test]
1057 fn test_replace_item() {
1058 let room_id = room_id!("!r0:matrix.org");
1059 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1060
1061 relational_linked_chunk.apply_updates(
1062 room_id,
1063 vec![
1064 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1066 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1068 Update::ReplaceItem { at: Position::new(CId::new(0), 1), item: 'B' },
1070 ],
1071 );
1072
1073 assert_eq!(
1075 relational_linked_chunk.chunks,
1076 &[ChunkRow {
1077 room_id: room_id.to_owned(),
1078 previous_chunk: None,
1079 chunk: CId::new(0),
1080 next_chunk: None,
1081 },],
1082 );
1083
1084 assert_eq!(
1086 relational_linked_chunk.items,
1087 &[
1088 ItemRow {
1089 room_id: room_id.to_owned(),
1090 position: Position::new(CId::new(0), 0),
1091 item: Either::Item('a')
1092 },
1093 ItemRow {
1094 room_id: room_id.to_owned(),
1095 position: Position::new(CId::new(0), 1),
1096 item: Either::Item('B')
1097 },
1098 ItemRow {
1099 room_id: room_id.to_owned(),
1100 position: Position::new(CId::new(0), 2),
1101 item: Either::Item('c')
1102 },
1103 ],
1104 );
1105 }
1106
1107 #[test]
1108 fn test_unordered_events() {
1109 let room_id = room_id!("!r0:matrix.org");
1110 let other_room_id = room_id!("!r1:matrix.org");
1111 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1112
1113 relational_linked_chunk.apply_updates(
1114 room_id,
1115 vec![
1116 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1117 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1118 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
1119 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['d', 'e', 'f'] },
1120 ],
1121 );
1122
1123 relational_linked_chunk.apply_updates(
1124 other_room_id,
1125 vec![
1126 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1127 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x', 'y', 'z'] },
1128 ],
1129 );
1130
1131 let mut events = relational_linked_chunk.unordered_events(room_id);
1132
1133 assert_eq!(*events.next().unwrap(), 'a');
1134 assert_eq!(*events.next().unwrap(), 'b');
1135 assert_eq!(*events.next().unwrap(), 'c');
1136 assert_eq!(*events.next().unwrap(), 'd');
1137 assert_eq!(*events.next().unwrap(), 'e');
1138 assert_eq!(*events.next().unwrap(), 'f');
1139 assert!(events.next().is_none());
1140 }
1141
1142 #[test]
1143 fn test_load_last_chunk() {
1144 let room_id = room_id!("!r0:matrix.org");
1145 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1146
1147 {
1149 let (last_chunk, chunk_identifier_generator) =
1150 relational_linked_chunk.load_last_chunk(room_id).unwrap();
1151
1152 assert!(last_chunk.is_none());
1153 assert_eq!(chunk_identifier_generator.current(), 0);
1154 }
1155
1156 {
1158 relational_linked_chunk.apply_updates(
1159 room_id,
1160 vec![
1161 Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1162 Update::PushItems { at: Position::new(CId::new(42), 0), items: vec!['a', 'b'] },
1163 ],
1164 );
1165
1166 let (last_chunk, chunk_identifier_generator) =
1167 relational_linked_chunk.load_last_chunk(room_id).unwrap();
1168
1169 assert_matches!(last_chunk, Some(last_chunk) => {
1170 assert_eq!(last_chunk.identifier, 42);
1171 assert!(last_chunk.previous.is_none());
1172 assert!(last_chunk.next.is_none());
1173 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1174 assert_eq!(items.len(), 2);
1175 assert_eq!(items, &['a', 'b']);
1176 });
1177 });
1178 assert_eq!(chunk_identifier_generator.current(), 42);
1179 }
1180
1181 {
1183 relational_linked_chunk.apply_updates(
1184 room_id,
1185 vec![
1186 Update::NewItemsChunk {
1187 previous: Some(CId::new(42)),
1188 new: CId::new(7),
1189 next: None,
1190 },
1191 Update::PushItems {
1192 at: Position::new(CId::new(7), 0),
1193 items: vec!['c', 'd', 'e'],
1194 },
1195 ],
1196 );
1197
1198 let (last_chunk, chunk_identifier_generator) =
1199 relational_linked_chunk.load_last_chunk(room_id).unwrap();
1200
1201 assert_matches!(last_chunk, Some(last_chunk) => {
1202 assert_eq!(last_chunk.identifier, 7);
1203 assert_matches!(last_chunk.previous, Some(previous) => {
1204 assert_eq!(previous, 42);
1205 });
1206 assert!(last_chunk.next.is_none());
1207 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1208 assert_eq!(items.len(), 3);
1209 assert_eq!(items, &['c', 'd', 'e']);
1210 });
1211 });
1212 assert_eq!(chunk_identifier_generator.current(), 42);
1213 }
1214 }
1215
1216 #[test]
1217 fn test_load_last_chunk_with_a_cycle() {
1218 let room_id = room_id!("!r0:matrix.org");
1219 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1220
1221 relational_linked_chunk.apply_updates(
1222 room_id,
1223 vec![
1224 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1225 Update::NewItemsChunk {
1226 previous: Some(CId::new(0)),
1230 new: CId::new(1),
1231 next: Some(CId::new(0)),
1232 },
1233 ],
1234 );
1235
1236 relational_linked_chunk.load_last_chunk(room_id).unwrap_err();
1237 }
1238
1239 #[test]
1240 fn test_load_previous_chunk() {
1241 let room_id = room_id!("!r0:matrix.org");
1242 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1243
1244 {
1247 let previous_chunk =
1248 relational_linked_chunk.load_previous_chunk(room_id, CId::new(153)).unwrap();
1249
1250 assert!(previous_chunk.is_none());
1251 }
1252
1253 {
1256 relational_linked_chunk.apply_updates(
1257 room_id,
1258 vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
1259 );
1260
1261 let previous_chunk =
1262 relational_linked_chunk.load_previous_chunk(room_id, CId::new(42)).unwrap();
1263
1264 assert!(previous_chunk.is_none());
1265 }
1266
1267 {
1269 relational_linked_chunk.apply_updates(
1270 room_id,
1271 vec![
1272 Update::NewItemsChunk {
1274 previous: None,
1275 new: CId::new(7),
1276 next: Some(CId::new(42)),
1277 },
1278 Update::PushItems {
1279 at: Position::new(CId::new(7), 0),
1280 items: vec!['a', 'b', 'c'],
1281 },
1282 ],
1283 );
1284
1285 let previous_chunk =
1286 relational_linked_chunk.load_previous_chunk(room_id, CId::new(42)).unwrap();
1287
1288 assert_matches!(previous_chunk, Some(previous_chunk) => {
1289 assert_eq!(previous_chunk.identifier, 7);
1290 assert!(previous_chunk.previous.is_none());
1291 assert_matches!(previous_chunk.next, Some(next) => {
1292 assert_eq!(next, 42);
1293 });
1294 assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
1295 assert_eq!(items.len(), 3);
1296 assert_eq!(items, &['a', 'b', 'c']);
1297 });
1298 });
1299 }
1300 }
1301}