1use std::{
19 collections::{BTreeMap, HashMap, HashSet},
20 hash::Hash,
21 ops::Not,
22};
23
24use ruma::{OwnedEventId, OwnedRoomId, RoomId};
25use thiserror::Error;
26
27use super::{ChunkContent, ChunkIdentifierGenerator, RawChunk};
28use crate::{
29 deserialized_responses::TimelineEvent,
30 linked_chunk::{
31 ChunkIdentifier, ChunkMetadata, LinkedChunkId, OwnedLinkedChunkId, Position, Update,
32 },
33};
34
35#[derive(Debug, PartialEq)]
37struct ChunkRow {
38 linked_chunk_id: OwnedLinkedChunkId,
39 previous_chunk: Option<ChunkIdentifier>,
40 chunk: ChunkIdentifier,
41 next_chunk: Option<ChunkIdentifier>,
42}
43
44#[derive(Debug, PartialEq)]
46struct ItemRow<ItemId, Gap> {
47 linked_chunk_id: OwnedLinkedChunkId,
48 position: Position,
49 item: Either<ItemId, Gap>,
50}
51
52#[derive(Debug, PartialEq)]
54enum Either<Item, Gap> {
55 Item(Item),
57
58 Gap(Gap),
60}
61
62#[derive(Debug)]
81pub struct RelationalLinkedChunk<ItemId, Item, Gap> {
82 chunks: Vec<ChunkRow>,
84
85 items_chunks: Vec<ItemRow<ItemId, Gap>>,
87
88 items_positions: HashSet<(OwnedLinkedChunkId, Position)>,
90
91 items: HashMap<OwnedLinkedChunkId, BTreeMap<ItemId, (Item, Option<Position>)>>,
93}
94
95#[derive(Debug, Error)]
98pub enum RelationalLinkedChunkError {
99 #[error("invalid chunk identifier: `{identifier:?}`")]
101 InvalidChunkIdentifier {
102 identifier: ChunkIdentifier,
104 },
105 #[error("item already in linked chunk")]
108 ItemAlreadyInLinkedChunk,
109 #[error("position already occupied")]
111 PositionAlreadyOccupied,
112}
113
114pub trait IndexableItem {
117 type ItemId: Hash + PartialEq + Eq + Clone;
118
119 fn id(&self) -> Self::ItemId;
121}
122
123impl IndexableItem for TimelineEvent {
124 type ItemId = OwnedEventId;
125
126 fn id(&self) -> Self::ItemId {
127 self.event_id()
128 .expect("all events saved into a relational linked chunk must have a valid event id")
129 .to_owned()
130 }
131}
132
133impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
134where
135 Item: IndexableItem<ItemId = ItemId> + Clone,
136 ItemId: Hash + PartialEq + Eq + Clone + Ord,
137{
138 pub fn new() -> Self {
140 Self {
141 chunks: Vec::new(),
142 items_chunks: Vec::new(),
143 items_positions: HashSet::new(),
144 items: HashMap::new(),
145 }
146 }
147
148 pub fn clear_room(&mut self, room_id: &RoomId) {
151 self.chunks.retain(|ChunkRow { linked_chunk_id, .. }| linked_chunk_id.room_id() != room_id);
152 self.items_chunks
153 .retain(|ItemRow { linked_chunk_id, .. }| linked_chunk_id.room_id() != room_id);
154 self.items_positions.retain(|(linked_chunk_id, _)| linked_chunk_id.room_id() != room_id);
155 self.items.retain(|key, _| key.room_id() != room_id);
156 }
157
158 pub fn clear(&mut self) {
160 self.chunks.clear();
161 self.items_chunks.clear();
162 self.items_positions.clear();
163 self.items.clear();
164 }
165
166 pub fn apply_updates(
169 &mut self,
170 linked_chunk_id: LinkedChunkId<'_>,
171 updates: Vec<Update<Item, Gap>>,
172 ) -> Result<(), RelationalLinkedChunkError> {
173 for update in updates {
174 match update {
175 Update::NewItemsChunk { previous, new, next } => {
176 Self::insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next)?;
177 }
178
179 Update::NewGapChunk { previous, new, next, gap } => {
180 Self::insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next)?;
181 self.items_chunks.push(ItemRow {
182 linked_chunk_id: linked_chunk_id.to_owned(),
183 position: Position::new(new, 0),
184 item: Either::Gap(gap),
185 });
186 }
187
188 Update::RemoveChunk(chunk_identifier) => {
189 Self::remove_chunk(&mut self.chunks, linked_chunk_id, chunk_identifier);
190
191 let indices_to_remove = self
192 .items_chunks
193 .iter()
194 .enumerate()
195 .filter_map(
196 |(
197 nth,
198 ItemRow {
199 linked_chunk_id: linked_chunk_id_candidate,
200 position,
201 ..
202 },
203 )| {
204 (linked_chunk_id == linked_chunk_id_candidate
205 && position.chunk_identifier() == chunk_identifier)
206 .then_some(nth)
207 },
208 )
209 .collect::<Vec<_>>();
210
211 for index_to_remove in indices_to_remove.into_iter().rev() {
212 self.items_chunks.remove(index_to_remove);
213 }
214 }
215
216 Update::PushItems { mut at, items } => {
217 for item in items {
218 let item_id = item.id();
219 let linked_chunk_items =
220 self.items.entry(linked_chunk_id.to_owned()).or_default();
221
222 if let Some((_, position)) = linked_chunk_items.get(&item_id)
231 && position.is_some()
232 {
233 return Err(RelationalLinkedChunkError::ItemAlreadyInLinkedChunk);
234 }
235
236 if self.items_positions.insert((linked_chunk_id.to_owned(), at)).not() {
239 return Err(RelationalLinkedChunkError::PositionAlreadyOccupied);
240 }
241
242 linked_chunk_items.insert(item_id.clone(), (item.clone(), Some(at)));
243 self.items_chunks.push(ItemRow {
244 linked_chunk_id: linked_chunk_id.to_owned(),
245 position: at,
246 item: Either::Item(item_id),
247 });
248
249 for items in &mut self.items.values_mut() {
252 items.entry(item.id()).and_modify(|e| e.0 = item.clone());
253 }
254
255 at.increment_index();
256 }
257 }
258
259 Update::ReplaceItem { at, item } => {
260 let existing = self
261 .items_chunks
262 .iter_mut()
263 .find(|item| item.position == at)
264 .expect("trying to replace at an unknown position");
265 assert!(
266 matches!(existing.item, Either::Item(..)),
267 "trying to replace a gap with an item"
268 );
269 let item_id = item.id();
270 self.items
271 .entry(linked_chunk_id.to_owned())
272 .or_default()
273 .insert(item_id.clone(), (item.clone(), Some(at)));
274 existing.item = Either::Item(item_id.clone());
275
276 for items in &mut self.items.values_mut() {
279 items.entry(item_id.clone()).and_modify(|e| e.0 = item.clone());
280 }
281 }
282
283 Update::RemoveItem { at } => {
284 let mut entry_to_remove = None;
285 let mut position_to_remove = Option::<Position>::None;
286
287 for (
288 nth,
289 ItemRow { linked_chunk_id: linked_chunk_id_candidate, position, .. },
290 ) in self.items_chunks.iter_mut().enumerate()
291 {
292 if linked_chunk_id != &*linked_chunk_id_candidate {
294 continue;
295 }
296
297 if position.chunk_identifier() == at.chunk_identifier()
299 && position_to_remove
300 .is_none_or(|inner| inner.index() < position.index())
301 {
302 position_to_remove.replace(*position);
303 }
304
305 if *position == at {
307 debug_assert!(entry_to_remove.is_none(), "Found the same entry twice");
308
309 entry_to_remove = Some(nth);
310 }
311
312 if position.chunk_identifier() == at.chunk_identifier()
315 && position.index() > at.index()
316 {
317 position.decrement_index();
318 }
319 }
320
321 self.items_chunks.remove(entry_to_remove.expect("Remove an unknown item"));
322 self.items_positions.remove(&(
323 linked_chunk_id.to_owned(),
324 position_to_remove.expect("Remove an unknown item"),
325 ));
326
327 self.items.entry(linked_chunk_id.to_owned()).and_modify(|items| {
329 for (_, opt) in items.values_mut() {
330 if let Some(position) = opt
331 && position.chunk_identifier() == at.chunk_identifier()
332 {
333 if position.index() == at.index() {
334 opt.take();
335 } else if position.index() > at.index() {
336 position.decrement_index();
337 }
338 }
339 }
340 });
341 }
342
343 Update::DetachLastItems { at } => {
344 let indices_to_remove = self
345 .items_chunks
346 .iter()
347 .enumerate()
348 .filter_map(
349 |(
350 nth,
351 ItemRow {
352 linked_chunk_id: linked_chunk_id_candidate,
353 position,
354 ..
355 },
356 )| {
357 (linked_chunk_id == linked_chunk_id_candidate
358 && position.chunk_identifier() == at.chunk_identifier()
359 && position.index() >= at.index())
360 .then_some((nth, *position))
361 },
362 )
363 .collect::<Vec<_>>();
364
365 for (index_to_remove, position) in indices_to_remove.into_iter().rev() {
366 self.items_positions.remove(&(linked_chunk_id.to_owned(), position));
367 self.items_chunks.remove(index_to_remove);
368 }
369
370 self.items.entry(linked_chunk_id.to_owned()).and_modify(|items| {
371 for (_, pos) in items.values_mut() {
372 pos.take_if(|pos| {
373 pos.chunk_identifier() == at.chunk_identifier()
374 && pos.index() >= at.index()
375 });
376 }
377 });
378 }
379
380 Update::StartReattachItems | Update::EndReattachItems => { }
381
382 Update::Clear => {
383 self.chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id);
384 self.items_chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id);
385 self.items_positions.retain(|(id, _)| id.as_ref() != linked_chunk_id);
386 self.items.entry(linked_chunk_id.to_owned()).and_modify(|items| {
388 for (_, pos) in items.values_mut() {
389 pos.take();
390 }
391 });
392 }
393 }
394 }
395 Ok(())
396 }
397
398 fn insert_chunk(
399 chunks: &mut Vec<ChunkRow>,
400 linked_chunk_id: LinkedChunkId<'_>,
401 previous: Option<ChunkIdentifier>,
402 new: ChunkIdentifier,
403 next: Option<ChunkIdentifier>,
404 ) -> Result<(), RelationalLinkedChunkError> {
405 if let Some(previous) = previous {
407 let entry_for_previous_chunk = chunks
408 .iter_mut()
409 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
410 linked_chunk_id == linked_chunk_id_candidate && *chunk == previous
411 })
412 .ok_or(RelationalLinkedChunkError::InvalidChunkIdentifier {
413 identifier: previous,
414 })?;
415
416 entry_for_previous_chunk.next_chunk = Some(new);
418 }
419
420 if let Some(next) = next {
422 let entry_for_next_chunk = chunks
423 .iter_mut()
424 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
425 linked_chunk_id == linked_chunk_id_candidate && *chunk == next
426 })
427 .ok_or(RelationalLinkedChunkError::InvalidChunkIdentifier { identifier: next })?;
428
429 entry_for_next_chunk.previous_chunk = Some(new);
431 }
432
433 chunks.push(ChunkRow {
435 linked_chunk_id: linked_chunk_id.to_owned(),
436 previous_chunk: previous,
437 chunk: new,
438 next_chunk: next,
439 });
440
441 Ok(())
442 }
443
444 fn remove_chunk(
445 chunks: &mut Vec<ChunkRow>,
446 linked_chunk_id: LinkedChunkId<'_>,
447 chunk_to_remove: ChunkIdentifier,
448 ) {
449 let entry_nth_to_remove = chunks
450 .iter()
451 .enumerate()
452 .find_map(
453 |(nth, ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. })| {
454 (linked_chunk_id == linked_chunk_id_candidate && *chunk == chunk_to_remove)
455 .then_some(nth)
456 },
457 )
458 .expect("Remove an unknown chunk");
459
460 let ChunkRow { linked_chunk_id, previous_chunk: previous, next_chunk: next, .. } =
461 chunks.remove(entry_nth_to_remove);
462
463 if let Some(previous) = previous {
465 let entry_for_previous_chunk = chunks
466 .iter_mut()
467 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
468 &linked_chunk_id == linked_chunk_id_candidate && *chunk == previous
469 })
470 .expect("Previous chunk should be present");
471
472 entry_for_previous_chunk.next_chunk = next;
474 }
475
476 if let Some(next) = next {
478 let entry_for_next_chunk = chunks
479 .iter_mut()
480 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
481 &linked_chunk_id == linked_chunk_id_candidate && *chunk == next
482 })
483 .expect("Next chunk should be present");
484
485 entry_for_next_chunk.previous_chunk = previous;
487 }
488 }
489
490 pub fn unordered_linked_chunk_items<'a>(
493 &'a self,
494 target: &OwnedLinkedChunkId,
495 ) -> impl Iterator<Item = (&'a Item, Position)> + use<'a, ItemId, Item, Gap> {
496 self.items.get(target).into_iter().flat_map(|items| {
497 items.values().filter_map(|(item, pos)| pos.map(|pos| (item, pos)))
499 })
500 }
501
502 pub fn items<'a>(
511 &'a self,
512 room_id: &'a RoomId,
513 ) -> impl Iterator<Item = (&'a OwnedLinkedChunkId, (&'a Item, Option<Position>))> {
514 self.items
515 .iter()
516 .filter(move |(linked_chunk_id, _)| linked_chunk_id.room_id() == room_id)
517 .flat_map(|(linked_chunk_id, items)| {
518 items.values().map(move |(item, pos)| (linked_chunk_id, (item, *pos)))
519 })
520 }
521}
522
523impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
524where
525 Item: IndexableItem<ItemId = ItemId> + Clone,
526 ItemId: Hash + PartialEq + Eq + Clone + Ord,
527{
528 pub fn save_item(&mut self, room_id: OwnedRoomId, item: Item) {
530 let id = item.id();
531
532 let mut linked_chunk_ids = self
533 .items
534 .keys()
535 .filter(|linked_chunk_id| linked_chunk_id.room_id() == room_id)
536 .cloned()
537 .collect::<HashSet<_>>();
538 linked_chunk_ids.insert(OwnedLinkedChunkId::Room(room_id));
539
540 for linked_chunk_id in linked_chunk_ids {
541 let map = self.items.entry(linked_chunk_id).or_default();
542 if let Some(prev_value) = map.get_mut(&id) {
543 prev_value.0 = item.clone();
545 } else {
546 map.insert(id.clone(), (item.clone(), None));
547 }
548 }
549 }
550}
551
552impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
553where
554 Gap: Clone,
555 Item: Clone,
556 ItemId: Hash + PartialEq + Eq + Ord,
557{
558 #[doc(hidden)]
563 pub fn load_all_chunks(
564 &self,
565 linked_chunk_id: LinkedChunkId<'_>,
566 ) -> Result<Vec<RawChunk<Item, Gap>>, String> {
567 self.chunks
568 .iter()
569 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
570 .map(|chunk_row| load_raw_chunk(self, chunk_row, linked_chunk_id))
571 .collect::<Result<Vec<_>, String>>()
572 }
573
574 #[doc(hidden)]
579 pub fn load_all_chunks_metadata(
580 &self,
581 linked_chunk_id: LinkedChunkId<'_>,
582 ) -> Result<Vec<ChunkMetadata>, String> {
583 self.chunks
584 .iter()
585 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
586 .map(|chunk_row| load_raw_chunk_metadata(self, chunk_row, linked_chunk_id))
587 .collect::<Result<Vec<_>, String>>()
588 }
589
590 pub fn load_last_chunk(
591 &self,
592 linked_chunk_id: LinkedChunkId<'_>,
593 ) -> Result<(Option<RawChunk<Item, Gap>>, ChunkIdentifierGenerator), String> {
594 let chunk_identifier_generator = match self
597 .chunks
598 .iter()
599 .filter_map(|chunk_row| {
600 (chunk_row.linked_chunk_id == linked_chunk_id).then_some(chunk_row.chunk)
601 })
602 .max()
603 {
604 Some(last_chunk_identifier) => {
605 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier)
606 }
607 None => ChunkIdentifierGenerator::new_from_scratch(),
608 };
609
610 let mut number_of_chunks = 0;
612 let mut chunk_row = None;
613
614 for chunk_row_candidate in &self.chunks {
615 if chunk_row_candidate.linked_chunk_id == linked_chunk_id {
616 number_of_chunks += 1;
617
618 if chunk_row_candidate.next_chunk.is_none() {
619 chunk_row = Some(chunk_row_candidate);
620
621 break;
622 }
623 }
624 }
625
626 let chunk_row = match chunk_row {
627 Some(chunk_row) => chunk_row,
629
630 None if number_of_chunks == 0 => {
633 return Ok((None, chunk_identifier_generator));
634 }
635
636 None => {
642 return Err(
643 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
644 .to_owned(),
645 );
646 }
647 };
648
649 load_raw_chunk(self, chunk_row, linked_chunk_id)
651 .map(|raw_chunk| (Some(raw_chunk), chunk_identifier_generator))
652 }
653
654 pub fn load_previous_chunk(
655 &self,
656 linked_chunk_id: LinkedChunkId<'_>,
657 before_chunk_identifier: ChunkIdentifier,
658 ) -> Result<Option<RawChunk<Item, Gap>>, String> {
659 let Some(chunk_row) = self.chunks.iter().find(|chunk_row| {
662 chunk_row.linked_chunk_id == linked_chunk_id
663 && chunk_row.next_chunk == Some(before_chunk_identifier)
664 }) else {
665 return Ok(None);
667 };
668
669 load_raw_chunk(self, chunk_row, linked_chunk_id).map(Some)
671 }
672}
673
674impl<ItemId, Item, Gap> Default for RelationalLinkedChunk<ItemId, Item, Gap>
675where
676 Item: IndexableItem<ItemId = ItemId> + Clone,
677 ItemId: Hash + PartialEq + Eq + Clone + Ord,
678{
679 fn default() -> Self {
680 Self::new()
681 }
682}
683
684fn load_raw_chunk<ItemId, Item, Gap>(
689 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
690 chunk_row: &ChunkRow,
691 linked_chunk_id: LinkedChunkId<'_>,
692) -> Result<RawChunk<Item, Gap>, String>
693where
694 Item: Clone,
695 Gap: Clone,
696 ItemId: Hash + PartialEq + Eq + Ord,
697{
698 let mut items = relational_linked_chunk
700 .items_chunks
701 .iter()
702 .filter(|item_row| {
703 item_row.linked_chunk_id == linked_chunk_id
704 && item_row.position.chunk_identifier() == chunk_row.chunk
705 })
706 .peekable();
707
708 let Some(first_item) = items.peek() else {
709 return Ok(RawChunk {
711 content: ChunkContent::Items(Vec::new()),
712 previous: chunk_row.previous_chunk,
713 identifier: chunk_row.chunk,
714 next: chunk_row.next_chunk,
715 });
716 };
717
718 Ok(match first_item.item {
719 Either::Item(_) => {
721 let mut collected_items = Vec::new();
723
724 for item_row in items {
725 match &item_row.item {
726 Either::Item(item_id) => {
727 collected_items.push((item_id, item_row.position.index()))
728 }
729
730 Either::Gap(_) => {
731 return Err(format!(
732 "unexpected gap in items chunk {}",
733 chunk_row.chunk.index()
734 ));
735 }
736 }
737 }
738
739 collected_items.sort_unstable_by_key(|(_item, index)| *index);
741
742 RawChunk {
743 content: ChunkContent::Items(
744 collected_items
745 .into_iter()
746 .filter_map(|(item_id, _index)| {
747 Some(
748 relational_linked_chunk
749 .items
750 .get(&linked_chunk_id.to_owned())?
751 .get(item_id)?
752 .0
753 .clone(),
754 )
755 })
756 .collect(),
757 ),
758 previous: chunk_row.previous_chunk,
759 identifier: chunk_row.chunk,
760 next: chunk_row.next_chunk,
761 }
762 }
763
764 Either::Gap(ref gap) => {
765 assert!(items.next().is_some(), "we just peeked the gap");
766
767 if items.next().is_some() {
769 return Err(format!(
770 "there shouldn't be more than one item row attached in gap chunk {}",
771 chunk_row.chunk.index()
772 ));
773 }
774
775 RawChunk {
776 content: ChunkContent::Gap(gap.clone()),
777 previous: chunk_row.previous_chunk,
778 identifier: chunk_row.chunk,
779 next: chunk_row.next_chunk,
780 }
781 }
782 })
783}
784
785fn load_raw_chunk_metadata<ItemId, Item, Gap>(
790 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
791 chunk_row: &ChunkRow,
792 linked_chunk_id: LinkedChunkId<'_>,
793) -> Result<ChunkMetadata, String>
794where
795 Item: Clone,
796 Gap: Clone,
797 ItemId: Hash + PartialEq + Eq,
798{
799 let mut items = relational_linked_chunk
801 .items_chunks
802 .iter()
803 .filter(|item_row| {
804 item_row.linked_chunk_id == linked_chunk_id
805 && item_row.position.chunk_identifier() == chunk_row.chunk
806 })
807 .peekable();
808
809 let Some(first_item) = items.peek() else {
810 return Ok(ChunkMetadata {
812 num_items: 0,
813 previous: chunk_row.previous_chunk,
814 identifier: chunk_row.chunk,
815 next: chunk_row.next_chunk,
816 });
817 };
818
819 Ok(match first_item.item {
820 Either::Item(_) => {
822 let mut num_items = 0;
827 for item in items {
828 match &item.item {
829 Either::Item(_) => num_items += 1,
830 Either::Gap(_) => {
831 return Err(format!(
832 "unexpected gap in items chunk {}",
833 chunk_row.chunk.index()
834 ));
835 }
836 }
837 }
838
839 ChunkMetadata {
840 num_items,
841 previous: chunk_row.previous_chunk,
842 identifier: chunk_row.chunk,
843 next: chunk_row.next_chunk,
844 }
845 }
846
847 Either::Gap(..) => {
848 assert!(items.next().is_some(), "we just peeked the gap");
849
850 if items.next().is_some() {
852 return Err(format!(
853 "there shouldn't be more than one item row attached in gap chunk {}",
854 chunk_row.chunk.index()
855 ));
856 }
857
858 ChunkMetadata {
859 num_items: 0,
861 previous: chunk_row.previous_chunk,
862 identifier: chunk_row.chunk,
863 next: chunk_row.next_chunk,
864 }
865 }
866 })
867}
868
869#[cfg(test)]
870mod tests {
871 use std::collections::BTreeMap;
872
873 use assert_matches::assert_matches;
874 use ruma::room_id;
875
876 use super::{super::lazy_loader::from_all_chunks, ChunkIdentifier as CId, *};
877
878 impl IndexableItem for char {
879 type ItemId = char;
880
881 fn id(&self) -> Self::ItemId {
882 *self
883 }
884 }
885
886 #[test]
887 fn test_new_items_chunk() {
888 let room_id = room_id!("!r0:matrix.org");
889 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
890
891 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
892
893 relational_linked_chunk
894 .apply_updates(
895 linked_chunk_id.as_ref(),
896 vec![
897 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
899 Update::NewItemsChunk {
901 previous: Some(CId::new(0)),
902 new: CId::new(1),
903 next: None,
904 },
905 Update::NewItemsChunk {
907 previous: None,
908 new: CId::new(2),
909 next: Some(CId::new(0)),
910 },
911 Update::NewItemsChunk {
913 previous: Some(CId::new(2)),
914 new: CId::new(3),
915 next: Some(CId::new(0)),
916 },
917 ],
918 )
919 .unwrap();
920
921 assert_eq!(
923 relational_linked_chunk.chunks,
924 &[
925 ChunkRow {
926 linked_chunk_id: linked_chunk_id.clone(),
927 previous_chunk: Some(CId::new(3)),
928 chunk: CId::new(0),
929 next_chunk: Some(CId::new(1))
930 },
931 ChunkRow {
932 linked_chunk_id: linked_chunk_id.clone(),
933 previous_chunk: Some(CId::new(0)),
934 chunk: CId::new(1),
935 next_chunk: None
936 },
937 ChunkRow {
938 linked_chunk_id: linked_chunk_id.clone(),
939 previous_chunk: None,
940 chunk: CId::new(2),
941 next_chunk: Some(CId::new(3))
942 },
943 ChunkRow {
944 linked_chunk_id,
945 previous_chunk: Some(CId::new(2)),
946 chunk: CId::new(3),
947 next_chunk: Some(CId::new(0))
948 },
949 ],
950 );
951
952 assert!(relational_linked_chunk.items_chunks.is_empty());
954 }
955
956 #[test]
957 fn test_new_gap_chunk() {
958 let room_id = room_id!("!r0:matrix.org");
959 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
960
961 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
962
963 relational_linked_chunk
964 .apply_updates(
965 linked_chunk_id.as_ref(),
966 vec![
967 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
969 Update::NewGapChunk {
971 previous: Some(CId::new(0)),
972 new: CId::new(1),
973 next: None,
974 gap: (),
975 },
976 Update::NewItemsChunk {
978 previous: Some(CId::new(1)),
979 new: CId::new(2),
980 next: None,
981 },
982 ],
983 )
984 .unwrap();
985
986 assert_eq!(
988 relational_linked_chunk.chunks,
989 &[
990 ChunkRow {
991 linked_chunk_id: linked_chunk_id.clone(),
992 previous_chunk: None,
993 chunk: CId::new(0),
994 next_chunk: Some(CId::new(1))
995 },
996 ChunkRow {
997 linked_chunk_id: linked_chunk_id.clone(),
998 previous_chunk: Some(CId::new(0)),
999 chunk: CId::new(1),
1000 next_chunk: Some(CId::new(2))
1001 },
1002 ChunkRow {
1003 linked_chunk_id: linked_chunk_id.clone(),
1004 previous_chunk: Some(CId::new(1)),
1005 chunk: CId::new(2),
1006 next_chunk: None
1007 },
1008 ],
1009 );
1010 assert_eq!(
1012 relational_linked_chunk.items_chunks,
1013 &[ItemRow {
1014 linked_chunk_id,
1015 position: Position::new(CId::new(1), 0),
1016 item: Either::Gap(())
1017 }],
1018 );
1019 }
1020
1021 #[test]
1022 fn test_remove_chunk() {
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
1029 .apply_updates(
1030 linked_chunk_id.as_ref(),
1031 vec![
1032 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1034 Update::NewGapChunk {
1036 previous: Some(CId::new(0)),
1037 new: CId::new(1),
1038 next: None,
1039 gap: (),
1040 },
1041 Update::NewItemsChunk {
1043 previous: Some(CId::new(1)),
1044 new: CId::new(2),
1045 next: None,
1046 },
1047 Update::RemoveChunk(CId::new(1)),
1049 ],
1050 )
1051 .unwrap();
1052
1053 assert_eq!(
1055 relational_linked_chunk.chunks,
1056 &[
1057 ChunkRow {
1058 linked_chunk_id: linked_chunk_id.clone(),
1059 previous_chunk: None,
1060 chunk: CId::new(0),
1061 next_chunk: Some(CId::new(2))
1062 },
1063 ChunkRow {
1064 linked_chunk_id,
1065 previous_chunk: Some(CId::new(0)),
1066 chunk: CId::new(2),
1067 next_chunk: None
1068 },
1069 ],
1070 );
1071
1072 assert!(relational_linked_chunk.items_chunks.is_empty());
1074 }
1075
1076 #[test]
1077 fn test_push_items() {
1078 let room_id = room_id!("!r0:matrix.org");
1079 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1080
1081 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1082
1083 relational_linked_chunk
1084 .apply_updates(
1085 linked_chunk_id.as_ref(),
1086 vec![
1087 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1090 Update::PushItems {
1092 at: Position::new(CId::new(0), 0),
1093 items: vec!['a', 'b', 'c'],
1094 },
1095 Update::NewItemsChunk {
1097 previous: Some(CId::new(0)),
1098 new: CId::new(1),
1099 next: None,
1100 },
1101 Update::PushItems {
1103 at: Position::new(CId::new(1), 0),
1104 items: vec!['x', 'y', 'z'],
1105 },
1106 Update::PushItems { at: Position::new(CId::new(0), 3), items: vec!['d', 'e'] },
1108 ],
1109 )
1110 .unwrap();
1111
1112 assert_eq!(
1114 relational_linked_chunk.chunks,
1115 &[
1116 ChunkRow {
1117 linked_chunk_id: linked_chunk_id.clone(),
1118 previous_chunk: None,
1119 chunk: CId::new(0),
1120 next_chunk: Some(CId::new(1))
1121 },
1122 ChunkRow {
1123 linked_chunk_id: linked_chunk_id.clone(),
1124 previous_chunk: Some(CId::new(0)),
1125 chunk: CId::new(1),
1126 next_chunk: None
1127 },
1128 ],
1129 );
1130 assert_eq!(
1132 relational_linked_chunk.items_chunks,
1133 &[
1134 ItemRow {
1135 linked_chunk_id: linked_chunk_id.clone(),
1136 position: Position::new(CId::new(0), 0),
1137 item: Either::Item('a')
1138 },
1139 ItemRow {
1140 linked_chunk_id: linked_chunk_id.clone(),
1141 position: Position::new(CId::new(0), 1),
1142 item: Either::Item('b')
1143 },
1144 ItemRow {
1145 linked_chunk_id: linked_chunk_id.clone(),
1146 position: Position::new(CId::new(0), 2),
1147 item: Either::Item('c')
1148 },
1149 ItemRow {
1150 linked_chunk_id: linked_chunk_id.clone(),
1151 position: Position::new(CId::new(1), 0),
1152 item: Either::Item('x')
1153 },
1154 ItemRow {
1155 linked_chunk_id: linked_chunk_id.clone(),
1156 position: Position::new(CId::new(1), 1),
1157 item: Either::Item('y')
1158 },
1159 ItemRow {
1160 linked_chunk_id: linked_chunk_id.clone(),
1161 position: Position::new(CId::new(1), 2),
1162 item: Either::Item('z')
1163 },
1164 ItemRow {
1165 linked_chunk_id: linked_chunk_id.clone(),
1166 position: Position::new(CId::new(0), 3),
1167 item: Either::Item('d')
1168 },
1169 ItemRow {
1170 linked_chunk_id: linked_chunk_id.clone(),
1171 position: Position::new(CId::new(0), 4),
1172 item: Either::Item('e')
1173 },
1174 ],
1175 );
1176 }
1177
1178 #[test]
1179 fn test_remove_item() {
1180 let room_id = room_id!("!r0:matrix.org");
1181 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1182
1183 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1184
1185 relational_linked_chunk
1186 .apply_updates(
1187 linked_chunk_id.as_ref(),
1188 vec![
1189 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1192 Update::PushItems {
1194 at: Position::new(CId::new(0), 0),
1195 items: vec!['a', 'b', 'c', 'd', 'e'],
1196 },
1197 Update::RemoveItem { at: Position::new(CId::new(0), 0) },
1199 Update::RemoveItem { at: Position::new(CId::new(0), 2) },
1201 ],
1202 )
1203 .unwrap();
1204
1205 assert_eq!(
1207 relational_linked_chunk.chunks,
1208 &[ChunkRow {
1209 linked_chunk_id: linked_chunk_id.clone(),
1210 previous_chunk: None,
1211 chunk: CId::new(0),
1212 next_chunk: None
1213 }],
1214 );
1215 assert_eq!(
1217 relational_linked_chunk.items_chunks,
1218 &[
1219 ItemRow {
1220 linked_chunk_id: linked_chunk_id.clone(),
1221 position: Position::new(CId::new(0), 0),
1222 item: Either::Item('b')
1223 },
1224 ItemRow {
1225 linked_chunk_id: linked_chunk_id.clone(),
1226 position: Position::new(CId::new(0), 1),
1227 item: Either::Item('c')
1228 },
1229 ItemRow {
1230 linked_chunk_id: linked_chunk_id.clone(),
1231 position: Position::new(CId::new(0), 2),
1232 item: Either::Item('e')
1233 },
1234 ],
1235 );
1236 }
1237
1238 #[test]
1239 fn test_detach_last_items() {
1240 let room_id = room_id!("!r0:matrix.org");
1241 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1242
1243 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1244
1245 relational_linked_chunk
1246 .apply_updates(
1247 linked_chunk_id.as_ref(),
1248 vec![
1249 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1251 Update::NewItemsChunk {
1253 previous: Some(CId::new(0)),
1254 new: CId::new(1),
1255 next: None,
1256 },
1257 Update::PushItems {
1259 at: Position::new(CId::new(0), 0),
1260 items: vec!['a', 'b', 'c', 'd', 'e'],
1261 },
1262 Update::PushItems {
1264 at: Position::new(CId::new(1), 0),
1265 items: vec!['x', 'y', 'z'],
1266 },
1267 Update::DetachLastItems { at: Position::new(CId::new(0), 2) },
1269 ],
1270 )
1271 .unwrap();
1272
1273 assert_eq!(
1275 relational_linked_chunk.chunks,
1276 &[
1277 ChunkRow {
1278 linked_chunk_id: linked_chunk_id.clone(),
1279 previous_chunk: None,
1280 chunk: CId::new(0),
1281 next_chunk: Some(CId::new(1))
1282 },
1283 ChunkRow {
1284 linked_chunk_id: linked_chunk_id.clone(),
1285 previous_chunk: Some(CId::new(0)),
1286 chunk: CId::new(1),
1287 next_chunk: None
1288 },
1289 ],
1290 );
1291 assert_eq!(
1293 relational_linked_chunk.items_chunks,
1294 &[
1295 ItemRow {
1296 linked_chunk_id: linked_chunk_id.clone(),
1297 position: Position::new(CId::new(0), 0),
1298 item: Either::Item('a')
1299 },
1300 ItemRow {
1301 linked_chunk_id: linked_chunk_id.clone(),
1302 position: Position::new(CId::new(0), 1),
1303 item: Either::Item('b')
1304 },
1305 ItemRow {
1306 linked_chunk_id: linked_chunk_id.clone(),
1307 position: Position::new(CId::new(1), 0),
1308 item: Either::Item('x')
1309 },
1310 ItemRow {
1311 linked_chunk_id: linked_chunk_id.clone(),
1312 position: Position::new(CId::new(1), 1),
1313 item: Either::Item('y')
1314 },
1315 ItemRow {
1316 linked_chunk_id: linked_chunk_id.clone(),
1317 position: Position::new(CId::new(1), 2),
1318 item: Either::Item('z')
1319 },
1320 ],
1321 );
1322 }
1323
1324 #[test]
1325 fn test_start_and_end_reattach_items() {
1326 let room_id = room_id!("!r0:matrix.org");
1327 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1328
1329 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1330
1331 relational_linked_chunk
1332 .apply_updates(
1333 linked_chunk_id.as_ref(),
1334 vec![Update::StartReattachItems, Update::EndReattachItems],
1335 )
1336 .unwrap();
1337
1338 assert!(relational_linked_chunk.chunks.is_empty());
1340 assert!(relational_linked_chunk.items_chunks.is_empty());
1341 }
1342
1343 #[test]
1344 fn test_clear() {
1345 let r0 = room_id!("!r0:matrix.org");
1346 let linked_chunk_id0 = OwnedLinkedChunkId::Room(r0.to_owned());
1347
1348 let r1 = room_id!("!r1:matrix.org");
1349 let linked_chunk_id1 = OwnedLinkedChunkId::Room(r1.to_owned());
1350
1351 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1352
1353 relational_linked_chunk
1354 .apply_updates(
1355 linked_chunk_id0.as_ref(),
1356 vec![
1357 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1360 Update::PushItems {
1362 at: Position::new(CId::new(0), 0),
1363 items: vec!['a', 'b', 'c'],
1364 },
1365 ],
1366 )
1367 .unwrap();
1368
1369 relational_linked_chunk
1370 .apply_updates(
1371 linked_chunk_id1.as_ref(),
1372 vec![
1373 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1376 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x'] },
1378 ],
1379 )
1380 .unwrap();
1381
1382 assert_eq!(
1384 relational_linked_chunk.chunks,
1385 &[
1386 ChunkRow {
1387 linked_chunk_id: linked_chunk_id0.to_owned(),
1388 previous_chunk: None,
1389 chunk: CId::new(0),
1390 next_chunk: None,
1391 },
1392 ChunkRow {
1393 linked_chunk_id: linked_chunk_id1.to_owned(),
1394 previous_chunk: None,
1395 chunk: CId::new(0),
1396 next_chunk: None,
1397 }
1398 ],
1399 );
1400
1401 assert_eq!(
1403 relational_linked_chunk.items_chunks,
1404 &[
1405 ItemRow {
1406 linked_chunk_id: linked_chunk_id0.to_owned(),
1407 position: Position::new(CId::new(0), 0),
1408 item: Either::Item('a')
1409 },
1410 ItemRow {
1411 linked_chunk_id: linked_chunk_id0.to_owned(),
1412 position: Position::new(CId::new(0), 1),
1413 item: Either::Item('b')
1414 },
1415 ItemRow {
1416 linked_chunk_id: linked_chunk_id0.to_owned(),
1417 position: Position::new(CId::new(0), 2),
1418 item: Either::Item('c')
1419 },
1420 ItemRow {
1421 linked_chunk_id: linked_chunk_id1.to_owned(),
1422 position: Position::new(CId::new(0), 0),
1423 item: Either::Item('x')
1424 },
1425 ],
1426 );
1427
1428 relational_linked_chunk
1430 .apply_updates(linked_chunk_id0.as_ref(), vec![Update::Clear])
1431 .unwrap();
1432
1433 assert_eq!(
1435 relational_linked_chunk.chunks,
1436 &[ChunkRow {
1437 linked_chunk_id: linked_chunk_id1.to_owned(),
1438 previous_chunk: None,
1439 chunk: CId::new(0),
1440 next_chunk: None,
1441 }],
1442 );
1443
1444 assert_eq!(
1445 relational_linked_chunk.items_chunks,
1446 &[ItemRow {
1447 linked_chunk_id: linked_chunk_id1.to_owned(),
1448 position: Position::new(CId::new(0), 0),
1449 item: Either::Item('x')
1450 },],
1451 );
1452 }
1453
1454 #[test]
1455 fn test_load_empty_linked_chunk() {
1456 let room_id = room_id!("!r0:matrix.org");
1457 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1458
1459 let relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1461 let result = relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap();
1462 assert!(result.is_empty());
1463 }
1464
1465 #[test]
1466 fn test_load_all_chunks_with_empty_items() {
1467 let room_id = room_id!("!r0:matrix.org");
1468 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1469
1470 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1471
1472 relational_linked_chunk
1474 .apply_updates(
1475 linked_chunk_id.as_ref(),
1476 vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }],
1477 )
1478 .unwrap();
1479
1480 let lc = from_all_chunks::<3, _, _>(
1482 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1483 )
1484 .expect("building succeeds")
1485 .expect("this leads to a non-empty linked chunk");
1486
1487 assert_items_eq!(lc, []);
1488 }
1489
1490 #[test]
1491 fn test_rebuild_linked_chunk() {
1492 let room_id = room_id!("!r0:matrix.org");
1493 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1494
1495 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1496
1497 relational_linked_chunk
1498 .apply_updates(
1499 linked_chunk_id.as_ref(),
1500 vec![
1501 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1503 Update::PushItems {
1505 at: Position::new(CId::new(0), 0),
1506 items: vec!['a', 'b', 'c'],
1507 },
1508 Update::NewGapChunk {
1510 previous: Some(CId::new(0)),
1511 new: CId::new(1),
1512 next: None,
1513 gap: 'g',
1514 },
1515 Update::NewItemsChunk {
1517 previous: Some(CId::new(1)),
1518 new: CId::new(2),
1519 next: None,
1520 },
1521 Update::PushItems {
1523 at: Position::new(CId::new(2), 0),
1524 items: vec!['d', 'e', 'f'],
1525 },
1526 ],
1527 )
1528 .unwrap();
1529
1530 let lc = from_all_chunks::<3, _, _>(
1531 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1532 )
1533 .expect("building succeeds")
1534 .expect("this leads to a non-empty linked chunk");
1535
1536 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']);
1538 }
1539
1540 #[test]
1541 fn test_replace_item() {
1542 let room_id = room_id!("!r0:matrix.org");
1543 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1544
1545 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1546
1547 relational_linked_chunk
1548 .apply_updates(
1549 linked_chunk_id.as_ref(),
1550 vec![
1551 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1554 Update::PushItems {
1556 at: Position::new(CId::new(0), 0),
1557 items: vec!['a', 'b', 'c'],
1558 },
1559 Update::ReplaceItem { at: Position::new(CId::new(0), 1), item: 'B' },
1561 ],
1562 )
1563 .unwrap();
1564
1565 assert_eq!(
1567 relational_linked_chunk.chunks,
1568 &[ChunkRow {
1569 linked_chunk_id: linked_chunk_id.clone(),
1570 previous_chunk: None,
1571 chunk: CId::new(0),
1572 next_chunk: None,
1573 },],
1574 );
1575
1576 assert_eq!(
1578 relational_linked_chunk.items_chunks,
1579 &[
1580 ItemRow {
1581 linked_chunk_id: linked_chunk_id.clone(),
1582 position: Position::new(CId::new(0), 0),
1583 item: Either::Item('a')
1584 },
1585 ItemRow {
1586 linked_chunk_id: linked_chunk_id.clone(),
1587 position: Position::new(CId::new(0), 1),
1588 item: Either::Item('B')
1589 },
1590 ItemRow {
1591 linked_chunk_id,
1592 position: Position::new(CId::new(0), 2),
1593 item: Either::Item('c')
1594 },
1595 ],
1596 );
1597 }
1598
1599 #[test]
1600 fn test_unordered_events() {
1601 let room_id = room_id!("!r0:matrix.org");
1602 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1603
1604 let other_room_id = room_id!("!r1:matrix.org");
1605 let other_linked_chunk_id = OwnedLinkedChunkId::Room(other_room_id.to_owned());
1606
1607 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1608
1609 relational_linked_chunk
1610 .apply_updates(
1611 linked_chunk_id.as_ref(),
1612 vec![
1613 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1614 Update::PushItems {
1615 at: Position::new(CId::new(0), 0),
1616 items: vec!['a', 'b', 'c'],
1617 },
1618 Update::NewItemsChunk {
1619 previous: Some(CId::new(0)),
1620 new: CId::new(1),
1621 next: None,
1622 },
1623 Update::PushItems {
1624 at: Position::new(CId::new(1), 0),
1625 items: vec!['d', 'e', 'f'],
1626 },
1627 ],
1628 )
1629 .unwrap();
1630
1631 relational_linked_chunk
1632 .apply_updates(
1633 other_linked_chunk_id.as_ref(),
1634 vec![
1635 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1636 Update::PushItems {
1637 at: Position::new(CId::new(0), 0),
1638 items: vec!['x', 'y', 'z'],
1639 },
1640 ],
1641 )
1642 .unwrap();
1643
1644 let events = BTreeMap::from_iter(
1645 relational_linked_chunk.unordered_linked_chunk_items(&linked_chunk_id),
1646 );
1647
1648 assert_eq!(events.len(), 6);
1649 assert_eq!(*events.get(&'a').unwrap(), Position::new(CId::new(0), 0));
1650 assert_eq!(*events.get(&'b').unwrap(), Position::new(CId::new(0), 1));
1651 assert_eq!(*events.get(&'c').unwrap(), Position::new(CId::new(0), 2));
1652 assert_eq!(*events.get(&'d').unwrap(), Position::new(CId::new(1), 0));
1653 assert_eq!(*events.get(&'e').unwrap(), Position::new(CId::new(1), 1));
1654 assert_eq!(*events.get(&'f').unwrap(), Position::new(CId::new(1), 2));
1655 }
1656
1657 #[test]
1658 fn test_load_last_chunk() {
1659 let room_id = room_id!("!r0:matrix.org");
1660 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1661
1662 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1663
1664 {
1666 let (last_chunk, chunk_identifier_generator) =
1667 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1668
1669 assert!(last_chunk.is_none());
1670 assert_eq!(chunk_identifier_generator.current(), 0);
1671 }
1672
1673 {
1675 relational_linked_chunk
1676 .apply_updates(
1677 linked_chunk_id.as_ref(),
1678 vec![
1679 Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1680 Update::PushItems {
1681 at: Position::new(CId::new(42), 0),
1682 items: vec!['a', 'b'],
1683 },
1684 ],
1685 )
1686 .unwrap();
1687
1688 let (last_chunk, chunk_identifier_generator) =
1689 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1690
1691 assert_matches!(last_chunk, Some(last_chunk) => {
1692 assert_eq!(last_chunk.identifier, 42);
1693 assert!(last_chunk.previous.is_none());
1694 assert!(last_chunk.next.is_none());
1695 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1696 assert_eq!(items.len(), 2);
1697 assert_eq!(items, &['a', 'b']);
1698 });
1699 });
1700 assert_eq!(chunk_identifier_generator.current(), 42);
1701 }
1702
1703 {
1705 relational_linked_chunk
1706 .apply_updates(
1707 linked_chunk_id.as_ref(),
1708 vec![
1709 Update::NewItemsChunk {
1710 previous: Some(CId::new(42)),
1711 new: CId::new(7),
1712 next: None,
1713 },
1714 Update::PushItems {
1715 at: Position::new(CId::new(7), 0),
1716 items: vec!['c', 'd', 'e'],
1717 },
1718 ],
1719 )
1720 .unwrap();
1721
1722 let (last_chunk, chunk_identifier_generator) =
1723 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1724
1725 assert_matches!(last_chunk, Some(last_chunk) => {
1726 assert_eq!(last_chunk.identifier, 7);
1727 assert_matches!(last_chunk.previous, Some(previous) => {
1728 assert_eq!(previous, 42);
1729 });
1730 assert!(last_chunk.next.is_none());
1731 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1732 assert_eq!(items.len(), 3);
1733 assert_eq!(items, &['c', 'd', 'e']);
1734 });
1735 });
1736 assert_eq!(chunk_identifier_generator.current(), 42);
1737 }
1738 }
1739
1740 #[test]
1741 fn test_load_last_chunk_with_a_cycle() {
1742 let room_id = room_id!("!r0:matrix.org");
1743 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1744 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1745
1746 relational_linked_chunk
1747 .apply_updates(
1748 linked_chunk_id.as_ref(),
1749 vec![
1750 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1751 Update::NewItemsChunk {
1752 previous: Some(CId::new(0)),
1757 new: CId::new(1),
1758 next: Some(CId::new(0)),
1759 },
1760 ],
1761 )
1762 .unwrap();
1763
1764 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap_err();
1765 }
1766
1767 #[test]
1768 fn test_load_previous_chunk() {
1769 let room_id = room_id!("!r0:matrix.org");
1770 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1771 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1772
1773 {
1776 let previous_chunk = relational_linked_chunk
1777 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(153))
1778 .unwrap();
1779
1780 assert!(previous_chunk.is_none());
1781 }
1782
1783 {
1786 relational_linked_chunk
1787 .apply_updates(
1788 linked_chunk_id.as_ref(),
1789 vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
1790 )
1791 .unwrap();
1792
1793 let previous_chunk = relational_linked_chunk
1794 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1795 .unwrap();
1796
1797 assert!(previous_chunk.is_none());
1798 }
1799
1800 {
1802 relational_linked_chunk
1803 .apply_updates(
1804 linked_chunk_id.as_ref(),
1805 vec![
1806 Update::NewItemsChunk {
1808 previous: None,
1809 new: CId::new(7),
1810 next: Some(CId::new(42)),
1811 },
1812 Update::PushItems {
1813 at: Position::new(CId::new(7), 0),
1814 items: vec!['a', 'b', 'c'],
1815 },
1816 ],
1817 )
1818 .unwrap();
1819
1820 let previous_chunk = relational_linked_chunk
1821 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1822 .unwrap();
1823
1824 assert_matches!(previous_chunk, Some(previous_chunk) => {
1825 assert_eq!(previous_chunk.identifier, 7);
1826 assert!(previous_chunk.previous.is_none());
1827 assert_matches!(previous_chunk.next, Some(next) => {
1828 assert_eq!(next, 42);
1829 });
1830 assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
1831 assert_eq!(items.len(), 3);
1832 assert_eq!(items, &['a', 'b', 'c']);
1833 });
1834 });
1835 }
1836 }
1837}