1use std::{
19 collections::{BTreeMap, HashMap, HashSet},
20 hash::Hash,
21};
22
23use ruma::{OwnedEventId, OwnedRoomId, RoomId};
24use thiserror::Error;
25
26use super::{ChunkContent, ChunkIdentifierGenerator, RawChunk};
27use crate::{
28 deserialized_responses::TimelineEvent,
29 linked_chunk::{
30 ChunkIdentifier, ChunkMetadata, LinkedChunkId, OwnedLinkedChunkId, Position, Update,
31 },
32};
33
34#[derive(Debug, PartialEq)]
36struct ChunkRow {
37 linked_chunk_id: OwnedLinkedChunkId,
38 previous_chunk: Option<ChunkIdentifier>,
39 chunk: ChunkIdentifier,
40 next_chunk: Option<ChunkIdentifier>,
41}
42
43#[derive(Debug, PartialEq)]
45struct ItemRow<ItemId, Gap> {
46 linked_chunk_id: OwnedLinkedChunkId,
47 position: Position,
48 item: Either<ItemId, Gap>,
49}
50
51#[derive(Debug, PartialEq)]
53enum Either<Item, Gap> {
54 Item(Item),
56
57 Gap(Gap),
59}
60
61#[derive(Debug)]
80pub struct RelationalLinkedChunk<ItemId, Item, Gap> {
81 chunks: Vec<ChunkRow>,
83
84 items_chunks: Vec<ItemRow<ItemId, Gap>>,
86
87 items: HashMap<OwnedLinkedChunkId, BTreeMap<ItemId, (Item, Option<Position>)>>,
89}
90
91#[derive(Debug, Error)]
94pub enum RelationalLinkedChunkError {
95 #[error("invalid chunk identifier: `{identifier:?}`")]
97 InvalidChunkIdentifier {
98 identifier: ChunkIdentifier,
100 },
101}
102
103pub trait IndexableItem {
106 type ItemId: Hash + PartialEq + Eq + Clone;
107
108 fn id(&self) -> Self::ItemId;
110}
111
112impl IndexableItem for TimelineEvent {
113 type ItemId = OwnedEventId;
114
115 fn id(&self) -> Self::ItemId {
116 self.event_id()
117 .expect("all events saved into a relational linked chunk must have a valid event id")
118 .to_owned()
119 }
120}
121
122impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
123where
124 Item: IndexableItem<ItemId = ItemId>,
125 ItemId: Hash + PartialEq + Eq + Clone + Ord,
126{
127 pub fn new() -> Self {
129 Self { chunks: Vec::new(), items_chunks: Vec::new(), items: HashMap::new() }
130 }
131
132 pub fn clear_room(&mut self, room_id: &RoomId) {
135 self.chunks.retain(|ChunkRow { linked_chunk_id, .. }| linked_chunk_id.room_id() != room_id);
136 self.items_chunks
137 .retain(|ItemRow { linked_chunk_id, .. }| linked_chunk_id.room_id() != room_id);
138 self.items.retain(|key, _| key.room_id() != room_id);
139 }
140
141 pub fn clear(&mut self) {
143 self.chunks.clear();
144 self.items_chunks.clear();
145 self.items.clear();
146 }
147
148 pub fn apply_updates(
151 &mut self,
152 linked_chunk_id: LinkedChunkId<'_>,
153 updates: Vec<Update<Item, Gap>>,
154 ) -> Result<(), RelationalLinkedChunkError> {
155 for update in updates {
156 match update {
157 Update::NewItemsChunk { previous, new, next } => {
158 Self::insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next)?;
159 }
160
161 Update::NewGapChunk { previous, new, next, gap } => {
162 Self::insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next)?;
163 self.items_chunks.push(ItemRow {
164 linked_chunk_id: linked_chunk_id.to_owned(),
165 position: Position::new(new, 0),
166 item: Either::Gap(gap),
167 });
168 }
169
170 Update::RemoveChunk(chunk_identifier) => {
171 Self::remove_chunk(&mut self.chunks, linked_chunk_id, chunk_identifier);
172
173 let indices_to_remove = self
174 .items_chunks
175 .iter()
176 .enumerate()
177 .filter_map(
178 |(
179 nth,
180 ItemRow {
181 linked_chunk_id: linked_chunk_id_candidate,
182 position,
183 ..
184 },
185 )| {
186 (linked_chunk_id == linked_chunk_id_candidate
187 && position.chunk_identifier() == chunk_identifier)
188 .then_some(nth)
189 },
190 )
191 .collect::<Vec<_>>();
192
193 for index_to_remove in indices_to_remove.into_iter().rev() {
194 self.items_chunks.remove(index_to_remove);
195 }
196 }
197
198 Update::PushItems { mut at, items } => {
199 for item in items {
200 let item_id = item.id();
201 self.items
202 .entry(linked_chunk_id.to_owned())
203 .or_default()
204 .insert(item_id.clone(), (item, Some(at)));
205 self.items_chunks.push(ItemRow {
206 linked_chunk_id: linked_chunk_id.to_owned(),
207 position: at,
208 item: Either::Item(item_id),
209 });
210 at.increment_index();
211 }
212 }
213
214 Update::ReplaceItem { at, item } => {
215 let existing = self
216 .items_chunks
217 .iter_mut()
218 .find(|item| item.position == at)
219 .expect("trying to replace at an unknown position");
220 assert!(
221 matches!(existing.item, Either::Item(..)),
222 "trying to replace a gap with an item"
223 );
224 let item_id = item.id();
225 self.items
226 .entry(linked_chunk_id.to_owned())
227 .or_default()
228 .insert(item_id.clone(), (item, Some(at)));
229 existing.item = Either::Item(item_id);
230 }
231
232 Update::RemoveItem { at } => {
233 let mut entry_to_remove = None;
234
235 for (
236 nth,
237 ItemRow { linked_chunk_id: linked_chunk_id_candidate, position, .. },
238 ) in self.items_chunks.iter_mut().enumerate()
239 {
240 if linked_chunk_id != &*linked_chunk_id_candidate {
242 continue;
243 }
244
245 if *position == at {
247 debug_assert!(entry_to_remove.is_none(), "Found the same entry twice");
248
249 entry_to_remove = Some(nth);
250 }
251
252 if position.chunk_identifier() == at.chunk_identifier()
254 && position.index() > at.index()
255 {
256 position.decrement_index();
257 }
258 }
259
260 self.items_chunks.remove(entry_to_remove.expect("Remove an unknown item"));
261 }
263
264 Update::DetachLastItems { at } => {
265 let indices_to_remove = self
266 .items_chunks
267 .iter()
268 .enumerate()
269 .filter_map(
270 |(
271 nth,
272 ItemRow {
273 linked_chunk_id: linked_chunk_id_candidate,
274 position,
275 ..
276 },
277 )| {
278 (linked_chunk_id == linked_chunk_id_candidate
279 && position.chunk_identifier() == at.chunk_identifier()
280 && position.index() >= at.index())
281 .then_some(nth)
282 },
283 )
284 .collect::<Vec<_>>();
285
286 for index_to_remove in indices_to_remove.into_iter().rev() {
287 self.items_chunks.remove(index_to_remove);
288 }
289 }
290
291 Update::StartReattachItems | Update::EndReattachItems => { }
292
293 Update::Clear => {
294 self.chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id);
295 self.items_chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id);
296 }
298 }
299 }
300 Ok(())
301 }
302
303 fn insert_chunk(
304 chunks: &mut Vec<ChunkRow>,
305 linked_chunk_id: LinkedChunkId<'_>,
306 previous: Option<ChunkIdentifier>,
307 new: ChunkIdentifier,
308 next: Option<ChunkIdentifier>,
309 ) -> Result<(), RelationalLinkedChunkError> {
310 if let Some(previous) = previous {
312 let entry_for_previous_chunk = chunks
313 .iter_mut()
314 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
315 linked_chunk_id == linked_chunk_id_candidate && *chunk == previous
316 })
317 .ok_or(RelationalLinkedChunkError::InvalidChunkIdentifier {
318 identifier: previous,
319 })?;
320
321 entry_for_previous_chunk.next_chunk = Some(new);
323 }
324
325 if let Some(next) = next {
327 let entry_for_next_chunk = chunks
328 .iter_mut()
329 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
330 linked_chunk_id == linked_chunk_id_candidate && *chunk == next
331 })
332 .ok_or(RelationalLinkedChunkError::InvalidChunkIdentifier { identifier: next })?;
333
334 entry_for_next_chunk.previous_chunk = Some(new);
336 }
337
338 chunks.push(ChunkRow {
340 linked_chunk_id: linked_chunk_id.to_owned(),
341 previous_chunk: previous,
342 chunk: new,
343 next_chunk: next,
344 });
345
346 Ok(())
347 }
348
349 fn remove_chunk(
350 chunks: &mut Vec<ChunkRow>,
351 linked_chunk_id: LinkedChunkId<'_>,
352 chunk_to_remove: ChunkIdentifier,
353 ) {
354 let entry_nth_to_remove = chunks
355 .iter()
356 .enumerate()
357 .find_map(
358 |(nth, ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. })| {
359 (linked_chunk_id == linked_chunk_id_candidate && *chunk == chunk_to_remove)
360 .then_some(nth)
361 },
362 )
363 .expect("Remove an unknown chunk");
364
365 let ChunkRow { linked_chunk_id, previous_chunk: previous, next_chunk: next, .. } =
366 chunks.remove(entry_nth_to_remove);
367
368 if let Some(previous) = previous {
370 let entry_for_previous_chunk = chunks
371 .iter_mut()
372 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
373 &linked_chunk_id == linked_chunk_id_candidate && *chunk == previous
374 })
375 .expect("Previous chunk should be present");
376
377 entry_for_previous_chunk.next_chunk = next;
379 }
380
381 if let Some(next) = next {
383 let entry_for_next_chunk = chunks
384 .iter_mut()
385 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
386 &linked_chunk_id == linked_chunk_id_candidate && *chunk == next
387 })
388 .expect("Next chunk should be present");
389
390 entry_for_next_chunk.previous_chunk = previous;
392 }
393 }
394
395 pub fn unordered_linked_chunk_items<'a>(
398 &'a self,
399 target: &OwnedLinkedChunkId,
400 ) -> impl Iterator<Item = (&'a Item, Position)> + use<'a, ItemId, Item, Gap> {
401 self.items.get(target).into_iter().flat_map(|items| {
402 items.values().filter_map(|(item, pos)| pos.map(|pos| (item, pos)))
404 })
405 }
406
407 pub fn items<'a>(
416 &'a self,
417 room_id: &'a RoomId,
418 ) -> impl Iterator<Item = (&'a OwnedLinkedChunkId, (&'a Item, Option<Position>))> {
419 self.items
420 .iter()
421 .filter(move |(linked_chunk_id, _)| linked_chunk_id.room_id() == room_id)
422 .flat_map(|(linked_chunk_id, items)| {
423 items.values().map(move |(item, pos)| (linked_chunk_id, (item, *pos)))
424 })
425 }
426}
427
428impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
429where
430 Item: IndexableItem<ItemId = ItemId> + Clone,
431 ItemId: Hash + PartialEq + Eq + Clone + Ord,
432{
433 pub fn save_item(&mut self, room_id: OwnedRoomId, item: Item) {
435 let id = item.id();
436
437 let mut linked_chunk_ids = self
438 .items
439 .keys()
440 .filter(|linked_chunk_id| linked_chunk_id.room_id() == room_id)
441 .cloned()
442 .collect::<HashSet<_>>();
443 linked_chunk_ids.insert(OwnedLinkedChunkId::Room(room_id));
444
445 for linked_chunk_id in linked_chunk_ids {
446 let map = self.items.entry(linked_chunk_id).or_default();
447 if let Some(prev_value) = map.get_mut(&id) {
448 prev_value.0 = item.clone();
450 } else {
451 map.insert(id.clone(), (item.clone(), None));
452 }
453 }
454 }
455}
456
457impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
458where
459 Gap: Clone,
460 Item: Clone,
461 ItemId: Hash + PartialEq + Eq + Ord,
462{
463 #[doc(hidden)]
468 pub fn load_all_chunks(
469 &self,
470 linked_chunk_id: LinkedChunkId<'_>,
471 ) -> Result<Vec<RawChunk<Item, Gap>>, String> {
472 self.chunks
473 .iter()
474 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
475 .map(|chunk_row| load_raw_chunk(self, chunk_row, linked_chunk_id))
476 .collect::<Result<Vec<_>, String>>()
477 }
478
479 #[doc(hidden)]
484 pub fn load_all_chunks_metadata(
485 &self,
486 linked_chunk_id: LinkedChunkId<'_>,
487 ) -> Result<Vec<ChunkMetadata>, String> {
488 self.chunks
489 .iter()
490 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
491 .map(|chunk_row| load_raw_chunk_metadata(self, chunk_row, linked_chunk_id))
492 .collect::<Result<Vec<_>, String>>()
493 }
494
495 pub fn load_last_chunk(
496 &self,
497 linked_chunk_id: LinkedChunkId<'_>,
498 ) -> Result<(Option<RawChunk<Item, Gap>>, ChunkIdentifierGenerator), String> {
499 let chunk_identifier_generator = match self
501 .chunks
502 .iter()
503 .filter_map(|chunk_row| {
504 (chunk_row.linked_chunk_id == linked_chunk_id).then_some(chunk_row.chunk)
505 })
506 .max()
507 {
508 Some(last_chunk_identifier) => {
509 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier)
510 }
511 None => ChunkIdentifierGenerator::new_from_scratch(),
512 };
513
514 let mut number_of_chunks = 0;
516 let mut chunk_row = None;
517
518 for chunk_row_candidate in &self.chunks {
519 if chunk_row_candidate.linked_chunk_id == linked_chunk_id {
520 number_of_chunks += 1;
521
522 if chunk_row_candidate.next_chunk.is_none() {
523 chunk_row = Some(chunk_row_candidate);
524
525 break;
526 }
527 }
528 }
529
530 let chunk_row = match chunk_row {
531 Some(chunk_row) => chunk_row,
533
534 None if number_of_chunks == 0 => {
537 return Ok((None, chunk_identifier_generator));
538 }
539
540 None => {
545 return Err(
546 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
547 .to_owned(),
548 );
549 }
550 };
551
552 load_raw_chunk(self, chunk_row, linked_chunk_id)
554 .map(|raw_chunk| (Some(raw_chunk), chunk_identifier_generator))
555 }
556
557 pub fn load_previous_chunk(
558 &self,
559 linked_chunk_id: LinkedChunkId<'_>,
560 before_chunk_identifier: ChunkIdentifier,
561 ) -> Result<Option<RawChunk<Item, Gap>>, String> {
562 let Some(chunk_row) = self.chunks.iter().find(|chunk_row| {
564 chunk_row.linked_chunk_id == linked_chunk_id
565 && chunk_row.next_chunk == Some(before_chunk_identifier)
566 }) else {
567 return Ok(None);
569 };
570
571 load_raw_chunk(self, chunk_row, linked_chunk_id).map(Some)
573 }
574}
575
576impl<ItemId, Item, Gap> Default for RelationalLinkedChunk<ItemId, Item, Gap>
577where
578 Item: IndexableItem<ItemId = ItemId>,
579 ItemId: Hash + PartialEq + Eq + Clone + Ord,
580{
581 fn default() -> Self {
582 Self::new()
583 }
584}
585
586fn load_raw_chunk<ItemId, Item, Gap>(
591 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
592 chunk_row: &ChunkRow,
593 linked_chunk_id: LinkedChunkId<'_>,
594) -> Result<RawChunk<Item, Gap>, String>
595where
596 Item: Clone,
597 Gap: Clone,
598 ItemId: Hash + PartialEq + Eq + Ord,
599{
600 let mut items = relational_linked_chunk
602 .items_chunks
603 .iter()
604 .filter(|item_row| {
605 item_row.linked_chunk_id == linked_chunk_id
606 && item_row.position.chunk_identifier() == chunk_row.chunk
607 })
608 .peekable();
609
610 let Some(first_item) = items.peek() else {
611 return Ok(RawChunk {
613 content: ChunkContent::Items(Vec::new()),
614 previous: chunk_row.previous_chunk,
615 identifier: chunk_row.chunk,
616 next: chunk_row.next_chunk,
617 });
618 };
619
620 Ok(match first_item.item {
621 Either::Item(_) => {
623 let mut collected_items = Vec::new();
625
626 for item_row in items {
627 match &item_row.item {
628 Either::Item(item_id) => {
629 collected_items.push((item_id, item_row.position.index()))
630 }
631
632 Either::Gap(_) => {
633 return Err(format!(
634 "unexpected gap in items chunk {}",
635 chunk_row.chunk.index()
636 ));
637 }
638 }
639 }
640
641 collected_items.sort_unstable_by_key(|(_item, index)| *index);
643
644 RawChunk {
645 content: ChunkContent::Items(
646 collected_items
647 .into_iter()
648 .filter_map(|(item_id, _index)| {
649 Some(
650 relational_linked_chunk
651 .items
652 .get(&linked_chunk_id.to_owned())?
653 .get(item_id)?
654 .0
655 .clone(),
656 )
657 })
658 .collect(),
659 ),
660 previous: chunk_row.previous_chunk,
661 identifier: chunk_row.chunk,
662 next: chunk_row.next_chunk,
663 }
664 }
665
666 Either::Gap(ref gap) => {
667 assert!(items.next().is_some(), "we just peeked the gap");
668
669 if items.next().is_some() {
671 return Err(format!(
672 "there shouldn't be more than one item row attached in gap chunk {}",
673 chunk_row.chunk.index()
674 ));
675 }
676
677 RawChunk {
678 content: ChunkContent::Gap(gap.clone()),
679 previous: chunk_row.previous_chunk,
680 identifier: chunk_row.chunk,
681 next: chunk_row.next_chunk,
682 }
683 }
684 })
685}
686
687fn load_raw_chunk_metadata<ItemId, Item, Gap>(
692 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
693 chunk_row: &ChunkRow,
694 linked_chunk_id: LinkedChunkId<'_>,
695) -> Result<ChunkMetadata, String>
696where
697 Item: Clone,
698 Gap: Clone,
699 ItemId: Hash + PartialEq + Eq,
700{
701 let mut items = relational_linked_chunk
703 .items_chunks
704 .iter()
705 .filter(|item_row| {
706 item_row.linked_chunk_id == linked_chunk_id
707 && item_row.position.chunk_identifier() == chunk_row.chunk
708 })
709 .peekable();
710
711 let Some(first_item) = items.peek() else {
712 return Ok(ChunkMetadata {
714 num_items: 0,
715 previous: chunk_row.previous_chunk,
716 identifier: chunk_row.chunk,
717 next: chunk_row.next_chunk,
718 });
719 };
720
721 Ok(match first_item.item {
722 Either::Item(_) => {
724 let mut num_items = 0;
728 for item in items {
729 match &item.item {
730 Either::Item(_) => num_items += 1,
731 Either::Gap(_) => {
732 return Err(format!(
733 "unexpected gap in items chunk {}",
734 chunk_row.chunk.index()
735 ));
736 }
737 }
738 }
739
740 ChunkMetadata {
741 num_items,
742 previous: chunk_row.previous_chunk,
743 identifier: chunk_row.chunk,
744 next: chunk_row.next_chunk,
745 }
746 }
747
748 Either::Gap(..) => {
749 assert!(items.next().is_some(), "we just peeked the gap");
750
751 if items.next().is_some() {
753 return Err(format!(
754 "there shouldn't be more than one item row attached in gap chunk {}",
755 chunk_row.chunk.index()
756 ));
757 }
758
759 ChunkMetadata {
760 num_items: 0,
762 previous: chunk_row.previous_chunk,
763 identifier: chunk_row.chunk,
764 next: chunk_row.next_chunk,
765 }
766 }
767 })
768}
769
770#[cfg(test)]
771mod tests {
772 use std::collections::BTreeMap;
773
774 use assert_matches::assert_matches;
775 use ruma::room_id;
776
777 use super::{super::lazy_loader::from_all_chunks, ChunkIdentifier as CId, *};
778
779 impl IndexableItem for char {
780 type ItemId = char;
781
782 fn id(&self) -> Self::ItemId {
783 *self
784 }
785 }
786
787 #[test]
788 fn test_new_items_chunk() {
789 let room_id = room_id!("!r0:matrix.org");
790 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
791
792 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
793
794 relational_linked_chunk
795 .apply_updates(
796 linked_chunk_id.as_ref(),
797 vec![
798 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
800 Update::NewItemsChunk {
802 previous: Some(CId::new(0)),
803 new: CId::new(1),
804 next: None,
805 },
806 Update::NewItemsChunk {
808 previous: None,
809 new: CId::new(2),
810 next: Some(CId::new(0)),
811 },
812 Update::NewItemsChunk {
814 previous: Some(CId::new(2)),
815 new: CId::new(3),
816 next: Some(CId::new(0)),
817 },
818 ],
819 )
820 .unwrap();
821
822 assert_eq!(
824 relational_linked_chunk.chunks,
825 &[
826 ChunkRow {
827 linked_chunk_id: linked_chunk_id.clone(),
828 previous_chunk: Some(CId::new(3)),
829 chunk: CId::new(0),
830 next_chunk: Some(CId::new(1))
831 },
832 ChunkRow {
833 linked_chunk_id: linked_chunk_id.clone(),
834 previous_chunk: Some(CId::new(0)),
835 chunk: CId::new(1),
836 next_chunk: None
837 },
838 ChunkRow {
839 linked_chunk_id: linked_chunk_id.clone(),
840 previous_chunk: None,
841 chunk: CId::new(2),
842 next_chunk: Some(CId::new(3))
843 },
844 ChunkRow {
845 linked_chunk_id,
846 previous_chunk: Some(CId::new(2)),
847 chunk: CId::new(3),
848 next_chunk: Some(CId::new(0))
849 },
850 ],
851 );
852
853 assert!(relational_linked_chunk.items_chunks.is_empty());
855 }
856
857 #[test]
858 fn test_new_gap_chunk() {
859 let room_id = room_id!("!r0:matrix.org");
860 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
861
862 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
863
864 relational_linked_chunk
865 .apply_updates(
866 linked_chunk_id.as_ref(),
867 vec![
868 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
870 Update::NewGapChunk {
872 previous: Some(CId::new(0)),
873 new: CId::new(1),
874 next: None,
875 gap: (),
876 },
877 Update::NewItemsChunk {
879 previous: Some(CId::new(1)),
880 new: CId::new(2),
881 next: None,
882 },
883 ],
884 )
885 .unwrap();
886
887 assert_eq!(
889 relational_linked_chunk.chunks,
890 &[
891 ChunkRow {
892 linked_chunk_id: linked_chunk_id.clone(),
893 previous_chunk: None,
894 chunk: CId::new(0),
895 next_chunk: Some(CId::new(1))
896 },
897 ChunkRow {
898 linked_chunk_id: linked_chunk_id.clone(),
899 previous_chunk: Some(CId::new(0)),
900 chunk: CId::new(1),
901 next_chunk: Some(CId::new(2))
902 },
903 ChunkRow {
904 linked_chunk_id: linked_chunk_id.clone(),
905 previous_chunk: Some(CId::new(1)),
906 chunk: CId::new(2),
907 next_chunk: None
908 },
909 ],
910 );
911 assert_eq!(
913 relational_linked_chunk.items_chunks,
914 &[ItemRow {
915 linked_chunk_id,
916 position: Position::new(CId::new(1), 0),
917 item: Either::Gap(())
918 }],
919 );
920 }
921
922 #[test]
923 fn test_remove_chunk() {
924 let room_id = room_id!("!r0:matrix.org");
925 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
926
927 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
928
929 relational_linked_chunk
930 .apply_updates(
931 linked_chunk_id.as_ref(),
932 vec![
933 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
935 Update::NewGapChunk {
937 previous: Some(CId::new(0)),
938 new: CId::new(1),
939 next: None,
940 gap: (),
941 },
942 Update::NewItemsChunk {
944 previous: Some(CId::new(1)),
945 new: CId::new(2),
946 next: None,
947 },
948 Update::RemoveChunk(CId::new(1)),
950 ],
951 )
952 .unwrap();
953
954 assert_eq!(
956 relational_linked_chunk.chunks,
957 &[
958 ChunkRow {
959 linked_chunk_id: linked_chunk_id.clone(),
960 previous_chunk: None,
961 chunk: CId::new(0),
962 next_chunk: Some(CId::new(2))
963 },
964 ChunkRow {
965 linked_chunk_id,
966 previous_chunk: Some(CId::new(0)),
967 chunk: CId::new(2),
968 next_chunk: None
969 },
970 ],
971 );
972
973 assert!(relational_linked_chunk.items_chunks.is_empty());
975 }
976
977 #[test]
978 fn test_push_items() {
979 let room_id = room_id!("!r0:matrix.org");
980 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
981
982 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
983
984 relational_linked_chunk
985 .apply_updates(
986 linked_chunk_id.as_ref(),
987 vec![
988 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
991 Update::PushItems {
993 at: Position::new(CId::new(0), 0),
994 items: vec!['a', 'b', 'c'],
995 },
996 Update::NewItemsChunk {
998 previous: Some(CId::new(0)),
999 new: CId::new(1),
1000 next: None,
1001 },
1002 Update::PushItems {
1004 at: Position::new(CId::new(1), 0),
1005 items: vec!['x', 'y', 'z'],
1006 },
1007 Update::PushItems { at: Position::new(CId::new(0), 3), items: vec!['d', 'e'] },
1009 ],
1010 )
1011 .unwrap();
1012
1013 assert_eq!(
1015 relational_linked_chunk.chunks,
1016 &[
1017 ChunkRow {
1018 linked_chunk_id: linked_chunk_id.clone(),
1019 previous_chunk: None,
1020 chunk: CId::new(0),
1021 next_chunk: Some(CId::new(1))
1022 },
1023 ChunkRow {
1024 linked_chunk_id: linked_chunk_id.clone(),
1025 previous_chunk: Some(CId::new(0)),
1026 chunk: CId::new(1),
1027 next_chunk: None
1028 },
1029 ],
1030 );
1031 assert_eq!(
1033 relational_linked_chunk.items_chunks,
1034 &[
1035 ItemRow {
1036 linked_chunk_id: linked_chunk_id.clone(),
1037 position: Position::new(CId::new(0), 0),
1038 item: Either::Item('a')
1039 },
1040 ItemRow {
1041 linked_chunk_id: linked_chunk_id.clone(),
1042 position: Position::new(CId::new(0), 1),
1043 item: Either::Item('b')
1044 },
1045 ItemRow {
1046 linked_chunk_id: linked_chunk_id.clone(),
1047 position: Position::new(CId::new(0), 2),
1048 item: Either::Item('c')
1049 },
1050 ItemRow {
1051 linked_chunk_id: linked_chunk_id.clone(),
1052 position: Position::new(CId::new(1), 0),
1053 item: Either::Item('x')
1054 },
1055 ItemRow {
1056 linked_chunk_id: linked_chunk_id.clone(),
1057 position: Position::new(CId::new(1), 1),
1058 item: Either::Item('y')
1059 },
1060 ItemRow {
1061 linked_chunk_id: linked_chunk_id.clone(),
1062 position: Position::new(CId::new(1), 2),
1063 item: Either::Item('z')
1064 },
1065 ItemRow {
1066 linked_chunk_id: linked_chunk_id.clone(),
1067 position: Position::new(CId::new(0), 3),
1068 item: Either::Item('d')
1069 },
1070 ItemRow {
1071 linked_chunk_id: linked_chunk_id.clone(),
1072 position: Position::new(CId::new(0), 4),
1073 item: Either::Item('e')
1074 },
1075 ],
1076 );
1077 }
1078
1079 #[test]
1080 fn test_remove_item() {
1081 let room_id = room_id!("!r0:matrix.org");
1082 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1083
1084 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1085
1086 relational_linked_chunk
1087 .apply_updates(
1088 linked_chunk_id.as_ref(),
1089 vec![
1090 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1093 Update::PushItems {
1095 at: Position::new(CId::new(0), 0),
1096 items: vec!['a', 'b', 'c', 'd', 'e'],
1097 },
1098 Update::RemoveItem { at: Position::new(CId::new(0), 0) },
1100 Update::RemoveItem { at: Position::new(CId::new(0), 2) },
1102 ],
1103 )
1104 .unwrap();
1105
1106 assert_eq!(
1108 relational_linked_chunk.chunks,
1109 &[ChunkRow {
1110 linked_chunk_id: linked_chunk_id.clone(),
1111 previous_chunk: None,
1112 chunk: CId::new(0),
1113 next_chunk: None
1114 }],
1115 );
1116 assert_eq!(
1118 relational_linked_chunk.items_chunks,
1119 &[
1120 ItemRow {
1121 linked_chunk_id: linked_chunk_id.clone(),
1122 position: Position::new(CId::new(0), 0),
1123 item: Either::Item('b')
1124 },
1125 ItemRow {
1126 linked_chunk_id: linked_chunk_id.clone(),
1127 position: Position::new(CId::new(0), 1),
1128 item: Either::Item('c')
1129 },
1130 ItemRow {
1131 linked_chunk_id: linked_chunk_id.clone(),
1132 position: Position::new(CId::new(0), 2),
1133 item: Either::Item('e')
1134 },
1135 ],
1136 );
1137 }
1138
1139 #[test]
1140 fn test_detach_last_items() {
1141 let room_id = room_id!("!r0:matrix.org");
1142 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1143
1144 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1145
1146 relational_linked_chunk
1147 .apply_updates(
1148 linked_chunk_id.as_ref(),
1149 vec![
1150 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1152 Update::NewItemsChunk {
1154 previous: Some(CId::new(0)),
1155 new: CId::new(1),
1156 next: None,
1157 },
1158 Update::PushItems {
1160 at: Position::new(CId::new(0), 0),
1161 items: vec!['a', 'b', 'c', 'd', 'e'],
1162 },
1163 Update::PushItems {
1165 at: Position::new(CId::new(1), 0),
1166 items: vec!['x', 'y', 'z'],
1167 },
1168 Update::DetachLastItems { at: Position::new(CId::new(0), 2) },
1170 ],
1171 )
1172 .unwrap();
1173
1174 assert_eq!(
1176 relational_linked_chunk.chunks,
1177 &[
1178 ChunkRow {
1179 linked_chunk_id: linked_chunk_id.clone(),
1180 previous_chunk: None,
1181 chunk: CId::new(0),
1182 next_chunk: Some(CId::new(1))
1183 },
1184 ChunkRow {
1185 linked_chunk_id: linked_chunk_id.clone(),
1186 previous_chunk: Some(CId::new(0)),
1187 chunk: CId::new(1),
1188 next_chunk: None
1189 },
1190 ],
1191 );
1192 assert_eq!(
1194 relational_linked_chunk.items_chunks,
1195 &[
1196 ItemRow {
1197 linked_chunk_id: linked_chunk_id.clone(),
1198 position: Position::new(CId::new(0), 0),
1199 item: Either::Item('a')
1200 },
1201 ItemRow {
1202 linked_chunk_id: linked_chunk_id.clone(),
1203 position: Position::new(CId::new(0), 1),
1204 item: Either::Item('b')
1205 },
1206 ItemRow {
1207 linked_chunk_id: linked_chunk_id.clone(),
1208 position: Position::new(CId::new(1), 0),
1209 item: Either::Item('x')
1210 },
1211 ItemRow {
1212 linked_chunk_id: linked_chunk_id.clone(),
1213 position: Position::new(CId::new(1), 1),
1214 item: Either::Item('y')
1215 },
1216 ItemRow {
1217 linked_chunk_id: linked_chunk_id.clone(),
1218 position: Position::new(CId::new(1), 2),
1219 item: Either::Item('z')
1220 },
1221 ],
1222 );
1223 }
1224
1225 #[test]
1226 fn test_start_and_end_reattach_items() {
1227 let room_id = room_id!("!r0:matrix.org");
1228 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1229
1230 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1231
1232 relational_linked_chunk
1233 .apply_updates(
1234 linked_chunk_id.as_ref(),
1235 vec![Update::StartReattachItems, Update::EndReattachItems],
1236 )
1237 .unwrap();
1238
1239 assert!(relational_linked_chunk.chunks.is_empty());
1241 assert!(relational_linked_chunk.items_chunks.is_empty());
1242 }
1243
1244 #[test]
1245 fn test_clear() {
1246 let r0 = room_id!("!r0:matrix.org");
1247 let linked_chunk_id0 = OwnedLinkedChunkId::Room(r0.to_owned());
1248
1249 let r1 = room_id!("!r1:matrix.org");
1250 let linked_chunk_id1 = OwnedLinkedChunkId::Room(r1.to_owned());
1251
1252 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1253
1254 relational_linked_chunk
1255 .apply_updates(
1256 linked_chunk_id0.as_ref(),
1257 vec![
1258 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1261 Update::PushItems {
1263 at: Position::new(CId::new(0), 0),
1264 items: vec!['a', 'b', 'c'],
1265 },
1266 ],
1267 )
1268 .unwrap();
1269
1270 relational_linked_chunk
1271 .apply_updates(
1272 linked_chunk_id1.as_ref(),
1273 vec![
1274 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1277 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x'] },
1279 ],
1280 )
1281 .unwrap();
1282
1283 assert_eq!(
1285 relational_linked_chunk.chunks,
1286 &[
1287 ChunkRow {
1288 linked_chunk_id: linked_chunk_id0.to_owned(),
1289 previous_chunk: None,
1290 chunk: CId::new(0),
1291 next_chunk: None,
1292 },
1293 ChunkRow {
1294 linked_chunk_id: linked_chunk_id1.to_owned(),
1295 previous_chunk: None,
1296 chunk: CId::new(0),
1297 next_chunk: None,
1298 }
1299 ],
1300 );
1301
1302 assert_eq!(
1304 relational_linked_chunk.items_chunks,
1305 &[
1306 ItemRow {
1307 linked_chunk_id: linked_chunk_id0.to_owned(),
1308 position: Position::new(CId::new(0), 0),
1309 item: Either::Item('a')
1310 },
1311 ItemRow {
1312 linked_chunk_id: linked_chunk_id0.to_owned(),
1313 position: Position::new(CId::new(0), 1),
1314 item: Either::Item('b')
1315 },
1316 ItemRow {
1317 linked_chunk_id: linked_chunk_id0.to_owned(),
1318 position: Position::new(CId::new(0), 2),
1319 item: Either::Item('c')
1320 },
1321 ItemRow {
1322 linked_chunk_id: linked_chunk_id1.to_owned(),
1323 position: Position::new(CId::new(0), 0),
1324 item: Either::Item('x')
1325 },
1326 ],
1327 );
1328
1329 relational_linked_chunk
1331 .apply_updates(linked_chunk_id0.as_ref(), vec![Update::Clear])
1332 .unwrap();
1333
1334 assert_eq!(
1336 relational_linked_chunk.chunks,
1337 &[ChunkRow {
1338 linked_chunk_id: linked_chunk_id1.to_owned(),
1339 previous_chunk: None,
1340 chunk: CId::new(0),
1341 next_chunk: None,
1342 }],
1343 );
1344
1345 assert_eq!(
1346 relational_linked_chunk.items_chunks,
1347 &[ItemRow {
1348 linked_chunk_id: linked_chunk_id1.to_owned(),
1349 position: Position::new(CId::new(0), 0),
1350 item: Either::Item('x')
1351 },],
1352 );
1353 }
1354
1355 #[test]
1356 fn test_load_empty_linked_chunk() {
1357 let room_id = room_id!("!r0:matrix.org");
1358 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1359
1360 let relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1362 let result = relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap();
1363 assert!(result.is_empty());
1364 }
1365
1366 #[test]
1367 fn test_load_all_chunks_with_empty_items() {
1368 let room_id = room_id!("!r0:matrix.org");
1369 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1370
1371 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1372
1373 relational_linked_chunk
1375 .apply_updates(
1376 linked_chunk_id.as_ref(),
1377 vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }],
1378 )
1379 .unwrap();
1380
1381 let lc = from_all_chunks::<3, _, _>(
1383 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1384 )
1385 .expect("building succeeds")
1386 .expect("this leads to a non-empty linked chunk");
1387
1388 assert_items_eq!(lc, []);
1389 }
1390
1391 #[test]
1392 fn test_rebuild_linked_chunk() {
1393 let room_id = room_id!("!r0:matrix.org");
1394 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1395
1396 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1397
1398 relational_linked_chunk
1399 .apply_updates(
1400 linked_chunk_id.as_ref(),
1401 vec![
1402 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1404 Update::PushItems {
1406 at: Position::new(CId::new(0), 0),
1407 items: vec!['a', 'b', 'c'],
1408 },
1409 Update::NewGapChunk {
1411 previous: Some(CId::new(0)),
1412 new: CId::new(1),
1413 next: None,
1414 gap: 'g',
1415 },
1416 Update::NewItemsChunk {
1418 previous: Some(CId::new(1)),
1419 new: CId::new(2),
1420 next: None,
1421 },
1422 Update::PushItems {
1424 at: Position::new(CId::new(2), 0),
1425 items: vec!['d', 'e', 'f'],
1426 },
1427 ],
1428 )
1429 .unwrap();
1430
1431 let lc = from_all_chunks::<3, _, _>(
1432 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1433 )
1434 .expect("building succeeds")
1435 .expect("this leads to a non-empty linked chunk");
1436
1437 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']);
1439 }
1440
1441 #[test]
1442 fn test_replace_item() {
1443 let room_id = room_id!("!r0:matrix.org");
1444 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1445
1446 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1447
1448 relational_linked_chunk
1449 .apply_updates(
1450 linked_chunk_id.as_ref(),
1451 vec![
1452 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1455 Update::PushItems {
1457 at: Position::new(CId::new(0), 0),
1458 items: vec!['a', 'b', 'c'],
1459 },
1460 Update::ReplaceItem { at: Position::new(CId::new(0), 1), item: 'B' },
1462 ],
1463 )
1464 .unwrap();
1465
1466 assert_eq!(
1468 relational_linked_chunk.chunks,
1469 &[ChunkRow {
1470 linked_chunk_id: linked_chunk_id.clone(),
1471 previous_chunk: None,
1472 chunk: CId::new(0),
1473 next_chunk: None,
1474 },],
1475 );
1476
1477 assert_eq!(
1479 relational_linked_chunk.items_chunks,
1480 &[
1481 ItemRow {
1482 linked_chunk_id: linked_chunk_id.clone(),
1483 position: Position::new(CId::new(0), 0),
1484 item: Either::Item('a')
1485 },
1486 ItemRow {
1487 linked_chunk_id: linked_chunk_id.clone(),
1488 position: Position::new(CId::new(0), 1),
1489 item: Either::Item('B')
1490 },
1491 ItemRow {
1492 linked_chunk_id,
1493 position: Position::new(CId::new(0), 2),
1494 item: Either::Item('c')
1495 },
1496 ],
1497 );
1498 }
1499
1500 #[test]
1501 fn test_unordered_events() {
1502 let room_id = room_id!("!r0:matrix.org");
1503 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1504
1505 let other_room_id = room_id!("!r1:matrix.org");
1506 let other_linked_chunk_id = OwnedLinkedChunkId::Room(other_room_id.to_owned());
1507
1508 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1509
1510 relational_linked_chunk
1511 .apply_updates(
1512 linked_chunk_id.as_ref(),
1513 vec![
1514 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1515 Update::PushItems {
1516 at: Position::new(CId::new(0), 0),
1517 items: vec!['a', 'b', 'c'],
1518 },
1519 Update::NewItemsChunk {
1520 previous: Some(CId::new(0)),
1521 new: CId::new(1),
1522 next: None,
1523 },
1524 Update::PushItems {
1525 at: Position::new(CId::new(1), 0),
1526 items: vec!['d', 'e', 'f'],
1527 },
1528 ],
1529 )
1530 .unwrap();
1531
1532 relational_linked_chunk
1533 .apply_updates(
1534 other_linked_chunk_id.as_ref(),
1535 vec![
1536 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1537 Update::PushItems {
1538 at: Position::new(CId::new(0), 0),
1539 items: vec!['x', 'y', 'z'],
1540 },
1541 ],
1542 )
1543 .unwrap();
1544
1545 let events = BTreeMap::from_iter(
1546 relational_linked_chunk.unordered_linked_chunk_items(&linked_chunk_id),
1547 );
1548
1549 assert_eq!(events.len(), 6);
1550 assert_eq!(*events.get(&'a').unwrap(), Position::new(CId::new(0), 0));
1551 assert_eq!(*events.get(&'b').unwrap(), Position::new(CId::new(0), 1));
1552 assert_eq!(*events.get(&'c').unwrap(), Position::new(CId::new(0), 2));
1553 assert_eq!(*events.get(&'d').unwrap(), Position::new(CId::new(1), 0));
1554 assert_eq!(*events.get(&'e').unwrap(), Position::new(CId::new(1), 1));
1555 assert_eq!(*events.get(&'f').unwrap(), Position::new(CId::new(1), 2));
1556 }
1557
1558 #[test]
1559 fn test_load_last_chunk() {
1560 let room_id = room_id!("!r0:matrix.org");
1561 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1562
1563 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1564
1565 {
1567 let (last_chunk, chunk_identifier_generator) =
1568 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1569
1570 assert!(last_chunk.is_none());
1571 assert_eq!(chunk_identifier_generator.current(), 0);
1572 }
1573
1574 {
1576 relational_linked_chunk
1577 .apply_updates(
1578 linked_chunk_id.as_ref(),
1579 vec![
1580 Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1581 Update::PushItems {
1582 at: Position::new(CId::new(42), 0),
1583 items: vec!['a', 'b'],
1584 },
1585 ],
1586 )
1587 .unwrap();
1588
1589 let (last_chunk, chunk_identifier_generator) =
1590 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1591
1592 assert_matches!(last_chunk, Some(last_chunk) => {
1593 assert_eq!(last_chunk.identifier, 42);
1594 assert!(last_chunk.previous.is_none());
1595 assert!(last_chunk.next.is_none());
1596 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1597 assert_eq!(items.len(), 2);
1598 assert_eq!(items, &['a', 'b']);
1599 });
1600 });
1601 assert_eq!(chunk_identifier_generator.current(), 42);
1602 }
1603
1604 {
1606 relational_linked_chunk
1607 .apply_updates(
1608 linked_chunk_id.as_ref(),
1609 vec![
1610 Update::NewItemsChunk {
1611 previous: Some(CId::new(42)),
1612 new: CId::new(7),
1613 next: None,
1614 },
1615 Update::PushItems {
1616 at: Position::new(CId::new(7), 0),
1617 items: vec!['c', 'd', 'e'],
1618 },
1619 ],
1620 )
1621 .unwrap();
1622
1623 let (last_chunk, chunk_identifier_generator) =
1624 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1625
1626 assert_matches!(last_chunk, Some(last_chunk) => {
1627 assert_eq!(last_chunk.identifier, 7);
1628 assert_matches!(last_chunk.previous, Some(previous) => {
1629 assert_eq!(previous, 42);
1630 });
1631 assert!(last_chunk.next.is_none());
1632 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1633 assert_eq!(items.len(), 3);
1634 assert_eq!(items, &['c', 'd', 'e']);
1635 });
1636 });
1637 assert_eq!(chunk_identifier_generator.current(), 42);
1638 }
1639 }
1640
1641 #[test]
1642 fn test_load_last_chunk_with_a_cycle() {
1643 let room_id = room_id!("!r0:matrix.org");
1644 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1645 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1646
1647 relational_linked_chunk
1648 .apply_updates(
1649 linked_chunk_id.as_ref(),
1650 vec![
1651 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1652 Update::NewItemsChunk {
1653 previous: Some(CId::new(0)),
1657 new: CId::new(1),
1658 next: Some(CId::new(0)),
1659 },
1660 ],
1661 )
1662 .unwrap();
1663
1664 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap_err();
1665 }
1666
1667 #[test]
1668 fn test_load_previous_chunk() {
1669 let room_id = room_id!("!r0:matrix.org");
1670 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1671 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1672
1673 {
1676 let previous_chunk = relational_linked_chunk
1677 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(153))
1678 .unwrap();
1679
1680 assert!(previous_chunk.is_none());
1681 }
1682
1683 {
1686 relational_linked_chunk
1687 .apply_updates(
1688 linked_chunk_id.as_ref(),
1689 vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
1690 )
1691 .unwrap();
1692
1693 let previous_chunk = relational_linked_chunk
1694 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1695 .unwrap();
1696
1697 assert!(previous_chunk.is_none());
1698 }
1699
1700 {
1702 relational_linked_chunk
1703 .apply_updates(
1704 linked_chunk_id.as_ref(),
1705 vec![
1706 Update::NewItemsChunk {
1708 previous: None,
1709 new: CId::new(7),
1710 next: Some(CId::new(42)),
1711 },
1712 Update::PushItems {
1713 at: Position::new(CId::new(7), 0),
1714 items: vec!['a', 'b', 'c'],
1715 },
1716 ],
1717 )
1718 .unwrap();
1719
1720 let previous_chunk = relational_linked_chunk
1721 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1722 .unwrap();
1723
1724 assert_matches!(previous_chunk, Some(previous_chunk) => {
1725 assert_eq!(previous_chunk.identifier, 7);
1726 assert!(previous_chunk.previous.is_none());
1727 assert_matches!(previous_chunk.next, Some(next) => {
1728 assert_eq!(next, 42);
1729 });
1730 assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
1731 assert_eq!(items.len(), 3);
1732 assert_eq!(items, &['a', 'b', 'c']);
1733 });
1734 });
1735 }
1736 }
1737}