1use std::{collections::HashMap, hash::Hash};
19
20use ruma::{OwnedEventId, OwnedRoomId};
21
22use super::{ChunkContent, ChunkIdentifierGenerator, RawChunk};
23use crate::{
24 deserialized_responses::TimelineEvent,
25 linked_chunk::{ChunkIdentifier, LinkedChunkId, OwnedLinkedChunkId, Position, Update},
26};
27
28#[derive(Debug, PartialEq)]
30struct ChunkRow {
31 linked_chunk_id: OwnedLinkedChunkId,
32 previous_chunk: Option<ChunkIdentifier>,
33 chunk: ChunkIdentifier,
34 next_chunk: Option<ChunkIdentifier>,
35}
36
37#[derive(Debug, PartialEq)]
39struct ItemRow<ItemId, Gap> {
40 linked_chunk_id: OwnedLinkedChunkId,
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<OwnedLinkedChunkId, 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(
123 &mut self,
124 linked_chunk_id: LinkedChunkId<'_>,
125 updates: Vec<Update<Item, Gap>>,
126 ) {
127 for update in updates {
128 match update {
129 Update::NewItemsChunk { previous, new, next } => {
130 insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next);
131 }
132
133 Update::NewGapChunk { previous, new, next, gap } => {
134 insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next);
135 self.items_chunks.push(ItemRow {
136 linked_chunk_id: linked_chunk_id.to_owned(),
137 position: Position::new(new, 0),
138 item: Either::Gap(gap),
139 });
140 }
141
142 Update::RemoveChunk(chunk_identifier) => {
143 remove_chunk(&mut self.chunks, linked_chunk_id, chunk_identifier);
144
145 let indices_to_remove = self
146 .items_chunks
147 .iter()
148 .enumerate()
149 .filter_map(
150 |(
151 nth,
152 ItemRow {
153 linked_chunk_id: linked_chunk_id_candidate,
154 position,
155 ..
156 },
157 )| {
158 (linked_chunk_id == linked_chunk_id_candidate
159 && position.chunk_identifier() == chunk_identifier)
160 .then_some(nth)
161 },
162 )
163 .collect::<Vec<_>>();
164
165 for index_to_remove in indices_to_remove.into_iter().rev() {
166 self.items_chunks.remove(index_to_remove);
167 }
168 }
169
170 Update::PushItems { mut at, items } => {
171 for item in items {
172 let item_id = item.id();
173 self.items
174 .entry(linked_chunk_id.to_owned())
175 .or_default()
176 .insert(item_id.clone(), item);
177 self.items_chunks.push(ItemRow {
178 linked_chunk_id: linked_chunk_id.to_owned(),
179 position: at,
180 item: Either::Item(item_id),
181 });
182 at.increment_index();
183 }
184 }
185
186 Update::ReplaceItem { at, item } => {
187 let existing = self
188 .items_chunks
189 .iter_mut()
190 .find(|item| item.position == at)
191 .expect("trying to replace at an unknown position");
192 assert!(
193 matches!(existing.item, Either::Item(..)),
194 "trying to replace a gap with an item"
195 );
196 let item_id = item.id();
197 self.items
198 .entry(linked_chunk_id.to_owned())
199 .or_default()
200 .insert(item_id.clone(), item);
201 existing.item = Either::Item(item_id);
202 }
203
204 Update::RemoveItem { at } => {
205 let mut entry_to_remove = None;
206
207 for (
208 nth,
209 ItemRow { linked_chunk_id: linked_chunk_id_candidate, position, .. },
210 ) in self.items_chunks.iter_mut().enumerate()
211 {
212 if linked_chunk_id != &*linked_chunk_id_candidate {
214 continue;
215 }
216
217 if *position == at {
219 debug_assert!(entry_to_remove.is_none(), "Found the same entry twice");
220
221 entry_to_remove = Some(nth);
222 }
223
224 if position.chunk_identifier() == at.chunk_identifier()
226 && position.index() > at.index()
227 {
228 position.decrement_index();
229 }
230 }
231
232 self.items_chunks.remove(entry_to_remove.expect("Remove an unknown item"));
233 }
235
236 Update::DetachLastItems { at } => {
237 let indices_to_remove = self
238 .items_chunks
239 .iter()
240 .enumerate()
241 .filter_map(
242 |(
243 nth,
244 ItemRow {
245 linked_chunk_id: linked_chunk_id_candidate,
246 position,
247 ..
248 },
249 )| {
250 (linked_chunk_id == linked_chunk_id_candidate
251 && position.chunk_identifier() == at.chunk_identifier()
252 && position.index() >= at.index())
253 .then_some(nth)
254 },
255 )
256 .collect::<Vec<_>>();
257
258 for index_to_remove in indices_to_remove.into_iter().rev() {
259 self.items_chunks.remove(index_to_remove);
260 }
261 }
262
263 Update::StartReattachItems | Update::EndReattachItems => { }
264
265 Update::Clear => {
266 self.chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id);
267 self.items_chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id);
268 }
270 }
271 }
272
273 fn insert_chunk(
274 chunks: &mut Vec<ChunkRow>,
275 linked_chunk_id: LinkedChunkId<'_>,
276 previous: Option<ChunkIdentifier>,
277 new: ChunkIdentifier,
278 next: Option<ChunkIdentifier>,
279 ) {
280 if let Some(previous) = previous {
282 let entry_for_previous_chunk = chunks
283 .iter_mut()
284 .find(
285 |ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
286 linked_chunk_id == linked_chunk_id_candidate && *chunk == previous
287 },
288 )
289 .expect("Previous chunk should be present");
290
291 entry_for_previous_chunk.next_chunk = Some(new);
293 }
294
295 if let Some(next) = next {
297 let entry_for_next_chunk = chunks
298 .iter_mut()
299 .find(
300 |ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
301 linked_chunk_id == linked_chunk_id_candidate && *chunk == next
302 },
303 )
304 .expect("Next chunk should be present");
305
306 entry_for_next_chunk.previous_chunk = Some(new);
308 }
309
310 chunks.push(ChunkRow {
312 linked_chunk_id: linked_chunk_id.to_owned(),
313 previous_chunk: previous,
314 chunk: new,
315 next_chunk: next,
316 });
317 }
318
319 fn remove_chunk(
320 chunks: &mut Vec<ChunkRow>,
321 linked_chunk_id: LinkedChunkId<'_>,
322 chunk_to_remove: ChunkIdentifier,
323 ) {
324 let entry_nth_to_remove = chunks
325 .iter()
326 .enumerate()
327 .find_map(
328 |(nth, ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. })| {
329 (linked_chunk_id == linked_chunk_id_candidate && *chunk == chunk_to_remove)
330 .then_some(nth)
331 },
332 )
333 .expect("Remove an unknown chunk");
334
335 let ChunkRow { linked_chunk_id, previous_chunk: previous, next_chunk: next, .. } =
336 chunks.remove(entry_nth_to_remove);
337
338 if let Some(previous) = previous {
340 let entry_for_previous_chunk = chunks
341 .iter_mut()
342 .find(
343 |ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
344 &linked_chunk_id == linked_chunk_id_candidate && *chunk == previous
345 },
346 )
347 .expect("Previous chunk should be present");
348
349 entry_for_previous_chunk.next_chunk = next;
351 }
352
353 if let Some(next) = next {
355 let entry_for_next_chunk = chunks
356 .iter_mut()
357 .find(
358 |ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
359 &linked_chunk_id == linked_chunk_id_candidate && *chunk == next
360 },
361 )
362 .expect("Next chunk should be present");
363
364 entry_for_next_chunk.previous_chunk = previous;
366 }
367 }
368 }
369
370 pub fn unordered_linked_chunk_items<'a>(
373 &'a self,
374 target: LinkedChunkId<'a>,
375 ) -> impl Iterator<Item = (&'a Item, Position)> {
376 self.items_chunks.iter().filter_map(move |item_row| {
377 if item_row.linked_chunk_id == target {
378 match &item_row.item {
379 Either::Item(item_id) => {
380 Some((self.items.get(&target.to_owned())?.get(item_id)?, item_row.position))
381 }
382 Either::Gap(..) => None,
383 }
384 } else {
385 None
386 }
387 })
388 }
389
390 pub fn items(&self) -> impl Iterator<Item = (&Item, LinkedChunkId<'_>)> {
395 self.items.iter().flat_map(|(linked_chunk_id, items)| {
396 items.values().map(|item| (item, linked_chunk_id.as_ref()))
397 })
398 }
399
400 pub fn save_item(&mut self, room_id: OwnedRoomId, item: Item) {
402 let id = item.id();
403 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id);
404 self.items.entry(linked_chunk_id).or_default().insert(id, item);
405 }
406}
407
408impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
409where
410 Gap: Clone,
411 Item: Clone,
412 ItemId: Hash + PartialEq + Eq,
413{
414 #[doc(hidden)]
419 pub fn load_all_chunks(
420 &self,
421 linked_chunk_id: LinkedChunkId<'_>,
422 ) -> Result<Vec<RawChunk<Item, Gap>>, String> {
423 self.chunks
424 .iter()
425 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
426 .map(|chunk_row| load_raw_chunk(self, chunk_row, linked_chunk_id))
427 .collect::<Result<Vec<_>, String>>()
428 }
429
430 pub fn load_last_chunk(
431 &self,
432 linked_chunk_id: LinkedChunkId<'_>,
433 ) -> Result<(Option<RawChunk<Item, Gap>>, ChunkIdentifierGenerator), String> {
434 let chunk_identifier_generator = match self
436 .chunks
437 .iter()
438 .filter_map(|chunk_row| {
439 (chunk_row.linked_chunk_id == linked_chunk_id).then_some(chunk_row.chunk)
440 })
441 .max()
442 {
443 Some(last_chunk_identifier) => {
444 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier)
445 }
446 None => ChunkIdentifierGenerator::new_from_scratch(),
447 };
448
449 let mut number_of_chunks = 0;
451 let mut chunk_row = None;
452
453 for chunk_row_candidate in &self.chunks {
454 if chunk_row_candidate.linked_chunk_id == linked_chunk_id {
455 number_of_chunks += 1;
456
457 if chunk_row_candidate.next_chunk.is_none() {
458 chunk_row = Some(chunk_row_candidate);
459
460 break;
461 }
462 }
463 }
464
465 let chunk_row = match chunk_row {
466 Some(chunk_row) => chunk_row,
468
469 None if number_of_chunks == 0 => {
472 return Ok((None, chunk_identifier_generator));
473 }
474
475 None => {
480 return Err(
481 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
482 .to_owned(),
483 );
484 }
485 };
486
487 load_raw_chunk(self, chunk_row, linked_chunk_id)
489 .map(|raw_chunk| (Some(raw_chunk), chunk_identifier_generator))
490 }
491
492 pub fn load_previous_chunk(
493 &self,
494 linked_chunk_id: LinkedChunkId<'_>,
495 before_chunk_identifier: ChunkIdentifier,
496 ) -> Result<Option<RawChunk<Item, Gap>>, String> {
497 let Some(chunk_row) = self.chunks.iter().find(|chunk_row| {
499 chunk_row.linked_chunk_id == linked_chunk_id
500 && chunk_row.next_chunk == Some(before_chunk_identifier)
501 }) else {
502 return Ok(None);
504 };
505
506 load_raw_chunk(self, chunk_row, linked_chunk_id).map(Some)
508 }
509}
510
511impl<ItemId, Item, Gap> Default for RelationalLinkedChunk<ItemId, Item, Gap>
512where
513 Item: IndexableItem<ItemId = ItemId>,
514 ItemId: Hash + PartialEq + Eq + Clone,
515{
516 fn default() -> Self {
517 Self::new()
518 }
519}
520
521fn load_raw_chunk<ItemId, Item, Gap>(
522 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
523 chunk_row: &ChunkRow,
524 linked_chunk_id: LinkedChunkId<'_>,
525) -> Result<RawChunk<Item, Gap>, String>
526where
527 Item: Clone,
528 Gap: Clone,
529 ItemId: Hash + PartialEq + Eq,
530{
531 let mut items = relational_linked_chunk
533 .items_chunks
534 .iter()
535 .filter(|item_row| {
536 item_row.linked_chunk_id == linked_chunk_id
537 && item_row.position.chunk_identifier() == chunk_row.chunk
538 })
539 .peekable();
540
541 let Some(first_item) = items.peek() else {
542 return Ok(RawChunk {
544 content: ChunkContent::Items(Vec::new()),
545 previous: chunk_row.previous_chunk,
546 identifier: chunk_row.chunk,
547 next: chunk_row.next_chunk,
548 });
549 };
550
551 Ok(match first_item.item {
552 Either::Item(_) => {
554 let mut collected_items = Vec::new();
556
557 for item_row in items {
558 match &item_row.item {
559 Either::Item(item_id) => {
560 collected_items.push((item_id, item_row.position.index()))
561 }
562
563 Either::Gap(_) => {
564 return Err(format!(
565 "unexpected gap in items chunk {}",
566 chunk_row.chunk.index()
567 ));
568 }
569 }
570 }
571
572 collected_items.sort_unstable_by_key(|(_item, index)| *index);
574
575 RawChunk {
576 content: ChunkContent::Items(
577 collected_items
578 .into_iter()
579 .filter_map(|(item_id, _index)| {
580 relational_linked_chunk
581 .items
582 .get(&linked_chunk_id.to_owned())?
583 .get(item_id)
584 .cloned()
585 })
586 .collect(),
587 ),
588 previous: chunk_row.previous_chunk,
589 identifier: chunk_row.chunk,
590 next: chunk_row.next_chunk,
591 }
592 }
593
594 Either::Gap(ref gap) => {
595 assert!(items.next().is_some(), "we just peeked the gap");
596
597 if items.next().is_some() {
599 return Err(format!(
600 "there shouldn't be more than one item row attached in gap chunk {}",
601 chunk_row.chunk.index()
602 ));
603 }
604
605 RawChunk {
606 content: ChunkContent::Gap(gap.clone()),
607 previous: chunk_row.previous_chunk,
608 identifier: chunk_row.chunk,
609 next: chunk_row.next_chunk,
610 }
611 }
612 })
613}
614
615#[cfg(test)]
616mod tests {
617 use assert_matches::assert_matches;
618 use ruma::room_id;
619
620 use super::{super::lazy_loader::from_all_chunks, ChunkIdentifier as CId, *};
621
622 impl IndexableItem for char {
623 type ItemId = char;
624
625 fn id(&self) -> Self::ItemId {
626 *self
627 }
628 }
629
630 #[test]
631 fn test_new_items_chunk() {
632 let room_id = room_id!("!r0:matrix.org");
633 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
634
635 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
636
637 relational_linked_chunk.apply_updates(
638 linked_chunk_id.as_ref(),
639 vec![
640 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
642 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
644 Update::NewItemsChunk { previous: None, new: CId::new(2), next: Some(CId::new(0)) },
646 Update::NewItemsChunk {
648 previous: Some(CId::new(2)),
649 new: CId::new(3),
650 next: Some(CId::new(0)),
651 },
652 ],
653 );
654
655 assert_eq!(
657 relational_linked_chunk.chunks,
658 &[
659 ChunkRow {
660 linked_chunk_id: linked_chunk_id.clone(),
661 previous_chunk: Some(CId::new(3)),
662 chunk: CId::new(0),
663 next_chunk: Some(CId::new(1))
664 },
665 ChunkRow {
666 linked_chunk_id: linked_chunk_id.clone(),
667 previous_chunk: Some(CId::new(0)),
668 chunk: CId::new(1),
669 next_chunk: None
670 },
671 ChunkRow {
672 linked_chunk_id: linked_chunk_id.clone(),
673 previous_chunk: None,
674 chunk: CId::new(2),
675 next_chunk: Some(CId::new(3))
676 },
677 ChunkRow {
678 linked_chunk_id,
679 previous_chunk: Some(CId::new(2)),
680 chunk: CId::new(3),
681 next_chunk: Some(CId::new(0))
682 },
683 ],
684 );
685
686 assert!(relational_linked_chunk.items_chunks.is_empty());
688 }
689
690 #[test]
691 fn test_new_gap_chunk() {
692 let room_id = room_id!("!r0:matrix.org");
693 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
694
695 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
696
697 relational_linked_chunk.apply_updates(
698 linked_chunk_id.as_ref(),
699 vec![
700 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
702 Update::NewGapChunk {
704 previous: Some(CId::new(0)),
705 new: CId::new(1),
706 next: None,
707 gap: (),
708 },
709 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
711 ],
712 );
713
714 assert_eq!(
716 relational_linked_chunk.chunks,
717 &[
718 ChunkRow {
719 linked_chunk_id: linked_chunk_id.clone(),
720 previous_chunk: None,
721 chunk: CId::new(0),
722 next_chunk: Some(CId::new(1))
723 },
724 ChunkRow {
725 linked_chunk_id: linked_chunk_id.clone(),
726 previous_chunk: Some(CId::new(0)),
727 chunk: CId::new(1),
728 next_chunk: Some(CId::new(2))
729 },
730 ChunkRow {
731 linked_chunk_id: linked_chunk_id.clone(),
732 previous_chunk: Some(CId::new(1)),
733 chunk: CId::new(2),
734 next_chunk: None
735 },
736 ],
737 );
738 assert_eq!(
740 relational_linked_chunk.items_chunks,
741 &[ItemRow {
742 linked_chunk_id,
743 position: Position::new(CId::new(1), 0),
744 item: Either::Gap(())
745 }],
746 );
747 }
748
749 #[test]
750 fn test_remove_chunk() {
751 let room_id = room_id!("!r0:matrix.org");
752 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
753
754 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
755
756 relational_linked_chunk.apply_updates(
757 linked_chunk_id.as_ref(),
758 vec![
759 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
761 Update::NewGapChunk {
763 previous: Some(CId::new(0)),
764 new: CId::new(1),
765 next: None,
766 gap: (),
767 },
768 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
770 Update::RemoveChunk(CId::new(1)),
772 ],
773 );
774
775 assert_eq!(
777 relational_linked_chunk.chunks,
778 &[
779 ChunkRow {
780 linked_chunk_id: linked_chunk_id.clone(),
781 previous_chunk: None,
782 chunk: CId::new(0),
783 next_chunk: Some(CId::new(2))
784 },
785 ChunkRow {
786 linked_chunk_id,
787 previous_chunk: Some(CId::new(0)),
788 chunk: CId::new(2),
789 next_chunk: None
790 },
791 ],
792 );
793
794 assert!(relational_linked_chunk.items_chunks.is_empty());
796 }
797
798 #[test]
799 fn test_push_items() {
800 let room_id = room_id!("!r0:matrix.org");
801 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
802
803 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
804
805 relational_linked_chunk.apply_updates(
806 linked_chunk_id.as_ref(),
807 vec![
808 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
810 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
812 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
814 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
816 Update::PushItems { at: Position::new(CId::new(0), 3), items: vec!['d', 'e'] },
818 ],
819 );
820
821 assert_eq!(
823 relational_linked_chunk.chunks,
824 &[
825 ChunkRow {
826 linked_chunk_id: linked_chunk_id.clone(),
827 previous_chunk: None,
828 chunk: CId::new(0),
829 next_chunk: Some(CId::new(1))
830 },
831 ChunkRow {
832 linked_chunk_id: linked_chunk_id.clone(),
833 previous_chunk: Some(CId::new(0)),
834 chunk: CId::new(1),
835 next_chunk: None
836 },
837 ],
838 );
839 assert_eq!(
841 relational_linked_chunk.items_chunks,
842 &[
843 ItemRow {
844 linked_chunk_id: linked_chunk_id.clone(),
845 position: Position::new(CId::new(0), 0),
846 item: Either::Item('a')
847 },
848 ItemRow {
849 linked_chunk_id: linked_chunk_id.clone(),
850 position: Position::new(CId::new(0), 1),
851 item: Either::Item('b')
852 },
853 ItemRow {
854 linked_chunk_id: linked_chunk_id.clone(),
855 position: Position::new(CId::new(0), 2),
856 item: Either::Item('c')
857 },
858 ItemRow {
859 linked_chunk_id: linked_chunk_id.clone(),
860 position: Position::new(CId::new(1), 0),
861 item: Either::Item('x')
862 },
863 ItemRow {
864 linked_chunk_id: linked_chunk_id.clone(),
865 position: Position::new(CId::new(1), 1),
866 item: Either::Item('y')
867 },
868 ItemRow {
869 linked_chunk_id: linked_chunk_id.clone(),
870 position: Position::new(CId::new(1), 2),
871 item: Either::Item('z')
872 },
873 ItemRow {
874 linked_chunk_id: linked_chunk_id.clone(),
875 position: Position::new(CId::new(0), 3),
876 item: Either::Item('d')
877 },
878 ItemRow {
879 linked_chunk_id: linked_chunk_id.clone(),
880 position: Position::new(CId::new(0), 4),
881 item: Either::Item('e')
882 },
883 ],
884 );
885 }
886
887 #[test]
888 fn test_remove_item() {
889 let room_id = room_id!("!r0:matrix.org");
890 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
891
892 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
893
894 relational_linked_chunk.apply_updates(
895 linked_chunk_id.as_ref(),
896 vec![
897 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
899 Update::PushItems {
901 at: Position::new(CId::new(0), 0),
902 items: vec!['a', 'b', 'c', 'd', 'e'],
903 },
904 Update::RemoveItem { at: Position::new(CId::new(0), 0) },
906 Update::RemoveItem { at: Position::new(CId::new(0), 2) },
908 ],
909 );
910
911 assert_eq!(
913 relational_linked_chunk.chunks,
914 &[ChunkRow {
915 linked_chunk_id: linked_chunk_id.clone(),
916 previous_chunk: None,
917 chunk: CId::new(0),
918 next_chunk: None
919 }],
920 );
921 assert_eq!(
923 relational_linked_chunk.items_chunks,
924 &[
925 ItemRow {
926 linked_chunk_id: linked_chunk_id.clone(),
927 position: Position::new(CId::new(0), 0),
928 item: Either::Item('b')
929 },
930 ItemRow {
931 linked_chunk_id: linked_chunk_id.clone(),
932 position: Position::new(CId::new(0), 1),
933 item: Either::Item('c')
934 },
935 ItemRow {
936 linked_chunk_id: linked_chunk_id.clone(),
937 position: Position::new(CId::new(0), 2),
938 item: Either::Item('e')
939 },
940 ],
941 );
942 }
943
944 #[test]
945 fn test_detach_last_items() {
946 let room_id = room_id!("!r0:matrix.org");
947 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
948
949 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
950
951 relational_linked_chunk.apply_updates(
952 linked_chunk_id.as_ref(),
953 vec![
954 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
956 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
958 Update::PushItems {
960 at: Position::new(CId::new(0), 0),
961 items: vec!['a', 'b', 'c', 'd', 'e'],
962 },
963 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
965 Update::DetachLastItems { at: Position::new(CId::new(0), 2) },
967 ],
968 );
969
970 assert_eq!(
972 relational_linked_chunk.chunks,
973 &[
974 ChunkRow {
975 linked_chunk_id: linked_chunk_id.clone(),
976 previous_chunk: None,
977 chunk: CId::new(0),
978 next_chunk: Some(CId::new(1))
979 },
980 ChunkRow {
981 linked_chunk_id: linked_chunk_id.clone(),
982 previous_chunk: Some(CId::new(0)),
983 chunk: CId::new(1),
984 next_chunk: None
985 },
986 ],
987 );
988 assert_eq!(
990 relational_linked_chunk.items_chunks,
991 &[
992 ItemRow {
993 linked_chunk_id: linked_chunk_id.clone(),
994 position: Position::new(CId::new(0), 0),
995 item: Either::Item('a')
996 },
997 ItemRow {
998 linked_chunk_id: linked_chunk_id.clone(),
999 position: Position::new(CId::new(0), 1),
1000 item: Either::Item('b')
1001 },
1002 ItemRow {
1003 linked_chunk_id: linked_chunk_id.clone(),
1004 position: Position::new(CId::new(1), 0),
1005 item: Either::Item('x')
1006 },
1007 ItemRow {
1008 linked_chunk_id: linked_chunk_id.clone(),
1009 position: Position::new(CId::new(1), 1),
1010 item: Either::Item('y')
1011 },
1012 ItemRow {
1013 linked_chunk_id: linked_chunk_id.clone(),
1014 position: Position::new(CId::new(1), 2),
1015 item: Either::Item('z')
1016 },
1017 ],
1018 );
1019 }
1020
1021 #[test]
1022 fn test_start_and_end_reattach_items() {
1023 let room_id = room_id!("!r0:matrix.org");
1024 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1025
1026 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1027
1028 relational_linked_chunk.apply_updates(
1029 linked_chunk_id.as_ref(),
1030 vec![Update::StartReattachItems, Update::EndReattachItems],
1031 );
1032
1033 assert!(relational_linked_chunk.chunks.is_empty());
1035 assert!(relational_linked_chunk.items_chunks.is_empty());
1036 }
1037
1038 #[test]
1039 fn test_clear() {
1040 let r0 = room_id!("!r0:matrix.org");
1041 let linked_chunk_id0 = OwnedLinkedChunkId::Room(r0.to_owned());
1042
1043 let r1 = room_id!("!r1:matrix.org");
1044 let linked_chunk_id1 = OwnedLinkedChunkId::Room(r1.to_owned());
1045
1046 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1047
1048 relational_linked_chunk.apply_updates(
1049 linked_chunk_id0.as_ref(),
1050 vec![
1051 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1053 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1055 ],
1056 );
1057
1058 relational_linked_chunk.apply_updates(
1059 linked_chunk_id1.as_ref(),
1060 vec![
1061 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1063 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x'] },
1065 ],
1066 );
1067
1068 assert_eq!(
1070 relational_linked_chunk.chunks,
1071 &[
1072 ChunkRow {
1073 linked_chunk_id: linked_chunk_id0.to_owned(),
1074 previous_chunk: None,
1075 chunk: CId::new(0),
1076 next_chunk: None,
1077 },
1078 ChunkRow {
1079 linked_chunk_id: linked_chunk_id1.to_owned(),
1080 previous_chunk: None,
1081 chunk: CId::new(0),
1082 next_chunk: None,
1083 }
1084 ],
1085 );
1086
1087 assert_eq!(
1089 relational_linked_chunk.items_chunks,
1090 &[
1091 ItemRow {
1092 linked_chunk_id: linked_chunk_id0.to_owned(),
1093 position: Position::new(CId::new(0), 0),
1094 item: Either::Item('a')
1095 },
1096 ItemRow {
1097 linked_chunk_id: linked_chunk_id0.to_owned(),
1098 position: Position::new(CId::new(0), 1),
1099 item: Either::Item('b')
1100 },
1101 ItemRow {
1102 linked_chunk_id: linked_chunk_id0.to_owned(),
1103 position: Position::new(CId::new(0), 2),
1104 item: Either::Item('c')
1105 },
1106 ItemRow {
1107 linked_chunk_id: linked_chunk_id1.to_owned(),
1108 position: Position::new(CId::new(0), 0),
1109 item: Either::Item('x')
1110 },
1111 ],
1112 );
1113
1114 relational_linked_chunk.apply_updates(linked_chunk_id0.as_ref(), vec![Update::Clear]);
1116
1117 assert_eq!(
1119 relational_linked_chunk.chunks,
1120 &[ChunkRow {
1121 linked_chunk_id: linked_chunk_id1.to_owned(),
1122 previous_chunk: None,
1123 chunk: CId::new(0),
1124 next_chunk: None,
1125 }],
1126 );
1127
1128 assert_eq!(
1129 relational_linked_chunk.items_chunks,
1130 &[ItemRow {
1131 linked_chunk_id: linked_chunk_id1.to_owned(),
1132 position: Position::new(CId::new(0), 0),
1133 item: Either::Item('x')
1134 },],
1135 );
1136 }
1137
1138 #[test]
1139 fn test_load_empty_linked_chunk() {
1140 let room_id = room_id!("!r0:matrix.org");
1141 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1142
1143 let relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1145 let result = relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap();
1146 assert!(result.is_empty());
1147 }
1148
1149 #[test]
1150 fn test_load_all_chunks_with_empty_items() {
1151 let room_id = room_id!("!r0:matrix.org");
1152 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1153
1154 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1155
1156 relational_linked_chunk.apply_updates(
1158 linked_chunk_id.as_ref(),
1159 vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }],
1160 );
1161
1162 let lc = from_all_chunks::<3, _, _>(
1164 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1165 )
1166 .expect("building succeeds")
1167 .expect("this leads to a non-empty linked chunk");
1168
1169 assert_items_eq!(lc, []);
1170 }
1171
1172 #[test]
1173 fn test_rebuild_linked_chunk() {
1174 let room_id = room_id!("!r0:matrix.org");
1175 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1176
1177 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1178
1179 relational_linked_chunk.apply_updates(
1180 linked_chunk_id.as_ref(),
1181 vec![
1182 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1184 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1186 Update::NewGapChunk {
1188 previous: Some(CId::new(0)),
1189 new: CId::new(1),
1190 next: None,
1191 gap: 'g',
1192 },
1193 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
1195 Update::PushItems { at: Position::new(CId::new(2), 0), items: vec!['d', 'e', 'f'] },
1197 ],
1198 );
1199
1200 let lc = from_all_chunks::<3, _, _>(
1201 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1202 )
1203 .expect("building succeeds")
1204 .expect("this leads to a non-empty linked chunk");
1205
1206 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']);
1208 }
1209
1210 #[test]
1211 fn test_replace_item() {
1212 let room_id = room_id!("!r0:matrix.org");
1213 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1214
1215 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1216
1217 relational_linked_chunk.apply_updates(
1218 linked_chunk_id.as_ref(),
1219 vec![
1220 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1222 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1224 Update::ReplaceItem { at: Position::new(CId::new(0), 1), item: 'B' },
1226 ],
1227 );
1228
1229 assert_eq!(
1231 relational_linked_chunk.chunks,
1232 &[ChunkRow {
1233 linked_chunk_id: linked_chunk_id.clone(),
1234 previous_chunk: None,
1235 chunk: CId::new(0),
1236 next_chunk: None,
1237 },],
1238 );
1239
1240 assert_eq!(
1242 relational_linked_chunk.items_chunks,
1243 &[
1244 ItemRow {
1245 linked_chunk_id: linked_chunk_id.clone(),
1246 position: Position::new(CId::new(0), 0),
1247 item: Either::Item('a')
1248 },
1249 ItemRow {
1250 linked_chunk_id: linked_chunk_id.clone(),
1251 position: Position::new(CId::new(0), 1),
1252 item: Either::Item('B')
1253 },
1254 ItemRow {
1255 linked_chunk_id,
1256 position: Position::new(CId::new(0), 2),
1257 item: Either::Item('c')
1258 },
1259 ],
1260 );
1261 }
1262
1263 #[test]
1264 fn test_unordered_events() {
1265 let room_id = room_id!("!r0:matrix.org");
1266 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1267
1268 let other_room_id = room_id!("!r1:matrix.org");
1269 let other_linked_chunk_id = OwnedLinkedChunkId::Room(other_room_id.to_owned());
1270
1271 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1272
1273 relational_linked_chunk.apply_updates(
1274 linked_chunk_id.as_ref(),
1275 vec![
1276 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1277 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
1278 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
1279 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['d', 'e', 'f'] },
1280 ],
1281 );
1282
1283 relational_linked_chunk.apply_updates(
1284 other_linked_chunk_id.as_ref(),
1285 vec![
1286 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1287 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x', 'y', 'z'] },
1288 ],
1289 );
1290
1291 let mut events =
1292 relational_linked_chunk.unordered_linked_chunk_items(linked_chunk_id.as_ref());
1293
1294 assert_eq!(events.next().unwrap(), (&'a', Position::new(CId::new(0), 0)));
1295 assert_eq!(events.next().unwrap(), (&'b', Position::new(CId::new(0), 1)));
1296 assert_eq!(events.next().unwrap(), (&'c', Position::new(CId::new(0), 2)));
1297 assert_eq!(events.next().unwrap(), (&'d', Position::new(CId::new(1), 0)));
1298 assert_eq!(events.next().unwrap(), (&'e', Position::new(CId::new(1), 1)));
1299 assert_eq!(events.next().unwrap(), (&'f', Position::new(CId::new(1), 2)));
1300 assert!(events.next().is_none());
1301 }
1302
1303 #[test]
1304 fn test_load_last_chunk() {
1305 let room_id = room_id!("!r0:matrix.org");
1306 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1307
1308 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1309
1310 {
1312 let (last_chunk, chunk_identifier_generator) =
1313 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1314
1315 assert!(last_chunk.is_none());
1316 assert_eq!(chunk_identifier_generator.current(), 0);
1317 }
1318
1319 {
1321 relational_linked_chunk.apply_updates(
1322 linked_chunk_id.as_ref(),
1323 vec![
1324 Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1325 Update::PushItems { at: Position::new(CId::new(42), 0), items: vec!['a', 'b'] },
1326 ],
1327 );
1328
1329 let (last_chunk, chunk_identifier_generator) =
1330 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1331
1332 assert_matches!(last_chunk, Some(last_chunk) => {
1333 assert_eq!(last_chunk.identifier, 42);
1334 assert!(last_chunk.previous.is_none());
1335 assert!(last_chunk.next.is_none());
1336 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1337 assert_eq!(items.len(), 2);
1338 assert_eq!(items, &['a', 'b']);
1339 });
1340 });
1341 assert_eq!(chunk_identifier_generator.current(), 42);
1342 }
1343
1344 {
1346 relational_linked_chunk.apply_updates(
1347 linked_chunk_id.as_ref(),
1348 vec![
1349 Update::NewItemsChunk {
1350 previous: Some(CId::new(42)),
1351 new: CId::new(7),
1352 next: None,
1353 },
1354 Update::PushItems {
1355 at: Position::new(CId::new(7), 0),
1356 items: vec!['c', 'd', 'e'],
1357 },
1358 ],
1359 );
1360
1361 let (last_chunk, chunk_identifier_generator) =
1362 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1363
1364 assert_matches!(last_chunk, Some(last_chunk) => {
1365 assert_eq!(last_chunk.identifier, 7);
1366 assert_matches!(last_chunk.previous, Some(previous) => {
1367 assert_eq!(previous, 42);
1368 });
1369 assert!(last_chunk.next.is_none());
1370 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1371 assert_eq!(items.len(), 3);
1372 assert_eq!(items, &['c', 'd', 'e']);
1373 });
1374 });
1375 assert_eq!(chunk_identifier_generator.current(), 42);
1376 }
1377 }
1378
1379 #[test]
1380 fn test_load_last_chunk_with_a_cycle() {
1381 let room_id = room_id!("!r0:matrix.org");
1382 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1383 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1384
1385 relational_linked_chunk.apply_updates(
1386 linked_chunk_id.as_ref(),
1387 vec![
1388 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1389 Update::NewItemsChunk {
1390 previous: Some(CId::new(0)),
1394 new: CId::new(1),
1395 next: Some(CId::new(0)),
1396 },
1397 ],
1398 );
1399
1400 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap_err();
1401 }
1402
1403 #[test]
1404 fn test_load_previous_chunk() {
1405 let room_id = room_id!("!r0:matrix.org");
1406 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1407 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1408
1409 {
1412 let previous_chunk = relational_linked_chunk
1413 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(153))
1414 .unwrap();
1415
1416 assert!(previous_chunk.is_none());
1417 }
1418
1419 {
1422 relational_linked_chunk.apply_updates(
1423 linked_chunk_id.as_ref(),
1424 vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
1425 );
1426
1427 let previous_chunk = relational_linked_chunk
1428 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1429 .unwrap();
1430
1431 assert!(previous_chunk.is_none());
1432 }
1433
1434 {
1436 relational_linked_chunk.apply_updates(
1437 linked_chunk_id.as_ref(),
1438 vec![
1439 Update::NewItemsChunk {
1441 previous: None,
1442 new: CId::new(7),
1443 next: Some(CId::new(42)),
1444 },
1445 Update::PushItems {
1446 at: Position::new(CId::new(7), 0),
1447 items: vec!['a', 'b', 'c'],
1448 },
1449 ],
1450 );
1451
1452 let previous_chunk = relational_linked_chunk
1453 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1454 .unwrap();
1455
1456 assert_matches!(previous_chunk, Some(previous_chunk) => {
1457 assert_eq!(previous_chunk.identifier, 7);
1458 assert!(previous_chunk.previous.is_none());
1459 assert_matches!(previous_chunk.next, Some(next) => {
1460 assert_eq!(next, 42);
1461 });
1462 assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
1463 assert_eq!(items.len(), 3);
1464 assert_eq!(items, &['a', 'b', 'c']);
1465 });
1466 });
1467 }
1468 }
1469}