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_room_items<'a>(
299 &'a self,
300 room_id: &'a RoomId,
301 ) -> impl Iterator<Item = (&'a Item, Position)> {
302 self.items.iter().filter_map(move |item_row| {
303 if item_row.room_id == room_id {
304 match &item_row.item {
305 Either::Item(item) => Some((item, item_row.position)),
306 Either::Gap(..) => None,
307 }
308 } else {
309 None
310 }
311 })
312 }
313
314 pub fn items(&self) -> impl Iterator<Item = (Position, &Item, &RoomId)> {
316 self.items.iter().filter_map(|item_row| {
317 if let Either::Item(item) = &item_row.item {
318 Some((item_row.position, item, item_row.room_id.as_ref()))
319 } else {
320 None
321 }
322 })
323 }
324}
325
326impl<Item, Gap> RelationalLinkedChunk<Item, Gap>
327where
328 Gap: Clone,
329 Item: Clone,
330{
331 #[doc(hidden)]
336 pub fn load_all_chunks(&self, room_id: &RoomId) -> Result<Vec<RawChunk<Item, Gap>>, String> {
337 self.chunks
338 .iter()
339 .filter(|chunk| chunk.room_id == room_id)
340 .map(|chunk_row| load_raw_chunk(self, chunk_row, room_id))
341 .collect::<Result<Vec<_>, String>>()
342 }
343
344 pub fn load_last_chunk(
345 &self,
346 room_id: &RoomId,
347 ) -> Result<(Option<RawChunk<Item, Gap>>, ChunkIdentifierGenerator), String> {
348 let chunk_identifier_generator = match self
350 .chunks
351 .iter()
352 .filter_map(|chunk_row| (chunk_row.room_id == room_id).then_some(chunk_row.chunk))
353 .max()
354 {
355 Some(last_chunk_identifier) => {
356 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier)
357 }
358 None => ChunkIdentifierGenerator::new_from_scratch(),
359 };
360
361 let mut number_of_chunks = 0;
363 let mut chunk_row = None;
364
365 for chunk_row_candidate in &self.chunks {
366 if chunk_row_candidate.room_id == room_id {
367 number_of_chunks += 1;
368
369 if chunk_row_candidate.next_chunk.is_none() {
370 chunk_row = Some(chunk_row_candidate);
371
372 break;
373 }
374 }
375 }
376
377 let chunk_row = match chunk_row {
378 Some(chunk_row) => chunk_row,
380
381 None if number_of_chunks == 0 => {
384 return Ok((None, chunk_identifier_generator));
385 }
386
387 None => {
392 return Err(
393 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
394 .to_owned(),
395 );
396 }
397 };
398
399 load_raw_chunk(self, chunk_row, room_id)
401 .map(|raw_chunk| (Some(raw_chunk), chunk_identifier_generator))
402 }
403
404 pub fn load_previous_chunk(
405 &self,
406 room_id: &RoomId,
407 before_chunk_identifier: ChunkIdentifier,
408 ) -> Result<Option<RawChunk<Item, Gap>>, String> {
409 let Some(chunk_row) = self.chunks.iter().find(|chunk_row| {
411 chunk_row.room_id == room_id && chunk_row.next_chunk == Some(before_chunk_identifier)
412 }) else {
413 return Ok(None);
415 };
416
417 load_raw_chunk(self, chunk_row, room_id).map(Some)
419 }
420}
421
422impl<Item, Gap> Default for RelationalLinkedChunk<Item, Gap> {
423 fn default() -> Self {
424 Self::new()
425 }
426}
427
428fn load_raw_chunk<Item, Gap>(
429 relational_linked_chunk: &RelationalLinkedChunk<Item, Gap>,
430 chunk_row: &ChunkRow,
431 room_id: &RoomId,
432) -> Result<RawChunk<Item, Gap>, String>
433where
434 Item: Clone,
435 Gap: Clone,
436{
437 let mut items = relational_linked_chunk
439 .items
440 .iter()
441 .filter(|item_row| {
442 item_row.room_id == room_id && item_row.position.chunk_identifier() == chunk_row.chunk
443 })
444 .peekable();
445
446 let Some(first_item) = items.peek() else {
447 return Ok(RawChunk {
449 content: ChunkContent::Items(Vec::new()),
450 previous: chunk_row.previous_chunk,
451 identifier: chunk_row.chunk,
452 next: chunk_row.next_chunk,
453 });
454 };
455
456 Ok(match first_item.item {
457 Either::Item(_) => {
459 let mut collected_items = Vec::new();
461
462 for item_row in items {
463 match &item_row.item {
464 Either::Item(item_value) => {
465 collected_items.push((item_value.clone(), item_row.position.index()))
466 }
467
468 Either::Gap(_) => {
469 return Err(format!(
470 "unexpected gap in items chunk {}",
471 chunk_row.chunk.index()
472 ));
473 }
474 }
475 }
476
477 collected_items.sort_unstable_by_key(|(_item, index)| *index);
479
480 RawChunk {
481 content: ChunkContent::Items(
482 collected_items.into_iter().map(|(item, _index)| item).collect(),
483 ),
484 previous: chunk_row.previous_chunk,
485 identifier: chunk_row.chunk,
486 next: chunk_row.next_chunk,
487 }
488 }
489
490 Either::Gap(ref gap) => {
491 assert!(items.next().is_some(), "we just peeked the gap");
492
493 if items.next().is_some() {
495 return Err(format!(
496 "there shouldn't be more than one item row attached in gap chunk {}",
497 chunk_row.chunk.index()
498 ));
499 }
500
501 RawChunk {
502 content: ChunkContent::Gap(gap.clone()),
503 previous: chunk_row.previous_chunk,
504 identifier: chunk_row.chunk,
505 next: chunk_row.next_chunk,
506 }
507 }
508 })
509}
510
511#[cfg(test)]
512mod tests {
513 use assert_matches::assert_matches;
514 use ruma::room_id;
515
516 use super::{super::lazy_loader::from_all_chunks, ChunkIdentifier as CId, *};
517
518 #[test]
519 fn test_new_items_chunk() {
520 let room_id = room_id!("!r0:matrix.org");
521 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
522
523 relational_linked_chunk.apply_updates(
524 room_id,
525 vec![
526 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
528 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
530 Update::NewItemsChunk { previous: None, new: CId::new(2), next: Some(CId::new(0)) },
532 Update::NewItemsChunk {
534 previous: Some(CId::new(2)),
535 new: CId::new(3),
536 next: Some(CId::new(0)),
537 },
538 ],
539 );
540
541 assert_eq!(
543 relational_linked_chunk.chunks,
544 &[
545 ChunkRow {
546 room_id: room_id.to_owned(),
547 previous_chunk: Some(CId::new(3)),
548 chunk: CId::new(0),
549 next_chunk: Some(CId::new(1))
550 },
551 ChunkRow {
552 room_id: room_id.to_owned(),
553 previous_chunk: Some(CId::new(0)),
554 chunk: CId::new(1),
555 next_chunk: None
556 },
557 ChunkRow {
558 room_id: room_id.to_owned(),
559 previous_chunk: None,
560 chunk: CId::new(2),
561 next_chunk: Some(CId::new(3))
562 },
563 ChunkRow {
564 room_id: room_id.to_owned(),
565 previous_chunk: Some(CId::new(2)),
566 chunk: CId::new(3),
567 next_chunk: Some(CId::new(0))
568 },
569 ],
570 );
571 assert!(relational_linked_chunk.items.is_empty());
573 }
574
575 #[test]
576 fn test_new_gap_chunk() {
577 let room_id = room_id!("!r0:matrix.org");
578 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
579
580 relational_linked_chunk.apply_updates(
581 room_id,
582 vec![
583 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
585 Update::NewGapChunk {
587 previous: Some(CId::new(0)),
588 new: CId::new(1),
589 next: None,
590 gap: (),
591 },
592 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
594 ],
595 );
596
597 assert_eq!(
599 relational_linked_chunk.chunks,
600 &[
601 ChunkRow {
602 room_id: room_id.to_owned(),
603 previous_chunk: None,
604 chunk: CId::new(0),
605 next_chunk: Some(CId::new(1))
606 },
607 ChunkRow {
608 room_id: room_id.to_owned(),
609 previous_chunk: Some(CId::new(0)),
610 chunk: CId::new(1),
611 next_chunk: Some(CId::new(2))
612 },
613 ChunkRow {
614 room_id: room_id.to_owned(),
615 previous_chunk: Some(CId::new(1)),
616 chunk: CId::new(2),
617 next_chunk: None
618 },
619 ],
620 );
621 assert_eq!(
623 relational_linked_chunk.items,
624 &[ItemRow {
625 room_id: room_id.to_owned(),
626 position: Position::new(CId::new(1), 0),
627 item: Either::Gap(())
628 }],
629 );
630 }
631
632 #[test]
633 fn test_remove_chunk() {
634 let room_id = room_id!("!r0:matrix.org");
635 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
636
637 relational_linked_chunk.apply_updates(
638 room_id,
639 vec![
640 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
642 Update::NewGapChunk {
644 previous: Some(CId::new(0)),
645 new: CId::new(1),
646 next: None,
647 gap: (),
648 },
649 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
651 Update::RemoveChunk(CId::new(1)),
653 ],
654 );
655
656 assert_eq!(
658 relational_linked_chunk.chunks,
659 &[
660 ChunkRow {
661 room_id: room_id.to_owned(),
662 previous_chunk: None,
663 chunk: CId::new(0),
664 next_chunk: Some(CId::new(2))
665 },
666 ChunkRow {
667 room_id: room_id.to_owned(),
668 previous_chunk: Some(CId::new(0)),
669 chunk: CId::new(2),
670 next_chunk: None
671 },
672 ],
673 );
674 assert!(relational_linked_chunk.items.is_empty());
676 }
677
678 #[test]
679 fn test_push_items() {
680 let room_id = room_id!("!r0:matrix.org");
681 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
682
683 relational_linked_chunk.apply_updates(
684 room_id,
685 vec![
686 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
688 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
690 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
692 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
694 Update::PushItems { at: Position::new(CId::new(0), 3), items: vec!['d', 'e'] },
696 ],
697 );
698
699 assert_eq!(
701 relational_linked_chunk.chunks,
702 &[
703 ChunkRow {
704 room_id: room_id.to_owned(),
705 previous_chunk: None,
706 chunk: CId::new(0),
707 next_chunk: Some(CId::new(1))
708 },
709 ChunkRow {
710 room_id: room_id.to_owned(),
711 previous_chunk: Some(CId::new(0)),
712 chunk: CId::new(1),
713 next_chunk: None
714 },
715 ],
716 );
717 assert_eq!(
719 relational_linked_chunk.items,
720 &[
721 ItemRow {
722 room_id: room_id.to_owned(),
723 position: Position::new(CId::new(0), 0),
724 item: Either::Item('a')
725 },
726 ItemRow {
727 room_id: room_id.to_owned(),
728 position: Position::new(CId::new(0), 1),
729 item: Either::Item('b')
730 },
731 ItemRow {
732 room_id: room_id.to_owned(),
733 position: Position::new(CId::new(0), 2),
734 item: Either::Item('c')
735 },
736 ItemRow {
737 room_id: room_id.to_owned(),
738 position: Position::new(CId::new(1), 0),
739 item: Either::Item('x')
740 },
741 ItemRow {
742 room_id: room_id.to_owned(),
743 position: Position::new(CId::new(1), 1),
744 item: Either::Item('y')
745 },
746 ItemRow {
747 room_id: room_id.to_owned(),
748 position: Position::new(CId::new(1), 2),
749 item: Either::Item('z')
750 },
751 ItemRow {
752 room_id: room_id.to_owned(),
753 position: Position::new(CId::new(0), 3),
754 item: Either::Item('d')
755 },
756 ItemRow {
757 room_id: room_id.to_owned(),
758 position: Position::new(CId::new(0), 4),
759 item: Either::Item('e')
760 },
761 ],
762 );
763 }
764
765 #[test]
766 fn test_remove_item() {
767 let room_id = room_id!("!r0:matrix.org");
768 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
769
770 relational_linked_chunk.apply_updates(
771 room_id,
772 vec![
773 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
775 Update::PushItems {
777 at: Position::new(CId::new(0), 0),
778 items: vec!['a', 'b', 'c', 'd', 'e'],
779 },
780 Update::RemoveItem { at: Position::new(CId::new(0), 0) },
782 Update::RemoveItem { at: Position::new(CId::new(0), 2) },
784 ],
785 );
786
787 assert_eq!(
789 relational_linked_chunk.chunks,
790 &[ChunkRow {
791 room_id: room_id.to_owned(),
792 previous_chunk: None,
793 chunk: CId::new(0),
794 next_chunk: None
795 }],
796 );
797 assert_eq!(
799 relational_linked_chunk.items,
800 &[
801 ItemRow {
802 room_id: room_id.to_owned(),
803 position: Position::new(CId::new(0), 0),
804 item: Either::Item('b')
805 },
806 ItemRow {
807 room_id: room_id.to_owned(),
808 position: Position::new(CId::new(0), 1),
809 item: Either::Item('c')
810 },
811 ItemRow {
812 room_id: room_id.to_owned(),
813 position: Position::new(CId::new(0), 2),
814 item: Either::Item('e')
815 },
816 ],
817 );
818 }
819
820 #[test]
821 fn test_detach_last_items() {
822 let room_id = room_id!("!r0:matrix.org");
823 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
824
825 relational_linked_chunk.apply_updates(
826 room_id,
827 vec![
828 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
830 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
832 Update::PushItems {
834 at: Position::new(CId::new(0), 0),
835 items: vec!['a', 'b', 'c', 'd', 'e'],
836 },
837 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
839 Update::DetachLastItems { at: Position::new(CId::new(0), 2) },
841 ],
842 );
843
844 assert_eq!(
846 relational_linked_chunk.chunks,
847 &[
848 ChunkRow {
849 room_id: room_id.to_owned(),
850 previous_chunk: None,
851 chunk: CId::new(0),
852 next_chunk: Some(CId::new(1))
853 },
854 ChunkRow {
855 room_id: room_id.to_owned(),
856 previous_chunk: Some(CId::new(0)),
857 chunk: CId::new(1),
858 next_chunk: None
859 },
860 ],
861 );
862 assert_eq!(
864 relational_linked_chunk.items,
865 &[
866 ItemRow {
867 room_id: room_id.to_owned(),
868 position: Position::new(CId::new(0), 0),
869 item: Either::Item('a')
870 },
871 ItemRow {
872 room_id: room_id.to_owned(),
873 position: Position::new(CId::new(0), 1),
874 item: Either::Item('b')
875 },
876 ItemRow {
877 room_id: room_id.to_owned(),
878 position: Position::new(CId::new(1), 0),
879 item: Either::Item('x')
880 },
881 ItemRow {
882 room_id: room_id.to_owned(),
883 position: Position::new(CId::new(1), 1),
884 item: Either::Item('y')
885 },
886 ItemRow {
887 room_id: room_id.to_owned(),
888 position: Position::new(CId::new(1), 2),
889 item: Either::Item('z')
890 },
891 ],
892 );
893 }
894
895 #[test]
896 fn test_start_and_end_reattach_items() {
897 let room_id = room_id!("!r0:matrix.org");
898 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
899
900 relational_linked_chunk
901 .apply_updates(room_id, vec![Update::StartReattachItems, Update::EndReattachItems]);
902
903 assert!(relational_linked_chunk.chunks.is_empty());
905 assert!(relational_linked_chunk.items.is_empty());
906 }
907
908 #[test]
909 fn test_clear() {
910 let r0 = room_id!("!r0:matrix.org");
911 let r1 = room_id!("!r1:matrix.org");
912 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
913
914 relational_linked_chunk.apply_updates(
915 r0,
916 vec![
917 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
919 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
921 ],
922 );
923
924 relational_linked_chunk.apply_updates(
925 r1,
926 vec![
927 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
929 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x'] },
931 ],
932 );
933
934 assert_eq!(
936 relational_linked_chunk.chunks,
937 &[
938 ChunkRow {
939 room_id: r0.to_owned(),
940 previous_chunk: None,
941 chunk: CId::new(0),
942 next_chunk: None,
943 },
944 ChunkRow {
945 room_id: r1.to_owned(),
946 previous_chunk: None,
947 chunk: CId::new(0),
948 next_chunk: None,
949 }
950 ],
951 );
952
953 assert_eq!(
955 relational_linked_chunk.items,
956 &[
957 ItemRow {
958 room_id: r0.to_owned(),
959 position: Position::new(CId::new(0), 0),
960 item: Either::Item('a')
961 },
962 ItemRow {
963 room_id: r0.to_owned(),
964 position: Position::new(CId::new(0), 1),
965 item: Either::Item('b')
966 },
967 ItemRow {
968 room_id: r0.to_owned(),
969 position: Position::new(CId::new(0), 2),
970 item: Either::Item('c')
971 },
972 ItemRow {
973 room_id: r1.to_owned(),
974 position: Position::new(CId::new(0), 0),
975 item: Either::Item('x')
976 },
977 ],
978 );
979
980 relational_linked_chunk.apply_updates(r0, vec![Update::Clear]);
982
983 assert_eq!(
985 relational_linked_chunk.chunks,
986 &[ChunkRow {
987 room_id: r1.to_owned(),
988 previous_chunk: None,
989 chunk: CId::new(0),
990 next_chunk: None,
991 }],
992 );
993
994 assert_eq!(
995 relational_linked_chunk.items,
996 &[ItemRow {
997 room_id: r1.to_owned(),
998 position: Position::new(CId::new(0), 0),
999 item: Either::Item('x')
1000 },],
1001 );
1002 }
1003
1004 #[test]
1005 fn test_load_empty_linked_chunk() {
1006 let room_id = room_id!("!r0:matrix.org");
1007
1008 let relational_linked_chunk = RelationalLinkedChunk::<char, char>::new();
1010 let result = relational_linked_chunk.load_all_chunks(room_id).unwrap();
1011 assert!(result.is_empty());
1012 }
1013
1014 #[test]
1015 fn test_load_all_chunks_with_empty_items() {
1016 let room_id = room_id!("!r0:matrix.org");
1017
1018 let mut relational_linked_chunk = RelationalLinkedChunk::<char, char>::new();
1019
1020 relational_linked_chunk.apply_updates(
1022 room_id,
1023 vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }],
1024 );
1025
1026 let lc =
1028 from_all_chunks::<3, _, _>(relational_linked_chunk.load_all_chunks(room_id).unwrap())
1029 .expect("building succeeds")
1030 .expect("this leads to a non-empty linked chunk");
1031
1032 assert_items_eq!(lc, []);
1033 }
1034
1035 #[test]
1036 fn test_rebuild_linked_chunk() {
1037 let room_id = room_id!("!r0:matrix.org");
1038 let mut relational_linked_chunk = RelationalLinkedChunk::<char, char>::new();
1039
1040 relational_linked_chunk.apply_updates(
1041 room_id,
1042 vec![
1043 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1045 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1047 Update::NewGapChunk {
1049 previous: Some(CId::new(0)),
1050 new: CId::new(1),
1051 next: None,
1052 gap: 'g',
1053 },
1054 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
1056 Update::PushItems { at: Position::new(CId::new(2), 0), items: vec!['d', 'e', 'f'] },
1058 ],
1059 );
1060
1061 let lc =
1062 from_all_chunks::<3, _, _>(relational_linked_chunk.load_all_chunks(room_id).unwrap())
1063 .expect("building succeeds")
1064 .expect("this leads to a non-empty linked chunk");
1065
1066 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']);
1068 }
1069
1070 #[test]
1071 fn test_replace_item() {
1072 let room_id = room_id!("!r0:matrix.org");
1073 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1074
1075 relational_linked_chunk.apply_updates(
1076 room_id,
1077 vec![
1078 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1080 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1082 Update::ReplaceItem { at: Position::new(CId::new(0), 1), item: 'B' },
1084 ],
1085 );
1086
1087 assert_eq!(
1089 relational_linked_chunk.chunks,
1090 &[ChunkRow {
1091 room_id: room_id.to_owned(),
1092 previous_chunk: None,
1093 chunk: CId::new(0),
1094 next_chunk: None,
1095 },],
1096 );
1097
1098 assert_eq!(
1100 relational_linked_chunk.items,
1101 &[
1102 ItemRow {
1103 room_id: room_id.to_owned(),
1104 position: Position::new(CId::new(0), 0),
1105 item: Either::Item('a')
1106 },
1107 ItemRow {
1108 room_id: room_id.to_owned(),
1109 position: Position::new(CId::new(0), 1),
1110 item: Either::Item('B')
1111 },
1112 ItemRow {
1113 room_id: room_id.to_owned(),
1114 position: Position::new(CId::new(0), 2),
1115 item: Either::Item('c')
1116 },
1117 ],
1118 );
1119 }
1120
1121 #[test]
1122 fn test_unordered_events() {
1123 let room_id = room_id!("!r0:matrix.org");
1124 let other_room_id = room_id!("!r1:matrix.org");
1125 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1126
1127 relational_linked_chunk.apply_updates(
1128 room_id,
1129 vec![
1130 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1131 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1132 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
1133 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['d', 'e', 'f'] },
1134 ],
1135 );
1136
1137 relational_linked_chunk.apply_updates(
1138 other_room_id,
1139 vec![
1140 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1141 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x', 'y', 'z'] },
1142 ],
1143 );
1144
1145 let mut events = relational_linked_chunk.unordered_room_items(room_id);
1146
1147 assert_eq!(events.next().unwrap(), (&'a', Position::new(CId::new(0), 0)));
1148 assert_eq!(events.next().unwrap(), (&'b', Position::new(CId::new(0), 1)));
1149 assert_eq!(events.next().unwrap(), (&'c', Position::new(CId::new(0), 2)));
1150 assert_eq!(events.next().unwrap(), (&'d', Position::new(CId::new(1), 0)));
1151 assert_eq!(events.next().unwrap(), (&'e', Position::new(CId::new(1), 1)));
1152 assert_eq!(events.next().unwrap(), (&'f', Position::new(CId::new(1), 2)));
1153 assert!(events.next().is_none());
1154 }
1155
1156 #[test]
1157 fn test_load_last_chunk() {
1158 let room_id = room_id!("!r0:matrix.org");
1159 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1160
1161 {
1163 let (last_chunk, chunk_identifier_generator) =
1164 relational_linked_chunk.load_last_chunk(room_id).unwrap();
1165
1166 assert!(last_chunk.is_none());
1167 assert_eq!(chunk_identifier_generator.current(), 0);
1168 }
1169
1170 {
1172 relational_linked_chunk.apply_updates(
1173 room_id,
1174 vec![
1175 Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1176 Update::PushItems { at: Position::new(CId::new(42), 0), items: vec!['a', 'b'] },
1177 ],
1178 );
1179
1180 let (last_chunk, chunk_identifier_generator) =
1181 relational_linked_chunk.load_last_chunk(room_id).unwrap();
1182
1183 assert_matches!(last_chunk, Some(last_chunk) => {
1184 assert_eq!(last_chunk.identifier, 42);
1185 assert!(last_chunk.previous.is_none());
1186 assert!(last_chunk.next.is_none());
1187 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1188 assert_eq!(items.len(), 2);
1189 assert_eq!(items, &['a', 'b']);
1190 });
1191 });
1192 assert_eq!(chunk_identifier_generator.current(), 42);
1193 }
1194
1195 {
1197 relational_linked_chunk.apply_updates(
1198 room_id,
1199 vec![
1200 Update::NewItemsChunk {
1201 previous: Some(CId::new(42)),
1202 new: CId::new(7),
1203 next: None,
1204 },
1205 Update::PushItems {
1206 at: Position::new(CId::new(7), 0),
1207 items: vec!['c', 'd', 'e'],
1208 },
1209 ],
1210 );
1211
1212 let (last_chunk, chunk_identifier_generator) =
1213 relational_linked_chunk.load_last_chunk(room_id).unwrap();
1214
1215 assert_matches!(last_chunk, Some(last_chunk) => {
1216 assert_eq!(last_chunk.identifier, 7);
1217 assert_matches!(last_chunk.previous, Some(previous) => {
1218 assert_eq!(previous, 42);
1219 });
1220 assert!(last_chunk.next.is_none());
1221 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1222 assert_eq!(items.len(), 3);
1223 assert_eq!(items, &['c', 'd', 'e']);
1224 });
1225 });
1226 assert_eq!(chunk_identifier_generator.current(), 42);
1227 }
1228 }
1229
1230 #[test]
1231 fn test_load_last_chunk_with_a_cycle() {
1232 let room_id = room_id!("!r0:matrix.org");
1233 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1234
1235 relational_linked_chunk.apply_updates(
1236 room_id,
1237 vec![
1238 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1239 Update::NewItemsChunk {
1240 previous: Some(CId::new(0)),
1244 new: CId::new(1),
1245 next: Some(CId::new(0)),
1246 },
1247 ],
1248 );
1249
1250 relational_linked_chunk.load_last_chunk(room_id).unwrap_err();
1251 }
1252
1253 #[test]
1254 fn test_load_previous_chunk() {
1255 let room_id = room_id!("!r0:matrix.org");
1256 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1257
1258 {
1261 let previous_chunk =
1262 relational_linked_chunk.load_previous_chunk(room_id, CId::new(153)).unwrap();
1263
1264 assert!(previous_chunk.is_none());
1265 }
1266
1267 {
1270 relational_linked_chunk.apply_updates(
1271 room_id,
1272 vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
1273 );
1274
1275 let previous_chunk =
1276 relational_linked_chunk.load_previous_chunk(room_id, CId::new(42)).unwrap();
1277
1278 assert!(previous_chunk.is_none());
1279 }
1280
1281 {
1283 relational_linked_chunk.apply_updates(
1284 room_id,
1285 vec![
1286 Update::NewItemsChunk {
1288 previous: None,
1289 new: CId::new(7),
1290 next: Some(CId::new(42)),
1291 },
1292 Update::PushItems {
1293 at: Position::new(CId::new(7), 0),
1294 items: vec!['a', 'b', 'c'],
1295 },
1296 ],
1297 );
1298
1299 let previous_chunk =
1300 relational_linked_chunk.load_previous_chunk(room_id, CId::new(42)).unwrap();
1301
1302 assert_matches!(previous_chunk, Some(previous_chunk) => {
1303 assert_eq!(previous_chunk.identifier, 7);
1304 assert!(previous_chunk.previous.is_none());
1305 assert_matches!(previous_chunk.next, Some(next) => {
1306 assert_eq!(next, 42);
1307 });
1308 assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
1309 assert_eq!(items.len(), 3);
1310 assert_eq!(items, &['a', 'b', 'c']);
1311 });
1312 });
1313 }
1314 }
1315}