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